From f76bed2f8b5e61e6616c44c8062e30126ac4ec9c Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Sun, 9 Feb 2020 00:53:40 +0800 Subject: [PATCH] add profile process service --- .../clash/service/ProfileBackgroundService.kt | 219 +++++++++++------- .../clash/service/ProfileProcessService.kt | 2 + .../clash/service/transact/ProfileRequest.kt | 18 -- .../clash/service/util/DefaultThreadPool.kt | 6 - ...ate_completed.xml => ic_update_normal.xml} | 0 service/src/main/res/values/strings.xml | 15 +- 6 files changed, 146 insertions(+), 114 deletions(-) delete mode 100644 service/src/main/java/com/github/kr328/clash/service/util/DefaultThreadPool.kt rename service/src/main/res/drawable/{ic_update_completed.xml => ic_update_normal.xml} (100%) diff --git a/service/src/main/java/com/github/kr328/clash/service/ProfileBackgroundService.kt b/service/src/main/java/com/github/kr328/clash/service/ProfileBackgroundService.kt index fe83127..0d0ecd4 100644 --- a/service/src/main/java/com/github/kr328/clash/service/ProfileBackgroundService.kt +++ b/service/src/main/java/com/github/kr328/clash/service/ProfileBackgroundService.kt @@ -1,6 +1,9 @@ package com.github.kr328.clash.service -import android.app.* +import android.app.AlarmManager +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.Intent @@ -8,7 +11,6 @@ import android.content.ServiceConnection import android.os.Binder import android.os.Build import android.os.IBinder -import android.os.RemoteException import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.github.kr328.clash.service.data.ClashDatabase @@ -20,92 +22,72 @@ import com.github.kr328.clash.service.util.RandomUtils import com.github.kr328.clash.service.util.componentName import com.github.kr328.clash.service.util.intent import com.github.kr328.clash.service.util.timeout -import kotlinx.coroutines.* +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import kotlinx.coroutines.selects.select class ProfileBackgroundService : BaseService() { companion object { private const val SERVICE_STATUS_CHANNEL = "profile_service_status" private const val SERVICE_RESULT_CHANNEL = "profile_service_result" - private const val SERVICE_NOTIFICATION_ID = 10000 + private const val SERVICE_NOTIFICATION_ID_BASE = 10000 } + private val database by lazy { ClashDatabase.getInstance(this).openClashProfileDao() } private val channel = Channel(2) - private val queue = mutableListOf>() + private val queue = mutableListOf>() private val profiles: ClashProfileDao by lazy { ClashDatabase.getInstance(this).openClashProfileDao() } private val connection = object : ServiceConnection { - override fun onServiceDisconnected(name: ComponentName?) {} + override fun onServiceDisconnected(name: ComponentName?) { + channel.close() + stopSelf() + } override fun onServiceConnected(name: ComponentName?, binder: IBinder?) { val service = IProfileService.Stub.asInterface(binder) ?: return stopSelf() - launch { - while (isActive) { - val timeout = timeout(1000 * 60L) - - select { - channel.onReceive { - val deferred = CompletableDeferred() - - it.withCallback(object : IStreamCallback.Stub() { - override fun complete() { - deferred.complete(it) - } - - override fun completeExceptionally(reason: String?) { - deferred.completeExceptionally(RemoteException(reason)) - } - - override fun send(data: ParcelableContainer?) {} - }) - - service.enqueueRequest(it) - - queue.add(deferred) - } - if (queue.isNotEmpty()) { - for (task in queue) { - task.onAwait { - queue.remove(task) - } - } - } else { - timeout.onJoin { - stopSelf() - cancel() - } - } - } - - timeout.cancel() - } - } + startProfileProcessor(service) } } override fun onCreate() { super.onCreate() + createNotificationChannels() + + startForeground() + bindService(ProfileProcessService::class.intent, connection, Context.BIND_AUTO_CREATE) } override fun onDestroy() { + super.onDestroy() + unbindService(connection) - super.onDestroy() + stopForeground(true) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) - val request = - intent?.getParcelableExtra(Intents.INTENT_EXTRA_PROFILE_REQUEST) - ?: return START_NOT_STICKY - - channel.offer(request) + when (intent?.action) { + Intents.INTENT_ACTION_PROFILE_ENQUEUE_REQUEST -> { + val request = + intent.getParcelableExtra(Intents.INTENT_EXTRA_PROFILE_REQUEST) + ?: return START_NOT_STICKY + if (request.id != 0L) + channel.offer(request) + } + Intents.INTENT_ACTION_PROFILE_SETUP -> { + resetProfileUpdateAlarm() + } + } return START_NOT_STICKY } @@ -114,6 +96,54 @@ class ProfileBackgroundService : BaseService() { return Binder() } + private fun startProfileProcessor(service: IProfileService) { + launch { + while (isActive) { + val timeout = timeout(1000 * 60L) + + select { + channel.onReceive { + val deferred = CompletableDeferred() + + it.withCallback(object : IStreamCallback.Stub() { + override fun complete() { + deferred.complete(it) + updateUpdateComplete(it.id) + } + + override fun completeExceptionally(reason: String?) { + deferred.complete(it) + updateUpdateFailure(it.id) + } + + override fun send(data: ParcelableContainer?) { + updateUpdating(it.id) + } + }) + + service.enqueueRequest(it) + + queue.add(deferred) + } + if (queue.isNotEmpty()) { + for (task in queue) { + task.onAwait { + queue.remove(task) + } + } + } else { + timeout.onJoin { + stopSelf() + cancel() + } + } + } + + timeout.cancel() + } + } + } + private fun resetProfileUpdateAlarm() { for (entity in profiles.queryProfiles()) { if (entity.updateInterval <= 0) continue @@ -150,49 +180,74 @@ class ProfileBackgroundService : BaseService() { ), NotificationChannel( SERVICE_RESULT_CHANNEL, - getText(R.string.profile_service_result), + getText(R.string.profile_status_channel), NotificationManager.IMPORTANCE_DEFAULT ) ) ) } - private fun createServiceNotification(content: CharSequence): Notification { - return NotificationCompat.Builder(this, SERVICE_STATUS_CHANNEL) + private fun startForeground() { + val notification = NotificationCompat.Builder(this, SERVICE_STATUS_CHANNEL) .setContentTitle(getText(R.string.profile_service_status_title)) - .setContentText(content) .setSmallIcon(R.drawable.ic_updating) .setOnlyAlertOnce(true) - .setProgress(Int.MAX_VALUE, 0, true) .build() + + startForeground(SERVICE_NOTIFICATION_ID_BASE, notification) } - private fun createResultNotification(success: Boolean, content: CharSequence): Notification { - return NotificationCompat.Builder(this, SERVICE_RESULT_CHANNEL) - .setContentTitle(getText(R.string.profile_process_result)) - .setContentText(content) - .setSmallIcon(if (success) R.drawable.ic_update_completed else R.drawable.ic_update_failure) + private fun updateUpdating(id: Long) { + val notificationId = (id % (Int.MAX_VALUE - SERVICE_NOTIFICATION_ID_BASE)).toInt() + val entity = database.queryProfileById(id) ?: return + + val notification = NotificationCompat.Builder(this, SERVICE_RESULT_CHANNEL) + .setContentTitle(getText(R.string.profile_status_title)) + .setContentText(getString(R.string.profile_status_updating, entity.name)) + .setSmallIcon(R.drawable.ic_update_normal) + .setOngoing(true) .build() - } - private fun updateNotificationWaiting() { - startForeground( - SERVICE_NOTIFICATION_ID, - createServiceNotification(getText(R.string.profile_service_status_waiting)) - ) - } - - private fun updateNotificationUpdating(profileName: String) { - createServiceNotification( - getString( - R.string.profile_service_status_updating, - profileName - ) - ) - } - - private fun notifyResultNotification(success: Boolean, content: CharSequence) { NotificationManagerCompat.from(this) - .notify(RandomUtils.nextInt(), createResultNotification(success, content)) + .notify(SERVICE_NOTIFICATION_ID_BASE + notificationId, notification) + } + + private fun updateUpdateComplete(id: Long) { + val notificationId = (id % (Int.MAX_VALUE - SERVICE_NOTIFICATION_ID_BASE)).toInt() + val entity = database.queryProfileById(id) + + if (entity == null) { + NotificationManagerCompat.from(this).cancel(notificationId) + return + } + + val notification = NotificationCompat.Builder(this, SERVICE_RESULT_CHANNEL) + .setContentTitle(getText(R.string.profile_status_title)) + .setContentText(getString(R.string.profile_status_update_completed, entity.name)) + .setSmallIcon(R.drawable.ic_update_normal) + .build() + + NotificationManagerCompat.from(this) + .notify(SERVICE_NOTIFICATION_ID_BASE + notificationId, notification) + } + + private fun updateUpdateFailure(id: Long) { + val notificationId = (id % (Int.MAX_VALUE - SERVICE_NOTIFICATION_ID_BASE)).toInt() + val entity = database.queryProfileById(id) + + if (entity == null) { + NotificationManagerCompat.from(this).cancel(notificationId) + return + } + + val notification = NotificationCompat.Builder(this, SERVICE_RESULT_CHANNEL) + .setContentTitle(getText(R.string.profile_status_title)) + .setContentText(getString(R.string.profile_status_update_failure, entity.name)) + .setSmallIcon(R.drawable.ic_update_normal) + .setOngoing(true) + .build() + + NotificationManagerCompat.from(this) + .notify(SERVICE_NOTIFICATION_ID_BASE + notificationId, notification) } } \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/ProfileProcessService.kt b/service/src/main/java/com/github/kr328/clash/service/ProfileProcessService.kt index 2cd4ecd..4732082 100644 --- a/service/src/main/java/com/github/kr328/clash/service/ProfileProcessService.kt +++ b/service/src/main/java/com/github/kr328/clash/service/ProfileProcessService.kt @@ -59,6 +59,8 @@ class ProfileProcessService : BaseService() { private suspend fun handleRequest(request: ProfileRequest) { try { + request.callback?.send(null) + when (request.action) { ProfileRequest.Action.UPDATE_OR_CREATE -> handleUpdateOrCreate(request) diff --git a/service/src/main/java/com/github/kr328/clash/service/transact/ProfileRequest.kt b/service/src/main/java/com/github/kr328/clash/service/transact/ProfileRequest.kt index 717bc98..b604ccf 100644 --- a/service/src/main/java/com/github/kr328/clash/service/transact/ProfileRequest.kt +++ b/service/src/main/java/com/github/kr328/clash/service/transact/ProfileRequest.kt @@ -74,24 +74,6 @@ class ProfileRequest private constructor(private val bundle: Bundle) : Parcelabl } } - override fun equals(other: Any?): Boolean { - if (other !is ProfileRequest) - return false - - for (key in bundle.keySet()) { - if (bundle.get(key) != other.bundle.get(key)) - return false - } - - return true - } - - override fun hashCode(): Int { - return bundle.keySet() - .joinToString { it + bundle.get(it)?.hashCode() } - .hashCode() - } - override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeBundle(bundle) } diff --git a/service/src/main/java/com/github/kr328/clash/service/util/DefaultThreadPool.kt b/service/src/main/java/com/github/kr328/clash/service/util/DefaultThreadPool.kt deleted file mode 100644 index 85a7783..0000000 --- a/service/src/main/java/com/github/kr328/clash/service/util/DefaultThreadPool.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.github.kr328.clash.service.util - -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors - -val DefaultThreadPool: ExecutorService = Executors.newCachedThreadPool() \ No newline at end of file diff --git a/service/src/main/res/drawable/ic_update_completed.xml b/service/src/main/res/drawable/ic_update_normal.xml similarity index 100% rename from service/src/main/res/drawable/ic_update_completed.xml rename to service/src/main/res/drawable/ic_update_normal.xml diff --git a/service/src/main/res/values/strings.xml b/service/src/main/res/values/strings.xml index df3547d..d5ff463 100644 --- a/service/src/main/res/values/strings.xml +++ b/service/src/main/res/values/strings.xml @@ -14,14 +14,13 @@ "%1$s↑\t%2$s↓" Profile Service Status - Processing Profiles - Waiting Request - Updating %s - Profile Service Result - Process Result - Update %s Completed - Update %s Failure - Profile %s deleted + Profile Updater Running + + Profile Processing Status + Processing Profile + Updating %s + Update %s Failure + Update %s Completed Clash Core Service Tun Device Service