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 }