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.lifecycle.lifecycleScope
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView
+import com.truphone.lpa.LocalProfileInfo
 import com.truphone.lpa.impl.ProfileKey.*
 import com.truphone.lpa.impl.ProfileKey.*
 import com.truphone.lpad.progress.Progress
 import com.truphone.lpad.progress.Progress
 import im.angry.openeuicc.R
 import im.angry.openeuicc.R
@@ -78,7 +79,7 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
             }
             }
 
 
             withContext(Dispatchers.Main) {
             withContext(Dispatchers.Main) {
-                adapter.profiles = profiles.filter { it[PROFILE_CLASS.name] != "0" }
+                adapter.profiles = profiles.filter { it.profileClass != LocalProfileInfo.Clazz.Testing }
                 adapter.notifyDataSetChanged()
                 adapter.notifyDataSetChanged()
                 binding.swipeRefresh.isRefreshing = false
                 binding.swipeRefresh.isRefreshing = false
             }
             }
@@ -134,9 +135,9 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
             binding.profileMenu.setOnClickListener { showOptionsMenu() }
             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
             this.profile = profile
             binding.name.text = getName()
             binding.name.text = getName()
 
 
@@ -147,20 +148,18 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
                     R.string.disabled
                     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()
             binding.iccid.transformationMethod = PasswordTransformationMethod.getInstance()
         }
         }
 
 
         private fun isEnabled(): Boolean =
         private fun isEnabled(): Boolean =
-            profile[STATE.name]?.lowercase() == "enabled"
+            profile.state == LocalProfileInfo.State.Enabled
 
 
         private fun getName(): String =
         private fun getName(): String =
-            if (profile[NICKNAME.name].isNullOrEmpty()) {
-                profile[NAME.name]
-            } else {
-                profile[NICKNAME.name]
-            }!!
+            profile.nickName.ifEmpty {
+                profile.name
+            }
 
 
         private fun showOptionsMenu() {
         private fun showOptionsMenu() {
             PopupMenu(binding.root.context, binding.profileMenu).apply {
             PopupMenu(binding.root.context, binding.profileMenu).apply {
@@ -179,20 +178,20 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
         private fun onMenuItemClicked(item: MenuItem): Boolean =
         private fun onMenuItemClicked(item: MenuItem): Boolean =
             when (item.itemId) {
             when (item.itemId) {
                 R.id.enable -> {
                 R.id.enable -> {
-                    enableOrDisableProfile(profile[ICCID.name]!!, true)
+                    enableOrDisableProfile(profile.iccid, true)
                     true
                     true
                 }
                 }
                 R.id.disable -> {
                 R.id.disable -> {
-                    enableOrDisableProfile(profile[ICCID.name]!!, false)
+                    enableOrDisableProfile(profile.iccid, false)
                     true
                     true
                 }
                 }
                 R.id.rename -> {
                 R.id.rename -> {
-                    ProfileRenameFragment.newInstance(slotId, profile[ICCID.name]!!, getName())
+                    ProfileRenameFragment.newInstance(slotId, profile.iccid, getName())
                         .show(childFragmentManager, ProfileRenameFragment.TAG)
                         .show(childFragmentManager, ProfileRenameFragment.TAG)
                     true
                     true
                 }
                 }
                 R.id.delete -> {
                 R.id.delete -> {
-                    ProfileDeleteFragment.newInstance(slotId, profile[ICCID.name]!!, getName())
+                    ProfileDeleteFragment.newInstance(slotId, profile.iccid, getName())
                         .show(childFragmentManager, ProfileDeleteFragment.TAG)
                         .show(childFragmentManager, ProfileDeleteFragment.TAG)
                     true
                     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 {
         override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
             val binding =
             val binding =
                 EuiccProfileBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                 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.application' version '7.1.3' apply false
     id 'com.android.library' 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.android' version '1.5.30' apply false
+    id 'org.jetbrains.kotlin.multiplatform' version '1.6.21' apply false
 }
 }
 
 
 task clean(type: Delete) {
 task clean(type: Delete) {

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

@@ -1,4 +1,5 @@
 apply plugin: 'java'
 apply plugin: 'java'
+apply plugin: 'kotlin'
 
 
 configurations {
 configurations {
     tool
     tool
@@ -27,6 +28,7 @@ task genAsn1(type: JavaExec) {
 }
 }
 
 
 compileJava.dependsOn genAsn1
 compileJava.dependsOn genAsn1
+compileKotlin.dependsOn genAsn1
 
 
 description = 'LPAd SM-DP+ Connector'
 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;
     void downloadProfile(String matchingId, DownloadProgress progress) throws Exception;
     
     
-    List<Map<String, String>> getProfiles();
+    List<LocalProfileInfo> getProfiles();
 
 
     /**
     /**
      * Gets the EID from the eUICC
      * Gets the EID from the eUICC
@@ -38,5 +38,6 @@ public interface LocalProfileAssistant {
 
 
     void processPendingNotifications();
     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.es9plus.Es9PlusImpl;
 import com.truphone.lpa.ApduChannel;
 import com.truphone.lpa.ApduChannel;
 import com.truphone.lpa.LocalProfileAssistant;
 import com.truphone.lpa.LocalProfileAssistant;
+import com.truphone.lpa.LocalProfileInfo;
 import com.truphone.lpa.apdu.ApduUtils;
 import com.truphone.lpa.apdu.ApduUtils;
 import com.truphone.lpa.apdu.NotificationType;
 import com.truphone.lpa.apdu.NotificationType;
 import com.truphone.lpa.progress.DownloadProgress;
 import com.truphone.lpa.progress.DownloadProgress;
@@ -90,8 +91,7 @@ public class LocalProfileAssistantImpl implements LocalProfileAssistant {
     }
     }
 
 
     @Override
     @Override
-    public List<Map<String, String>> getProfiles() {
-
+    public List<LocalProfileInfo> getProfiles() {
         return new ListProfilesWorker(apduChannel).run();
         return new ListProfilesWorker(apduChannel).run();
     }
     }