From 91c8e43ab947482f7c3ab55eca53d4f476f591bc Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 27 Dec 2020 17:09:51 +0100 Subject: [PATCH] EN: Make service more robust against exceptions while processing app input --- .../ExposureNotificationServiceImpl.kt | 63 +++++++++++-------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index 0f04427e..2b3b1bc7 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -12,6 +12,7 @@ import android.content.* import android.os.* import android.util.Log import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import com.google.android.gms.common.api.Status @@ -19,6 +20,8 @@ import com.google.android.gms.nearby.exposurenotification.* import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes.* import com.google.android.gms.nearby.exposurenotification.internal.* import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.withTimeout import org.json.JSONArray import org.json.JSONObject @@ -37,6 +40,8 @@ import kotlin.random.Random class ExposureNotificationServiceImpl(private val context: Context, private val lifecycle: Lifecycle, private val packageName: String) : INearbyExposureNotificationService.Stub(), LifecycleOwner { + private fun LifecycleCoroutineScope.launchSafely(block: suspend CoroutineScope.() -> Unit): Job = launchWhenStarted { try { block() } catch (e: Exception) { Log.w(TAG, "Error in coroutine", e) } } + override fun getLifecycle(): Lifecycle = lifecycle private fun pendingConfirm(permission: String): PendingIntent { @@ -102,7 +107,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun start(params: StartParams) { - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { val isAuthorized = ExposureDatabase.with(context) { it.isAppAuthorized(packageName) } val adapter = BluetoothAdapter.getDefaultAdapter() val status = if (isAuthorized && ExposurePreferences(context).enabled) { @@ -131,7 +136,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun stop(params: StopParams) { - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { val isAuthorized = ExposureDatabase.with(context) { database -> database.isAppAuthorized(packageName).also { if (it) database.noteAppAction(packageName, "stop") @@ -149,7 +154,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun isEnabled(params: IsEnabledParams) { - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { val isAuthorized = ExposureDatabase.with(context) { database -> database.isAppAuthorized(packageName) } @@ -162,7 +167,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun getTemporaryExposureKeyHistory(params: GetTemporaryExposureKeyHistoryParams) { - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { val status = confirmPermission(CONFIRM_ACTION_KEYS) val response = when { status.isSuccess -> ExposureDatabase.with(context) { database -> @@ -243,7 +248,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val override fun provideDiagnosisKeys(params: ProvideDiagnosisKeysParams) { val token = params.token ?: TOKEN_A Log.w(TAG, "provideDiagnosisKeys() with $packageName/$token") - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { val tid = ExposureDatabase.with(context) { database -> val configuration = params.configuration if (configuration != null) { @@ -259,7 +264,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } catch (e: Exception) { Log.w(TAG, "Callback failed", e) } - return@launchWhenStarted + return@launchSafely } ExposureDatabase.with(context) { database -> val start = System.currentTimeMillis() @@ -289,21 +294,25 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } params.keyFileSupplier?.let { keyFileSupplier -> Log.d(TAG, "Using key file supplier") - while (keyFileSupplier.isAvailable && keyFileSupplier.hasNext()) { - try { - val cacheFile = File(context.cacheDir, "en-keyfile-${System.currentTimeMillis()}-${Random.nextInt()}.zip") - ParcelFileDescriptor.AutoCloseInputStream(keyFileSupplier.next()).use { it.copyToFile(cacheFile) } - val hash = MessageDigest.getInstance("SHA-256").digest(cacheFile) - val storedKeys = database.storeDiagnosisFileUsed(tid, hash) - if (storedKeys != null) { - keys += storedKeys.toInt() - cacheFile.delete() - } else { - todoKeyFiles.add(cacheFile to hash) + try { + while (keyFileSupplier.isAvailable && keyFileSupplier.hasNext()) { + try { + val cacheFile = File(context.cacheDir, "en-keyfile-${System.currentTimeMillis()}-${Random.nextInt()}.zip") + ParcelFileDescriptor.AutoCloseInputStream(keyFileSupplier.next()).use { it.copyToFile(cacheFile) } + val hash = MessageDigest.getInstance("SHA-256").digest(cacheFile) + val storedKeys = database.storeDiagnosisFileUsed(tid, hash) + if (storedKeys != null) { + keys += storedKeys.toInt() + cacheFile.delete() + } else { + todoKeyFiles.add(cacheFile to hash) + } + } catch (e: Exception) { + Log.w(TAG, "Failed parsing file", e) } - } catch (e: Exception) { - Log.w(TAG, "Failed parsing file", e) } + } catch (e: Exception) { + Log.w(TAG, "Disconnected from key file supplier", e) } } @@ -389,7 +398,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun getExposureSummary(params: GetExposureSummaryParams) { - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { val response = buildExposureSummary(params.token) ExposureDatabase.with(context) { database -> @@ -413,7 +422,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun getExposureInformation(params: GetExposureInformationParams) { - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { ExposureDatabase.with(context) { database -> val pair = database.loadConfiguration(packageName, params.token) val response = if (pair != null && database.isAppAuthorized(packageName)) { @@ -494,7 +503,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun getExposureWindows(params: GetExposureWindowsParams) { - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { val response = getExposureWindowsInternal(params.token ?: TOKEN_A) ExposureDatabase.with(context) { database -> @@ -529,7 +538,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun getDailySummaries(params: GetDailySummariesParams) { - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { val response = getExposureWindowsInternal().groupBy { it.dateMillisSinceEpoch }.map { val map = arrayListOf() for (i in 0 until ReportType.VALUES) { @@ -564,7 +573,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun setDiagnosisKeysDataMapping(params: SetDiagnosisKeysDataMappingParams) { - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { ExposureDatabase.with(context) { database -> database.storeConfiguration(packageName, TOKEN_A, params.mapping) database.noteAppAction(packageName, "setDiagnosisKeysDataMapping") @@ -578,7 +587,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun getDiagnosisKeysDataMapping(params: GetDiagnosisKeysDataMappingParams) { - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { val mapping = ExposureDatabase.with(context) { database -> val triple = database.loadConfiguration(packageName, TOKEN_A) database.noteAppAction(packageName, "getDiagnosisKeysDataMapping") @@ -594,7 +603,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val override fun getPackageConfiguration(params: GetPackageConfigurationParams) { Log.w(TAG, "Not yet implemented: getPackageConfiguration") - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { ExposureDatabase.with(context) { database -> database.noteAppAction(packageName, "getPackageConfiguration") } @@ -608,7 +617,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val override fun getStatus(params: GetStatusParams) { Log.w(TAG, "Not yet implemented: getStatus") - lifecycleScope.launchWhenStarted { + lifecycleScope.launchSafely { ExposureDatabase.with(context) { database -> database.noteAppAction(packageName, "getStatus") }