Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit 9b91bf63 authored by Marvin W.'s avatar Marvin W. 🐿️
Browse files

EN: (UI) Improve display of reported exposures

parent 369c3d75
Loading
Loading
Loading
Loading
+65 −23
Original line number Diff line number Diff line
@@ -8,10 +8,14 @@ package org.microg.gms.nearby.core.ui
import android.content.Intent
import android.os.Bundle
import android.text.format.DateUtils
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceViewHolder
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
import org.json.JSONObject
import org.microg.gms.nearby.exposurenotification.ExposureDatabase
@@ -19,7 +23,9 @@ import org.microg.gms.nearby.exposurenotification.merge

class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() {
    private lateinit var open: Preference
    private lateinit var report: Preference
    private lateinit var reportedExposures: PreferenceCategory
    private lateinit var reportedExposuresNone: Preference
    private lateinit var reportedExposuresUpdated: Preference
    private lateinit var apiUsage: Preference
    private val packageName: String?
        get() = arguments?.getString("package")
@@ -30,7 +36,11 @@ class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() {

    override fun onBindPreferences() {
        open = preferenceScreen.findPreference("pref_exposure_app_open") ?: open
        report = preferenceScreen.findPreference("pref_exposure_app_report") ?: report
        reportedExposures = preferenceScreen.findPreference("prefcat_exposure_app_report") ?: reportedExposures
        reportedExposuresNone = preferenceScreen.findPreference("pref_exposure_app_report_none")
                ?: reportedExposuresNone
        reportedExposuresUpdated = preferenceScreen.findPreference("pref_exposure_app_report_updated")
                ?: reportedExposuresUpdated
        apiUsage = preferenceScreen.findPreference("pref_exposure_app_api_usage") ?: apiUsage
        open.onPreferenceClickListener = Preference.OnPreferenceClickListener {
            try {
@@ -54,35 +64,67 @@ class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() {
    private fun ExposureConfiguration?.orDefault() = this
            ?: ExposureConfiguration.ExposureConfigurationBuilder().build()

    private fun formatRelativeDateTimeString(time: Long): CharSequence? =
            DateUtils.getRelativeDateTimeString(
                    requireContext(),
                    time,
                    DateUtils.DAY_IN_MILLIS,
                    DateUtils.DAY_IN_MILLIS * 2,
                    0
            )

    fun updateContent() {
        packageName?.let { packageName ->
            lifecycleScope.launchWhenResumed {
                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() }
                data class NTuple4<T1, T2, T3, T4>(val t1: T1, val t2: T2, val t3: T3, val t4: T4)
                val (mergedExposures, keysInvolved, lastCheckTime, methodUsageHistogram) = ExposureDatabase.with(requireContext()) { database ->
                    val methodUsageHistogram = database.methodUsageHistogram(packageName)

                    val token = database.lastMethodCallArgs(packageName, "provideDiagnosisKeys")?.let { JSONObject(it).getString("request_token") }
                            ?: return@with Triple(null, null, apiUsageSummary)
                            ?: return@with NTuple4(null, null, null, methodUsageHistogram)
                    val lastCheckTime = database.lastMethodCall(packageName, "provideDiagnosisKeys")
                            ?: return@with Triple(null, null, apiUsageSummary)
                            ?: return@with NTuple4(null, null, null, methodUsageHistogram)
                    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)
                            ?: return@with NTuple4(null, null, null, methodUsageHistogram)
                    val mergedExposures = database.findAllMeasuredExposures(config.first).merge().sortedBy { it.timestamp }
                    val keysInvolved = database.countDiagnosisKeysInvolved(config.first)
                    NTuple4(mergedExposures, keysInvolved, lastCheckTime, methodUsageHistogram)
                }

                reportedExposures.removeAll()
                if (mergedExposures.isNullOrEmpty()) {
                    reportedExposures.addPreference(reportedExposuresNone)
                } else {
                        merged.map {
                            val riskScore = it.getRiskScore(config.second.orDefault())
                            "· " + 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>" }
                    for (exposure in mergedExposures) {
                        val minAttenuation = exposure.subs.map { it.attenuation }.minOrNull() ?: exposure.attenuation
                        val nearby = exposure.attenuation < 63 || minAttenuation < 55
                        val distanceString = if (nearby) getString(R.string.pref_exposure_app_report_entry_distance_close) else getString(R.string.pref_exposure_app_report_entry_distance_far)
                        val durationString = if (exposure.durationInMinutes < 5) getString(R.string.pref_exposure_app_report_entry_time_short) else getString(R.string.pref_exposure_app_report_entry_time_about, exposure.durationInMinutes)
                        val preference = object : Preference(requireContext()) {
                            override fun onBindViewHolder(holder: PreferenceViewHolder?) {
                                val titleView = holder!!.findViewById(android.R.id.title) as? TextView
                                val titleViewTextColor = titleView?.textColors
                                super.onBindViewHolder(holder)
                                if (titleViewTextColor != null) titleView.setTextColor(titleViewTextColor)
                            }
                    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()
                        preference.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_alert)
                        preference.title = DateUtils.formatDateRange(requireContext(), exposure.timestamp, exposure.timestamp + exposure.durationInMinutes * 60 * 1000L, DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE)
                        preference.summary = getString(R.string.pref_exposure_app_report_entry_combined, durationString, distanceString)
                        preference.isSelectable = false
                        reportedExposures.addPreference(preference)
                    }
                }

                reportedExposuresUpdated.isVisible = lastCheckTime != null
                reportedExposuresUpdated.title = if (lastCheckTime != null) getString(R.string.pref_exposure_app_report_updated_title, DateUtils.getRelativeDateTimeString(requireContext(), lastCheckTime, DateUtils.DAY_IN_MILLIS, DateUtils.DAY_IN_MILLIS * 2, 0)) else null
                reportedExposuresUpdated.summary = getString(R.string.pref_exposure_app_last_report_summary_diagnosis_keys, keysInvolved?.toInt()
                        ?: 0)
                reportedExposures.addPreference(reportedExposuresUpdated)

                val apiUsageSummary = methodUsageHistogram.map {
                    getString(R.string.pref_exposure_app_api_usage_summary_line, it.second, it.first.let { "<small><tt>$it</tt></small>" })
                }.joinToString("<br>").takeIf { it.isNotEmpty() }
                apiUsage.isVisible = apiUsageSummary != null
                apiUsage.summary = HtmlCompat.fromHtml(apiUsageSummary.orEmpty(), HtmlCompat.FROM_HTML_MODE_COMPACT).trim()
            }
+1 −1
Original line number Diff line number Diff line
@@ -12,5 +12,5 @@
    android:viewportHeight="24">
    <path
        android:fillColor="#000"
        android:pathData="M13 14H11V9H13M13 18H11V16H13M1 21H23L12 2L1 21Z" />
        android:pathData="M11,15H13V17H11V15M11,7H13V13H11V7M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20Z" />
</vector>
+7 −1
Original line number Diff line number Diff line
@@ -15,7 +15,13 @@
    <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_last_report_title">Letzter Bericht (<xliff:g example="vor 2 Stunden">%1$s</xliff:g>)</string>
    <string name="prefcat_exposure_app_report_title">Gemeldete Begegnungen</string>
    <string name="pref_exposure_app_report_updated_title">Aktualisiert: <xliff:g example="Today, 14:02">%1$s</xliff:g></string>
    <string name="pref_exposure_app_report_entry_time_short">Kürzer als 5 Minutem</string>
    <string name="pref_exposure_app_report_entry_time_about">Etwa <xliff:g example="13">%1$d</xliff:g> Minuten</string>
    <string name="pref_exposure_app_report_entry_distance_close">nahe Begegnung</string>
    <string name="pref_exposure_app_report_entry_distance_far">entfernte Begegnung</string>
    <string name="pref_exposure_app_report_entry_combined"><xliff:g example="About 12 minutes">%1$s</xliff:g>, <xliff:g example="distant exposure">%2$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>
+7 −1
Original line number Diff line number Diff line
@@ -25,7 +25,13 @@
    <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_last_report_title">Last report (<xliff:g example="3 hours ago">%1$s</xliff:g>)</string>
    <string name="prefcat_exposure_app_report_title">Reported exposures</string>
    <string name="pref_exposure_app_report_updated_title">Updated: <xliff:g example="Today, 14:02">%1$s</xliff:g></string>
    <string name="pref_exposure_app_report_entry_time_short">Less than 5 minutes</string>
    <string name="pref_exposure_app_report_entry_time_about">About <xliff:g example="13">%1$d</xliff:g> minutes</string>
    <string name="pref_exposure_app_report_entry_distance_close">nearby exposure</string>
    <string name="pref_exposure_app_report_entry_distance_far">distant exposure</string>
    <string name="pref_exposure_app_report_entry_combined"><xliff:g example="About 12 minutes">%1$s</xliff:g>, <xliff:g example="distant exposure">%2$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>
+1 −1
Original line number Diff line number Diff line
@@ -61,7 +61,7 @@
            tools:summary="@string/pref_exposure_collected_rpis_summary" />
        <Preference
            android:key="pref_exposure_advertising_id"
            android:selectable="false"
            android:enabled="false"
            android:title="@string/pref_exposure_advertising_id_title"
            tools:summary="9a799d68-925f-4c0c-a73c-b418f22a1250" />
    </PreferenceCategory>
Loading