mirror of
https://github.com/TeamVanced/VancedMicroG
synced 2025-02-02 14:07:32 +01:00
EN API: Only use a single writable database instance
Should fix some issues with older Android versions, #1115
This commit is contained in:
parent
ee176c42cc
commit
f30605b145
@ -47,14 +47,14 @@ class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() {
|
||||
|
||||
fun updateContent() {
|
||||
packageName?.let { packageName ->
|
||||
val database = ExposureDatabase(requireContext())
|
||||
var str = getString(R.string.pref_exposure_app_checks_summary, database.countMethodCalls(packageName, "provideDiagnosisKeys"))
|
||||
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))
|
||||
ExposureDatabase.with(requireContext()) { database ->
|
||||
var str = getString(R.string.pref_exposure_app_checks_summary, database.countMethodCalls(packageName, "provideDiagnosisKeys"))
|
||||
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))
|
||||
}
|
||||
checks.summary = str
|
||||
}
|
||||
checks.summary = str
|
||||
database.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,16 +25,10 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() {
|
||||
private lateinit var exposureAppsNone: Preference
|
||||
private lateinit var collectedRpis: Preference
|
||||
private lateinit var advertisingId: Preference
|
||||
private lateinit var database: ExposureDatabase
|
||||
private val handler = Handler()
|
||||
private val updateStatusRunnable = Runnable { updateStatus() }
|
||||
private val updateContentRunnable = Runnable { updateContent() }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
database = ExposureDatabase(requireContext())
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences_exposure_notifications)
|
||||
}
|
||||
@ -53,13 +47,14 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
updateStatus()
|
||||
updateContent()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
database.close()
|
||||
|
||||
handler.removeCallbacks(updateStatusRunnable)
|
||||
handler.removeCallbacks(updateContentRunnable)
|
||||
}
|
||||
@ -78,26 +73,27 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() {
|
||||
handler.postDelayed(updateContentRunnable, UPDATE_CONTENT_INTERVAL)
|
||||
val context = requireContext()
|
||||
val (apps, lastHourKeys, currentId) = withContext(Dispatchers.IO) {
|
||||
val apps = database.appList.map { packageName ->
|
||||
context.packageManager.getApplicationInfoIfExists(packageName)
|
||||
}.filterNotNull().mapIndexed { idx, applicationInfo ->
|
||||
val pref = AppIconPreference(context)
|
||||
pref.order = idx
|
||||
pref.title = applicationInfo.loadLabel(context.packageManager)
|
||||
pref.icon = applicationInfo.loadIcon(context.packageManager)
|
||||
pref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
findNavController().navigate(R.id.openExposureAppDetails, bundleOf(
|
||||
"package" to applicationInfo.packageName
|
||||
))
|
||||
true
|
||||
ExposureDatabase.with(context) { database ->
|
||||
val apps = database.appList.map { packageName ->
|
||||
context.packageManager.getApplicationInfoIfExists(packageName)
|
||||
}.filterNotNull().mapIndexed { idx, applicationInfo ->
|
||||
val pref = AppIconPreference(context)
|
||||
pref.order = idx
|
||||
pref.title = applicationInfo.loadLabel(context.packageManager)
|
||||
pref.icon = applicationInfo.loadIcon(context.packageManager)
|
||||
pref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
findNavController().navigate(R.id.openExposureAppDetails, bundleOf(
|
||||
"package" to applicationInfo.packageName
|
||||
))
|
||||
true
|
||||
}
|
||||
pref.key = "pref_exposure_app_" + applicationInfo.packageName
|
||||
pref
|
||||
}
|
||||
pref.key = "pref_exposure_app_" + applicationInfo.packageName
|
||||
pref
|
||||
val lastHourKeys = database.hourRpiCount
|
||||
val currentId = database.currentRpiId
|
||||
Triple(apps, lastHourKeys, currentId)
|
||||
}
|
||||
val lastHourKeys = database.hourRpiCount
|
||||
val currentId = database.currentRpiId
|
||||
database.close()
|
||||
Triple(apps, lastHourKeys, currentId)
|
||||
}
|
||||
collectedRpis.summary = getString(R.string.pref_exposure_collected_rpis_summary, lastHourKeys)
|
||||
advertisingId.summary = currentId.toString()
|
||||
|
@ -22,12 +22,6 @@ import kotlin.math.roundToInt
|
||||
class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() {
|
||||
private lateinit var histogramCategory: PreferenceCategory
|
||||
private lateinit var histogram: BarChartPreference
|
||||
private lateinit var database: ExposureDatabase
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
database = ExposureDatabase(requireContext())
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences_exposure_notifications_rpis)
|
||||
@ -43,28 +37,24 @@ class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() {
|
||||
updateChart()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
database.close()
|
||||
}
|
||||
|
||||
fun updateChart() {
|
||||
lifecycleScope.launchWhenResumed {
|
||||
val (totalRpiCount, rpiHistogram) = withContext(Dispatchers.IO) {
|
||||
val map = linkedMapOf<String, Float>()
|
||||
val lowestDate = Math.round((Date().time / 24 / 60 / 60 / 1000 - 13).toDouble()) * 24 * 60 * 60 * 1000
|
||||
for (i in 0..13) {
|
||||
val date = Calendar.getInstance().apply { this.time = Date(lowestDate + i * 24 * 60 * 60 * 1000) }.get(Calendar.DAY_OF_MONTH)
|
||||
map[date.toString()] = 0f
|
||||
ExposureDatabase.with(requireContext()) { database ->
|
||||
val map = linkedMapOf<String, Float>()
|
||||
val lowestDate = Math.round((Date().time / 24 / 60 / 60 / 1000 - 13).toDouble()) * 24 * 60 * 60 * 1000
|
||||
for (i in 0..13) {
|
||||
val date = Calendar.getInstance().apply { this.time = Date(lowestDate + i * 24 * 60 * 60 * 1000) }.get(Calendar.DAY_OF_MONTH)
|
||||
map[date.toString()] = 0f
|
||||
}
|
||||
for (entry in database.rpiHistogram) {
|
||||
val time = Date(entry.key * 24 * 60 * 60 * 1000)
|
||||
val date = Calendar.getInstance().apply { this.time = time }.get(Calendar.DAY_OF_MONTH)
|
||||
map[date.toString()] = entry.value.toFloat()
|
||||
}
|
||||
val totalRpiCount = database.totalRpiCount
|
||||
totalRpiCount to map
|
||||
}
|
||||
for (entry in database.rpiHistogram) {
|
||||
val time = Date(entry.key * 24 * 60 * 60 * 1000)
|
||||
val date = Calendar.getInstance().apply { this.time = time }.get(Calendar.DAY_OF_MONTH)
|
||||
map[date.toString()] = entry.value.toFloat()
|
||||
}
|
||||
val totalRpiCount = database.totalRpiCount
|
||||
database.close()
|
||||
totalRpiCount to map
|
||||
}
|
||||
histogramCategory.title = getString(R.string.prefcat_exposure_rpis_histogram_title, totalRpiCount)
|
||||
histogram.labelsFormatter = { it.roundToInt().toString() }
|
||||
|
@ -13,7 +13,7 @@ fun PackageManager.getApplicationInfoIfExists(packageName: String?, flags: Int =
|
||||
try {
|
||||
getApplicationInfo(it, flags)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Package does not exist", e)
|
||||
Log.w(TAG, "Package $packageName not installed.")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class AdvertiserService : LifecycleService() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
database = ExposureDatabase(this)
|
||||
database = ExposureDatabase.ref(this)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
@ -56,7 +56,7 @@ class AdvertiserService : LifecycleService() {
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
stopAdvertising()
|
||||
database.close()
|
||||
database.unref()
|
||||
}
|
||||
|
||||
fun startAdvertising() {
|
||||
|
@ -24,22 +24,8 @@ import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ExposureDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
||||
private val refCount = AtomicInteger(0)
|
||||
|
||||
fun ref(): ExposureDatabase {
|
||||
refCount.incrementAndGet()
|
||||
return this
|
||||
}
|
||||
|
||||
fun unref() {
|
||||
val nu = refCount.decrementAndGet()
|
||||
if (nu == 0) {
|
||||
close()
|
||||
} else if (nu < 0) {
|
||||
throw RuntimeException("ref/unref mismatch")
|
||||
}
|
||||
}
|
||||
class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
||||
private var refCount = 0
|
||||
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
onUpgrade(db, 0, DB_VERSION)
|
||||
@ -447,6 +433,35 @@ class ExposureDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, nu
|
||||
|
||||
fun generateCurrentPayload(metadata: ByteArray) = currentTemporaryExposureKey.generatePayload(currentIntervalNumber.toInt(), metadata)
|
||||
|
||||
override fun getWritableDatabase(): SQLiteDatabase {
|
||||
if (this != instance) {
|
||||
throw IllegalStateException("Tried to open writable database from secondary instance")
|
||||
}
|
||||
val db = super.getWritableDatabase()
|
||||
db.enableWriteAheadLogging()
|
||||
return db
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
synchronized(Companion) {
|
||||
super.close()
|
||||
instance = null
|
||||
}
|
||||
}
|
||||
|
||||
fun ref(): ExposureDatabase = synchronized(Companion) {
|
||||
refCount++
|
||||
return this
|
||||
}
|
||||
|
||||
fun unref() = synchronized(Companion) {
|
||||
refCount--
|
||||
if (refCount == 0) {
|
||||
close()
|
||||
} else if (refCount < 0) {
|
||||
throw IllegalStateException("ref/unref mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DB_NAME = "exposure.db"
|
||||
@ -457,6 +472,23 @@ class ExposureDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, nu
|
||||
private const val TABLE_TEK_CHECK = "tek_check"
|
||||
private const val TABLE_DIAGNOSIS = "diagnosis"
|
||||
private const val TABLE_CONFIGURATIONS = "configurations"
|
||||
|
||||
private var instance: ExposureDatabase? = null
|
||||
fun ref(context: Context): ExposureDatabase = synchronized(this) {
|
||||
if (instance == null) {
|
||||
instance = ExposureDatabase(context.applicationContext)
|
||||
}
|
||||
instance!!.ref()
|
||||
}
|
||||
|
||||
fun <T> with(context: Context, call: (ExposureDatabase) -> T): T {
|
||||
val it = ref(context)
|
||||
try {
|
||||
return call(it)
|
||||
} finally {
|
||||
it.unref()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,21 +18,6 @@ import org.microg.gms.common.GmsService
|
||||
import org.microg.gms.common.PackageUtils
|
||||
|
||||
class ExposureNotificationService : BaseService(TAG, GmsService.NEARBY_EXPOSURE) {
|
||||
lateinit var database: ExposureDatabase
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
database.unref()
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (!this::database.isInitialized) {
|
||||
database = ExposureDatabase(this)
|
||||
}
|
||||
database.ref()
|
||||
}
|
||||
|
||||
override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) {
|
||||
PackageUtils.getAndCheckCallingPackage(this, request.packageName)
|
||||
|
||||
@ -53,7 +38,7 @@ class ExposureNotificationService : BaseService(TAG, GmsService.NEARBY_EXPOSURE)
|
||||
}
|
||||
|
||||
Log.d(TAG, "handleServiceRequest: " + request.packageName)
|
||||
callback.onPostInitCompleteWithConnectionInfo(SUCCESS, ExposureNotificationServiceImpl(this, request.packageName, database), ConnectionInfo().apply {
|
||||
callback.onPostInitCompleteWithConnectionInfo(SUCCESS, ExposureNotificationServiceImpl(this, request.packageName), ConnectionInfo().apply {
|
||||
features = arrayOf(Feature("nearby_exposure_notification", 2))
|
||||
})
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyProt
|
||||
import java.util.*
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
class ExposureNotificationServiceImpl(private val context: Context, private val packageName: String, private val database: ExposureDatabase) : INearbyExposureNotificationService.Stub() {
|
||||
class ExposureNotificationServiceImpl(private val context: Context, private val packageName: String) : INearbyExposureNotificationService.Stub() {
|
||||
private fun confirm(action: String, callback: (resultCode: Int, resultData: Bundle?) -> Unit) {
|
||||
val intent = Intent(ACTION_CONFIRM)
|
||||
intent.`package` = context.packageName
|
||||
@ -59,9 +59,11 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
if (resultCode == SUCCESS) {
|
||||
ExposurePreferences(context).scannerEnabled = true
|
||||
}
|
||||
database.noteAppAction(packageName, "start", JSONObject().apply {
|
||||
put("result", resultCode)
|
||||
}.toString())
|
||||
ExposureDatabase.with(context) { database ->
|
||||
database.noteAppAction(packageName, "start", JSONObject().apply {
|
||||
put("result", resultCode)
|
||||
}.toString())
|
||||
}
|
||||
try {
|
||||
params.callback.onResult(Status(if (resultCode == SUCCESS) SUCCESS else FAILED_REJECTED_OPT_IN, resultData?.getString("message")))
|
||||
} catch (e: Exception) {
|
||||
@ -75,9 +77,11 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
if (resultCode == SUCCESS) {
|
||||
ExposurePreferences(context).scannerEnabled = false
|
||||
}
|
||||
database.noteAppAction(packageName, "stop", JSONObject().apply {
|
||||
put("result", resultCode)
|
||||
}.toString())
|
||||
ExposureDatabase.with(context) { database ->
|
||||
database.noteAppAction(packageName, "stop", JSONObject().apply {
|
||||
put("result", resultCode)
|
||||
}.toString())
|
||||
}
|
||||
try {
|
||||
params.callback.onResult(Status.SUCCESS)
|
||||
} catch (e: Exception) {
|
||||
@ -94,7 +98,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTemporaryExposureKeyHistory(params: GetTemporaryExposureKeyHistoryParams) {
|
||||
override fun getTemporaryExposureKeyHistory(params: GetTemporaryExposureKeyHistoryParams): Unit = ExposureDatabase.with(context) { database ->
|
||||
confirm(CONFIRM_ACTION_START) { resultCode, resultData ->
|
||||
val (status, response) = if (resultCode == SUCCESS) {
|
||||
SUCCESS to database.allKeys
|
||||
@ -122,7 +126,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
.setTransmissionRiskLevel(transmission_risk_level ?: 0)
|
||||
.build()
|
||||
|
||||
private fun storeDiagnosisKeyExport(token: String, export: TemporaryExposureKeyExport): Int {
|
||||
private fun storeDiagnosisKeyExport(token: String, export: TemporaryExposureKeyExport): Int = ExposureDatabase.with(context) { database ->
|
||||
Log.d(TAG, "Importing keys from file ${export.start_timestamp?.let { Date(it * 1000) }} to ${export.end_timestamp?.let { Date(it * 1000) }}")
|
||||
for (key in export.keys) {
|
||||
database.storeDiagnosisKey(packageName, token, key.toKey())
|
||||
@ -130,13 +134,12 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
for (key in export.revised_keys) {
|
||||
database.updateDiagnosisKey(packageName, token, key.toKey())
|
||||
}
|
||||
return export.keys.size + export.revised_keys.size
|
||||
export.keys.size + export.revised_keys.size
|
||||
}
|
||||
|
||||
override fun provideDiagnosisKeys(params: ProvideDiagnosisKeysParams) {
|
||||
database.ref()
|
||||
Thread(Runnable {
|
||||
try {
|
||||
ExposureDatabase.with(context) { database ->
|
||||
if (params.configuration != null) {
|
||||
database.storeConfiguration(packageName, params.token, params.configuration)
|
||||
}
|
||||
@ -205,13 +208,11 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Callback failed", e)
|
||||
}
|
||||
} finally {
|
||||
database.unref()
|
||||
}
|
||||
}).start()
|
||||
}
|
||||
|
||||
override fun getExposureSummary(params: GetExposureSummaryParams) {
|
||||
override fun getExposureSummary(params: GetExposureSummaryParams): Unit = ExposureDatabase.with(context) { database ->
|
||||
val configuration = database.loadConfiguration(packageName, params.token)
|
||||
if (configuration == null) {
|
||||
try {
|
||||
@ -219,7 +220,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Callback failed", e)
|
||||
}
|
||||
return
|
||||
return@with
|
||||
}
|
||||
val exposures = database.findAllMeasuredExposures(packageName, params.token)
|
||||
val response = ExposureSummary.ExposureSummaryBuilder()
|
||||
@ -251,7 +252,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
}
|
||||
}
|
||||
|
||||
override fun getExposureInformation(params: GetExposureInformationParams) {
|
||||
override fun getExposureInformation(params: GetExposureInformationParams): Unit = ExposureDatabase.with(context) { database ->
|
||||
// TODO: Notify user?
|
||||
val configuration = database.loadConfiguration(packageName, params.token)
|
||||
if (configuration == null) {
|
||||
@ -260,7 +261,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Callback failed", e)
|
||||
}
|
||||
return
|
||||
return@with
|
||||
}
|
||||
val response = database.findAllMeasuredExposures(packageName, params.token).map {
|
||||
it.toExposureInformation(configuration)
|
||||
|
@ -15,12 +15,12 @@ import android.os.IBinder
|
||||
@TargetApi(21)
|
||||
class ScannerService : Service() {
|
||||
private var started = false
|
||||
private lateinit var db: ExposureDatabase
|
||||
private lateinit var database: ExposureDatabase
|
||||
private val callback = object : ScanCallback() {
|
||||
override fun onScanResult(callbackType: Int, result: ScanResult?) {
|
||||
val data = result?.scanRecord?.serviceData?.get(SERVICE_UUID) ?: return
|
||||
if (data.size < 16) return // Ignore invalid advertisements
|
||||
db.noteAdvertisement(data.sliceArray(0..15), data.drop(16).toByteArray(), result.rssi)
|
||||
database.noteAdvertisement(data.sliceArray(0..15), data.drop(16).toByteArray(), result.rssi)
|
||||
}
|
||||
}
|
||||
private val scanner: BluetoothLeScanner
|
||||
@ -36,9 +36,15 @@ class ScannerService : Service() {
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
database = ExposureDatabase.ref(this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
stopScan()
|
||||
database.unref()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
@ -48,7 +54,6 @@ class ScannerService : Service() {
|
||||
@Synchronized
|
||||
private fun startScan() {
|
||||
if (started) return
|
||||
db = ExposureDatabase(this)
|
||||
scanner.startScan(
|
||||
listOf(ScanFilter.Builder().setServiceUuid(SERVICE_UUID).setServiceData(SERVICE_UUID, byteArrayOf(0), byteArrayOf(0)).build()),
|
||||
ScanSettings.Builder().build(),
|
||||
@ -61,7 +66,6 @@ class ScannerService : Service() {
|
||||
private fun stopScan() {
|
||||
if (!started) return
|
||||
scanner.stopScan(callback)
|
||||
db.close()
|
||||
started = false
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user