Compare commits
2 commits
b2abe5ee84
...
f2c233fe1c
Author | SHA1 | Date | |
---|---|---|---|
f2c233fe1c | |||
3507c17834 |
1 changed files with 70 additions and 26 deletions
|
@ -15,10 +15,12 @@ import im.angry.openeuicc.core.EuiccChannelManager
|
|||
import im.angry.openeuicc.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.buffer
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
@ -98,6 +100,25 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
|
|||
private val foregroundTaskState: MutableStateFlow<ForegroundTaskState> =
|
||||
MutableStateFlow(ForegroundTaskState.Idle)
|
||||
|
||||
/**
|
||||
* A simple wrapper over a flow with taskId added.
|
||||
*
|
||||
* taskID is the exact millisecond-precision timestamp when the task is launched.
|
||||
*/
|
||||
class ForegroundTaskSubscriberFlow(val taskId: Long, inner: Flow<ForegroundTaskState>) :
|
||||
Flow<ForegroundTaskState> by inner
|
||||
|
||||
/**
|
||||
* A cache of subscribers to 5 recently-launched foreground tasks, identified by ID
|
||||
*
|
||||
* Only one can be run at the same time, but those that are done will be kept in this
|
||||
* map for a little while -- because UI components may be stopped and recreated while
|
||||
* tasks are running. Having this buffer allows the components to re-subscribe even if
|
||||
* the task completes while they are being recreated.
|
||||
*/
|
||||
private val foregroundTaskSubscribers: MutableMap<Long, ForegroundTaskSubscriberFlow> =
|
||||
mutableMapOf()
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
super.onBind(intent)
|
||||
return LocalBinder()
|
||||
|
@ -194,7 +215,9 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
|
|||
failureTitle: String,
|
||||
iconRes: Int,
|
||||
task: suspend EuiccChannelManagerService.() -> Unit
|
||||
): Flow<ForegroundTaskState> {
|
||||
): ForegroundTaskSubscriberFlow {
|
||||
val taskID = System.currentTimeMillis()
|
||||
|
||||
// Atomically set the state to InProgress. If this returns true, we are
|
||||
// the only task currently in progress.
|
||||
if (!foregroundTaskState.compareAndSet(
|
||||
|
@ -202,7 +225,9 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
|
|||
ForegroundTaskState.InProgress(0)
|
||||
)
|
||||
) {
|
||||
return flow { emit(ForegroundTaskState.Done(IllegalStateException("There are tasks currently running"))) }
|
||||
return ForegroundTaskSubscriberFlow(
|
||||
taskID,
|
||||
flow { emit(ForegroundTaskState.Done(IllegalStateException("There are tasks currently running"))) })
|
||||
}
|
||||
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
|
@ -249,29 +274,48 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
|
|||
// Then, we complete the returned flow, but we also set the state back to Idle.
|
||||
// The state update back to Idle won't show up in the returned stream, because
|
||||
// it has been completed by that point.
|
||||
return foregroundTaskState.transformWhile {
|
||||
// Also update our notification when we see an update
|
||||
// But ignore the first progress = 0 update -- that is the current value.
|
||||
// we need that to be handled by the main coroutine after it finishes.
|
||||
if (it !is ForegroundTaskState.InProgress || it.progress != 0) {
|
||||
withContext(Dispatchers.Main) {
|
||||
updateForegroundNotification(title, iconRes)
|
||||
val subscriberFlow = foregroundTaskState
|
||||
.transformWhile {
|
||||
// Also update our notification when we see an update
|
||||
// But ignore the first progress = 0 update -- that is the current value.
|
||||
// we need that to be handled by the main coroutine after it finishes.
|
||||
if (it !is ForegroundTaskState.InProgress || it.progress != 0) {
|
||||
withContext(Dispatchers.Main) {
|
||||
updateForegroundNotification(title, iconRes)
|
||||
}
|
||||
}
|
||||
}
|
||||
emit(it)
|
||||
it !is ForegroundTaskState.Done
|
||||
}.onStart {
|
||||
// When this Flow is started, we unblock the coroutine launched above by
|
||||
// self-starting as a foreground service.
|
||||
withContext(Dispatchers.Main) {
|
||||
startForegroundService(
|
||||
Intent(
|
||||
this@EuiccChannelManagerService,
|
||||
this@EuiccChannelManagerService::class.java
|
||||
emit(it)
|
||||
it !is ForegroundTaskState.Done
|
||||
}.onStart {
|
||||
// When this Flow is started, we unblock the coroutine launched above by
|
||||
// self-starting as a foreground service.
|
||||
withContext(Dispatchers.Main) {
|
||||
startForegroundService(
|
||||
Intent(
|
||||
this@EuiccChannelManagerService,
|
||||
this@EuiccChannelManagerService::class.java
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}.onCompletion { foregroundTaskState.value = ForegroundTaskState.Idle }
|
||||
// Buffer the returned flow by 2, so that if there is an error,
|
||||
// we always get a copy of the last process update before completion.
|
||||
// This also guarantees that our onCompletion callback is always run
|
||||
// even if the returned flow isn't subscribed to
|
||||
.buffer(capacity = 2, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
|
||||
val ret = ForegroundTaskSubscriberFlow(taskID, subscriberFlow)
|
||||
foregroundTaskSubscribers[taskID] = ret
|
||||
|
||||
if (foregroundTaskSubscribers.size > 5) {
|
||||
// Remove enough elements so that the size is kept at 5
|
||||
for (key in foregroundTaskSubscribers.keys.sorted()
|
||||
.take(foregroundTaskSubscribers.size - 5)) {
|
||||
foregroundTaskSubscribers.remove(key)
|
||||
}
|
||||
}.onCompletion { foregroundTaskState.value = ForegroundTaskState.Idle }
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
val isForegroundTaskRunning: Boolean
|
||||
|
@ -289,7 +333,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
|
|||
matchingId: String?,
|
||||
confirmationCode: String?,
|
||||
imei: String?
|
||||
): Flow<ForegroundTaskState> =
|
||||
): ForegroundTaskSubscriberFlow =
|
||||
launchForegroundTask(
|
||||
getString(R.string.task_profile_download),
|
||||
getString(R.string.task_profile_download_failure),
|
||||
|
@ -325,7 +369,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
|
|||
portId: Int,
|
||||
iccid: String,
|
||||
name: String
|
||||
): Flow<ForegroundTaskState> =
|
||||
): ForegroundTaskSubscriberFlow =
|
||||
launchForegroundTask(
|
||||
getString(R.string.task_profile_rename),
|
||||
getString(R.string.task_profile_rename_failure),
|
||||
|
@ -347,7 +391,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
|
|||
slotId: Int,
|
||||
portId: Int,
|
||||
iccid: String
|
||||
): Flow<ForegroundTaskState> =
|
||||
): ForegroundTaskSubscriberFlow =
|
||||
launchForegroundTask(
|
||||
getString(R.string.task_profile_delete),
|
||||
getString(R.string.task_profile_delete_failure),
|
||||
|
@ -370,7 +414,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
|
|||
iccid: String,
|
||||
enable: Boolean, // Enable or disable the profile indicated in iccid
|
||||
reconnectTimeoutMillis: Long = 0 // 0 = do not wait for reconnect, useful for USB readers
|
||||
): Flow<ForegroundTaskState> =
|
||||
): ForegroundTaskSubscriberFlow =
|
||||
launchForegroundTask(
|
||||
getString(R.string.task_profile_switch),
|
||||
getString(R.string.task_profile_switch_failure),
|
||||
|
|
Loading…
Add table
Reference in a new issue