ProfileDownloadFragment.kt 7.8 KB

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