diff --git a/.gitignore b/.gitignore
index 1aa6f8a..2b15a47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,20 @@
-/.gradle
-/captures
-
-# Configuration files
-
-/keystore.properties
+*.iml
+.gradle
/local.properties
-
-# macOS
-
+/keystore.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+/.idea/deploymentTargetDropDown.xml
.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+/libs/**/build
+/buildSrc/build
+/app-deps/libs
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
index 0d51aca..26d3352 100644
--- a/.idea/.gitignore
+++ b/.idea/.gitignore
@@ -1,13 +1,3 @@
-/shelf
-/caches
-/libraries
-/assetWizardSettings.xml
-/deploymentTargetDropDown.xml
-/gradle.xml
-/misc.xml
-/modules.xml
-/navEditor.xml
-/runConfigurations.xml
+# Default ignored files
+/shelf/
/workspace.xml
-
-**/*.iml
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..589fc6c
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..a1f9d9f
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt b/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt
index 22ece46..e6a648a 100644
--- a/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt
@@ -46,7 +46,7 @@ class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) :
imei: String?,
confirmationCode: String?,
callback: ProfileDownloadCallback
- ) = lpa.downloadProfile(smdp, matchingId, imei, confirmationCode, callback)
+ ): Boolean = lpa.downloadProfile(smdp, matchingId, imei, confirmationCode, callback)
override fun deleteNotification(seqNumber: Long): Boolean = lpa.deleteNotification(seqNumber)
diff --git a/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt b/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt
index c4d16df..9cfd526 100644
--- a/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt
@@ -15,19 +15,16 @@ import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
-import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.last
import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.isActive
@@ -68,18 +65,6 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
*/
suspend fun Flow.waitDone(): Throwable? =
(this.last() as ForegroundTaskState.Done).error
-
- /**
- * Apply transform to a ForegroundTaskState flow so that it completes when a Done is seen.
- *
- * This must be applied each time a flow is returned for subscription purposes. If applied
- * beforehand, we lose the ability to subscribe multiple times.
- */
- private fun Flow.applyCompletionTransform() =
- transformWhile {
- emit(it)
- it !is ForegroundTaskState.Done
- }
}
inner class LocalBinder : Binder() {
@@ -113,25 +98,6 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
private val foregroundTaskState: MutableStateFlow =
MutableStateFlow(ForegroundTaskState.Idle)
- /**
- * A simple wrapper over a flow with taskId added.
- *
- * taskID is the exact millisecond-precision timestamp when the task is launched.
- */
- class ForegroundTaskSubscriberFlow(val taskId: Long, inner: Flow) :
- Flow by inner
-
- /**
- * A cache of subscribers to 5 recently-launched foreground tasks, identified by ID
- *
- * Only one can be run at the same time, but those that are done will be kept in this
- * map for a little while -- because UI components may be stopped and recreated while
- * tasks are running. Having this buffer allows the components to re-subscribe even if
- * the task completes while they are being recreated.
- */
- private val foregroundTaskSubscribers: MutableMap> =
- mutableMapOf()
-
override fun onBind(intent: Intent): IBinder {
super.onBind(intent)
return LocalBinder()
@@ -209,26 +175,12 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
NotificationManagerCompat.from(this).notify(TASK_FAILURE_ID, notification)
}
- /**
- * Recover the subscriber to a foreground task that is recently launched.
- *
- * null if the task doesn't exist, or was launched too long ago.
- */
- fun recoverForegroundTaskSubscriber(taskId: Long): ForegroundTaskSubscriberFlow? =
- foregroundTaskSubscribers[taskId]?.let {
- ForegroundTaskSubscriberFlow(taskId, it.applyCompletionTransform())
- }
-
/**
* Launch a potentially blocking foreground task in this service's lifecycle context.
* This function does not block, but returns a Flow that emits ForegroundTaskState
* updates associated with this task. The last update the returned flow will emit is
- * always ForegroundTaskState.Done.
- *
- * The returned flow can only be subscribed to once even though the underlying implementation
- * is a SharedFlow. This is due to the need to apply transformations so that the stream
- * actually completes. In order to subscribe multiple times, use `recoverForegroundTaskSubscriber`
- * to acquire another instance.
+ * always ForegroundTaskState.Done. The returned flow MUST be started in order for the
+ * foreground task to run.
*
* The task closure is expected to update foregroundTaskState whenever appropriate.
* If a foreground task is already running, this function returns null.
@@ -242,9 +194,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
failureTitle: String,
iconRes: Int,
task: suspend EuiccChannelManagerService.() -> Unit
- ): ForegroundTaskSubscriberFlow {
- val taskID = System.currentTimeMillis()
-
+ ): Flow {
// Atomically set the state to InProgress. If this returns true, we are
// the only task currently in progress.
if (!foregroundTaskState.compareAndSet(
@@ -252,9 +202,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
ForegroundTaskState.InProgress(0)
)
) {
- return ForegroundTaskSubscriberFlow(
- taskID,
- flow { emit(ForegroundTaskState.Done(IllegalStateException("There are tasks currently running"))) })
+ return flow { emit(ForegroundTaskState.Done(IllegalStateException("There are tasks currently running"))) }
}
lifecycleScope.launch(Dispatchers.Main) {
@@ -296,70 +244,34 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
}
}
- // This is the flow we are going to return. We allow multiple subscribers by
- // re-emitting state updates into this flow from another coroutine.
- // replay = 2 ensures that we at least have 1 previous state whenever subscribed to.
- // This is helpful when the task completed and is then re-subscribed to due to a
- // UI recreation event -- this way, the UI will know at least one last progress event
- // before completion / failure
- val subscriberFlow = MutableSharedFlow(
- replay = 2,
- onBufferOverflow = BufferOverflow.DROP_OLDEST
- )
-
// We should be the only task running, so we can subscribe to foregroundTaskState
// until we encounter ForegroundTaskState.Done.
// Then, we complete the returned flow, but we also set the state back to Idle.
// The state update back to Idle won't show up in the returned stream, because
// it has been completed by that point.
- lifecycleScope.launch(Dispatchers.Main) {
- foregroundTaskState
- .applyCompletionTransform()
- .onEach {
- // Also update our notification when we see an update
- // But ignore the first progress = 0 update -- that is the current value.
- // we need that to be handled by the main coroutine after it finishes.
- if (it !is ForegroundTaskState.InProgress || it.progress != 0) {
- updateForegroundNotification(title, iconRes)
- }
-
- subscriberFlow.emit(it)
+ return foregroundTaskState.transformWhile {
+ // Also update our notification when we see an update
+ // But ignore the first progress = 0 update -- that is the current value.
+ // we need that to be handled by the main coroutine after it finishes.
+ if (it !is ForegroundTaskState.InProgress || it.progress != 0) {
+ withContext(Dispatchers.Main) {
+ updateForegroundNotification(title, iconRes)
}
- .onCompletion {
- // Reset state back to Idle when we are done.
- // We do it here because otherwise Idle and Done might become conflated
- // when emitted by the main coroutine in quick succession.
- // Doing it here ensures we've seen Done. This Idle event won't be
- // emitted to the consumer because the subscription has completed here.
- foregroundTaskState.value = ForegroundTaskState.Idle
- }
- .collect()
- }
-
- foregroundTaskSubscribers[taskID] = subscriberFlow.asSharedFlow()
-
- if (foregroundTaskSubscribers.size > 5) {
- // Remove enough elements so that the size is kept at 5
- for (key in foregroundTaskSubscribers.keys.sorted()
- .take(foregroundTaskSubscribers.size - 5)) {
- foregroundTaskSubscribers.remove(key)
}
- }
-
- // Before we return, and after we have set everything up,
- // self-start with foreground permission.
- // This is going to unblock the main coroutine handling the task.
- startForegroundService(
- Intent(
- this@EuiccChannelManagerService,
- this@EuiccChannelManagerService::class.java
- )
- )
-
- return ForegroundTaskSubscriberFlow(
- taskID,
- subscriberFlow.asSharedFlow().applyCompletionTransform()
- )
+ emit(it)
+ it !is ForegroundTaskState.Done
+ }.onStart {
+ // When this Flow is started, we unblock the coroutine launched above by
+ // self-starting as a foreground service.
+ withContext(Dispatchers.Main) {
+ startForegroundService(
+ Intent(
+ this@EuiccChannelManagerService,
+ this@EuiccChannelManagerService::class.java
+ )
+ )
+ }
+ }.onCompletion { foregroundTaskState.value = ForegroundTaskState.Idle }
}
val isForegroundTaskRunning: Boolean
@@ -377,14 +289,14 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
matchingId: String?,
confirmationCode: String?,
imei: String?
- ): ForegroundTaskSubscriberFlow =
+ ): Flow =
launchForegroundTask(
getString(R.string.task_profile_download),
getString(R.string.task_profile_download_failure),
R.drawable.ic_task_sim_card_download
) {
euiccChannelManager.beginTrackedOperation(slotId, portId) {
- euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
+ val res = euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
channel.lpa.downloadProfile(
smdp,
matchingId,
@@ -399,6 +311,11 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
})
}
+ if (!res) {
+ // TODO: Provide more details on the error
+ throw RuntimeException("Failed to download profile; this is typically caused by another error happened before.")
+ }
+
preferenceRepository.notificationDownloadFlow.first()
}
}
@@ -408,7 +325,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
portId: Int,
iccid: String,
name: String
- ): ForegroundTaskSubscriberFlow =
+ ): Flow =
launchForegroundTask(
getString(R.string.task_profile_rename),
getString(R.string.task_profile_rename_failure),
@@ -430,7 +347,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
slotId: Int,
portId: Int,
iccid: String
- ): ForegroundTaskSubscriberFlow =
+ ): Flow =
launchForegroundTask(
getString(R.string.task_profile_delete),
getString(R.string.task_profile_delete_failure),
@@ -453,7 +370,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
iccid: String,
enable: Boolean, // Enable or disable the profile indicated in iccid
reconnectTimeoutMillis: Long = 0 // 0 = do not wait for reconnect, useful for USB readers
- ): ForegroundTaskSubscriberFlow =
+ ): Flow =
launchForegroundTask(
getString(R.string.task_profile_switch),
getString(R.string.task_profile_switch_failure),
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt
index 6d810cf..408c55d 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt
@@ -13,19 +13,10 @@ import androidx.fragment.app.Fragment
import im.angry.openeuicc.common.R
import im.angry.openeuicc.ui.BaseEuiccAccessActivity
import im.angry.openeuicc.util.*
-import net.typeblog.lpac_jni.LocalProfileAssistant
class DownloadWizardActivity: BaseEuiccAccessActivity() {
data class DownloadWizardState(
- var currentStepFragmentClassName: String?,
- var selectedLogicalSlot: Int,
- var smdp: String,
- var matchingId: String?,
- var confirmationCode: String?,
- var imei: String?,
- var downloadStarted: Boolean,
- var downloadTaskID: Long,
- var downloadError: LocalProfileAssistant.ProfileDownloadException?,
+ var selectedLogicalSlot: Int
)
private lateinit var state: DownloadWizardState
@@ -35,12 +26,6 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
private lateinit var prevButton: Button
private var currentFragment: DownloadWizardStepFragment? = null
- set(value) {
- if (this::state.isInitialized) {
- state.currentStepFragmentClassName = value?.javaClass?.name
- }
- field = value
- }
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
@@ -48,35 +33,18 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
setContentView(R.layout.activity_download_wizard)
onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
- // Make back == prev
- onPrevPressed()
+ // TODO: Actually implement this
}
})
state = DownloadWizardState(
- null,
- intent.getIntExtra("selectedLogicalSlot", 0),
- "",
- null,
- null,
- null,
- false,
- -1,
- null
+ intent.getIntExtra("selectedLogicalSlot", 0)
)
progressBar = requireViewById(R.id.progress)
nextButton = requireViewById(R.id.download_wizard_next)
prevButton = requireViewById(R.id.download_wizard_back)
- nextButton.setOnClickListener {
- onNextPressed()
- }
-
- prevButton.setOnClickListener {
- onPrevPressed()
- }
-
val navigation = requireViewById(R.id.download_wizard_navigation)
val origHeight = navigation.layoutParams.height
@@ -103,76 +71,14 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
}
}
- override fun onSaveInstanceState(outState: Bundle) {
- super.onSaveInstanceState(outState)
- outState.putString("currentStepFragmentClassName", state.currentStepFragmentClassName)
- outState.putInt("selectedLogicalSlot", state.selectedLogicalSlot)
- outState.putString("smdp", state.smdp)
- outState.putString("matchingId", state.matchingId)
- outState.putString("confirmationCode", state.confirmationCode)
- outState.putString("imei", state.imei)
- outState.putBoolean("downloadStarted", state.downloadStarted)
- outState.putLong("downloadTaskID", state.downloadTaskID)
- }
-
- override fun onRestoreInstanceState(savedInstanceState: Bundle) {
- super.onRestoreInstanceState(savedInstanceState)
- state.currentStepFragmentClassName = savedInstanceState.getString(
- "currentStepFragmentClassName",
- state.currentStepFragmentClassName
- )
- state.selectedLogicalSlot =
- savedInstanceState.getInt("selectedLogicalSlot", state.selectedLogicalSlot)
- state.smdp = savedInstanceState.getString("smdp", state.smdp)
- state.matchingId = savedInstanceState.getString("matchingId", state.matchingId)
- state.imei = savedInstanceState.getString("imei", state.imei)
- state.downloadStarted =
- savedInstanceState.getBoolean("downloadStarted", state.downloadStarted)
- state.downloadTaskID = savedInstanceState.getLong("downloadTaskID", state.downloadTaskID)
- }
-
- private fun onPrevPressed() {
- if (currentFragment?.hasPrev == true) {
- val prevFrag = currentFragment?.createPrevFragment()
- if (prevFrag == null) {
- finish()
- } else {
- showFragment(prevFrag, R.anim.slide_in_left, R.anim.slide_out_right)
- }
- }
- }
-
- private fun onNextPressed() {
- if (currentFragment?.hasNext == true) {
- currentFragment?.beforeNext()
- val nextFrag = currentFragment?.createNextFragment()
- if (nextFrag == null) {
- finish()
- } else {
- showFragment(nextFrag, R.anim.slide_in_right, R.anim.slide_out_left)
- }
- }
- }
-
override fun onInit() {
progressBar.visibility = View.GONE
-
- if (state.currentStepFragmentClassName != null) {
- val clazz = Class.forName(state.currentStepFragmentClassName!!)
- showFragment(clazz.getDeclaredConstructor().newInstance() as DownloadWizardStepFragment)
- } else {
- showFragment(DownloadWizardSlotSelectFragment())
- }
+ showFragment(DownloadWizardSlotSelectFragment())
}
- private fun showFragment(
- nextFrag: DownloadWizardStepFragment,
- enterAnim: Int = 0,
- exitAnim: Int = 0
- ) {
+ private fun showFragment(nextFrag: DownloadWizardStepFragment) {
currentFragment = nextFrag
- supportFragmentManager.beginTransaction().setCustomAnimations(enterAnim, exitAnim)
- .replace(R.id.step_fragment_container, nextFrag)
+ supportFragmentManager.beginTransaction().replace(R.id.step_fragment_container, nextFrag)
.commit()
refreshButtons()
}
@@ -201,15 +107,6 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
abstract fun createNextFragment(): DownloadWizardStepFragment?
abstract fun createPrevFragment(): DownloadWizardStepFragment?
- protected fun gotoNextFragment(next: DownloadWizardStepFragment? = null) {
- val realNext = next ?: createNextFragment()
- (requireActivity() as DownloadWizardActivity).showFragment(
- realNext!!,
- R.anim.slide_in_right,
- R.anim.slide_out_left
- )
- }
-
protected fun hideProgressBar() {
(requireActivity() as DownloadWizardActivity).progressBar.visibility = View.GONE
}
@@ -229,7 +126,5 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
protected fun refreshButtons() {
(requireActivity() as DownloadWizardActivity).refreshButtons()
}
-
- open fun beforeNext() {}
}
}
\ No newline at end of file
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt
deleted file mode 100644
index eb36710..0000000
--- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package im.angry.openeuicc.ui.wizard
-
-import android.os.Bundle
-import android.util.Patterns
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.core.widget.addTextChangedListener
-import com.google.android.material.textfield.TextInputLayout
-import im.angry.openeuicc.common.R
-
-class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
- private var inputComplete = false
-
- override val hasNext: Boolean
- get() = inputComplete
- override val hasPrev: Boolean
- get() = true
-
- private lateinit var smdp: TextInputLayout
- private lateinit var matchingId: TextInputLayout
- private lateinit var confirmationCode: TextInputLayout
- private lateinit var imei: TextInputLayout
-
- override fun beforeNext() {
- state.smdp = smdp.editText!!.text.toString().trim()
- // Treat empty inputs as null -- this is important for the download step
- state.matchingId = matchingId.editText!!.text.toString().trim().ifBlank { null }
- state.confirmationCode = confirmationCode.editText!!.text.toString().trim().ifBlank { null }
- state.imei = imei.editText!!.text.toString().ifBlank { null }
- }
-
- override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment =
- DownloadWizardProgressFragment()
-
- override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment =
- DownloadWizardMethodSelectFragment()
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- val view = inflater.inflate(R.layout.fragment_download_details, container, false)
- smdp = view.requireViewById(R.id.profile_download_server)
- matchingId = view.requireViewById(R.id.profile_download_code)
- confirmationCode = view.requireViewById(R.id.profile_download_confirmation_code)
- imei = view.requireViewById(R.id.profile_download_imei)
- smdp.editText!!.addTextChangedListener {
- updateInputCompleteness()
- }
- return view
- }
-
- override fun onStart() {
- super.onStart()
- smdp.editText!!.setText(state.smdp)
- matchingId.editText!!.setText(state.matchingId)
- confirmationCode.editText!!.setText(state.confirmationCode)
- imei.editText!!.setText(state.imei)
- updateInputCompleteness()
- }
-
- private fun updateInputCompleteness() {
- inputComplete = Patterns.DOMAIN_NAME.matcher(smdp.editText!!.text).matches()
- refreshButtons()
- }
-}
\ No newline at end of file
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt
deleted file mode 100644
index 6c578dd..0000000
--- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-package im.angry.openeuicc.ui.wizard
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import im.angry.openeuicc.common.R
-import im.angry.openeuicc.util.*
-
-class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
- override val hasNext: Boolean
- get() = true
- override val hasPrev: Boolean
- get() = false
-
- private lateinit var diagnosticTextView: TextView
-
- override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? = null
-
- override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment? = null
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- val view = inflater.inflate(R.layout.fragment_download_diagnostics, container, false)
- diagnosticTextView = view.requireViewById(R.id.download_wizard_diagnostics_text)
- return view
- }
-
- override fun onStart() {
- super.onStart()
- val str = buildDiagnosticsText()
- if (str == null) {
- requireActivity().finish()
- return
- }
-
- diagnosticTextView.text = str
- }
-
- private fun buildDiagnosticsText(): String? = state.downloadError?.let { err ->
- val ret = StringBuilder()
-
- err.lastHttpResponse?.let { resp ->
- if (resp.rcode != 200) {
- // Only show the status if it's not 200
- // Because we can have errors even if the rcode is 200 due to SM-DP+ servers being dumb
- // and showing 200 might mislead users
- ret.appendLine(
- getString(
- R.string.download_wizard_diagnostics_last_http_status,
- resp.rcode
- )
- )
- ret.appendLine()
- }
-
- ret.appendLine(getString(R.string.download_wizard_diagnostics_last_http_response))
- ret.appendLine()
-
- val str = resp.data.decodeToString(throwOnInvalidSequence = false)
- ret.appendLine(
- if (str.startsWith('{')) {
- str.prettyPrintJson()
- } else {
- str
- }
- )
-
- ret.appendLine()
- }
-
- err.lastHttpException?.let { e ->
- ret.appendLine(getString(R.string.download_wizard_diagnostics_last_http_exception))
- ret.appendLine()
- ret.appendLine("${e.javaClass.name}: ${e.message}")
- ret.appendLine(e.stackTrace.joinToString("\n"))
- ret.appendLine()
- }
-
- err.lastApduResponse?.let { resp ->
- val isSuccess =
- resp.size >= 2 && resp[resp.size - 2] == 0x90.toByte() && resp[resp.size - 1] == 0x00.toByte()
-
- if (isSuccess) {
- ret.appendLine(getString(R.string.download_wizard_diagnostics_last_apdu_response_success))
- } else {
- // Only show the full APDU response when it's a failure
- // Otherwise it's going to get very crammed
- ret.appendLine(
- getString(
- R.string.download_wizard_diagnostics_last_apdu_response,
- resp.encodeHex()
- )
- )
- ret.appendLine()
-
- ret.appendLine(getString(R.string.download_wizard_diagnostics_last_apdu_response_fail))
- }
- }
-
- err.lastApduException?.let { e ->
- ret.appendLine(getString(R.string.download_wizard_diagnostics_last_apdu_exception))
- ret.appendLine()
- ret.appendLine("${e.javaClass.name}: ${e.message}")
- ret.appendLine(e.stackTrace.joinToString("\n"))
- ret.appendLine()
- }
-
- ret.toString()
- }
-}
\ No newline at end of file
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt
deleted file mode 100644
index 4d8a38f..0000000
--- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-package im.angry.openeuicc.ui.wizard
-
-import android.graphics.BitmapFactory
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.lifecycle.lifecycleScope
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import com.journeyapps.barcodescanner.ScanContract
-import com.journeyapps.barcodescanner.ScanOptions
-import im.angry.openeuicc.common.R
-import im.angry.openeuicc.util.*
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
- data class DownloadMethod(
- val iconRes: Int,
- val titleRes: Int,
- val onClick: () -> Unit
- )
-
- // TODO: Maybe we should find a better barcode scanner (or an external one?)
- private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
- result.contents?.let { content ->
- processLpaString(content)
- }
- }
-
- private val gallerySelectorLauncher =
- registerForActivityResult(ActivityResultContracts.GetContent()) { result ->
- if (result == null) return@registerForActivityResult
-
- lifecycleScope.launch(Dispatchers.IO) {
- runCatching {
- requireContext().contentResolver.openInputStream(result)?.let { input ->
- val bmp = BitmapFactory.decodeStream(input)
- input.close()
-
- decodeQrFromBitmap(bmp)?.let {
- withContext(Dispatchers.Main) {
- processLpaString(it)
- }
- }
-
- bmp.recycle()
- }
- }
- }
- }
-
- val downloadMethods = arrayOf(
- DownloadMethod(R.drawable.ic_scan_black, R.string.download_wizard_method_qr_code) {
- barcodeScannerLauncher.launch(ScanOptions().apply {
- setDesiredBarcodeFormats(ScanOptions.QR_CODE)
- setOrientationLocked(false)
- })
- },
- DownloadMethod(R.drawable.ic_gallery_black, R.string.download_wizard_method_gallery) {
- gallerySelectorLauncher.launch("image/*")
- },
- DownloadMethod(R.drawable.ic_edit, R.string.download_wizard_method_manual) {
- gotoNextFragment(DownloadWizardDetailsFragment())
- }
- )
-
- override val hasNext: Boolean
- get() = false
- override val hasPrev: Boolean
- get() = true
-
- override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? =
- null
-
- override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment =
- DownloadWizardSlotSelectFragment()
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- val view = inflater.inflate(R.layout.fragment_download_method_select, container, false)
- val recyclerView = view.requireViewById(R.id.download_method_list)
- recyclerView.adapter = DownloadMethodAdapter()
- recyclerView.layoutManager =
- LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
- recyclerView.addItemDecoration(
- DividerItemDecoration(
- requireContext(),
- LinearLayoutManager.VERTICAL
- )
- )
- return view
- }
-
- private fun processLpaString(s: String) {
- val components = s.split("$")
- if (components.size < 3 || components[0] != "LPA:1") return
- state.smdp = components[1]
- state.matchingId = components[2]
- gotoNextFragment(DownloadWizardDetailsFragment())
- }
-
- private class DownloadMethodViewHolder(private val root: View) : ViewHolder(root) {
- private val icon = root.requireViewById(R.id.download_method_icon)
- private val title = root.requireViewById(R.id.download_method_title)
-
- fun bind(item: DownloadMethod) {
- icon.setImageResource(item.iconRes)
- title.setText(item.titleRes)
- root.setOnClickListener { item.onClick() }
- }
- }
-
- private inner class DownloadMethodAdapter : RecyclerView.Adapter() {
- override fun onCreateViewHolder(
- parent: ViewGroup,
- viewType: Int
- ): DownloadMethodViewHolder {
- val view = LayoutInflater.from(parent.context)
- .inflate(R.layout.download_method_item, parent, false)
- return DownloadMethodViewHolder(view)
- }
-
- override fun getItemCount(): Int = downloadMethods.size
-
- override fun onBindViewHolder(holder: DownloadMethodViewHolder, position: Int) {
- holder.bind(downloadMethods[position])
- }
-
- }
-}
\ No newline at end of file
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardProgressFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardProgressFragment.kt
deleted file mode 100644
index f6f63fd..0000000
--- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardProgressFragment.kt
+++ /dev/null
@@ -1,239 +0,0 @@
-package im.angry.openeuicc.ui.wizard
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.ProgressBar
-import android.widget.TextView
-import androidx.lifecycle.lifecycleScope
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import im.angry.openeuicc.common.R
-import im.angry.openeuicc.service.EuiccChannelManagerService
-import im.angry.openeuicc.util.*
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.launch
-import net.typeblog.lpac_jni.LocalProfileAssistant
-import net.typeblog.lpac_jni.ProfileDownloadCallback
-
-class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
- companion object {
- /**
- * An array of LPA-side state types, mapping 1:1 to progressItems
- */
- val LPA_PROGRESS_STATES = arrayOf(
- ProfileDownloadCallback.DownloadState.Preparing,
- ProfileDownloadCallback.DownloadState.Connecting,
- ProfileDownloadCallback.DownloadState.Authenticating,
- ProfileDownloadCallback.DownloadState.Downloading,
- ProfileDownloadCallback.DownloadState.Finalizing,
- )
- }
-
- private enum class ProgressState {
- NotStarted,
- InProgress,
- Done,
- Error
- }
-
- private data class ProgressItem(
- val titleRes: Int,
- var state: ProgressState
- )
-
- private val progressItems = arrayOf(
- ProgressItem(R.string.download_wizard_progress_step_preparing, ProgressState.NotStarted),
- ProgressItem(R.string.download_wizard_progress_step_connecting, ProgressState.NotStarted),
- ProgressItem(
- R.string.download_wizard_progress_step_authenticating,
- ProgressState.NotStarted
- ),
- ProgressItem(R.string.download_wizard_progress_step_downloading, ProgressState.NotStarted),
- ProgressItem(R.string.download_wizard_progress_step_finalizing, ProgressState.NotStarted)
- )
-
- private val adapter = ProgressItemAdapter()
-
- private var isDone = false
-
- override val hasNext: Boolean
- get() = isDone
- override val hasPrev: Boolean
- get() = false
-
- override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? =
- if (state.downloadError != null) {
- DownloadWizardDiagnosticsFragment()
- } else {
- null
- }
-
- override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment? = null
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- val view = inflater.inflate(R.layout.fragment_download_progress, container, false)
- val recyclerView = view.requireViewById(R.id.download_progress_list)
- recyclerView.adapter = adapter
- recyclerView.layoutManager =
- LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
- recyclerView.addItemDecoration(
- DividerItemDecoration(
- requireContext(),
- LinearLayoutManager.VERTICAL
- )
- )
- return view
- }
-
- override fun onStart() {
- super.onStart()
-
- lifecycleScope.launch {
- showProgressBar(-1) // set indeterminate first
- ensureEuiccChannelManager()
-
- val subscriber = startDownloadOrSubscribe()
-
- if (subscriber == null) {
- requireActivity().finish()
- return@launch
- }
-
- subscriber.onEach {
- when (it) {
- is EuiccChannelManagerService.ForegroundTaskState.Done -> {
- hideProgressBar()
-
- // Change the state of the last InProgress item to Error
- progressItems.forEachIndexed { index, progressItem ->
- if (progressItem.state == ProgressState.InProgress) {
- progressItem.state = ProgressState.Error
- }
-
- adapter.notifyItemChanged(index)
- }
-
- state.downloadError =
- it.error as? LocalProfileAssistant.ProfileDownloadException
-
- isDone = true
- refreshButtons()
- }
-
- is EuiccChannelManagerService.ForegroundTaskState.InProgress -> {
- updateProgress(it.progress)
- }
-
- else -> {}
- }
- }.collect()
- }
- }
-
- private suspend fun startDownloadOrSubscribe(): EuiccChannelManagerService.ForegroundTaskSubscriberFlow? =
- if (state.downloadStarted) {
- // This will also return null if task ID is -1 (uninitialized), too
- euiccChannelManagerService.recoverForegroundTaskSubscriber(state.downloadTaskID)
- } else {
- euiccChannelManagerService.waitForForegroundTask()
-
- val (slotId, portId) = euiccChannelManager.withEuiccChannel(state.selectedLogicalSlot) { channel ->
- Pair(channel.slotId, channel.portId)
- }
-
- // Set started to true even before we start -- in case we get killed in the middle
- state.downloadStarted = true
-
- val ret = euiccChannelManagerService.launchProfileDownloadTask(
- slotId,
- portId,
- state.smdp,
- state.matchingId,
- state.confirmationCode,
- state.imei
- )
-
- state.downloadTaskID = ret.taskId
-
- ret
- }
-
- private fun updateProgress(progress: Int) {
- showProgressBar(progress)
-
- val lpaState = ProfileDownloadCallback.lookupStateFromProgress(progress)
- val stateIndex = LPA_PROGRESS_STATES.indexOf(lpaState)
-
- if (stateIndex > 0) {
- for (i in (0..(R.id.download_progress_item_title)
- private val progressBar =
- root.requireViewById(R.id.download_progress_icon_progress)
- private val icon = root.requireViewById(R.id.download_progress_icon)
-
- fun bind(item: ProgressItem) {
- title.text = getString(item.titleRes)
-
- when (item.state) {
- ProgressState.NotStarted -> {
- progressBar.visibility = View.GONE
- icon.visibility = View.GONE
- }
-
- ProgressState.InProgress -> {
- progressBar.visibility = View.VISIBLE
- icon.visibility = View.GONE
- }
-
- ProgressState.Done -> {
- progressBar.visibility = View.GONE
- icon.setImageResource(R.drawable.ic_checkmark_outline)
- icon.visibility = View.VISIBLE
- }
-
- ProgressState.Error -> {
- progressBar.visibility = View.GONE
- icon.setImageResource(R.drawable.ic_error_outline)
- icon.visibility = View.VISIBLE
- }
- }
- }
- }
-
- private inner class ProgressItemAdapter : RecyclerView.Adapter() {
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProgressItemHolder {
- val root = LayoutInflater.from(parent.context)
- .inflate(R.layout.download_progress_item, parent, false)
- return ProgressItemHolder(root)
- }
-
- override fun getItemCount(): Int = progressItems.size
-
- override fun onBindViewHolder(holder: ProgressItemHolder, position: Int) {
- holder.bind(progressItems[position])
- }
- }
-}
\ No newline at end of file
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt
index c9a9e0f..6720242 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt
@@ -26,8 +26,6 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
val hasMultiplePorts: Boolean,
val portId: Int,
val eID: String,
- val freeSpace: Int,
- val imei: String,
val enabledProfileName: String?
)
@@ -40,8 +38,9 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
override val hasPrev: Boolean
get() = true
- override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment =
- DownloadWizardMethodSelectFragment()
+ override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? {
+ TODO("Not yet implemented")
+ }
override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment? = null
@@ -66,7 +65,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
}
}
- @SuppressLint("NotifyDataSetChanged", "MissingPermission")
+ @SuppressLint("NotifyDataSetChanged")
private suspend fun init() {
ensureEuiccChannelManager()
showProgressBar(-1)
@@ -78,16 +77,10 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
channel.port.card.ports.size > 1,
channel.portId,
channel.lpa.eID,
- channel.lpa.euiccInfo2?.freeNvram ?: 0,
- try {
- telephonyManager.getImei(channel.logicalSlotId) ?: ""
- } catch (e: Exception) {
- ""
- },
channel.lpa.profiles.find { it.state == LocalProfileInfo.State.Enabled }?.displayName
)
}
- }.toList().sortedBy { it.logicalSlotId }
+ }.toList()
adapter.slots = slots
// Ensure we always have a selected slot by default
@@ -101,10 +94,6 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
0
}
- if (slots.isNotEmpty()) {
- state.imei = slots[adapter.currentSelectedIdx].imei
- }
-
adapter.notifyDataSetChanged()
hideProgressBar()
loaded = true
@@ -116,7 +105,6 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
private val type = root.requireViewById(R.id.slot_item_type)
private val eID = root.requireViewById(R.id.slot_item_eid)
private val activeProfile = root.requireViewById(R.id.slot_item_active_profile)
- private val freeSpace = root.requireViewById(R.id.slot_item_free_space)
private val checkBox = root.requireViewById(R.id.slot_checkbox)
private var curIdx = -1
@@ -136,7 +124,6 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
adapter.notifyItemChanged(curIdx)
// Selected index isn't logical slot ID directly, needs a conversion
state.selectedLogicalSlot = adapter.slots[adapter.currentSelectedIdx].logicalSlotId
- state.imei = adapter.slots[adapter.currentSelectedIdx].imei
}
fun bind(item: SlotInfo, idx: Int) {
@@ -156,7 +143,6 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
title.text = root.context.getString(R.string.download_wizard_slot_title, item.logicalSlotId)
eID.text = item.eID
activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.unknown)
- freeSpace.text = formatFreeSpace(item.freeSpace)
checkBox.isChecked = adapter.currentSelectedIdx == idx
}
}
diff --git a/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt
index 8d72462..ebf8729 100644
--- a/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt
@@ -27,74 +27,4 @@ fun formatFreeSpace(size: Int): String =
"%.2f KiB".format(size.toDouble() / 1024)
} else {
"$size B"
- }
-
-fun String.prettyPrintJson(): String {
- val ret = StringBuilder()
- var inQuotes = false
- var escaped = false
- val indentSymbolStack = ArrayDeque()
-
- val addNewLine = {
- ret.append('\n')
- repeat(indentSymbolStack.size) {
- ret.append('\t')
- }
- }
-
- var lastChar = ' '
-
- for (c in this) {
- when {
- !inQuotes && (c == '{' || c == '[') -> {
- ret.append(c)
- indentSymbolStack.addLast(c)
- addNewLine()
- }
-
- !inQuotes && (c == '}' || c == ']') -> {
- indentSymbolStack.removeLast()
- if (lastChar != ',') {
- addNewLine()
- }
- ret.append(c)
- }
-
- !inQuotes && c == ',' -> {
- ret.append(c)
- addNewLine()
- }
-
- !inQuotes && c == ':' -> {
- ret.append(c)
- ret.append(' ')
- }
-
- inQuotes && c == '\\' -> {
- ret.append(c)
- escaped = true
- continue
- }
-
- !escaped && c == '"' -> {
- ret.append(c)
- inQuotes = !inQuotes
- }
-
- !inQuotes && c == ' ' -> {
- // Do nothing -- we ignore spaces outside of quotes by default
- // This is to ensure predictable formatting
- }
-
- else -> ret.append(c)
- }
-
- if (escaped) {
- escaped = false
- }
-
- lastChar = c
- }
-
- return ret.toString()
-}
\ No newline at end of file
+ }
\ No newline at end of file
diff --git a/app-common/src/main/res/anim/slide_in_left.xml b/app-common/src/main/res/anim/slide_in_left.xml
deleted file mode 100644
index 9078d1f..0000000
--- a/app-common/src/main/res/anim/slide_in_left.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
diff --git a/app-common/src/main/res/anim/slide_in_right.xml b/app-common/src/main/res/anim/slide_in_right.xml
deleted file mode 100644
index 42aa3f5..0000000
--- a/app-common/src/main/res/anim/slide_in_right.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
diff --git a/app-common/src/main/res/anim/slide_out_left.xml b/app-common/src/main/res/anim/slide_out_left.xml
deleted file mode 100644
index 1a806a9..0000000
--- a/app-common/src/main/res/anim/slide_out_left.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
diff --git a/app-common/src/main/res/anim/slide_out_right.xml b/app-common/src/main/res/anim/slide_out_right.xml
deleted file mode 100644
index f209f38..0000000
--- a/app-common/src/main/res/anim/slide_out_right.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
diff --git a/app-common/src/main/res/drawable/ic_edit.xml b/app-common/src/main/res/drawable/ic_edit.xml
deleted file mode 100644
index 3c53db7..0000000
--- a/app-common/src/main/res/drawable/ic_edit.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/app-common/src/main/res/layout/download_method_item.xml b/app-common/src/main/res/layout/download_method_item.xml
deleted file mode 100644
index 5b2c2a8..0000000
--- a/app-common/src/main/res/layout/download_method_item.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app-common/src/main/res/layout/download_progress_item.xml b/app-common/src/main/res/layout/download_progress_item.xml
deleted file mode 100644
index f1d0852..0000000
--- a/app-common/src/main/res/layout/download_progress_item.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app-common/src/main/res/layout/download_slot_item.xml b/app-common/src/main/res/layout/download_slot_item.xml
index d0ca176..fa06b4c 100644
--- a/app-common/src/main/res/layout/download_slot_item.xml
+++ b/app-common/src/main/res/layout/download_slot_item.xml
@@ -61,20 +61,6 @@
android:layout_height="wrap_content"
android:textSize="14sp" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app-common/src/main/res/layout/fragment_download_diagnostics.xml b/app-common/src/main/res/layout/fragment_download_diagnostics.xml
deleted file mode 100644
index b9a0bc2..0000000
--- a/app-common/src/main/res/layout/fragment_download_diagnostics.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app-common/src/main/res/layout/fragment_download_method_select.xml b/app-common/src/main/res/layout/fragment_download_method_select.xml
deleted file mode 100644
index a57e186..0000000
--- a/app-common/src/main/res/layout/fragment_download_method_select.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app-common/src/main/res/layout/fragment_download_progress.xml b/app-common/src/main/res/layout/fragment_download_progress.xml
deleted file mode 100644
index 0ec58e4..0000000
--- a/app-common/src/main/res/layout/fragment_download_progress.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app-common/src/main/res/layout/fragment_download_slot_select.xml b/app-common/src/main/res/layout/fragment_download_slot_select.xml
index 3dfe6fd..6bd2e5d 100644
--- a/app-common/src/main/res/layout/fragment_download_slot_select.xml
+++ b/app-common/src/main/res/layout/fragment_download_slot_select.xml
@@ -9,15 +9,10 @@
android:text="@string/download_wizard_slot_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center_horizontal"
android:textSize="20sp"
- android:layout_marginTop="20sp"
- android:layout_marginBottom="20sp"
- android:layout_marginStart="60sp"
- android:layout_marginEnd="60sp"
+ android:layout_margin="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constrainedWidth="true"
app:layout_constraintTop_toTopOf="parent" />
Download Wizard
Back
Next
- Select or confirm the eSIM you would like to download to:
+ Confirm the eSIM slot:
Logical slot %d
Type:
Removable
@@ -69,26 +69,6 @@
Internal, port %d
eID:
Active Profile:
- Free Space:
- How would you like to download the eSIM profile?
- Scan a QR code with camera
- Load a QR code from gallery
- Enter manually
- Input or confirm details for downloading your eSIM:
- Downloading your eSIM…
- Preparing
- Establishing connection to server
- Authenticating your device with server
- Downloading eSIM profile
- Loading eSIM profile into storage
- Error diagnostics
- Last HTTP status (from server): %d
- Last HTTP response (from server):
- Last HTTP exception:
- Last APDU response (from SIM): %s
- Last APDU response (from SIM) is successful
- Last APDU response (from SIM) is a failure
- Last APDU exception:
New nickname
diff --git a/app-deps/.gitignore b/app-deps/.gitignore
index c23e5a2..42afabf 100644
--- a/app-deps/.gitignore
+++ b/app-deps/.gitignore
@@ -1,2 +1 @@
-/build
-/libs
\ No newline at end of file
+/build
\ No newline at end of file
diff --git a/app-common/src/main/res/drawable/ic_checkmark_outline.xml b/app-unpriv/src/main/res/drawable/ic_checkmark_outline.xml
similarity index 100%
rename from app-common/src/main/res/drawable/ic_checkmark_outline.xml
rename to app-unpriv/src/main/res/drawable/ic_checkmark_outline.xml
diff --git a/app-common/src/main/res/drawable/ic_error_outline.xml b/app-unpriv/src/main/res/drawable/ic_error_outline.xml
similarity index 100%
rename from app-common/src/main/res/drawable/ic_error_outline.xml
rename to app-unpriv/src/main/res/drawable/ic_error_outline.xml
diff --git a/buildSrc/.gitignore b/buildSrc/.gitignore
deleted file mode 100644
index 6fbe8a4..0000000
--- a/buildSrc/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/.gradle
-/build
\ No newline at end of file
diff --git a/libs/lpac-jni/.gitignore b/libs/lpac-jni/.gitignore
deleted file mode 100644
index 42afabf..0000000
--- a/libs/lpac-jni/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt
index cf870e1..f256caf 100644
--- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt
+++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt
@@ -1,16 +1,6 @@
package net.typeblog.lpac_jni
-import net.typeblog.lpac_jni.HttpInterface.HttpResponse
-
interface LocalProfileAssistant {
- @Suppress("ArrayInDataClass")
- data class ProfileDownloadException(
- val lastHttpResponse: HttpResponse?,
- val lastHttpException: Exception?,
- val lastApduResponse: ByteArray?,
- val lastApduException: Exception?,
- ) : Exception("Failed to download profile")
-
val valid: Boolean
val profiles: List
val notifications: List
@@ -32,7 +22,7 @@ interface LocalProfileAssistant {
fun deleteProfile(iccid: String): Boolean
fun downloadProfile(smdp: String, matchingId: String?, imei: String?,
- confirmationCode: String?, callback: ProfileDownloadCallback)
+ confirmationCode: String?, callback: ProfileDownloadCallback): Boolean
fun deleteNotification(seqNumber: Long): Boolean
fun handleNotification(seqNumber: Long): Boolean
diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ProfileDownloadCallback.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ProfileDownloadCallback.kt
index 289ddf6..579ad58 100644
--- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ProfileDownloadCallback.kt
+++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ProfileDownloadCallback.kt
@@ -1,18 +1,6 @@
package net.typeblog.lpac_jni
interface ProfileDownloadCallback {
- companion object {
- fun lookupStateFromProgress(progress: Int): DownloadState =
- when (progress) {
- 0 -> DownloadState.Preparing
- 20 -> DownloadState.Connecting
- 40 -> DownloadState.Authenticating
- 60 -> DownloadState.Downloading
- 80 -> DownloadState.Finalizing
- else -> throw IllegalArgumentException("Unknown state")
- }
- }
-
enum class DownloadState(val progress: Int) {
Preparing(0),
Connecting(20), // Before {server,client} authentication
diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt
index 70606d9..51039cd 100644
--- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt
+++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt
@@ -5,76 +5,19 @@ import net.typeblog.lpac_jni.LpacJni
import net.typeblog.lpac_jni.ApduInterface
import net.typeblog.lpac_jni.EuiccInfo2
import net.typeblog.lpac_jni.HttpInterface
-import net.typeblog.lpac_jni.HttpInterface.HttpResponse
import net.typeblog.lpac_jni.LocalProfileAssistant
import net.typeblog.lpac_jni.LocalProfileInfo
import net.typeblog.lpac_jni.LocalProfileNotification
import net.typeblog.lpac_jni.ProfileDownloadCallback
class LocalProfileAssistantImpl(
- rawApduInterface: ApduInterface,
- rawHttpInterface: HttpInterface
+ private val apduInterface: ApduInterface,
+ httpInterface: HttpInterface
): LocalProfileAssistant {
companion object {
private const val TAG = "LocalProfileAssistantImpl"
}
- /**
- * A thin wrapper over ApduInterface to acquire exceptions and errors transparently
- */
- private class ApduInterfaceWrapper(val apduInterface: ApduInterface) :
- ApduInterface by apduInterface {
- var lastApduResponse: ByteArray? = null
- var lastApduException: Exception? = null
-
- override fun transmit(tx: ByteArray): ByteArray =
- try {
- apduInterface.transmit(tx).also {
- lastApduException = null
- lastApduResponse = it
- }
- } catch (e: Exception) {
- lastApduResponse = null
- lastApduException = e
- throw e
- }
- }
-
- /**
- * Same for HTTP for diagnostics
- */
- private class HttpInterfaceWrapper(val httpInterface: HttpInterface) :
- HttpInterface by httpInterface {
- /**
- * The last HTTP response we have received from the SM-DP+ server.
- *
- * This is intended for error diagnosis. However, note that most SM-DP+ servers
- * respond with 200 even when there is an error. This needs to be taken into
- * account when designing UI.
- */
- var lastHttpResponse: HttpResponse? = null
-
- /**
- * The last exception that has been thrown during a HTTP connection
- */
- var lastHttpException: Exception? = null
-
- override fun transmit(url: String, tx: ByteArray, headers: Array): HttpResponse =
- try {
- httpInterface.transmit(url, tx, headers).also {
- lastHttpException = null
- lastHttpResponse = it
- }
- } catch (e: Exception) {
- lastHttpResponse = null
- lastHttpException = e
- throw e
- }
- }
-
- private val apduInterface = ApduInterfaceWrapper(rawApduInterface)
- private val httpInterface = HttpInterfaceWrapper(rawHttpInterface)
-
private var finalized = false
private var contextHandle: Long = LpacJni.createContext(apduInterface, httpInterface)
@@ -201,24 +144,15 @@ class LocalProfileAssistantImpl(
@Synchronized
override fun downloadProfile(smdp: String, matchingId: String?, imei: String?,
- confirmationCode: String?, callback: ProfileDownloadCallback) {
- val res = LpacJni.downloadProfile(
+ confirmationCode: String?, callback: ProfileDownloadCallback): Boolean {
+ return LpacJni.downloadProfile(
contextHandle,
smdp,
matchingId,
imei,
confirmationCode,
callback
- )
-
- if (res != 0) {
- throw LocalProfileAssistant.ProfileDownloadException(
- httpInterface.lastHttpResponse,
- httpInterface.lastHttpException,
- apduInterface.lastApduResponse,
- apduInterface.lastApduException,
- )
- }
+ ) == 0
}
@Synchronized