diff --git a/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt b/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt index f6dba33..fb553f9 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt +++ b/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 + private lateinit var profile: LocalProfileInfo - fun setProfile(profile: Map) { + 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>) : RecyclerView.Adapter() { + inner class EuiccProfileAdapter(var profiles: List) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val binding = EuiccProfileBinding.inflate(LayoutInflater.from(parent.context), parent, false) diff --git a/build.gradle b/build.gradle index 17f0646..b05656c 100644 --- a/build.gradle +++ b/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) { diff --git a/libs/lpad-sm-dp-plus-connector/build.gradle b/libs/lpad-sm-dp-plus-connector/build.gradle index cc53684..11e5ab1 100644 --- a/libs/lpad-sm-dp-plus-connector/build.gradle +++ b/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' diff --git a/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/LocalProfileAssistant.java b/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/LocalProfileAssistant.java index 7cdfda3..c898bc2 100644 --- a/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/LocalProfileAssistant.java +++ b/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> getProfiles(); + List 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 + ); } \ No newline at end of file diff --git a/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/LocalProfileInfo.kt b/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/LocalProfileInfo.kt new file mode 100644 index 0000000..98e9f9e --- /dev/null +++ b/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 . + */ + +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() + } + } +} \ No newline at end of file diff --git a/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/ListProfilesWorker.java b/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/ListProfilesWorker.java deleted file mode 100644 index b0ea5ed..0000000 --- a/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/ListProfilesWorker.java +++ /dev/null @@ -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> run() { - String profilesInfo = getProfileInfoListResponse(); - ProfileInfoListResponse profiles = new ProfileInfoListResponse(); - List> profileList = new ArrayList<>(); - - try { - decodeProfiles(profilesInfo, profiles); - - for (ProfileInfo info : profiles.getProfileInfoListOk().getProfileInfo()) { - Map 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(); - } -} diff --git a/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/ListProfilesWorker.kt b/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/ListProfilesWorker.kt new file mode 100644 index 0000000..3724ccd --- /dev/null +++ b/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 . + */ + +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 { + val profilesInfo = profileInfoListResponse + val profiles = ProfileInfoListResponse() + val profileList: MutableList = 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 + ) + } +} \ No newline at end of file diff --git a/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/LocalProfileAssistantImpl.java b/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/LocalProfileAssistantImpl.java index d221093..37f071a 100644 --- a/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/LocalProfileAssistantImpl.java +++ b/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> getProfiles() { - + public List getProfiles() { return new ListProfilesWorker(apduChannel).run(); }