diff --git a/app/src/main/java/com/github/kr328/clash/MainApplication.kt b/app/src/main/java/com/github/kr328/clash/MainApplication.kt index 8305328..7ff19a7 100644 --- a/app/src/main/java/com/github/kr328/clash/MainApplication.kt +++ b/app/src/main/java/com/github/kr328/clash/MainApplication.kt @@ -39,12 +39,12 @@ class MainApplication : Application() { private fun ByteArray.toHexString(): String { return this.map { Integer.toHexString(it.toInt() and 0xff) - }.map { + }.joinToString(separator = "") { if (it.length < 2) "0$it" else it - }.joinToString(separator = "") + } } } @@ -68,40 +68,6 @@ class MainApplication : Application() { Crashlytics.setBool(CRASHLYTICS_SPLIT_APK_KEY, detectSplitArchive()) Crashlytics.setString(CRASHLYTICS_BASE_SIZE_KEY, getBaseApkSize()) Crashlytics.setUserIdentifier(userIdentifier) - - Log.handler = object : Log.LogHandler { - override fun info(message: String, throwable: Throwable?) { - android.util.Log.i(Constants.TAG, message, throwable) - } - - override fun warn(message: String, throwable: Throwable?) { - throwable?.also { - Crashlytics.logException(it) - } - - android.util.Log.w(Constants.TAG, message, throwable) - } - - override fun error(message: String, throwable: Throwable?) { - throwable?.also { - Crashlytics.logException(it) - } - - android.util.Log.e(Constants.TAG, message, throwable) - } - - override fun wtf(message: String, throwable: Throwable?) { - throwable?.also { - Crashlytics.logException(it) - } - - android.util.Log.wtf(Constants.TAG, message, throwable) - } - - override fun debug(message: String, throwable: Throwable?) { - android.util.Log.d(Constants.TAG, message, throwable) - } - } } private fun detectFromPlay(): Boolean { 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 9fbd7cc..10d4b4b 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.Context -import android.content.SharedPreferences import androidx.core.content.edit import com.github.kr328.clash.core.Clash import com.github.kr328.clash.core.model.General @@ -9,7 +8,7 @@ import com.github.kr328.clash.core.model.ProxyGroup import com.github.kr328.clash.service.ipc.ParcelableCompletedFuture import com.github.kr328.clash.service.ipc.ParcelablePipe -class ClashManager(context: Context): IClashManager.Stub() { +class ClashManager(context: Context) : IClashManager.Stub() { private val settings = context.getSharedPreferences("service", Context.MODE_PRIVATE) override fun queryAllProxies(): Array { @@ -27,7 +26,7 @@ class ClashManager(context: Context): IClashManager.Stub() { } override fun openBandwidthEvent(): ParcelablePipe { - return object: ParcelablePipe() { + return object : ParcelablePipe() { val stream = Clash.openBandwidthEvent().apply { onEvent { send(it) @@ -45,7 +44,7 @@ class ClashManager(context: Context): IClashManager.Stub() { return ParcelableCompletedFuture().apply { Clash.startHealthCheck(group).whenComplete { _: Unit?, u: Throwable? -> - if ( u != null ) + if (u != null) this.completeExceptionally(u) else this.complete(null) @@ -54,7 +53,7 @@ class ClashManager(context: Context): IClashManager.Stub() { } override fun openLogEvent(): ParcelablePipe { - return object: ParcelablePipe() { + return object : ParcelablePipe() { val stream = Clash.openLogEvent().apply { onEvent { send(it) diff --git a/service/src/main/java/com/github/kr328/clash/service/ClashProfileManager.kt b/service/src/main/java/com/github/kr328/clash/service/ClashProfileManager.kt index 495b92d..be9d888 100644 --- a/service/src/main/java/com/github/kr328/clash/service/ClashProfileManager.kt +++ b/service/src/main/java/com/github/kr328/clash/service/ClashProfileManager.kt @@ -17,8 +17,10 @@ class ClashProfileManager(private val context: Context, private val database: Cl override fun updateProfile(id: Int): ParcelableCompletedFuture { val entity = database.openClashProfileDao().queryProfileById(id) - require(entity != null && (entity.type == ClashProfileEntity.Type.URL || - entity.type == ClashProfileEntity.Type.FILE)) + require( + entity != null && (entity.type == ClashProfileEntity.Type.URL || + entity.type == ClashProfileEntity.Type.FILE) + ) val result = ParcelableCompletedFuture() diff --git a/service/src/main/java/com/github/kr328/clash/service/ClashService.kt b/service/src/main/java/com/github/kr328/clash/service/ClashService.kt index d911e28..6ce6d83 100644 --- a/service/src/main/java/com/github/kr328/clash/service/ClashService.kt +++ b/service/src/main/java/com/github/kr328/clash/service/ClashService.kt @@ -1,13 +1,13 @@ package com.github.kr328.clash.service import android.app.Service -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.ServiceConnection +import android.content.* import android.os.Binder import android.os.IBinder import com.github.kr328.clash.core.Clash +import com.github.kr328.clash.service.data.ClashDatabase +import com.github.kr328.clash.service.util.DefaultThreadPool +import com.github.kr328.clash.service.util.sendBroadcastSelf class ClashService : Service() { companion object { @@ -25,16 +25,18 @@ class ClashService : Service() { val tun = service.queryLocalInterface(TunService::class.java.name) as TunService tun.startTun().whenComplete { _, u -> - if (u != null) { - stopReason = u.message - stopSelf() - return@whenComplete - } + if (u != null) + return@whenComplete stopSelf(u.message ?: "Start tun failure") notification.setVpn(true) } } } + private val profileObserver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + reloadProfile() + } + } override fun onCreate() { super.onCreate() @@ -49,7 +51,7 @@ class ClashService : Service() { Clash.start() - sendBroadcast(Intent(Intents.INTENT_ACTION_CLASH_STARTED)) + sendBroadcastSelf(Intent(Intents.INTENT_ACTION_CLASH_STARTED)) val startVpn = intent?.getBooleanExtra(INTENT_EXTRA_START_TUN, true) ?: true @@ -61,6 +63,10 @@ class ClashService : Service() { Context.BIND_AUTO_CREATE ) + reloadProfile() + + registerReceiver(profileObserver, IntentFilter(Intents.INTENT_ACTION_PROFILE_CHANGED)) + return START_NOT_STICKY } @@ -77,15 +83,33 @@ class ClashService : Service() { notification.destroy() - sendBroadcast( + sendBroadcastSelf( Intent(Intents.INTENT_ACTION_CLASH_STOPPED) .putExtra(Intents.INTENT_ACTION_CLASH_STOP_REASON, stopReason) ) + unregisterReceiver(profileObserver) + super.onDestroy() } private fun reloadProfile() { + DefaultThreadPool.submit { + val active = ClashDatabase.getInstance(this).openClashProfileDao().queryActiveProfile() + ?: return@submit stopSelf("Empty active profile") + Clash.loadProfile( + filesDir.resolve(Constants.PROFILES_DIR).resolve(active.file), + filesDir.resolve(Constants.CLASH_DIR).resolve(active.base) + ).whenComplete { _, u -> + if (u != null) + return@whenComplete stopSelf(u.message ?: "Load profile failure") + } + } + } + + private fun stopSelf(reason: String) { + stopReason = reason + stopSelf() } } \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/Constants.kt b/service/src/main/java/com/github/kr328/clash/service/Constants.kt index 8e6b4ae..b47169d 100644 --- a/service/src/main/java/com/github/kr328/clash/service/Constants.kt +++ b/service/src/main/java/com/github/kr328/clash/service/Constants.kt @@ -1,8 +1,10 @@ package com.github.kr328.clash.service object Constants { - const val CLASH_PROCESS_BROADCAST_ACTION = "com.github.kr328.clash.ClashService.ClashProcessEvent" - const val CLASH_RELOAD_BROADCAST_ACTION = "com.github.kr328.clash.ClashService.ProfileReloadEvent" + const val CLASH_PROCESS_BROADCAST_ACTION = + "com.github.kr328.clash.ClashService.ClashProcessEvent" + const val CLASH_RELOAD_BROADCAST_ACTION = + "com.github.kr328.clash.ClashService.ProfileReloadEvent" const val CLASH_DIR = "clash" const val PROFILES_DIR = "profiles" diff --git a/service/src/main/java/com/github/kr328/clash/service/Intents.kt b/service/src/main/java/com/github/kr328/clash/service/Intents.kt index ceeeb8e..8d3d037 100644 --- a/service/src/main/java/com/github/kr328/clash/service/Intents.kt +++ b/service/src/main/java/com/github/kr328/clash/service/Intents.kt @@ -1,9 +1,15 @@ package com.github.kr328.clash.service object Intents { - const val INTENT_ACTION_BIND_TUN_SERVICE = "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.bind.tun" - const val INTENT_ACTION_CLASH_STARTED = "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.clash.started" - const val INTENT_ACTION_CLASH_STOPPED = "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.clash.stopped" + const val INTENT_ACTION_BIND_TUN_SERVICE = + "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.bind.tun" + const val INTENT_ACTION_CLASH_STARTED = + "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.clash.started" + const val INTENT_ACTION_CLASH_STOPPED = + "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.clash.stopped" + const val INTENT_ACTION_PROFILE_CHANGED = + "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.profile.changed" - const val INTENT_ACTION_CLASH_STOP_REASON = "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.extra.clash.stop.reason" + const val INTENT_ACTION_CLASH_STOP_REASON = + "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.extra.clash.stop.reason" } \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/Settings.kt b/service/src/main/java/com/github/kr328/clash/service/Settings.kt index 5549a70..3ff4d24 100644 --- a/service/src/main/java/com/github/kr328/clash/service/Settings.kt +++ b/service/src/main/java/com/github/kr328/clash/service/Settings.kt @@ -20,11 +20,11 @@ class Settings(private val clashManager: IClashManager) { return clashManager.getSetting(key) } - fun get(setting: Setting): T { + fun get(setting: Setting): T { return setting.parseValue(get(setting.key)) } - fun put(setting: Setting, value: T) { + fun put(setting: Setting, value: T) { put(setting.key, setting.valueToString(value)) } @@ -34,41 +34,46 @@ class Settings(private val clashManager: IClashManager) { fun valueToString(value: T): String } - class BooleanSetting(override val key: String, private val def: Boolean): Setting { + class BooleanSetting(override val key: String, private val def: Boolean) : Setting { override fun parseValue(value: String?): Boolean { val v = value ?: return def return v.toBoolean() } + override fun valueToString(value: Boolean): String { return value.toString() } } - class IntSetting(override val key: String, private val def: Int): Setting { + class IntSetting(override val key: String, private val def: Int) : Setting { override fun parseValue(value: String?): Int { val v = value ?: return def return v.toIntOrNull() ?: def } + override fun valueToString(value: Int): String { return value.toString() } } - class StringSetting(override val key: String, val def: String): Setting { + class StringSetting(override val key: String, val def: String) : Setting { override fun parseValue(value: String?): String { return value ?: def } + override fun valueToString(value: String): String { return value } } - class PackageListSetting(override val key: String, private val def: List): Setting> { + class PackageListSetting(override val key: String, private val def: List) : + Setting> { override fun parseValue(value: String?): List { val v = value ?: return def return v.split(":") } + override fun valueToString(value: List): String { return value.joinToString(":") } diff --git a/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabase.kt b/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabase.kt index 9eef229..3972461 100644 --- a/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabase.kt +++ b/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabase.kt @@ -5,7 +5,11 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase -@Database(version = 2, exportSchema = false, entities = [ClashProfileEntity::class, ClashProfileProxyEntity::class]) +@Database( + version = 2, + exportSchema = false, + entities = [ClashProfileEntity::class, ClashProfileProxyEntity::class] +) abstract class ClashDatabase : RoomDatabase() { abstract fun openClashProfileDao(): ClashProfileDao abstract fun openClashProfileProxyDao(): ClashProfileProxyDao diff --git a/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabaseMigrations.kt b/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabaseMigrations.kt index 5a8ba2b..f06b909 100644 --- a/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabaseMigrations.kt +++ b/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabaseMigrations.kt @@ -14,22 +14,25 @@ object ClashDatabaseMigrations { database.execSQL("ALTER TABLE profiles RENAME TO _profiles") - database.execSQL("CREATE TABLE profiles(" + - "name TEXT NOT NULL, " + - "type INTEGER NOT NULL, " + - "uri TEXT NOT NULL, " + - "file TEXT NOT NULL, " + - "base TEXT NOT NULL" + - "active INTEGER NOT NULL, " + - "last_update INTEGER NOT NULL, " + - "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)") + database.execSQL( + "CREATE TABLE profiles(" + + "name TEXT NOT NULL, " + + "type INTEGER NOT NULL, " + + "uri TEXT NOT NULL, " + + "file TEXT NOT NULL, " + + "base TEXT NOT NULL" + + "active INTEGER NOT NULL, " + + "last_update INTEGER NOT NULL, " + + "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)" + ) - val cursor = database.query("SELECT name, token, file, active, last_update FROM _profile") + val cursor = + database.query("SELECT name, token, file, active, last_update FROM _profile") val random = SecureRandom() val bases = mutableSetOf() cursor.moveToFirst() - while ( !cursor.isAfterLast ) { + while (!cursor.isAfterLast) { // old val name = cursor.getString(0) val token = cursor.getString(1) @@ -46,7 +49,7 @@ object ClashDatabaseMigrations { val uri = token.removePrefix("url|").removePrefix("file|") var base = random.nextLong().absoluteValue - while ( bases.contains(base) ) + while (bases.contains(base)) base = random.nextLong().absoluteValue database.insert("profiles", @@ -59,7 +62,7 @@ object ClashDatabaseMigrations { put("active", active) put("last_update", lastUpdate) put("base", base.toString()) - }) + }) } cursor.close() diff --git a/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileEntity.kt b/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileEntity.kt index 3539961..3997feb 100644 --- a/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileEntity.kt +++ b/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileEntity.kt @@ -41,7 +41,7 @@ data class ClashProfileEntity( @TypeConverter fun intToType(value: Int?): Type { - return when ( value ) { + return when (value) { Type.URL.id -> Type.URL Type.FILE.id -> Type.FILE Type.UNKNOWN.id -> Type.UNKNOWN diff --git a/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileProxyEntity.kt b/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileProxyEntity.kt index 2b41ff0..ca3002d 100644 --- a/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileProxyEntity.kt +++ b/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileProxyEntity.kt @@ -2,13 +2,17 @@ package com.github.kr328.clash.service.data import androidx.room.* -@Entity(tableName = "profile_select_proxies", +@Entity( + tableName = "profile_select_proxies", indices = [Index("profile_id")], - foreignKeys = [ForeignKey(entity = ClashProfileEntity::class, + foreignKeys = [ForeignKey( + entity = ClashProfileEntity::class, childColumns = ["profile_id"], parentColumns = ["id"], onDelete = ForeignKey.CASCADE, - onUpdate = ForeignKey.CASCADE)]) + onUpdate = ForeignKey.CASCADE + )] +) data class ClashProfileProxyEntity( @ColumnInfo(name = "profile_id") val profileId: Int, @ColumnInfo(name = "proxy") val proxy: String, diff --git a/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableCompletedFuture.kt b/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableCompletedFuture.kt index 56488c5..7055326 100644 --- a/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableCompletedFuture.kt +++ b/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableCompletedFuture.kt @@ -7,7 +7,7 @@ import java.util.concurrent.CompletableFuture class ParcelableCompletedFuture private constructor(private val pipe: ParcelablePipe) : CompletableFuture(), Parcelable { - constructor(): this(ParcelablePipe()) + constructor() : this(ParcelablePipe()) constructor(parcel: Parcel) : this( parcel.readParcelable( ParcelableCompletedFuture::class.java.classLoader @@ -16,17 +16,16 @@ class ParcelableCompletedFuture private constructor(private val pipe: Parcelable pipe.onReceive { val result = (it ?: throw NullPointerException()) as ParcelableResult - if ( result.exception != null ) { + if (result.exception != null) { completeExceptionally(RemoteException(result.exception)) - } - else { + } else { complete(result.data) } } } override fun complete(value: Parcelable?): Boolean { - if ( super.complete(value) ) { + if (super.complete(value)) { pipe.send(ParcelableResult(value, null)) return true } @@ -34,7 +33,7 @@ class ParcelableCompletedFuture private constructor(private val pipe: Parcelable } override fun completeExceptionally(ex: Throwable?): Boolean { - if ( super.completeExceptionally(ex) ) { + if (super.completeExceptionally(ex)) { pipe.send(ParcelableResult(null, ex?.message ?: "Unknown")) return true } diff --git a/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableResult.kt b/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableResult.kt index cd3b28f..f73cc1f 100644 --- a/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableResult.kt +++ b/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableResult.kt @@ -3,7 +3,7 @@ package com.github.kr328.clash.service.ipc import android.os.Parcel import android.os.Parcelable -data class ParcelableResult(val data: Parcelable?, val exception: String?): Parcelable { +data class ParcelableResult(val data: Parcelable?, val exception: String?) : Parcelable { constructor(parcel: Parcel) : this( parcel.readParcelable(ParcelableResult::class.java.classLoader), parcel.readString() diff --git a/service/src/main/java/com/github/kr328/clash/service/net/DefaultNetworkObserver.kt b/service/src/main/java/com/github/kr328/clash/service/net/DefaultNetworkObserver.kt index 3a7b544..c3e5b4a 100644 --- a/service/src/main/java/com/github/kr328/clash/service/net/DefaultNetworkObserver.kt +++ b/service/src/main/java/com/github/kr328/clash/service/net/DefaultNetworkObserver.kt @@ -50,7 +50,8 @@ class DefaultNetworkObserver(val context: Context, val listener: (Network?) -> U return try { connectivity.allNetworks .flatMap { network -> - connectivity.getNetworkCapabilities(network)?.let { listOf(it to network) } ?: emptyList() + connectivity.getNetworkCapabilities(network)?.let { listOf(it to network) } + ?: emptyList() } .asSequence() .filterNot { diff --git a/service/src/main/java/com/github/kr328/clash/service/util/BroadcastUtils.kt b/service/src/main/java/com/github/kr328/clash/service/util/BroadcastUtils.kt new file mode 100644 index 0000000..a15f08f --- /dev/null +++ b/service/src/main/java/com/github/kr328/clash/service/util/BroadcastUtils.kt @@ -0,0 +1,8 @@ +package com.github.kr328.clash.service.util + +import android.content.Context +import android.content.Intent + +fun Context.sendBroadcastSelf(intent: Intent) { + this.sendBroadcast(intent.setPackage(this.packageName)) +} \ No newline at end of file