SnapEnhance/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt
rhunk 3e9c97c18c feat(manager): conversation preview (wip)
- add messaging bridge
- refactor export chat messages
2023-10-14 18:56:16 +02:00

204 lines
8.0 KiB
Kotlin

package me.rhunk.snapenhance
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.core.app.CoreComponentFactory
import androidx.documentfile.provider.DocumentFile
import coil.ImageLoader
import coil.decode.VideoFrameDecoder
import coil.disk.DiskCache
import coil.memory.MemoryCache
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import me.rhunk.snapenhance.bridge.BridgeService
import me.rhunk.snapenhance.common.BuildConfig
import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.common.bridge.wrapper.MappingsWrapper
import me.rhunk.snapenhance.common.config.ModConfig
import me.rhunk.snapenhance.download.DownloadTaskManager
import me.rhunk.snapenhance.e2ee.E2EEImplementation
import me.rhunk.snapenhance.messaging.ModDatabase
import me.rhunk.snapenhance.messaging.StreaksReminder
import me.rhunk.snapenhance.scripting.RemoteScriptManager
import me.rhunk.snapenhance.ui.manager.MainActivity
import me.rhunk.snapenhance.ui.manager.data.InstallationSummary
import me.rhunk.snapenhance.ui.manager.data.ModInfo
import me.rhunk.snapenhance.ui.manager.data.PlatformInfo
import me.rhunk.snapenhance.ui.manager.data.SnapchatAppInfo
import me.rhunk.snapenhance.ui.overlay.SettingsOverlay
import me.rhunk.snapenhance.ui.setup.Requirements
import me.rhunk.snapenhance.ui.setup.SetupActivity
import java.io.ByteArrayInputStream
import java.lang.ref.WeakReference
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
class RemoteSideContext(
val androidContext: Context
) {
val coroutineScope = CoroutineScope(Dispatchers.IO)
private var _activity: WeakReference<ComponentActivity>? = null
var bridgeService: BridgeService? = null
var activity: ComponentActivity?
get() = _activity?.get()
set(value) { _activity?.clear(); _activity = WeakReference(value) }
val config = ModConfig()
val translation = LocaleWrapper()
val mappings = MappingsWrapper()
val downloadTaskManager = DownloadTaskManager()
val modDatabase = ModDatabase(this)
val streaksReminder = StreaksReminder(this)
val log = LogManager(this)
val scriptManager = RemoteScriptManager(this)
val settingsOverlay = SettingsOverlay(this)
val e2eeImplementation = E2EEImplementation(this)
//used to load bitmoji selfies and download previews
val imageLoader by lazy {
ImageLoader.Builder(androidContext)
.dispatcher(Dispatchers.IO)
.memoryCache {
MemoryCache.Builder(androidContext)
.maxSizePercent(0.25)
.build()
}
.diskCache {
DiskCache.Builder()
.directory(androidContext.cacheDir.resolve("coil-disk-cache"))
.maxSizeBytes(1024 * 1024 * 100) // 100MB
.build()
}
.components { add(VideoFrameDecoder.Factory()) }.build()
}
val gson: Gson by lazy { GsonBuilder().setPrettyPrinting().create() }
fun reload() {
log.verbose("Loading RemoteSideContext")
runCatching {
config.loadFromContext(androidContext)
translation.apply {
userLocale = config.locale
loadFromContext(androidContext)
}
mappings.apply {
loadFromContext(androidContext)
init(androidContext)
}
downloadTaskManager.init(androidContext)
modDatabase.init()
streaksReminder.init()
scriptManager.init()
}.onFailure {
log.error("Failed to load RemoteSideContext", it)
}
scriptManager.runtime.eachModule {
callFunction("module.onManagerLoad",androidContext)
}
}
val installationSummary by lazy {
InstallationSummary(
snapchatInfo = mappings.getSnapchatPackageInfo()?.let {
SnapchatAppInfo(
packageName = it.packageName,
version = it.versionName,
versionCode = it.longVersionCode,
isLSPatched = it.applicationInfo.appComponentFactory != CoreComponentFactory::class.java.name,
isSplitApk = it.splitNames?.isNotEmpty() ?: false
)
},
modInfo = ModInfo(
loaderPackageName = MainActivity::class.java.`package`?.name,
buildPackageName = BuildConfig.APPLICATION_ID,
buildVersion = BuildConfig.VERSION_NAME,
buildVersionCode = BuildConfig.VERSION_CODE.toLong(),
buildIssuer = androidContext.packageManager.getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_SIGNING_CERTIFICATES)
?.signingInfo?.apkContentsSigners?.firstOrNull()?.let {
val certFactory = CertificateFactory.getInstance("X509")
val cert = certFactory.generateCertificate(ByteArrayInputStream(it.toByteArray())) as X509Certificate
cert.issuerDN.toString()
} ?: throw Exception("Failed to get certificate info"),
isDebugBuild = BuildConfig.DEBUG,
mappingVersion = mappings.getGeneratedBuildNumber(),
mappingsOutdated = mappings.isMappingsOutdated()
),
platformInfo = PlatformInfo(
device = Build.DEVICE,
androidVersion = Build.VERSION.RELEASE,
systemAbi = Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"
)
)
}
fun longToast(message: Any) {
androidContext.mainExecutor.execute {
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show()
}
log.debug(message.toString())
}
fun shortToast(message: Any) {
androidContext.mainExecutor.execute {
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show()
}
log.debug(message.toString())
}
fun hasMessagingBridge() = bridgeService != null && bridgeService?.messagingBridge != null
fun checkForRequirements(overrideRequirements: Int? = null): Boolean {
var requirements = overrideRequirements ?: 0
if(BuildConfig.DEBUG) {
val unixTime = System.currentTimeMillis() / 1000 //unix time in seconds cuz cool
if(BuildConfig.BUILD_DATE + 604800 < unixTime.toInt()) {
Toast.makeText(androidContext, "This SnapEnhance build has expired.", Toast.LENGTH_LONG).show();
throw RuntimeException("This build has expired. This crash is intentional.")
}
}
if (!config.wasPresent) {
requirements = requirements or Requirements.FIRST_RUN
}
config.root.downloader.saveFolder.get().let {
if (it.isEmpty() || run {
val documentFile = runCatching { DocumentFile.fromTreeUri(androidContext, Uri.parse(it)) }.getOrNull()
documentFile == null || !documentFile.exists() || !documentFile.canWrite()
}) {
requirements = requirements or Requirements.SAVE_FOLDER
}
}
if (mappings.isMappingsOutdated() || !mappings.isMappingsLoaded()) {
requirements = requirements or Requirements.MAPPINGS
}
if (requirements == 0) return false
val currentContext = activity ?: androidContext
Intent(currentContext, SetupActivity::class.java).apply {
putExtra("requirements", requirements)
if (currentContext !is Activity) {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
currentContext.startActivity(this)
return true
}
}
}