Browse Source

Display remaining space on card when downloading new profiles

TODO: Refuse to download more profiles (or at least warn) if running
out of space.
Peter Cai 2 years ago
parent
commit
af75b8f5fb

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

@@ -3,9 +3,11 @@ package im.angry.openeuicc.ui
 import android.app.Dialog
 import android.app.Dialog
 import android.os.Bundle
 import android.os.Bundle
 import android.text.Editable
 import android.text.Editable
+import android.text.format.Formatter
 import android.util.Log
 import android.util.Log
 import android.view.*
 import android.view.*
 import android.widget.ProgressBar
 import android.widget.ProgressBar
+import android.widget.TextView
 import android.widget.Toast
 import android.widget.Toast
 import androidx.appcompat.widget.Toolbar
 import androidx.appcompat.widget.Toolbar
 import androidx.fragment.app.DialogFragment
 import androidx.fragment.app.DialogFragment
@@ -34,8 +36,11 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
     private lateinit var profileDownloadCode: TextInputLayout
     private lateinit var profileDownloadCode: TextInputLayout
     private lateinit var profileDownloadConfirmationCode: TextInputLayout
     private lateinit var profileDownloadConfirmationCode: TextInputLayout
     private lateinit var profileDownloadIMEI: TextInputLayout
     private lateinit var profileDownloadIMEI: TextInputLayout
+    private lateinit var profileDownloadFreeSpace: TextView
     private lateinit var progress: ProgressBar
     private lateinit var progress: ProgressBar
 
 
+    private var freeNvram: Int = -1
+
     private var downloading = false
     private var downloading = false
 
 
     private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
     private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
@@ -60,6 +65,7 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
         profileDownloadCode = view.findViewById(R.id.profile_download_code)
         profileDownloadCode = view.findViewById(R.id.profile_download_code)
         profileDownloadConfirmationCode = view.findViewById(R.id.profile_download_confirmation_code)
         profileDownloadConfirmationCode = view.findViewById(R.id.profile_download_confirmation_code)
         profileDownloadIMEI = view.findViewById(R.id.profile_download_imei)
         profileDownloadIMEI = view.findViewById(R.id.profile_download_imei)
+        profileDownloadFreeSpace = view.findViewById(R.id.profile_download_free_space)
         progress = view.findViewById(R.id.progress)
         progress = view.findViewById(R.id.progress)
 
 
         toolbar.inflateMenu(R.menu.fragment_profile_download)
         toolbar.inflateMenu(R.menu.fragment_profile_download)
@@ -102,6 +108,18 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
     override fun onStart() {
     override fun onStart() {
         super.onStart()
         super.onStart()
         profileDownloadIMEI.editText!!.text = Editable.Factory.getInstance().newEditable(channel.imei)
         profileDownloadIMEI.editText!!.text = Editable.Factory.getInstance().newEditable(channel.imei)
+
+        lifecycleScope.launch(Dispatchers.IO) {
+            // Fetch remaining NVRAM
+            val str = channel.lpa.euiccInfo2?.freeNvram?.also {
+                freeNvram = it
+            }?.let { Formatter.formatShortFileSize(requireContext(), it.toLong()) }
+
+            withContext(Dispatchers.Main) {
+                profileDownloadFreeSpace.text = getString(R.string.profile_download_free_space,
+                    str ?: getText(R.string.unknown))
+            }
+        }
     }
     }
 
 
     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

+ 15 - 2
app-common/src/main/res/layout/fragment_profile_download.xml

@@ -102,13 +102,14 @@
         android:id="@+id/profile_download_imei"
         android:id="@+id/profile_download_imei"
         android:layout_width="0dp"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginVertical="15dp"
+        android:layout_marginTop="15dp"
+        android:layout_marginBottom="6dp"
         android:hint="@string/profile_download_imei"
         android:hint="@string/profile_download_imei"
         style="@style/Widget.OpenEUICC.Input"
         style="@style/Widget.OpenEUICC.Input"
         app:layout_constraintTop_toBottomOf="@id/profile_download_confirmation_code"
         app:layout_constraintTop_toBottomOf="@id/profile_download_confirmation_code"
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintRight_toRightOf="parent"
         app:layout_constraintRight_toRightOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/profile_download_free_space"
         app:layout_constraintWidth_percent=".8"
         app:layout_constraintWidth_percent=".8"
         app:passwordToggleEnabled="true">
         app:passwordToggleEnabled="true">
 
 
@@ -120,4 +121,16 @@
 
 
     </com.google.android.material.textfield.TextInputLayout>
     </com.google.android.material.textfield.TextInputLayout>
 
 
+    <TextView
+        android:id="@+id/profile_download_free_space"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:textSize="11sp"
+        android:layout_marginBottom="4dp"
+        app:layout_constraintTop_toBottomOf="@id/profile_download_imei"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>
 </androidx.constraintlayout.widget.ConstraintLayout>

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

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
 <resources>
     <string name="no_euicc">No eUICC card on this device is accessible by this app.\nInsert a supported eUICC card, or try out the privileged OpenEUICC app instead.</string>
     <string name="no_euicc">No eUICC card on this device is accessible by this app.\nInsert a supported eUICC card, or try out the privileged OpenEUICC app instead.</string>
+    <string name="unknown">Unknown</string>
 
 
     <string name="enabled">Enabled</string>
     <string name="enabled">Enabled</string>
     <string name="disabled">Disabled</string>
     <string name="disabled">Disabled</string>
@@ -21,6 +22,7 @@
     <string name="profile_download_code">Activation Code</string>
     <string name="profile_download_code">Activation Code</string>
     <string name="profile_download_confirmation_code">Confirmation Code (Optional)</string>
     <string name="profile_download_confirmation_code">Confirmation Code (Optional)</string>
     <string name="profile_download_imei">IMEI (Optional)</string>
     <string name="profile_download_imei">IMEI (Optional)</string>
+    <string name="profile_download_free_space">Space remaining: %s</string>
     <string name="profile_download_scan">Scan QR Code</string>
     <string name="profile_download_scan">Scan QR Code</string>
     <string name="profile_download_ok">Download</string>
     <string name="profile_download_ok">Download</string>
     <string name="profile_download_failed">Failed to download eSIM. Check your activation / QR code.</string>
     <string name="profile_download_failed">Failed to download eSIM. Check your activation / QR code.</string>

+ 15 - 0
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt

@@ -0,0 +1,15 @@
+package net.typeblog.lpac_jni
+
+/* Corresponds to EuiccInfo2 in SGP.22 */
+data class EuiccInfo2(
+    val profileVersion: String,
+    val sgp22Version: String,
+    val euiccFirmwareVersion: String,
+    val uiccFirmwareVersion: String,
+    val globalPlatformVersion: String,
+    val sasAccreditationNumber: String,
+    val ppVersion: String,
+    val installedApp: Int,
+    val freeNvram: Int,
+    val freeRam: Int,
+)

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

@@ -3,6 +3,8 @@ package net.typeblog.lpac_jni
 interface LocalProfileAssistant {
 interface LocalProfileAssistant {
     val profiles: List<LocalProfileInfo>
     val profiles: List<LocalProfileInfo>
     val eID: String
     val eID: String
+    // Extended EuiccInfo for use with LUIs, containing information such as firmware version
+    val euiccInfo2: EuiccInfo2?
 
 
     fun enableProfile(iccid: String): Boolean
     fun enableProfile(iccid: String): Boolean
     fun disableProfile(iccid: String): Boolean
     fun disableProfile(iccid: String): Boolean

+ 3 - 0
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt

@@ -25,4 +25,7 @@ internal object LpacJni {
     // We do not expose all of the functions because of tediousness :)
     // We do not expose all of the functions because of tediousness :)
     external fun downloadProfile(handle: Long, smdp: String, matchingId: String?, imei: String?,
     external fun downloadProfile(handle: Long, smdp: String, matchingId: String?, imei: String?,
                                  confirmationCode: String?, callback: ProfileDownloadCallback): Int
                                  confirmationCode: String?, callback: ProfileDownloadCallback): Int
+
+    // es10cex (actually part of es10b)
+    external fun es10cexGetEuiccInfo2(handle: Long): EuiccInfo2?
 }
 }

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

@@ -2,6 +2,7 @@ package net.typeblog.lpac_jni.impl
 
 
 import net.typeblog.lpac_jni.LpacJni
 import net.typeblog.lpac_jni.LpacJni
 import net.typeblog.lpac_jni.ApduInterface
 import net.typeblog.lpac_jni.ApduInterface
+import net.typeblog.lpac_jni.EuiccInfo2
 import net.typeblog.lpac_jni.HttpInterface
 import net.typeblog.lpac_jni.HttpInterface
 import net.typeblog.lpac_jni.LocalProfileAssistant
 import net.typeblog.lpac_jni.LocalProfileAssistant
 import net.typeblog.lpac_jni.LocalProfileInfo
 import net.typeblog.lpac_jni.LocalProfileInfo
@@ -25,6 +26,9 @@ class LocalProfileAssistantImpl(
         LpacJni.es10cGetEid(contextHandle)!!
         LpacJni.es10cGetEid(contextHandle)!!
     }
     }
 
 
+    override val euiccInfo2: EuiccInfo2?
+        get() = LpacJni.es10cexGetEuiccInfo2(contextHandle)
+
     override fun enableProfile(iccid: String): Boolean {
     override fun enableProfile(iccid: String): Boolean {
         return LpacJni.es10cEnableProfile(contextHandle, iccid) == 0
         return LpacJni.es10cEnableProfile(contextHandle, iccid) == 0
     }
     }

+ 48 - 0
libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c

@@ -24,6 +24,9 @@ jstring empty_string;
 jclass string_class;
 jclass string_class;
 jmethodID string_constructor;
 jmethodID string_constructor;
 
 
+jclass euicc_info2_class;
+jmethodID euicc_info2_constructor;
+
 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
     jvm = vm;
     jvm = vm;
     interface_wrapper_init();
     interface_wrapper_init();
@@ -58,6 +61,10 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
     local_profile_class_operational = (*env)->GetStaticObjectField(env, local_profile_class_class, field_operational);
     local_profile_class_operational = (*env)->GetStaticObjectField(env, local_profile_class_class, field_operational);
     local_profile_class_operational = (*env)->NewGlobalRef(env, local_profile_class_operational);
     local_profile_class_operational = (*env)->NewGlobalRef(env, local_profile_class_operational);
 
 
+    euicc_info2_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/EuiccInfo2");
+    euicc_info2_class = (*env)->NewGlobalRef(env, euicc_info2_class);
+    euicc_info2_constructor = (*env)->GetMethodID(env, euicc_info2_class, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;III)V");
+
     const char _unused[1];
     const char _unused[1];
     empty_string = (*env)->NewString(env, _unused, 0);
     empty_string = (*env)->NewString(env, _unused, 0);
     empty_string = (*env)->NewGlobalRef(env, empty_string);
     empty_string = (*env)->NewGlobalRef(env, empty_string);
@@ -229,4 +236,45 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cDeleteProfile(JNIEnv *env, jobject thiz
     int ret = es10c_delete_profile_iccid(ctx, _iccid);
     int ret = es10c_delete_profile_iccid(ctx, _iccid);
     (*env)->ReleaseStringUTFChars(env, iccid, _iccid);
     (*env)->ReleaseStringUTFChars(env, iccid, _iccid);
     return ret;
     return ret;
+}
+
+JNIEXPORT jobject JNICALL
+Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject thiz, jlong handle) {
+    struct euicc_ctx *ctx = (struct euicc_ctx *) handle;
+    struct es10cex_euiccinfo2 info;
+    jstring sas_accreditation_number = NULL;
+    jstring global_platform_version = NULL;
+    jstring euicc_firmware_version = NULL;
+    jstring uicc_firmware_version = NULL;
+    jstring profile_version = NULL;
+    jstring sgp22_version = NULL;
+    jstring pp_version = NULL;
+    jobject ret = NULL;
+
+    if (es10cex_get_euiccinfo2(ctx, &info) < 0)
+        goto out;
+
+    profile_version = toJString(env, info.profile_version);
+    sgp22_version = toJString(env, info.sgp22_version);
+    euicc_firmware_version = toJString(env, info.euicc_firmware_version);
+    uicc_firmware_version = toJString(env, info.uicc_firmware_version);
+    global_platform_version = toJString(env, info.global_platform_version);
+    sas_accreditation_number = toJString(env, info.sas_accreditation_number);
+    pp_version = toJString(env, info.pp_version);
+
+    ret = (*env)->NewObject(env, euicc_info2_class, euicc_info2_constructor,
+                            profile_version, sgp22_version, euicc_firmware_version,
+                            uicc_firmware_version, global_platform_version,
+                            sas_accreditation_number, pp_version,
+                            info.installed_app, info.free_nvram, info.free_ram);
+
+    out:
+    (*env)->DeleteLocalRef(env, profile_version);
+    (*env)->DeleteLocalRef(env, sgp22_version);
+    (*env)->DeleteLocalRef(env, euicc_firmware_version);
+    (*env)->DeleteLocalRef(env, uicc_firmware_version);
+    (*env)->DeleteLocalRef(env, global_platform_version);
+    (*env)->DeleteLocalRef(env, sas_accreditation_number);
+    (*env)->DeleteLocalRef(env, pp_version);
+    return ret;
 }
 }