add profile process service
This commit is contained in:
6 files changed
+146
-114
No files matched your search
@@ -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<ProfileRequest>(2)
|
||||
private val queue = mutableListOf<Deferred<ProfileRequest>>()
|
||||
private val queue = mutableListOf<CompletableDeferred<ProfileRequest>>()
|
||||
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<Unit> {
|
||||
channel.onReceive {
|
||||
val deferred = CompletableDeferred<ProfileRequest>()
|
||||
|
||||
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<ProfileRequest>(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<ProfileRequest>(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<Unit> {
|
||||
channel.onReceive {
|
||||
val deferred = CompletableDeferred<ProfileRequest>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
File renamed without changes.
@@ -14,14 +14,13 @@
|
||||
<string name="clash_notification_content">"%1$s↑\t%2$s↓"</string>
|
||||
|
||||
<string name="profile_service_status_channel">Profile Service Status</string>
|
||||
<string name="profile_service_status_title">Processing Profiles</string>
|
||||
<string name="profile_service_status_waiting">Waiting Request</string>
|
||||
<string name="profile_service_status_updating">Updating %s</string>
|
||||
<string name="profile_service_result">Profile Service Result</string>
|
||||
<string name="profile_process_result">Process Result</string>
|
||||
<string name="profile_update_completed">Update %s Completed</string>
|
||||
<string name="profile_update_failure">Update %s Failure</string>
|
||||
<string name="profile_deleted">Profile %s deleted</string>
|
||||
<string name="profile_service_status_title">Profile Updater Running</string>
|
||||
|
||||
<string name="profile_status_channel">Profile Processing Status</string>
|
||||
<string name="profile_status_title">Processing Profile</string>
|
||||
<string name="profile_status_updating">Updating %s</string>
|
||||
<string name="profile_status_update_failure">Update %s Failure</string>
|
||||
<string name="profile_status_update_completed">Update %s Completed</string>
|
||||
|
||||
<string name="clash_service_label">Clash Core Service</string>
|
||||
<string name="tun_service_label">Tun Device Service</string>
|
||||
|
||||
Reference in new issue
Block a user