implement eUICC profile downloading
hope that this actually works...
This commit is contained in:
parent
ca637da5ee
commit
df46ed883b
|
@ -17,7 +17,8 @@
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.OpenEUICC">
|
android:theme="@style/Theme.OpenEUICC"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.MainActivity"
|
android:name=".ui.MainActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
|
@ -20,4 +20,8 @@ val <T> T.slotId: Int where T: Fragment, T: EuiccFragmentMarker
|
||||||
|
|
||||||
val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccFragmentMarker
|
val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccFragmentMarker
|
||||||
get() =
|
get() =
|
||||||
(requireActivity().application as OpenEUICCApplication).euiccChannelRepo.availableChannels[slotId]
|
(requireActivity().application as OpenEUICCApplication).euiccChannelRepo.availableChannels[slotId]
|
||||||
|
|
||||||
|
interface EuiccProfilesChangedListener {
|
||||||
|
fun onEuiccProfilesChanged()
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class EuiccManagementFragment : Fragment(), EuiccFragmentMarker {
|
class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesChangedListener {
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance(slotId: Int): EuiccManagementFragment =
|
fun newInstance(slotId: Int): EuiccManagementFragment =
|
||||||
newInstanceEuicc(EuiccManagementFragment::class.java, slotId)
|
newInstanceEuicc(EuiccManagementFragment::class.java, slotId)
|
||||||
|
@ -55,6 +55,10 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker {
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onEuiccProfilesChanged() {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
private fun refresh() {
|
private fun refresh() {
|
||||||
binding.swipeRefresh.isRefreshing = true
|
binding.swipeRefresh.isRefreshing = true
|
||||||
|
|
|
@ -2,14 +2,22 @@ package im.angry.openeuicc.ui
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.view.*
|
import android.view.*
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.journeyapps.barcodescanner.ScanContract
|
import com.journeyapps.barcodescanner.ScanContract
|
||||||
import com.journeyapps.barcodescanner.ScanOptions
|
import com.journeyapps.barcodescanner.ScanOptions
|
||||||
|
import com.truphone.lpa.progress.DownloadProgress
|
||||||
import im.angry.openeuicc.R
|
import im.angry.openeuicc.R
|
||||||
import im.angry.openeuicc.databinding.FragmentProfileDownloadBinding
|
import im.angry.openeuicc.databinding.FragmentProfileDownloadBinding
|
||||||
import im.angry.openeuicc.util.setWidthPercent
|
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 {
|
class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.OnMenuItemClickListener {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -22,6 +30,8 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
|
||||||
private var _binding: FragmentProfileDownloadBinding? = null
|
private var _binding: FragmentProfileDownloadBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private var downloading = false
|
||||||
|
|
||||||
private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
|
private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
|
||||||
result.contents?.let { content ->
|
result.contents?.let { content ->
|
||||||
val components = content.split("$")
|
val components = content.split("$")
|
||||||
|
@ -46,13 +56,13 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
|
||||||
binding.toolbar.apply {
|
binding.toolbar.apply {
|
||||||
setTitle(R.string.profile_download)
|
setTitle(R.string.profile_download)
|
||||||
setNavigationOnClickListener {
|
setNavigationOnClickListener {
|
||||||
dismiss()
|
if (!downloading) dismiss()
|
||||||
}
|
}
|
||||||
setOnMenuItemClickListener(this@ProfileDownloadFragment)
|
setOnMenuItemClickListener(this@ProfileDownloadFragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMenuItemClick(item: MenuItem): Boolean =
|
override fun onMenuItemClick(item: MenuItem): Boolean = downloading ||
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.scan -> {
|
R.id.scan -> {
|
||||||
barcodeScannerLauncher.launch(ScanOptions().apply {
|
barcodeScannerLauncher.launch(ScanOptions().apply {
|
||||||
|
@ -61,6 +71,10 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
|
||||||
})
|
})
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.ok -> {
|
||||||
|
startDownloadProfile()
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,4 +89,56 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
|
||||||
it.setCanceledOnTouchOutside(false)
|
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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
10
app/src/main/res/drawable/ic_check_black.xml
Normal file
10
app/src/main/res/drawable/ic_check_black.xml
Normal 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>
|
|
@ -16,6 +16,27 @@
|
||||||
app:layout_constraintWidth_percent="1"
|
app:layout_constraintWidth_percent="1"
|
||||||
app:navigationIcon="?homeAsUpIndicator" />
|
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
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/profile_download_server"
|
android:id="@+id/profile_download_server"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
|
@ -6,4 +6,10 @@
|
||||||
android:icon="@drawable/ic_scan_black"
|
android:icon="@drawable/ic_scan_black"
|
||||||
android:title="@string/profile_download_scan"
|
android:title="@string/profile_download_scan"
|
||||||
app:showAsAction="ifRoom"/>
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/ok"
|
||||||
|
android:icon="@drawable/ic_check_black"
|
||||||
|
android:title="@string/profile_download_ok"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
</menu>
|
</menu>
|
15
app/src/main/res/raw/symantec_gsma_rspv2_root_ci1
Normal file
15
app/src/main/res/raw/symantec_gsma_rspv2_root_ci1
Normal 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-----
|
|
@ -12,4 +12,6 @@
|
||||||
<string name="profile_download_server">Server (RSP / SM-DP+)</string>
|
<string name="profile_download_server">Server (RSP / SM-DP+)</string>
|
||||||
<string name="profile_download_code">Activation Code</string>
|
<string name="profile_download_code">Activation Code</string>
|
||||||
<string name="profile_download_scan">Scan QR 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>
|
</resources>
|
9
app/src/main/res/xml/network_security_config.xml
Normal file
9
app/src/main/res/xml/network_security_config.xml
Normal 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>
|
Loading…
Reference in a new issue