From b67a11f4e66e842fe1fcb79d84225a4f70dd1d77 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 17 Oct 2020 22:43:55 +0200 Subject: [PATCH] EN: Display more details about app usage, add feature for deleting RPI storage --- play-services-nearby-core-ui/build.gradle | 4 ++ ...sureNotificationsAppPreferencesFragment.kt | 52 +++++++++++-------- .../ui/ExposureNotificationsRpisFragment.kt | 24 +++++++++ .../exposure_notification_confirm_delete.xml | 17 ++++++ .../src/main/res/values-de/strings.xml | 12 ++++- .../src/main/res/values/plurals.xml | 23 -------- .../src/main/res/values/strings.xml | 12 ++++- ...preferences_exposure_notifications_app.xml | 12 ++++- ...references_exposure_notifications_rpis.xml | 1 - .../exposurenotification/ExposureDatabase.kt | 32 ++++++++++-- 10 files changed, 135 insertions(+), 54 deletions(-) create mode 100644 play-services-nearby-core-ui/src/main/res/layout/exposure_notification_confirm_delete.xml delete mode 100644 play-services-nearby-core-ui/src/main/res/values/plurals.xml diff --git a/play-services-nearby-core-ui/build.gradle b/play-services-nearby-core-ui/build.gradle index 8516e315..8fed8856 100644 --- a/play-services-nearby-core-ui/build.gradle +++ b/play-services-nearby-core-ui/build.gradle @@ -57,4 +57,8 @@ android { sourceCompatibility = 1.8 targetCompatibility = 1.8 } + + kotlinOptions { + jvmTarget = 1.8 + } } diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppPreferencesFragment.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppPreferencesFragment.kt index aa71ecb5..9ce7f3c1 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppPreferencesFragment.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppPreferencesFragment.kt @@ -8,16 +8,18 @@ package org.microg.gms.nearby.core.ui import android.content.Intent import android.os.Bundle import android.text.format.DateUtils +import androidx.core.text.HtmlCompat import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import org.json.JSONObject import org.microg.gms.nearby.exposurenotification.ExposureDatabase -import java.util.concurrent.TimeUnit +import org.microg.gms.nearby.exposurenotification.merge class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() { private lateinit var open: Preference - private lateinit var checks: Preference + private lateinit var report: Preference + private lateinit var apiUsage: Preference private val packageName: String? get() = arguments?.getString("package") @@ -27,7 +29,8 @@ class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() { override fun onBindPreferences() { open = preferenceScreen.findPreference("pref_exposure_app_open") ?: open - checks = preferenceScreen.findPreference("pref_exposure_app_checks") ?: checks + report = preferenceScreen.findPreference("pref_exposure_app_report") ?: report + apiUsage = preferenceScreen.findPreference("pref_exposure_app_api_usage") ?: apiUsage open.onPreferenceClickListener = Preference.OnPreferenceClickListener { try { packageName?.let { @@ -50,27 +53,34 @@ class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() { fun updateContent() { packageName?.let { packageName -> lifecycleScope.launchWhenResumed { - checks.summary = ExposureDatabase.with(requireContext()) { database -> - var str = getString(R.string.pref_exposure_app_checks_summary, database.countMethodCalls(packageName, "provideDiagnosisKeys")) + val (reportTitle, reportSummary, apiUsageSummary) = ExposureDatabase.with(requireContext()) { database -> + val apiUsageSummary = database.methodUsageHistogram(packageName).map { + getString(R.string.pref_exposure_app_api_usage_summary_line, it.second, it.first.let { "$it" }) + }.joinToString("
").takeIf { it.isNotEmpty() } + val token = database.lastMethodCallArgs(packageName, "provideDiagnosisKeys")?.let { JSONObject(it).getString("request_token") } + ?: return@with Triple(null, null, apiUsageSummary) val lastCheckTime = database.lastMethodCall(packageName, "provideDiagnosisKeys") - if (lastCheckTime != null && lastCheckTime != 0L) { - str += "\n" + getString(R.string.pref_exposure_app_last_check_summary, DateUtils.getRelativeDateTimeString(context, lastCheckTime, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME)) + ?: return@with Triple(null, null, apiUsageSummary) + val config = database.loadConfiguration(packageName, token) + ?: return@with Triple(null, null, apiUsageSummary) + val merged = database.findAllMeasuredExposures(config.first).merge().sortedBy { it.timestamp } + val reportTitle = getString(R.string.pref_exposure_app_last_report_title, DateUtils.getRelativeTimeSpanString(lastCheckTime, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS)) + val diagnosisKeysLine = getString(R.string.pref_exposure_app_last_report_summary_diagnosis_keys, database.countDiagnosisKeysInvolved(config.first)) + val encountersLine = if (merged.isEmpty()) { + getString(R.string.pref_exposure_app_last_report_summary_encounters_no) + } else { + database.findAllMeasuredExposures(config.first).merge().map { + val riskScore = it.getRiskScore(config.second) + "· " + getString(R.string.pref_exposure_app_last_report_summary_encounters_line, DateUtils.formatDateRange(requireContext(), it.timestamp, it.timestamp + it.durationInMinutes * 60 * 1000L, DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE), riskScore) + }.joinToString("
").let { getString(R.string.pref_exposure_app_last_report_summary_encounters_prefix, merged.size) + "
$it
" + getString(R.string.pref_exposure_app_last_report_summary_encounters_suffix) + "" } } - val lastExposureSummaryTime = database.lastMethodCall(packageName, "getExposureSummary") - val lastExposureSummary = database.lastMethodCallArgs(packageName, "getExposureSummary") - if (lastExposureSummaryTime != null && lastExposureSummary != null && System.currentTimeMillis() - lastExposureSummaryTime <= TimeUnit.DAYS.toMillis(1)) { - try { - val json = JSONObject(lastExposureSummary) - val matchedKeys = json.optInt("response_matched_keys") - val daysSince = json.optInt("response_days_since", -1) - if (matchedKeys > 0 && daysSince >= 0) { - str += "\n" + resources.getQuantityString(R.plurals.pref_exposure_app_last_report_summary, matchedKeys, matchedKeys, daysSince) - } - } catch (ignored: Exception) { - } - } - str + Triple(reportTitle, "$diagnosisKeysLine
$encountersLine", apiUsageSummary) } + report.isVisible = reportSummary != null + report.title = reportTitle + report.summary = HtmlCompat.fromHtml(reportSummary.orEmpty(), HtmlCompat.FROM_HTML_MODE_COMPACT).trim() + apiUsage.isVisible = apiUsageSummary != null + apiUsage.summary = HtmlCompat.fromHtml(apiUsageSummary.orEmpty(), HtmlCompat.FROM_HTML_MODE_COMPACT).trim() } } } diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsRpisFragment.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsRpisFragment.kt index 387f491c..8402a445 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsRpisFragment.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsRpisFragment.kt @@ -6,9 +6,15 @@ package org.microg.gms.nearby.core.ui import android.annotation.TargetApi +import android.content.DialogInterface import android.os.Bundle import android.text.format.DateFormat +import android.util.Log +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.core.view.setPadding import androidx.lifecycle.lifecycleScope +import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import com.db.williamchart.data.Scale @@ -23,6 +29,7 @@ import kotlin.math.roundToLong class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() { private lateinit var histogramCategory: PreferenceCategory private lateinit var histogram: BarChartPreference + private lateinit var deleteAll: Preference override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_exposure_notifications_rpis) @@ -31,6 +38,22 @@ class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() { override fun onBindPreferences() { histogramCategory = preferenceScreen.findPreference("prefcat_exposure_rpi_histogram") ?: histogramCategory histogram = preferenceScreen.findPreference("pref_exposure_rpi_histogram") ?: histogram + deleteAll = preferenceScreen.findPreference("pref_exposure_rpi_delete_all") ?: deleteAll + deleteAll.onPreferenceClickListener = Preference.OnPreferenceClickListener { + AlertDialog.Builder(requireContext()) + .setTitle(R.string.pref_exposure_rpi_delete_all_title) + .setView(R.layout.exposure_notification_confirm_delete) + .setPositiveButton(R.string.pref_exposure_rpi_delete_all_warning_confirm_button) { _, _ -> + lifecycleScope.launchWhenStarted { + ExposureDatabase.with(requireContext()) { it.deleteAllCollectedAdvertisements() } + updateChart() + } + } + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .create() + .show() + true + } } override fun onResume() { @@ -66,6 +89,7 @@ class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() { val totalRpiCount = database.totalRpiCount totalRpiCount to map } + deleteAll.isEnabled = totalRpiCount != 0L histogramCategory.title = getString(R.string.prefcat_exposure_rpis_histogram_title, totalRpiCount) histogram.labelsFormatter = { it.roundToInt().toString() } histogram.scale = Scale(0f, rpiHistogram.values.max()?.coerceAtLeast(0.1f) ?: 0.1f) diff --git a/play-services-nearby-core-ui/src/main/res/layout/exposure_notification_confirm_delete.xml b/play-services-nearby-core-ui/src/main/res/layout/exposure_notification_confirm_delete.xml new file mode 100644 index 00000000..4b9c0215 --- /dev/null +++ b/play-services-nearby-core-ui/src/main/res/layout/exposure_notification_confirm_delete.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/play-services-nearby-core-ui/src/main/res/values-de/strings.xml b/play-services-nearby-core-ui/src/main/res/values-de/strings.xml index 8283220d..0296fdd7 100644 --- a/play-services-nearby-core-ui/src/main/res/values-de/strings.xml +++ b/play-services-nearby-core-ui/src/main/res/values-de/strings.xml @@ -11,10 +11,18 @@ Gesammelte IDs %1$d IDs in den letzten 60 Minuten Aktuell verwendete ID - %1$d Prüfungen während der letzen 14 Tage - Letzte Prüfung: %1$s + Letzter Bericht (%1$s) + %1$d Diagnoseschlüssel verarbeitet. + Keine Risiko-Begegnung erfasst. + %1$d Risiko-Begegnungen: + %1$s, Risiko-Level %2$d + Hinweis: Der Risiko-Level wird durch die App bestimmt. Hohe Werte können ein niedriges Risiko bedeuten oder andersherum. + Nutzung der API in den letzten 14 Tagen + %1$d Aufrufe von %2$s %1$d gesammelte IDs Alle gesammelten IDs löschen + Nach dem Löschen der gesammelten IDs kannst du nicht mehr informiert werden, falls einer deiner Kontakte der letzten 14 Tage positiv getested wurde. + Trotzdem löschen "Die Exposure Notifications API ermöglicht es Apps, dich zu warnen, falls du Kontakt zu einer positiv getesteten Person hattest. Das Datum, die Zeitdauer und die Signalstärke, die dem Kontakt zugeordnet sind, werden mit der zugehörigen App geteilt." diff --git a/play-services-nearby-core-ui/src/main/res/values/plurals.xml b/play-services-nearby-core-ui/src/main/res/values/plurals.xml deleted file mode 100644 index b36fa08e..00000000 --- a/play-services-nearby-core-ui/src/main/res/values/plurals.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - Last report: %1$d match, %2$d days ago - Last report: %1$d matches, latest %2$d days ago - - diff --git a/play-services-nearby-core-ui/src/main/res/values/strings.xml b/play-services-nearby-core-ui/src/main/res/values/strings.xml index 6f490483..f56e1dc2 100644 --- a/play-services-nearby-core-ui/src/main/res/values/strings.xml +++ b/play-services-nearby-core-ui/src/main/res/values/strings.xml @@ -21,10 +21,18 @@ Collected IDs %1$d IDs in last hour Currently broadcasted ID - %1$d checks in past 14 days - Last check: %1$s + Last report (%1$s) + Processed %1$d diagnosis keys. + No exposure encounters reported. + Reported %1$d exposure encounters: + %1$s, risk score %2$d + Note: The risk score is defined by the app. High numbers can refer to low risk or vice-versa. + API usage in the last 14 days + %1$d calls to %2$s %1$d IDs collected Delete all collected IDs + Deleting collected IDs will make it impossible to notify you in case any of your contacts of the last 14 days is diagnosed. + Delete anyways "Exposure Notifications API allows apps to notify you if you were exposed to someone who reported to be diagnosed positive. The date, duration, and signal strength associated with an exposure will be shared with the corresponding app." diff --git a/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications_app.xml b/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications_app.xml index 26caa9d8..c08872ef 100644 --- a/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications_app.xml +++ b/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications_app.xml @@ -14,8 +14,16 @@ + tools:summary="@string/pref_exposure_app_last_report_summary_encounters_no" + tools:title="@string/pref_exposure_app_last_report_title" /> + + + diff --git a/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications_rpis.xml b/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications_rpis.xml index 7215423f..7ef08d91 100644 --- a/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications_rpis.xml +++ b/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications_rpis.xml @@ -15,7 +15,6 @@ diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt index b2c6b2f1..4789a95c 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt @@ -19,6 +19,7 @@ import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import kotlinx.coroutines.* import okio.ByteString +import org.json.JSONObject import java.io.File import java.lang.Runnable import java.nio.ByteBuffer @@ -162,9 +163,6 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit update(TABLE_TEK_CHECK_SINGLE, ContentValues().apply { put("matched", 0) }, null, null) - update(TABLE_TEK_CHECK_FILE, ContentValues().apply { - put("matched", 0) - }, null, null) } fun noteAppAction(packageName: String, method: String, args: String? = null, timestamp: Long = Date().time) = writableDatabase.run { @@ -658,6 +656,34 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } } + fun countDiagnosisKeysInvolved(tid: Long): Long = readableDatabase.run { + val fromFile = rawQuery("SELECT SUM($TABLE_TEK_CHECK_FILE.keys) AS keys FROM $TABLE_TEK_CHECK_FILE_TOKEN JOIN $TABLE_TEK_CHECK_FILE ON $TABLE_TEK_CHECK_FILE_TOKEN.tcfid = $TABLE_TEK_CHECK_FILE.tcfid WHERE $TABLE_TEK_CHECK_FILE_TOKEN.tid = $tid;", null).use { cursor -> + if (cursor.moveToNext()) { + cursor.getLong(0) + } else { + 0 + } + } + val single = rawQuery("SELECT COUNT(*) as keys FROM $TABLE_TEK_CHECK_SINGLE_TOKEN WHERE $TABLE_TEK_CHECK_SINGLE_TOKEN.tid = $tid;", null).use { cursor -> + if (cursor.moveToNext()) { + cursor.getLong(0) + } else { + 0 + } + } + return fromFile + single + } + + fun methodUsageHistogram(packageName: String): List> = readableDatabase.run { + val list = arrayListOf>() + rawQuery("SELECT method, COUNT(*) AS count FROM $TABLE_APP_LOG WHERE package = ? GROUP BY method;", arrayOf(packageName)).use { cursor -> + while (cursor.moveToNext()) { + list.add(cursor.getString(0) to cursor.getInt(1)) + } + } + list.sortedByDescending { it.second } + } + private fun ensureTemporaryExposureKey(): TemporaryExposureKey = writableDatabase.let { database -> database.beginTransactionNonExclusive() try {