ProfileDownloadFragment.kt 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. package im.angry.openeuicc.ui
  2. import android.annotation.SuppressLint
  3. import android.app.Dialog
  4. import android.os.Bundle
  5. import android.text.Editable
  6. import android.text.format.Formatter
  7. import android.util.Log
  8. import android.view.*
  9. import android.widget.ProgressBar
  10. import android.widget.TextView
  11. import android.widget.Toast
  12. import androidx.appcompat.widget.Toolbar
  13. import androidx.lifecycle.lifecycleScope
  14. import com.google.android.material.textfield.TextInputLayout
  15. import com.journeyapps.barcodescanner.ScanContract
  16. import com.journeyapps.barcodescanner.ScanOptions
  17. import im.angry.openeuicc.common.R
  18. import im.angry.openeuicc.util.openEuiccApplication
  19. import im.angry.openeuicc.util.preferenceRepository
  20. import im.angry.openeuicc.util.setWidthPercent
  21. import kotlinx.coroutines.Dispatchers
  22. import kotlinx.coroutines.flow.first
  23. import kotlinx.coroutines.launch
  24. import kotlinx.coroutines.withContext
  25. import net.typeblog.lpac_jni.ProfileDownloadCallback
  26. import kotlin.Exception
  27. class ProfileDownloadFragment : BaseMaterialDialogFragment(), EuiccFragmentMarker, Toolbar.OnMenuItemClickListener {
  28. companion object {
  29. const val TAG = "ProfileDownloadFragment"
  30. fun newInstance(slotId: Int, portId: Int): ProfileDownloadFragment =
  31. newInstanceEuicc(ProfileDownloadFragment::class.java, slotId, portId)
  32. }
  33. private lateinit var toolbar: Toolbar
  34. private lateinit var profileDownloadServer: TextInputLayout
  35. private lateinit var profileDownloadCode: TextInputLayout
  36. private lateinit var profileDownloadConfirmationCode: TextInputLayout
  37. private lateinit var profileDownloadIMEI: TextInputLayout
  38. private lateinit var profileDownloadFreeSpace: TextView
  39. private lateinit var progress: ProgressBar
  40. private var freeNvram: Int = -1
  41. private var downloading = false
  42. private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
  43. result.contents?.let { content ->
  44. Log.d(TAG, content)
  45. val components = content.split("$")
  46. if (components.size < 3 || components[0] != "LPA:1") return@registerForActivityResult
  47. profileDownloadServer.editText?.setText(components[1])
  48. profileDownloadCode.editText?.setText(components[2])
  49. }
  50. }
  51. override fun onCreateView(
  52. inflater: LayoutInflater,
  53. container: ViewGroup?,
  54. savedInstanceState: Bundle?
  55. ): View {
  56. val view = inflater.inflate(R.layout.fragment_profile_download, container, false)
  57. toolbar = view.findViewById(R.id.toolbar)
  58. profileDownloadServer = view.findViewById(R.id.profile_download_server)
  59. profileDownloadCode = view.findViewById(R.id.profile_download_code)
  60. profileDownloadConfirmationCode = view.findViewById(R.id.profile_download_confirmation_code)
  61. profileDownloadIMEI = view.findViewById(R.id.profile_download_imei)
  62. profileDownloadFreeSpace = view.findViewById(R.id.profile_download_free_space)
  63. progress = view.findViewById(R.id.progress)
  64. toolbar.inflateMenu(R.menu.fragment_profile_download)
  65. return view
  66. }
  67. override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  68. super.onViewCreated(view, savedInstanceState)
  69. toolbar.apply {
  70. setTitle(R.string.profile_download)
  71. setNavigationOnClickListener {
  72. if (!downloading) dismiss()
  73. }
  74. setOnMenuItemClickListener(this@ProfileDownloadFragment)
  75. }
  76. }
  77. override fun onMenuItemClick(item: MenuItem): Boolean = downloading ||
  78. when (item.itemId) {
  79. R.id.scan -> {
  80. barcodeScannerLauncher.launch(ScanOptions().apply {
  81. setDesiredBarcodeFormats(ScanOptions.QR_CODE)
  82. setOrientationLocked(false)
  83. })
  84. true
  85. }
  86. R.id.ok -> {
  87. startDownloadProfile()
  88. true
  89. }
  90. else -> false
  91. }
  92. override fun onResume() {
  93. super.onResume()
  94. setWidthPercent(95)
  95. }
  96. @SuppressLint("MissingPermission")
  97. override fun onStart() {
  98. super.onStart()
  99. profileDownloadIMEI.editText!!.text = Editable.Factory.getInstance().newEditable(
  100. try {
  101. openEuiccApplication.telephonyManager.getImei(channel.logicalSlotId)
  102. } catch (e: Exception) {
  103. ""
  104. }
  105. )
  106. lifecycleScope.launch(Dispatchers.IO) {
  107. // Fetch remaining NVRAM
  108. val str = channel.lpa.euiccInfo2?.freeNvram?.also {
  109. freeNvram = it
  110. }?.let { Formatter.formatShortFileSize(requireContext(), it.toLong()) }
  111. withContext(Dispatchers.Main) {
  112. profileDownloadFreeSpace.text = getString(R.string.profile_download_free_space,
  113. str ?: getText(R.string.unknown))
  114. }
  115. }
  116. }
  117. override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
  118. return super.onCreateDialog(savedInstanceState).also {
  119. it.setCanceledOnTouchOutside(false)
  120. }
  121. }
  122. private fun startDownloadProfile() {
  123. val server = profileDownloadServer.editText!!.let {
  124. it.text.toString().trim().apply {
  125. if (isEmpty()) {
  126. it.requestFocus()
  127. return@startDownloadProfile
  128. }
  129. }
  130. }
  131. val code = profileDownloadCode.editText!!.text.toString().trim()
  132. .ifBlank { null }
  133. val confirmationCode = profileDownloadConfirmationCode.editText!!.text.toString().trim()
  134. .ifBlank { null }
  135. val imei = profileDownloadIMEI.editText!!.text.toString().trim()
  136. .ifBlank { null }
  137. downloading = true
  138. profileDownloadServer.editText!!.isEnabled = false
  139. profileDownloadCode.editText!!.isEnabled = false
  140. profileDownloadConfirmationCode.editText!!.isEnabled = false
  141. profileDownloadIMEI.editText!!.isEnabled = false
  142. progress.isIndeterminate = true
  143. progress.visibility = View.VISIBLE
  144. lifecycleScope.launch {
  145. try {
  146. doDownloadProfile(server, code, confirmationCode, imei)
  147. } catch (e: Exception) {
  148. Log.d(TAG, "Error downloading profile")
  149. Log.d(TAG, Log.getStackTraceString(e))
  150. Toast.makeText(context, R.string.profile_download_failed, Toast.LENGTH_LONG).show()
  151. } finally {
  152. if (parentFragment is EuiccProfilesChangedListener) {
  153. (parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
  154. }
  155. dismiss()
  156. }
  157. }
  158. }
  159. private suspend fun doDownloadProfile(server: String, code: String?, confirmationCode: String?, imei: String?) = channel.lpa.beginOperation {
  160. downloadProfile(server, code, imei, confirmationCode, object : ProfileDownloadCallback {
  161. override fun onStateUpdate(state: ProfileDownloadCallback.DownloadState) {
  162. lifecycleScope.launch(Dispatchers.Main) {
  163. progress.isIndeterminate = false
  164. progress.progress = state.progress
  165. }
  166. }
  167. })
  168. // If we get here, we are successful
  169. // Only send notifications if the user allowed us to
  170. preferenceRepository.notificationDownloadFlow.first()
  171. }
  172. }