UsbCcidReaderFragment.kt 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package im.angry.openeuicc.ui
  2. import android.annotation.SuppressLint
  3. import android.app.PendingIntent
  4. import android.content.BroadcastReceiver
  5. import android.content.Context
  6. import android.content.Intent
  7. import android.content.IntentFilter
  8. import android.hardware.usb.UsbDevice
  9. import android.hardware.usb.UsbManager
  10. import android.os.Build
  11. import android.os.Bundle
  12. import android.view.LayoutInflater
  13. import android.view.View
  14. import android.view.ViewGroup
  15. import android.widget.Button
  16. import android.widget.ProgressBar
  17. import android.widget.TextView
  18. import androidx.fragment.app.Fragment
  19. import androidx.fragment.app.commit
  20. import androidx.lifecycle.lifecycleScope
  21. import im.angry.openeuicc.common.R
  22. import im.angry.openeuicc.core.EuiccChannel
  23. import im.angry.openeuicc.core.EuiccChannelManager
  24. import im.angry.openeuicc.util.*
  25. import kotlinx.coroutines.Dispatchers
  26. import kotlinx.coroutines.launch
  27. import kotlinx.coroutines.withContext
  28. /**
  29. * A wrapper fragment over EuiccManagementFragment where we handle
  30. * logic specific to USB devices. This is mainly USB permission
  31. * requests, and the fact that USB devices may or may not be
  32. * available by the time the user selects it from MainActivity.
  33. *
  34. * Having this fragment allows MainActivity to be (mostly) agnostic
  35. * of the underlying implementation of different types of channels.
  36. * When permission is granted, this fragment will simply load
  37. * EuiccManagementFragment using its own childFragmentManager.
  38. *
  39. * Note that for now we assume there will only be one USB card reader
  40. * device. This is also an implicit assumption in EuiccChannelManager.
  41. */
  42. class UsbCcidReaderFragment : Fragment(), OpenEuiccContextMarker {
  43. companion object {
  44. const val ACTION_USB_PERMISSION = "im.angry.openeuicc.USB_PERMISSION"
  45. }
  46. private val euiccChannelManager: EuiccChannelManager by lazy {
  47. (requireActivity() as MainActivity).euiccChannelManager
  48. }
  49. private val usbManager: UsbManager by lazy {
  50. requireContext().getSystemService(Context.USB_SERVICE) as UsbManager
  51. }
  52. private val usbPermissionReceiver = object : BroadcastReceiver() {
  53. override fun onReceive(context: Context?, intent: Intent?) {
  54. if (intent?.action == ACTION_USB_PERMISSION) {
  55. if (usbDevice != null && usbManager.hasPermission(usbDevice)) {
  56. lifecycleScope.launch(Dispatchers.Main) {
  57. tryLoadUsbChannel()
  58. }
  59. }
  60. }
  61. }
  62. }
  63. private lateinit var usbPendingIntent: PendingIntent
  64. private lateinit var text: TextView
  65. private lateinit var permissionButton: Button
  66. private lateinit var loadingProgress: ProgressBar
  67. private var usbDevice: UsbDevice? = null
  68. private var usbChannel: EuiccChannel? = null
  69. override fun onCreateView(
  70. inflater: LayoutInflater,
  71. container: ViewGroup?,
  72. savedInstanceState: Bundle?
  73. ): View? {
  74. val view = inflater.inflate(R.layout.fragment_usb_ccid_reader, container, false)
  75. text = view.requireViewById(R.id.usb_reader_text)
  76. permissionButton = view.requireViewById(R.id.usb_grant_permission)
  77. loadingProgress = view.requireViewById(R.id.loading)
  78. permissionButton.setOnClickListener {
  79. usbManager.requestPermission(usbDevice, usbPendingIntent)
  80. }
  81. return view
  82. }
  83. @SuppressLint("UnspecifiedRegisterReceiverFlag", "WrongConstant")
  84. override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  85. super.onViewCreated(view, savedInstanceState)
  86. usbPendingIntent = PendingIntent.getBroadcast(
  87. requireContext(), 0,
  88. Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE
  89. )
  90. val filter = IntentFilter(ACTION_USB_PERMISSION)
  91. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
  92. requireContext().registerReceiver(
  93. usbPermissionReceiver,
  94. filter,
  95. Context.RECEIVER_EXPORTED
  96. )
  97. } else {
  98. requireContext().registerReceiver(usbPermissionReceiver, filter)
  99. }
  100. lifecycleScope.launch(Dispatchers.Main) {
  101. tryLoadUsbChannel()
  102. }
  103. }
  104. override fun onDetach() {
  105. super.onDetach()
  106. try {
  107. requireContext().unregisterReceiver(usbPermissionReceiver)
  108. } catch (_: Exception) {
  109. }
  110. }
  111. override fun onDestroy() {
  112. super.onDestroy()
  113. try {
  114. requireContext().unregisterReceiver(usbPermissionReceiver)
  115. } catch (_: Exception) {
  116. }
  117. }
  118. private suspend fun tryLoadUsbChannel() {
  119. text.visibility = View.GONE
  120. permissionButton.visibility = View.GONE
  121. loadingProgress.visibility = View.VISIBLE
  122. val (device, channel) = withContext(Dispatchers.IO) {
  123. euiccChannelManager.enumerateUsbEuiccChannel()
  124. }
  125. loadingProgress.visibility = View.GONE
  126. usbDevice = device
  127. usbChannel = channel
  128. if (device != null && channel == null && !usbManager.hasPermission(device)) {
  129. text.text = getString(R.string.usb_permission_needed)
  130. text.visibility = View.VISIBLE
  131. permissionButton.visibility = View.VISIBLE
  132. } else if (device != null && channel != null) {
  133. childFragmentManager.commit {
  134. replace(
  135. R.id.child_container,
  136. appContainer.uiComponentFactory.createEuiccManagementFragment(
  137. channel.slotId,
  138. channel.portId
  139. )
  140. )
  141. }
  142. } else {
  143. text.text = getString(R.string.usb_failed)
  144. text.visibility = View.VISIBLE
  145. permissionButton.visibility = View.GONE
  146. }
  147. }
  148. }