Loading app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt +8 −0 Original line number Diff line number Diff line Loading @@ -196,6 +196,14 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker { NotificationManagerCompat.from(this).notify(TASK_FAILURE_ID, notification) } /** * Recover the subscriber to a foreground task that is recently launched. * * null if the task doesn't exist, or was launched too long ago. */ fun recoverForegroundTaskSubscriber(taskId: Long): ForegroundTaskSubscriberFlow? = foregroundTaskSubscribers[taskId] /** * Launch a potentially blocking foreground task in this service's lifecycle context. * This function does not block, but returns a Flow that emits ForegroundTaskState Loading app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt +10 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { var matchingId: String?, var confirmationCode: String?, var imei: String?, var downloadStarted: Boolean, var downloadTaskID: Long, ) private lateinit var state: DownloadWizardState Loading Loading @@ -55,7 +57,9 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { "", null, null, null null, false, -1 ) progressBar = requireViewById(R.id.progress) Loading Loading @@ -104,6 +108,8 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { outState.putString("matchingId", state.matchingId) outState.putString("confirmationCode", state.confirmationCode) outState.putString("imei", state.imei) outState.putBoolean("downloadStarted", state.downloadStarted) outState.putLong("downloadTaskID", state.downloadTaskID) } override fun onRestoreInstanceState(savedInstanceState: Bundle) { Loading @@ -117,6 +123,9 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { state.smdp = savedInstanceState.getString("smdp", state.smdp) state.matchingId = savedInstanceState.getString("matchingId", state.matchingId) state.imei = savedInstanceState.getString("imei", state.imei) state.downloadStarted = savedInstanceState.getBoolean("downloadStarted", state.downloadStarted) state.downloadTaskID = savedInstanceState.getLong("downloadTaskID", state.downloadTaskID) } private fun onPrevPressed() { Loading app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardProgressFragment.kt +115 −2 Original line number Diff line number Diff line Loading @@ -7,12 +7,32 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import im.angry.openeuicc.common.R import im.angry.openeuicc.service.EuiccChannelManagerService import im.angry.openeuicc.util.* import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import net.typeblog.lpac_jni.ProfileDownloadCallback class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStepFragment() { companion object { /** * An array of LPA-side state types, mapping 1:1 to progressItems */ val LPA_PROGRESS_STATES = arrayOf( ProfileDownloadCallback.DownloadState.Preparing, ProfileDownloadCallback.DownloadState.Connecting, ProfileDownloadCallback.DownloadState.Authenticating, ProfileDownloadCallback.DownloadState.Downloading, ProfileDownloadCallback.DownloadState.Finalizing, ) } private enum class ProgressState { NotStarted, InProgress, Loading @@ -22,7 +42,7 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep private data class ProgressItem( val titleRes: Int, val state: ProgressState var state: ProgressState ) private val progressItems = arrayOf( Loading @@ -38,8 +58,10 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep private val adapter = ProgressItemAdapter() private var isDone = false override val hasNext: Boolean get() = false get() = isDone override val hasPrev: Boolean get() = false Loading @@ -66,6 +88,97 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep return view } override fun onStart() { super.onStart() lifecycleScope.launch { showProgressBar(-1) // set indeterminate first ensureEuiccChannelManager() val subscriber = startDownloadOrSubscribe() if (subscriber == null) { requireActivity().finish() return@launch } subscriber.onEach { when (it) { is EuiccChannelManagerService.ForegroundTaskState.Done -> { hideProgressBar() // Change the state of the last InProgress item to Error progressItems.forEachIndexed { index, progressItem -> if (progressItem.state == ProgressState.InProgress) { progressItem.state = ProgressState.Error } adapter.notifyItemChanged(index) } isDone = true refreshButtons() } is EuiccChannelManagerService.ForegroundTaskState.InProgress -> { updateProgress(it.progress) } else -> {} } }.collect() } } private suspend fun startDownloadOrSubscribe(): EuiccChannelManagerService.ForegroundTaskSubscriberFlow? = if (state.downloadStarted) { // This will also return null if task ID is -1 (uninitialized), too euiccChannelManagerService.recoverForegroundTaskSubscriber(state.downloadTaskID) } else { euiccChannelManagerService.waitForForegroundTask() val (slotId, portId) = euiccChannelManager.withEuiccChannel(state.selectedLogicalSlot) { channel -> Pair(channel.slotId, channel.portId) } // Set started to true even before we start -- in case we get killed in the middle state.downloadStarted = true val ret = euiccChannelManagerService.launchProfileDownloadTask( slotId, portId, state.smdp, state.matchingId, state.confirmationCode, state.imei ) state.downloadTaskID = ret.taskId ret } private fun updateProgress(progress: Int) { showProgressBar(progress) val lpaState = ProfileDownloadCallback.lookupStateFromProgress(progress) val stateIndex = LPA_PROGRESS_STATES.indexOf(lpaState) if (stateIndex > 0) { for (i in (0..<stateIndex)) { if (progressItems[i].state != ProgressState.Done) { progressItems[i].state = ProgressState.Done adapter.notifyItemChanged(i) } } } if (progressItems[stateIndex].state != ProgressState.InProgress) { progressItems[stateIndex].state = ProgressState.InProgress adapter.notifyItemChanged(stateIndex) } } private inner class ProgressItemHolder(val root: View) : RecyclerView.ViewHolder(root) { private val title = root.requireViewById<TextView>(R.id.download_progress_item_title) private val progressBar = Loading Loading
app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt +8 −0 Original line number Diff line number Diff line Loading @@ -196,6 +196,14 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker { NotificationManagerCompat.from(this).notify(TASK_FAILURE_ID, notification) } /** * Recover the subscriber to a foreground task that is recently launched. * * null if the task doesn't exist, or was launched too long ago. */ fun recoverForegroundTaskSubscriber(taskId: Long): ForegroundTaskSubscriberFlow? = foregroundTaskSubscribers[taskId] /** * Launch a potentially blocking foreground task in this service's lifecycle context. * This function does not block, but returns a Flow that emits ForegroundTaskState Loading
app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt +10 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { var matchingId: String?, var confirmationCode: String?, var imei: String?, var downloadStarted: Boolean, var downloadTaskID: Long, ) private lateinit var state: DownloadWizardState Loading Loading @@ -55,7 +57,9 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { "", null, null, null null, false, -1 ) progressBar = requireViewById(R.id.progress) Loading Loading @@ -104,6 +108,8 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { outState.putString("matchingId", state.matchingId) outState.putString("confirmationCode", state.confirmationCode) outState.putString("imei", state.imei) outState.putBoolean("downloadStarted", state.downloadStarted) outState.putLong("downloadTaskID", state.downloadTaskID) } override fun onRestoreInstanceState(savedInstanceState: Bundle) { Loading @@ -117,6 +123,9 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { state.smdp = savedInstanceState.getString("smdp", state.smdp) state.matchingId = savedInstanceState.getString("matchingId", state.matchingId) state.imei = savedInstanceState.getString("imei", state.imei) state.downloadStarted = savedInstanceState.getBoolean("downloadStarted", state.downloadStarted) state.downloadTaskID = savedInstanceState.getLong("downloadTaskID", state.downloadTaskID) } private fun onPrevPressed() { Loading
app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardProgressFragment.kt +115 −2 Original line number Diff line number Diff line Loading @@ -7,12 +7,32 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import im.angry.openeuicc.common.R import im.angry.openeuicc.service.EuiccChannelManagerService import im.angry.openeuicc.util.* import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import net.typeblog.lpac_jni.ProfileDownloadCallback class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStepFragment() { companion object { /** * An array of LPA-side state types, mapping 1:1 to progressItems */ val LPA_PROGRESS_STATES = arrayOf( ProfileDownloadCallback.DownloadState.Preparing, ProfileDownloadCallback.DownloadState.Connecting, ProfileDownloadCallback.DownloadState.Authenticating, ProfileDownloadCallback.DownloadState.Downloading, ProfileDownloadCallback.DownloadState.Finalizing, ) } private enum class ProgressState { NotStarted, InProgress, Loading @@ -22,7 +42,7 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep private data class ProgressItem( val titleRes: Int, val state: ProgressState var state: ProgressState ) private val progressItems = arrayOf( Loading @@ -38,8 +58,10 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep private val adapter = ProgressItemAdapter() private var isDone = false override val hasNext: Boolean get() = false get() = isDone override val hasPrev: Boolean get() = false Loading @@ -66,6 +88,97 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep return view } override fun onStart() { super.onStart() lifecycleScope.launch { showProgressBar(-1) // set indeterminate first ensureEuiccChannelManager() val subscriber = startDownloadOrSubscribe() if (subscriber == null) { requireActivity().finish() return@launch } subscriber.onEach { when (it) { is EuiccChannelManagerService.ForegroundTaskState.Done -> { hideProgressBar() // Change the state of the last InProgress item to Error progressItems.forEachIndexed { index, progressItem -> if (progressItem.state == ProgressState.InProgress) { progressItem.state = ProgressState.Error } adapter.notifyItemChanged(index) } isDone = true refreshButtons() } is EuiccChannelManagerService.ForegroundTaskState.InProgress -> { updateProgress(it.progress) } else -> {} } }.collect() } } private suspend fun startDownloadOrSubscribe(): EuiccChannelManagerService.ForegroundTaskSubscriberFlow? = if (state.downloadStarted) { // This will also return null if task ID is -1 (uninitialized), too euiccChannelManagerService.recoverForegroundTaskSubscriber(state.downloadTaskID) } else { euiccChannelManagerService.waitForForegroundTask() val (slotId, portId) = euiccChannelManager.withEuiccChannel(state.selectedLogicalSlot) { channel -> Pair(channel.slotId, channel.portId) } // Set started to true even before we start -- in case we get killed in the middle state.downloadStarted = true val ret = euiccChannelManagerService.launchProfileDownloadTask( slotId, portId, state.smdp, state.matchingId, state.confirmationCode, state.imei ) state.downloadTaskID = ret.taskId ret } private fun updateProgress(progress: Int) { showProgressBar(progress) val lpaState = ProfileDownloadCallback.lookupStateFromProgress(progress) val stateIndex = LPA_PROGRESS_STATES.indexOf(lpaState) if (stateIndex > 0) { for (i in (0..<stateIndex)) { if (progressItems[i].state != ProgressState.Done) { progressItems[i].state = ProgressState.Done adapter.notifyItemChanged(i) } } } if (progressItems[stateIndex].state != ProgressState.InProgress) { progressItems[stateIndex].state = ProgressState.InProgress adapter.notifyItemChanged(stateIndex) } } private inner class ProgressItemHolder(val root: View) : RecyclerView.ViewHolder(root) { private val title = root.requireViewById<TextView>(R.id.download_progress_item_title) private val progressBar = Loading