RemoteSideContext.kt (9896B) - raw


      1 package me.rhunk.snapenhance
      2 
      3 import android.app.Activity
      4 import android.content.Context
      5 import android.content.Intent
      6 import android.content.SharedPreferences
      7 import android.content.pm.PackageManager
      8 import android.net.Uri
      9 import android.os.Build
     10 import android.widget.Toast
     11 import androidx.activity.ComponentActivity
     12 import androidx.core.app.CoreComponentFactory
     13 import androidx.documentfile.provider.DocumentFile
     14 import coil.ImageLoader
     15 import coil.decode.VideoFrameDecoder
     16 import coil.disk.DiskCache
     17 import coil.memory.MemoryCache
     18 import com.google.gson.Gson
     19 import com.google.gson.GsonBuilder
     20 import kotlinx.coroutines.CoroutineScope
     21 import kotlinx.coroutines.Dispatchers
     22 import kotlinx.coroutines.launch
     23 import kotlinx.coroutines.runBlocking
     24 import me.rhunk.snapenhance.bridge.BridgeService
     25 import me.rhunk.snapenhance.common.BuildConfig
     26 import me.rhunk.snapenhance.common.Constants
     27 import me.rhunk.snapenhance.common.action.EnumAction
     28 import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
     29 import me.rhunk.snapenhance.common.bridge.wrapper.LoggerWrapper
     30 import me.rhunk.snapenhance.common.bridge.wrapper.MappingsWrapper
     31 import me.rhunk.snapenhance.common.config.ModConfig
     32 import me.rhunk.snapenhance.common.logger.fatalCrash
     33 import me.rhunk.snapenhance.common.util.constantLazyBridge
     34 import me.rhunk.snapenhance.common.util.getPurgeTime
     35 import me.rhunk.snapenhance.e2ee.E2EEImplementation
     36 import me.rhunk.snapenhance.scripting.RemoteScriptManager
     37 import me.rhunk.snapenhance.storage.AppDatabase
     38 import me.rhunk.snapenhance.task.TaskManager
     39 import me.rhunk.snapenhance.ui.manager.MainActivity
     40 import me.rhunk.snapenhance.ui.manager.data.InstallationSummary
     41 import me.rhunk.snapenhance.ui.manager.data.ModInfo
     42 import me.rhunk.snapenhance.ui.manager.data.PlatformInfo
     43 import me.rhunk.snapenhance.ui.manager.data.SnapchatAppInfo
     44 import me.rhunk.snapenhance.ui.overlay.RemoteOverlay
     45 import me.rhunk.snapenhance.ui.setup.Requirements
     46 import me.rhunk.snapenhance.ui.setup.SetupActivity
     47 import java.io.ByteArrayInputStream
     48 import java.lang.ref.WeakReference
     49 import java.security.cert.CertificateFactory
     50 import java.security.cert.X509Certificate
     51 
     52 
     53 class RemoteSideContext(
     54     val androidContext: Context
     55 ) {
     56     val coroutineScope = CoroutineScope(Dispatchers.IO)
     57 
     58     private var _activity: WeakReference<ComponentActivity>? = null
     59     var bridgeService: BridgeService? = null
     60 
     61     var activity: ComponentActivity?
     62         get() = _activity?.get()
     63         set(value) { _activity?.clear(); _activity = WeakReference(value) }
     64 
     65     val sharedPreferences: SharedPreferences get() = androidContext.getSharedPreferences("prefs", 0)
     66     val fileHandleManager = RemoteFileHandleManager(this)
     67     val config = ModConfig(androidContext, constantLazyBridge { fileHandleManager })
     68     val translation = LocaleWrapper(constantLazyBridge { fileHandleManager })
     69     val mappings = MappingsWrapper(constantLazyBridge { fileHandleManager })
     70     val taskManager = TaskManager(this)
     71     val database = AppDatabase(this)
     72     val streaksReminder = StreaksReminder(this)
     73     val log = LogManager(this)
     74     val scriptManager = RemoteScriptManager(this)
     75     val remoteOverlay = RemoteOverlay(this)
     76     val e2eeImplementation = E2EEImplementation(this)
     77     val messageLogger by lazy { LoggerWrapper(androidContext) }
     78     val tracker = RemoteTracker(this)
     79     val accountStorage = RemoteAccountStorage(this)
     80     val locationManager = RemoteLocationManager(this)
     81 
     82     //used to load bitmoji selfies and download previews
     83     val imageLoader by lazy {
     84         ImageLoader.Builder(androidContext)
     85             .dispatcher(Dispatchers.IO)
     86             .memoryCache {
     87                 MemoryCache.Builder(androidContext)
     88                     .maxSizePercent(0.25)
     89                     .build()
     90             }
     91             .diskCache {
     92                 DiskCache.Builder()
     93                     .directory(androidContext.cacheDir.resolve("coil-disk-cache"))
     94                     .maxSizeBytes(1024 * 1024 * 100) // 100MB
     95                     .build()
     96             }
     97             .components { add(VideoFrameDecoder.Factory()) }.build()
     98     }
     99 
    100     val gson: Gson by lazy { GsonBuilder().setPrettyPrinting().create() }
    101 
    102     fun reload() {
    103         runCatching {
    104             runBlocking(Dispatchers.IO) {
    105                 log.init()
    106                 log.verbose("Loading RemoteSideContext")
    107                 config.load()
    108                 launch {
    109                     mappings.apply {
    110                         init(androidContext)
    111                     }
    112                 }
    113                 translation.apply {
    114                     userLocale = config.locale
    115                     load()
    116                 }
    117                 database.init()
    118                 streaksReminder.init()
    119                 scriptManager.init()
    120                 launch {
    121                     taskManager.init()
    122                     config.root.messaging.messageLogger.takeIf {
    123                         it.globalState == true
    124                     }?.autoPurge?.let { getPurgeTime(it.getNullable()) }?.let {
    125                         messageLogger.purgeAll(it)
    126                     }
    127 
    128                     config.root.friendTracker.takeIf {
    129                         it.globalState == true
    130                     }?.autoPurge?.let { getPurgeTime(it.getNullable()) }?.let {
    131                         messageLogger.purgeTrackerLogs(it)
    132                     }
    133                 }
    134             }
    135         }.onFailure {
    136             log.error("Failed to load RemoteSideContext", it)
    137             androidContext.fatalCrash(it)
    138         }
    139 
    140         scriptManager.runtime.eachModule {
    141             callFunction("module.onSnapEnhanceLoad", androidContext)
    142         }
    143     }
    144 
    145     val installationSummary by lazy {
    146         InstallationSummary(
    147             snapchatInfo = mappings.getSnapchatPackageInfo()?.let {
    148                 SnapchatAppInfo(
    149                     packageName = it.packageName,
    150                     version = it.versionName,
    151                     versionCode = it.longVersionCode,
    152                     isLSPatched = it.applicationInfo.appComponentFactory != CoreComponentFactory::class.java.name,
    153                     isSplitApk = it.splitNames?.isNotEmpty() ?: false
    154                 )
    155             },
    156             modInfo = ModInfo(
    157                 loaderPackageName = MainActivity::class.java.`package`?.name,
    158                 buildPackageName = androidContext.packageName,
    159                 buildVersion = BuildConfig.VERSION_NAME,
    160                 buildVersionCode = BuildConfig.VERSION_CODE.toLong(),
    161                 buildIssuer = androidContext.packageManager.getPackageInfo(androidContext.packageName, PackageManager.GET_SIGNING_CERTIFICATES)
    162                     ?.signingInfo?.apkContentsSigners?.firstOrNull()?.let {
    163                         val certFactory = CertificateFactory.getInstance("X509")
    164                         val cert = certFactory.generateCertificate(ByteArrayInputStream(it.toByteArray())) as X509Certificate
    165                         cert.issuerDN.toString()
    166                     } ?: throw Exception("Failed to get certificate info"),
    167                 gitHash = BuildConfig.GIT_HASH,
    168                 isDebugBuild = BuildConfig.DEBUG,
    169                 mappingVersion = mappings.getGeneratedBuildNumber(),
    170                 mappingsOutdated = mappings.isMappingsOutdated()
    171             ),
    172             platformInfo = PlatformInfo(
    173                 device = Build.DEVICE,
    174                 androidVersion = Build.VERSION.RELEASE,
    175                 systemAbi = Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"
    176             )
    177         )
    178     }
    179 
    180     fun longToast(message: Any) {
    181         androidContext.mainExecutor.execute {
    182             Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show()
    183         }
    184         log.debug(message.toString())
    185     }
    186 
    187     fun shortToast(message: Any) {
    188         androidContext.mainExecutor.execute {
    189             Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show()
    190         }
    191         log.debug(message.toString())
    192     }
    193 
    194     fun hasMessagingBridge() = bridgeService != null && bridgeService?.messagingBridge != null && bridgeService?.messagingBridge?.asBinder()?.pingBinder() == true
    195 
    196     fun checkForRequirements(overrideRequirements: Int? = null): Boolean {
    197         var requirements = overrideRequirements ?: 0
    198         if (!config.wasPresent) {
    199             requirements = requirements or Requirements.FIRST_RUN
    200         }
    201 
    202         config.root.downloader.saveFolder.get().let {
    203             if (it.isEmpty() || run {
    204                     val documentFile = runCatching { DocumentFile.fromTreeUri(androidContext, Uri.parse(it)) }.getOrNull()
    205                     documentFile == null || !documentFile.exists() || !documentFile.canWrite()
    206                 }) {
    207                 requirements = requirements or Requirements.SAVE_FOLDER
    208             }
    209         }
    210 
    211         if (!sharedPreferences.getBoolean("debug_disable_mapper", false) && mappings.getSnapchatPackageInfo() != null && mappings.isMappingsOutdated()) {
    212             requirements = requirements or Requirements.MAPPINGS
    213         }
    214 
    215         if (requirements == 0) return false
    216 
    217         val currentContext = activity ?: androidContext
    218 
    219         Intent(currentContext, SetupActivity::class.java).apply {
    220             putExtra("requirements", requirements)
    221             if (currentContext !is Activity) {
    222                 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    223             }
    224             currentContext.startActivity(this)
    225             return true
    226         }
    227     }
    228 
    229     fun launchActionIntent(action: EnumAction) {
    230         val intent = androidContext.packageManager.getLaunchIntentForPackage(
    231             Constants.SNAPCHAT_PACKAGE_NAME
    232         )
    233         if (intent == null) {
    234             shortToast("Can't execute action: Snapchat is not installed")
    235             return
    236         }
    237         intent.putExtra(EnumAction.ACTION_PARAMETER, action.key)
    238         androidContext.startActivity(intent)
    239     }
    240 }