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 }