refactor clash core context
This commit is contained in:
24 files changed
+438
-8
No files matched your search
@@ -71,6 +71,7 @@ dependencies {
|
||||
implementation(project(":core"))
|
||||
implementation(project(":service"))
|
||||
implementation(project(":design"))
|
||||
implementation(project(":component"))
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$gKotlinVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$gKotlinCoroutineVersion")
|
||||
implementation("androidx.lifecycle:lifecycle-extensions:$gLifecycleVersion")
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.github.kr328.clash
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.github.kr328.clash.service.Intents
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.service.ProfileBackgroundService
|
||||
import com.github.kr328.clash.service.util.componentName
|
||||
import com.github.kr328.clash.service.util.startForegroundServiceCompat
|
||||
|
||||
@@ -6,7 +6,7 @@ import android.os.Bundle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.github.kr328.clash.adapter.ProfileAdapter
|
||||
import com.github.kr328.clash.remote.withProfile
|
||||
import com.github.kr328.clash.service.Intents
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.service.ProfileBackgroundService
|
||||
import com.github.kr328.clash.service.data.ClashProfileEntity
|
||||
import com.github.kr328.clash.service.transact.ProfileRequest
|
||||
|
||||
@@ -8,7 +8,7 @@ import android.graphics.drawable.Icon
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import com.github.kr328.clash.remote.RemoteUtils
|
||||
import com.github.kr328.clash.service.Intents
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.utils.startClashService
|
||||
import com.github.kr328.clash.utils.stopClashService
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import android.content.IntentFilter
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.github.kr328.clash.service.Intents
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
|
||||
object Broadcasts {
|
||||
interface Receiver {
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.content.Intent
|
||||
import android.net.VpnService
|
||||
import com.github.kr328.clash.preference.UiSettings
|
||||
import com.github.kr328.clash.service.ClashService
|
||||
import com.github.kr328.clash.service.Intents
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.service.TunService
|
||||
import com.github.kr328.clash.service.util.intent
|
||||
import com.github.kr328.clash.service.util.sendBroadcastSelf
|
||||
|
||||
+3
-1
@@ -1,4 +1,6 @@
|
||||
package com.github.kr328.clash.service
|
||||
package com.github.kr328.clash.component.ids
|
||||
|
||||
import com.github.kr328.clash.component.BuildConfig
|
||||
|
||||
object Intents {
|
||||
const val INTENT_ACTION_CLASH_STARTED =
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.github.kr328.clash.component.ids
|
||||
|
||||
object NotificationChannels {
|
||||
const val CLASH_STATUS = "clash_status_channel"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.github.kr328.clash.component.ids
|
||||
|
||||
object NotificationIds {
|
||||
const val CLASH_STATUS = 1
|
||||
}
|
||||
@@ -65,6 +65,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":component"))
|
||||
implementation("androidx.core:core-ktx:$gAndroidKtxVersion")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$gKotlinVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$gKotlinCoroutineVersion")
|
||||
|
||||
@@ -52,6 +52,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":component"))
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$gKotlinVersion")
|
||||
implementation("androidx.appcompat:appcompat:$gAppCompatVersion")
|
||||
implementation("androidx.core:core-ktx:$gAndroidKtxVersion")
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.PowerManager
|
||||
import androidx.core.content.getSystemService
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
import com.github.kr328.clash.service.data.ClashDatabase
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.service.data.ClashDatabase
|
||||
import com.github.kr328.clash.service.data.ClashProfileDao
|
||||
import com.github.kr328.clash.service.ipc.IStreamCallback
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.os.CancellationSignal
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.DocumentsProvider
|
||||
import com.github.kr328.clash.core.Global
|
||||
import com.github.kr328.clash.service.data.ClashDatabase
|
||||
|
||||
class ProfileDocumentProvider : DocumentsProvider() {
|
||||
companion object {
|
||||
private const val DEFAULT_ROOT_ID = 0
|
||||
}
|
||||
|
||||
val database = ClashDatabase.getInstance(Global.application)
|
||||
|
||||
override fun openDocument(
|
||||
documentId: String?,
|
||||
mode: String?,
|
||||
signal: CancellationSignal?
|
||||
): ParcelFileDescriptor {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun queryChildDocuments(
|
||||
parentDocumentId: String?,
|
||||
projection: Array<out String>?,
|
||||
sortOrder: String?
|
||||
): Cursor {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun queryDocument(documentId: String?, projection: Array<out String>?): Cursor {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun queryRoots(projection: Array<out String>?): Cursor {
|
||||
return MatrixCursor(projection).apply {
|
||||
newRow().apply {
|
||||
add(DocumentsContract.Root.COLUMN_ROOT_ID, DEFAULT_ROOT_ID)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.github.kr328.clash.service
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.service.transact.ProfileRequest
|
||||
import com.github.kr328.clash.service.util.componentName
|
||||
import com.github.kr328.clash.service.util.startForegroundServiceCompat
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.IBinder
|
||||
import androidx.core.content.FileProvider
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.core.Global
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
import com.github.kr328.clash.service.data.ClashDatabase
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.github.kr328.clash.service.clash
|
||||
|
||||
import com.github.kr328.clash.service.clash.module.Module
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
class ClashRuntime {
|
||||
companion object {
|
||||
private val GIL = Mutex()
|
||||
}
|
||||
|
||||
private val modules: MutableList<Module> = mutableListOf()
|
||||
private val mutex = Mutex()
|
||||
private var started = false
|
||||
private var stopped = false
|
||||
|
||||
suspend fun start() = mutex.withLock {
|
||||
if (!GIL.tryLock())
|
||||
throw IllegalStateException("ClashRuntime running")
|
||||
|
||||
if (started || stopped)
|
||||
return
|
||||
|
||||
started = true
|
||||
|
||||
modules.forEach {
|
||||
it.onStart()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun stop() = mutex.withLock {
|
||||
if (!started || stopped)
|
||||
return
|
||||
|
||||
modules.forEach {
|
||||
it.onStop()
|
||||
it.onDestroy()
|
||||
}
|
||||
|
||||
GIL.unlock()
|
||||
}
|
||||
|
||||
suspend fun <T : Module> install(module: T, configure: T.() -> Unit = {}) = mutex.withLock {
|
||||
if (stopped)
|
||||
return
|
||||
|
||||
stopped = true
|
||||
|
||||
modules.add(module)
|
||||
|
||||
module.onCreate()
|
||||
module.configure()
|
||||
|
||||
if (started)
|
||||
module.onStart()
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package com.github.kr328.clash.service.clash.module
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.PowerManager
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.component.ids.NotificationChannels
|
||||
import com.github.kr328.clash.component.ids.NotificationIds
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.utils.asBytesString
|
||||
import com.github.kr328.clash.service.R
|
||||
import com.github.kr328.clash.service.data.ClashDatabase
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.selects.select
|
||||
|
||||
class DynamicNotificationModule(private val service: Service) : NotificationModule {
|
||||
private val contentIntent = Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
.setPackage(service.packageName)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
private val builder = NotificationCompat.Builder(service, NotificationChannels.CLASH_STATUS)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setOngoing(true)
|
||||
.setColor(service.getColor(R.color.colorAccentService))
|
||||
.setOnlyAlertOnce(true)
|
||||
.setShowWhen(false)
|
||||
.setGroup(NotificationChannels.CLASH_STATUS)
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
service,
|
||||
NotificationIds.CLASH_STATUS,
|
||||
contentIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
private val reloadChannel = Channel<Unit>(Channel.CONFLATED)
|
||||
private val screenChannel = Channel<Boolean>(Channel.CONFLATED)
|
||||
private val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_SCREEN_ON ->
|
||||
screenChannel.offer(true)
|
||||
Intent.ACTION_SCREEN_OFF ->
|
||||
screenChannel.offer(false)
|
||||
Intents.INTENT_ACTION_PROFILE_CHANGED ->
|
||||
reloadChannel.offer(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
private lateinit var backgroundJob: Job
|
||||
private var currentProfile = "Not selected"
|
||||
|
||||
override suspend fun onCreate() {
|
||||
withContext(Dispatchers.Unconfined) {
|
||||
val database = ClashDatabase.getInstance(service).openClashProfileDao()
|
||||
val tickerChannel = Channel<Unit>()
|
||||
|
||||
backgroundJob = launch {
|
||||
launch {
|
||||
while (isActive) {
|
||||
tickerChannel.send(Unit)
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
|
||||
var refreshEnabled =
|
||||
service.getSystemService(PowerManager::class.java).isInteractive
|
||||
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
reloadChannel.onReceive {
|
||||
currentProfile = database.queryActiveProfile()?.name ?: "Not selected"
|
||||
}
|
||||
screenChannel.onReceive {
|
||||
refreshEnabled = it
|
||||
}
|
||||
if (refreshEnabled) {
|
||||
tickerChannel.onReceive {
|
||||
update()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onStart() {
|
||||
reloadChannel.offer(Unit)
|
||||
|
||||
service.registerReceiver(receiver, IntentFilter().apply {
|
||||
addAction(Intents.INTENT_ACTION_PROFILE_CHANGED)
|
||||
addAction(Intent.ACTION_SCREEN_OFF)
|
||||
addAction(Intent.ACTION_SCREEN_ON)
|
||||
})
|
||||
}
|
||||
|
||||
override suspend fun onStop() {
|
||||
service.unregisterReceiver(receiver)
|
||||
}
|
||||
|
||||
override suspend fun onDestroy() {
|
||||
backgroundJob.cancel()
|
||||
}
|
||||
|
||||
override fun update() {
|
||||
val traffic = Clash.queryTraffic()
|
||||
val bandwidth = Clash.queryBandwidth()
|
||||
|
||||
val uploading = traffic.upload.asBytesString()
|
||||
val downloading = traffic.download.asBytesString()
|
||||
val uploaded = bandwidth.upload.asBytesString()
|
||||
val downloaded = bandwidth.download.asBytesString()
|
||||
|
||||
val notification = builder
|
||||
.setContentTitle(currentProfile)
|
||||
.setContentText(
|
||||
service.getString(
|
||||
R.string.clash_notification_content,
|
||||
uploading, downloading
|
||||
)
|
||||
)
|
||||
.setSubText(
|
||||
service.getString(
|
||||
R.string.clash_notification_content,
|
||||
uploaded, downloaded
|
||||
)
|
||||
)
|
||||
.build()
|
||||
|
||||
service.startForeground(NotificationIds.CLASH_STATUS, notification)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.github.kr328.clash.service.clash.module
|
||||
|
||||
interface Module {
|
||||
suspend fun onCreate()
|
||||
suspend fun onStart()
|
||||
suspend fun onStop()
|
||||
suspend fun onDestroy()
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.github.kr328.clash.service.clash.module
|
||||
|
||||
interface NotificationModule : Module {
|
||||
fun update()
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.github.kr328.clash.service.clash.module
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.service.data.ClashDatabase
|
||||
import com.github.kr328.clash.service.util.resolveBase
|
||||
import com.github.kr328.clash.service.util.resolveProfile
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
|
||||
class ReloadModule(private val context: Context) : Module {
|
||||
private lateinit var backgroundJob: Job
|
||||
private var emptyProfileCallback: () -> Unit = {}
|
||||
private val reloadChannel = Channel<Unit>(Channel.CONFLATED)
|
||||
private val reloadBroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
reloadChannel.offer(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onCreate() {
|
||||
backgroundJob = withContext(Dispatchers.Unconfined) {
|
||||
launch {
|
||||
val database = ClashDatabase.getInstance(context).openClashProfileDao()
|
||||
|
||||
while (isActive) {
|
||||
val id = database.queryActiveProfile()?.id
|
||||
if (id == null) {
|
||||
emptyProfileCallback()
|
||||
continue
|
||||
}
|
||||
|
||||
Clash.loadProfile(resolveProfile(id), resolveBase(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onStart() {
|
||||
context.registerReceiver(
|
||||
reloadBroadcastReceiver,
|
||||
IntentFilter(Intents.INTENT_ACTION_PROFILE_CHANGED)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun onStop() {
|
||||
context.unregisterReceiver(reloadBroadcastReceiver)
|
||||
}
|
||||
|
||||
override suspend fun onDestroy() {
|
||||
backgroundJob.cancel()
|
||||
}
|
||||
|
||||
fun onEmptyProfile(callback: () -> Unit) {
|
||||
emptyProfileCallback = callback
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package com.github.kr328.clash.service.clash.module
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.component.ids.NotificationChannels
|
||||
import com.github.kr328.clash.component.ids.NotificationIds
|
||||
import com.github.kr328.clash.service.R
|
||||
import com.github.kr328.clash.service.data.ClashDatabase
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
|
||||
class StaticNotificationModule(private val service: Service) : NotificationModule {
|
||||
private val contentIntent = Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
.setPackage(service.packageName)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
private val builder = NotificationCompat.Builder(service, NotificationChannels.CLASH_STATUS)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setOngoing(true)
|
||||
.setColor(service.getColor(R.color.colorAccentService))
|
||||
.setOnlyAlertOnce(true)
|
||||
.setShowWhen(false)
|
||||
.setGroup(NotificationChannels.CLASH_STATUS)
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
service,
|
||||
NotificationIds.CLASH_STATUS,
|
||||
contentIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
private val reloadChannel = Channel<Unit>(Channel.CONFLATED)
|
||||
private val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
Intents.INTENT_ACTION_PROFILE_CHANGED ->
|
||||
reloadChannel.offer(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
private var currentProfile: String = "Not selected"
|
||||
private lateinit var backgroundJob: Job
|
||||
|
||||
override fun update() {
|
||||
val notification = builder
|
||||
.setContentTitle(currentProfile)
|
||||
.setContentText(service.getText(R.string.running))
|
||||
.build()
|
||||
|
||||
service.startForeground(NotificationIds.CLASH_STATUS, notification)
|
||||
}
|
||||
|
||||
override suspend fun onCreate() {
|
||||
withContext(Dispatchers.Unconfined) {
|
||||
val database = ClashDatabase.getInstance(service).openClashProfileDao()
|
||||
|
||||
backgroundJob = launch {
|
||||
while (isActive) {
|
||||
reloadChannel.receive()
|
||||
|
||||
currentProfile = database.queryActiveProfile()?.name ?: "Not selected"
|
||||
|
||||
update()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onStart() {
|
||||
service.registerReceiver(receiver, IntentFilter(Intents.INTENT_ACTION_PROFILE_CHANGED))
|
||||
|
||||
reloadChannel.offer(Unit)
|
||||
}
|
||||
|
||||
override suspend fun onStop() {
|
||||
service.unregisterReceiver(receiver)
|
||||
}
|
||||
|
||||
override suspend fun onDestroy() {
|
||||
backgroundJob.cancel()
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package com.github.kr328.clash.service.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.core.Global
|
||||
import com.github.kr328.clash.service.Intents
|
||||
|
||||
fun Context.sendBroadcastSelf(intent: Intent) {
|
||||
this.sendBroadcast(intent.setPackage(this.packageName))
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.github.kr328.clash.service.Intents
|
||||
import com.github.kr328.clash.component.ids.Intents
|
||||
import com.github.kr328.clash.service.ProfileRequestReceiver
|
||||
import com.github.kr328.clash.service.data.ClashProfileEntity
|
||||
|
||||
|
||||
Reference in new issue
Block a user