refactor clash core context

This commit is contained in:
Kr328 committed 2020-04-02 00:44:26 +08:00
1 parent 1ef828497c
commit ea1f424b33
24 files changed
+438 -8

No files matched your search

+1
View File
@@ -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
@@ -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
}
+1
View File
@@ -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")
+1
View File
@@ -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()
}
}
@@ -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()
}
@@ -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
}
}
@@ -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