Browse Source

refactor: Emit structured profile info from the LPA library

Peter Cai 3 years ago
parent
commit
4b29660ef2

+ 15 - 16
app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt

@@ -14,6 +14,7 @@ import androidx.fragment.app.Fragment
 import androidx.lifecycle.lifecycleScope
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import com.truphone.lpa.LocalProfileInfo
 import com.truphone.lpa.impl.ProfileKey.*
 import com.truphone.lpad.progress.Progress
 import im.angry.openeuicc.R
@@ -78,7 +79,7 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
             }
 
             withContext(Dispatchers.Main) {
-                adapter.profiles = profiles.filter { it[PROFILE_CLASS.name] != "0" }
+                adapter.profiles = profiles.filter { it.profileClass != LocalProfileInfo.Clazz.Testing }
                 adapter.notifyDataSetChanged()
                 binding.swipeRefresh.isRefreshing = false
             }
@@ -134,9 +135,9 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
             binding.profileMenu.setOnClickListener { showOptionsMenu() }
         }
 
-        private lateinit var profile: Map<String, String>
+        private lateinit var profile: LocalProfileInfo
 
-        fun setProfile(profile: Map<String, String>) {
+        fun setProfile(profile: LocalProfileInfo) {
             this.profile = profile
             binding.name.text = getName()
 
@@ -147,20 +148,18 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
                     R.string.disabled
                 }
             )
-            binding.provider.text = profile[PROVIDER_NAME.name]
-            binding.iccid.text = profile[ICCID_LITTLE.name]!!
+            binding.provider.text = profile.providerName
+            binding.iccid.text = profile.iccidLittleEndian
             binding.iccid.transformationMethod = PasswordTransformationMethod.getInstance()
         }
 
         private fun isEnabled(): Boolean =
-            profile[STATE.name]?.lowercase() == "enabled"
+            profile.state == LocalProfileInfo.State.Enabled
 
         private fun getName(): String =
-            if (profile[NICKNAME.name].isNullOrEmpty()) {
-                profile[NAME.name]
-            } else {
-                profile[NICKNAME.name]
-            }!!
+            profile.nickName.ifEmpty {
+                profile.name
+            }
 
         private fun showOptionsMenu() {
             PopupMenu(binding.root.context, binding.profileMenu).apply {
@@ -179,20 +178,20 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
         private fun onMenuItemClicked(item: MenuItem): Boolean =
             when (item.itemId) {
                 R.id.enable -> {
-                    enableOrDisableProfile(profile[ICCID.name]!!, true)
+                    enableOrDisableProfile(profile.iccid, true)
                     true
                 }
                 R.id.disable -> {
-                    enableOrDisableProfile(profile[ICCID.name]!!, false)
+                    enableOrDisableProfile(profile.iccid, false)
                     true
                 }
                 R.id.rename -> {
-                    ProfileRenameFragment.newInstance(slotId, profile[ICCID.name]!!, getName())
+                    ProfileRenameFragment.newInstance(slotId, profile.iccid, getName())
                         .show(childFragmentManager, ProfileRenameFragment.TAG)
                     true
                 }
                 R.id.delete -> {
-                    ProfileDeleteFragment.newInstance(slotId, profile[ICCID.name]!!, getName())
+                    ProfileDeleteFragment.newInstance(slotId, profile.iccid, getName())
                         .show(childFragmentManager, ProfileDeleteFragment.TAG)
                     true
                 }
@@ -200,7 +199,7 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
             }
     }
 
-    inner class EuiccProfileAdapter(var profiles: List<Map<String, String>>) : RecyclerView.Adapter<ViewHolder>() {
+    inner class EuiccProfileAdapter(var profiles: List<LocalProfileInfo>) : RecyclerView.Adapter<ViewHolder>() {
         override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
             val binding =
                 EuiccProfileBinding.inflate(LayoutInflater.from(parent.context), parent, false)

+ 1 - 0
build.gradle

@@ -3,6 +3,7 @@ plugins {
     id 'com.android.application' version '7.1.3' apply false
     id 'com.android.library' version '7.1.3' apply false
     id 'org.jetbrains.kotlin.android' version '1.5.30' apply false
+    id 'org.jetbrains.kotlin.multiplatform' version '1.6.21' apply false
 }
 
 task clean(type: Delete) {

+ 2 - 0
libs/lpad-sm-dp-plus-connector/build.gradle

@@ -1,4 +1,5 @@
 apply plugin: 'java'
+apply plugin: 'kotlin'
 
 configurations {
     tool
@@ -27,6 +28,7 @@ task genAsn1(type: JavaExec) {
 }
 
 compileJava.dependsOn genAsn1
+compileKotlin.dependsOn genAsn1
 
 description = 'LPAd SM-DP+ Connector'
 

+ 3 - 2
libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/LocalProfileAssistant.java

@@ -21,7 +21,7 @@ public interface LocalProfileAssistant {
     
     void downloadProfile(String matchingId, DownloadProgress progress) throws Exception;
     
-    List<Map<String, String>> getProfiles();
+    List<LocalProfileInfo> getProfiles();
 
     /**
      * Gets the EID from the eUICC
@@ -38,5 +38,6 @@ public interface LocalProfileAssistant {
 
     void processPendingNotifications();
 
-    boolean setNickname(String iccid, String nickname);
+    boolean setNickname(String iccid, String nickname
+    );
 }

+ 69 - 0
libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/LocalProfileInfo.kt

@@ -0,0 +1,69 @@
+/*
+ * Copyright 2022 Peter Cai & Pierre-Hugues Husson
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.truphone.lpa
+
+import java.lang.IllegalArgumentException
+import java.lang.StringBuilder
+
+data class LocalProfileInfo(
+    val iccid: String,
+    val state: State,
+    val name: String,
+    val nickName: String,
+    val providerName: String,
+    val isdpAID: String,
+    val profileClass: Clazz
+) {
+    val iccidLittleEndian by lazy {
+        iccidBigToLittle(iccid)
+    }
+
+    enum class State {
+        Enabled,
+        Disabled
+    }
+
+    enum class Clazz {
+        Testing,
+        Provisioning,
+        Operational
+    }
+
+    companion object {
+        fun stateFromString(state: String?): State =
+            if (state == "0") {
+                State.Disabled
+            } else {
+                State.Enabled
+            }
+
+        fun classFromString(clazz: String?): Clazz =
+            when (clazz) {
+                "0" -> Clazz.Testing
+                "1" -> Clazz.Provisioning
+                "2" -> Clazz.Operational
+                else -> throw IllegalArgumentException("Unknown profile class $clazz")
+            }
+
+        private fun iccidBigToLittle(iccid: String): String {
+            val builder = StringBuilder()
+            for (i in 0 until iccid.length / 2) {
+                builder.append(iccid[i * 2 + 1])
+                if (iccid[i * 2] != 'F') builder.append(iccid[i * 2])
+            }
+            return builder.toString()
+        }
+    }
+}

+ 0 - 107
libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/ListProfilesWorker.java

@@ -1,107 +0,0 @@
-package com.truphone.lpa.impl;
-
-import com.truphone.rsp.dto.asn1.rspdefinitions.ProfileInfo;
-import com.truphone.rsp.dto.asn1.rspdefinitions.ProfileInfoListResponse;
-import org.apache.commons.codec.DecoderException;
-import org.apache.commons.codec.binary.Hex;
-import com.truphone.lpa.ApduChannel;
-import com.truphone.lpa.apdu.ApduUtils;
-import com.truphone.util.LogStub;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-class ListProfilesWorker {
-    private static final Logger LOG = Logger.getLogger(ListProfilesWorker.class.getName());
-
-    private final ApduChannel apduChannel;
-
-    ListProfilesWorker(ApduChannel apduChannel) {
-
-        this.apduChannel = apduChannel;
-    }
-
-    List<Map<String, String>> run() {
-        String profilesInfo = getProfileInfoListResponse();
-        ProfileInfoListResponse profiles = new ProfileInfoListResponse();
-        List<Map<String, String>> profileList = new ArrayList<>();
-
-        try {
-            decodeProfiles(profilesInfo, profiles);
-
-            for (ProfileInfo info : profiles.getProfileInfoListOk().getProfileInfo()) {
-                Map<String, String> profileMap = new HashMap<>();
-
-                profileMap.put(ProfileKey.STATE.name(), LocalProfileAssistantImpl.DISABLED_STATE.equals(info.getProfileState().toString()) ? "Disabled" : "Enabled");
-                profileMap.put(ProfileKey.ICCID.name(), info.getIccid().toString());
-                profileMap.put(ProfileKey.ICCID_LITTLE.name(), iccidBigToLittle(info.getIccid().toString()));
-                profileMap.put(ProfileKey.NAME.name(), (info.getProfileName()!=null)?info.getProfileName().toString():"");
-                profileMap.put(ProfileKey.NICKNAME.name(), (info.getProfileNickname()!=null)?info.getProfileNickname().toString():"");
-                profileMap.put(ProfileKey.PROVIDER_NAME.name(), (info.getServiceProviderName()!=null)?info.getServiceProviderName().toString():"");
-                profileMap.put(ProfileKey.ISDP_AID.name(), (info.getIsdpAid()!=null)?info.getIsdpAid().toString():"");
-                profileMap.put(ProfileKey.PROFILE_CLASS.name(), (info.getProfileClass()!=null)?info.getProfileClass().toString():"");
-                profileMap.put(ProfileKey.PROFILE_STATE.name(), info.getProfileState().toString());
-
-                profileList.add(profileMap);
-            }
-
-            if (LogStub.getInstance().isDebugEnabled()) {
-                LogStub.getInstance().logDebug (LOG, LogStub.getInstance().getTag() + " - getProfiles - returning: " + profileList.toString());
-            }
-
-            return profileList;
-
-        } catch (DecoderException e) {
-            LOG.log(Level.SEVERE, LogStub.getInstance().getTag() + " - " + e.getMessage(), e);
-            LOG.log(Level.SEVERE, LogStub.getInstance().getTag() + " -  Unable to retrieve profiles. Exception in Decoder:" + e.getMessage());
-
-            throw new RuntimeException("Unable to retrieve profiles");
-        } catch (IOException ioe) {
-            LOG.log(Level.SEVERE, LogStub.getInstance().getTag() + " - " + ioe.getMessage(), ioe);
-
-            throw new RuntimeException("Unable to retrieve profiles");
-        }
-    }
-
-    private void decodeProfiles(String profilesInfo, ProfileInfoListResponse profiles) throws DecoderException, IOException {
-        InputStream is = new ByteArrayInputStream(Hex.decodeHex(profilesInfo.toCharArray()));
-
-        profiles.decode(is);
-
-        if (LogStub.getInstance().isDebugEnabled()) {
-            LogStub.getInstance().logDebug (LOG,"Profile list object: " + profiles.toString());
-        }
-    }
-
-    private String getProfileInfoListResponse() {
-
-        if (LogStub.getInstance().isDebugEnabled()) {
-            LogStub.getInstance().logDebug (LOG, LogStub.getInstance().getTag() + " - Getting Profiles");
-        }
-
-        String apdu = ApduUtils.getProfilesInfoApdu(null);
-
-        if (LogStub.getInstance().isDebugEnabled()) {
-            LogStub.getInstance().logDebug (LOG,"List profiles APDU: " + apdu);
-        }
-
-        return apduChannel.transmitAPDU(apdu);
-    }
-
-    private String iccidBigToLittle(String iccid) {
-        StringBuilder builder = new StringBuilder();
-        for (int i = 0; i < iccid.length() / 2; i++) {
-            builder.append(iccid.charAt(i * 2 + 1));
-            if (iccid.charAt(i * 2) != 'F')
-                builder.append(iccid.charAt(i * 2));
-        }
-        return builder.toString();
-    }
-}

+ 98 - 0
libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/ListProfilesWorker.kt

@@ -0,0 +1,98 @@
+/*
+ * Copyright 2022 Peter Cai & Pierre-Hugues Husson
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.truphone.lpa.impl
+
+import com.truphone.lpa.ApduChannel
+import com.truphone.lpa.LocalProfileInfo
+import com.truphone.rsp.dto.asn1.rspdefinitions.ProfileInfoListResponse
+import com.truphone.util.LogStub
+import org.apache.commons.codec.DecoderException
+import org.apache.commons.codec.binary.Hex
+import com.truphone.lpa.apdu.ApduUtils
+import java.io.ByteArrayInputStream
+import java.io.IOException
+import java.io.InputStream
+import java.lang.RuntimeException
+import java.util.logging.Level
+import java.util.logging.Logger
+
+internal class ListProfilesWorker(private val apduChannel: ApduChannel) {
+    fun run(): List<LocalProfileInfo> {
+        val profilesInfo = profileInfoListResponse
+        val profiles = ProfileInfoListResponse()
+        val profileList: MutableList<LocalProfileInfo> = mutableListOf()
+        return try {
+            decodeProfiles(profilesInfo, profiles)
+            for (info in profiles.profileInfoListOk.profileInfo) {
+                profileList.add(
+                    LocalProfileInfo(
+                        iccid = info.iccid.toString(),
+                        state = LocalProfileInfo.stateFromString(info.profileState?.toString()),
+                        name = info.profileName?.toString() ?: "",
+                        nickName = info.profileNickname?.toString() ?: "",
+                        providerName = info.serviceProviderName?.toString() ?: "",
+                        isdpAID = info.isdpAid?.toString() ?: "",
+                        profileClass = LocalProfileInfo.classFromString(info.profileClass?.toString())
+                    )
+                )
+            }
+            if (LogStub.getInstance().isDebugEnabled) {
+                LogStub.getInstance().logDebug(
+                    LOG,
+                    LogStub.getInstance().tag + " - getProfiles - returning: " + profileList.toString()
+                )
+            }
+            profileList
+        } catch (e: DecoderException) {
+            LOG.log(Level.SEVERE, LogStub.getInstance().tag + " - " + e.message, e)
+            LOG.log(
+                Level.SEVERE,
+                LogStub.getInstance().tag + " -  Unable to retrieve profiles. Exception in Decoder:" + e.message
+            )
+            throw RuntimeException("Unable to retrieve profiles")
+        } catch (ioe: IOException) {
+            LOG.log(Level.SEVERE, LogStub.getInstance().tag + " - " + ioe.message, ioe)
+            throw RuntimeException("Unable to retrieve profiles")
+        }
+    }
+
+    @Throws(DecoderException::class, IOException::class)
+    private fun decodeProfiles(profilesInfo: String, profiles: ProfileInfoListResponse) {
+        val `is`: InputStream = ByteArrayInputStream(Hex.decodeHex(profilesInfo.toCharArray()))
+        profiles.decode(`is`)
+        if (LogStub.getInstance().isDebugEnabled) {
+            LogStub.getInstance().logDebug(LOG, "Profile list object: $profiles")
+        }
+    }
+
+    private val profileInfoListResponse: String
+        get() {
+            if (LogStub.getInstance().isDebugEnabled) {
+                LogStub.getInstance()
+                    .logDebug(LOG, LogStub.getInstance().tag + " - Getting Profiles")
+            }
+            val apdu = ApduUtils.getProfilesInfoApdu(null)
+            if (LogStub.getInstance().isDebugEnabled) {
+                LogStub.getInstance().logDebug(LOG, "List profiles APDU: $apdu")
+            }
+            return apduChannel.transmitAPDU(apdu)
+        }
+
+    companion object {
+        private val LOG = Logger.getLogger(
+            ListProfilesWorker::class.java.name
+        )
+    }
+}

+ 2 - 2
libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/LocalProfileAssistantImpl.java

@@ -3,6 +3,7 @@ package com.truphone.lpa.impl;
 import com.truphone.es9plus.Es9PlusImpl;
 import com.truphone.lpa.ApduChannel;
 import com.truphone.lpa.LocalProfileAssistant;
+import com.truphone.lpa.LocalProfileInfo;
 import com.truphone.lpa.apdu.ApduUtils;
 import com.truphone.lpa.apdu.NotificationType;
 import com.truphone.lpa.progress.DownloadProgress;
@@ -90,8 +91,7 @@ public class LocalProfileAssistantImpl implements LocalProfileAssistant {
     }
 
     @Override
-    public List<Map<String, String>> getProfiles() {
-
+    public List<LocalProfileInfo> getProfiles() {
         return new ListProfilesWorker(apduChannel).run();
     }