Browse Source

Refactor OMAPI SEService creation

Peter Cai 2 years ago
parent
commit
5926a6601d

+ 1 - 16
app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt

@@ -1,8 +1,6 @@
 package im.angry.openeuicc.core
 
 import android.content.Context
-import android.os.Handler
-import android.os.HandlerThread
 import android.se.omapi.SEService
 import android.telephony.SubscriptionManager
 import android.util.Log
@@ -14,8 +12,6 @@ import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
 import java.lang.IllegalArgumentException
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
 
 open class EuiccChannelManager(protected val context: Context) {
     companion object {
@@ -32,23 +28,12 @@ open class EuiccChannelManager(protected val context: Context) {
         (context.applicationContext as OpenEuiccApplication).telephonyManager
     }
 
-    private val handler = Handler(HandlerThread("BaseEuiccChannelManager").also { it.start() }.looper)
-
     protected open val uiccCards: Collection<UiccCardInfoCompat>
         get() = (0..<tm.activeModemCountCompat).map { FakeUiccCardInfoCompat(it) }
 
-    private suspend fun connectSEService(): SEService = suspendCoroutine { cont ->
-        handler.post {
-            var service: SEService? = null
-            service = SEService(context, { handler.post(it) }) {
-                cont.resume(service!!)
-            }
-        }
-    }
-
     private suspend fun ensureSEService() {
          if (seService == null) {
-             seService = connectSEService()
+             seService = connectSEService(context)
          }
     }
 

+ 35 - 0
app-common/src/main/java/im/angry/openeuicc/util/TelephonyCompat.kt

@@ -1,9 +1,16 @@
 package im.angry.openeuicc.util
 
+import android.content.Context
 import android.os.Build
 import android.se.omapi.Reader
 import android.se.omapi.SEService
 import android.telephony.TelephonyManager
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
 
 val TelephonyManager.activeModemCountCompat: Int
     get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@@ -20,6 +27,34 @@ fun SEService.getUiccReaderCompat(slotNumber: Int): Reader {
     }
 }
 
+// Create an instance of OMAPI SEService in a manner that "makes sense" without unpredictable callbacks
+suspend fun connectSEService(context: Context): SEService = suspendCoroutine { cont ->
+    // Use a Mutex to make sure the continuation is run *after* the "service" variable is assigned
+    val lock = Mutex()
+    var service: SEService? = null
+    val callback = {
+        runBlocking {
+            lock.withLock {
+                cont.resume(service!!)
+            }
+        }
+    }
+
+    runBlocking {
+        // If this were not protected by a Mutex, callback might be run before service is even assigned
+        // Yes, we are on Android, we could have used something like a Handler, but we cannot really
+        // assume the coroutine is run on a thread that has a Handler. We either use our own HandlerThread
+        // (and then cleanup becomes an issue), or we use a lock
+        lock.withLock {
+            try {
+                service = SEService(context, { it.run() }, callback)
+            } catch (e: Exception) {
+                cont.resumeWithException(e)
+            }
+        }
+    }
+}
+
 /*
  * In the privileged version, the EuiccChannelManager should work
  * based on real Uicc{Card,Port}Info reported by TelephonyManager.

+ 1 - 18
app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt

@@ -3,14 +3,11 @@ package im.angry.openeuicc.util
 import android.content.Context
 import android.content.pm.PackageManager
 import android.os.Build
-import android.se.omapi.SEService
 import android.telephony.TelephonyManager
 import im.angry.easyeuicc.R
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.withContext
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
 
 fun getCompatibilityChecks(context: Context): List<CompatibilityCheck> =
     listOf(
@@ -95,22 +92,8 @@ internal class OmapiConnCheck(private val context: Context): CompatibilityCheck(
     override val defaultDescription: String
         get() = context.getString(R.string.compatibility_check_omapi_connectivity_desc)
 
-    private suspend fun getSEService(): SEService = suspendCoroutine { cont ->
-        var service: SEService? = null
-        var resumed = false
-        val resume = {
-            if (!resumed && service != null) {
-                cont.resume(service!!)
-                resumed = true
-            }
-        }
-        service = SEService(context, { it.run() }, { resume() })
-        Thread.sleep(1000)
-        resume()
-    }
-
     override suspend fun doCheck(): Boolean {
-        val seService = getSEService()
+        val seService = connectSEService(context)
         if (!seService.isConnected) {
             failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail)
             return false