Compare commits

...

2 commits

Author SHA1 Message Date
f2c233fe1c EuiccChannelManagerService: Introduce IDs for tasks
All checks were successful
/ build-debug (push) Successful in 4m25s
2024-11-24 10:42:02 -05:00
3507c17834 EuiccChannalManagerService: manually buffer the returned flow 2024-11-24 10:18:54 -05:00

View file

@ -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,7 +274,8 @@ 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 {
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.
@ -272,6 +298,24 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
)
}
}.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)
}
}
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),