RemoteSideContext.kt (10390B) - 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     val remoteSharedLibraryManager = RemoteSharedLibraryManager(this)
     82 
     83     //used to load bitmoji selfies and download previews
     84     val imageLoader by lazy {
     85         ImageLoader.Builder(androidContext)
     86             .dispatcher(Dispatchers.IO)
     87             .memoryCache {
     88                 MemoryCache.Builder(androidContext)
     89                     .maxSizePercent(0.25)
     90                     .build()
     91             }
     92             .diskCache {
     93                 DiskCache.Builder()
     94                     .directory(androidContext.cacheDir.resolve("coil-disk-cache"))
     95                     .maxSizeBytes(1024 * 1024 * 100) // 100MB
     96                     .build()
     97             }
     98             .components { add(VideoFrameDecoder.Factory()) }.build()
     99     }
    100 
    101     val gson: Gson by lazy { GsonBuilder().setPrettyPrinting().create() }
    102 
    103     fun reload() {
    104         runCatching {
    105             runBlocking(Dispatchers.IO) {
    106                 log.init()
    107                 log.verbose("Loading RemoteSideContext")
    108                 config.load()
    109                 launch {
    110                     mappings.apply {
    111                         init(androidContext)
    112                     }
    113                 }
    114                 translation.apply {
    115                     userLocale = config.locale
    116                     load()
    117                 }
    118                 database.init()
    119                 streaksReminder.init()
    120                 scriptManager.init()
    121                 launch {
    122                     taskManager.init()
    123                     config.root.messaging.messageLogger.takeIf {
    124                         it.globalState == true
    125                     }?.autoPurge?.let { getPurgeTime(it.getNullable()) }?.let {
    126                         messageLogger.purgeAll(it)
    127                     }
    128 
    129                     config.root.friendTracker.takeIf {
    130                         it.globalState == true
    131                     }?.autoPurge?.let { getPurgeTime(it.getNullable()) }?.let {
    132                         messageLogger.purgeTrackerLogs(it)
    133                     }
    134                 }
    135                 coroutineScope.launch {
    136                     runCatching {
    137                         remoteSharedLibraryManager.init()
    138                     }.onFailure {
    139                         log.error("Failed to init RemoteSharedLibraryManager", it)
    140                     }
    141                 }
    142             }
    143         }.onFailure {
    144             log.error("Failed to load RemoteSideContext", it)
    145             androidContext.fatalCrash(it)
    146         }
    147 
    148         scriptManager.runtime.eachModule {
    149             callFunction("module.onSnapEnhanceLoad", androidContext)
    150         }
    151     }
    152 
    153     val installationSummary by lazy {
    154         InstallationSummary(
    155             snapchatInfo = mappings.getSnapchatPackageInfo()?.let {
    156                 SnapchatAppInfo(
    157                     packageName = it.packageName,
    158                     version = it.versionName,
    159                     versionCode = it.longVersionCode,
    160                     isLSPatched = it.applicationInfo.appComponentFactory != CoreComponentFactory::class.java.name,
    161                     isSplitApk = it.splitNames?.isNotEmpty() ?: false
    162                 )
    163             },
    164             modInfo = ModInfo(
    165                 loaderPackageName = MainActivity::class.java.`package`?.name,
    166                 buildPackageName = androidContext.packageName,
    167                 buildVersion = BuildConfig.VERSION_NAME,
    168                 buildVersionCode = BuildConfig.VERSION_CODE.toLong(),
    169                 buildIssuer = androidContext.packageManager.getPackageInfo(androidContext.packageName, PackageManager.GET_SIGNING_CERTIFICATES)
    170                     ?.signingInfo?.apkContentsSigners?.firstOrNull()?.let {
    171                         val certFactory = CertificateFactory.getInstance("X509")
    172                         val cert = certFactory.generateCertificate(ByteArrayInputStream(it.toByteArray())) as X509Certificate
    173                         cert.issuerDN.toString()
    174                     } ?: throw Exception("Failed to get certificate info"),
    175                 gitHash = BuildConfig.GIT_HASH,
    176                 isDebugBuild = BuildConfig.DEBUG,
    177                 mappingVersion = mappings.getGeneratedBuildNumber(),
    178                 mappingsOutdated = mappings.isMappingsOutdated()
    179             ),
    180             platformInfo = PlatformInfo(
    181                 device = Build.DEVICE,
    182                 androidVersion = Build.VERSION.RELEASE,
    183                 systemAbi = Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"
    184             )
    185         )
    186     }
    187 
    188     fun longToast(message: Any) {
    189         androidContext.mainExecutor.execute {
    190             Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show()
    191         }
    192         log.debug(message.toString())
    193     }
    194 
    195     fun shortToast(message: Any) {
    196         androidContext.mainExecutor.execute {
    197             Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show()
    198         }
    199         log.debug(message.toString())
    200     }
    201 
    202     fun hasMessagingBridge() = bridgeService != null && bridgeService?.messagingBridge != null && bridgeService?.messagingBridge?.asBinder()?.pingBinder() == true
    203 
    204     fun checkForRequirements(overrideRequirements: Int? = null): Boolean {
    205         var requirements = overrideRequirements ?: 0
    206         if (!config.wasPresent) {
    207             requirements = requirements or Requirements.FIRST_RUN
    208         }
    209 
    210         config.root.downloader.saveFolder.get().let {
    211             if (it.isEmpty() || run {
    212                     val documentFile = runCatching { DocumentFile.fromTreeUri(androidContext, Uri.parse(it)) }.getOrNull()
    213                     documentFile == null || !documentFile.exists() || !documentFile.canWrite()
    214                 }) {
    215                 requirements = requirements or Requirements.SAVE_FOLDER
    216             }
    217         }
    218 
    219         if (!sharedPreferences.getBoolean("debug_disable_mapper", false) && mappings.getSnapchatPackageInfo() != null && mappings.isMappingsOutdated()) {
    220             requirements = requirements or Requirements.MAPPINGS
    221         }
    222 
    223         if (sharedPreferences.getString("sif", null) == null) {
    224             requirements = requirements or Requirements.SIF
    225         }
    226 
    227         if (requirements == 0) return false
    228 
    229         val currentContext = activity ?: androidContext
    230 
    231         Intent(currentContext, SetupActivity::class.java).apply {
    232             putExtra("requirements", requirements)
    233             if (currentContext !is Activity) {
    234                 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    235             }
    236             currentContext.startActivity(this)
    237             return true
    238         }
    239     }
    240 
    241     fun launchActionIntent(action: EnumAction) {
    242         val intent = androidContext.packageManager.getLaunchIntentForPackage(
    243             Constants.SNAPCHAT_PACKAGE_NAME
    244         )
    245         if (intent == null) {
    246             shortToast("Can't execute action: Snapchat is not installed")
    247             return
    248         }
    249         intent.putExtra(EnumAction.ACTION_PARAMETER, action.key)
    250         androidContext.startActivity(intent)
    251     }
    252 }