BridgeService.kt (10087B) - raw


      1 package me.rhunk.snapenhance.bridge
      2 
      3 import android.app.Service
      4 import android.content.Intent
      5 import android.os.IBinder
      6 import android.os.ParcelFileDescriptor
      7 import kotlinx.coroutines.runBlocking
      8 import me.rhunk.snapenhance.RemoteSideContext
      9 import me.rhunk.snapenhance.SharedContextHolder
     10 import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge
     11 import me.rhunk.snapenhance.common.data.MessagingFriendInfo
     12 import me.rhunk.snapenhance.common.data.MessagingGroupInfo
     13 import me.rhunk.snapenhance.common.data.SocialScope
     14 import me.rhunk.snapenhance.common.logger.LogLevel
     15 import me.rhunk.snapenhance.common.ui.OverlayType
     16 import me.rhunk.snapenhance.common.util.toParcelable
     17 import me.rhunk.snapenhance.download.DownloadProcessor
     18 import me.rhunk.snapenhance.download.FFMpegProcessor
     19 import me.rhunk.snapenhance.storage.*
     20 import me.rhunk.snapenhance.task.Task
     21 import me.rhunk.snapenhance.task.TaskType
     22 import java.io.File
     23 import java.util.UUID
     24 import kotlin.system.measureTimeMillis
     25 
     26 class BridgeService : Service() {
     27     private lateinit var remoteSideContext: RemoteSideContext
     28     lateinit var syncCallback: SyncCallback
     29     var messagingBridge: MessagingBridge? = null
     30 
     31     override fun onDestroy() {
     32         if (::remoteSideContext.isInitialized) {
     33             remoteSideContext.bridgeService = null
     34         }
     35     }
     36 
     37     override fun onBind(intent: Intent): IBinder? {
     38         remoteSideContext = SharedContextHolder.remote(this).apply {
     39             if (checkForRequirements()) return null
     40         }
     41         remoteSideContext.apply {
     42             bridgeService = this@BridgeService
     43         }
     44         return BridgeBinder()
     45     }
     46 
     47     fun triggerScopeSync(scope: SocialScope, id: String, updateOnly: Boolean = false) {
     48         runCatching {
     49             if (!syncCallback.asBinder().pingBinder()) {
     50                 remoteSideContext.log.warn("Failed to sync $scope $id: Callback is dead")
     51                 return
     52             }
     53             val modDatabase = remoteSideContext.database
     54             val syncedObject = when (scope) {
     55                 SocialScope.FRIEND -> {
     56                     if (updateOnly && modDatabase.getFriendInfo(id) == null) return
     57                     syncCallback.syncFriend(id)
     58                 }
     59                 SocialScope.GROUP -> {
     60                     if (updateOnly && modDatabase.getGroupInfo(id) == null) return
     61                     syncCallback.syncGroup(id)
     62                 }
     63                 else -> null
     64             }
     65 
     66             if (syncedObject == null) {
     67                 remoteSideContext.log.warn("Failed to sync $scope $id")
     68                 return
     69             }
     70 
     71             when (scope) {
     72                 SocialScope.FRIEND -> {
     73                     toParcelable<MessagingFriendInfo>(syncedObject)?.let {
     74                         modDatabase.syncFriend(it)
     75                     }
     76                 }
     77                 SocialScope.GROUP -> {
     78                     toParcelable<MessagingGroupInfo>(syncedObject)?.let {
     79                         modDatabase.syncGroupInfo(it)
     80                     }
     81                 }
     82             }
     83         }.onFailure {
     84             remoteSideContext.log.error("Failed to sync $scope $id", it)
     85         }
     86     }
     87 
     88     inner class BridgeBinder : BridgeInterface.Stub() {
     89         override fun getApplicationApkPath(): String = applicationInfo.publicSourceDir
     90 
     91         override fun broadcastLog(tag: String, level: String, message: String) {
     92             remoteSideContext.log.internalLog(tag, LogLevel.fromShortName(level) ?: LogLevel.INFO, message)
     93         }
     94         override fun enqueueDownload(intent: Intent, callback: DownloadCallback) {
     95             DownloadProcessor(
     96                 remoteSideContext = remoteSideContext,
     97                 callback = callback
     98             ).onReceive(intent)
     99         }
    100 
    101         override fun convertMedia(
    102             input: ParcelFileDescriptor?,
    103             inputExtension: String,
    104             outputExtension: String,
    105             audioCodec: String?,
    106             videoCodec: String?
    107         ): ParcelFileDescriptor? {
    108             return runBlocking {
    109                 val taskId = UUID.randomUUID().toString()
    110                 val inputFile = File.createTempFile(taskId, ".$inputExtension", remoteSideContext.androidContext.cacheDir)
    111 
    112                 runCatching {
    113                     ParcelFileDescriptor.AutoCloseInputStream(input).use { inputStream ->
    114                         inputFile.outputStream().use { outputStream ->
    115                             inputStream.copyTo(outputStream)
    116                         }
    117                     }
    118                 }.onFailure {
    119                     remoteSideContext.log.error("Failed to copy input file", it)
    120                     inputFile.delete()
    121                     return@runBlocking null
    122                 }
    123                 val cachedFile = File.createTempFile(taskId, ".$outputExtension", remoteSideContext.androidContext.cacheDir)
    124 
    125                 val pendingTask = remoteSideContext.taskManager.createPendingTask(
    126                     Task(
    127                         type = TaskType.DOWNLOAD,
    128                         title = "Media conversion",
    129                         author = null,
    130                         hash = taskId
    131                     )
    132                 )
    133                 runCatching {
    134                     FFMpegProcessor.newFFMpegProcessor(remoteSideContext, pendingTask).execute(
    135                         FFMpegProcessor.Request(
    136                             action = FFMpegProcessor.Action.CONVERSION,
    137                             inputs = listOf(inputFile.absolutePath),
    138                             output = cachedFile,
    139                             videoCodec = videoCodec,
    140                             audioCodec = audioCodec
    141                         )
    142                     )
    143                     pendingTask.success()
    144                     return@runBlocking ParcelFileDescriptor.open(cachedFile, ParcelFileDescriptor.MODE_READ_ONLY)
    145                 }.onFailure {
    146                     pendingTask.fail(it.message ?: "Failed to convert video")
    147                     remoteSideContext.log.error("Failed to convert video", it)
    148                 }
    149 
    150                 inputFile.delete()
    151                 cachedFile.delete()
    152                 null
    153             }
    154         }
    155 
    156         override fun getRules(uuid: String): List<String> {
    157             return remoteSideContext.database.getRules(uuid).map { it.key }
    158         }
    159 
    160         override fun getRuleIds(type: String): MutableList<String> {
    161             return remoteSideContext.database.getRuleIds(type)
    162         }
    163 
    164         override fun setRule(uuid: String, rule: String, state: Boolean) {
    165             remoteSideContext.database.setRule(uuid, rule, state)
    166         }
    167 
    168         override fun sync(callback: SyncCallback) {
    169             syncCallback = callback
    170             measureTimeMillis {
    171                 remoteSideContext.database.getFriends().map { it.userId } .forEach { friendId ->
    172                     triggerScopeSync(SocialScope.FRIEND, friendId, true)
    173                 }
    174                 remoteSideContext.database.getGroups().map { it.conversationId }.forEach { groupId ->
    175                     triggerScopeSync(SocialScope.GROUP, groupId, true)
    176                 }
    177             }.also {
    178                 remoteSideContext.log.verbose("Syncing remote took $it ms")
    179             }
    180         }
    181 
    182         override fun triggerSync(scope: String, id: String) {
    183             remoteSideContext.log.verbose("trigger sync for $scope $id")
    184             triggerScopeSync(SocialScope.getByName(scope), id, true)
    185         }
    186 
    187         override fun passGroupsAndFriends(
    188             groups: List<String>,
    189             friends: List<String>
    190         ) {
    191             remoteSideContext.log.verbose("Received ${groups.size} groups and ${friends.size} friends")
    192             remoteSideContext.database.receiveMessagingDataCallback(
    193                 friends.mapNotNull { toParcelable<MessagingFriendInfo>(it) },
    194                 groups.mapNotNull { toParcelable<MessagingGroupInfo>(it) }
    195             )
    196         }
    197 
    198         override fun getScopeNotes(id: String): String? {
    199             return remoteSideContext.database.getScopeNotes(id)
    200         }
    201 
    202         override fun setScopeNotes(id: String, content: String?) {
    203             remoteSideContext.database.setScopeNotes(id, content)
    204         }
    205 
    206         override fun getScriptingInterface() = remoteSideContext.scriptManager
    207 
    208         override fun getE2eeInterface() = remoteSideContext.e2eeImplementation
    209         override fun getLogger() = remoteSideContext.messageLogger
    210         override fun getTracker() = remoteSideContext.tracker
    211         override fun getAccountStorage() = remoteSideContext.accountStorage
    212         override fun getFileHandleManager() = remoteSideContext.fileHandleManager
    213         override fun getLocationManager() = remoteSideContext.locationManager
    214 
    215         override fun registerMessagingBridge(bridge: MessagingBridge) {
    216             messagingBridge = bridge
    217         }
    218 
    219         override fun openOverlay(type: String) {
    220             runCatching {
    221                 val overlayType = OverlayType.fromKey(type) ?: throw IllegalArgumentException("Unknown overlay type: $type")
    222                 remoteSideContext.remoteOverlay.show { routes ->
    223                     when (overlayType) {
    224                         OverlayType.SETTINGS -> routes.features
    225                         OverlayType.BETTER_LOCATION -> routes.betterLocation
    226                     }
    227                 }
    228             }.onFailure {
    229                 remoteSideContext.log.error("Failed to open $type overlay", it)
    230             }
    231         }
    232 
    233         override fun closeOverlay() {
    234             runCatching {
    235                 remoteSideContext.remoteOverlay.close()
    236             }.onFailure {
    237                 remoteSideContext.log.error("Failed to close overlay", it)
    238             }
    239         }
    240 
    241         override fun registerConfigStateListener(listener: ConfigStateListener) {
    242             remoteSideContext.config.configStateListener = listener
    243         }
    244 
    245         override fun getDebugProp(key: String, defaultValue: String?): String? {
    246             return remoteSideContext.sharedPreferences.all["debug_$key"]?.toString() ?: defaultValue
    247         }
    248     }
    249 }