EN: Make service more robust against exceptions while processing app input

This commit is contained in:
Marvin W 2020-12-27 17:09:51 +01:00
parent 45d4dffb88
commit 91c8e43ab9
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A

View File

@ -12,6 +12,7 @@ import android.content.*
import android.os.* import android.os.*
import android.util.Log import android.util.Log
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.gms.common.api.Status 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.ExposureNotificationStatusCodes.*
import com.google.android.gms.nearby.exposurenotification.internal.* import com.google.android.gms.nearby.exposurenotification.internal.*
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject 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 { 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 override fun getLifecycle(): Lifecycle = lifecycle
private fun pendingConfirm(permission: String): PendingIntent { private fun pendingConfirm(permission: String): PendingIntent {
@ -102,7 +107,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
} }
override fun start(params: StartParams) { override fun start(params: StartParams) {
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
val isAuthorized = ExposureDatabase.with(context) { it.isAppAuthorized(packageName) } val isAuthorized = ExposureDatabase.with(context) { it.isAppAuthorized(packageName) }
val adapter = BluetoothAdapter.getDefaultAdapter() val adapter = BluetoothAdapter.getDefaultAdapter()
val status = if (isAuthorized && ExposurePreferences(context).enabled) { val status = if (isAuthorized && ExposurePreferences(context).enabled) {
@ -131,7 +136,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
} }
override fun stop(params: StopParams) { override fun stop(params: StopParams) {
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
val isAuthorized = ExposureDatabase.with(context) { database -> val isAuthorized = ExposureDatabase.with(context) { database ->
database.isAppAuthorized(packageName).also { database.isAppAuthorized(packageName).also {
if (it) database.noteAppAction(packageName, "stop") if (it) database.noteAppAction(packageName, "stop")
@ -149,7 +154,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
} }
override fun isEnabled(params: IsEnabledParams) { override fun isEnabled(params: IsEnabledParams) {
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
val isAuthorized = ExposureDatabase.with(context) { database -> val isAuthorized = ExposureDatabase.with(context) { database ->
database.isAppAuthorized(packageName) database.isAppAuthorized(packageName)
} }
@ -162,7 +167,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
} }
override fun getTemporaryExposureKeyHistory(params: GetTemporaryExposureKeyHistoryParams) { override fun getTemporaryExposureKeyHistory(params: GetTemporaryExposureKeyHistoryParams) {
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
val status = confirmPermission(CONFIRM_ACTION_KEYS) val status = confirmPermission(CONFIRM_ACTION_KEYS)
val response = when { val response = when {
status.isSuccess -> ExposureDatabase.with(context) { database -> status.isSuccess -> ExposureDatabase.with(context) { database ->
@ -243,7 +248,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
override fun provideDiagnosisKeys(params: ProvideDiagnosisKeysParams) { override fun provideDiagnosisKeys(params: ProvideDiagnosisKeysParams) {
val token = params.token ?: TOKEN_A val token = params.token ?: TOKEN_A
Log.w(TAG, "provideDiagnosisKeys() with $packageName/$token") Log.w(TAG, "provideDiagnosisKeys() with $packageName/$token")
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
val tid = ExposureDatabase.with(context) { database -> val tid = ExposureDatabase.with(context) { database ->
val configuration = params.configuration val configuration = params.configuration
if (configuration != null) { if (configuration != null) {
@ -259,7 +264,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Callback failed", e) Log.w(TAG, "Callback failed", e)
} }
return@launchWhenStarted return@launchSafely
} }
ExposureDatabase.with(context) { database -> ExposureDatabase.with(context) { database ->
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
@ -289,21 +294,25 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
} }
params.keyFileSupplier?.let { keyFileSupplier -> params.keyFileSupplier?.let { keyFileSupplier ->
Log.d(TAG, "Using key file supplier") Log.d(TAG, "Using key file supplier")
while (keyFileSupplier.isAvailable && keyFileSupplier.hasNext()) { try {
try { while (keyFileSupplier.isAvailable && keyFileSupplier.hasNext()) {
val cacheFile = File(context.cacheDir, "en-keyfile-${System.currentTimeMillis()}-${Random.nextInt()}.zip") try {
ParcelFileDescriptor.AutoCloseInputStream(keyFileSupplier.next()).use { it.copyToFile(cacheFile) } val cacheFile = File(context.cacheDir, "en-keyfile-${System.currentTimeMillis()}-${Random.nextInt()}.zip")
val hash = MessageDigest.getInstance("SHA-256").digest(cacheFile) ParcelFileDescriptor.AutoCloseInputStream(keyFileSupplier.next()).use { it.copyToFile(cacheFile) }
val storedKeys = database.storeDiagnosisFileUsed(tid, hash) val hash = MessageDigest.getInstance("SHA-256").digest(cacheFile)
if (storedKeys != null) { val storedKeys = database.storeDiagnosisFileUsed(tid, hash)
keys += storedKeys.toInt() if (storedKeys != null) {
cacheFile.delete() keys += storedKeys.toInt()
} else { cacheFile.delete()
todoKeyFiles.add(cacheFile to hash) } 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) { override fun getExposureSummary(params: GetExposureSummaryParams) {
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
val response = buildExposureSummary(params.token) val response = buildExposureSummary(params.token)
ExposureDatabase.with(context) { database -> ExposureDatabase.with(context) { database ->
@ -413,7 +422,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
} }
override fun getExposureInformation(params: GetExposureInformationParams) { override fun getExposureInformation(params: GetExposureInformationParams) {
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
ExposureDatabase.with(context) { database -> ExposureDatabase.with(context) { database ->
val pair = database.loadConfiguration(packageName, params.token) val pair = database.loadConfiguration(packageName, params.token)
val response = if (pair != null && database.isAppAuthorized(packageName)) { 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) { override fun getExposureWindows(params: GetExposureWindowsParams) {
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
val response = getExposureWindowsInternal(params.token ?: TOKEN_A) val response = getExposureWindowsInternal(params.token ?: TOKEN_A)
ExposureDatabase.with(context) { database -> ExposureDatabase.with(context) { database ->
@ -529,7 +538,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
} }
override fun getDailySummaries(params: GetDailySummariesParams) { override fun getDailySummaries(params: GetDailySummariesParams) {
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
val response = getExposureWindowsInternal().groupBy { it.dateMillisSinceEpoch }.map { val response = getExposureWindowsInternal().groupBy { it.dateMillisSinceEpoch }.map {
val map = arrayListOf<DailySummary.ExposureSummaryData>() val map = arrayListOf<DailySummary.ExposureSummaryData>()
for (i in 0 until ReportType.VALUES) { for (i in 0 until ReportType.VALUES) {
@ -564,7 +573,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
} }
override fun setDiagnosisKeysDataMapping(params: SetDiagnosisKeysDataMappingParams) { override fun setDiagnosisKeysDataMapping(params: SetDiagnosisKeysDataMappingParams) {
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
ExposureDatabase.with(context) { database -> ExposureDatabase.with(context) { database ->
database.storeConfiguration(packageName, TOKEN_A, params.mapping) database.storeConfiguration(packageName, TOKEN_A, params.mapping)
database.noteAppAction(packageName, "setDiagnosisKeysDataMapping") database.noteAppAction(packageName, "setDiagnosisKeysDataMapping")
@ -578,7 +587,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
} }
override fun getDiagnosisKeysDataMapping(params: GetDiagnosisKeysDataMappingParams) { override fun getDiagnosisKeysDataMapping(params: GetDiagnosisKeysDataMappingParams) {
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
val mapping = ExposureDatabase.with(context) { database -> val mapping = ExposureDatabase.with(context) { database ->
val triple = database.loadConfiguration(packageName, TOKEN_A) val triple = database.loadConfiguration(packageName, TOKEN_A)
database.noteAppAction(packageName, "getDiagnosisKeysDataMapping") database.noteAppAction(packageName, "getDiagnosisKeysDataMapping")
@ -594,7 +603,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
override fun getPackageConfiguration(params: GetPackageConfigurationParams) { override fun getPackageConfiguration(params: GetPackageConfigurationParams) {
Log.w(TAG, "Not yet implemented: getPackageConfiguration") Log.w(TAG, "Not yet implemented: getPackageConfiguration")
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
ExposureDatabase.with(context) { database -> ExposureDatabase.with(context) { database ->
database.noteAppAction(packageName, "getPackageConfiguration") database.noteAppAction(packageName, "getPackageConfiguration")
} }
@ -608,7 +617,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
override fun getStatus(params: GetStatusParams) { override fun getStatus(params: GetStatusParams) {
Log.w(TAG, "Not yet implemented: getStatus") Log.w(TAG, "Not yet implemented: getStatus")
lifecycleScope.launchWhenStarted { lifecycleScope.launchSafely {
ExposureDatabase.with(context) { database -> ExposureDatabase.with(context) { database ->
database.noteAppAction(packageName, "getStatus") database.noteAppAction(packageName, "getStatus")
} }