EuiccChannelManagerService.kt 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. package im.angry.openeuicc.service
  2. import android.content.Intent
  3. import android.content.pm.PackageManager
  4. import android.os.Binder
  5. import android.os.IBinder
  6. import android.util.Log
  7. import androidx.core.app.NotificationChannelCompat
  8. import androidx.core.app.NotificationCompat
  9. import androidx.core.app.NotificationManagerCompat
  10. import androidx.lifecycle.LifecycleService
  11. import androidx.lifecycle.lifecycleScope
  12. import im.angry.openeuicc.common.R
  13. import im.angry.openeuicc.core.EuiccChannelManager
  14. import im.angry.openeuicc.util.*
  15. import kotlinx.coroutines.Dispatchers
  16. import kotlinx.coroutines.NonCancellable
  17. import kotlinx.coroutines.channels.BufferOverflow
  18. import kotlinx.coroutines.delay
  19. import kotlinx.coroutines.flow.Flow
  20. import kotlinx.coroutines.flow.MutableSharedFlow
  21. import kotlinx.coroutines.flow.MutableStateFlow
  22. import kotlinx.coroutines.flow.SharedFlow
  23. import kotlinx.coroutines.flow.asSharedFlow
  24. import kotlinx.coroutines.flow.collect
  25. import kotlinx.coroutines.flow.first
  26. import kotlinx.coroutines.flow.flow
  27. import kotlinx.coroutines.flow.last
  28. import kotlinx.coroutines.flow.onCompletion
  29. import kotlinx.coroutines.flow.onEach
  30. import kotlinx.coroutines.flow.takeWhile
  31. import kotlinx.coroutines.flow.transformWhile
  32. import kotlinx.coroutines.isActive
  33. import kotlinx.coroutines.launch
  34. import kotlinx.coroutines.withContext
  35. import kotlinx.coroutines.withTimeoutOrNull
  36. import kotlinx.coroutines.yield
  37. import net.typeblog.lpac_jni.ProfileDownloadCallback
  38. /**
  39. * An Android Service wrapper for EuiccChannelManager.
  40. * The purpose of this wrapper is mainly lifecycle-wise: having a Service allows the manager
  41. * instance to have its own independent lifecycle. This way it can be created as requested and
  42. * destroyed when no other components are bound to this service anymore.
  43. * This behavior allows us to avoid keeping the APDU channels open at all times. For example,
  44. * the EuiccService implementation should *only* bind to this service when it requires an
  45. * instance of EuiccChannelManager. UI components can keep being bound to this service for
  46. * their entire lifecycles, since the whole purpose of them is to expose the current state
  47. * to the user.
  48. *
  49. * Additionally, this service is also responsible for long-running "foreground" tasks that
  50. * are not suitable to be managed by UI components. This includes profile downloading, etc.
  51. * When a UI component needs to run one of these tasks, they have to bind to this service
  52. * and call one of the `launch*` methods, which will run the task inside this service's
  53. * lifecycle context and return a Flow instance for the UI component to subscribe to its
  54. * progress.
  55. */
  56. class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
  57. companion object {
  58. private const val TAG = "EuiccChannelManagerService"
  59. private const val CHANNEL_ID = "tasks"
  60. private const val FOREGROUND_ID = 1000
  61. private const val TASK_FAILURE_ID = 1000
  62. /**
  63. * Utility function to wait for a foreground task to be done, return its
  64. * error if any, or null on success.
  65. */
  66. suspend fun Flow<ForegroundTaskState>.waitDone(): Throwable? =
  67. (this.last() as ForegroundTaskState.Done).error
  68. /**
  69. * Apply transform to a ForegroundTaskState flow so that it completes when a Done is seen.
  70. *
  71. * This must be applied each time a flow is returned for subscription purposes. If applied
  72. * beforehand, we lose the ability to subscribe multiple times.
  73. */
  74. private fun Flow<ForegroundTaskState>.applyCompletionTransform() =
  75. transformWhile {
  76. emit(it)
  77. it !is ForegroundTaskState.Done
  78. }
  79. }
  80. inner class LocalBinder : Binder() {
  81. val service = this@EuiccChannelManagerService
  82. }
  83. private val euiccChannelManagerDelegate = lazy {
  84. appContainer.euiccChannelManagerFactory.createEuiccChannelManager(this)
  85. }
  86. val euiccChannelManager: EuiccChannelManager by euiccChannelManagerDelegate
  87. /**
  88. * The state of a "foreground" task (named so due to the need to startForeground())
  89. */
  90. sealed interface ForegroundTaskState {
  91. data object Idle : ForegroundTaskState
  92. data class InProgress(val progress: Int) : ForegroundTaskState
  93. data class Done(val error: Throwable?) : ForegroundTaskState
  94. }
  95. /**
  96. * This flow emits whenever the service has had a start command, from startService()
  97. * The service self-starts when foreground is required, because other components
  98. * only bind to this service and do not start it per-se.
  99. */
  100. private val foregroundStarted: MutableSharedFlow<Unit> = MutableSharedFlow()
  101. /**
  102. * This flow is used to emit progress updates when a foreground task is running.
  103. */
  104. private val foregroundTaskState: MutableStateFlow<ForegroundTaskState> =
  105. MutableStateFlow(ForegroundTaskState.Idle)
  106. /**
  107. * A simple wrapper over a flow with taskId added.
  108. *
  109. * taskID is the exact millisecond-precision timestamp when the task is launched.
  110. */
  111. class ForegroundTaskSubscriberFlow(val taskId: Long, inner: Flow<ForegroundTaskState>) :
  112. Flow<ForegroundTaskState> by inner
  113. /**
  114. * A cache of subscribers to 5 recently-launched foreground tasks, identified by ID
  115. *
  116. * Only one can be run at the same time, but those that are done will be kept in this
  117. * map for a little while -- because UI components may be stopped and recreated while
  118. * tasks are running. Having this buffer allows the components to re-subscribe even if
  119. * the task completes while they are being recreated.
  120. */
  121. private val foregroundTaskSubscribers: MutableMap<Long, SharedFlow<ForegroundTaskState>> =
  122. mutableMapOf()
  123. override fun onBind(intent: Intent): IBinder {
  124. super.onBind(intent)
  125. return LocalBinder()
  126. }
  127. override fun onDestroy() {
  128. super.onDestroy()
  129. if (euiccChannelManagerDelegate.isInitialized()) {
  130. euiccChannelManager.invalidate()
  131. }
  132. }
  133. override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
  134. return super.onStartCommand(intent, flags, startId).also {
  135. lifecycleScope.launch {
  136. foregroundStarted.emit(Unit)
  137. }
  138. }
  139. }
  140. private fun ensureForegroundTaskNotificationChannel() {
  141. val nm = NotificationManagerCompat.from(this)
  142. if (nm.getNotificationChannelCompat(CHANNEL_ID) == null) {
  143. val channel =
  144. NotificationChannelCompat.Builder(
  145. CHANNEL_ID,
  146. NotificationManagerCompat.IMPORTANCE_LOW
  147. )
  148. .setName(getString(R.string.task_notification))
  149. .setVibrationEnabled(false)
  150. .build()
  151. nm.createNotificationChannel(channel)
  152. }
  153. }
  154. private suspend fun updateForegroundNotification(title: String, iconRes: Int) {
  155. ensureForegroundTaskNotificationChannel()
  156. val nm = NotificationManagerCompat.from(this)
  157. val state = foregroundTaskState.value
  158. if (state is ForegroundTaskState.InProgress) {
  159. val notification = NotificationCompat.Builder(this, CHANNEL_ID)
  160. .setContentTitle(title)
  161. .setProgress(100, state.progress, state.progress == 0)
  162. .setSmallIcon(iconRes)
  163. .setPriority(NotificationCompat.PRIORITY_LOW)
  164. .setOngoing(true)
  165. .setOnlyAlertOnce(true)
  166. .build()
  167. if (state.progress == 0) {
  168. startForeground(FOREGROUND_ID, notification)
  169. } else if (checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
  170. nm.notify(FOREGROUND_ID, notification)
  171. }
  172. // Yield out so that the main looper can handle the notification event
  173. // Without this yield, the notification sent above will not be shown in time.
  174. yield()
  175. } else {
  176. stopForeground(STOP_FOREGROUND_REMOVE)
  177. }
  178. }
  179. private fun postForegroundTaskFailureNotification(title: String) {
  180. if (checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
  181. return
  182. }
  183. val notification = NotificationCompat.Builder(this, CHANNEL_ID)
  184. .setContentTitle(title)
  185. .setSmallIcon(R.drawable.ic_x_black)
  186. .build()
  187. NotificationManagerCompat.from(this).notify(TASK_FAILURE_ID, notification)
  188. }
  189. /**
  190. * Recover the subscriber to a foreground task that is recently launched.
  191. *
  192. * null if the task doesn't exist, or was launched too long ago.
  193. */
  194. fun recoverForegroundTaskSubscriber(taskId: Long): ForegroundTaskSubscriberFlow? =
  195. foregroundTaskSubscribers[taskId]?.let {
  196. ForegroundTaskSubscriberFlow(taskId, it.applyCompletionTransform())
  197. }
  198. /**
  199. * Launch a potentially blocking foreground task in this service's lifecycle context.
  200. * This function does not block, but returns a Flow that emits ForegroundTaskState
  201. * updates associated with this task. The last update the returned flow will emit is
  202. * always ForegroundTaskState.Done.
  203. *
  204. * The returned flow can only be subscribed to once even though the underlying implementation
  205. * is a SharedFlow. This is due to the need to apply transformations so that the stream
  206. * actually completes. In order to subscribe multiple times, use `recoverForegroundTaskSubscriber`
  207. * to acquire another instance.
  208. *
  209. * The task closure is expected to update foregroundTaskState whenever appropriate.
  210. * If a foreground task is already running, this function returns null.
  211. *
  212. * To wait for foreground tasks to be available, use waitForForegroundTask().
  213. *
  214. * The function will set the state back to Idle once it sees ForegroundTaskState.Done.
  215. */
  216. private fun launchForegroundTask(
  217. title: String,
  218. failureTitle: String,
  219. iconRes: Int,
  220. task: suspend EuiccChannelManagerService.() -> Unit
  221. ): ForegroundTaskSubscriberFlow {
  222. val taskID = System.currentTimeMillis()
  223. // Atomically set the state to InProgress. If this returns true, we are
  224. // the only task currently in progress.
  225. if (!foregroundTaskState.compareAndSet(
  226. ForegroundTaskState.Idle,
  227. ForegroundTaskState.InProgress(0)
  228. )
  229. ) {
  230. return ForegroundTaskSubscriberFlow(
  231. taskID,
  232. flow { emit(ForegroundTaskState.Done(IllegalStateException("There are tasks currently running"))) })
  233. }
  234. lifecycleScope.launch(Dispatchers.Main) {
  235. // Wait until our self-start command has succeeded.
  236. // We can only call startForeground() after that
  237. val res = withTimeoutOrNull(30 * 1000) {
  238. foregroundStarted.first()
  239. }
  240. if (res == null) {
  241. // The only case where the wait above could time out is if the subscriber
  242. // to the flow is stuck. Or we failed to start foreground.
  243. // In that case, we should just set our state back to Idle -- setting it
  244. // to Done wouldn't help much because nothing is going to then set it Idle.
  245. foregroundTaskState.value = ForegroundTaskState.Idle
  246. return@launch
  247. }
  248. updateForegroundNotification(title, iconRes)
  249. try {
  250. withContext(Dispatchers.IO + NonCancellable) { // Any LPA-related task must always complete
  251. this@EuiccChannelManagerService.task()
  252. }
  253. // This update will be sent by the subscriber (as shown below)
  254. foregroundTaskState.value = ForegroundTaskState.Done(null)
  255. } catch (t: Throwable) {
  256. Log.e(TAG, "Foreground task encountered an error")
  257. Log.e(TAG, Log.getStackTraceString(t))
  258. foregroundTaskState.value = ForegroundTaskState.Done(t)
  259. if (isActive) {
  260. postForegroundTaskFailureNotification(failureTitle)
  261. }
  262. } finally {
  263. if (isActive) {
  264. stopSelf()
  265. }
  266. }
  267. }
  268. // This is the flow we are going to return. We allow multiple subscribers by
  269. // re-emitting state updates into this flow from another coroutine.
  270. // replay = 2 ensures that we at least have 1 previous state whenever subscribed to.
  271. // This is helpful when the task completed and is then re-subscribed to due to a
  272. // UI recreation event -- this way, the UI will know at least one last progress event
  273. // before completion / failure
  274. val subscriberFlow = MutableSharedFlow<ForegroundTaskState>(
  275. replay = 2,
  276. onBufferOverflow = BufferOverflow.DROP_OLDEST
  277. )
  278. // We should be the only task running, so we can subscribe to foregroundTaskState
  279. // until we encounter ForegroundTaskState.Done.
  280. // Then, we complete the returned flow, but we also set the state back to Idle.
  281. // The state update back to Idle won't show up in the returned stream, because
  282. // it has been completed by that point.
  283. lifecycleScope.launch(Dispatchers.Main) {
  284. foregroundTaskState
  285. .applyCompletionTransform()
  286. .onEach {
  287. // Also update our notification when we see an update
  288. // But ignore the first progress = 0 update -- that is the current value.
  289. // we need that to be handled by the main coroutine after it finishes.
  290. if (it !is ForegroundTaskState.InProgress || it.progress != 0) {
  291. updateForegroundNotification(title, iconRes)
  292. }
  293. subscriberFlow.emit(it)
  294. }
  295. .onCompletion {
  296. // Reset state back to Idle when we are done.
  297. // We do it here because otherwise Idle and Done might become conflated
  298. // when emitted by the main coroutine in quick succession.
  299. // Doing it here ensures we've seen Done. This Idle event won't be
  300. // emitted to the consumer because the subscription has completed here.
  301. foregroundTaskState.value = ForegroundTaskState.Idle
  302. }
  303. .collect()
  304. }
  305. foregroundTaskSubscribers[taskID] = subscriberFlow.asSharedFlow()
  306. if (foregroundTaskSubscribers.size > 5) {
  307. // Remove enough elements so that the size is kept at 5
  308. for (key in foregroundTaskSubscribers.keys.sorted()
  309. .take(foregroundTaskSubscribers.size - 5)) {
  310. foregroundTaskSubscribers.remove(key)
  311. }
  312. }
  313. // Before we return, and after we have set everything up,
  314. // self-start with foreground permission.
  315. // This is going to unblock the main coroutine handling the task.
  316. startForegroundService(
  317. Intent(
  318. this@EuiccChannelManagerService,
  319. this@EuiccChannelManagerService::class.java
  320. )
  321. )
  322. return ForegroundTaskSubscriberFlow(
  323. taskID,
  324. subscriberFlow.asSharedFlow().applyCompletionTransform()
  325. )
  326. }
  327. val isForegroundTaskRunning: Boolean
  328. get() = foregroundTaskState.value != ForegroundTaskState.Idle
  329. suspend fun waitForForegroundTask() {
  330. foregroundTaskState.takeWhile { it != ForegroundTaskState.Idle }
  331. .collect()
  332. }
  333. fun launchProfileDownloadTask(
  334. slotId: Int,
  335. portId: Int,
  336. smdp: String,
  337. matchingId: String?,
  338. confirmationCode: String?,
  339. imei: String?
  340. ): ForegroundTaskSubscriberFlow =
  341. launchForegroundTask(
  342. getString(R.string.task_profile_download),
  343. getString(R.string.task_profile_download_failure),
  344. R.drawable.ic_task_sim_card_download
  345. ) {
  346. euiccChannelManager.beginTrackedOperation(slotId, portId) {
  347. euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
  348. channel.lpa.downloadProfile(
  349. smdp,
  350. matchingId,
  351. imei,
  352. confirmationCode,
  353. object : ProfileDownloadCallback {
  354. override fun onStateUpdate(state: ProfileDownloadCallback.DownloadState) {
  355. if (state.progress == 0) return
  356. foregroundTaskState.value =
  357. ForegroundTaskState.InProgress(state.progress)
  358. }
  359. })
  360. }
  361. preferenceRepository.notificationDownloadFlow.first()
  362. }
  363. }
  364. fun launchProfileRenameTask(
  365. slotId: Int,
  366. portId: Int,
  367. iccid: String,
  368. name: String
  369. ): ForegroundTaskSubscriberFlow =
  370. launchForegroundTask(
  371. getString(R.string.task_profile_rename),
  372. getString(R.string.task_profile_rename_failure),
  373. R.drawable.ic_task_rename
  374. ) {
  375. euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
  376. channel.lpa.setNickname(
  377. iccid,
  378. name
  379. )
  380. }
  381. }
  382. fun launchProfileDeleteTask(
  383. slotId: Int,
  384. portId: Int,
  385. iccid: String
  386. ): ForegroundTaskSubscriberFlow =
  387. launchForegroundTask(
  388. getString(R.string.task_profile_delete),
  389. getString(R.string.task_profile_delete_failure),
  390. R.drawable.ic_task_delete
  391. ) {
  392. euiccChannelManager.beginTrackedOperation(slotId, portId) {
  393. euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
  394. channel.lpa.deleteProfile(iccid)
  395. }
  396. preferenceRepository.notificationDeleteFlow.first()
  397. }
  398. }
  399. class SwitchingProfilesRefreshException : Exception()
  400. fun launchProfileSwitchTask(
  401. slotId: Int,
  402. portId: Int,
  403. iccid: String,
  404. enable: Boolean, // Enable or disable the profile indicated in iccid
  405. reconnectTimeoutMillis: Long = 0 // 0 = do not wait for reconnect, useful for USB readers
  406. ): ForegroundTaskSubscriberFlow =
  407. launchForegroundTask(
  408. getString(R.string.task_profile_switch),
  409. getString(R.string.task_profile_switch_failure),
  410. R.drawable.ic_task_switch
  411. ) {
  412. euiccChannelManager.beginTrackedOperation(slotId, portId) {
  413. val (res, refreshed) = euiccChannelManager.withEuiccChannel(
  414. slotId,
  415. portId
  416. ) { channel ->
  417. if (!channel.lpa.switchProfile(iccid, enable, refresh = true)) {
  418. // Sometimes, we *can* enable or disable the profile, but we cannot
  419. // send the refresh command to the modem because the profile somehow
  420. // makes the modem "busy". In this case, we can still switch by setting
  421. // refresh to false, but then the switch cannot take effect until the
  422. // user resets the modem manually by toggling airplane mode or rebooting.
  423. Pair(channel.lpa.switchProfile(iccid, enable, refresh = false), false)
  424. } else {
  425. Pair(true, true)
  426. }
  427. }
  428. if (!res) {
  429. throw RuntimeException("Could not switch profile")
  430. }
  431. if (!refreshed && slotId != EuiccChannelManager.USB_CHANNEL_ID) {
  432. // We may have switched the profile, but we could not refresh. Tell the caller about this
  433. // but only if we are talking to a modem and not a USB reader
  434. throw SwitchingProfilesRefreshException()
  435. }
  436. if (reconnectTimeoutMillis > 0) {
  437. // Add an unconditional delay first to account for any race condition between
  438. // the card sending the refresh command and the modem actually refreshing
  439. delay(reconnectTimeoutMillis / 10)
  440. // This throws TimeoutCancellationException if timed out
  441. euiccChannelManager.waitForReconnect(
  442. slotId,
  443. portId,
  444. reconnectTimeoutMillis / 10 * 9
  445. )
  446. }
  447. preferenceRepository.notificationSwitchFlow.first()
  448. }
  449. }
  450. }