refactor: Emit structured profile info from the LPA library
This commit is contained in:
parent
5894dc9a71
commit
4b29660ef2
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue