mirror of
https://github.com/TeamVanced/VancedMicroG
synced 2024-12-28 13:45:50 +01:00
EN: Display historgram of collected IDs using hourly heat map
This commit is contained in:
parent
6e176cceed
commit
11a86d9169
@ -14,8 +14,6 @@ dependencies {
|
|||||||
implementation project(':play-services-nearby-core')
|
implementation project(':play-services-nearby-core')
|
||||||
implementation project(':play-services-base-core-ui')
|
implementation project(':play-services-base-core-ui')
|
||||||
|
|
||||||
implementation "com.diogobernardino:williamchart:3.7.1"
|
|
||||||
|
|
||||||
// AndroidX UI
|
// AndroidX UI
|
||||||
implementation "androidx.multidex:multidex:$multidexVersion"
|
implementation "androidx.multidex:multidex:$multidexVersion"
|
||||||
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||||
|
@ -7,8 +7,6 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="org.microg.gms.nearby.core.ui">
|
package="org.microg.gms.nearby.core.ui">
|
||||||
|
|
||||||
<uses-sdk tools:overrideLibrary="com.db.williamchart" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
|
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.microg.gms.nearby.core.ui
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.PreferenceViewHolder
|
|
||||||
import com.db.williamchart.data.Scale
|
|
||||||
import com.db.williamchart.view.BarChartView
|
|
||||||
|
|
||||||
class BarChartPreference : Preference {
|
|
||||||
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
|
|
||||||
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
|
||||||
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
|
|
||||||
constructor(context: Context?) : super(context)
|
|
||||||
|
|
||||||
init {
|
|
||||||
layoutResource = R.layout.preference_bar_chart
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var chart: BarChartView
|
|
||||||
var labelsFormatter: (Float) -> String = { it.toString() }
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
if (this::chart.isInitialized) {
|
|
||||||
chart.labelsFormatter = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var scale: Scale? = null
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
if (value != null && this::chart.isInitialized) {
|
|
||||||
chart.scale = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var data: LinkedHashMap<String, Float> = linkedMapOf()
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
if (this::chart.isInitialized) {
|
|
||||||
chart.animate(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
|
||||||
super.onBindViewHolder(holder)
|
|
||||||
chart = holder.itemView as? BarChartView ?: holder.findViewById(R.id.bar_chart) as BarChartView
|
|
||||||
chart.labelsFormatter = labelsFormatter
|
|
||||||
scale?.let { chart.scale = it }
|
|
||||||
chart.animate(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.gms.nearby.core.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceViewHolder
|
||||||
|
import org.microg.gms.nearby.exposurenotification.ExposureScanSummary
|
||||||
|
|
||||||
|
class DotChartPreference : Preference {
|
||||||
|
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
|
||||||
|
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||||
|
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
|
||||||
|
constructor(context: Context?) : super(context)
|
||||||
|
|
||||||
|
init {
|
||||||
|
layoutResource = R.layout.preference_dot_chart
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var chart: DotChartView
|
||||||
|
var data: Set<ExposureScanSummary> = emptySet()
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
if (this::chart.isInitialized) {
|
||||||
|
chart.data = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||||
|
super.onBindViewHolder(holder)
|
||||||
|
chart = holder.itemView as? DotChartView ?: holder.findViewById(R.id.dot_chart) as DotChartView
|
||||||
|
chart.data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.gms.nearby.core.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.TypedArray
|
||||||
|
import android.graphics.*
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.View
|
||||||
|
import org.microg.gms.nearby.exposurenotification.ExposureScanSummary
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
|
||||||
|
class DotChartView : View {
|
||||||
|
@TargetApi(21)
|
||||||
|
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
|
||||||
|
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||||
|
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
|
||||||
|
constructor(context: Context?) : super(context)
|
||||||
|
|
||||||
|
var data: Set<ExposureScanSummary>? = null
|
||||||
|
@SuppressLint("SimpleDateFormat")
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
val displayData = hashMapOf<Int, Pair<String, MutableMap<Int, Int>>>()
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val min = now - 14 * 24 * 60 * 60 * 1000L
|
||||||
|
val date = Date(min)
|
||||||
|
val format = Settings.System.getString(context.contentResolver, Settings.System.DATE_FORMAT);
|
||||||
|
val dateFormat = if (TextUtils.isEmpty(format)) {
|
||||||
|
android.text.format.DateFormat.getMediumDateFormat(context)
|
||||||
|
} else {
|
||||||
|
SimpleDateFormat(format)
|
||||||
|
}
|
||||||
|
val lowest = dateFormat.parse(dateFormat.format(date))?.time ?: date.time
|
||||||
|
for (day in 0 until 15) {
|
||||||
|
date.time = now - (14 - day) * 24 * 60 * 60 * 1000L
|
||||||
|
displayData[day] = dateFormat.format(date) to hashMapOf()
|
||||||
|
}
|
||||||
|
if (value != null) {
|
||||||
|
for (summary in value) {
|
||||||
|
val off = summary.time - lowest
|
||||||
|
if (off < 0) continue
|
||||||
|
val totalHours = (off / 1000 / 60 / 60).toInt()
|
||||||
|
val day = totalHours / 24
|
||||||
|
val hour = totalHours % 24
|
||||||
|
displayData[day]?.second?.set(hour, (displayData[day]?.second?.get(hour) ?: 0) + summary.rpis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (hour in 0..((min-lowest)/1000/60/60).toInt()) {
|
||||||
|
displayData[0]?.second?.set(hour, displayData[0]?.second?.get(hour) ?: -1)
|
||||||
|
}
|
||||||
|
for (hour in ((min-lowest)/1000/60/60).toInt() until 24) {
|
||||||
|
displayData[14]?.second?.set(hour, displayData[14]?.second?.get(hour) ?: -1)
|
||||||
|
}
|
||||||
|
this.displayData = displayData
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var displayData: Map<Int, Pair<String, Map<Int, Int>>> = emptyMap()
|
||||||
|
private val paint = Paint()
|
||||||
|
private val tempRect = Rect()
|
||||||
|
private val tempRectF = RectF()
|
||||||
|
|
||||||
|
private fun fetchAccentColor(): Int {
|
||||||
|
val typedValue = TypedValue()
|
||||||
|
val a: TypedArray = context.obtainStyledAttributes(typedValue.data, intArrayOf(androidx.appcompat.R.attr.colorAccent))
|
||||||
|
val color = a.getColor(0, 0)
|
||||||
|
a.recycle()
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
if (data == null) data = emptySet()
|
||||||
|
paint.textSize = 10 * resources.displayMetrics.scaledDensity
|
||||||
|
paint.isAntiAlias = true
|
||||||
|
paint.strokeWidth = 2f
|
||||||
|
var maxTextWidth = 0
|
||||||
|
var maxTextHeight = 0
|
||||||
|
for (dateString in displayData.values.map { it.first }) {
|
||||||
|
paint.getTextBounds(dateString, 0, dateString.length, tempRect)
|
||||||
|
maxTextWidth = max(maxTextWidth, tempRect.width())
|
||||||
|
maxTextHeight = max(maxTextHeight, tempRect.height())
|
||||||
|
}
|
||||||
|
|
||||||
|
val legendLeft = maxTextWidth + 4 * resources.displayMetrics.scaledDensity
|
||||||
|
val legendBottom = maxTextHeight + 4 * resources.displayMetrics.scaledDensity
|
||||||
|
|
||||||
|
val distHeight = (height - 28 - paddingTop - paddingBottom - legendBottom).toDouble()
|
||||||
|
val distWidth = (width - 46 - paddingLeft - paddingRight - legendLeft).toDouble()
|
||||||
|
val perHeight = distHeight / 15.0
|
||||||
|
val perWidth = distWidth / 24.0
|
||||||
|
|
||||||
|
paint.textAlign = Paint.Align.RIGHT
|
||||||
|
val maxValue = displayData.values.mapNotNull { it.second.values.maxOrNull() }.maxOrNull() ?: 0
|
||||||
|
val accentColor = fetchAccentColor()
|
||||||
|
val accentRed = Color.red(accentColor)
|
||||||
|
val accentGreen = Color.green(accentColor)
|
||||||
|
val accentBlue = Color.blue(accentColor)
|
||||||
|
for (day in 0 until 15) {
|
||||||
|
val (dateString, hours) = displayData[day] ?: "" to emptyMap()
|
||||||
|
val top = day * (perHeight + 2) + paddingTop
|
||||||
|
if (day % 2 == 0) {
|
||||||
|
paint.setARGB(255, 100, 100, 100)
|
||||||
|
canvas.drawText(dateString, (paddingLeft + legendLeft - 4 * resources.displayMetrics.scaledDensity), (top + perHeight / 2.0 + maxTextHeight / 2.0).toFloat(), paint)
|
||||||
|
}
|
||||||
|
for (hour in 0 until 24) {
|
||||||
|
val value = hours[hour] ?: 0 // TODO: Actually allow null to display offline state as soon as we properly record it
|
||||||
|
val left = hour * (perWidth + 2) + paddingLeft + legendLeft
|
||||||
|
tempRectF.set(left.toFloat() + 2f, top.toFloat() + 2f, (left + perWidth).toFloat() - 2f, (top + perHeight).toFloat() - 2f)
|
||||||
|
when {
|
||||||
|
value == null -> {
|
||||||
|
paint.style = Paint.Style.FILL_AND_STROKE
|
||||||
|
paint.setARGB(30, 100, 100, 100)
|
||||||
|
canvas.drawRoundRect(tempRectF, 2f, 2f, paint)
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
}
|
||||||
|
maxValue == 0 -> {
|
||||||
|
paint.setARGB(50, accentRed, accentGreen, accentBlue)
|
||||||
|
paint.style = Paint.Style.STROKE
|
||||||
|
canvas.drawRoundRect(tempRectF, 2f, 2f, paint)
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
}
|
||||||
|
value >= 0 -> {
|
||||||
|
val alpha = ((value.toDouble() / maxValue.toDouble()) * 255).toInt()
|
||||||
|
paint.setARGB(max(50, alpha), accentRed, accentGreen, accentBlue)
|
||||||
|
paint.style = Paint.Style.STROKE
|
||||||
|
canvas.drawRoundRect(tempRectF, 2f, 2f, paint)
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
paint.setARGB(alpha, accentRed, accentGreen, accentBlue)
|
||||||
|
canvas.drawRoundRect(tempRectF, 2f, 2f, paint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val legendTop = 15 * (perHeight + 2) + paddingTop + maxTextHeight + 4 * resources.displayMetrics.scaledDensity
|
||||||
|
paint.textAlign = Paint.Align.CENTER
|
||||||
|
paint.setARGB(255, 100, 100, 100)
|
||||||
|
for (hour in 0 until 24) {
|
||||||
|
if (hour % 3 == 0) {
|
||||||
|
val left = hour * (perWidth + 2) + paddingLeft + legendLeft + perWidth / 2.0
|
||||||
|
canvas.drawText("${hour}:00", left.toFloat(), legendTop.toFloat(), paint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,22 +7,17 @@ package org.microg.gms.nearby.core.ui
|
|||||||
|
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.format.DateFormat
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceCategory
|
import androidx.preference.PreferenceCategory
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import com.db.williamchart.data.Scale
|
|
||||||
import org.microg.gms.nearby.exposurenotification.ExposureDatabase
|
import org.microg.gms.nearby.exposurenotification.ExposureDatabase
|
||||||
import java.util.*
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import kotlin.math.roundToLong
|
|
||||||
|
|
||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() {
|
class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() {
|
||||||
private lateinit var histogramCategory: PreferenceCategory
|
private lateinit var histogramCategory: PreferenceCategory
|
||||||
private lateinit var histogram: BarChartPreference
|
private lateinit var histogram: DotChartPreference
|
||||||
private lateinit var deleteAll: Preference
|
private lateinit var deleteAll: Preference
|
||||||
private lateinit var exportDb: Preference
|
private lateinit var exportDb: Preference
|
||||||
|
|
||||||
@ -63,37 +58,11 @@ class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() {
|
|||||||
|
|
||||||
fun updateChart() {
|
fun updateChart() {
|
||||||
lifecycleScope.launchWhenResumed {
|
lifecycleScope.launchWhenResumed {
|
||||||
val (totalRpiCount, rpiHistogram) = ExposureDatabase.with(requireContext()) { database ->
|
val rpiHourHistogram = ExposureDatabase.with(requireContext()) { database -> database.rpiHourHistogram }
|
||||||
val map = linkedMapOf<String, Float>()
|
val totalRpiCount = rpiHourHistogram.map { it.rpis }.sum()
|
||||||
val lowestDate = (System.currentTimeMillis() / 24 / 60 / 60 / 1000 - 13).toDouble().roundToLong() * 24 * 60 * 60 * 1000
|
deleteAll.isEnabled = totalRpiCount > 0
|
||||||
for (i in 0..13) {
|
|
||||||
val date = Calendar.getInstance().apply { this.time = Date(lowestDate + i * 24 * 60 * 60 * 1000) }.get(Calendar.DAY_OF_MONTH)
|
|
||||||
val str = when (i) {
|
|
||||||
0, 13 -> DateFormat.format(DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMMd"), lowestDate + i * 24 * 60 * 60 * 1000).toString()
|
|
||||||
else -> IntArray(date).joinToString("").replace("0", "\u200B")
|
|
||||||
}
|
|
||||||
map[str] = 0f
|
|
||||||
}
|
|
||||||
val refDateLow = Calendar.getInstance().apply { this.time = Date(lowestDate) }.get(Calendar.DAY_OF_MONTH)
|
|
||||||
val refDateHigh = Calendar.getInstance().apply { this.time = Date(lowestDate + 13 * 24 * 60 * 60 * 1000) }.get(Calendar.DAY_OF_MONTH)
|
|
||||||
for (entry in database.rpiHistogram) {
|
|
||||||
val time = Date(entry.key * 24 * 60 * 60 * 1000)
|
|
||||||
if (time.time < lowestDate) continue // Ignore old data
|
|
||||||
val date = Calendar.getInstance().apply { this.time = time }.get(Calendar.DAY_OF_MONTH)
|
|
||||||
val str = when (date) {
|
|
||||||
refDateLow, refDateHigh -> DateFormat.format(DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMMd"), entry.key * 24 * 60 * 60 * 1000).toString()
|
|
||||||
else -> IntArray(date).joinToString("").replace("0", "\u200B")
|
|
||||||
}
|
|
||||||
map[str] = entry.value.toFloat()
|
|
||||||
}
|
|
||||||
val totalRpiCount = database.totalRpiCount
|
|
||||||
totalRpiCount to map
|
|
||||||
}
|
|
||||||
deleteAll.isEnabled = totalRpiCount != 0L
|
|
||||||
histogramCategory.title = getString(R.string.prefcat_exposure_rpis_histogram_title, totalRpiCount)
|
histogramCategory.title = getString(R.string.prefcat_exposure_rpis_histogram_title, totalRpiCount)
|
||||||
histogram.labelsFormatter = { it.roundToInt().toString() }
|
histogram.data = rpiHourHistogram
|
||||||
histogram.scale = Scale(0f, rpiHistogram.values.max()?.coerceAtLeast(0.1f) ?: 0.1f)
|
|
||||||
histogram.data = rpiHistogram
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
~ SPDX-FileCopyrightText: 2020, microG Project Team
|
|
||||||
~ SPDX-License-Identifier: Apache-2.0
|
|
||||||
-->
|
|
||||||
|
|
||||||
<com.db.williamchart.view.BarChartView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/bar_chart"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="180dp"
|
|
||||||
android:padding="16dp"
|
|
||||||
app:chart_barsColor="?attr/colorAccent"
|
|
||||||
app:chart_barsRadius="3dp"
|
|
||||||
app:chart_labelsColor="?android:attr/textColorSecondary"
|
|
||||||
app:chart_labelsSize="14sp"
|
|
||||||
app:chart_spacing="6dp" />
|
|
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
~ SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
<org.microg.gms.nearby.core.ui.DotChartView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/dot_chart"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="230dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp" />
|
@ -9,15 +9,16 @@
|
|||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:key="prefcat_exposure_rpi_histogram"
|
android:key="prefcat_exposure_rpi_histogram"
|
||||||
tools:title="@string/prefcat_exposure_rpis_histogram_title">
|
tools:title="@string/prefcat_exposure_rpis_histogram_title">
|
||||||
<org.microg.gms.nearby.core.ui.BarChartPreference
|
<org.microg.gms.nearby.core.ui.DotChartPreference
|
||||||
android:key="pref_exposure_rpi_histogram"
|
android:key="pref_exposure_rpi_histogram"
|
||||||
tools:layout="@layout/preference_bar_chart" />
|
android:selectable="false"
|
||||||
|
tools:layout="@layout/preference_dot_chart" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:layout="@layout/preference_category_no_label">
|
<PreferenceCategory android:layout="@layout/preference_category_no_label">
|
||||||
<Preference
|
<Preference
|
||||||
android:key="pref_exposure_export_database"
|
android:key="pref_exposure_export_database"
|
||||||
android:title="@string/pref_exposure_rpi_export_title"
|
android:summary="@string/pref_exposure_rpi_export_summary"
|
||||||
android:summary="@string/pref_exposure_rpi_export_summary" />
|
android:title="@string/pref_exposure_rpi_export_title" />
|
||||||
<Preference
|
<Preference
|
||||||
android:key="pref_exposure_rpi_delete_all"
|
android:key="pref_exposure_rpi_delete_all"
|
||||||
android:summary="@string/pref_exposure_rpi_delete_all_summary"
|
android:summary="@string/pref_exposure_rpi_delete_all_summary"
|
||||||
|
@ -673,25 +673,14 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val rpiHistogram: Map<Long, Long>
|
val rpiHourHistogram: Set<ExposureScanSummary>
|
||||||
get() = readableDatabase.run {
|
get() = readableDatabase.run {
|
||||||
rawQuery("SELECT round(timestamp/(24*60*60*1000)), COUNT(*) FROM $TABLE_ADVERTISEMENTS WHERE timestamp > ? GROUP BY round(timestamp/(24*60*60*1000)) ORDER BY timestamp ASC;", arrayOf((Date().time - (14 * 24 * 60 * 60 * 1000)).toString())).use { cursor ->
|
rawQuery("SELECT round(timestamp/(60*60*1000))*60*60*1000, COUNT(*), COUNT(*) FROM $TABLE_ADVERTISEMENTS WHERE timestamp > ? GROUP BY round(timestamp/(60*60*1000)) ORDER BY timestamp ASC;", arrayOf((System.currentTimeMillis() - (14 * 24 * 60 * 60 * 1000L)).toString())).use { cursor ->
|
||||||
val map = linkedMapOf<Long, Long>()
|
val set = hashSetOf<ExposureScanSummary>()
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
map[cursor.getLong(0)] = cursor.getLong(1)
|
set.add(ExposureScanSummary(cursor.getLong(0), cursor.getInt(1), cursor.getInt(2)))
|
||||||
}
|
|
||||||
map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val totalRpiCount: Long
|
|
||||||
get() = readableDatabase.run {
|
|
||||||
rawQuery("SELECT COUNT(*) FROM $TABLE_ADVERTISEMENTS WHERE timestamp > ?;", arrayOf((Date().time - (14 * 24 * 60 * 60 * 1000)).toString())).use { cursor ->
|
|
||||||
if (cursor.moveToNext()) {
|
|
||||||
cursor.getLong(0)
|
|
||||||
} else {
|
|
||||||
0L
|
|
||||||
}
|
}
|
||||||
|
set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ import android.util.Log
|
|||||||
import com.google.android.gms.nearby.exposurenotification.*
|
import com.google.android.gms.nearby.exposurenotification.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
data class ExposureScanSummary(val time: Long, val rpis: Int, val records: Int)
|
||||||
|
|
||||||
data class PlainExposure(val rpi: ByteArray, val aem: ByteArray, val timestamp: Long, val duration: Long, val rssi: Int)
|
data class PlainExposure(val rpi: ByteArray, val aem: ByteArray, val timestamp: Long, val duration: Long, val rssi: Int)
|
||||||
|
|
||||||
data class MeasuredExposure(val timestamp: Long, val duration: Long, val rssi: Int, val txPower: Int, @CalibrationConfidence val confidence: Int, val key: TemporaryExposureKey) {
|
data class MeasuredExposure(val timestamp: Long, val duration: Long, val rssi: Int, val txPower: Int, @CalibrationConfidence val confidence: Int, val key: TemporaryExposureKey) {
|
||||||
|
Loading…
Reference in New Issue
Block a user