浏览代码

Refactor automatic notification handling

Sometimes there could be more than one notification queued for each
operation.
Peter Cai 2 年之前
父节点
当前提交
6784eae43b

+ 4 - 8
app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt

@@ -152,19 +152,15 @@ open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfi
     }
     }
 
 
     private suspend fun doEnableProfile(iccid: String) =
     private suspend fun doEnableProfile(iccid: String) =
-        withContext(Dispatchers.IO) {
+        channel.lpa.beginOperation {
             channel.lpa.enableProfile(iccid)
             channel.lpa.enableProfile(iccid)
-            if (preferenceRepository.notificationEnableFlow.first()) {
-                channel.lpa.handleLatestNotification(LocalProfileNotification.Operation.Enable)
-            }
+            preferenceRepository.notificationEnableFlow.first()
         }
         }
 
 
     private suspend fun doDisableProfile(iccid: String) =
     private suspend fun doDisableProfile(iccid: String) =
-        withContext(Dispatchers.IO) {
+        channel.lpa.beginOperation {
             channel.lpa.disableProfile(iccid)
             channel.lpa.disableProfile(iccid)
-            if (preferenceRepository.notificationDisableFlow.first()) {
-                channel.lpa.handleLatestNotification(LocalProfileNotification.Operation.Disable)
-            }
+            preferenceRepository.notificationDisableFlow.first()
         }
         }
 
 
     sealed class ViewHolder(root: View) : RecyclerView.ViewHolder(root) {
     sealed class ViewHolder(root: View) : RecyclerView.ViewHolder(root) {

+ 2 - 4
app-common/src/main/java/im/angry/openeuicc/ui/ProfileDeleteFragment.kt

@@ -73,10 +73,8 @@ class ProfileDeleteFragment : DialogFragment(), EuiccFragmentMarker {
         }
         }
     }
     }
 
 
-    private suspend fun doDelete() = withContext(Dispatchers.IO) {
+    private suspend fun doDelete() = channel.lpa.beginOperation {
         channel.lpa.deleteProfile(requireArguments().getString("iccid")!!)
         channel.lpa.deleteProfile(requireArguments().getString("iccid")!!)
-        if (preferenceRepository.notificationDeleteFlow.first()) {
-            channel.lpa.handleLatestNotification(LocalProfileNotification.Operation.Delete)
-        }
+        preferenceRepository.notificationDeleteFlow.first()
     }
     }
 }
 }

+ 4 - 5
app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt

@@ -184,8 +184,8 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
         }
         }
     }
     }
 
 
-    private suspend fun doDownloadProfile(server: String, code: String?, confirmationCode: String?, imei: String?) = withContext(Dispatchers.IO) {
-        channel.lpa.downloadProfile(server, code, imei, confirmationCode, object : ProfileDownloadCallback {
+    private suspend fun doDownloadProfile(server: String, code: String?, confirmationCode: String?, imei: String?) = channel.lpa.beginOperation {
+        downloadProfile(server, code, imei, confirmationCode, object : ProfileDownloadCallback {
             override fun onStateUpdate(state: ProfileDownloadCallback.DownloadState) {
             override fun onStateUpdate(state: ProfileDownloadCallback.DownloadState) {
                 lifecycleScope.launch(Dispatchers.Main) {
                 lifecycleScope.launch(Dispatchers.Main) {
                     progress.isIndeterminate = false
                     progress.isIndeterminate = false
@@ -195,8 +195,7 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
         })
         })
 
 
         // If we get here, we are successful
         // If we get here, we are successful
-        if (preferenceRepository.notificationDownloadFlow.first()) {
-            channel.lpa.handleLatestNotification(LocalProfileNotification.Operation.Install)
-        }
+        // Only send notifications if the user allowed us to
+        preferenceRepository.notificationDownloadFlow.first()
     }
     }
 }
 }

+ 28 - 3
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt

@@ -1,12 +1,22 @@
 package net.typeblog.lpac_jni
 package net.typeblog.lpac_jni
 
 
+import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
 interface LocalProfileAssistant {
 interface LocalProfileAssistant {
+    companion object {
+        private const val TAG = "LocalProfileAssistant"
+    }
+
     val profiles: List<LocalProfileInfo>
     val profiles: List<LocalProfileInfo>
     val notifications: List<LocalProfileNotification>
     val notifications: List<LocalProfileNotification>
     val eID: String
     val eID: String
     // Extended EuiccInfo for use with LUIs, containing information such as firmware version
     // Extended EuiccInfo for use with LUIs, containing information such as firmware version
     val euiccInfo2: EuiccInfo2?
     val euiccInfo2: EuiccInfo2?
 
 
+    // All blocking functions in this class assume that they are executed on non-Main threads
+    // The IO context in Kotlin's coroutine library is recommended.
     fun enableProfile(iccid: String): Boolean
     fun enableProfile(iccid: String): Boolean
     fun disableProfile(iccid: String): Boolean
     fun disableProfile(iccid: String): Boolean
     fun deleteProfile(iccid: String): Boolean
     fun deleteProfile(iccid: String): Boolean
@@ -16,9 +26,24 @@ interface LocalProfileAssistant {
 
 
     fun deleteNotification(seqNumber: Long): Boolean
     fun deleteNotification(seqNumber: Long): Boolean
     fun handleNotification(seqNumber: Long): Boolean
     fun handleNotification(seqNumber: Long): Boolean
-    // Handle the latest entry of a particular type of notification
-    // Note that this is not guaranteed to always be reliable and no feedback will be provided on errors.
-    fun handleLatestNotification(operation: LocalProfileNotification.Operation)
+
+    // Wraps an operation on the eSIM chip (any of the other blocking functions)
+    // Handles notifications automatically after the operation, unless the lambda executing
+    // the operation returns false, which inhibits automatic notification processing.
+    // All code executed within are also wrapped automatically in the IO context.
+    suspend fun beginOperation(op: suspend LocalProfileAssistant.() -> Boolean) =
+        withContext(Dispatchers.IO) {
+            val latestSeq = notifications.firstOrNull()?.seqNumber ?: 0
+            Log.d(TAG, "Latest notification is $latestSeq before operation")
+            if (op(this@LocalProfileAssistant)) {
+                Log.d(TAG, "Operation has requested notification handling")
+                notifications.filter { it.seqNumber > latestSeq }.forEach {
+                    Log.d(TAG, "Handling notification $it")
+                    handleNotification(it.seqNumber)
+                }
+            }
+            Log.d(TAG, "Operation complete")
+        }
 
 
     fun setNickname(
     fun setNickname(
         iccid: String, nickname: String
         iccid: String, nickname: String

+ 0 - 7
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt

@@ -64,13 +64,6 @@ class LocalProfileAssistantImpl(
             Log.d(TAG, "handleNotification $seqNumber = $it")
             Log.d(TAG, "handleNotification $seqNumber = $it")
         } == 0
         } == 0
 
 
-    override fun handleLatestNotification(operation: LocalProfileNotification.Operation) {
-        notifications.find { it.profileManagementOperation == operation }?.let {
-            Log.d(TAG, "handleLatestNotification: $it")
-            handleNotification(it.seqNumber)
-        }
-    }
-
     override fun setNickname(iccid: String, nickname: String): Boolean {
     override fun setNickname(iccid: String, nickname: String): Boolean {
         return LpacJni.es10cSetNickname(contextHandle, iccid, nickname) == 0
         return LpacJni.es10cSetNickname(contextHandle, iccid, nickname) == 0
     }
     }