|
@@ -1,298 +0,0 @@
|
|
|
-package im.angry.openeuicc.ui
|
|
|
|
|
-
|
|
|
|
|
-import android.annotation.SuppressLint
|
|
|
|
|
-import android.app.Dialog
|
|
|
|
|
-import android.content.DialogInterface
|
|
|
|
|
-import android.graphics.BitmapFactory
|
|
|
|
|
-import android.os.Bundle
|
|
|
|
|
-import android.text.Editable
|
|
|
|
|
-import android.util.Log
|
|
|
|
|
-import android.view.*
|
|
|
|
|
-import android.widget.ProgressBar
|
|
|
|
|
-import android.widget.TextView
|
|
|
|
|
-import android.widget.Toast
|
|
|
|
|
-import androidx.activity.result.contract.ActivityResultContracts
|
|
|
|
|
-import androidx.appcompat.app.AlertDialog
|
|
|
|
|
-import androidx.appcompat.widget.Toolbar
|
|
|
|
|
-import androidx.lifecycle.lifecycleScope
|
|
|
|
|
-import com.google.android.material.textfield.TextInputLayout
|
|
|
|
|
-import com.journeyapps.barcodescanner.ScanContract
|
|
|
|
|
-import com.journeyapps.barcodescanner.ScanOptions
|
|
|
|
|
-import im.angry.openeuicc.common.R
|
|
|
|
|
-import im.angry.openeuicc.service.EuiccChannelManagerService
|
|
|
|
|
-import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
|
|
|
|
-import im.angry.openeuicc.util.*
|
|
|
|
|
-import kotlinx.coroutines.Dispatchers
|
|
|
|
|
-import kotlinx.coroutines.flow.onEach
|
|
|
|
|
-import kotlinx.coroutines.launch
|
|
|
|
|
-import kotlinx.coroutines.withContext
|
|
|
|
|
-
|
|
|
|
|
-class ProfileDownloadFragment : BaseMaterialDialogFragment(),
|
|
|
|
|
- Toolbar.OnMenuItemClickListener, EuiccChannelFragmentMarker {
|
|
|
|
|
- companion object {
|
|
|
|
|
- const val TAG = "ProfileDownloadFragment"
|
|
|
|
|
-
|
|
|
|
|
- const val LOW_NVRAM_THRESHOLD = 30 * 1024 // < 30 KiB, the alert may fail
|
|
|
|
|
-
|
|
|
|
|
- fun newInstance(slotId: Int, portId: Int, finishWhenDone: Boolean = false): ProfileDownloadFragment =
|
|
|
|
|
- newInstanceEuicc(ProfileDownloadFragment::class.java, slotId, portId) {
|
|
|
|
|
- putBoolean("finishWhenDone", finishWhenDone)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private lateinit var toolbar: Toolbar
|
|
|
|
|
- private lateinit var profileDownloadServer: TextInputLayout
|
|
|
|
|
- private lateinit var profileDownloadCode: TextInputLayout
|
|
|
|
|
- private lateinit var profileDownloadConfirmationCode: TextInputLayout
|
|
|
|
|
- private lateinit var profileDownloadIMEI: TextInputLayout
|
|
|
|
|
- private lateinit var profileDownloadFreeSpace: TextView
|
|
|
|
|
- private lateinit var progress: ProgressBar
|
|
|
|
|
-
|
|
|
|
|
- private var freeNvram: Int = -1
|
|
|
|
|
-
|
|
|
|
|
- private var downloading = false
|
|
|
|
|
-
|
|
|
|
|
- private val finishWhenDone by lazy {
|
|
|
|
|
- requireArguments().getBoolean("finishWhenDone", false)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
|
|
|
|
|
- result.contents?.let { content ->
|
|
|
|
|
- onScanResult(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) {
|
|
|
|
|
- onScanResult(it)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- bmp.recycle()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private fun onScanResult(result: String) {
|
|
|
|
|
- val components = result.split("$")
|
|
|
|
|
- if (components.size < 3 || components[0] != "LPA:1") return
|
|
|
|
|
- profileDownloadServer.editText?.setText(components[1])
|
|
|
|
|
- profileDownloadCode.editText?.setText(components[2])
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun onCreateView(
|
|
|
|
|
- inflater: LayoutInflater,
|
|
|
|
|
- container: ViewGroup?,
|
|
|
|
|
- savedInstanceState: Bundle?
|
|
|
|
|
- ): View {
|
|
|
|
|
- val view = inflater.inflate(R.layout.fragment_profile_download, container, false)
|
|
|
|
|
-
|
|
|
|
|
- toolbar = view.requireViewById(R.id.toolbar)
|
|
|
|
|
- profileDownloadServer = view.requireViewById(R.id.profile_download_server)
|
|
|
|
|
- profileDownloadCode = view.requireViewById(R.id.profile_download_code)
|
|
|
|
|
- profileDownloadConfirmationCode = view.requireViewById(R.id.profile_download_confirmation_code)
|
|
|
|
|
- profileDownloadIMEI = view.requireViewById(R.id.profile_download_imei)
|
|
|
|
|
- profileDownloadFreeSpace = view.requireViewById(R.id.profile_download_free_space)
|
|
|
|
|
- progress = view.requireViewById(R.id.progress)
|
|
|
|
|
-
|
|
|
|
|
- toolbar.inflateMenu(R.menu.fragment_profile_download)
|
|
|
|
|
-
|
|
|
|
|
- return view
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
|
- super.onViewCreated(view, savedInstanceState)
|
|
|
|
|
- toolbar.apply {
|
|
|
|
|
- setTitle(R.string.profile_download)
|
|
|
|
|
- setNavigationOnClickListener {
|
|
|
|
|
- if (!downloading) {
|
|
|
|
|
- dismiss()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- setOnMenuItemClickListener(this@ProfileDownloadFragment)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun onMenuItemClick(item: MenuItem): Boolean = downloading ||
|
|
|
|
|
- when (item.itemId) {
|
|
|
|
|
- R.id.scan -> {
|
|
|
|
|
- barcodeScannerLauncher.launch(ScanOptions().apply {
|
|
|
|
|
- setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
|
|
|
|
- setOrientationLocked(false)
|
|
|
|
|
- })
|
|
|
|
|
- true
|
|
|
|
|
- }
|
|
|
|
|
- R.id.scan_from_gallery -> {
|
|
|
|
|
- gallerySelectorLauncher.launch("image/*")
|
|
|
|
|
- true
|
|
|
|
|
- }
|
|
|
|
|
- R.id.ok -> {
|
|
|
|
|
- if (freeNvram > LOW_NVRAM_THRESHOLD) {
|
|
|
|
|
- startDownloadProfile()
|
|
|
|
|
- } else {
|
|
|
|
|
- AlertDialog.Builder(requireContext()).apply {
|
|
|
|
|
- setTitle(R.string.profile_download_low_nvram_title)
|
|
|
|
|
- setMessage(R.string.profile_download_low_nvram_message)
|
|
|
|
|
- setIcon(android.R.drawable.ic_dialog_alert)
|
|
|
|
|
- setCancelable(true)
|
|
|
|
|
- setPositiveButton(android.R.string.ok) { _, _ ->
|
|
|
|
|
- startDownloadProfile()
|
|
|
|
|
- }
|
|
|
|
|
- setNegativeButton(android.R.string.cancel, null)
|
|
|
|
|
- show()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- true
|
|
|
|
|
- }
|
|
|
|
|
- else -> false
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun onResume() {
|
|
|
|
|
- super.onResume()
|
|
|
|
|
- setWidthPercent(95)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
|
|
- override fun onStart() {
|
|
|
|
|
- super.onStart()
|
|
|
|
|
-
|
|
|
|
|
- lifecycleScope.launch(Dispatchers.IO) {
|
|
|
|
|
- ensureEuiccChannelManager()
|
|
|
|
|
- if (euiccChannelManagerService.isForegroundTaskRunning) {
|
|
|
|
|
- withContext(Dispatchers.Main) {
|
|
|
|
|
- dismiss()
|
|
|
|
|
- }
|
|
|
|
|
- return@launch
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- withEuiccChannel { channel ->
|
|
|
|
|
- val imei = try {
|
|
|
|
|
- telephonyManager.getImei(channel.logicalSlotId) ?: ""
|
|
|
|
|
- } catch (e: Exception) {
|
|
|
|
|
- ""
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Fetch remaining NVRAM
|
|
|
|
|
- val str = channel.lpa.euiccInfo2?.freeNvram?.also {
|
|
|
|
|
- freeNvram = it
|
|
|
|
|
- }?.let { formatFreeSpace(it) }
|
|
|
|
|
-
|
|
|
|
|
- withContext(Dispatchers.Main) {
|
|
|
|
|
- profileDownloadFreeSpace.text = getString(
|
|
|
|
|
- R.string.profile_download_free_space,
|
|
|
|
|
- str ?: getText(R.string.unknown)
|
|
|
|
|
- )
|
|
|
|
|
- profileDownloadIMEI.editText!!.text =
|
|
|
|
|
- Editable.Factory.getInstance().newEditable(imei)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
|
|
|
- return super.onCreateDialog(savedInstanceState).also {
|
|
|
|
|
- it.setCanceledOnTouchOutside(false)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private fun startDownloadProfile() {
|
|
|
|
|
- val server = profileDownloadServer.editText!!.let {
|
|
|
|
|
- it.text.toString().trim().apply {
|
|
|
|
|
- if (isEmpty()) {
|
|
|
|
|
- it.requestFocus()
|
|
|
|
|
- return@startDownloadProfile
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- val code = profileDownloadCode.editText!!.text.toString().trim()
|
|
|
|
|
- .ifBlank { null }
|
|
|
|
|
- val confirmationCode = profileDownloadConfirmationCode.editText!!.text.toString().trim()
|
|
|
|
|
- .ifBlank { null }
|
|
|
|
|
- val imei = profileDownloadIMEI.editText!!.text.toString().trim()
|
|
|
|
|
- .ifBlank { null }
|
|
|
|
|
-
|
|
|
|
|
- downloading = true
|
|
|
|
|
-
|
|
|
|
|
- profileDownloadServer.editText!!.isEnabled = false
|
|
|
|
|
- profileDownloadCode.editText!!.isEnabled = false
|
|
|
|
|
- profileDownloadConfirmationCode.editText!!.isEnabled = false
|
|
|
|
|
- profileDownloadIMEI.editText!!.isEnabled = false
|
|
|
|
|
-
|
|
|
|
|
- progress.isIndeterminate = true
|
|
|
|
|
- progress.visibility = View.VISIBLE
|
|
|
|
|
-
|
|
|
|
|
- lifecycleScope.launch {
|
|
|
|
|
- ensureEuiccChannelManager()
|
|
|
|
|
- euiccChannelManagerService.waitForForegroundTask()
|
|
|
|
|
- val err = doDownloadProfile(server, code, confirmationCode, imei)
|
|
|
|
|
-
|
|
|
|
|
- if (err != null) {
|
|
|
|
|
- Log.d(TAG, "Error downloading profile")
|
|
|
|
|
- Log.d(TAG, Log.getStackTraceString(err))
|
|
|
|
|
-
|
|
|
|
|
- Toast.makeText(requireContext(), R.string.profile_download_failed, Toast.LENGTH_LONG).show()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (parentFragment is EuiccProfilesChangedListener) {
|
|
|
|
|
- (parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- dismiss()
|
|
|
|
|
- } catch (e: IllegalStateException) {
|
|
|
|
|
- // Ignored
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private suspend fun doDownloadProfile(
|
|
|
|
|
- server: String,
|
|
|
|
|
- code: String?,
|
|
|
|
|
- confirmationCode: String?,
|
|
|
|
|
- imei: String?
|
|
|
|
|
- ) = withContext(Dispatchers.Main) {
|
|
|
|
|
- // The service is responsible for launching the actual blocking part on the IO context
|
|
|
|
|
- // On our side, we need the Main context because of the UI updates
|
|
|
|
|
- euiccChannelManagerService.launchProfileDownloadTask(
|
|
|
|
|
- slotId,
|
|
|
|
|
- portId,
|
|
|
|
|
- server,
|
|
|
|
|
- code,
|
|
|
|
|
- confirmationCode,
|
|
|
|
|
- imei
|
|
|
|
|
- ).onEach {
|
|
|
|
|
- if (it is EuiccChannelManagerService.ForegroundTaskState.InProgress) {
|
|
|
|
|
- progress.progress = it.progress
|
|
|
|
|
- progress.isIndeterminate = it.progress == 0
|
|
|
|
|
- } else {
|
|
|
|
|
- progress.progress = 100
|
|
|
|
|
- progress.isIndeterminate = false
|
|
|
|
|
- }
|
|
|
|
|
- }.waitDone()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun onDismiss(dialog: DialogInterface) {
|
|
|
|
|
- super.onDismiss(dialog)
|
|
|
|
|
- if (finishWhenDone) {
|
|
|
|
|
- activity?.finish()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun onCancel(dialog: DialogInterface) {
|
|
|
|
|
- super.onCancel(dialog)
|
|
|
|
|
- if (finishWhenDone) {
|
|
|
|
|
- activity?.finish()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|