瀏覽代碼

ui: wizard: Implement the download process

Peter Cai 1 年之前
父節點
當前提交
5b079c95ac

+ 8 - 0
app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt

@@ -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

+ 10 - 1
app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt

@@ -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
@@ -55,7 +57,9 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
             "",
             null,
             null,
-            null
+            null,
+            false,
+            -1
         )
 
         progressBar = requireViewById(R.id.progress)
@@ -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) {
@@ -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() {

+ 115 - 2
app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardProgressFragment.kt

@@ -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,
@@ -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(
@@ -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
 
@@ -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 =