diff --git a/.idea/misc.xml b/.idea/misc.xml
index 9c06351..2008b36 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -8,8 +8,10 @@
+
+
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 106b7ef..4d21fdd 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.impl.ProfileKey.*
import com.truphone.lpad.progress.Progress
import im.angry.openeuicc.R
import im.angry.openeuicc.databinding.EuiccProfileBinding
@@ -77,7 +78,7 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
}
withContext(Dispatchers.Main) {
- adapter.profiles = profiles.filter { it["PROFILE_CLASS"] != "0" }
+ adapter.profiles = profiles.filter { it[PROFILE_CLASS.name] != "0" }
adapter.notifyDataSetChanged()
binding.swipeRefresh.isRefreshing = false
}
@@ -127,8 +128,8 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
fun setProfile(profile: Map) {
this.profile = profile
- // TODO: The library is not exposing the nicknames. Expose them so that we can do something here.
- binding.name.text = profile["NAME"]
+ binding.name.text = getName()
+
binding.state.setText(
if (isEnabled()) {
R.string.enabled
@@ -136,13 +137,20 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
R.string.disabled
}
)
- binding.provider.text = profile["PROVIDER_NAME"]
- binding.iccid.text = profile["ICCID"]
+ binding.provider.text = profile[PROVIDER_NAME.name]
+ binding.iccid.text = profile[ICCID.name]
binding.iccid.transformationMethod = PasswordTransformationMethod.getInstance()
}
private fun isEnabled(): Boolean =
- profile["STATE"]?.lowercase() == "enabled"
+ profile[STATE.name]?.lowercase() == "enabled"
+
+ private fun getName(): String =
+ if (profile[NICKNAME.name].isNullOrEmpty()) {
+ profile[NAME.name]
+ } else {
+ profile[NICKNAME.name]
+ }!!
private fun showOptionsMenu() {
PopupMenu(binding.root.context, binding.profileMenu).apply {
@@ -158,7 +166,12 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
private fun onMenuItemClicked(item: MenuItem): Boolean =
when (item.itemId) {
R.id.enable -> {
- enableProfile(profile["ICCID"]!!)
+ enableProfile(profile[ICCID.name]!!)
+ true
+ }
+ R.id.rename -> {
+ ProfileRenameFragment.newInstance(slotId, profile[ICCID.name]!!, getName())
+ .show(childFragmentManager, ProfileRenameFragment.TAG)
true
}
else -> false
diff --git a/app/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt
new file mode 100644
index 0000000..e068582
--- /dev/null
+++ b/app/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt
@@ -0,0 +1,109 @@
+package im.angry.openeuicc.ui
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.lifecycleScope
+import im.angry.openeuicc.R
+import im.angry.openeuicc.databinding.FragmentProfileRenameBinding
+import im.angry.openeuicc.util.setWidthPercent
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.lang.Exception
+import java.lang.RuntimeException
+
+class ProfileRenameFragment : DialogFragment(), EuiccFragmentMarker {
+ companion object {
+ const val TAG = "ProfileRenameFragment"
+
+ fun newInstance(slotId: Int, iccid: String, currentName: String): ProfileRenameFragment {
+ val instance = newInstanceEuicc(ProfileRenameFragment::class.java, slotId)
+ instance.requireArguments().apply {
+ putString("iccid", iccid)
+ putString("currentName", currentName)
+ }
+ return instance
+ }
+ }
+
+ private var _binding: FragmentProfileRenameBinding? = null
+ private val binding get() = _binding!!
+
+ private var renaming = false
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentProfileRenameBinding.inflate(inflater, container, false)
+ binding.toolbar.inflateMenu(R.menu.fragment_profile_rename)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.toolbar.apply {
+ setTitle(R.string.rename)
+ setNavigationOnClickListener {
+ if (!renaming) dismiss()
+ }
+ setOnMenuItemClickListener {
+ if (!renaming) rename()
+ true
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ binding.profileRenameNewName.editText!!.setText(requireArguments().getString("currentName"))
+ }
+
+ override fun onResume() {
+ super.onResume()
+ setWidthPercent(95)
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return super.onCreateDialog(savedInstanceState).also {
+ it.window?.requestFeature(Window.FEATURE_NO_TITLE)
+ it.setCanceledOnTouchOutside(false)
+ }
+ }
+
+ private fun rename() {
+ val name = binding.profileRenameNewName.editText!!.text.toString().trim()
+
+ renaming = true
+ binding.progress.isIndeterminate = true
+ binding.progress.visibility = View.VISIBLE
+
+ lifecycleScope.launch {
+ try {
+ doRename(name)
+ } catch (e: Exception) {
+ Log.d(TAG, "Failed to rename profile")
+ Log.d(TAG, Log.getStackTraceString(e))
+ } finally {
+ if (parentFragment is EuiccProfilesChangedListener) {
+ (parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
+ }
+ dismiss()
+ }
+ }
+ }
+
+ private suspend fun doRename(name: String) = withContext(Dispatchers.IO) {
+ if (!channel.lpa.setNickname(requireArguments().getString("iccid"), name)) {
+ throw RuntimeException("Profile nickname not changed")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_profile_rename.xml b/app/src/main/res/layout/fragment_profile_rename.xml
new file mode 100644
index 0000000..9ac391f
--- /dev/null
+++ b/app/src/main/res/layout/fragment_profile_rename.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/fragment_profile_rename.xml b/app/src/main/res/menu/fragment_profile_rename.xml
new file mode 100644
index 0000000..bde850f
--- /dev/null
+++ b/app/src/main/res/menu/fragment_profile_rename.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/profile_options.xml b/app/src/main/res/menu/profile_options.xml
index 7929308..658b4e4 100644
--- a/app/src/main/res/menu/profile_options.xml
+++ b/app/src/main/res/menu/profile_options.xml
@@ -4,6 +4,10 @@
android:id="@+id/enable"
android:title="@string/enable"/>
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1597d22..9197709 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -10,6 +10,7 @@
Enable
Delete
+ Rename
eSIM profile switched. Please wait for a while when the card is restarting.
Cannot switch to new eSIM profile.
@@ -20,4 +21,6 @@
Scan QR Code
Download
Failed to download eSIM. Check your activation / QR code.
+
+ New nickname
\ No newline at end of file
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 f61a7c3..7cdfda3 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
@@ -37,4 +37,6 @@ public interface LocalProfileAssistant {
String allocateProfile(String mcc);
void processPendingNotifications();
+
+ 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/impl/ListProfilesWorker.java b/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/ListProfilesWorker.java
index 21b7d5c..546c18f 100644
--- 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
@@ -42,6 +42,7 @@ class ListProfilesWorker {
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.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():"");
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 a6c92a9..d221093 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
@@ -136,6 +136,11 @@ public class LocalProfileAssistantImpl implements LocalProfileAssistant {
}
+ @Override
+ public boolean setNickname(String iccid, String nickname) {
+ return new SetNicknameWorker(iccid, nickname, apduChannel).run();
+ }
+
public void smdsRetrieveEvents(Progress progress) {
// return new SmdsRetrieveEvents();
}
diff --git a/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/ProfileKey.java b/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/ProfileKey.java
index 135e8cc..7409c80 100644
--- a/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/ProfileKey.java
+++ b/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/ProfileKey.java
@@ -4,9 +4,10 @@ public enum ProfileKey {
ICCID,
STATE,
NAME,
+ NICKNAME,
PROVIDER_NAME,
ISDP_AID,
PROFILE_CLASS,
PROFILE_STATE
-
+
}
diff --git a/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/SetNicknameWorker.java b/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/SetNicknameWorker.java
new file mode 100644
index 0000000..b4f04e1
--- /dev/null
+++ b/libs/lpad-sm-dp-plus-connector/src/main/java/com/truphone/lpa/impl/SetNicknameWorker.java
@@ -0,0 +1,67 @@
+package com.truphone.lpa.impl;
+
+import com.truphone.lpa.ApduChannel;
+import com.truphone.lpa.apdu.ApduUtils;
+import com.truphone.rsp.dto.asn1.rspdefinitions.SetNicknameResponse;
+import com.truphone.util.LogStub;
+import com.truphone.util.Util;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Hex;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class SetNicknameWorker {
+ private static final Logger LOG = Logger.getLogger(ListProfilesWorker.class.getName());
+
+ private final String iccid;
+ private final String nickname;
+ private final ApduChannel apduChannel;
+
+ SetNicknameWorker(String iccid, String nickname, ApduChannel apduChannel) {
+ this.apduChannel = apduChannel;
+ this.iccid = iccid;
+ this.nickname = nickname;
+ }
+
+ boolean run() {
+ if (LogStub.getInstance().isDebugEnabled()) {
+ LogStub.getInstance().logDebug(LOG, LogStub.getInstance().getTag() + " - Renaming profile: " + iccid);
+ }
+
+ String apdu = ApduUtils.setNicknameApdu(iccid, Util.byteArrayToHexString(nickname.getBytes(), ""));
+ String eResponse = apduChannel.transmitAPDU(apdu);
+
+ try {
+ InputStream is = new ByteArrayInputStream(Hex.decodeHex(eResponse.toCharArray()));
+ SetNicknameResponse response = new SetNicknameResponse();
+
+ response.decode(is);
+
+ if ("0".equals(response.getSetNicknameResult().toString())) {
+ if (LogStub.getInstance().isDebugEnabled()) {
+ LogStub.getInstance().logDebug(LOG, LogStub.getInstance().getTag() + " - Profile renamed: " + iccid);
+ }
+ return true;
+ } else {
+ if (LogStub.getInstance().isDebugEnabled()) {
+ LogStub.getInstance().logDebug(LOG, LogStub.getInstance().getTag() + " - Profile not renamed: " + iccid);
+ }
+ return false;
+ }
+ } catch (IOException ioe) {
+ LOG.log(Level.SEVERE, LogStub.getInstance().getTag() + " - iccid: " + iccid + " profile failed to be renamed");
+
+ throw new RuntimeException("Unable to rename profile: " + iccid + ", response: " + eResponse);
+ } catch (DecoderException e) {
+ LOG.log(Level.SEVERE, LogStub.getInstance().getTag() + " - " + e.getMessage(), e);
+ LOG.log(Level.SEVERE, LogStub.getInstance().getTag() + " - iccid: " + iccid + " profile failed to be renamed. Exception in Decoder:" + e.getMessage());
+
+ throw new RuntimeException("Unable to rename profile: " + iccid + ", response: " + eResponse);
+ }
+ }
+}