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 {