Display remaining space on card when downloading new profiles

TODO: Refuse to download more profiles (or at least warn) if running
out of space.
This commit is contained in:
Peter Cai 2023-11-30 20:04:04 -05:00
parent 47b2a7060b
commit af75b8f5fb
8 changed files with 107 additions and 2 deletions

View file

@ -3,9 +3,11 @@ package im.angry.openeuicc.ui
import android.app.Dialog
import android.os.Bundle
import android.text.Editable
import android.text.format.Formatter
import android.util.Log
import android.view.*
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment
@ -34,8 +36,11 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
private lateinit var profileDownloadCode: TextInputLayout
private lateinit var profileDownloadConfirmationCode: TextInputLayout
private lateinit var profileDownloadIMEI: TextInputLayout
private lateinit var profileDownloadFreeSpace: TextView
private lateinit var progress: ProgressBar
private var freeNvram: Int = -1
private var downloading = false
private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
@ -60,6 +65,7 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
profileDownloadCode = view.findViewById(R.id.profile_download_code)
profileDownloadConfirmationCode = view.findViewById(R.id.profile_download_confirmation_code)
profileDownloadIMEI = view.findViewById(R.id.profile_download_imei)
profileDownloadFreeSpace = view.findViewById(R.id.profile_download_free_space)
progress = view.findViewById(R.id.progress)
toolbar.inflateMenu(R.menu.fragment_profile_download)
@ -102,6 +108,18 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
override fun onStart() {
super.onStart()
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 {

View file

@ -102,13 +102,14 @@
android:id="@+id/profile_download_imei"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="15dp"
android:layout_marginTop="15dp"
android:layout_marginBottom="6dp"
android:hint="@string/profile_download_imei"
style="@style/Widget.OpenEUICC.Input"
app:layout_constraintTop_toBottomOf="@id/profile_download_confirmation_code"
app:layout_constraintLeft_toLeftOf="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:passwordToggleEnabled="true">
@ -120,4 +121,16 @@
</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>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<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="unknown">Unknown</string>
<string name="enabled">Enabled</string>
<string name="disabled">Disabled</string>
@ -21,6 +22,7 @@
<string name="profile_download_code">Activation Code</string>
<string name="profile_download_confirmation_code">Confirmation Code (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_ok">Download</string>
<string name="profile_download_failed">Failed to download eSIM. Check your activation / QR code.</string>

View file

@ -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,
)

View file

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

View file

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

View file

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

View file

@ -24,6 +24,9 @@ jstring empty_string;
jclass string_class;
jmethodID string_constructor;
jclass euicc_info2_class;
jmethodID euicc_info2_constructor;
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
jvm = vm;
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)->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];
empty_string = (*env)->NewString(env, _unused, 0);
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);
(*env)->ReleaseStringUTFChars(env, iccid, _iccid);
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;
}