implement eUICC profile downloading

hope that this actually works...
This commit is contained in:
Peter Cai 2022-04-30 16:39:33 -04:00
parent ca637da5ee
commit df46ed883b
10 changed files with 143 additions and 5 deletions

View file

@ -17,7 +17,8 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.OpenEUICC">
android:theme="@style/Theme.OpenEUICC"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".ui.MainActivity"
android:exported="true">

View file

@ -20,4 +20,8 @@ val <T> T.slotId: Int where T: Fragment, T: EuiccFragmentMarker
val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccFragmentMarker
get() =
(requireActivity().application as OpenEUICCApplication).euiccChannelRepo.availableChannels[slotId]
(requireActivity().application as OpenEUICCApplication).euiccChannelRepo.availableChannels[slotId]
interface EuiccProfilesChangedListener {
fun onEuiccProfilesChanged()
}

View file

@ -17,7 +17,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class EuiccManagementFragment : Fragment(), EuiccFragmentMarker {
class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesChangedListener {
companion object {
fun newInstance(slotId: Int): EuiccManagementFragment =
newInstanceEuicc(EuiccManagementFragment::class.java, slotId)
@ -55,6 +55,10 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker {
refresh()
}
override fun onEuiccProfilesChanged() {
refresh()
}
@SuppressLint("NotifyDataSetChanged")
private fun refresh() {
binding.swipeRefresh.isRefreshing = true

View file

@ -2,14 +2,22 @@ package im.angry.openeuicc.ui
import android.app.Dialog
import android.os.Bundle
import android.util.Log
import android.view.*
import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import com.truphone.lpa.progress.DownloadProgress
import im.angry.openeuicc.R
import im.angry.openeuicc.databinding.FragmentProfileDownloadBinding
import im.angry.openeuicc.util.setWidthPercent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.lang.Exception
class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.OnMenuItemClickListener {
companion object {
@ -22,6 +30,8 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
private var _binding: FragmentProfileDownloadBinding? = null
private val binding get() = _binding!!
private var downloading = false
private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
result.contents?.let { content ->
val components = content.split("$")
@ -46,13 +56,13 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
binding.toolbar.apply {
setTitle(R.string.profile_download)
setNavigationOnClickListener {
dismiss()
if (!downloading) dismiss()
}
setOnMenuItemClickListener(this@ProfileDownloadFragment)
}
}
override fun onMenuItemClick(item: MenuItem): Boolean =
override fun onMenuItemClick(item: MenuItem): Boolean = downloading ||
when (item.itemId) {
R.id.scan -> {
barcodeScannerLauncher.launch(ScanOptions().apply {
@ -61,6 +71,10 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
})
true
}
R.id.ok -> {
startDownloadProfile()
true
}
else -> false
}
@ -75,4 +89,56 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
it.setCanceledOnTouchOutside(false)
}
}
private fun startDownloadProfile() {
val server = binding.profileDownloadServer.editText!!.let {
it.text.toString().trim().apply {
if (isEmpty()) {
it.requestFocus()
return@startDownloadProfile
}
}
}
val code = binding.profileDownloadCode.editText!!.let {
it.text.toString().trim().apply {
if (isEmpty()) {
it.requestFocus()
return@startDownloadProfile
}
}
}
downloading = true
binding.profileDownloadServer.editText!!.isEnabled = false
binding.profileDownloadCode.editText!!.isEnabled = false
binding.progress.isIndeterminate = true
binding.progress.visibility = View.VISIBLE
lifecycleScope.launch {
try {
doDownloadProfile(server, code)
} catch (e: Exception) {
Log.d(TAG, "Error downloading profile")
Log.d(TAG, Log.getStackTraceString(e))
Toast.makeText(context, R.string.profile_download_failed, Toast.LENGTH_LONG).show()
} finally {
if (parentFragment is EuiccProfilesChangedListener) {
(parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
}
dismiss()
}
}
}
private suspend fun doDownloadProfile(server: String, code: String) = withContext(Dispatchers.IO) {
channel.lpa.downloadProfile("1\$${server}\$${code}", DownloadProgress().apply {
setProgressListener { _, _, percentage, _ ->
binding.progress.isIndeterminate = false
binding.progress.progress = (percentage * 100).toInt()
}
})
}
}

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

View file

@ -16,6 +16,27 @@
app:layout_constraintWidth_percent="1"
app:navigationIcon="?homeAsUpIndicator" />
<View
android:id="@+id/guideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@id/toolbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ProgressBar
android:id="@+id/progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/guideline"
style="@style/Widget.AppCompat.ProgressBar.Horizontal" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/profile_download_server"
android:layout_width="0dp"

View file

@ -6,4 +6,10 @@
android:icon="@drawable/ic_scan_black"
android:title="@string/profile_download_scan"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/ok"
android:icon="@drawable/ic_check_black"
android:title="@string/profile_download_ok"
app:showAsAction="ifRoom"/>
</menu>

View file

@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICSTCCAe+gAwIBAgIQbmhWeneg7nyF7hg5Y9+qejAKBggqhkjOPQQDAjBEMRgw
FgYDVQQKEw9HU00gQXNzb2NpYXRpb24xKDAmBgNVBAMTH0dTTSBBc3NvY2lhdGlv
biAtIFJTUDIgUm9vdCBDSTEwIBcNMTcwMjIyMDAwMDAwWhgPMjA1MjAyMjEyMzU5
NTlaMEQxGDAWBgNVBAoTD0dTTSBBc3NvY2lhdGlvbjEoMCYGA1UEAxMfR1NNIEFz
c29jaWF0aW9uIC0gUlNQMiBSb290IENJMTBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABJ1qutL0HCMX52GJ6/jeibsAqZfULWj/X10p/Min6seZN+hf5llovbCNuB2n
unLz+O8UD0SUCBUVo8e6n9X1TuajgcAwgb0wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
EwEB/wQFMAMBAf8wEwYDVR0RBAwwCogIKwYBBAGC6WAwFwYDVR0gAQH/BA0wCzAJ
BgdngRIBAgEAME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9nc21hLWNybC5zeW1h
dXRoLmNvbS9vZmZsaW5lY2EvZ3NtYS1yc3AyLXJvb3QtY2kxLmNybDAdBgNVHQ4E
FgQUgTcPUSXQsdQI1MOyMubSXnlb6/swCgYIKoZIzj0EAwIDSAAwRQIgIJdYsOMF
WziPK7l8nh5mu0qiRiVf25oa9ullG/OIASwCIQDqCmDrYf+GziHXBOiwJwnBaeBO
aFsiLzIEOaUuZwdNUw==
-----END CERTIFICATE-----

View file

@ -12,4 +12,6 @@
<string name="profile_download_server">Server (RSP / SM-DP+)</string>
<string name="profile_download_code">Activation Code</string>
<string name="profile_download_scan">Scan QR Code</string>
<string name="profile_download_ok">Download</string>
<string name="profile_download_failed">Failed to download eSIM. Check your activation / QR code.</string>
</resources>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="@raw/symantec_gsma_rspv2_root_ci1"/>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>