mirror of
https://github.com/TeamVanced/VancedMicroG
synced 2024-11-27 14:36:49 +01:00
EN: Display more details about app usage, add feature for deleting RPI storage
This commit is contained in:
parent
15fb118bbd
commit
b67a11f4e6
@ -57,4 +57,8 @@ android {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = 1.8
|
||||
}
|
||||
}
|
||||
|
@ -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 { "<tt>$it</tt>" })
|
||||
}.joinToString("<br>").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("<br>").let { getString(R.string.pref_exposure_app_last_report_summary_encounters_prefix, merged.size) + "<br>$it<br><i>" + getString(R.string.pref_exposure_app_last_report_summary_encounters_suffix) + "</i>" }
|
||||
}
|
||||
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<br>$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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:padding="?attr/dialogPreferredPadding"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/pref_exposure_rpi_delete_all_warning" />
|
||||
</FrameLayout>
|
@ -11,10 +11,18 @@
|
||||
<string name="pref_exposure_collected_rpis_title">Gesammelte IDs</string>
|
||||
<string name="pref_exposure_collected_rpis_summary"><xliff:g example="63">%1$d</xliff:g> IDs in den letzten 60 Minuten</string>
|
||||
<string name="pref_exposure_advertising_id_title">Aktuell verwendete ID</string>
|
||||
<string name="pref_exposure_app_checks_summary"><xliff:g example="5">%1$d</xliff:g> Prüfungen während der letzen 14 Tage</string>
|
||||
<string name="pref_exposure_app_last_check_summary">Letzte Prüfung: <xliff:g example="3 hours ago">%1$s</xliff:g></string>
|
||||
<string name="pref_exposure_app_last_report_title">Letzter Bericht (<xliff:g example="vor 2 Stunden">%1$s</xliff:g>)</string>
|
||||
<string name="pref_exposure_app_last_report_summary_diagnosis_keys"><xliff:g example="121031">%1$d</xliff:g> Diagnoseschlüssel verarbeitet.</string>
|
||||
<string name="pref_exposure_app_last_report_summary_encounters_no">Keine Risiko-Begegnung erfasst.</string>
|
||||
<string name="pref_exposure_app_last_report_summary_encounters_prefix"><xliff:g example="3">%1$d</xliff:g> Risiko-Begegnungen:</string>
|
||||
<string name="pref_exposure_app_last_report_summary_encounters_line"><xliff:g example="Gestern, 12:00 - 12:30">%1$s</xliff:g>, Risiko-Level <xliff:g example="99">%2$d</xliff:g></string>
|
||||
<string name="pref_exposure_app_last_report_summary_encounters_suffix">Hinweis: Der Risiko-Level wird durch die App bestimmt. Hohe Werte können ein niedriges Risiko bedeuten oder andersherum.</string>
|
||||
<string name="pref_exposure_app_api_usage_title">Nutzung der API in den letzten 14 Tagen</string>
|
||||
<string name="pref_exposure_app_api_usage_summary_line"><xliff:g example="12">%1$d</xliff:g> Aufrufe von <xliff:g example="provideDiagnosisKeys">%2$s</xliff:g></string>
|
||||
<string name="prefcat_exposure_rpis_histogram_title"><xliff:g example="230">%1$d</xliff:g> gesammelte IDs</string>
|
||||
<string name="pref_exposure_rpi_delete_all_title">Alle gesammelten IDs löschen</string>
|
||||
<string name="pref_exposure_rpi_delete_all_warning">Nach dem Löschen der gesammelten IDs kannst du nicht mehr informiert werden, falls einer deiner Kontakte der letzten 14 Tage positiv getested wurde.</string>
|
||||
<string name="pref_exposure_rpi_delete_all_warning_confirm_button">Trotzdem löschen</string>
|
||||
<string name="pref_exposure_info_summary">"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."</string>
|
||||
|
@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2017 microG Project Team
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<plurals name="pref_exposure_app_last_report_summary">
|
||||
<item quantity="one">Last report: <xliff:g example="1">%1$d</xliff:g> match, <xliff:g example="3">%2$d</xliff:g> days ago</item>
|
||||
<item quantity="other">Last report: <xliff:g example="2">%1$d</xliff:g> matches, latest <xliff:g example="3">%2$d</xliff:g> days ago</item>
|
||||
</plurals>
|
||||
</resources>
|
@ -21,10 +21,18 @@
|
||||
<string name="pref_exposure_collected_rpis_title">Collected IDs</string>
|
||||
<string name="pref_exposure_collected_rpis_summary"><xliff:g example="63">%1$d</xliff:g> IDs in last hour</string>
|
||||
<string name="pref_exposure_advertising_id_title">Currently broadcasted ID</string>
|
||||
<string name="pref_exposure_app_checks_summary"><xliff:g example="5">%1$d</xliff:g> checks in past 14 days</string>
|
||||
<string name="pref_exposure_app_last_check_summary">Last check: <xliff:g example="3 hours ago">%1$s</xliff:g></string>
|
||||
<string name="pref_exposure_app_last_report_title">Last report (<xliff:g example="3 hours ago">%1$s</xliff:g>)</string>
|
||||
<string name="pref_exposure_app_last_report_summary_diagnosis_keys">Processed <xliff:g example="121031">%1$d</xliff:g> diagnosis keys.</string>
|
||||
<string name="pref_exposure_app_last_report_summary_encounters_no">No exposure encounters reported.</string>
|
||||
<string name="pref_exposure_app_last_report_summary_encounters_prefix">Reported <xliff:g example="3">%1$d</xliff:g> exposure encounters:</string>
|
||||
<string name="pref_exposure_app_last_report_summary_encounters_line"><xliff:g example="Yesterday, 12:00 - 14:00">%1$s</xliff:g>, risk score <xliff:g example="99">%2$d</xliff:g></string>
|
||||
<string name="pref_exposure_app_last_report_summary_encounters_suffix">Note: The risk score is defined by the app. High numbers can refer to low risk or vice-versa.</string>
|
||||
<string name="pref_exposure_app_api_usage_title">API usage in the last 14 days</string>
|
||||
<string name="pref_exposure_app_api_usage_summary_line"><xliff:g example="12">%1$d</xliff:g> calls to <xliff:g example="provideDiagnosisKeys">%2$s</xliff:g></string>
|
||||
<string name="prefcat_exposure_rpis_histogram_title"><xliff:g example="230">%1$d</xliff:g> IDs collected</string>
|
||||
<string name="pref_exposure_rpi_delete_all_title">Delete all collected IDs</string>
|
||||
<string name="pref_exposure_rpi_delete_all_warning">Deleting collected IDs will make it impossible to notify you in case any of your contacts of the last 14 days is diagnosed.</string>
|
||||
<string name="pref_exposure_rpi_delete_all_warning_confirm_button">Delete anyways</string>
|
||||
<string name="pref_exposure_info_summary">"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."</string>
|
||||
|
@ -14,8 +14,16 @@
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:layout="@layout/preference_category_no_label">
|
||||
<Preference
|
||||
android:key="pref_exposure_app_checks"
|
||||
android:key="pref_exposure_app_report"
|
||||
android:selectable="false"
|
||||
tools:summary="7 checks in past 14 days\nLast check: 3 hours ago\nLast report: 2 matches, latest 3 days ago" />
|
||||
tools:summary="@string/pref_exposure_app_last_report_summary_encounters_no"
|
||||
tools:title="@string/pref_exposure_app_last_report_title" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:layout="@layout/preference_category_no_label">
|
||||
<Preference
|
||||
android:key="pref_exposure_app_api_usage"
|
||||
android:selectable="false"
|
||||
android:title="@string/pref_exposure_app_api_usage_title"
|
||||
tools:summary="@string/pref_exposure_app_api_usage_summary_line" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
@ -15,7 +15,6 @@
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:layout="@layout/preference_category_no_label">
|
||||
<Preference
|
||||
android:enabled="false"
|
||||
android:key="pref_exposure_rpi_delete_all"
|
||||
android:title="@string/pref_exposure_rpi_delete_all_title" />
|
||||
</PreferenceCategory>
|
||||
|
@ -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<Pair<String, Int>> = readableDatabase.run {
|
||||
val list = arrayListOf<Pair<String, Int>>()
|
||||
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 {
|
||||
|
Loading…
Reference in New Issue
Block a user