浏览代码

refactor: Stop exitting the application when enabling/disabling profiles

..instead, attempt to reconnect the LPA. This is needed to properly
send notifications after the operation.
Peter Cai 2 年之前
父节点
当前提交
451b17d0a5

+ 6 - 10
app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt

@@ -127,7 +127,6 @@ open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfi
 
     private fun enableOrDisableProfile(iccid: String, enable: Boolean) {
         swipeRefresh.isRefreshing = true
-        swipeRefresh.isEnabled = false
         fab.isEnabled = false
 
         lifecycleScope.launch {
@@ -137,15 +136,12 @@ open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfi
                 } else {
                     doDisableProfile(iccid)
                 }
-                Toast.makeText(context, R.string.toast_profile_enabled, Toast.LENGTH_LONG).show()
-                // The APDU channel will be invalid when the SIM reboots. For now, just exit the app
-                euiccChannelManager.invalidate()
-                requireActivity().finish()
+                refresh()
+                fab.isEnabled = true
             } catch (e: Exception) {
                 Log.d(TAG, "Failed to enable / disable profile $iccid")
                 Log.d(TAG, Log.getStackTraceString(e))
                 fab.isEnabled = true
-                swipeRefresh.isEnabled = true
                 Toast.makeText(context, R.string.toast_profile_enable_failed, Toast.LENGTH_LONG).show()
             }
         }
@@ -153,14 +149,14 @@ open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfi
 
     private suspend fun doEnableProfile(iccid: String) =
         channel.lpa.beginOperation {
-            channel.lpa.enableProfile(iccid)
-            preferenceRepository.notificationEnableFlow.first()
+            channel.lpa.enableProfile(iccid, reconnectTimeout = 15 * 1000) &&
+                preferenceRepository.notificationEnableFlow.first()
         }
 
     private suspend fun doDisableProfile(iccid: String) =
         channel.lpa.beginOperation {
-            channel.lpa.disableProfile(iccid)
-            preferenceRepository.notificationDisableFlow.first()
+            channel.lpa.disableProfile(iccid, reconnectTimeout = 15 * 1000) &&
+                preferenceRepository.notificationDisableFlow.first()
         }
 
     sealed class ViewHolder(root: View) : RecyclerView.ViewHolder(root) {

+ 0 - 1
app-common/src/main/res/values/strings.xml

@@ -16,7 +16,6 @@
     <string name="delete">Delete</string>
     <string name="rename">Rename</string>
 
-    <string name="toast_profile_enabled">eSIM profile switched. Please wait for a while when the card is restarting.</string>
     <string name="toast_profile_enable_failed">Cannot switch to new eSIM profile.</string>
     <string name="toast_profile_name_too_long">Nickname cannot be longer than 64 characters</string>
 

+ 1 - 0
app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt

@@ -20,6 +20,7 @@ class TelephonyManagerApduInterface(
 
     override fun disconnect() {
         // Do nothing
+        lastChannel = -1
     }
 
     override fun logicalChannelOpen(aid: ByteArray): Int {

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

@@ -17,8 +17,8 @@ interface LocalProfileAssistant {
 
     // 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 disableProfile(iccid: String): Boolean
+    fun enableProfile(iccid: String, reconnectTimeout: Long = 0): Boolean
+    fun disableProfile(iccid: String, reconnectTimeout: Long = 0): Boolean
     fun deleteProfile(iccid: String): Boolean
 
     fun downloadProfile(smdp: String, matchingId: String?, imei: String?,

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

@@ -1,6 +1,9 @@
 package net.typeblog.lpac_jni.impl
 
 import android.util.Log
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
 import net.typeblog.lpac_jni.LpacJni
 import net.typeblog.lpac_jni.ApduInterface
 import net.typeblog.lpac_jni.EuiccInfo2
@@ -11,20 +14,52 @@ import net.typeblog.lpac_jni.LocalProfileNotification
 import net.typeblog.lpac_jni.ProfileDownloadCallback
 
 class LocalProfileAssistantImpl(
-    apduInterface: ApduInterface,
-    httpInterface: HttpInterface
+    private val apduInterface: ApduInterface,
+    private val httpInterface: HttpInterface
 ): LocalProfileAssistant {
     companion object {
         private const val TAG = "LocalProfileAssistantImpl"
     }
 
-    private val contextHandle: Long = LpacJni.createContext(apduInterface, httpInterface)
+    private var contextHandle: Long = LpacJni.createContext(apduInterface, httpInterface)
     init {
         if (LpacJni.es10xInit(contextHandle) < 0) {
             throw IllegalArgumentException("Failed to initialize LPA")
         }
     }
 
+    private fun tryReconnect(timeoutMillis: Long) = runBlocking {
+        withTimeout(timeoutMillis) {
+            try {
+                LpacJni.es10xFini(contextHandle)
+                LpacJni.destroyContext(contextHandle)
+            } catch (e: Exception) {
+                // Ignored
+            }
+
+            while (true) {
+                try {
+                    apduInterface.disconnect()
+                } catch (e: Exception) {
+                    // Ignored
+                }
+
+                try {
+                    apduInterface.connect()
+                    contextHandle = LpacJni.createContext(apduInterface, httpInterface)
+                    val res = LpacJni.es10xInit(contextHandle)
+                    Log.d(TAG, "$res")
+                    check(res >= 0) { "Reconnect attempt failed" }
+                    break
+                } catch (e: Exception) {
+                    e.printStackTrace()
+                    // continue retrying
+                    delay(1000)
+                }
+            }
+        }
+    }
+
     override val profiles: List<LocalProfileInfo>
         get() = LpacJni.es10cGetProfilesInfo(contextHandle)!!.asList()
 
@@ -39,12 +74,28 @@ class LocalProfileAssistantImpl(
     override val euiccInfo2: EuiccInfo2?
         get() = LpacJni.es10cexGetEuiccInfo2(contextHandle)
 
-    override fun enableProfile(iccid: String): Boolean {
-        return LpacJni.es10cEnableProfile(contextHandle, iccid) == 0
+    override fun enableProfile(iccid: String, reconnectTimeout: Long): Boolean {
+        val res = LpacJni.es10cEnableProfile(contextHandle, iccid) == 0
+        if (reconnectTimeout > 0) {
+            try {
+                tryReconnect(reconnectTimeout)
+            } catch (e: Exception) {
+                return false
+            }
+        }
+        return res
     }
 
-    override fun disableProfile(iccid: String): Boolean {
-        return LpacJni.es10cDisableProfile(contextHandle, iccid) == 0
+    override fun disableProfile(iccid: String, reconnectTimeout: Long): Boolean {
+        val res = LpacJni.es10cDisableProfile(contextHandle, iccid) == 0
+        if (reconnectTimeout > 0) {
+            try {
+                tryReconnect(reconnectTimeout)
+            } catch (e: Exception) {
+                return false
+            }
+        }
+        return res
     }
 
     override fun deleteProfile(iccid: String): Boolean {