EuiccChannelManagerService.kt 24 KB

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