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