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 im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
@ -98,6 +100,25 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
private val foregroundTaskState: MutableStateFlow<ForegroundTaskState> = private val foregroundTaskState: MutableStateFlow<ForegroundTaskState> =
MutableStateFlow(ForegroundTaskState.Idle) 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 { override fun onBind(intent: Intent): IBinder {
super.onBind(intent) super.onBind(intent)
return LocalBinder() return LocalBinder()
@ -194,7 +215,9 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
failureTitle: String, failureTitle: String,
iconRes: Int, iconRes: Int,
task: suspend EuiccChannelManagerService.() -> Unit task: suspend EuiccChannelManagerService.() -> Unit
): Flow<ForegroundTaskState> { ): ForegroundTaskSubscriberFlow {
val taskID = System.currentTimeMillis()
// Atomically set the state to InProgress. If this returns true, we are // Atomically set the state to InProgress. If this returns true, we are
// the only task currently in progress. // the only task currently in progress.
if (!foregroundTaskState.compareAndSet( if (!foregroundTaskState.compareAndSet(
@ -202,7 +225,9 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
ForegroundTaskState.InProgress(0) 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) { 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. // 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 // The state update back to Idle won't show up in the returned stream, because
// it has been completed by that point. // it has been completed by that point.
return foregroundTaskState.transformWhile { val subscriberFlow = foregroundTaskState
.transformWhile {
// Also update our notification when we see an update // Also update our notification when we see an update
// But ignore the first progress = 0 update -- that is the current value. // 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. // 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 } }.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 val isForegroundTaskRunning: Boolean
@ -289,7 +333,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
matchingId: String?, matchingId: String?,
confirmationCode: String?, confirmationCode: String?,
imei: String? imei: String?
): Flow<ForegroundTaskState> = ): ForegroundTaskSubscriberFlow =
launchForegroundTask( launchForegroundTask(
getString(R.string.task_profile_download), getString(R.string.task_profile_download),
getString(R.string.task_profile_download_failure), getString(R.string.task_profile_download_failure),
@ -325,7 +369,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
portId: Int, portId: Int,
iccid: String, iccid: String,
name: String name: String
): Flow<ForegroundTaskState> = ): ForegroundTaskSubscriberFlow =
launchForegroundTask( launchForegroundTask(
getString(R.string.task_profile_rename), getString(R.string.task_profile_rename),
getString(R.string.task_profile_rename_failure), getString(R.string.task_profile_rename_failure),
@ -347,7 +391,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
slotId: Int, slotId: Int,
portId: Int, portId: Int,
iccid: String iccid: String
): Flow<ForegroundTaskState> = ): ForegroundTaskSubscriberFlow =
launchForegroundTask( launchForegroundTask(
getString(R.string.task_profile_delete), getString(R.string.task_profile_delete),
getString(R.string.task_profile_delete_failure), getString(R.string.task_profile_delete_failure),
@ -370,7 +414,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
iccid: String, iccid: String,
enable: Boolean, // Enable or disable the profile indicated in iccid enable: Boolean, // Enable or disable the profile indicated in iccid
reconnectTimeoutMillis: Long = 0 // 0 = do not wait for reconnect, useful for USB readers reconnectTimeoutMillis: Long = 0 // 0 = do not wait for reconnect, useful for USB readers
): Flow<ForegroundTaskState> = ): ForegroundTaskSubscriberFlow =
launchForegroundTask( launchForegroundTask(
getString(R.string.task_profile_switch), getString(R.string.task_profile_switch),
getString(R.string.task_profile_switch_failure), getString(R.string.task_profile_switch_failure),