From a8f502ef4f3460b8bb127d01d430f24a08259156 Mon Sep 17 00:00:00 2001
From: kr328 <kr328app@outlook.com>
Date: Tue, 25 May 2021 20:28:40 +0800
Subject: [PATCH] Improve: merge ClashManager and ProfileService

---
 .../github/kr328/clash/AppCrashedActivity.kt  |  2 -
 .../com/github/kr328/clash/LogcatService.kt   |  8 +-
 .../github/kr328/clash/log/SystemLogcat.kt    |  3 +-
 .../com/github/kr328/clash/remote/Remote.kt   |  6 +-
 .../com/github/kr328/clash/remote/Service.kt  | 64 ++++++++++++++
 .../com/github/kr328/clash/remote/Services.kt | 88 -------------------
 .../com/github/kr328/clash/util/Remote.kt     | 10 ++-
 service/src/main/AndroidManifest.xml          |  6 +-
 .../kr328/clash/service/ClashManager.kt       | 17 ++--
 .../{ProfileService.kt => ProfileManager.kt}  | 50 +++++------
 .../kr328/clash/service/RemoteService.kt      | 46 ++++++++++
 .../clash/service/remote/IRemoteService.kt    |  9 ++
 12 files changed, 161 insertions(+), 148 deletions(-)
 create mode 100644 app/src/main/java/com/github/kr328/clash/remote/Service.kt
 delete mode 100644 app/src/main/java/com/github/kr328/clash/remote/Services.kt
 rename service/src/main/java/com/github/kr328/clash/service/{ProfileService.kt => ProfileManager.kt} (81%)
 create mode 100644 service/src/main/java/com/github/kr328/clash/service/RemoteService.kt
 create mode 100644 service/src/main/java/com/github/kr328/clash/service/remote/IRemoteService.kt

diff --git a/app/src/main/java/com/github/kr328/clash/AppCrashedActivity.kt b/app/src/main/java/com/github/kr328/clash/AppCrashedActivity.kt
index 374c6fa9..0a0a2044 100644
--- a/app/src/main/java/com/github/kr328/clash/AppCrashedActivity.kt
+++ b/app/src/main/java/com/github/kr328/clash/AppCrashedActivity.kt
@@ -24,8 +24,6 @@ class AppCrashedActivity : BaseActivity<AppCrashedDesign>() {
             SystemLogcat.dumpCrash()
         }
 
-        Tracker.uploadLogcat(logs)
-
         design.setAppLogs(logs)
 
         while (isActive) {
diff --git a/app/src/main/java/com/github/kr328/clash/LogcatService.kt b/app/src/main/java/com/github/kr328/clash/LogcatService.kt
index 11a75757..4484f86a 100644
--- a/app/src/main/java/com/github/kr328/clash/LogcatService.kt
+++ b/app/src/main/java/com/github/kr328/clash/LogcatService.kt
@@ -20,9 +20,9 @@ import com.github.kr328.clash.common.util.intent
 import com.github.kr328.clash.core.model.LogMessage
 import com.github.kr328.clash.log.LogcatCache
 import com.github.kr328.clash.log.LogcatWriter
-import com.github.kr328.clash.service.ClashManager
-import com.github.kr328.clash.service.remote.IClashManager
+import com.github.kr328.clash.service.RemoteService
 import com.github.kr328.clash.service.remote.ILogObserver
+import com.github.kr328.clash.service.remote.IRemoteService
 import com.github.kr328.clash.service.remote.unwrap
 import com.github.kr328.clash.util.logsDir
 import kotlinx.coroutines.*
@@ -52,7 +52,7 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
 
         showNotification()
 
-        bindService(ClashManager::class.intent, connection, Context.BIND_AUTO_CREATE)
+        bindService(RemoteService::class.intent, connection, Context.BIND_AUTO_CREATE)
     }
 
     override fun onDestroy() {
@@ -88,7 +88,7 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
             return stopSelf()
 
         launch(Dispatchers.IO) {
-            val service = binder.unwrap(IClashManager::class)
+            val service = binder.unwrap(IRemoteService::class).clash()
             val channel = Channel<LogMessage>(CACHE_CAPACITY)
 
             try {
diff --git a/app/src/main/java/com/github/kr328/clash/log/SystemLogcat.kt b/app/src/main/java/com/github/kr328/clash/log/SystemLogcat.kt
index bbead046..848e0572 100644
--- a/app/src/main/java/com/github/kr328/clash/log/SystemLogcat.kt
+++ b/app/src/main/java/com/github/kr328/clash/log/SystemLogcat.kt
@@ -8,7 +8,8 @@ object SystemLogcat {
         "Go",
         "DEBUG",
         "AndroidRuntime",
-        "ClashForAndroid"
+        "ClashForAndroid",
+        "LwIP",
     )
 
     fun dumpCrash(): String {
diff --git a/app/src/main/java/com/github/kr328/clash/remote/Remote.kt b/app/src/main/java/com/github/kr328/clash/remote/Remote.kt
index 664ecc3a..a80bc2ae 100644
--- a/app/src/main/java/com/github/kr328/clash/remote/Remote.kt
+++ b/app/src/main/java/com/github/kr328/clash/remote/Remote.kt
@@ -15,7 +15,7 @@ import kotlinx.coroutines.launch
 
 object Remote {
     val broadcasts: Broadcasts = Broadcasts(Global.application)
-    val services: Services = Services(Global.application) {
+    val service: Service = Service(Global.application) {
         ApplicationObserver.createdActivities.forEach { it.finish() }
 
         val intent = AppCrashedActivity::class.intent
@@ -56,10 +56,10 @@ object Remote {
 
         while (true) {
             if (visible.receive()) {
-                services.bind()
+                service.bind()
                 broadcasts.register()
             } else {
-                services.unbind()
+                service.unbind()
                 broadcasts.unregister()
             }
         }
diff --git a/app/src/main/java/com/github/kr328/clash/remote/Service.kt b/app/src/main/java/com/github/kr328/clash/remote/Service.kt
new file mode 100644
index 00000000..079c0ae4
--- /dev/null
+++ b/app/src/main/java/com/github/kr328/clash/remote/Service.kt
@@ -0,0 +1,64 @@
+package com.github.kr328.clash.remote
+
+import android.app.Application
+import android.content.ComponentName
+import android.content.Context
+import android.content.ServiceConnection
+import android.os.IBinder
+import com.github.kr328.clash.Tracker
+import com.github.kr328.clash.common.log.Log
+import com.github.kr328.clash.common.util.intent
+import com.github.kr328.clash.log.SystemLogcat
+import com.github.kr328.clash.service.RemoteService
+import com.github.kr328.clash.service.remote.IRemoteService
+import com.github.kr328.clash.service.remote.unwrap
+import com.github.kr328.clash.util.unbindServiceSilent
+import java.util.concurrent.TimeUnit
+
+class Service(private val context: Application, val crashed: () -> Unit) {
+    val remote = Resource<IRemoteService>()
+
+    private val connection = object : ServiceConnection {
+        private var lastCrashed: Long = -1
+
+        override fun onServiceConnected(name: ComponentName?, service: IBinder) {
+            remote.set(service.unwrap(IRemoteService::class))
+        }
+
+        override fun onServiceDisconnected(name: ComponentName?) {
+            remote.set(null)
+
+            Tracker.uploadLogcat(SystemLogcat.dumpCrash())
+
+            if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
+                unbind()
+
+                crashed()
+            }
+
+            lastCrashed = System.currentTimeMillis()
+
+            Log.w("RemoteManager crashed")
+        }
+    }
+
+    fun bind() {
+        try {
+            context.bindService(RemoteService::class.intent, connection, Context.BIND_AUTO_CREATE)
+        } catch (e: Exception) {
+            unbind()
+
+            crashed()
+        }
+    }
+
+    fun unbind() {
+        context.unbindServiceSilent(connection)
+
+        remote.set(null)
+    }
+
+    companion object {
+        private val TOGGLE_CRASHED_INTERVAL = TimeUnit.SECONDS.toMillis(10)
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/kr328/clash/remote/Services.kt b/app/src/main/java/com/github/kr328/clash/remote/Services.kt
deleted file mode 100644
index 3d9d7f10..00000000
--- a/app/src/main/java/com/github/kr328/clash/remote/Services.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.github.kr328.clash.remote
-
-import android.app.Application
-import android.content.ComponentName
-import android.content.Context
-import android.content.ServiceConnection
-import android.os.IBinder
-import com.github.kr328.clash.common.log.Log
-import com.github.kr328.clash.common.util.intent
-import com.github.kr328.clash.service.ClashManager
-import com.github.kr328.clash.service.ProfileService
-import com.github.kr328.clash.service.remote.IClashManager
-import com.github.kr328.clash.service.remote.IProfileManager
-import com.github.kr328.clash.service.remote.unwrap
-import com.github.kr328.clash.util.unbindServiceSilent
-import java.util.concurrent.TimeUnit
-
-class Services(private val context: Application, val crashed: () -> Unit) {
-    val clash = Resource<IClashManager>()
-    val profile = Resource<IProfileManager>()
-
-    private val clashConnection = object : ServiceConnection {
-        private var lastCrashed: Long = -1
-
-        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
-            clash.set(service?.unwrap(IClashManager::class))
-        }
-
-        override fun onServiceDisconnected(name: ComponentName?) {
-            clash.set(null)
-
-            if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
-                unbind()
-
-                crashed()
-            }
-
-            lastCrashed = System.currentTimeMillis()
-
-            Log.w("ClashManager crashed")
-        }
-    }
-
-    private val profileConnection = object : ServiceConnection {
-        private var lastCrashed: Long = -1
-
-        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
-            profile.set(service?.unwrap(IProfileManager::class))
-        }
-
-        override fun onServiceDisconnected(name: ComponentName?) {
-            profile.set(null)
-
-            if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
-                unbind()
-
-                crashed()
-            }
-
-            lastCrashed = System.currentTimeMillis()
-
-            Log.w("ProfileService crashed")
-        }
-    }
-
-    fun bind() {
-        try {
-            context.bindService(ClashManager::class.intent, clashConnection, Context.BIND_AUTO_CREATE)
-            context.bindService(ProfileService::class.intent, profileConnection, Context.BIND_AUTO_CREATE)
-        } catch (e: Exception) {
-            unbind()
-
-            crashed()
-        }
-    }
-
-    fun unbind() {
-        context.unbindServiceSilent(clashConnection)
-        context.unbindServiceSilent(profileConnection)
-
-        clash.set(null)
-        profile.set(null)
-    }
-
-    companion object {
-        private val TOGGLE_CRASHED_INTERVAL = TimeUnit.SECONDS.toMillis(10)
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/kr328/clash/util/Remote.kt b/app/src/main/java/com/github/kr328/clash/util/Remote.kt
index ffc9c8bc..20120c41 100644
--- a/app/src/main/java/com/github/kr328/clash/util/Remote.kt
+++ b/app/src/main/java/com/github/kr328/clash/util/Remote.kt
@@ -14,14 +14,15 @@ suspend fun <T> withClash(
     block: suspend IClashManager.() -> T
 ): T {
     while (true) {
-        val client = Remote.services.clash.get()
+        val remote = Remote.service.remote.get()
+        val client = remote.clash()
 
         try {
             return withContext(context) { client.block() }
         } catch (e: DeadObjectException) {
             Log.w("Remote services panic")
 
-            Remote.services.clash.reset(client)
+            Remote.service.remote.reset(remote)
         }
     }
 }
@@ -31,14 +32,15 @@ suspend fun <T> withProfile(
     block: suspend IProfileManager.() -> T
 ): T {
     while (true) {
-        val client = Remote.services.profile.get()
+        val remote = Remote.service.remote.get()
+        val client = remote.profile()
 
         try {
             return withContext(context) { client.block() }
         } catch (e: DeadObjectException) {
             Log.w("Remote services panic")
 
-            Remote.services.profile.reset(client)
+            Remote.service.remote.reset(remote)
         }
     }
 }
diff --git a/service/src/main/AndroidManifest.xml b/service/src/main/AndroidManifest.xml
index 60d3dbfd..d869fb38 100644
--- a/service/src/main/AndroidManifest.xml
+++ b/service/src/main/AndroidManifest.xml
@@ -25,11 +25,7 @@
             </intent-filter>
         </service>
         <service
-            android:name=".ClashManager"
-            android:exported="false"
-            android:process=":background" />
-        <service
-            android:name=".ProfileService"
+            android:name=".RemoteService"
             android:exported="false"
             android:process=":background" />
         <service
diff --git a/service/src/main/java/com/github/kr328/clash/service/ClashManager.kt b/service/src/main/java/com/github/kr328/clash/service/ClashManager.kt
index 33670558..aa92b4af 100644
--- a/service/src/main/java/com/github/kr328/clash/service/ClashManager.kt
+++ b/service/src/main/java/com/github/kr328/clash/service/ClashManager.kt
@@ -1,7 +1,6 @@
 package com.github.kr328.clash.service
 
-import android.content.Intent
-import android.os.IBinder
+import android.content.Context
 import com.github.kr328.clash.common.log.Log
 import com.github.kr328.clash.core.Clash
 import com.github.kr328.clash.core.model.*
@@ -9,22 +8,16 @@ import com.github.kr328.clash.service.data.Selection
 import com.github.kr328.clash.service.data.SelectionDao
 import com.github.kr328.clash.service.remote.IClashManager
 import com.github.kr328.clash.service.remote.ILogObserver
-import com.github.kr328.clash.service.remote.wrap
 import com.github.kr328.clash.service.store.ServiceStore
 import com.github.kr328.clash.service.util.sendOverrideChanged
 import kotlinx.coroutines.*
 import kotlinx.coroutines.channels.ReceiveChannel
-import java.util.*
 
-class ClashManager : BaseService(), IClashManager {
-    private val store by lazy { ServiceStore(this) }
-    private val binder = this.wrap()
+class ClashManager(private val context: Context) : IClashManager,
+    CoroutineScope by CoroutineScope(Dispatchers.IO) {
+    private val store = ServiceStore(context)
     private var logReceiver: ReceiveChannel<LogMessage>? = null
 
-    override fun onBind(intent: Intent?): IBinder {
-        return binder
-    }
-
     override fun queryTunnelState(): TunnelState {
         return Clash.queryTunnelState()
     }
@@ -68,7 +61,7 @@ class ClashManager : BaseService(), IClashManager {
     override fun patchOverride(slot: Clash.OverrideSlot, configuration: ConfigurationOverride) {
         Clash.patchOverride(slot, configuration)
 
-        sendOverrideChanged()
+        context.sendOverrideChanged()
     }
 
     override fun clearOverride(slot: Clash.OverrideSlot) {
diff --git a/service/src/main/java/com/github/kr328/clash/service/ProfileService.kt b/service/src/main/java/com/github/kr328/clash/service/ProfileManager.kt
similarity index 81%
rename from service/src/main/java/com/github/kr328/clash/service/ProfileService.kt
rename to service/src/main/java/com/github/kr328/clash/service/ProfileManager.kt
index 699bec8c..47d66b04 100644
--- a/service/src/main/java/com/github/kr328/clash/service/ProfileService.kt
+++ b/service/src/main/java/com/github/kr328/clash/service/ProfileManager.kt
@@ -1,7 +1,6 @@
 package com.github.kr328.clash.service
 
-import android.content.Intent
-import android.os.IBinder
+import android.content.Context
 import com.github.kr328.clash.service.data.Database
 import com.github.kr328.clash.service.data.ImportedDao
 import com.github.kr328.clash.service.data.Pending
@@ -9,34 +8,27 @@ import com.github.kr328.clash.service.data.PendingDao
 import com.github.kr328.clash.service.model.Profile
 import com.github.kr328.clash.service.remote.IFetchObserver
 import com.github.kr328.clash.service.remote.IProfileManager
-import com.github.kr328.clash.service.remote.wrap
 import com.github.kr328.clash.service.store.ServiceStore
 import com.github.kr328.clash.service.util.directoryLastModified
 import com.github.kr328.clash.service.util.generateProfileUUID
 import com.github.kr328.clash.service.util.importedDir
 import com.github.kr328.clash.service.util.pendingDir
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import java.io.FileNotFoundException
 import java.util.*
 
-class ProfileService : BaseService(), IProfileManager {
-    private val service = this
-    private val store by lazy { ServiceStore(this) }
-    private val binder = this.wrap()
-
-    override fun onBind(intent: Intent?): IBinder {
-        return binder
-    }
-
-    override fun onCreate() {
-        super.onCreate()
-
-        Database.database //.init
+class ProfileManager(private val context: Context) : IProfileManager,
+    CoroutineScope by CoroutineScope(Dispatchers.IO) {
+    private val store = ServiceStore(context)
 
+    init {
         launch {
-            ProfileReceiver.rescheduleAll(service)
+            Database.database //.init
+
+            ProfileReceiver.rescheduleAll(context)
         }
     }
 
@@ -52,7 +44,7 @@ class ProfileService : BaseService(), IProfileManager {
 
         PendingDao().insert(pending)
 
-        pendingDir.resolve(uuid.toString()).apply {
+        context.pendingDir.resolve(uuid.toString()).apply {
             deleteRecursively()
             mkdirs()
 
@@ -119,21 +111,21 @@ class ProfileService : BaseService(), IProfileManager {
     }
 
     override suspend fun commit(uuid: UUID, callback: IFetchObserver?) {
-        ProfileProcessor.apply(service, uuid, callback)
+        ProfileProcessor.apply(context, uuid, callback)
 
         scheduleUpdate(uuid, false)
     }
 
     override suspend fun release(uuid: UUID) {
-        ProfileProcessor.release(this, uuid)
+        ProfileProcessor.release(context, uuid)
     }
 
     override suspend fun delete(uuid: UUID) {
         ImportedDao().queryByUUID(uuid)?.also {
-            ProfileReceiver.cancelNext(service, it)
+            ProfileReceiver.cancelNext(context, it)
         }
 
-        ProfileProcessor.delete(service, uuid)
+        ProfileProcessor.delete(context, uuid)
     }
 
     override suspend fun queryByUUID(uuid: UUID): Profile? {
@@ -159,7 +151,7 @@ class ProfileService : BaseService(), IProfileManager {
     }
 
     override suspend fun setActive(profile: Profile) {
-        ProfileProcessor.active(this, profile.uuid)
+        ProfileProcessor.active(context, profile.uuid)
     }
 
     private suspend fun resolveProfile(uuid: UUID): Profile? {
@@ -186,14 +178,14 @@ class ProfileService : BaseService(), IProfileManager {
     }
 
     private fun resolveUpdatedAt(uuid: UUID): Long {
-        return pendingDir.resolve(uuid.toString()).directoryLastModified
-            ?: importedDir.resolve(uuid.toString()).directoryLastModified
+        return context.pendingDir.resolve(uuid.toString()).directoryLastModified
+            ?: context.importedDir.resolve(uuid.toString()).directoryLastModified
             ?: -1
     }
 
     private fun cloneImportedFiles(source: UUID, target: UUID = source) {
-        val s = importedDir.resolve(source.toString())
-        val t = pendingDir.resolve(target.toString())
+        val s = context.importedDir.resolve(source.toString())
+        val t = context.pendingDir.resolve(target.toString())
 
         if (!s.exists())
             throw FileNotFoundException("profile $source not found")
@@ -207,9 +199,9 @@ class ProfileService : BaseService(), IProfileManager {
         val imported = ImportedDao().queryByUUID(uuid) ?: return
 
         if (startImmediately) {
-            ProfileReceiver.schedule(service, imported)
+            ProfileReceiver.schedule(context, imported)
         } else {
-            ProfileReceiver.scheduleNext(service, imported)
+            ProfileReceiver.scheduleNext(context, imported)
         }
     }
 }
\ No newline at end of file
diff --git a/service/src/main/java/com/github/kr328/clash/service/RemoteService.kt b/service/src/main/java/com/github/kr328/clash/service/RemoteService.kt
new file mode 100644
index 00000000..802110f3
--- /dev/null
+++ b/service/src/main/java/com/github/kr328/clash/service/RemoteService.kt
@@ -0,0 +1,46 @@
+package com.github.kr328.clash.service
+
+import android.content.Intent
+import android.os.IBinder
+import com.github.kr328.clash.service.remote.IClashManager
+import com.github.kr328.clash.service.remote.IRemoteService
+import com.github.kr328.clash.service.remote.IProfileManager
+import com.github.kr328.clash.service.remote.wrap
+import com.github.kr328.clash.service.util.cancelAndJoinBlocking
+
+class RemoteService : BaseService(), IRemoteService {
+    private val binder = this.wrap()
+
+    private var clash: ClashManager? = null
+    private var profile: ProfileManager? = null
+    private var clashBinder: IClashManager? = null
+    private var profileBinder: IProfileManager? = null
+
+    override fun onCreate() {
+        super.onCreate()
+
+        clash = ClashManager(this)
+        profile = ProfileManager(this)
+        clashBinder = clash?.wrap() as IClashManager?
+        profileBinder = profile?.wrap() as IProfileManager?
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+
+        clash?.cancelAndJoinBlocking()
+        profile?.cancelAndJoinBlocking()
+    }
+
+    override fun onBind(intent: Intent?): IBinder {
+        return binder
+    }
+
+    override fun clash(): IClashManager {
+        return clashBinder!!
+    }
+
+    override fun profile(): IProfileManager {
+        return profileBinder!!
+    }
+}
\ No newline at end of file
diff --git a/service/src/main/java/com/github/kr328/clash/service/remote/IRemoteService.kt b/service/src/main/java/com/github/kr328/clash/service/remote/IRemoteService.kt
new file mode 100644
index 00000000..b8c92456
--- /dev/null
+++ b/service/src/main/java/com/github/kr328/clash/service/remote/IRemoteService.kt
@@ -0,0 +1,9 @@
+package com.github.kr328.clash.service.remote
+
+import com.github.kr328.kaidl.BinderInterface
+
+@BinderInterface
+interface IRemoteService {
+    fun clash(): IClashManager
+    fun profile(): IProfileManager
+}
\ No newline at end of file
-- 
GitLab