ソースを参照

feat: Introduce option for global verbose logging

Peter Cai 1 年間 前
コミット
290bdca75a

+ 16 - 2
app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt

@@ -33,7 +33,15 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
 
         Log.i(DefaultEuiccChannelManager.TAG, "Trying OMAPI for physical slot ${port.card.physicalSlotIndex}")
         try {
-            return EuiccChannel(port, OmapiApduInterface(seService!!, port))
+            return EuiccChannel(
+                port,
+                OmapiApduInterface(
+                    seService!!,
+                    port,
+                    context.preferenceRepository.verboseLoggingFlow
+                ),
+                context.preferenceRepository.verboseLoggingFlow
+            )
         } catch (e: IllegalArgumentException) {
             // Failed
             Log.w(
@@ -52,7 +60,13 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
         if (!conn.claimInterface(usbInterface, true)) return null
         return EuiccChannel(
             FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)),
-            UsbApduInterface(conn, bulkIn, bulkOut)
+            UsbApduInterface(
+                conn,
+                bulkIn,
+                bulkOut,
+                context.preferenceRepository.verboseLoggingFlow
+            ),
+            context.preferenceRepository.verboseLoggingFlow
         )
     }
 

+ 4 - 1
app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt

@@ -1,6 +1,7 @@
 package im.angry.openeuicc.core
 
 import im.angry.openeuicc.util.*
+import kotlinx.coroutines.flow.Flow
 import net.typeblog.lpac_jni.ApduInterface
 import net.typeblog.lpac_jni.LocalProfileAssistant
 import net.typeblog.lpac_jni.impl.HttpInterfaceImpl
@@ -9,12 +10,14 @@ import net.typeblog.lpac_jni.impl.LocalProfileAssistantImpl
 class EuiccChannel(
     val port: UiccPortInfoCompat,
     apduInterface: ApduInterface,
+    verboseLoggingFlow: Flow<Boolean>
 ) {
     val slotId = port.card.physicalSlotIndex // PHYSICAL slot
     val logicalSlotId = port.logicalSlotIndex
     val portId = port.portIndex
 
-    val lpa: LocalProfileAssistant = LocalProfileAssistantImpl(apduInterface, HttpInterfaceImpl())
+    val lpa: LocalProfileAssistant =
+        LocalProfileAssistantImpl(apduInterface, HttpInterfaceImpl(verboseLoggingFlow))
 
     val valid: Boolean
         get() = lpa.valid

+ 12 - 3
app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt

@@ -5,11 +5,16 @@ import android.se.omapi.SEService
 import android.se.omapi.Session
 import android.util.Log
 import im.angry.openeuicc.util.*
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.single
+import kotlinx.coroutines.runBlocking
 import net.typeblog.lpac_jni.ApduInterface
 
 class OmapiApduInterface(
     private val service: SEService,
-    private val port: UiccPortInfoCompat
+    private val port: UiccPortInfoCompat,
+    private val verboseLoggingFlow: Flow<Boolean>
 ): ApduInterface {
     companion object {
         const val TAG = "OmapiApduInterface"
@@ -49,11 +54,15 @@ class OmapiApduInterface(
             "Unknown channel"
         }
 
-        Log.d(TAG, "OMAPI APDU: ${tx.encodeHex()}")
+        if (runBlocking { verboseLoggingFlow.first() }) {
+            Log.d(TAG, "OMAPI APDU: ${tx.encodeHex()}")
+        }
 
         try {
             return lastChannel.transmit(tx).also {
-                Log.d(TAG, "OMAPI APDU response: ${it.encodeHex()}")
+                if (runBlocking { verboseLoggingFlow.first() }) {
+                    Log.d(TAG, "OMAPI APDU response: ${it.encodeHex()}")
+                }
             }
         } catch (e: Exception) {
             Log.e(TAG, "OMAPI APDU exception")

+ 4 - 2
app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt

@@ -4,12 +4,14 @@ import android.hardware.usb.UsbDeviceConnection
 import android.hardware.usb.UsbEndpoint
 import android.util.Log
 import im.angry.openeuicc.util.*
+import kotlinx.coroutines.flow.Flow
 import net.typeblog.lpac_jni.ApduInterface
 
 class UsbApduInterface(
     private val conn: UsbDeviceConnection,
     private val bulkIn: UsbEndpoint,
-    private val bulkOut: UsbEndpoint
+    private val bulkOut: UsbEndpoint,
+    private val verboseLoggingFlow: Flow<Boolean>
 ): ApduInterface {
     companion object {
         private const val TAG = "UsbApduInterface"
@@ -27,7 +29,7 @@ class UsbApduInterface(
             throw IllegalArgumentException("Unsupported card reader; T=0 support is required")
         }
 
-        transceiver = UsbCcidTransceiver(conn, bulkIn, bulkOut, ccidDescription)
+        transceiver = UsbCcidTransceiver(conn, bulkIn, bulkOut, ccidDescription, verboseLoggingFlow)
 
         try {
             transceiver.iccPowerOn()

+ 8 - 2
app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidTransceiver.kt

@@ -5,6 +5,9 @@ import android.hardware.usb.UsbEndpoint
 import android.os.SystemClock
 import android.util.Log
 import im.angry.openeuicc.util.*
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
 import java.nio.ByteBuffer
 import java.nio.ByteOrder
 
@@ -18,7 +21,8 @@ class UsbCcidTransceiver(
     private val usbConnection: UsbDeviceConnection,
     private val usbBulkIn: UsbEndpoint,
     private val usbBulkOut: UsbEndpoint,
-    private val usbCcidDescription: UsbCcidDescription
+    private val usbCcidDescription: UsbCcidDescription,
+    private val verboseLoggingFlow: Flow<Boolean>
 ) {
     companion object {
         private const val TAG = "UsbCcidTransceiver"
@@ -178,7 +182,9 @@ class UsbCcidTransceiver(
             readBytes = usbConnection.bulkTransfer(
                 usbBulkIn, inputBuffer, inputBuffer.size, DEVICE_COMMUNICATE_TIMEOUT_MILLIS
             )
-            Log.d(TAG, "Received " + readBytes + " bytes: " + inputBuffer.encodeHex())
+            if (runBlocking { verboseLoggingFlow.first() }) {
+                Log.d(TAG, "Received " + readBytes + " bytes: " + inputBuffer.encodeHex())
+            }
         } while (readBytes <= 0 && attempts-- > 0)
         if (readBytes < CCID_HEADER_LENGTH) {
             throw UsbTransportException("USB-CCID error - failed to receive CCID header")

+ 4 - 1
app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt

@@ -25,6 +25,7 @@ import com.google.android.material.tabs.TabLayoutMediator
 import im.angry.openeuicc.common.R
 import im.angry.openeuicc.util.*
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
@@ -140,7 +141,9 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
         val knownChannels = withContext(Dispatchers.IO) {
             euiccChannelManager.enumerateEuiccChannels().onEach {
                 Log.d(TAG, "slot ${it.slotId} port ${it.portId}")
-                Log.d(TAG, it.lpa.eID)
+                if (preferenceRepository.verboseLoggingFlow.first()) {
+                    Log.d(TAG, it.lpa.eID)
+                }
                 // Request the system to refresh the list of profiles every time we start
                 // Note that this is currently supposed to be no-op when unprivileged,
                 // but it could change in the future

+ 0 - 1
app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt

@@ -56,7 +56,6 @@ class ProfileDownloadFragment : BaseMaterialDialogFragment(),
 
     private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
         result.contents?.let { content ->
-            Log.d(TAG, content)
             onScanResult(content)
         }
     }

+ 3 - 0
app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt

@@ -47,6 +47,9 @@ class SettingsFragment: PreferenceFragmentCompat() {
 
         findPreference<CheckBoxPreference>("pref_advanced_disable_safeguard_removable_esim")
             ?.bindBooleanFlow(preferenceRepository.disableSafeguardFlow, PreferenceKeys.DISABLE_SAFEGUARD_REMOVABLE_ESIM)
+
+        findPreference<CheckBoxPreference>("pref_advanced_verbose_logging")
+            ?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow, PreferenceKeys.VERBOSE_LOGGING)
     }
 
     override fun onStart() {

+ 4 - 0
app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt

@@ -24,6 +24,7 @@ object PreferenceKeys {
     val NOTIFICATION_DELETE = booleanPreferencesKey("notification_delete")
     val NOTIFICATION_SWITCH = booleanPreferencesKey("notification_switch")
     val DISABLE_SAFEGUARD_REMOVABLE_ESIM = booleanPreferencesKey("disable_safeguard_removable_esim")
+    val VERBOSE_LOGGING = booleanPreferencesKey("verbose_logging")
 }
 
 class PreferenceRepository(context: Context) {
@@ -44,6 +45,9 @@ class PreferenceRepository(context: Context) {
     val disableSafeguardFlow: Flow<Boolean> =
         dataStore.data.map { it[PreferenceKeys.DISABLE_SAFEGUARD_REMOVABLE_ESIM] ?: false }
 
+    val verboseLoggingFlow: Flow<Boolean> =
+        dataStore.data.map { it[PreferenceKeys.VERBOSE_LOGGING] ?: false }
+
     suspend fun <T> updatePreference(key: Preferences.Key<T>, value: T) {
         dataStore.edit {
             it[key] = value

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

@@ -82,6 +82,8 @@
     <string name="pref_advanced">Advanced</string>
     <string name="pref_advanced_disable_safeguard_removable_esim">Allow Disabling / Deleting Active Profile</string>
     <string name="pref_advanced_disable_safeguard_removable_esim_desc">By default, this app prevents you from disabling the active profile on a removable eSIM inserted in the device, because doing so may <i>sometimes</i> render it inaccessible.\nCheck this box to <i>remove</i> this safeguard.</string>
+    <string name="pref_advanced_verbose_logging">Verbose Logging</string>
+    <string name="pref_advanced_verbose_logging_desc">Enable verbose logs, which may contain sensitive information. Only share your logs with someone you trust after turning this on.</string>
     <string name="pref_advanced_logs">Logs</string>
     <string name="pref_advanced_logs_desc">View recent debug logs of the application</string>
     <string name="pref_info">Info</string>

+ 6 - 0
app-common/src/main/res/xml/pref_settings.xml

@@ -30,6 +30,12 @@
             app:title="@string/pref_advanced_disable_safeguard_removable_esim"
             app:summary="@string/pref_advanced_disable_safeguard_removable_esim_desc" />
 
+        <CheckBoxPreference
+            app:key="pref_advanced_verbose_logging"
+            app:iconSpaceReserved="false"
+            app:title="@string/pref_advanced_verbose_logging"
+            app:summary="@string/pref_advanced_verbose_logging_desc" />
+
         <Preference
             app:key="pref_advanced_logs"
             app:iconSpaceReserved="false"

+ 9 - 1
app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt

@@ -26,7 +26,15 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
                 "Trying TelephonyManager for slot ${port.card.physicalSlotIndex} port ${port.portIndex}"
             )
             try {
-                return EuiccChannel(port, TelephonyManagerApduInterface(port, tm))
+                return EuiccChannel(
+                    port,
+                    TelephonyManagerApduInterface(
+                        port,
+                        tm,
+                        context.preferenceRepository.verboseLoggingFlow
+                    ),
+                    context.preferenceRepository.verboseLoggingFlow
+                )
             } catch (e: IllegalArgumentException) {
                 // Failed
                 Log.w(

+ 25 - 2
app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt

@@ -2,13 +2,22 @@ package im.angry.openeuicc.core
 
 import android.telephony.IccOpenLogicalChannelResponse
 import android.telephony.TelephonyManager
+import android.util.Log
 import im.angry.openeuicc.util.*
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
 import net.typeblog.lpac_jni.ApduInterface
 
 class TelephonyManagerApduInterface(
     private val port: UiccPortInfoCompat,
-    private val tm: TelephonyManager
+    private val tm: TelephonyManager,
+    private val verboseLoggingFlow: Flow<Boolean>
 ): ApduInterface {
+    companion object {
+        const val TAG = "TelephonyManagerApduInterface"
+    }
+
     private var lastChannel: Int = -1
 
     override val valid: Boolean
@@ -45,6 +54,10 @@ class TelephonyManagerApduInterface(
     override fun transmit(tx: ByteArray): ByteArray {
         check(lastChannel != -1) { "Uninitialized" }
 
+        if (runBlocking { verboseLoggingFlow.first() }) {
+            Log.d(TAG, "TelephonyManager APDU: ${tx.encodeHex()}")
+        }
+
         val cla = tx[0].toUByte().toInt()
         val instruction = tx[1].toUByte().toInt()
         val p1 = tx[2].toUByte().toInt()
@@ -53,7 +66,17 @@ class TelephonyManagerApduInterface(
         val p4 = tx.drop(5).toByteArray().encodeHex()
 
         return tm.iccTransmitApduLogicalChannelByPortCompat(port.card.physicalSlotIndex, port.portIndex, lastChannel,
-            cla, instruction, p1, p2, p3, p4)?.decodeHex() ?: byteArrayOf()
+            cla,
+            instruction,
+            p1,
+            p2,
+            p3,
+            p4
+        ).also {
+            if (runBlocking { verboseLoggingFlow.first() }) {
+                Log.d(TAG, "TelephonyManager APDU response: $it")
+            }
+        }?.decodeHex() ?: byteArrayOf()
     }
 
 }

+ 18 - 2
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/HttpInterfaceImpl.kt

@@ -1,6 +1,9 @@
 package net.typeblog.lpac_jni.impl
 
 import android.util.Log
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
 import net.typeblog.lpac_jni.HttpInterface
 import java.net.URL
 import java.security.SecureRandom
@@ -9,7 +12,7 @@ import javax.net.ssl.SSLContext
 import javax.net.ssl.TrustManager
 import javax.net.ssl.TrustManagerFactory
 
-class HttpInterfaceImpl: HttpInterface {
+class HttpInterfaceImpl(private val verboseLoggingFlow: Flow<Boolean>) : HttpInterface {
     companion object {
         private const val TAG = "HttpInterfaceImpl"
     }
@@ -23,6 +26,10 @@ class HttpInterfaceImpl: HttpInterface {
     ): HttpInterface.HttpResponse {
         Log.d(TAG, "transmit(url = $url)")
 
+        if (runBlocking { verboseLoggingFlow.first() }) {
+            Log.d(TAG, "HTTP tx = ${tx.decodeToString(throwOnInvalidSequence = false)}")
+        }
+
         val parsedUrl = URL(url)
         if (parsedUrl.protocol != "https") {
             throw IllegalArgumentException("SM-DP+ servers must use the HTTPS protocol")
@@ -56,7 +63,16 @@ class HttpInterfaceImpl: HttpInterface {
 
             Log.d(TAG, "transmit responseCode = ${conn.responseCode}")
 
-            return HttpInterface.HttpResponse(conn.responseCode, conn.inputStream.readBytes())
+            val bytes = conn.inputStream.readBytes().also {
+                if (runBlocking { verboseLoggingFlow.first() }) {
+                    Log.d(
+                        TAG,
+                        "HTTP response body = ${it.decodeToString(throwOnInvalidSequence = false)}"
+                    )
+                }
+            }
+
+            return HttpInterface.HttpResponse(conn.responseCode, bytes)
         } catch (e: Exception) {
             e.printStackTrace()
             throw e