Advertisement
Guest User

Immuni WA

a guest
May 25th, 2020
559
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Kotlin 9.83 KB | None | 0 0
  1. /*
  2.  * Copyright (C) 2020 Presidenza del Consiglio dei Ministri.
  3.  * Please refer to the AUTHORS file for more information.
  4.  * This program is free software: you can redistribute it and/or modify
  5.  * it under the terms of the GNU Affero General Public License as
  6.  * published by the Free Software Foundation, either version 3 of the
  7.  * License, or (at your option) any later version.
  8.  * This program is distributed in the hope that it will be useful,
  9.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11.  * GNU Affero General Public License for more details.
  12.  * You should have received a copy of the GNU Affero General Public License
  13.  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  14.  */
  15.  
  16. package it.ministerodellasalute.immuni.extensions.nearby
  17.  
  18. import android.app.Activity
  19. import android.content.Context
  20. import android.content.Intent
  21. import android.content.IntentSender
  22. import com.google.android.gms.common.api.ApiException
  23. import com.google.android.gms.nearby.Nearby
  24. import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes
  25. import it.ministerodellasalute.immuni.extensions.bluetooth.BluetoothStateFlow
  26. import it.ministerodellasalute.immuni.extensions.lifecycle.AppLifecycleObserver
  27. import it.ministerodellasalute.immuni.extensions.location.LocationStateFlow
  28. import it.ministerodellasalute.immuni.extensions.nearby.ExposureNotificationClient.*
  29. import it.ministerodellasalute.immuni.extensions.utils.log
  30. import java.io.File
  31. import java.util.*
  32. import kotlinx.coroutines.*
  33. import kotlinx.coroutines.flow.*
  34.  
  35. class ExposureNotificationManager(
  36.     private val locationStateFlow: LocationStateFlow,
  37.     private val bluetoothStateFlow: BluetoothStateFlow,
  38.     private val lifecycleObserver: AppLifecycleObserver,
  39.     private val exposureNotificationClient: ExposureNotificationClient
  40. ) {
  41.     interface Delegate {
  42.         suspend fun processKeys(
  43.             serverDate: Date,
  44.             summary: ExposureSummary,
  45.             getInfos: suspend () -> List<ExposureInformation>
  46.         )
  47.     }
  48.  
  49.     constructor(context: Context) : this(
  50.         locationStateFlow = LocationStateFlow(context),
  51.         bluetoothStateFlow = BluetoothStateFlow(context),
  52.         lifecycleObserver = AppLifecycleObserver(),
  53.         exposureNotificationClient =
  54.         ExposureNotificationClientWrapper(Nearby.getExposureNotificationClient(context))
  55.     )
  56.  
  57.     companion object {
  58.         const val DAYS_OF_SELF_ISOLATION = 14
  59.         const val REQUEST_CODE_START_EXPOSURE_NOTIFICATION = 620
  60.         const val REQUEST_CODE_TEK_HISTORY = 621
  61.     }
  62.  
  63.     private val job = Job()
  64.     private val scope = CoroutineScope(Dispatchers.Default + job)
  65.  
  66.     // Google's Exposure Notification Service is enabled
  67.     private val _areExposureNotificationsEnabled = MutableStateFlow<Boolean?>(null)
  68.     val areExposureNotificationsEnabled: StateFlow<Boolean?> = _areExposureNotificationsEnabled
  69.  
  70.     // This implies the service is active together with BLE and Location services
  71.     private val _isBroadcastingActive = MutableStateFlow<Boolean?>(null)
  72.     val isBroadcastingActive: StateFlow<Boolean?> = _isBroadcastingActive
  73.  
  74.     private lateinit var delegate: Delegate
  75.  
  76.     fun setup(delegate: Delegate) {
  77.         this.delegate = delegate
  78.  
  79.         combine(
  80.             locationStateFlow,
  81.             bluetoothStateFlow,
  82.             areExposureNotificationsEnabled
  83.         ) { isLocationActive, isBluetoothActive, areExposureNotificationsEnabled ->
  84.             log("isLocationActive $isLocationActive")
  85.             log("isBluetoothActive $isBluetoothActive")
  86.             log("areExposureNotificationsEnabled $areExposureNotificationsEnabled")
  87.             if (areExposureNotificationsEnabled == null) null
  88.             else isLocationActive && isBluetoothActive && (areExposureNotificationsEnabled == true)
  89.         }.conflate().onEach {
  90.             _isBroadcastingActive.value = it
  91.         }.launchIn(scope)
  92.  
  93.         lifecycleObserver.isActive.filter { it }.onEach {
  94.             update()
  95.         }.launchIn(scope)
  96.     }
  97.  
  98.     fun cancel() {
  99.         job.cancel()
  100.     }
  101.  
  102.     suspend fun processKeys(token: String, serverDate: Date) {
  103.         val summary = exposureNotificationClient.getExposureSummary(token)
  104.  
  105.         delegate.processKeys(serverDate, summary) {
  106.             exposureNotificationClient.getExposureInformation(token)
  107.         }
  108.     }
  109.  
  110.     suspend fun update() {
  111.         var isEnabled = false
  112.         try {
  113.             isEnabled = true
  114.         } catch (e: Exception) {
  115.             e.printStackTrace()
  116.         } finally {
  117.             _areExposureNotificationsEnabled.value = isEnabled
  118.         }
  119.     }
  120.  
  121.     private var optInCompleter: CompletableDeferred<Unit>? = null
  122.     suspend fun optInAndStartExposureTracing(activity: Activity) {
  123.         if (optInCompleter != null) {
  124.             log("we are already performing this operation")
  125.             return
  126.         }
  127.         if (exposureNotificationClient.isEnabled()) {
  128.             log("Already enabled. Skipping.")
  129.             return
  130.         }
  131.  
  132. //        try {
  133. //            exposureNotificationClient.start()
  134. //        } catch (exception: Exception) {
  135. //
  136. //            val completer = optInCompleter
  137. //            if (completer != null) {
  138. //                log("Error already tried to resolve")
  139. //                completer.completeExceptionally(exception)
  140. //                optInCompleter = null
  141. //                return
  142. //            }
  143. //
  144. //            if (exception !is ApiException) {
  145. //                log("Unknown error")
  146. //                throw exception
  147. //            }
  148. //
  149. //            if (exception.statusCode == ExposureNotificationStatusCodes.RESOLUTION_REQUIRED) {
  150. //                optInCompleter = CompletableDeferred()
  151. //                try {
  152. //                    exception.status.startResolutionForResult(
  153. //                        activity,
  154. //                        REQUEST_CODE_START_EXPOSURE_NOTIFICATION
  155. //                    )
  156. //                    optInCompleter?.await()
  157. //                    optInCompleter = null
  158. //
  159. //                    optInAndStartExposureTracing(activity)
  160. //                    return
  161. //                } catch (e: IntentSender.SendIntentException) {
  162. //                    log("Error calling startResolutionForResult, sending to settings")
  163. //                    optInCompleter?.completeExceptionally(e)
  164. //                    optInCompleter = null
  165. //                    return
  166. //                } catch (e: Exception) {
  167. //                    log("user denied permissions")
  168. //                    optInCompleter = null
  169. //                }
  170. //            } else {
  171. //                log("No RESOLUTION_REQUIRED in result, sending to settings")
  172. //                throw exception
  173. //            }
  174. //        }
  175.         update()
  176.     }
  177.  
  178.     private var tekRequestCompleter: CompletableDeferred<Unit>? = null
  179.     suspend fun requestTekHistory(activity: Activity): List<TemporaryExposureKey> {
  180.         if (tekRequestCompleter != null) {
  181.             log("we are already performing this operation")
  182.             throw Exception()
  183.         }
  184.  
  185.         try {
  186.             return exposureNotificationClient.getTemporaryExposureKeyHistory()
  187.         } catch (exception: Exception) {
  188.             val completer = tekRequestCompleter
  189.             if (completer != null) {
  190.                 log("Error already tried to resolve")
  191.                 completer.completeExceptionally(exception)
  192.                 tekRequestCompleter = null
  193.                 throw exception
  194.             }
  195.  
  196.             if (exception !is ApiException) {
  197.                 log("Unknown error")
  198.                 throw exception
  199.             }
  200.  
  201.             if (exception.statusCode == ExposureNotificationStatusCodes.RESOLUTION_REQUIRED) {
  202.                 tekRequestCompleter = CompletableDeferred()
  203.                 try {
  204.                     exception.status.startResolutionForResult(
  205.                         activity,
  206.                         REQUEST_CODE_TEK_HISTORY
  207.                     )
  208.                     tekRequestCompleter?.await()
  209.                     tekRequestCompleter = null
  210.  
  211.                     return requestTekHistory(activity)
  212.                 } catch (e: IntentSender.SendIntentException) {
  213.                     log("Error calling startResolutionForResult, sending to settings")
  214.                     tekRequestCompleter?.completeExceptionally(e)
  215.                     tekRequestCompleter = null
  216.                     throw e
  217.                 }
  218.             } else {
  219.                 log("No RESOLUTION_REQUIRED in result, sending to settings")
  220.                 throw exception
  221.             }
  222.         }
  223.     }
  224.  
  225.     fun onRequestPermissionsResult(
  226.         activity: Activity,
  227.         requestCode: Int,
  228.         resultCode: Int,
  229.         data: Intent?
  230.     ) {
  231.         val completer = when (requestCode) {
  232.             REQUEST_CODE_START_EXPOSURE_NOTIFICATION -> optInCompleter
  233.             REQUEST_CODE_TEK_HISTORY -> tekRequestCompleter
  234.             else -> return
  235.         }
  236.         scope.launch {
  237.             if (resultCode == Activity.RESULT_OK) {
  238.                 completer?.complete(Unit)
  239.             } else {
  240.                 completer?.completeExceptionally(Exception("Unknown Exception"))
  241.             }
  242.         }
  243.     }
  244.  
  245.     suspend fun stopExposureNotification() {
  246.         // if already disabled, avoid throwing errors
  247.         if (exposureNotificationClient.isEnabled()) {
  248.             exposureNotificationClient.stop()
  249.             update()
  250.         }
  251.     }
  252.  
  253.     suspend fun provideDiagnosisKeys(
  254.         keyFiles: List<File>,
  255.         configuration: ExposureConfiguration,
  256.         token: String
  257.     ) {
  258.         exposureNotificationClient.provideDiagnosisKeys(
  259.             keyFiles = keyFiles,
  260.             configuration = configuration,
  261.             token = token
  262.         )
  263.     }
  264. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement