From 0388917350db24b3535000f91291d43bdf92bc31 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 11 Aug 2020 22:33:30 +0200 Subject: [PATCH 01/24] Add more stubs for measurement API --- .../gms/measurement/internal/AppMetadata.aidl | 3 ++ .../ConditionalUserPropertyParcel.aidl | 3 ++ .../gms/measurement/internal/EventParcel.aidl | 3 ++ .../internal/IMeasurementService.aidl | 24 +++++++++++++-- .../internal/UserAttributeParcel.aidl | 3 ++ .../gms/measurement/internal/AppMetadata.java | 19 ++++++++++++ .../ConditionalUserPropertyParcel.java | 12 ++++++++ .../gms/measurement/internal/EventParcel.java | 12 ++++++++ .../internal/UserAttributeParcel.java | 12 ++++++++ .../measurement/MeasurementServiceImpl.java | 29 +++++++++++++++++++ 10 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/AppMetadata.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/ConditionalUserPropertyParcel.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/EventParcel.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/UserAttributeParcel.aidl create mode 100644 play-services-api/src/main/java/com/google/android/gms/measurement/internal/AppMetadata.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/measurement/internal/ConditionalUserPropertyParcel.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/measurement/internal/EventParcel.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/measurement/internal/UserAttributeParcel.java diff --git a/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/AppMetadata.aidl b/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/AppMetadata.aidl new file mode 100644 index 00000000..005edc39 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/AppMetadata.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.measurement.internal; + +parcelable AppMetadata; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/ConditionalUserPropertyParcel.aidl b/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/ConditionalUserPropertyParcel.aidl new file mode 100644 index 00000000..de59ae57 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/ConditionalUserPropertyParcel.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.measurement.internal; + +parcelable ConditionalUserPropertyParcel; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/EventParcel.aidl b/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/EventParcel.aidl new file mode 100644 index 00000000..372d66f6 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/EventParcel.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.measurement.internal; + +parcelable EventParcel; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/IMeasurementService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/IMeasurementService.aidl index 692d414a..8a99173a 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/IMeasurementService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/IMeasurementService.aidl @@ -1,5 +1,25 @@ package com.google.android.gms.measurement.internal; -interface IMeasurementService { +import com.google.android.gms.measurement.internal.AppMetadata; +import com.google.android.gms.measurement.internal.ConditionalUserPropertyParcel; +import com.google.android.gms.measurement.internal.EventParcel; -} \ No newline at end of file +interface IMeasurementService { + void f1(in EventParcel p0, in AppMetadata p1) = 0; +// void zza(UserAttributeParcel p0, AppMetadata p1) = 1; + void f4(in AppMetadata p0) = 3; +// void zza(EventParcel p0, String p1, String p2) = 4; +// void zzb(AppMetadata p0) = 5; +// List zza(AppMetadata p0, boolean p1) = 6; +// byte[] zza(EventParcel p0, String p1) = 8; + void f10(long p0, String p1, String p2, String p3) = 9; + String f11(in AppMetadata p0) = 10; + void f12(in ConditionalUserPropertyParcel p0, in AppMetadata p1) = 11; +// void zza(ConditionalUserPropertyParcel p0) = 12; +// List zza(String p0, String p1, boolean p2, AppMetadata p3) = 13; +// List zza(String p0, String p1, String p2, boolean p3) = 14; +// List zza(String p0, String p1, AppMetadata p2) = 15; +// List zza(String p0, String p1, String p2) = 16; +// void zzd(AppMetadata p0) = 17; +// void zza(Bundle p0, AppMetadata p1) = 18; +} diff --git a/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/UserAttributeParcel.aidl b/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/UserAttributeParcel.aidl new file mode 100644 index 00000000..efb3b721 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/UserAttributeParcel.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.measurement.internal; + +parcelable UserAttributeParcel; diff --git a/play-services-api/src/main/java/com/google/android/gms/measurement/internal/AppMetadata.java b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/AppMetadata.java new file mode 100644 index 00000000..754f31b5 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/AppMetadata.java @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.measurement.internal; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class AppMetadata extends AutoSafeParcelable { + @Field(2) + public String packageName; + @Field(4) + public String versionName; + @Field(11) + public long versionCode; + + public static final Creator CREATOR = new AutoCreator<>(AppMetadata.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/measurement/internal/ConditionalUserPropertyParcel.java b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/ConditionalUserPropertyParcel.java new file mode 100644 index 00000000..e0594ca8 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/ConditionalUserPropertyParcel.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.measurement.internal; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class ConditionalUserPropertyParcel extends AutoSafeParcelable { + public static final Creator CREATOR = new AutoCreator<>(ConditionalUserPropertyParcel.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/measurement/internal/EventParcel.java b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/EventParcel.java new file mode 100644 index 00000000..3ff7c796 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/EventParcel.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.measurement.internal; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class EventParcel extends AutoSafeParcelable { + public static final Creator CREATOR = new AutoCreator<>(EventParcel.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/measurement/internal/UserAttributeParcel.java b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/UserAttributeParcel.java new file mode 100644 index 00000000..6070b923 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/UserAttributeParcel.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.measurement.internal; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class UserAttributeParcel extends AutoSafeParcelable { + public static final Creator CREATOR = new AutoCreator<>(UserAttributeParcel.class); +} diff --git a/play-services-core/src/main/java/org/microg/gms/measurement/MeasurementServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/measurement/MeasurementServiceImpl.java index c78851b0..b6fbb62b 100644 --- a/play-services-core/src/main/java/org/microg/gms/measurement/MeasurementServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/measurement/MeasurementServiceImpl.java @@ -20,6 +20,9 @@ import android.os.Parcel; import android.os.RemoteException; import android.util.Log; +import com.google.android.gms.measurement.internal.AppMetadata; +import com.google.android.gms.measurement.internal.ConditionalUserPropertyParcel; +import com.google.android.gms.measurement.internal.EventParcel; import com.google.android.gms.measurement.internal.IMeasurementService; public class MeasurementServiceImpl extends IMeasurementService.Stub { @@ -31,4 +34,30 @@ public class MeasurementServiceImpl extends IMeasurementService.Stub { Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags); return false; } + + @Override + public void f1(EventParcel p0, AppMetadata p1) throws RemoteException { + Log.d(TAG, "f1: " + p1.packageName); + } + + @Override + public void f4(AppMetadata p0) throws RemoteException { + Log.d(TAG, "f4: " + p0.packageName); + } + + @Override + public void f10(long p0, String p1, String p2, String p3) throws RemoteException { + Log.d(TAG, "f10: " + p1); + } + + @Override + public String f11(AppMetadata p0) throws RemoteException { + Log.d(TAG, "f11: " + p0.packageName); + return null; + } + + @Override + public void f12(ConditionalUserPropertyParcel p0, AppMetadata p1) throws RemoteException { + Log.d(TAG, "f12: " + p1.packageName); + } } From df5ad9f3de16d33c0bc3c88713c8b167c5aba223 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 11 Aug 2020 22:34:04 +0200 Subject: [PATCH 02/24] Update EN API --- .../org/microg/gms/ui/BarChartPreference.kt | 1 - .../ui/ExposureNotificationsRpisFragment.kt | 17 +- .../main/res/layout/preference_bar_chart.xml | 4 +- .../ExposureConfiguration.java | 5 +- .../ExposureInformation.java | 5 +- .../ExposureNotificationStatusCodes.java | 5 +- .../exposurenotification/ExposureSummary.java | 5 +- .../exposurenotification/RiskLevel.java | 5 +- .../TemporaryExposureKey.java | 5 +- .../exposurenotification/AdvertiserService.kt | 28 +- .../nearby/exposurenotification/Constants.kt | 25 ++ .../nearby/exposurenotification/DeviceInfo.kt | 362 +++++++++++++++++- .../ExposureNotificationService.kt | 2 +- .../ExposureNotificationServiceImpl.kt | 6 + .../exposurenotification/ScannerService.kt | 10 +- .../com/google/android/gms/nearby/Nearby.java | 5 +- .../ExposureNotificationClient.java | 5 +- 17 files changed, 469 insertions(+), 26 deletions(-) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/BarChartPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/BarChartPreference.kt index 368cf457..ec078295 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/BarChartPreference.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/BarChartPreference.kt @@ -7,7 +7,6 @@ package org.microg.gms.ui import android.content.Context import android.util.AttributeSet -import android.util.Log import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import com.db.williamchart.data.Scale diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsRpisFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsRpisFragment.kt index 70ff7b54..9f9270ba 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsRpisFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsRpisFragment.kt @@ -6,7 +6,10 @@ package org.microg.gms.ui import android.annotation.TargetApi +import android.icu.text.DateFormat.getDateInstance import android.os.Bundle +import android.text.format.DateFormat +import android.text.format.DateUtils import androidx.lifecycle.lifecycleScope import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat @@ -45,12 +48,22 @@ class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() { 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 + 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) val date = Calendar.getInstance().apply { this.time = time }.get(Calendar.DAY_OF_MONTH) - map[date.toString()] = entry.value.toFloat() + 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 diff --git a/play-services-core/src/main/res/layout/preference_bar_chart.xml b/play-services-core/src/main/res/layout/preference_bar_chart.xml index 768cc86d..51b45d33 100644 --- a/play-services-core/src/main/res/layout/preference_bar_chart.xml +++ b/play-services-core/src/main/res/layout/preference_bar_chart.xml @@ -12,7 +12,7 @@ android:layout_height="180dp" android:padding="16dp" app:chart_barsColor="?attr/colorAccent" - app:chart_barsRadius="5dp" + app:chart_barsRadius="3dp" app:chart_labelsColor="?android:attr/textColorSecondary" app:chart_labelsSize="14sp" - app:chart_spacing="10dp" /> + app:chart_spacing="6dp" /> diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureConfiguration.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureConfiguration.java index def18cdb..5fe7f650 100644 --- a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureConfiguration.java +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureConfiguration.java @@ -1,6 +1,9 @@ /* * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 + * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.nearby.exposurenotification; diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureInformation.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureInformation.java index 39d5eba2..8157ce64 100644 --- a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureInformation.java +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureInformation.java @@ -1,6 +1,9 @@ /* * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 + * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.nearby.exposurenotification; diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureNotificationStatusCodes.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureNotificationStatusCodes.java index 6b3f3735..4efe5508 100644 --- a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureNotificationStatusCodes.java +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureNotificationStatusCodes.java @@ -1,6 +1,9 @@ /* * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 + * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.nearby.exposurenotification; diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureSummary.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureSummary.java index 1d9d3139..f1fb086a 100644 --- a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureSummary.java +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureSummary.java @@ -1,6 +1,9 @@ /* * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 + * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.nearby.exposurenotification; diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/RiskLevel.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/RiskLevel.java index 08a433e9..6e7c288d 100644 --- a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/RiskLevel.java +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/RiskLevel.java @@ -1,6 +1,9 @@ /* * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 + * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.nearby.exposurenotification; diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/TemporaryExposureKey.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/TemporaryExposureKey.java index 31e5f0c6..96243d30 100644 --- a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/TemporaryExposureKey.java +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/TemporaryExposureKey.java @@ -1,6 +1,9 @@ /* * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 + * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.nearby.exposurenotification; diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt index 7f2858da..08501bf0 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt @@ -10,17 +10,21 @@ import android.bluetooth.BluetoothAdapter import android.bluetooth.le.* import android.bluetooth.le.AdvertiseSettings.* import android.content.Intent +import android.util.Log import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.delay import java.io.FileDescriptor import java.io.PrintWriter +import java.nio.ByteBuffer +import java.util.* import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine @TargetApi(21) class AdvertiserService : LifecycleService() { + private val version = VERSION_1_0 private var callback: AdvertiseCallback? = null private val advertiser: BluetoothLeAdvertiser get() = BluetoothAdapter.getDefaultAdapter().bluetoothLeAdvertiser @@ -62,12 +66,22 @@ class AdvertiserService : LifecycleService() { fun startAdvertising() { lifecycleScope.launchWhenStarted { do { - val payload = database.generateCurrentPayload(byteArrayOf( - 0x40, // Version 1.0 - currentDeviceInfo.txPowerCorrection.toByte(), // TX Power (TODO) - 0x00, // Reserved - 0x00 // Reserved - )) + val aem = when (version) { + VERSION_1_0 -> byteArrayOf( + version, // Version and flags + (currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power + 0x00, // Reserved + 0x00 // Reserved + ) + VERSION_1_1 -> byteArrayOf( + (version + currentDeviceInfo.confidence * 4).toByte(), // Version and flags + (currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power + 0x00, // Reserved + 0x00 // Reserved + ) + else -> return@launchWhenStarted + } + val payload = database.generateCurrentPayload(aem) var nextSend = nextKeyMillis.coerceAtMost(180000) startAdvertising(payload, nextSend.toInt()) delay(nextSend) @@ -84,6 +98,8 @@ class AdvertiserService : LifecycleService() { .setTxPowerLevel(ADVERTISE_TX_POWER_MEDIUM) .setConnectable(false) .build() + val (uuid, aem) = ByteBuffer.wrap(bytes).let { UUID(it.long, it.long) to it.int } + Log.d(TAG, "RPI: $uuid, Version: 0x${version.toString(16)}, TX Power: ${currentDeviceInfo.txPowerCorrection + TX_POWER_LOW}, AEM: 0x${aem.toLong().let { if (it < 0) 0x100000000L + it else it }.toString(16)}, Timeout: ${nextSend}ms") callback = advertiser.startAdvertising(settings, data) } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt index 5c085a96..d41b53bb 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt @@ -27,3 +27,28 @@ const val CONFIRM_ACTION_STOP = "stop" const val CONFIRM_ACTION_KEYS = "keys" const val PERMISSION_EXPOSURE_CALLBACK = "com.google.android.gms.nearby.exposurenotification.EXPOSURE_CALLBACK" + +const val TX_POWER_LOW = -15 + +const val VERSION_1_0: Byte = 0x40 +const val VERSION_1_1: Byte = 0x50 + +/** + * No calibration data, using fleet-wide as default options. + */ +const val CONFIDENCE_LOWEST: Byte = 0 + +/** + * Using average calibration over models from manufacturer. + */ +const val CONFIDENCE_LOW: Byte = 1 + +/** + * Using single-antenna orientation for a similar model. + */ +const val CONFIDENCE_MEDIUM: Byte = 2 + +/** + * Using significant calibration data for this model. + */ +const val CONFIDENCE_HIGH: Byte = 3 diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/DeviceInfo.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/DeviceInfo.kt index 16354294..49d10b13 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/DeviceInfo.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/DeviceInfo.kt @@ -1,12 +1,366 @@ /* * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 + * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package org.microg.gms.nearby.exposurenotification -data class DeviceInfo(val txPowerCorrection: Int, val rssiCorrection: Int) +import android.os.Build +import android.util.Log +import kotlin.math.roundToInt + +data class DeviceInfo(val oem: String, val model: String, val txPowerCorrection: Byte, val rssiCorrection: Byte, val confidence: Byte = CONFIDENCE_MEDIUM) + +private var knownDeviceInfo: DeviceInfo? = null + +fun averageCurrentDeviceInfo(oem: String, model: String, deviceInfos: List, confidence: Byte = CONFIDENCE_LOW): DeviceInfo = + DeviceInfo(oem, model, deviceInfos.map { it.txPowerCorrection }.average().roundToInt().toByte(), deviceInfos.map { it.txPowerCorrection }.average().roundToInt().toByte(), CONFIDENCE_LOW) -// TODO val currentDeviceInfo: DeviceInfo - get() = DeviceInfo(-17, -5) + get() { + var deviceInfo = knownDeviceInfo + if (deviceInfo == null) { + val byOem = allDeviceInfos.filter { it.oem == Build.MANUFACTURER } + val exactMatch = byOem.find { it.model == Build.MODEL } + deviceInfo = when { + exactMatch != null -> { + // Exact match + exactMatch + } + byOem.isNotEmpty() -> { + // Fallback to OEM average + averageCurrentDeviceInfo(Build.MANUFACTURER, Build.MODEL, byOem) + } + else -> { + // Fallback to all device average + averageCurrentDeviceInfo(Build.MANUFACTURER, Build.MODEL, allDeviceInfos, CONFIDENCE_LOWEST) + } + } + Log.d(TAG, "Selected $deviceInfo") + knownDeviceInfo = deviceInfo + } + return deviceInfo + } + +/* + * Derived from en-calibration-2020-06-13.csv published via + * https://developers.google.com/android/exposure-notifications/ble-attenuation-computation#device-list + */ +val allDeviceInfos = listOf( + DeviceInfo("Asus", "ASUS_A001", -1, -22), + DeviceInfo("Asus", "ASUS_X008DC", -8, -19), + DeviceInfo("Asus", "ASUS_X00TD", -8, -29), + DeviceInfo("Asus", "ASUS_X018D", -6, -23), + DeviceInfo("Asus", "ASUS_X01BDA", -5, -26), + DeviceInfo("Asus", "ASUS_Z010D", -8, -17), + DeviceInfo("Asus", "ASUS_Z01FD", -4, -20), + DeviceInfo("Asus", "P027", -7, -23), + DeviceInfo("BlackBerry", "BBE100-4", -4, -24), + DeviceInfo("BlackBerry", "BBF100-6", -2, -26), + DeviceInfo("Blu", "Grand M", -12, -20), + DeviceInfo("Blu", "Studio Mega", -4, -28), + DeviceInfo("Blu", "Tank Xtreme 4.0", -6, -22), + DeviceInfo("Blu", "VIVO 5", -6, -30), + DeviceInfo("Blu", "Vivo ONE", -6, -36), + DeviceInfo("Coolpad", "Coolpad 8722V", -11, -18), + DeviceInfo("Essential Products", "PH-1", -3, -24), + DeviceInfo("Google", "Pixel", -15, -28), + DeviceInfo("Google", "Pixel 3", -4, -34), + DeviceInfo("Google", "Pixel 3a", -5, -39), + DeviceInfo("Google", "Pixel 3a XL", -7, -35), + DeviceInfo("Google", "Pixel 4", 5, -28), + DeviceInfo("Google", "Pixel 4 XL", -3, -26), + DeviceInfo("Google", "Pixel XL", -11, -34), + DeviceInfo("HTC", "2PS64", -2, -41), + DeviceInfo("HTC", "HTC 10", -3, -36), + DeviceInfo("HTC", "HTC Desire 530", -4, -33), + DeviceInfo("HTC", "HTC One A9", 1, -28), + DeviceInfo("HTC", "HTC One M9", 4, -30), + DeviceInfo("HTC", "HTC U11", -6, -26), + DeviceInfo("HTC", "HTC U11 plus", -3, -28), + DeviceInfo("HTC", "HTC U12 life", 5, -32), + DeviceInfo("HTC", "HTC U12+", -3, -29), + DeviceInfo("HTC", "HTC_U-1u", -18, -23), + DeviceInfo("HTC", "HTC_U-3u", -2, -20), + DeviceInfo("Huawei", "ANE-LX3", -6, -7), + DeviceInfo("Huawei", "CLT-L09", -7, -31), + DeviceInfo("Huawei", "COR-L29", -7, -5), + DeviceInfo("Huawei", "DRA-LX3", -6, -18), + DeviceInfo("Huawei", "HUAWEI TIT-AL00", -6, -9), + DeviceInfo("Huawei", "HW-01K", -9, -32), + DeviceInfo("Huawei", "KIW-L24", -1, -23), + DeviceInfo("Huawei", "MHA-L29", -1, -37), + DeviceInfo("Huawei", "NEO-L29", -13, -29), + DeviceInfo("Huawei", "Nexus 6P", -4, -31), + DeviceInfo("Huawei", "PIC-AL00", -7, -9), + DeviceInfo("Huawei", "PRA-LX1", 0, -7), + DeviceInfo("Huawei", "WAS-LX3", 0, -9), + DeviceInfo("Itel", "itel A32F", -1, -25), + DeviceInfo("LGE", "LG-AS110", -7, -24), + DeviceInfo("LGE", "LG-F500L", -13, -24), + DeviceInfo("LGE", "LG-F700L", 6, -31), + DeviceInfo("LGE", "LG-H345", -4, -23), + DeviceInfo("LGE", "LG-H815", -10, -24), + DeviceInfo("LGE", "LG-K100", -7, -19), + DeviceInfo("LGE", "LG-K350", -5, -20), + DeviceInfo("LGE", "LG-K430", -4, -20), + DeviceInfo("LGE", "LG-K500", -2, -20), + DeviceInfo("LGE", "LG-K540", 6, -27), + DeviceInfo("LGE", "LG-M250", 1, -25), + DeviceInfo("LGE", "LG-X230", -7, -25), + DeviceInfo("LGE", "LG-X240", -6, -28), + DeviceInfo("LGE", "LGLK430", -2, -17), + DeviceInfo("LGE", "LGLS450", -2, -21), + DeviceInfo("LGE", "LGM-V300L", -1, -24), + DeviceInfo("LGE", "LGUS991", -11, -25), + DeviceInfo("LGE", "LM-Q910", -8, -24), + DeviceInfo("LGE", "LM-X210", -7, -14), + DeviceInfo("LGE", "Nexus 5X", -15, -17), + DeviceInfo("LGE", "RS988", -2, -2), + DeviceInfo("LGE", "VS988", -14, -6), + DeviceInfo("LGE", "VS995", 2, -3), + DeviceInfo("Lava", "Z50", -6, -27), + DeviceInfo("LeTV", "Le X527", -2, -21), + DeviceInfo("Leagoo", "T5c", -37, -20), + DeviceInfo("Lenovo", "Lenovo A1010a20", -8, -27), + DeviceInfo("Lenovo", "Lenovo A6600d40", -7, -22), + DeviceInfo("Lenovo", "Lenovo K33a42", -13, -19), + DeviceInfo("Lenovo", "Lenovo K520", 7, -31), + DeviceInfo("Lenovo", "Lenovo K52e78", 3, -27), + DeviceInfo("Lenovo", "Lenovo K53a48", 2, -35), + DeviceInfo("Lenovo", "Lenovo K8", -2, -22), + DeviceInfo("Lenovo", "Lenovo K8 Note", -1, -28), + DeviceInfo("Lenovo", "Lenovo L78011", -6, -20), + DeviceInfo("Lenovo", "Lenovo P2a42", -3, -18), + DeviceInfo("Lenovo", "Lenovo TB3-710I", 8, -37), + DeviceInfo("Lenovo", "Lenovo Z90-3", -1, -23), + DeviceInfo("Motorola", "Moto G (4)", -9, -14), + DeviceInfo("Motorola", "Moto G (5)", -6, -23), + DeviceInfo("Motorola", "Moto G (5) Plus", -6, -17), + DeviceInfo("Motorola", "Moto G (5S) Plus", 7, -28), + DeviceInfo("Motorola", "Moto G Play", -3, -23), + DeviceInfo("Motorola", "Moto Z2 Play", -4, -21), + DeviceInfo("Motorola", "MotoE2", -3, 0), + DeviceInfo("Motorola", "MotoG3", 1, -29), + DeviceInfo("Motorola", "Nexus 6", 14, -46), + DeviceInfo("Motorola", "XT1034", -5, 0), + DeviceInfo("Motorola", "XT1080", -1, -31), + DeviceInfo("Motorola", "XT1575", -3, -32), + DeviceInfo("Motorola", "XT1609", 4, -33), + DeviceInfo("Motorola", "XT1635-01", -5, -16), + DeviceInfo("Motorola", "XT1650", -11, -29), + DeviceInfo("Motorola", "XT1706", -4, -23), + DeviceInfo("Motorola", "moto e5 play", -4, -28), + DeviceInfo("Motorola", "moto e5 plus", 4, -22), + DeviceInfo("Motorola", "moto g(6) play", -4, -21), + DeviceInfo("Motorola", "moto g(7)", -6, -20), + DeviceInfo("Motorola", "moto x4", -8, -26), + DeviceInfo("Motorola", "moto z3", -7, -27), + DeviceInfo("Motorola", "moto z4", 0, -37), + DeviceInfo("Motorola", "motorola one", -3, -27), + DeviceInfo("Nokia", "Nokia 1", -3, -30), + DeviceInfo("Nokia", "Nokia 2", -4, -26), + DeviceInfo("Nokia", "Nokia 2.1", -9, -22), + DeviceInfo("Nokia", "Nokia 3.1", -7, -18), + DeviceInfo("Nokia", "Nokia 6.1 Plus", 2, -28), + DeviceInfo("Nokia", "Nokia 7 plus", -1, -33), + DeviceInfo("Nokia", "Nokia 7.1", 0, -33), + DeviceInfo("Nokia", "Nokia 7.2", -3, -7), + DeviceInfo("Nokia", "Nokia 8 Sirocco", 2, -36), + DeviceInfo("Nokia", "TA-1025", 5, -24), + DeviceInfo("Nokia", "TA-1054", -3, -31), + DeviceInfo("OnePlus", "ONEPLUS A3000", -12, -29), + DeviceInfo("OnePlus", "ONEPLUS A5000", 1, -25), + DeviceInfo("OnePlus", "ONEPLUS A5010", -2, -27), + DeviceInfo("OnePlus", "ONEPLUS A6003", 0, -37), + DeviceInfo("OnePlus", "ONEPLUS A6013", -2, -24), + DeviceInfo("Orbic", "RC555L", 2, -27), + DeviceInfo("Razer", "Phone", -3, -33), + DeviceInfo("Razer", "Phone 2", -5, -27), + DeviceInfo("Samsung", "SAMSUNG-SM-G891A", -1, -31), + DeviceInfo("Samsung", "SAMSUNG-SM-G900A", 0, -27), + DeviceInfo("Samsung", "SAMSUNG-SM-G930AZ", 6, -34), + DeviceInfo("Samsung", "SAMSUNG-SM-G935A", 6, -35), + DeviceInfo("Samsung", "SC-02J", 7, -36), + DeviceInfo("Samsung", "SC-02K", 4, -27), + DeviceInfo("Samsung", "SC-02L", -2, -25), + DeviceInfo("Samsung", "SC-03K", 1, -35), + DeviceInfo("Samsung", "SCV33", -1, -28), + DeviceInfo("Samsung", "SCV36", 1, -29), + DeviceInfo("Samsung", "SM-A105FN", -4, -27), + DeviceInfo("Samsung", "SM-A300FU", -3, -18), + DeviceInfo("Samsung", "SM-A320FL", -17, -26), + DeviceInfo("Samsung", "SM-A510F", -6, -2), + DeviceInfo("Samsung", "SM-A510M", -5, 2), + DeviceInfo("Samsung", "SM-A710F", 4, -6), + DeviceInfo("Samsung", "SM-A750GN", -2, -21), + DeviceInfo("Samsung", "SM-A9200", -1, -30), + DeviceInfo("Samsung", "SM-A920F", 0, -32), + DeviceInfo("Samsung", "SM-C5010", -1, -22), + DeviceInfo("Samsung", "SM-C7100", -2, -2), + DeviceInfo("Samsung", "SM-G1650", -2, -26), + DeviceInfo("Samsung", "SM-G532F", -5, -18), + DeviceInfo("Samsung", "SM-G532M", -6, -22), + DeviceInfo("Samsung", "SM-G6000", -3, -20), + DeviceInfo("Samsung", "SM-G610F", -6, -1), + DeviceInfo("Samsung", "SM-G611F", 0, 0), + DeviceInfo("Samsung", "SM-G850F", 0, -28), + DeviceInfo("Samsung", "SM-G8850", -5, -27), + DeviceInfo("Samsung", "SM-G885S", -3, -33), + DeviceInfo("Samsung", "SM-G892A", -5, -16), + DeviceInfo("Samsung", "SM-G900F", 0, -28), + DeviceInfo("Samsung", "SM-G920F", 3, -29), + DeviceInfo("Samsung", "SM-G920I", 4, -25), + DeviceInfo("Samsung", "SM-G920T", 0, -25), + DeviceInfo("Samsung", "SM-G925F", -4, -30), + DeviceInfo("Samsung", "SM-G925I", 4, -21), + DeviceInfo("Samsung", "SM-G930P", 2, -35), + DeviceInfo("Samsung", "SM-G930T", 0, -29), + DeviceInfo("Samsung", "SM-G930V", -4, -36), + DeviceInfo("Samsung", "SM-G930VL", -4, -31), + DeviceInfo("Samsung", "SM-G9350", -5, -35), + DeviceInfo("Samsung", "SM-G935F", 2, -26), + DeviceInfo("Samsung", "SM-G935P", -4, -31), + DeviceInfo("Samsung", "SM-G935R4", 0, -29), + DeviceInfo("Samsung", "SM-G935T", -5, -26), + DeviceInfo("Samsung", "SM-G935V", 6, -34), + DeviceInfo("Samsung", "SM-G950F", -3, -27), + DeviceInfo("Samsung", "SM-G950N", -3, -29), + DeviceInfo("Samsung", "SM-G950U1", -3, -30), + DeviceInfo("Samsung", "SM-G950W", 0, -27), + DeviceInfo("Samsung", "SM-G955F", -1, -28), + DeviceInfo("Samsung", "SM-G955N", 7, -28), + DeviceInfo("Samsung", "SM-G955U", -6, -23), + DeviceInfo("Samsung", "SM-G955U1", -2, -24), + DeviceInfo("Samsung", "SM-G955W", -5, -21), + DeviceInfo("Samsung", "SM-G9600", -2, -28), + DeviceInfo("Samsung", "SM-G960F", 1, -27), + DeviceInfo("Samsung", "SM-G960N", -3, -31), + DeviceInfo("Samsung", "SM-G960U1", -3, -21), + DeviceInfo("Samsung", "SM-G9650", 1, -25), + DeviceInfo("Samsung", "SM-G965F", -3, -25), + DeviceInfo("Samsung", "SM-G965N", -2, -28), + DeviceInfo("Samsung", "SM-G965U1", -3, -31), + DeviceInfo("Samsung", "SM-G975F", -7, -27), + DeviceInfo("Samsung", "SM-G977P", -3, -35), + DeviceInfo("Samsung", "SM-J100VPP", 2, -25), + DeviceInfo("Samsung", "SM-J250F", -6, -27), + DeviceInfo("Samsung", "SM-J260G", -7, -20), + DeviceInfo("Samsung", "SM-J320V", -10, -12), + DeviceInfo("Samsung", "SM-J327U", -1, -23), + DeviceInfo("Samsung", "SM-J330F", -9, -17), + DeviceInfo("Samsung", "SM-J330L", -9, -17), + DeviceInfo("Samsung", "SM-J337V", -5, -18), + DeviceInfo("Samsung", "SM-J400G", -4, -26), + DeviceInfo("Samsung", "SM-J500F", -3, -17), + DeviceInfo("Samsung", "SM-J500H", -8, -15), + DeviceInfo("Samsung", "SM-J500M", -3, -16), + DeviceInfo("Samsung", "SM-J510F", -5, -13), + DeviceInfo("Samsung", "SM-J510FN", -6, -15), + DeviceInfo("Samsung", "SM-J510GN", -6, -16), + DeviceInfo("Samsung", "SM-J510MN", -6, -15), + DeviceInfo("Samsung", "SM-J610F", 0, -30), + DeviceInfo("Samsung", "SM-J727V", -2, -20), + DeviceInfo("Samsung", "SM-J730K", -5, -28), + DeviceInfo("Samsung", "SM-J810F", -6, -26), + DeviceInfo("Samsung", "SM-M205F", 4, -25), + DeviceInfo("Samsung", "SM-N910C", 10, -41), + DeviceInfo("Samsung", "SM-N915G", -5, -29), + DeviceInfo("Samsung", "SM-N9208", -2, -31), + DeviceInfo("Samsung", "SM-N920C", -3, -40), + DeviceInfo("Samsung", "SM-N930R4", 2, -31), + DeviceInfo("Samsung", "SM-N950F", -5, -18), + DeviceInfo("Samsung", "SM-N950N", -3, -20), + DeviceInfo("Samsung", "SM-N950U", -3, -20), + DeviceInfo("Samsung", "SM-N950U1", -3, -27), + DeviceInfo("Samsung", "SM-N960F", 2, -37), + DeviceInfo("Samsung", "SM-N960N", -1, -28), + DeviceInfo("Samsung", "SM-N960U1", -2, -27), + DeviceInfo("Samsung", "SM-S367VL", -8, -26), + DeviceInfo("Samsung", "SM-S727VL", -1, -20), + DeviceInfo("Samsung", "SM-T350", 4, -28), + DeviceInfo("Samsung", "SM-T378V", -6, -22), + DeviceInfo("Samsung", "SM-T380", -4, -16), + DeviceInfo("Samsung", "SM-T380C", 7, -28), + DeviceInfo("Samsung", "SM-T385C", -2, -21), + DeviceInfo("Samsung", "SM-T385K", 0, -22), + DeviceInfo("Samsung", "SM-T385L", -8, -16), + DeviceInfo("Samsung", "SM-T390", -17, -29), + DeviceInfo("Samsung", "SM-T395", -13, -34), + DeviceInfo("Samsung", "SM-T580", -15, -22), + DeviceInfo("Samsung", "SM-T820", -21, -22), + DeviceInfo("Samsung", "SM-T827V", -6, -18), + DeviceInfo("Samsung", "SM-T837V", -2, -21), + DeviceInfo("Sharp", "SH-03K", -4, -26), + DeviceInfo("Sony", "602SO", -8, -33), + DeviceInfo("Sony", "701SO", -3, -22), + DeviceInfo("Sony", "801SO", 6, -36), + DeviceInfo("Sony", "E6603", -3, -18), + DeviceInfo("Sony", "E6633", -2, -20), + DeviceInfo("Sony", "E6683", 1, -19), + DeviceInfo("Sony", "F5121", 8, -46), + DeviceInfo("Sony", "F8131", -11, -33), + DeviceInfo("Sony", "F8331", -12, -34), + DeviceInfo("Sony", "F8332", -11, -31), + DeviceInfo("Sony", "G3123", -5, -24), + DeviceInfo("Sony", "G3223", 3, -38), + DeviceInfo("Sony", "G3313", 0, -24), + DeviceInfo("Sony", "G8142", 1, -23), + DeviceInfo("Sony", "G8232", -5, -36), + DeviceInfo("Sony", "G8341", 5, -33), + DeviceInfo("Sony", "G8342", 1, -31), + DeviceInfo("Sony", "G8441", 3, -26), + DeviceInfo("Sony", "H8216", -6, -28), + DeviceInfo("Sony", "H8266", -1, -31), + DeviceInfo("Sony", "H8296", -5, -30), + DeviceInfo("Sony", "H8314", -2, -36), + DeviceInfo("Sony", "H8324", -6, -24), + DeviceInfo("Sony", "H8416", 5, -36), + DeviceInfo("Sony", "I4213", 0, -32), + DeviceInfo("Sony", "SO-01J", -3, -36), + DeviceInfo("Sony", "SO-03J", -6, -33), + DeviceInfo("Sony", "SO-04J", -1, -35), + DeviceInfo("Sony", "SOV33", -10, -31), + DeviceInfo("Sony", "SOV34", -4, -34), + DeviceInfo("Sony", "SOV36", -2, -30), + DeviceInfo("TCL", "5159A", 1, -35), + DeviceInfo("Tecno", "TECNO KB8", -9, -33), + DeviceInfo("Vivo", "vivo 1723", 4, -30), + DeviceInfo("Vivo", "vivo 1804", -4, -22), + DeviceInfo("Vivo", "vivo 1851", 0, -31), + DeviceInfo("Xiaomi", "MI 4W", -4, -21), + DeviceInfo("Xiaomi", "MI 5", -7, -26), + DeviceInfo("Xiaomi", "MI 5s", -13, -24), + DeviceInfo("Xiaomi", "MI 5s Plus", -6, -30), + DeviceInfo("Xiaomi", "MI 6", 3, -36), + DeviceInfo("Xiaomi", "MI 8", -8, -26), + DeviceInfo("Xiaomi", "MI 8 Lite", -9, -19), + DeviceInfo("Xiaomi", "MI 8 Pro", -8, -33), + DeviceInfo("Xiaomi", "MI MAX 3", -1, -32), + DeviceInfo("Xiaomi", "MI NOTE LTE", 10, -30), + DeviceInfo("Xiaomi", "MIX", -15, -28), + DeviceInfo("Xiaomi", "Mi A1", -4, -28), + DeviceInfo("Xiaomi", "Mi A2", -5, -34), + DeviceInfo("Xiaomi", "Mi A2 Lite", -4, -19), + DeviceInfo("Xiaomi", "Mi A3", -5, -20), + DeviceInfo("Xiaomi", "Mi MIX 2", -4, -27), + DeviceInfo("Xiaomi", "Mi MIX 2S", -8, -17), + DeviceInfo("Xiaomi", "Mi Note 2", -10, -32), + DeviceInfo("Xiaomi", "POCOPHONE F1", 2, -37), + DeviceInfo("Xiaomi", "Redmi 3", -6, -45), + DeviceInfo("Xiaomi", "Redmi 3S", -8, -25), + DeviceInfo("Xiaomi", "Redmi 4X", -8, -24), + DeviceInfo("Xiaomi", "Redmi 5A", -6, -25), + DeviceInfo("Xiaomi", "Redmi 6 Pro", -3, -24), + DeviceInfo("Xiaomi", "Redmi Note 3", -2, -18), + DeviceInfo("Xiaomi", "Redmi Note 5", -2, -22), + DeviceInfo("Xiaomi", "Redmi Note 6 Pro", 0, -32), + DeviceInfo("Xiaomi", "Redmi S2", -5, -24), + DeviceInfo("ZTE", "Z833", -2, -29), + DeviceInfo("ZTE", "ZTE A2020U Pro", -9, -21), + DeviceInfo("Zebra", "TC52", 5, -35), + DeviceInfo("Zebra", "TC57", -7, -23) +) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationService.kt index 045fb70c..f62e2152 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationService.kt @@ -39,7 +39,7 @@ class ExposureNotificationService : BaseService(TAG, GmsService.NEARBY_EXPOSURE) Log.d(TAG, "handleServiceRequest: " + request.packageName) callback.onPostInitCompleteWithConnectionInfo(SUCCESS, ExposureNotificationServiceImpl(this, request.packageName), ConnectionInfo().apply { - features = arrayOf(Feature("nearby_exposure_notification", 2)) + features = arrayOf(Feature("nearby_exposure_notification", 3)) }) } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index 738553ff..444384c4 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -277,4 +277,10 @@ class ExposureNotificationServiceImpl(private val context: Context, private val Log.w(TAG, "Callback failed", e) } } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { + if (super.onTransact(code, data, reply, flags)) return true + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags") + return false + } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt index 3bb000c5..fb9896ce 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt @@ -10,6 +10,7 @@ import android.app.Service import android.bluetooth.BluetoothAdapter import android.bluetooth.le.* import android.content.Intent +import android.os.Build import android.os.IBinder @TargetApi(21) @@ -55,8 +56,13 @@ class ScannerService : Service() { private fun startScan() { if (started) return scanner.startScan( - listOf(ScanFilter.Builder().setServiceUuid(SERVICE_UUID).setServiceData(SERVICE_UUID, byteArrayOf(0), byteArrayOf(0)).build()), - ScanSettings.Builder().build(), + listOf(ScanFilter.Builder() + .setServiceUuid(SERVICE_UUID) + .setServiceData(SERVICE_UUID, byteArrayOf(0), byteArrayOf(0)) + .build()), + ScanSettings.Builder() + .let { if (Build.VERSION.SDK_INT >= 23) it.setMatchMode(ScanSettings.MATCH_MODE_STICKY) else it } + .build(), callback ) started = true diff --git a/play-services-nearby/src/main/java/com/google/android/gms/nearby/Nearby.java b/play-services-nearby/src/main/java/com/google/android/gms/nearby/Nearby.java index 8b2d25a1..fbcabc13 100644 --- a/play-services-nearby/src/main/java/com/google/android/gms/nearby/Nearby.java +++ b/play-services-nearby/src/main/java/com/google/android/gms/nearby/Nearby.java @@ -1,6 +1,9 @@ /* * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 + * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.nearby; diff --git a/play-services-nearby/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureNotificationClient.java b/play-services-nearby/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureNotificationClient.java index 4cb03aa0..f7bdd897 100644 --- a/play-services-nearby/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureNotificationClient.java +++ b/play-services-nearby/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureNotificationClient.java @@ -1,6 +1,9 @@ /* * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 + * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.nearby.exposurenotification; From e1bb395ff8ad20b564a914a455019575b3b1c0aa Mon Sep 17 00:00:00 2001 From: Fs00 Date: Wed, 5 Aug 2020 22:25:30 +0200 Subject: [PATCH 03/24] Don't require apps to use RECEIVE permission for GCM/FCM --- play-services-core/src/main/AndroidManifest.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 2b9e6147..b816da4a 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -214,8 +214,7 @@ + android:name="org.microg.gms.gcm.PushRegisterService"> @@ -233,8 +232,7 @@ + android:name="org.microg.gms.gcm.SendReceiver"> From aea55a5c90082d0ea41fd2169edf6868aa6942e6 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Aug 2020 23:54:14 +0200 Subject: [PATCH 04/24] Update EN API --- .../ui/ExposureNotificationsRpisFragment.kt | 3 +- .../src/main/AndroidManifest.xml | 3 - .../exposurenotification/AdvertiserService.kt | 126 ++++++++++++++---- .../nearby/exposurenotification/DeviceInfo.kt | 7 +- .../exposurenotification/ScannerService.kt | 70 +++++++++- .../exposurenotification/ServiceTrigger.kt | 2 + 6 files changed, 169 insertions(+), 42 deletions(-) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsRpisFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsRpisFragment.kt index 9f9270ba..eba85a4e 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsRpisFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsRpisFragment.kt @@ -45,7 +45,7 @@ class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() { val (totalRpiCount, rpiHistogram) = withContext(Dispatchers.IO) { ExposureDatabase.with(requireContext()) { database -> val map = linkedMapOf() - val lowestDate = Math.round((Date().time / 24 / 60 / 60 / 1000 - 13).toDouble()) * 24 * 60 * 60 * 1000 + val lowestDate = Math.round((System.currentTimeMillis() / 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) val str = when (i) { @@ -58,6 +58,7 @@ class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() { 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() diff --git a/play-services-nearby-core/src/main/AndroidManifest.xml b/play-services-nearby-core/src/main/AndroidManifest.xml index f7d0e933..c8e03e70 100644 --- a/play-services-nearby-core/src/main/AndroidManifest.xml +++ b/play-services-nearby-core/src/main/AndroidManifest.xml @@ -30,9 +30,6 @@ - - - diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt index 08501bf0..e3e166c1 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt @@ -9,7 +9,10 @@ import android.annotation.TargetApi import android.bluetooth.BluetoothAdapter import android.bluetooth.le.* import android.bluetooth.le.AdvertiseSettings.* +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.util.Log import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope @@ -25,10 +28,27 @@ import kotlin.coroutines.suspendCoroutine @TargetApi(21) class AdvertiserService : LifecycleService() { private val version = VERSION_1_0 + private var looping = false private var callback: AdvertiseCallback? = null - private val advertiser: BluetoothLeAdvertiser + private val advertiser: BluetoothLeAdvertiser? get() = BluetoothAdapter.getDefaultAdapter().bluetoothLeAdvertiser private lateinit var database: ExposureDatabase + private val trigger = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == "android.bluetooth.adapter.action.STATE_CHANGED") { + when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) { + BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_OFF -> stopAdvertising() + BluetoothAdapter.STATE_ON -> { + if (looping) { + lifecycleScope.launchWhenStarted { restartAdvertising() } + } else { + loopAdvertising() + } + } + } + } + } + } private suspend fun BluetoothLeAdvertiser.startAdvertising(settings: AdvertiseSettings, advertiseData: AdvertiseData): AdvertiseCallback = suspendCoroutine { startAdvertising(settings, advertiseData, object : AdvertiseCallback() { @@ -45,12 +65,13 @@ class AdvertiserService : LifecycleService() { override fun onCreate() { super.onCreate() database = ExposureDatabase.ref(this) + registerReceiver(trigger, IntentFilter().also { it.addAction("android.bluetooth.adapter.action.STATE_CHANGED") }) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) if (ExposurePreferences(this).advertiserEnabled) { - startAdvertising() + loopAdvertising() } else { stopSelf() } @@ -59,40 +80,63 @@ class AdvertiserService : LifecycleService() { override fun onDestroy() { super.onDestroy() + unregisterReceiver(trigger) stopAdvertising() database.unref() } - fun startAdvertising() { + @Synchronized + fun loopAdvertising() { + if (looping) return + looping = true lifecycleScope.launchWhenStarted { - do { - val aem = when (version) { - VERSION_1_0 -> byteArrayOf( - version, // Version and flags - (currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power - 0x00, // Reserved - 0x00 // Reserved - ) - VERSION_1_1 -> byteArrayOf( - (version + currentDeviceInfo.confidence * 4).toByte(), // Version and flags - (currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power - 0x00, // Reserved - 0x00 // Reserved - ) - else -> return@launchWhenStarted - } - val payload = database.generateCurrentPayload(aem) - var nextSend = nextKeyMillis.coerceAtMost(180000) - startAdvertising(payload, nextSend.toInt()) - delay(nextSend) - } while (callback != null) + Log.d(TAG, "Looping advertising") + try { + do { + val aem = when (version) { + VERSION_1_0 -> byteArrayOf( + version, // Version and flags + (currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power + 0x00, // Reserved + 0x00 // Reserved + ) + VERSION_1_1 -> byteArrayOf( + (version + currentDeviceInfo.confidence * 4).toByte(), // Version and flags + (currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power + 0x00, // Reserved + 0x00 // Reserved + ) + else -> return@launchWhenStarted + } + val payload = database.generateCurrentPayload(aem) + var nextSend = nextKeyMillis.coerceAtMost(180000) + startAdvertising(payload, nextSend.toInt()) + if (callback != null) delay(nextSend) + } while (callback != null) + } catch (e: Exception) { + Log.w(TAG, "Error during advertising loop", e) + } + Log.d(TAG, "No longer advertising") + synchronized(this@AdvertiserService) { + looping = false + } } } + var startTime = System.currentTimeMillis() + var sendingBytes = ByteArray(0) + var sendingNext = 0 suspend fun startAdvertising(bytes: ByteArray, nextSend: Int) { + startTime = System.currentTimeMillis() + sendingBytes = bytes + sendingNext = nextSend + continueAdvertising(bytes, nextSend) + } + + private suspend fun continueAdvertising(bytes: ByteArray, nextSend: Int) { stopAdvertising() val data = AdvertiseData.Builder().addServiceUuid(SERVICE_UUID).addServiceData(SERVICE_UUID, bytes).build() - val settings = AdvertiseSettings.Builder() + val settings = Builder() .setTimeout(nextSend) .setAdvertiseMode(ADVERTISE_MODE_LOW_POWER) .setTxPowerLevel(ADVERTISE_TX_POWER_MEDIUM) @@ -100,18 +144,42 @@ class AdvertiserService : LifecycleService() { .build() val (uuid, aem) = ByteBuffer.wrap(bytes).let { UUID(it.long, it.long) to it.int } Log.d(TAG, "RPI: $uuid, Version: 0x${version.toString(16)}, TX Power: ${currentDeviceInfo.txPowerCorrection + TX_POWER_LOW}, AEM: 0x${aem.toLong().let { if (it < 0) 0x100000000L + it else it }.toString(16)}, Timeout: ${nextSend}ms") - callback = advertiser.startAdvertising(settings, data) + callback = advertiser?.startAdvertising(settings, data) + } + + suspend fun restartAdvertising() { + val startTime = startTime + val bytes = sendingBytes + val next = sendingNext + if (next == 0 || bytes.isEmpty()) return + val nextSend = (startTime - System.currentTimeMillis() + next).toInt() + if (nextSend < 5000) return + continueAdvertising(bytes, nextSend) } override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array?) { + writer?.println("Looping: $looping") writer?.println("Active: ${callback != null}") - writer?.println("Currently advertising: ${database.currentRpiId}") - writer?.println("Next key change in ${nextKeyMillis}ms") + try { + val startTime = startTime + val bytes = sendingBytes + val (uuid, aem) = ByteBuffer.wrap(bytes).let { UUID(it.long, it.long) to it.int } + writer?.println(""" + Last advertising: + Since: ${Date(startTime)} + RPI: $uuid + Version: 0x${version.toString(16)} + TX Power: ${currentDeviceInfo.txPowerCorrection + TX_POWER_LOW} + AEM: 0x${aem.toLong().let { if (it < 0) 0x100000000L + it else it }.toString(16)} + """.trimIndent()) + } catch (e: Exception) { + writer?.println("Last advertising: ${e.message ?: e.toString()}") + } } @Synchronized fun stopAdvertising() { - callback?.let { advertiser.stopAdvertising(it) } + callback?.let { advertiser?.stopAdvertising(it) } callback = null } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/DeviceInfo.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/DeviceInfo.kt index 49d10b13..12aaa42d 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/DeviceInfo.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/DeviceInfo.kt @@ -23,8 +23,8 @@ val currentDeviceInfo: DeviceInfo get() { var deviceInfo = knownDeviceInfo if (deviceInfo == null) { - val byOem = allDeviceInfos.filter { it.oem == Build.MANUFACTURER } - val exactMatch = byOem.find { it.model == Build.MODEL } + val byOem = allDeviceInfos.filter { it.oem.equalsIgnoreCase(Build.MANUFACTURER) } + val exactMatch = byOem.find { it.model.equalsIgnoreCase(Build.MODEL) } deviceInfo = when { exactMatch != null -> { // Exact match @@ -45,6 +45,9 @@ val currentDeviceInfo: DeviceInfo return deviceInfo } +@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") +private fun String.equalsIgnoreCase(other: String): Boolean = (this as java.lang.String).equalsIgnoreCase(other) + /* * Derived from en-calibration-2020-06-13.csv published via * https://developers.google.com/android/exposure-notifications/ble-attenuation-computation#device-list diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt index fb9896ce..a2f020d7 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt @@ -8,42 +8,86 @@ package org.microg.gms.nearby.exposurenotification import android.annotation.TargetApi import android.app.Service import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothAdapter.* import android.bluetooth.le.* +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.os.Build import android.os.IBinder +import android.util.Log +import java.io.FileDescriptor +import java.io.PrintWriter +import java.util.* @TargetApi(21) class ScannerService : Service() { private var started = false + private var startTime = 0L + private var seenAdvertisements = 0L 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 - database.noteAdvertisement(data.sliceArray(0..15), data.drop(16).toByteArray(), result.rssi) + result?.let { onScanResult(it) } + } + + override fun onBatchScanResults(results: MutableList) { + Log.d(TAG, "onBatchScanResults: ${results.size}") + for (result in results) { + onScanResult(result) + } + } + + override fun onScanFailed(errorCode: Int) { + Log.w(TAG, "onScanFailed: $errorCode") + stopScan() } } - private val scanner: BluetoothLeScanner - get() = BluetoothAdapter.getDefaultAdapter().bluetoothLeScanner + private val trigger = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == "android.bluetooth.adapter.action.STATE_CHANGED") { + when (intent.getIntExtra(EXTRA_STATE, -1)) { + STATE_TURNING_OFF, STATE_OFF -> stopScan() + STATE_ON -> startScanIfNeeded() + } + } + } + } + + private val scanner: BluetoothLeScanner? + get() = getDefaultAdapter().bluetoothLeScanner override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) + startScanIfNeeded() + return START_STICKY + } + + fun onScanResult(result: ScanResult) { + val data = result.scanRecord?.serviceData?.get(SERVICE_UUID) ?: return + if (data.size < 16) return // Ignore invalid advertisements + database.noteAdvertisement(data.sliceArray(0..15), data.drop(16).toByteArray(), result.rssi) + seenAdvertisements++ + } + + fun startScanIfNeeded() { if (ExposurePreferences(this).scannerEnabled) { startScan() } else { stopSelf() } - return START_STICKY } override fun onCreate() { super.onCreate() database = ExposureDatabase.ref(this) + registerReceiver(trigger, IntentFilter().also { it.addAction("android.bluetooth.adapter.action.STATE_CHANGED") }) } override fun onDestroy() { super.onDestroy() + unregisterReceiver(trigger) stopScan() database.unref() } @@ -55,6 +99,8 @@ class ScannerService : Service() { @Synchronized private fun startScan() { if (started) return + val scanner = scanner ?: return + Log.d(TAG, "Starting scanner for service $SERVICE_UUID") scanner.startScan( listOf(ScanFilter.Builder() .setServiceUuid(SERVICE_UUID) @@ -66,12 +112,22 @@ class ScannerService : Service() { callback ) started = true + startTime = System.currentTimeMillis() } @Synchronized private fun stopScan() { if (!started) return - scanner.stopScan(callback) + Log.d(TAG, "Stopping scanner for service $SERVICE_UUID") started = false + scanner?.stopScan(callback) + } + + override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array?) { + writer?.println("Started: $started") + if (started) { + writer?.println("Since ${Date(startTime)}") + writer?.println("Seen advertisements: $seenAdvertisements") + } } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceTrigger.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceTrigger.kt index f9d2e918..b6c5cfc3 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceTrigger.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceTrigger.kt @@ -9,11 +9,13 @@ import android.annotation.SuppressLint import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.util.Log import org.microg.gms.common.ForegroundServiceContext class ServiceTrigger : BroadcastReceiver() { @SuppressLint("UnsafeProtectedBroadcastReceiver") override fun onReceive(context: Context, intent: Intent?) { + Log.d(TAG, "ServiceTrigger: $intent") if (ExposurePreferences(context).scannerEnabled) { ForegroundServiceContext(context).startService(Intent(context, ScannerService::class.java)) } From 74c0e28e27386365d7e1a59515faf06edb218978 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Aug 2020 23:55:43 +0200 Subject: [PATCH 05/24] Ignore longstanding gps requests with more than 30s request interval --- .../java/org/microg/gms/location/RealLocationProvider.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java b/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java index 0ae75148..0fe9e67b 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java @@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @SuppressWarnings("MissingPermission") public class RealLocationProvider { public static final String TAG = "GmsLocProviderReal"; + private static final int MIN_GPS_TIME = 30000; private final LocationManager locationManager; private final String name; @@ -119,6 +120,12 @@ public class RealLocationProvider { if (sb.length() != 0) sb.append(", "); sb.append(request.packageName).append(":").append(request.locationRequest.getInterval()).append("ms"); } + if (minTime > MIN_GPS_TIME && name.equals("gps")) { + Log.d(TAG, name + ": ignoring request as " + minTime + "ms (" + sb + "), is less than " + MIN_GPS_TIME); + locationManager.removeUpdates(listener); + connected.set(false); + return; + } Log.d(TAG, name + ": requesting location updates with interval " + minTime + "ms (" + sb + "), minDistance=" + minDistance); if (connected.get()) { if (connectedMinTime != minTime || connectedMinDistance != minDistance) { From 60cc63ed60f8bf401bf48655ae96123cd0b49511 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 22 Aug 2020 23:43:14 +0200 Subject: [PATCH 06/24] GCM: Add support for message acking, deliver to all receivers of package when working permissionless --- .../java/org/microg/gms/gcm/GcmConstants.java | 1 + .../java/org/microg/gms/gcm/McsConstants.java | 2 + .../java/org/microg/gms/gcm/McsService.java | 112 +++++++++++----- .../microg/gms/gcm/PushRegisterHandler.java | 126 ++++++++++++------ 4 files changed, 168 insertions(+), 73 deletions(-) diff --git a/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java b/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java index d33bbfde..3b6a55de 100644 --- a/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java +++ b/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java @@ -30,6 +30,7 @@ public final class GcmConstants { public static final String ACTION_INSTANCE_ID = "com.google.android.gms.iid.InstanceID"; public static final String EXTRA_APP = "app"; + public static final String EXTRA_APP_OVERRIDE = "org.microg.gms.gcm.APP_OVERRIDE"; public static final String EXTRA_APP_ID = "appid"; public static final String EXTRA_APP_VERSION_CODE = "app_ver"; public static final String EXTRA_APP_VERSION_NAME = "app_ver_name"; diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsConstants.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsConstants.java index 300dac0c..0b3108dd 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsConstants.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsConstants.java @@ -36,10 +36,12 @@ public final class McsConstants { public static final int MSG_TEARDOWN = 30; public static final int MSG_CONNECT = 40; public static final int MSG_HEARTBEAT = 41; + public static final int MSG_ACK = 42; public static String ACTION_CONNECT = "org.microg.gms.gcm.mcs.CONNECT"; public static String ACTION_RECONNECT = "org.microg.gms.gcm.mcs.RECONNECT"; public static String ACTION_HEARTBEAT = "org.microg.gms.gcm.mcs.HEARTBEAT"; public static String ACTION_SEND = "org.microg.gms.gcm.mcs.SEND"; + public static String ACTION_ACK = "org.microg.gms.gcm.mcs.ACK"; public static String EXTRA_REASON = "org.microg.gms.gcm.mcs.REASON"; } diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java index 238c1c55..32a91736 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java @@ -26,6 +26,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.net.ConnectivityManager; import android.os.Build; @@ -51,8 +52,10 @@ import org.microg.gms.common.PackageUtils; import org.microg.gms.gcm.mcs.AppData; import org.microg.gms.gcm.mcs.Close; import org.microg.gms.gcm.mcs.DataMessageStanza; +import org.microg.gms.gcm.mcs.Extension; import org.microg.gms.gcm.mcs.HeartbeatAck; import org.microg.gms.gcm.mcs.HeartbeatPing; +import org.microg.gms.gcm.mcs.IqStanza; import org.microg.gms.gcm.mcs.LoginRequest; import org.microg.gms.gcm.mcs.LoginResponse; import org.microg.gms.gcm.mcs.Setting; @@ -64,6 +67,7 @@ import java.net.Socket; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLContext; @@ -74,6 +78,7 @@ import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.common.ForegroundServiceContext.EXTRA_FOREGROUND; import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_RECEIVE; import static org.microg.gms.gcm.GcmConstants.EXTRA_APP; +import static org.microg.gms.gcm.GcmConstants.EXTRA_APP_OVERRIDE; import static org.microg.gms.gcm.GcmConstants.EXTRA_COLLAPSE_KEY; import static org.microg.gms.gcm.GcmConstants.EXTRA_FROM; import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSAGE_ID; @@ -82,6 +87,7 @@ import static org.microg.gms.gcm.GcmConstants.EXTRA_REGISTRATION_ID; import static org.microg.gms.gcm.GcmConstants.EXTRA_SEND_FROM; import static org.microg.gms.gcm.GcmConstants.EXTRA_SEND_TO; import static org.microg.gms.gcm.GcmConstants.EXTRA_TTL; +import static org.microg.gms.gcm.McsConstants.ACTION_ACK; import static org.microg.gms.gcm.McsConstants.ACTION_CONNECT; import static org.microg.gms.gcm.McsConstants.ACTION_HEARTBEAT; import static org.microg.gms.gcm.McsConstants.ACTION_RECONNECT; @@ -91,8 +97,10 @@ import static org.microg.gms.gcm.McsConstants.MCS_CLOSE_TAG; import static org.microg.gms.gcm.McsConstants.MCS_DATA_MESSAGE_STANZA_TAG; import static org.microg.gms.gcm.McsConstants.MCS_HEARTBEAT_ACK_TAG; import static org.microg.gms.gcm.McsConstants.MCS_HEARTBEAT_PING_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_IQ_STANZA_TAG; import static org.microg.gms.gcm.McsConstants.MCS_LOGIN_REQUEST_TAG; import static org.microg.gms.gcm.McsConstants.MCS_LOGIN_RESPONSE_TAG; +import static org.microg.gms.gcm.McsConstants.MSG_ACK; import static org.microg.gms.gcm.McsConstants.MSG_CONNECT; import static org.microg.gms.gcm.McsConstants.MSG_HEARTBEAT; import static org.microg.gms.gcm.McsConstants.MSG_INPUT; @@ -119,6 +127,7 @@ public class McsService extends Service implements Handler.Callback { private static long lastIncomingNetworkRealtime = 0; private static long startTimestamp = 0; public static String activeNetworkPref = null; + private AtomicInteger nextMessageId = new AtomicInteger(0x1000000); private static Socket sslSocket; private static McsInputStream inputStream; @@ -307,6 +316,8 @@ public class McsService extends Service implements Handler.Callback { rootHandler.sendMessage(rootHandler.obtainMessage(MSG_HEARTBEAT, reason)); } else if (ACTION_SEND.equals(intent.getAction())) { handleSendMessage(intent); + } else if (ACTION_ACK.equals(intent.getAction())) { + rootHandler.sendMessage(rootHandler.obtainMessage(MSG_ACK, reason)); } WakefulBroadcastReceiver.completeWakefulIntent(intent); } else if (connectIntent == null) { @@ -347,12 +358,20 @@ public class McsService extends Service implements Handler.Callback { Log.w(TAG, "Failed to send message, missing package name"); return; } + if (packageName.equals(getPackageName()) && intent.hasExtra(EXTRA_APP_OVERRIDE)) { + packageName = intent.getStringExtra(EXTRA_APP_OVERRIDE); + intent.removeExtra(EXTRA_APP_OVERRIDE); + } intent.removeExtra(EXTRA_APP); int ttl; try { - ttl = Integer.parseInt(intent.getStringExtra(EXTRA_TTL)); - if (ttl < 0 || ttl > maxTtl) { + if (intent.hasExtra(EXTRA_TTL)) { + ttl = Integer.parseInt(intent.getStringExtra(EXTRA_TTL)); + if (ttl < 0 || ttl > maxTtl) { + ttl = maxTtl; + } + } else { ttl = maxTtl; } } catch (NumberFormatException e) { @@ -403,7 +422,8 @@ public class McsService extends Service implements Handler.Callback { try { DataMessageStanza msg = new DataMessageStanza.Builder() .sent(System.currentTimeMillis() / 1000L) - .id(messageId) + .id(Integer.toHexString(nextMessageId.incrementAndGet())) + .persistent_id(messageId) .token(collapseKey) .from(from) .reg_id(registrationId) @@ -513,9 +533,11 @@ public class McsService extends Service implements Handler.Callback { Intent intent = new Intent(); intent.setAction(ACTION_C2DM_RECEIVE); - intent.setPackage(packageName); intent.putExtra(EXTRA_FROM, msg.from); intent.putExtra(EXTRA_MESSAGE_ID, msg.id); + if (msg.persistent_id != null) { + intent.putExtra(EXTRA_MESSAGE_ID, msg.persistent_id); + } if (app.wakeForDelivery) { intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); } else { @@ -526,41 +548,52 @@ public class McsService extends Service implements Handler.Callback { intent.putExtra(appData.key, appData.value); } - String receiverPermission; + String receiverPermission = null; try { String name = packageName + ".permission.C2D_MESSAGE"; - getPackageManager().getPermissionInfo(name, 0); - receiverPermission = name; - } catch (PackageManager.NameNotFoundException e) { - receiverPermission = null; + PermissionInfo info = getPackageManager().getPermissionInfo(name, 0); + if (info.packageName.equals(packageName)) { + receiverPermission = name; + } + } catch (Exception ignored) { + // Keep null, no valid permission found } - List infos = getPackageManager().queryBroadcastReceivers(intent, PackageManager.GET_RESOLVED_FILTER); - if (infos == null || infos.isEmpty()) { - logd("No target for message, wut?"); + if (receiverPermission == null) { + // Without receiver permission, we only restrict by package name + logd("Deliver message to all receivers in package " + packageName); + intent.setPackage(packageName); + sendOrderedBroadcast(intent, null); } else { - for (ResolveInfo resolveInfo : infos) { - logd("Target: " + resolveInfo); - Intent targetIntent = new Intent(intent); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && app.wakeForDelivery) { - try { - if (getUserIdMethod != null && addPowerSaveTempWhitelistAppMethod != null && deviceIdleController != null) { - int userId = (int) getUserIdMethod.invoke(null, getPackageManager().getApplicationInfo(packageName, 0).uid); - logd("Adding app " + packageName + " for userId " + userId + " to the temp whitelist"); - addPowerSaveTempWhitelistAppMethod.invoke(deviceIdleController, packageName, 10000, userId, "GCM Push"); + List infos = getPackageManager().queryBroadcastReceivers(intent, PackageManager.GET_RESOLVED_FILTER); + if (infos == null || infos.isEmpty()) { + logd("No target for message, wut?"); + } else { + for (ResolveInfo resolveInfo : infos) { + Intent targetIntent = new Intent(intent); + targetIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)); + if (resolveInfo.activityInfo.packageName.equals(packageName)) { + // Wake up the package itself + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && app.wakeForDelivery) { + try { + if (getUserIdMethod != null && addPowerSaveTempWhitelistAppMethod != null && deviceIdleController != null) { + int userId = (int) getUserIdMethod.invoke(null, getPackageManager().getApplicationInfo(packageName, 0).uid); + logd("Adding app " + packageName + " for userId " + userId + " to the temp whitelist"); + addPowerSaveTempWhitelistAppMethod.invoke(deviceIdleController, packageName, 10000, userId, "GCM Push"); + } + } catch (Exception e) { + Log.w(TAG, e); + } } - } catch (Exception e) { - Log.w(TAG, e); + // We don't need receiver permission for our own package + logd("Deliver message to own receiver " + resolveInfo); + sendOrderedBroadcast(targetIntent, null); + } else if (resolveInfo.filter.hasCategory(packageName)) { + // Permission required + logd("Deliver message to third-party receiver (with permission check)" + resolveInfo); + sendOrderedBroadcast(targetIntent, receiverPermission); } } - targetIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)); - if (resolveInfo.activityInfo.packageName.equals(packageName)) { - sendOrderedBroadcast(targetIntent, null); - } else if (receiverPermission != null) { - sendOrderedBroadcast(targetIntent, receiverPermission); - } else { - Log.w(TAG, resolveInfo.activityInfo.packageName + "/" + resolveInfo.activityInfo.name + " matches for C2D message to " + packageName + " but corresponding permission was not declared"); - } } } } @@ -569,6 +602,7 @@ public class McsService extends Service implements Handler.Callback { for (AppData appData : msg.app_data) { if (IDLE_NOTIFICATION.equals(appData.key)) { DataMessageStanza.Builder msgResponse = new DataMessageStanza.Builder() + .id(Integer.toHexString(nextMessageId.incrementAndGet())) .from(FROM_FIELD) .sent(System.currentTimeMillis() / 1000) .ttl(0) @@ -633,6 +667,22 @@ public class McsService extends Service implements Handler.Callback { scheduleReconnect(this); } return true; + case MSG_ACK: + logd("Ack initiated, reason: " + msg.obj); + if (isConnected()) { + IqStanza.Builder iq = new IqStanza.Builder() + .type(IqStanza.IqType.SET) + .id("") + .extension(new Extension.Builder().id(13).data(ByteString.EMPTY).build()) // StreamAck + .status(0L); + if (inputStream.newStreamIdAvailable()) { + iq.last_stream_id_received(inputStream.getStreamId()); + } + send(MCS_IQ_STANZA_TAG, iq.build()); + } else { + logd("Ignoring ack, not connected!"); + } + return true; case MSG_OUTPUT_READY: logd("Sending login request..."); send(MCS_LOGIN_REQUEST_TAG, buildLoginRequest()); diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterHandler.java b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterHandler.java index e95c98f0..15aaecc1 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterHandler.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterHandler.java @@ -28,13 +28,17 @@ import android.os.RemoteException; import android.util.Log; import org.microg.gms.checkin.LastCheckinInfo; +import org.microg.gms.common.ForegroundServiceContext; import org.microg.gms.common.PackageUtils; import org.microg.gms.common.Utils; import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTRATION; import static org.microg.gms.gcm.GcmConstants.ERROR_SERVICE_NOT_AVAILABLE; import static org.microg.gms.gcm.GcmConstants.EXTRA_APP; +import static org.microg.gms.gcm.GcmConstants.EXTRA_APP_OVERRIDE; import static org.microg.gms.gcm.GcmConstants.EXTRA_ERROR; +import static org.microg.gms.gcm.McsConstants.ACTION_ACK; +import static org.microg.gms.gcm.McsConstants.ACTION_SEND; class PushRegisterHandler extends Handler { private static final String TAG = "GmsGcmRegisterHdl"; @@ -54,40 +58,54 @@ class PushRegisterHandler extends Handler { return super.sendMessageAtTime(msg, uptimeMillis); } - private void sendReply(int what, int id, Messenger replyTo, Bundle data) { - if (what == 0) { - Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION); - outIntent.putExtras(data); - Message message = Message.obtain(); - message.obj = outIntent; - try { - replyTo.send(message); - } catch (RemoteException e) { - Log.w(TAG, e); - } - } else { - Bundle messageData = new Bundle(); - messageData.putBundle("data", data); - Message response = Message.obtain(); - response.what = what; - response.arg1 = id; - response.setData(messageData); - try { - replyTo.send(response); - } catch (RemoteException e) { - Log.w(TAG, e); - } + private void sendReplyViaMessage(int what, int id, Messenger replyTo, Bundle messageData) { + Message response = Message.obtain(); + response.what = what; + response.arg1 = id; + response.setData(messageData); + try { + replyTo.send(response); + } catch (RemoteException e) { + Log.w(TAG, e); } } - private void replyError(int what, int id, Messenger replyTo, String errorMessage) { + private void sendReplyViaIntent(Intent outIntent, Messenger replyTo) { + Message message = Message.obtain(); + message.obj = outIntent; + try { + replyTo.send(message); + } catch (RemoteException e) { + Log.w(TAG, e); + } + } + + private void sendReply(int what, int id, Messenger replyTo, Bundle data, boolean oneWay) { + if (what == 0) { + Intent outIntent = new Intent(ACTION_C2DM_REGISTRATION); + outIntent.putExtras(data); + sendReplyViaIntent(outIntent, replyTo); + return; + } + Bundle messageData = new Bundle(); + messageData.putBundle("data", data); + sendReplyViaMessage(what, id, replyTo, messageData); + } + + private void replyError(int what, int id, Messenger replyTo, String errorMessage, boolean oneWay) { Bundle bundle = new Bundle(); bundle.putString(EXTRA_ERROR, errorMessage); - sendReply(what, id, replyTo, bundle); + sendReply(what, id, replyTo, bundle, oneWay); } private void replyNotAvailable(int what, int id, Messenger replyTo) { - replyError(what, id, replyTo, ERROR_SERVICE_NOT_AVAILABLE); + replyError(what, id, replyTo, ERROR_SERVICE_NOT_AVAILABLE, false); + } + + private PendingIntent getSelfAuthIntent() { + Intent intent = new Intent(); + intent.setPackage("com.google.example.invalidpackage"); + return PendingIntent.getBroadcast(context, 0, intent, 0); } @Override @@ -119,15 +137,9 @@ class PushRegisterHandler extends Handler { return; } Bundle data = msg.getData(); - if (data.getBoolean("oneWay", false)) { - Log.w(TAG, "oneWay requested"); - return; - } String packageName = data.getString("pkg"); Bundle subdata = data.getBundle("data"); - String sender = subdata.getString("sender"); - boolean delete = subdata.get("delete") != null; try { PackageUtils.checkPackageUid(context, packageName, callingUid); @@ -136,16 +148,46 @@ class PushRegisterHandler extends Handler { return; } - // TODO: We should checkin and/or ask for permission here. + Log.d(TAG, "handleMessage: package=" + packageName + " what=" + what + " id=" + id); - PushRegisterManager.completeRegisterRequest(context, database, - new RegisterRequest() - .build(Utils.getBuild(context)) - .sender(sender) - .checkin(LastCheckinInfo.read(context)) - .app(packageName) - .delete(delete) - .extraParams(subdata), - bundle -> sendReply(what, id, replyTo, bundle)); + boolean oneWay = data.getBoolean("oneWay", false); + + switch (what) { + case 0: + case 1: + // TODO: We should checkin and/or ask for permission here. + String sender = subdata.getString("sender"); + boolean delete = subdata.get("delete") != null; + + PushRegisterManager.completeRegisterRequest(context, database, + new RegisterRequest() + .build(Utils.getBuild(context)) + .sender(sender) + .checkin(LastCheckinInfo.read(context)) + .app(packageName) + .delete(delete) + .extraParams(subdata), + bundle -> sendReply(what, id, replyTo, bundle, oneWay)); + break; + case 2: + String messageId = subdata.getString("google.message_id"); + Log.d(TAG, "Ack " + messageId + " for " + packageName); + Intent i = new Intent(context, McsService.class); + i.setAction(ACTION_ACK); + i.putExtra(EXTRA_APP, getSelfAuthIntent()); + new ForegroundServiceContext(context).startService(i); + break; + default: + Bundle bundle = new Bundle(); + bundle.putBoolean("unsupported", true); + sendReplyViaMessage(what, id, replyTo, bundle); + return; + } + + if (oneWay) { + Bundle bundle = new Bundle(); + bundle.putBoolean("ack", true); + sendReplyViaMessage(what, id, replyTo, bundle); + } } } From c88832213c8f6eb91e1e1118ae548c317a878f87 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 25 Aug 2020 13:11:50 +0200 Subject: [PATCH 07/24] Move UI into separate process --- .../org/microg/gms/common/PackageUtils.java | 15 ++++++ .../src/main/AndroidManifest.xml | 41 ++++++++++---- .../kotlin/org/microg/gms/gcm/StatusInfo.kt | 53 +++++++++++++++++++ .../ui/PushNotificationPreferencesFragment.kt | 12 +++-- 4 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/gcm/StatusInfo.kt diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java index 58b209b8..b4b3d69d 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java @@ -17,6 +17,7 @@ package org.microg.gms.common; import android.app.ActivityManager; +import android.app.Application; import android.app.PendingIntent; import android.content.Context; import android.content.pm.PackageInfo; @@ -26,6 +27,7 @@ import android.os.Binder; import androidx.annotation.Nullable; +import java.lang.reflect.Method; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; @@ -230,6 +232,19 @@ public class PackageUtils { } } + public static String getProcessName() { + if (android.os.Build.VERSION.SDK_INT >= 28) + return Application.getProcessName(); + try { + Class activityThread = Class.forName("android.app.ActivityThread"); + String methodName = android.os.Build.VERSION.SDK_INT >= 18 ? "currentProcessName" : "currentPackageName"; + Method getProcessName = activityThread.getDeclaredMethod(methodName); + return (String) getProcessName.invoke(null); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public static String sha1sum(byte[] bytes) { MessageDigest md; try { diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index b816da4a..831a1514 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -129,11 +129,11 @@ - + @@ -238,6 +238,8 @@ + + @@ -346,7 +348,8 @@ + android:exported="true" + android:process=":ui"> @@ -358,6 +361,7 @@ android:name="org.microg.gms.auth.login.LoginActivity" android:configChanges="keyboardHidden|orientation|screenSize" android:exported="true" + android:process=":ui" android:theme="@style/Theme.LoginBlue"> @@ -370,6 +374,7 @@ android:name="org.microg.gms.auth.AskPermissionActivity" android:excludeFromRecents="true" android:exported="true" + android:process=":ui" android:theme="@style/Theme.AppCompat.DayNight.Dialog" /> + android:exported="true" + android:process=":ui" /> @@ -422,6 +429,7 @@ @@ -433,6 +441,7 @@ android:name="org.microg.gms.ui.SettingsActivity" android:icon="@mipmap/ic_microg_settings" android:label="@string/gms_settings_name" + android:process=":ui" android:roundIcon="@mipmap/ic_microg_settings"> @@ -441,6 +450,7 @@ + @@ -449,6 +459,7 @@ android:name="org.microg.gms.ui.SettingsDashboardActivity" android:icon="@mipmap/ic_microg_settings" android:label="Legacy microG Settings" + android:process=":ui" android:roundIcon="@mipmap/ic_microg_settings" /> @@ -456,6 +467,7 @@ android:name="org.microg.gms.ui.SettingsActivityLink" android:icon="@drawable/microg_light_color_24" android:label="@string/gms_settings_name" + android:process=":ui" android:targetActivity="org.microg.gms.ui.SettingsActivity"> @@ -475,25 +487,32 @@ + android:label="@string/pref_about_title" + android:process=":ui" /> + android:label="@string/gms_settings_name" + android:process=":ui" /> + android:label="@string/service_name_snet" + android:process=":ui" /> + android:label="@string/self_check_title" + android:process=":ui" /> - + @@ -503,7 +522,9 @@ - + diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/StatusInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/StatusInfo.kt new file mode 100644 index 00000000..1797ca6c --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/StatusInfo.kt @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.gcm + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.util.Log +import java.io.Serializable +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +private const val ACTION_STATUS_INFO_REQUEST = "org.microg.gms.STATUS_INFO_REQUEST" +private const val ACTION_STATUS_INFO_RESPONSE = "org.microg.gms.STATUS_INFO_RESPONSE" +private const val EXTRA_STATUS_INFO = "org.microg.gms.STATUS_INFO" +private const val TAG = "GmsGcmStatusInfo" + +data class StatusInfo(val connected: Boolean, val startTimestamp: Long) : Serializable + +class StatusInfoProvider : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + try { + context.sendOrderedBroadcast(Intent(ACTION_STATUS_INFO_RESPONSE).apply { + setPackage(context.packageName) + putExtra(EXTRA_STATUS_INFO, StatusInfo(McsService.isConnected(), McsService.getStartTimestamp())) + }, null) + } catch (e: Exception) { + Log.w(TAG, e) + } + } +} + +suspend fun getStatusInfo(context: Context): StatusInfo? = suspendCoroutine { + context.registerReceiver(object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent?) { + try { + it.resume(intent?.getSerializableExtra(EXTRA_STATUS_INFO) as? StatusInfo) + } catch (e: Exception) { + Log.w(TAG, e) + } + context.unregisterReceiver(this) + } + }, IntentFilter(ACTION_STATUS_INFO_RESPONSE)) + try { + context.sendOrderedBroadcast(Intent(context, StatusInfoProvider::class.java), null) + } catch (e: Exception) { + it.resume(null) + } +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt index 43e5fca8..9b5f8281 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.withContext import org.microg.gms.gcm.GcmDatabase import org.microg.gms.gcm.GcmPrefs import org.microg.gms.gcm.McsService +import org.microg.gms.gcm.getStatusInfo class PushNotificationPreferencesFragment : PreferenceFragmentCompat() { private lateinit var pushStatusCategory: PreferenceCategory @@ -67,10 +68,13 @@ class PushNotificationPreferencesFragment : PreferenceFragmentCompat() { private fun updateStatus() { handler.postDelayed(updateRunnable, UPDATE_INTERVAL) pushStatusCategory.isVisible = GcmPrefs.get(context).isEnabled - pushStatus.summary = if (McsService.isConnected()) { - getString(R.string.gcm_network_state_connected, DateUtils.getRelativeTimeSpanString(McsService.getStartTimestamp(), System.currentTimeMillis(), 0)) - } else { - getString(R.string.gcm_network_state_disconnected) + lifecycleScope.launchWhenStarted { + val statusInfo = getStatusInfo(requireContext()) + pushStatus.summary = if (statusInfo != null && statusInfo.connected) { + getString(R.string.gcm_network_state_connected, DateUtils.getRelativeTimeSpanString(statusInfo.startTimestamp, System.currentTimeMillis(), 0)) + } else { + getString(R.string.gcm_network_state_disconnected) + } } } From cfc1c314d4eac9a4f763678e4734f658d527634e Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 24 Aug 2020 10:12:49 +0200 Subject: [PATCH 08/24] EN: Cleanup data after 14 days, improve storage efficiency, add randomness for changing RPI --- .../src/main/AndroidManifest.xml | 9 +- .../exposurenotification/AdvertiserService.kt | 16 ++- .../exposurenotification/CleanupService.kt | 34 ++++++ .../nearby/exposurenotification/Constants.kt | 3 + .../exposurenotification/ExposureDatabase.kt | 111 +++++++++++------- .../ExposureNotificationServiceImpl.kt | 11 +- .../ExposurePreferences.kt | 5 + .../exposurenotification/ScannerService.kt | 11 ++ .../exposurenotification/ServiceTrigger.kt | 12 +- 9 files changed, 155 insertions(+), 57 deletions(-) create mode 100644 play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt diff --git a/play-services-nearby-core/src/main/AndroidManifest.xml b/play-services-nearby-core/src/main/AndroidManifest.xml index c8e03e70..83a8bda1 100644 --- a/play-services-nearby-core/src/main/AndroidManifest.xml +++ b/play-services-nearby-core/src/main/AndroidManifest.xml @@ -14,12 +14,9 @@ - - + + + diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt index e3e166c1..417aaf8e 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt @@ -7,8 +7,11 @@ package org.microg.gms.nearby.exposurenotification import android.annotation.TargetApi import android.bluetooth.BluetoothAdapter -import android.bluetooth.le.* +import android.bluetooth.le.AdvertiseCallback +import android.bluetooth.le.AdvertiseData +import android.bluetooth.le.AdvertiseSettings import android.bluetooth.le.AdvertiseSettings.* +import android.bluetooth.le.BluetoothLeAdvertiser import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -17,6 +20,7 @@ import android.util.Log import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.delay +import org.microg.gms.common.ForegroundServiceContext import java.io.FileDescriptor import java.io.PrintWriter import java.nio.ByteBuffer @@ -24,6 +28,7 @@ import java.util.* import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine +import kotlin.random.Random @TargetApi(21) class AdvertiserService : LifecycleService() { @@ -69,6 +74,7 @@ class AdvertiserService : LifecycleService() { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + ForegroundServiceContext.completeForegroundService(this, intent, TAG) super.onStartCommand(intent, flags, startId) if (ExposurePreferences(this).advertiserEnabled) { loopAdvertising() @@ -109,7 +115,7 @@ class AdvertiserService : LifecycleService() { else -> return@launchWhenStarted } val payload = database.generateCurrentPayload(aem) - var nextSend = nextKeyMillis.coerceAtMost(180000) + val nextSend = (nextKeyMillis + Random.nextInt(-ADVERTISER_OFFSET, ADVERTISER_OFFSET)).coerceIn(0, 180000) startAdvertising(payload, nextSend.toInt()) if (callback != null) delay(nextSend) } while (callback != null) @@ -182,4 +188,10 @@ class AdvertiserService : LifecycleService() { callback?.let { advertiser?.stopAdvertising(it) } callback = null } + + companion object { + fun isNeeded(context: Context): Boolean { + return ExposurePreferences(context).scannerEnabled + } + } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt new file mode 100644 index 00000000..f7908f85 --- /dev/null +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.nearby.exposurenotification + +import android.content.Context +import android.content.Intent +import androidx.lifecycle.LifecycleService +import org.microg.gms.common.ForegroundServiceContext + +class CleanupService : LifecycleService() { + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + ForegroundServiceContext.completeForegroundService(this, intent, TAG) + super.onStartCommand(intent, flags, startId) + if (isNeeded(this)) { + ExposureDatabase.with(this@CleanupService) { + it.dailyCleanup() + } + ExposurePreferences(this).lastCleanup = System.currentTimeMillis() + } + stopSelf() + return START_NOT_STICKY + } + + companion object { + fun isNeeded(context: Context): Boolean { + return ExposurePreferences(context).let { + it.scannerEnabled && it.lastCleanup < System.currentTimeMillis() - CLEANUP_INTERVAL + } + } + } +} diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt index d41b53bb..891d80c1 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt @@ -30,6 +30,9 @@ const val PERMISSION_EXPOSURE_CALLBACK = "com.google.android.gms.nearby.exposure const val TX_POWER_LOW = -15 +const val ADVERTISER_OFFSET = 60 * 1000 +const val CLEANUP_INTERVAL = 24 * 60 * 60 * 1000 + const val VERSION_1_0: Byte = 0x40 const val VERSION_1_1: Byte = 0x50 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 077be955..4f435efe 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 @@ -5,28 +5,38 @@ package org.microg.gms.nearby.exposurenotification +import android.annotation.TargetApi import android.content.ContentValues import android.content.Context import android.database.sqlite.SQLiteCursor import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE import android.database.sqlite.SQLiteOpenHelper +import android.database.sqlite.SQLiteStatement +import android.os.Build import android.os.Parcel import android.os.Parcelable +import android.text.TextUtils import android.util.Log import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey +import org.microg.gms.common.PackageUtils import java.nio.ByteBuffer import java.util.* import java.util.concurrent.Future import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicInteger import kotlin.math.roundToInt -class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { +@TargetApi(21) +class ExposureDatabase private constructor(private val context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { private var refCount = 0 + init { + setWriteAheadLoggingEnabled(true) + } + override fun onCreate(db: SQLiteDatabase) { onUpgrade(db, 0, DB_VERSION) } @@ -39,19 +49,30 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_APP_LOG(package TEXT NOT NULL, timestamp INTEGER NOT NULL, method TEXT NOT NULL, args TEXT);") db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_APP_LOG}_package_timestamp ON $TABLE_APP_LOG(package, timestamp);") db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK(keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL);") - db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK(keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, matched INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(keyData, rollingStartNumber, rollingPeriod));") - db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_DIAGNOSIS(package TEXT NOT NULL, token TEXT NOT NULL, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, transmissionRiskLevel INTEGER NOT NULL, PRIMARY KEY(package, token, keyData));") db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_CONFIGURATIONS(package TEXT NOT NULL, token TEXT NOT NULL, configuration BLOB, PRIMARY KEY(package, token))") } + if (oldVersion < 2) { + db.execSQL("DROP TABLE IF EXISTS $TABLE_TEK_CHECK;") + db.execSQL("DROP TABLE IF EXISTS $TABLE_DIAGNOSIS;") + db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK(tcid INTEGER PRIMARY KEY, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, matched INTEGER, UNIQUE(keyData, rollingStartNumber, rollingPeriod));") + db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_DIAGNOSIS(package TEXT NOT NULL, token TEXT NOT NULL, tcid INTEGER REFERENCES $TABLE_TEK_CHECK(tcid) ON DELETE CASCADE, transmissionRiskLevel INTEGER NOT NULL);") + db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_DIAGNOSIS}_package_token ON $TABLE_DIAGNOSIS(package, token);") + } } + fun SQLiteDatabase.delete(table: String, whereClause: String, args: LongArray): Int = + compileStatement("DELETE FROM $table WHERE $whereClause").use { + args.forEachIndexed { idx, l -> it.bindLong(idx + 1, l) } + it.executeUpdateDelete() + } + fun dailyCleanup() = writableDatabase.run { val rollingStartTime = currentRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong()) - delete(TABLE_ADVERTISEMENTS, "timestamp < ?", arrayOf(rollingStartTime.toString())) - delete(TABLE_APP_LOG, "timestamp < ?", arrayOf(rollingStartTime.toString())) - delete(TABLE_TEK, "rollingStartNumber + rollingPeriod < ?", arrayOf((rollingStartTime / ROLLING_WINDOW_LENGTH_MS).toString())) - delete(TABLE_TEK_CHECK, "rollingStartNumber + rollingPeriod < ?", arrayOf((rollingStartTime / ROLLING_WINDOW_LENGTH_MS).toString())) - delete(TABLE_DIAGNOSIS, "rollingStartNumber + rollingPeriod < ?", arrayOf((rollingStartTime / ROLLING_WINDOW_LENGTH_MS).toString())) + val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime)) + val appLogEntries = delete(TABLE_APP_LOG, "timestamp < ?", longArrayOf(rollingStartTime)) + val temporaryExposureKeys = delete(TABLE_TEK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS)) + val checkedTemporaryExposureKeys = delete(TABLE_TEK_CHECK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS)) + Log.d(TAG, "Deleted on daily cleanup: $advertisements adv, $appLogEntries applogs, $temporaryExposureKeys teks, $checkedTemporaryExposureKeys cteks") } fun noteAdvertisement(rpi: ByteArray, aem: ByteArray, rssi: Int, timestamp: Long = Date().time) = writableDatabase.run { @@ -78,7 +99,7 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( fun deleteAllCollectedAdvertisements() = writableDatabase.run { delete(TABLE_ADVERTISEMENTS, null, null) - update(TABLE_DIAGNOSIS, ContentValues().apply { + update(TABLE_TEK_CHECK, ContentValues().apply { put("matched", 0) }, null, null) } @@ -102,37 +123,48 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( key } + fun getTekCheckId(key: TemporaryExposureKey, mayInsert: Boolean = false): Long? = (if (mayInsert) writableDatabase else readableDatabase).run { + if (mayInsert) { + insertWithOnConflict(TABLE_TEK_CHECK, "NULL", ContentValues().apply { + put("keyData", key.keyData) + put("rollingStartNumber", key.rollingStartIntervalNumber) + put("rollingPeriod", key.rollingPeriod) + }, CONFLICT_IGNORE) + } + compileStatement("SELECT tcid FROM $TABLE_TEK_CHECK WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?").use { + it.bindBlob(1, key.keyData) + it.bindLong(2, key.rollingStartIntervalNumber.toLong()) + it.bindLong(3, key.rollingPeriod.toLong()) + it.simpleQueryForLong() + } + } + fun storeDiagnosisKey(packageName: String, token: String, key: TemporaryExposureKey) = writableDatabase.run { + val tcid = getTekCheckId(key, true) insert(TABLE_DIAGNOSIS, "NULL", ContentValues().apply { put("package", packageName) put("token", token) - put("keyData", key.keyData) - put("rollingStartNumber", key.rollingStartIntervalNumber) - put("rollingPeriod", key.rollingPeriod) + put("tcid", tcid) put("transmissionRiskLevel", key.transmissionRiskLevel) }) } fun updateDiagnosisKey(packageName: String, token: String, key: TemporaryExposureKey) = writableDatabase.run { - compileStatement("UPDATE $TABLE_DIAGNOSIS SET rollingStartNumber = ?, rollingPeriod = ?, transmissionRiskLevel = ? WHERE package = ? AND token = ? AND keyData = ?;").use { - it.bindLong(1, key.rollingStartIntervalNumber.toLong()) - it.bindLong(2, key.rollingPeriod.toLong()) - it.bindLong(3, key.transmissionRiskLevel.toLong()) - it.bindString(4, packageName) - it.bindString(5, token) - it.bindBlob(6, key.keyData) + val tcid = getTekCheckId(key) ?: return 0 + compileStatement("UPDATE $TABLE_DIAGNOSIS SET transmissionRiskLevel = ? WHERE package = ? AND token = ? AND tcid = ?;").use { + it.bindLong(1, key.transmissionRiskLevel.toLong()) + it.bindString(2, packageName) + it.bindString(3, token) + it.bindLong(4, tcid) it.executeUpdateDelete() } } fun listDiagnosisKeysPendingSearch(packageName: String, token: String) = readableDatabase.run { rawQuery(""" - SELECT $TABLE_DIAGNOSIS.keyData, $TABLE_DIAGNOSIS.rollingStartNumber, $TABLE_DIAGNOSIS.rollingPeriod + SELECT $TABLE_TEK_CHECK.keyData, $TABLE_TEK_CHECK.rollingStartNumber, $TABLE_TEK_CHECK.rollingPeriod FROM $TABLE_DIAGNOSIS - LEFT JOIN $TABLE_TEK_CHECK ON - $TABLE_DIAGNOSIS.keyData = $TABLE_TEK_CHECK.keyData AND - $TABLE_DIAGNOSIS.rollingStartNumber = $TABLE_TEK_CHECK.rollingStartNumber AND - $TABLE_DIAGNOSIS.rollingPeriod = $TABLE_TEK_CHECK.rollingPeriod + LEFT JOIN $TABLE_TEK_CHECK ON $TABLE_DIAGNOSIS.tcid = $TABLE_TEK_CHECK.tcid WHERE $TABLE_DIAGNOSIS.package = ? AND $TABLE_DIAGNOSIS.token = ? AND @@ -151,22 +183,20 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( } fun applyDiagnosisKeySearchResult(key: TemporaryExposureKey, matched: Boolean) = writableDatabase.run { - insert(TABLE_TEK_CHECK, "NULL", ContentValues().apply { - put("keyData", key.keyData) - put("rollingStartNumber", key.rollingStartIntervalNumber) - put("rollingPeriod", key.rollingPeriod) - put("matched", if (matched) 1 else 0) - }) + compileStatement("UPDATE $TABLE_TEK_CHECK SET matched = ? WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?;").use { + it.bindLong(1, if (matched) 1 else 0) + it.bindBlob(2, key.keyData) + it.bindLong(3, key.rollingStartIntervalNumber.toLong()) + it.bindLong(4, key.rollingPeriod.toLong()) + it.executeUpdateDelete() + } } fun listMatchedDiagnosisKeys(packageName: String, token: String) = readableDatabase.run { rawQuery(""" - SELECT $TABLE_DIAGNOSIS.keyData, $TABLE_DIAGNOSIS.rollingStartNumber, $TABLE_DIAGNOSIS.rollingPeriod, $TABLE_DIAGNOSIS.transmissionRiskLevel + SELECT $TABLE_TEK_CHECK.keyData, $TABLE_TEK_CHECK.rollingStartNumber, $TABLE_TEK_CHECK.rollingPeriod, $TABLE_DIAGNOSIS.transmissionRiskLevel FROM $TABLE_DIAGNOSIS - LEFT JOIN $TABLE_TEK_CHECK ON - $TABLE_DIAGNOSIS.keyData = $TABLE_TEK_CHECK.keyData AND - $TABLE_DIAGNOSIS.rollingStartNumber = $TABLE_TEK_CHECK.rollingStartNumber AND - $TABLE_DIAGNOSIS.rollingPeriod = $TABLE_TEK_CHECK.rollingPeriod + LEFT JOIN $TABLE_TEK_CHECK ON $TABLE_DIAGNOSIS.tcid = $TABLE_TEK_CHECK.tcid WHERE $TABLE_DIAGNOSIS.package = ? AND $TABLE_DIAGNOSIS.token = ? AND @@ -208,7 +238,7 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( } val time = (System.currentTimeMillis() - start).toDouble() / 1000.0 executor.shutdown() - Log.d(TAG, "Processed ${keys.size} keys in ${time}s -> ${(keys.size.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s") + Log.d(TAG, "Processed ${keys.size} new keys in ${time}s -> ${(keys.size.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s") } fun findAllMeasuredExposures(packageName: String, token: String): List { @@ -226,7 +256,6 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( val pos = i * 16 allRpis.sliceArray(pos until (pos + 16)) } - val start = System.currentTimeMillis() val measures = findMeasuredExposures(rpis, key.rollingStartIntervalNumber.toLong() * ROLLING_WINDOW_LENGTH_MS - ALLOWED_KEY_OFFSET_MS, (key.rollingStartIntervalNumber.toLong() + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS) measures.filter { val index = rpis.indexOf(it.rpi) @@ -437,9 +466,7 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( if (this != instance) { throw IllegalStateException("Tried to open writable database from secondary instance") } - val db = super.getWritableDatabase() - db.enableWriteAheadLogging() - return db + return super.getWritableDatabase() } override fun close() { @@ -465,7 +492,7 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( companion object { private const val DB_NAME = "exposure.db" - private const val DB_VERSION = 1 + private const val DB_VERSION = 2 private const val TABLE_ADVERTISEMENTS = "advertisements" private const val TABLE_APP_LOG = "app_log" private const val TABLE_TEK = "tek" diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index 444384c4..efbb7c74 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -19,8 +19,7 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import com.google.android.gms.nearby.exposurenotification.internal.* import org.json.JSONArray import org.json.JSONObject -import org.microg.gms.nearby.exposurenotification.Constants.ACTION_EXPOSURE_NOT_FOUND -import org.microg.gms.nearby.exposurenotification.Constants.ACTION_EXPOSURE_STATE_UPDATED +import org.microg.gms.nearby.exposurenotification.Constants.* import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyExport import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyProto import java.util.* @@ -73,6 +72,10 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun stop(params: StopParams) { + if (!ExposurePreferences(context).scannerEnabled) { + params.callback.onResult(Status.SUCCESS) + return + } confirm(CONFIRM_ACTION_STOP) { resultCode, _ -> if (resultCode == SUCCESS) { ExposurePreferences(context).scannerEnabled = false @@ -189,6 +192,8 @@ class ExposureNotificationServiceImpl(private val context: Context, private val put("request_keys_count", keys) }.toString()) + database.finishMatching(packageName, params.token) + Handler(Looper.getMainLooper()).post { try { params.callback.onResult(Status.SUCCESS) @@ -197,11 +202,11 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } } - database.finishMatching(packageName, params.token) val match = database.findAllMeasuredExposures(packageName, params.token).isNotEmpty() try { val intent = Intent(if (match) ACTION_EXPOSURE_STATE_UPDATED else ACTION_EXPOSURE_NOT_FOUND) + intent.putExtra(EXTRA_TOKEN, params.token) intent.`package` = packageName Log.d(TAG, "Sending $intent") context.sendOrderedBroadcast(intent, PERMISSION_EXPOSURE_CALLBACK) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt index b10908af..1945d519 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt @@ -29,7 +29,12 @@ class ExposurePreferences(private val context: Context) { val advertiserEnabled get() = scannerEnabled + var lastCleanup + get() = preferences.getLong(PREF_LAST_CLEANUP, 0) + set(value) = preferences.edit().putLong(PREF_LAST_CLEANUP, value).apply() + companion object { private const val PREF_SCANNER_ENABLED = "exposure_scanner_enabled" + private const val PREF_LAST_CLEANUP = "exposure_last_cleanup" } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt index a2f020d7..f180e1d6 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt @@ -17,6 +17,7 @@ import android.content.IntentFilter import android.os.Build import android.os.IBinder import android.util.Log +import org.microg.gms.common.ForegroundServiceContext import java.io.FileDescriptor import java.io.PrintWriter import java.util.* @@ -26,6 +27,7 @@ class ScannerService : Service() { private var started = false private var startTime = 0L private var seenAdvertisements = 0L + private var lastAdvertisement = 0L private lateinit var database: ExposureDatabase private val callback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult?) { @@ -59,6 +61,7 @@ class ScannerService : Service() { get() = getDefaultAdapter().bluetoothLeScanner override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + ForegroundServiceContext.completeForegroundService(this, intent, TAG) super.onStartCommand(intent, flags, startId) startScanIfNeeded() return START_STICKY @@ -69,6 +72,7 @@ class ScannerService : Service() { if (data.size < 16) return // Ignore invalid advertisements database.noteAdvertisement(data.sliceArray(0..15), data.drop(16).toByteArray(), result.rssi) seenAdvertisements++ + lastAdvertisement = System.currentTimeMillis() } fun startScanIfNeeded() { @@ -128,6 +132,13 @@ class ScannerService : Service() { if (started) { writer?.println("Since ${Date(startTime)}") writer?.println("Seen advertisements: $seenAdvertisements") + writer?.println("Last advertisement: ${Date(lastAdvertisement)}") + } + } + + companion object { + fun isNeeded(context: Context): Boolean { + return ExposurePreferences(context).scannerEnabled } } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceTrigger.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceTrigger.kt index b6c5cfc3..a375c9c9 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceTrigger.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceTrigger.kt @@ -16,11 +16,15 @@ class ServiceTrigger : BroadcastReceiver() { @SuppressLint("UnsafeProtectedBroadcastReceiver") override fun onReceive(context: Context, intent: Intent?) { Log.d(TAG, "ServiceTrigger: $intent") - if (ExposurePreferences(context).scannerEnabled) { - ForegroundServiceContext(context).startService(Intent(context, ScannerService::class.java)) + val serviceContext = ForegroundServiceContext(context) + if (ScannerService.isNeeded(context)) { + serviceContext.startService(Intent(context, ScannerService::class.java)) } - if (ExposurePreferences(context).advertiserEnabled) { - ForegroundServiceContext(context).startService(Intent(context, AdvertiserService::class.java)) + if (AdvertiserService.isNeeded(context)) { + serviceContext.startService(Intent(context, AdvertiserService::class.java)) + } + if (CleanupService.isNeeded(context)) { + serviceContext.startService(Intent(context, CleanupService::class.java)) } } } From 6794ab4417df322036af8ec9eaefd22e29e9538b Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 2 Sep 2020 21:10:29 +0200 Subject: [PATCH 09/24] EN: Merge enabled settings --- .../src/main/AndroidManifest.xml | 5 ++ .../exposurenotification/AdvertiserService.kt | 4 +- .../exposurenotification/CleanupService.kt | 2 +- .../ExposureNotificationServiceImpl.kt | 10 +-- .../ExposurePreferences.kt | 13 ++- .../exposurenotification/ScannerService.kt | 5 +- .../exposurenotification/ServiceInfo.kt | 89 +++++++++++++++++++ 7 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceInfo.kt diff --git a/play-services-nearby-core/src/main/AndroidManifest.xml b/play-services-nearby-core/src/main/AndroidManifest.xml index 83a8bda1..e40ecda8 100644 --- a/play-services-nearby-core/src/main/AndroidManifest.xml +++ b/play-services-nearby-core/src/main/AndroidManifest.xml @@ -14,6 +14,9 @@ + + + @@ -24,6 +27,8 @@ + + diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt index 417aaf8e..d3e0b2e8 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt @@ -76,7 +76,7 @@ class AdvertiserService : LifecycleService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { ForegroundServiceContext.completeForegroundService(this, intent, TAG) super.onStartCommand(intent, flags, startId) - if (ExposurePreferences(this).advertiserEnabled) { + if (ExposurePreferences(this).enabled) { loopAdvertising() } else { stopSelf() @@ -191,7 +191,7 @@ class AdvertiserService : LifecycleService() { companion object { fun isNeeded(context: Context): Boolean { - return ExposurePreferences(context).scannerEnabled + return ExposurePreferences(context).enabled } } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt index f7908f85..0883b803 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt @@ -27,7 +27,7 @@ class CleanupService : LifecycleService() { companion object { fun isNeeded(context: Context): Boolean { return ExposurePreferences(context).let { - it.scannerEnabled && it.lastCleanup < System.currentTimeMillis() - CLEANUP_INTERVAL + it.enabled && it.lastCleanup < System.currentTimeMillis() - CLEANUP_INTERVAL } } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index efbb7c74..a14bf339 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -50,13 +50,13 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun start(params: StartParams) { - if (ExposurePreferences(context).scannerEnabled) { + if (ExposurePreferences(context).enabled) { params.callback.onResult(Status(FAILED_ALREADY_STARTED)) return } confirm(CONFIRM_ACTION_START) { resultCode, resultData -> if (resultCode == SUCCESS) { - ExposurePreferences(context).scannerEnabled = true + ExposurePreferences(context).enabled = true } ExposureDatabase.with(context) { database -> database.noteAppAction(packageName, "start", JSONObject().apply { @@ -72,13 +72,13 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun stop(params: StopParams) { - if (!ExposurePreferences(context).scannerEnabled) { + if (!ExposurePreferences(context).enabled) { params.callback.onResult(Status.SUCCESS) return } confirm(CONFIRM_ACTION_STOP) { resultCode, _ -> if (resultCode == SUCCESS) { - ExposurePreferences(context).scannerEnabled = false + ExposurePreferences(context).enabled = false } ExposureDatabase.with(context) { database -> database.noteAppAction(packageName, "stop", JSONObject().apply { @@ -95,7 +95,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val override fun isEnabled(params: IsEnabledParams) { try { - params.callback.onResult(Status.SUCCESS, ExposurePreferences(context).scannerEnabled) + params.callback.onResult(Status.SUCCESS, ExposurePreferences(context).enabled) } catch (e: Exception) { Log.w(TAG, "Callback failed", e) } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt index 1945d519..511e3b02 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt @@ -8,13 +8,21 @@ package org.microg.gms.nearby.exposurenotification import android.content.Context import android.content.Intent import android.content.SharedPreferences +import android.util.Log import androidx.preference.PreferenceManager +import org.microg.gms.common.PackageUtils class ExposurePreferences(private val context: Context) { private var preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - var scannerEnabled + init { + if (context.packageName != PackageUtils.getProcessName()) { + Log.w("Preferences", ExposurePreferences::class.simpleName + " initialized outside main process", RuntimeException()) + } + } + + var enabled get() = preferences.getBoolean(PREF_SCANNER_ENABLED, false) set(newStatus) { preferences.edit().putBoolean(PREF_SCANNER_ENABLED, newStatus).commit() @@ -26,9 +34,6 @@ class ExposurePreferences(private val context: Context) { } } - val advertiserEnabled - get() = scannerEnabled - var lastCleanup get() = preferences.getLong(PREF_LAST_CLEANUP, 0) set(value) = preferences.edit().putLong(PREF_LAST_CLEANUP, value).apply() diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt index f180e1d6..470beca7 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt @@ -7,7 +7,6 @@ package org.microg.gms.nearby.exposurenotification import android.annotation.TargetApi import android.app.Service -import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter.* import android.bluetooth.le.* import android.content.BroadcastReceiver @@ -76,7 +75,7 @@ class ScannerService : Service() { } fun startScanIfNeeded() { - if (ExposurePreferences(this).scannerEnabled) { + if (ExposurePreferences(this).enabled) { startScan() } else { stopSelf() @@ -138,7 +137,7 @@ class ScannerService : Service() { companion object { fun isNeeded(context: Context): Boolean { - return ExposurePreferences(context).scannerEnabled + return ExposurePreferences(context).enabled } } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceInfo.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceInfo.kt new file mode 100644 index 00000000..1379e9ce --- /dev/null +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceInfo.kt @@ -0,0 +1,89 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.nearby.exposurenotification + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.util.Log +import java.io.Serializable +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +private const val ACTION_SERVICE_INFO_REQUEST = "org.microg.gms.nearby.exposurenotification.SERVICE_INFO_REQUEST" +private const val ACTION_UPDATE_CONFIGURATION = "org.microg.gms.nearby.exposurenotification.UPDATE_CONFIGURATION" +private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.nearby.exposurenotification.SERVICE_INFO_RESPONSE" +private const val EXTRA_SERVICE_INFO = "org.microg.gms.nearby.exposurenotification.SERVICE_INFO" +private const val EXTRA_CONFIGURATION = "org.microg.gms.nearby.exposurenotification.CONFIGURATION" + +data class ServiceInfo(val configuration: ServiceConfiguration) : Serializable + +data class ServiceConfiguration(val enabled: Boolean) : Serializable { + fun saveToPrefs(context: Context) { + ExposurePreferences(context).enabled = enabled + } +} + +private fun ExposurePreferences.toConfiguration(): ServiceConfiguration = ServiceConfiguration(enabled) + +class ServiceInfoReceiver : BroadcastReceiver() { + private fun sendInfoResponse(context: Context) { + context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { + setPackage(context.packageName) + putExtra(EXTRA_SERVICE_INFO, ServiceInfo(ExposurePreferences(context).toConfiguration())) + }, null) + } + + override fun onReceive(context: Context, intent: Intent) { + try { + when (intent.action) { + ACTION_UPDATE_CONFIGURATION -> { + (intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context) + } + } + sendInfoResponse(context) + } catch (e: Exception) { + Log.w(TAG, e) + } + } +} + +private suspend fun sendToServiceInfoReceiver(intent: Intent, context: Context): ServiceInfo = suspendCoroutine { + context.registerReceiver(object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + context.unregisterReceiver(this) + val serviceInfo = try { + intent.getSerializableExtra(EXTRA_SERVICE_INFO) as ServiceInfo + } catch (e: Exception) { + it.resumeWithException(e) + return + } + try { + it.resume(serviceInfo) + } catch (e: Exception) { + Log.w(TAG, e) + } + } + }, IntentFilter(ACTION_SERVICE_INFO_RESPONSE)) + try { + context.sendOrderedBroadcast(intent, null) + } catch (e: Exception) { + it.resumeWithException(e) + } +} + +suspend fun getExposureNotificationsServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver( + Intent(context, ServiceInfoReceiver::class.java).apply { + action = ACTION_SERVICE_INFO_REQUEST + }, context) + +suspend fun setExposureNotificationsServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver( + Intent(context, ServiceInfoReceiver::class.java).apply { + action = ACTION_UPDATE_CONFIGURATION + putExtra(EXTRA_CONFIGURATION, configuration) + }, context) From d42d8a81a46624f266e163d11c9d80d74ce767a9 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 2 Sep 2020 21:10:35 +0200 Subject: [PATCH 10/24] Ensure unified client is set up when using gms location --- play-services-location-core/build.gradle | 2 +- .../gms/location/GoogleLocationManager.java | 22 ++++++++++----- .../GoogleLocationManagerService.java | 9 ++++++- .../GoogleLocationManagerServiceImpl.java | 4 +++ .../gms/location/RealLocationProvider.java | 7 ++++- .../gms/location/UnifiedLocationProvider.kt | 27 ++++++++++++++++--- 6 files changed, 59 insertions(+), 12 deletions(-) diff --git a/play-services-location-core/build.gradle b/play-services-location-core/build.gradle index 0556c781..a15115fe 100644 --- a/play-services-location-core/build.gradle +++ b/play-services-location-core/build.gradle @@ -15,10 +15,10 @@ dependencies { implementation "org.microg.nlp:location-v2:$nlpVersion" implementation "org.microg.nlp:location-v3:$nlpVersion" implementation "org.microg.nlp:service:$nlpVersion" - implementation "org.microg.nlp:client:$nlpVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" + api "org.microg.nlp:client:$nlpVersion" api "org.microg.nlp:ui:$nlpVersion" } diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java index ae767666..0ad9e872 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java @@ -64,18 +64,28 @@ public class GoogleLocationManager implements LocationChangeListener { this.gpsProvider = null; } if (Utils.hasSelfPermissionOrNotify(context, Manifest.permission.ACCESS_COARSE_LOCATION)) { - if (locationManager.getAllProviders().contains(NETWORK_PROVIDER)) { - this.networkProvider = new UnifiedLocationProvider(context, this); - } else { - // TODO: Add ability to directly contact UnifiedNlp without the system location provider - this.networkProvider = null; - } + this.networkProvider = new UnifiedLocationProvider(context, this); } else { this.networkProvider = null; } mockProvider = new MockLocationProvider(this); } + public void invokeOnceReady(Runnable runnable) { + Runnable networkRunnable = () -> { + if (networkProvider != null) { + networkProvider.invokeOnceReady(runnable); + } else { + runnable.run(); + } + }; + if (gpsProvider != null) { + gpsProvider.invokeOnceReady(networkRunnable); + } else { + networkRunnable.run(); + } + } + public Location getLastLocation(String packageName) { return getLocation(hasFineLocationPermission(), hasCoarseLocationPermission()); } diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerService.java b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerService.java index 1a105150..f2761010 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerService.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerService.java @@ -17,6 +17,7 @@ package org.microg.gms.location; import android.os.RemoteException; +import android.util.Log; import com.google.android.gms.common.internal.GetServiceRequest; import com.google.android.gms.common.internal.IGmsCallbacks; @@ -33,6 +34,12 @@ public class GoogleLocationManagerService extends BaseService { @Override public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException { - callback.onPostInitComplete(0, impl.asBinder(), null); + impl.invokeOnceReady(() -> { + try { + callback.onPostInitComplete(0, impl.asBinder(), null); + } catch (RemoteException e) { + Log.w(TAG, e); + } + }); } } diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerServiceImpl.java b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerServiceImpl.java index 31ac74a3..b4315769 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerServiceImpl.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerServiceImpl.java @@ -68,6 +68,10 @@ public class GoogleLocationManagerServiceImpl extends IGoogleLocationManagerServ this.context = context; } + public void invokeOnceReady(Runnable runnable) { + getLocationManager().invokeOnceReady(runnable); + } + private GoogleLocationManager getLocationManager() { if (locationManager == null) locationManager = new GoogleLocationManager(context); diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java b/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java index 0fe9e67b..98e1947a 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java @@ -30,7 +30,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @SuppressWarnings("MissingPermission") public class RealLocationProvider { public static final String TAG = "GmsLocProviderReal"; - private static final int MIN_GPS_TIME = 30000; + private static final int MIN_GPS_TIME = 10000; private final LocationManager locationManager; private final String name; @@ -76,6 +76,11 @@ public class RealLocationProvider { if (newLocation != null) lastLocation = newLocation; } + public void invokeOnceReady(Runnable runnable) { + // Always ready + runnable.run(); + } + public Location getLastLocation() { if (!connected.get()) { updateLastLocation(); diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt b/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt index 51cbfbf5..e56e6c9f 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt +++ b/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt @@ -3,6 +3,7 @@ package org.microg.gms.location import android.content.Context import android.location.Location import android.util.Log +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.microg.nlp.client.UnifiedLocationClient @@ -22,10 +23,30 @@ class UnifiedLocationProvider(context: Context?, changeListener: LocationChangeL changeListener.onLocationChanged() } } + private var ready = false + private val invokeOnceReady = hashSetOf() private fun updateLastLocation() { - GlobalScope.launch { - client.getLastLocation()?.let { lastLocation = it } + GlobalScope.launch(Dispatchers.Main) { + Log.d(TAG, "unified network: requesting last location") + val lastLocation = client.getLastLocation() + Log.d(TAG, "unified network: got last location: $lastLocation") + if (lastLocation != null) { + this@UnifiedLocationProvider.lastLocation = lastLocation + } + synchronized(invokeOnceReady) { + for (runnable in invokeOnceReady) { + runnable.run() + } + ready = true + } + } + } + + fun invokeOnceReady(runnable: Runnable) { + synchronized(invokeOnceReady) { + if (ready) runnable.run() + else invokeOnceReady.add(runnable) } } @@ -92,4 +113,4 @@ class UnifiedLocationProvider(context: Context?, changeListener: LocationChangeL this.changeListener = changeListener updateLastLocation() } -} \ No newline at end of file +} From 3ef330ad7bacc0d38e7eba48452d2c472337c879 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 2 Sep 2020 21:11:53 +0200 Subject: [PATCH 11/24] Mapbox: Fix crash when using Marker.getTag() when tag was not set yet --- .../src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt | 2 +- .../main/kotlin/org/microg/gms/maps/mapbox/model/Marker.kt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt index 45e29593..ca565a05 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt @@ -272,7 +272,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) return fill } - override fun addMarker(options: MarkerOptions): IMarkerDelegate? { + override fun addMarker(options: MarkerOptions): IMarkerDelegate { val marker = MarkerImpl(this, "m${markerId++}", options) synchronized(this) { val symbolManager = symbolManager diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Marker.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Marker.kt index 981bc860..129157c0 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Marker.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Marker.kt @@ -18,6 +18,7 @@ package org.microg.gms.maps.mapbox.model import android.util.Log import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.ObjectWrapper import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MarkerOptions import com.google.android.gms.maps.model.internal.IMarkerDelegate @@ -199,9 +200,9 @@ class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options this.tag = obj } - override fun getTag(): IObjectWrapper? = tag + override fun getTag(): IObjectWrapper = tag ?: ObjectWrapper.wrap(null) companion object { private val TAG = "GmsMapMarker" } -} \ No newline at end of file +} From d641ca7e7e9a147f65120c5cb40216c11744b702 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 2 Sep 2020 21:41:15 +0200 Subject: [PATCH 12/24] Fix UI mismatching actual configuration due to multiprocess --- .../src/main/AndroidManifest.xml | 6 +- .../org/microg/gms/checkin/CheckinPrefs.java | 22 +++- .../microg/gms/checkin/LastCheckinInfo.java | 2 +- .../java/org/microg/gms/gcm/GcmDatabase.java | 4 + .../java/org/microg/gms/gcm/GcmPrefs.java | 53 +++++--- .../java/org/microg/gms/gcm/McsService.java | 46 +++---- .../org/microg/gms/gcm/TriggerReceiver.java | 2 +- .../org/microg/gms/snet/SafetyNetPrefs.java | 49 ++++++-- .../org/microg/gms/ui/SettingsFragment.java | 115 ------------------ .../org/microg/gms/checkin/ServiceInfo.kt | 93 ++++++++++++++ .../kotlin/org/microg/gms/gcm/ServiceInfo.kt | 92 ++++++++++++++ .../kotlin/org/microg/gms/gcm/StatusInfo.kt | 53 -------- .../kotlin/org/microg/gms/snet/ServiceInfo.kt | 90 ++++++++++++++ .../gms/ui/DeviceRegistrationFragment.kt | 21 +++- .../DeviceRegistrationPreferencesFragment.kt | 23 ++-- .../gms/ui/ExposureNotificationsFragment.kt | 21 +++- ...xposureNotificationsPreferencesFragment.kt | 8 +- .../microg/gms/ui/PushNotificationFragment.kt | 25 +++- .../ui/PushNotificationPreferencesFragment.kt | 8 +- .../org/microg/gms/ui/SafetyNetFragment.kt | 28 ++++- .../org/microg/gms/ui/SettingsFragment.kt | 100 +++++++++++++++ .../src/main/res/values/strings.xml | 1 + .../xml/preferences_device_registration.xml | 5 + 23 files changed, 608 insertions(+), 259 deletions(-) delete mode 100644 play-services-core/src/main/java/org/microg/gms/ui/SettingsFragment.java create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt delete mode 100644 play-services-core/src/main/kotlin/org/microg/gms/gcm/StatusInfo.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/snet/ServiceInfo.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 831a1514..d2305954 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -193,6 +193,8 @@ + + @@ -238,7 +240,7 @@ - + @@ -627,6 +629,8 @@ + + diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinPrefs.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinPrefs.java index f090c97e..348f4244 100644 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinPrefs.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinPrefs.java @@ -9,6 +9,11 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; +import android.util.Log; + +import org.microg.gms.common.PackageUtils; + +import java.io.File; public class CheckinPrefs implements SharedPreferences.OnSharedPreferenceChangeListener { public static final String PREF_ENABLE_CHECKIN = "checkin_enable_service"; @@ -16,6 +21,9 @@ public class CheckinPrefs implements SharedPreferences.OnSharedPreferenceChangeL public static CheckinPrefs get(Context context) { if (INSTANCE == null) { + if (!context.getPackageName().equals(PackageUtils.getProcessName())) { + Log.w("Preferences", CheckinPrefs.class.getName() + " initialized outside main process", new RuntimeException()); + } if (context == null) return new CheckinPrefs(null); INSTANCE = new CheckinPrefs(context.getApplicationContext()); } @@ -23,18 +31,30 @@ public class CheckinPrefs implements SharedPreferences.OnSharedPreferenceChangeL } private SharedPreferences preferences; + private SharedPreferences systemDefaultPreferences; private boolean checkinEnabled = false; private CheckinPrefs(Context context) { if (context != null) { preferences = PreferenceManager.getDefaultSharedPreferences(context); preferences.registerOnSharedPreferenceChangeListener(this); + try { + systemDefaultPreferences = (SharedPreferences) Context.class.getDeclaredMethod("getSharedPreferences", File.class, int.class).invoke(context, new File("/system/etc/microg.xml"), Context.MODE_PRIVATE); + } catch (Exception ignored) { + } update(); } } + private boolean getSettingsBoolean(String key, boolean def) { + if (systemDefaultPreferences != null) { + def = systemDefaultPreferences.getBoolean(key, def); + } + return preferences.getBoolean(key, def); + } + private void update() { - checkinEnabled = preferences.getBoolean(PREF_ENABLE_CHECKIN, false); + checkinEnabled = getSettingsBoolean(PREF_ENABLE_CHECKIN, false); } @Override diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/LastCheckinInfo.java b/play-services-core/src/main/java/org/microg/gms/checkin/LastCheckinInfo.java index 4cdfd88c..30e0a1af 100644 --- a/play-services-core/src/main/java/org/microg/gms/checkin/LastCheckinInfo.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/LastCheckinInfo.java @@ -55,6 +55,6 @@ public class LastCheckinInfo { .putLong(PREF_SECURITY_TOKEN, securityToken) .putString(PREF_VERSION_INFO, versionInfo) .putString(PREF_DEVICE_DATA_VERSION_INFO, deviceDataVersionInfo) - .apply(); + .commit(); } } diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/GcmDatabase.java b/play-services-core/src/main/java/org/microg/gms/gcm/GcmDatabase.java index 3dd67b53..bc524047 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/GcmDatabase.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/GcmDatabase.java @@ -21,6 +21,7 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.os.Build; import android.text.TextUtils; import java.util.ArrayList; @@ -65,6 +66,9 @@ public class GcmDatabase extends SQLiteOpenHelper { public GcmDatabase(Context context) { super(context, DB_NAME, null, DB_VERSION); this.context = context; + if (Build.VERSION.SDK_INT >= 16) { + this.setWriteAheadLoggingEnabled(true); + } } public static class App { diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/GcmPrefs.java b/play-services-core/src/main/java/org/microg/gms/gcm/GcmPrefs.java index 4bfe71d5..568efd1f 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/GcmPrefs.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/GcmPrefs.java @@ -24,6 +24,9 @@ import android.net.NetworkInfo; import android.preference.PreferenceManager; import android.util.Log; +import org.microg.gms.common.PackageUtils; + +import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -50,6 +53,9 @@ public class GcmPrefs implements SharedPreferences.OnSharedPreferenceChangeListe public static GcmPrefs get(Context context) { if (INSTANCE == null) { + if (!context.getPackageName().equals(PackageUtils.getProcessName())) { + Log.w("Preferences", GcmPrefs.class.getName() + " initialized outside main process", new RuntimeException()); + } if (context == null) return new GcmPrefs(null); INSTANCE = new GcmPrefs(context.getApplicationContext()); } @@ -70,30 +76,43 @@ public class GcmPrefs implements SharedPreferences.OnSharedPreferenceChangeListe private int learntMobile = 300000; private int learntOther = 300000; - private SharedPreferences defaultPreferences; + private SharedPreferences preferences; + private SharedPreferences systemDefaultPreferences; private GcmPrefs(Context context) { if (context != null) { - defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context); - defaultPreferences.registerOnSharedPreferenceChangeListener(this); + preferences = PreferenceManager.getDefaultSharedPreferences(context); + preferences.registerOnSharedPreferenceChangeListener(this); + try { + systemDefaultPreferences = (SharedPreferences) Context.class.getDeclaredMethod("getSharedPreferences", File.class, int.class).invoke(context, new File("/system/etc/microg.xml"), Context.MODE_PRIVATE); + } catch (Exception ignored) { + } update(); } } + private boolean getSettingsBoolean(String key, boolean def) { + if (systemDefaultPreferences != null) { + def = systemDefaultPreferences.getBoolean(key, def); + } + return preferences.getBoolean(key, def); + } + public void update() { - gcmLogEnabled = defaultPreferences.getBoolean(PREF_FULL_LOG, true); - lastPersistedId = defaultPreferences.getString(PREF_LAST_PERSISTENT_ID, ""); - confirmNewApps = defaultPreferences.getBoolean(PREF_CONFIRM_NEW_APPS, false); - gcmEnabled = defaultPreferences.getBoolean(PREF_ENABLE_GCM, false); + gcmEnabled = getSettingsBoolean(PREF_ENABLE_GCM, false); + gcmLogEnabled = getSettingsBoolean(PREF_FULL_LOG, true); + confirmNewApps = getSettingsBoolean(PREF_CONFIRM_NEW_APPS, false); - networkMobile = Integer.parseInt(defaultPreferences.getString(PREF_NETWORK_MOBILE, "0")); - networkWifi = Integer.parseInt(defaultPreferences.getString(PREF_NETWORK_WIFI, "0")); - networkRoaming = Integer.parseInt(defaultPreferences.getString(PREF_NETWORK_ROAMING, "0")); - networkOther = Integer.parseInt(defaultPreferences.getString(PREF_NETWORK_OTHER, "0")); + lastPersistedId = preferences.getString(PREF_LAST_PERSISTENT_ID, ""); - learntMobile = defaultPreferences.getInt(PREF_LEARNT_MOBILE, 300000); - learntWifi = defaultPreferences.getInt(PREF_LEARNT_WIFI, 300000); - learntOther = defaultPreferences.getInt(PREF_LEARNT_OTHER, 300000); + networkMobile = Integer.parseInt(preferences.getString(PREF_NETWORK_MOBILE, "0")); + networkWifi = Integer.parseInt(preferences.getString(PREF_NETWORK_WIFI, "0")); + networkRoaming = Integer.parseInt(preferences.getString(PREF_NETWORK_ROAMING, "0")); + networkOther = Integer.parseInt(preferences.getString(PREF_NETWORK_OTHER, "0")); + + learntMobile = preferences.getInt(PREF_LEARNT_MOBILE, 300000); + learntWifi = preferences.getInt(PREF_LEARNT_WIFI, 300000); + learntOther = preferences.getInt(PREF_LEARNT_OTHER, 300000); } public String getNetworkPrefForInfo(NetworkInfo info) { @@ -182,7 +201,7 @@ public class GcmPrefs implements SharedPreferences.OnSharedPreferenceChangeListe learntMobile = Math.max(MIN_INTERVAL, Math.min(learntMobile, MAX_INTERVAL)); learntWifi = Math.max(MIN_INTERVAL, Math.min(learntWifi, MAX_INTERVAL)); learntOther = Math.max(MIN_INTERVAL, Math.min(learntOther, MAX_INTERVAL)); - defaultPreferences.edit().putInt(PREF_LEARNT_MOBILE, learntMobile).putInt(PREF_LEARNT_WIFI, learntWifi).putInt(PREF_LEARNT_OTHER, learntOther).apply(); + preferences.edit().putInt(PREF_LEARNT_MOBILE, learntMobile).putInt(PREF_LEARNT_WIFI, learntWifi).putInt(PREF_LEARNT_OTHER, learntOther).apply(); } @Override @@ -223,11 +242,11 @@ public class GcmPrefs implements SharedPreferences.OnSharedPreferenceChangeListe public void extendLastPersistedId(String id) { if (!lastPersistedId.isEmpty()) lastPersistedId += "|"; lastPersistedId += id; - defaultPreferences.edit().putString(PREF_LAST_PERSISTENT_ID, lastPersistedId).apply(); + preferences.edit().putString(PREF_LAST_PERSISTENT_ID, lastPersistedId).apply(); } public void clearLastPersistedId() { lastPersistedId = ""; - defaultPreferences.edit().putString(PREF_LAST_PERSISTENT_ID, lastPersistedId).apply(); + preferences.edit().putString(PREF_LAST_PERSISTENT_ID, lastPersistedId).apply(); } } diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java index 32a91736..03dc0d97 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java @@ -176,8 +176,8 @@ public class McsService extends Service implements Handler.Callback { } } - private static void logd(String msg) { - if (GcmPrefs.get(null).isGcmLogEnabled()) Log.d(TAG, msg); + private static void logd(Context context, String msg) { + if (context == null || GcmPrefs.get(context).isGcmLogEnabled()) Log.d(TAG, msg); } @Override @@ -237,7 +237,7 @@ public class McsService extends Service implements Handler.Callback { public synchronized static boolean isConnected() { if (inputStream == null || !inputStream.isAlive() || outputStream == null || !outputStream.isAlive()) { - logd("Connection is not enabled or dead."); + logd(null, "Connection is not enabled or dead."); return false; } // consider connection to be dead if we did not receive an ack within twice the heartbeat interval @@ -245,7 +245,7 @@ public class McsService extends Service implements Handler.Callback { if (heartbeatMs < 0) { closeAll(); } else if (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime > 2 * heartbeatMs) { - logd("No heartbeat for " + (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime) / 1000 + " seconds, connection assumed to be dead after " + 2 * heartbeatMs / 1000 + " seconds"); + logd(null, "No heartbeat for " + (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime) / 1000 + " seconds, connection assumed to be dead after " + 2 * heartbeatMs / 1000 + " seconds"); GcmPrefs.get(null).learnTimeout(activeNetworkPref); return false; } @@ -259,7 +259,7 @@ public class McsService extends Service implements Handler.Callback { public static void scheduleReconnect(Context context) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); long delay = getCurrentDelay(); - logd("Scheduling reconnect in " + delay / 1000 + " seconds..."); + logd(context, "Scheduling reconnect in " + delay / 1000 + " seconds..."); PendingIntent pi = PendingIntent.getBroadcast(context, 1, new Intent(ACTION_RECONNECT, null, context, TriggerReceiver.class), 0); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delay, pi); @@ -275,7 +275,7 @@ public class McsService extends Service implements Handler.Callback { if (heartbeatMs < 0) { closeAll(); } - logd("Scheduling heartbeat in " + heartbeatMs / 1000 + " seconds..."); + logd(context, "Scheduling heartbeat in " + heartbeatMs / 1000 + " seconds..."); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // This is supposed to work even when running in idle and without battery optimization disabled alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, heartbeatIntent); @@ -449,11 +449,11 @@ public class McsService extends Service implements Handler.Callback { return; } - logd("Starting MCS connection..."); + logd(this, "Starting MCS connection..."); Socket socket = new Socket(SERVICE_HOST, SERVICE_PORT); - logd("Connected to " + SERVICE_HOST + ":" + SERVICE_PORT); + logd(this, "Connected to " + SERVICE_HOST + ":" + SERVICE_PORT); sslSocket = SSLContext.getDefault().getSocketFactory().createSocket(socket, SERVICE_HOST, SERVICE_PORT, true); - logd("Activated SSL with " + SERVICE_HOST + ":" + SERVICE_PORT); + logd(this, "Activated SSL with " + SERVICE_HOST + ":" + SERVICE_PORT); inputStream = new McsInputStream(sslSocket.getInputStream(), rootHandler); outputStream = new McsOutputStream(sslSocket.getOutputStream(), rootHandler); inputStream.start(); @@ -476,7 +476,7 @@ public class McsService extends Service implements Handler.Callback { private void handleLoginResponse(LoginResponse loginResponse) { if (loginResponse.error == null) { GcmPrefs.get(this).clearLastPersistedId(); - logd("Logged in"); + logd(this, "Logged in"); wakeLock.release(); } else { throw new RuntimeException("Could not login: " + loginResponse.error); @@ -561,13 +561,13 @@ public class McsService extends Service implements Handler.Callback { if (receiverPermission == null) { // Without receiver permission, we only restrict by package name - logd("Deliver message to all receivers in package " + packageName); + logd(this, "Deliver message to all receivers in package " + packageName); intent.setPackage(packageName); sendOrderedBroadcast(intent, null); } else { List infos = getPackageManager().queryBroadcastReceivers(intent, PackageManager.GET_RESOLVED_FILTER); if (infos == null || infos.isEmpty()) { - logd("No target for message, wut?"); + logd(this, "No target for message, wut?"); } else { for (ResolveInfo resolveInfo : infos) { Intent targetIntent = new Intent(intent); @@ -578,7 +578,7 @@ public class McsService extends Service implements Handler.Callback { try { if (getUserIdMethod != null && addPowerSaveTempWhitelistAppMethod != null && deviceIdleController != null) { int userId = (int) getUserIdMethod.invoke(null, getPackageManager().getApplicationInfo(packageName, 0).uid); - logd("Adding app " + packageName + " for userId " + userId + " to the temp whitelist"); + logd(this, "Adding app " + packageName + " for userId " + userId + " to the temp whitelist"); addPowerSaveTempWhitelistAppMethod.invoke(deviceIdleController, packageName, 10000, userId, "GCM Push"); } } catch (Exception e) { @@ -586,11 +586,11 @@ public class McsService extends Service implements Handler.Callback { } } // We don't need receiver permission for our own package - logd("Deliver message to own receiver " + resolveInfo); + logd(this, "Deliver message to own receiver " + resolveInfo); sendOrderedBroadcast(targetIntent, null); } else if (resolveInfo.filter.hasCategory(packageName)) { // Permission required - logd("Deliver message to third-party receiver (with permission check)" + resolveInfo); + logd(this, "Deliver message to third-party receiver (with permission check)" + resolveInfo); sendOrderedBroadcast(targetIntent, receiverPermission); } } @@ -640,21 +640,21 @@ public class McsService extends Service implements Handler.Callback { return true; case MSG_INPUT_ERROR: case MSG_OUTPUT_ERROR: - logd("I/O error: " + msg.obj); + logd(this, "I/O error: " + msg.obj); rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, msg.obj)); return true; case MSG_TEARDOWN: - logd("Teardown initiated, reason: " + msg.obj); + logd(this, "Teardown initiated, reason: " + msg.obj); handleTeardown(msg); return true; case MSG_CONNECT: - logd("Connect initiated, reason: " + msg.obj); + logd(this, "Connect initiated, reason: " + msg.obj); if (!isConnected()) { connect(); } return true; case MSG_HEARTBEAT: - logd("Heartbeat initiated, reason: " + msg.obj); + logd(this, "Heartbeat initiated, reason: " + msg.obj); if (isConnected()) { HeartbeatPing.Builder ping = new HeartbeatPing.Builder(); if (inputStream.newStreamIdAvailable()) { @@ -663,12 +663,12 @@ public class McsService extends Service implements Handler.Callback { send(MCS_HEARTBEAT_PING_TAG, ping.build()); scheduleHeartbeat(this); } else { - logd("Ignoring heartbeat, not connected!"); + logd(this, "Ignoring heartbeat, not connected!"); scheduleReconnect(this); } return true; case MSG_ACK: - logd("Ack initiated, reason: " + msg.obj); + logd(this, "Ack initiated, reason: " + msg.obj); if (isConnected()) { IqStanza.Builder iq = new IqStanza.Builder() .type(IqStanza.IqType.SET) @@ -680,11 +680,11 @@ public class McsService extends Service implements Handler.Callback { } send(MCS_IQ_STANZA_TAG, iq.build()); } else { - logd("Ignoring ack, not connected!"); + logd(this, "Ignoring ack, not connected!"); } return true; case MSG_OUTPUT_READY: - logd("Sending login request..."); + logd(this, "Sending login request..."); send(MCS_LOGIN_REQUEST_TAG, buildLoginRequest()); return true; case MSG_OUTPUT_DONE: diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java index 3bc5e9a5..1166f385 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java @@ -45,7 +45,7 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { public synchronized static void register(Context context) { if (SDK_INT >= N && !registered) { IntentFilter intentFilter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"); - context.registerReceiver(new TriggerReceiver(), intentFilter); + context.getApplicationContext().registerReceiver(new TriggerReceiver(), intentFilter); registered = true; } } diff --git a/play-services-core/src/main/java/org/microg/gms/snet/SafetyNetPrefs.java b/play-services-core/src/main/java/org/microg/gms/snet/SafetyNetPrefs.java index a6475791..e3c11a89 100644 --- a/play-services-core/src/main/java/org/microg/gms/snet/SafetyNetPrefs.java +++ b/play-services-core/src/main/java/org/microg/gms/snet/SafetyNetPrefs.java @@ -19,6 +19,11 @@ package org.microg.gms.snet; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; +import android.util.Log; + +import org.microg.gms.common.PackageUtils; + +import java.io.File; public class SafetyNetPrefs implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String OFFICIAL_ATTEST_BASE_URL = "https://www.googleapis.com/androidcheck/v1/attestations/attest"; @@ -33,6 +38,9 @@ public class SafetyNetPrefs implements SharedPreferences.OnSharedPreferenceChang public static SafetyNetPrefs get(Context context) { if (INSTANCE == null) { + if (!context.getPackageName().equals(PackageUtils.getProcessName())) { + Log.w("Preferences", SafetyNetPrefs.class.getName() + " initialized outside main process", new RuntimeException()); + } if (context == null) return new SafetyNetPrefs(null); INSTANCE = new SafetyNetPrefs(context.getApplicationContext()); } @@ -45,22 +53,41 @@ public class SafetyNetPrefs implements SharedPreferences.OnSharedPreferenceChang private boolean thirdParty; private String customUrl; - private SharedPreferences defaultPreferences; + private SharedPreferences preferences; + private SharedPreferences systemDefaultPreferences; private SafetyNetPrefs(Context context) { if (context != null) { - defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context); - defaultPreferences.registerOnSharedPreferenceChangeListener(this); + preferences = PreferenceManager.getDefaultSharedPreferences(context); + preferences.registerOnSharedPreferenceChangeListener(this); + try { + systemDefaultPreferences = (SharedPreferences) Context.class.getDeclaredMethod("getSharedPreferences", File.class, int.class).invoke(context, new File("/system/etc/microg.xml"), Context.MODE_PRIVATE); + } catch (Exception ignored) { + } update(); } } + private boolean getSettingsBoolean(String key, boolean def) { + if (systemDefaultPreferences != null) { + def = systemDefaultPreferences.getBoolean(key, def); + } + return preferences.getBoolean(key, def); + } + + private String getSettingsString(String key, String def) { + if (systemDefaultPreferences != null) { + def = systemDefaultPreferences.getString(key, def); + } + return preferences.getString(key, def); + } + public void update() { - disabled = defaultPreferences.getBoolean(PREF_SNET_DISABLED, true); - official = defaultPreferences.getBoolean(PREF_SNET_OFFICIAL, false); - selfSigned = defaultPreferences.getBoolean(PREF_SNET_SELF_SIGNED, false); - thirdParty = defaultPreferences.getBoolean(PREF_SNET_THIRD_PARTY, false); - customUrl = defaultPreferences.getString(PREF_SNET_CUSTOM_URL, null); + disabled = getSettingsBoolean(PREF_SNET_DISABLED, true); + official = getSettingsBoolean(PREF_SNET_OFFICIAL, false); + selfSigned = getSettingsBoolean(PREF_SNET_SELF_SIGNED, false); + thirdParty = getSettingsBoolean(PREF_SNET_THIRD_PARTY, false); + customUrl = getSettingsString(PREF_SNET_CUSTOM_URL, null); } @Override @@ -73,11 +100,13 @@ public class SafetyNetPrefs implements SharedPreferences.OnSharedPreferenceChang } public void setEnabled(boolean enabled) { - defaultPreferences.edit().putBoolean(PREF_SNET_DISABLED, !enabled).apply(); + SharedPreferences.Editor edit = preferences.edit(); + edit.putBoolean(PREF_SNET_DISABLED, !enabled); if (enabled && !isEnabled()) { official = true; - defaultPreferences.edit().putBoolean(PREF_SNET_OFFICIAL, true).apply(); + edit.putBoolean(PREF_SNET_OFFICIAL, true); } + edit.commit(); } public boolean isSelfSigned() { diff --git a/play-services-core/src/main/java/org/microg/gms/ui/SettingsFragment.java b/play-services-core/src/main/java/org/microg/gms/ui/SettingsFragment.java deleted file mode 100644 index c0e37661..00000000 --- a/play-services-core/src/main/java/org/microg/gms/ui/SettingsFragment.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.microg.gms.ui; - -import android.os.Build; -import android.os.Bundle; - -import androidx.annotation.Nullable; -import androidx.navigation.NavOptions; -import androidx.navigation.fragment.NavHostFragment; - -import com.google.android.gms.R; - -import org.microg.gms.checkin.CheckinPrefs; -import org.microg.gms.gcm.GcmDatabase; -import org.microg.gms.gcm.GcmPrefs; -import org.microg.gms.nearby.exposurenotification.ExposurePreferences; -import org.microg.gms.snet.SafetyNetPrefs; -import org.microg.tools.ui.ResourceSettingsFragment; - -public class SettingsFragment extends ResourceSettingsFragment { - - public static final String PREF_ABOUT = "pref_about"; - public static final String PREF_GCM = "pref_gcm"; - public static final String PREF_SNET = "pref_snet"; - public static final String PREF_UNIFIEDNLP = "pref_unifiednlp"; - public static final String PREF_CHECKIN = "pref_checkin"; - public static final String PREF_EXPOSURE = "pref_exposure"; - - public SettingsFragment() { - preferencesResource = R.xml.preferences_start; - } - - @Override - public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) { - super.onCreatePreferences(savedInstanceState, rootKey); - updateDetails(); - } - - @Override - public void onResume() { - super.onResume(); - updateDetails(); - } - - private void updateDetails() { - findPreference(PREF_ABOUT).setSummary(getString(R.string.about_version_str, AboutFragment.getSelfVersion(getContext()))); - findPreference(PREF_ABOUT).setOnPreferenceClickListener(preference -> { - UtilsKt.navigate(NavHostFragment.findNavController(SettingsFragment.this), getContext(), R.id.openAbout, null); - return true; - }); - if (GcmPrefs.get(getContext()).isEnabled()) { - GcmDatabase database = new GcmDatabase(getContext()); - int regCount = database.getRegistrationList().size(); - database.close(); - findPreference(PREF_GCM).setSummary(getString(R.string.service_status_enabled_short) + " - " + getResources().getQuantityString(R.plurals.gcm_registered_apps_counter, regCount, regCount)); - } else { - findPreference(PREF_GCM).setSummary(R.string.service_status_disabled_short); - } - findPreference(PREF_GCM).setOnPreferenceClickListener(preference -> { - UtilsKt.navigate(NavHostFragment.findNavController(SettingsFragment.this), getContext(), R.id.openGcmSettings, null); - return true; - }); - - if (SafetyNetPrefs.get(getContext()).isEnabled()) { - String snet_info = ""; - - if (SafetyNetPrefs.get(getContext()).isOfficial()) { - snet_info = getString(R.string.pref_snet_status_official_info); - } else if (SafetyNetPrefs.get(getContext()).isSelfSigned()) { - snet_info = getString(R.string.pref_snet_status_self_signed_info); - } else if (SafetyNetPrefs.get(getContext()).isThirdParty()) { - snet_info = getString(R.string.pref_snet_status_third_party_info); - } - - findPreference(PREF_SNET).setSummary(getString(R.string.service_status_enabled_short)); - } else { - findPreference(PREF_SNET).setSummary(R.string.service_status_disabled_short); - } - findPreference(PREF_SNET).setOnPreferenceClickListener(preference -> { - UtilsKt.navigate(NavHostFragment.findNavController(SettingsFragment.this), getContext(), R.id.openSafetyNetSettings, null); - return true; - }); - -// Preferences unifiedNlPrefs = new Preferences(getContext()); -// int backendCount = TextUtils.isEmpty(unifiedNlPrefs.getLocationBackends()) ? 0 : -// Preferences.splitBackendString(unifiedNlPrefs.getLocationBackends()).length; -// backendCount += TextUtils.isEmpty(unifiedNlPrefs.getGeocoderBackends()) ? 0 : -// Preferences.splitBackendString(unifiedNlPrefs.getGeocoderBackends()).length; -// findPreference(PREF_UNIFIEDNLP).setSummary(getResources().getQuantityString(R.plurals.pref_unifiednlp_summary, backendCount, backendCount)); - findPreference(PREF_UNIFIEDNLP).setOnPreferenceClickListener(preference -> { - UtilsKt.navigate(NavHostFragment.findNavController(SettingsFragment.this), getContext(), R.id.openUnifiedNlpSettings, null); - return true; - }); - if (Build.VERSION.SDK_INT >= 21) { - findPreference(PREF_EXPOSURE).setVisible(true); - if (new ExposurePreferences(getContext()).getScannerEnabled()) { - findPreference(PREF_EXPOSURE).setSummary(getString(R.string.service_status_enabled_short)); - } else { - findPreference(PREF_EXPOSURE).setSummary(R.string.service_status_disabled_short); - } - findPreference(PREF_EXPOSURE).setOnPreferenceClickListener(preference -> { - UtilsKt.navigate(NavHostFragment.findNavController(SettingsFragment.this), getContext(), R.id.openExposureNotificationSettings, null); - return true; - }); - } else { - findPreference(PREF_EXPOSURE).setVisible(false); - } - - boolean checkinEnabled = CheckinPrefs.get(getContext()).isEnabled(); - findPreference(PREF_CHECKIN).setSummary(checkinEnabled ? R.string.service_status_enabled_short : R.string.service_status_disabled_short); - findPreference(PREF_CHECKIN).setOnPreferenceClickListener(preference -> { - UtilsKt.navigate(NavHostFragment.findNavController(SettingsFragment.this), getContext(), R.id.openCheckinSettings, null); - return true; - }); - } -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt new file mode 100644 index 00000000..d8902b8f --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.checkin + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.util.Log +import java.io.Serializable +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +private const val ACTION_SERVICE_INFO_REQUEST = "org.microg.gms.checkin.SERVICE_INFO_REQUEST" +private const val ACTION_UPDATE_CONFIGURATION = "org.microg.gms.checkin.UPDATE_CONFIGURATION" +private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.checkin.SERVICE_INFO_RESPONSE" +private const val EXTRA_SERVICE_INFO = "org.microg.gms.checkin.SERVICE_INFO" +private const val EXTRA_CONFIGURATION = "org.microg.gms.checkin.CONFIGURATION" +private const val TAG = "GmsCheckinStatusInfo" + +data class ServiceInfo(val configuration: ServiceConfiguration, val lastCheckin: Long, val androidId: Long) : Serializable + +data class ServiceConfiguration(val enabled: Boolean) : Serializable { + fun saveToPrefs(context: Context) { + CheckinPrefs.setEnabled(context, enabled) + } +} + +private fun CheckinPrefs.toConfiguration(): ServiceConfiguration = ServiceConfiguration(isEnabled) + +class ServiceInfoReceiver : BroadcastReceiver() { + private fun sendInfoResponse(context: Context) { + context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { + setPackage(context.packageName) + val checkinInfo = LastCheckinInfo.read(context) + putExtra(EXTRA_SERVICE_INFO, ServiceInfo(CheckinPrefs.get(context).toConfiguration(), checkinInfo.lastCheckin, checkinInfo.androidId)) + }, null) + } + + override fun onReceive(context: Context, intent: Intent) { + try { + when (intent.action) { + ACTION_UPDATE_CONFIGURATION -> { + (intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context) + } + } + sendInfoResponse(context) + } catch (e: Exception) { + Log.w(TAG, e) + } + } +} + + + +private suspend fun sendToServiceInfoReceiver(intent: Intent, context: Context): ServiceInfo = suspendCoroutine { + context.registerReceiver(object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + context.unregisterReceiver(this) + val serviceInfo = try { + intent.getSerializableExtra(EXTRA_SERVICE_INFO) as ServiceInfo + } catch (e: Exception) { + it.resumeWithException(e) + return + } + try { + it.resume(serviceInfo) + } catch (e: Exception) { + Log.w(TAG, e) + } + } + }, IntentFilter(ACTION_SERVICE_INFO_RESPONSE)) + try { + context.sendOrderedBroadcast(intent, null) + } catch (e: Exception) { + it.resumeWithException(e) + } +} + +suspend fun getCheckinServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver( + Intent(context, ServiceInfoReceiver::class.java).apply { + action = ACTION_SERVICE_INFO_REQUEST + }, context) + +suspend fun setCheckinServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver( + Intent(context, ServiceInfoReceiver::class.java).apply { + action = ACTION_UPDATE_CONFIGURATION + putExtra(EXTRA_CONFIGURATION, configuration) + }, context) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt new file mode 100644 index 00000000..ade5652d --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.gcm + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.util.Log +import java.io.Serializable +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +private const val ACTION_SERVICE_INFO_REQUEST = "org.microg.gms.gcm.SERVICE_INFO_REQUEST" +private const val ACTION_UPDATE_CONFIGURATION = "org.microg.gms.gcm.UPDATE_CONFIGURATION" +private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.gcm.SERVICE_INFO_RESPONSE" +private const val EXTRA_SERVICE_INFO = "org.microg.gms.gcm.SERVICE_INFO" +private const val EXTRA_CONFIGURATION = "org.microg.gms.gcm.CONFIGURATION" +private const val TAG = "GmsGcmStatusInfo" + +data class ServiceInfo(val configuration: ServiceConfiguration, val connected: Boolean, val startTimestamp: Long) : Serializable + +// TODO: Intervals +data class ServiceConfiguration(val enabled: Boolean, val confirmNewApps: Boolean) : Serializable { + fun saveToPrefs(context: Context) { + GcmPrefs.setEnabled(context, enabled) + // TODO: confirm new apps + } +} + +private fun GcmPrefs.toConfiguration(): ServiceConfiguration = ServiceConfiguration(isEnabled, isConfirmNewApps) + +class ServiceInfoReceiver : BroadcastReceiver() { + private fun sendInfoResponse(context: Context) { + context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { + setPackage(context.packageName) + putExtra(EXTRA_SERVICE_INFO, ServiceInfo(GcmPrefs.get(context).toConfiguration(), McsService.isConnected(), McsService.getStartTimestamp())) + }, null) + } + + override fun onReceive(context: Context, intent: Intent) { + try { + when (intent.action) { + ACTION_UPDATE_CONFIGURATION -> { + (intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context) + } + } + sendInfoResponse(context) + } catch (e: Exception) { + Log.w(TAG, e) + } + } +} + +private suspend fun sendToServiceInfoReceiver(intent: Intent, context: Context): ServiceInfo = suspendCoroutine { + context.registerReceiver(object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + context.unregisterReceiver(this) + val serviceInfo = try { + intent.getSerializableExtra(EXTRA_SERVICE_INFO) as ServiceInfo + } catch (e: Exception) { + it.resumeWithException(e) + return + } + try { + it.resume(serviceInfo) + } catch (e: Exception) { + Log.w(TAG, e) + } + } + }, IntentFilter(ACTION_SERVICE_INFO_RESPONSE)) + try { + context.sendOrderedBroadcast(intent, null) + } catch (e: Exception) { + it.resumeWithException(e) + } +} + +suspend fun getGcmServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver( + Intent(context, ServiceInfoReceiver::class.java).apply { + action = ACTION_SERVICE_INFO_REQUEST + }, context) + +suspend fun setGcmServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver( + Intent(context, ServiceInfoReceiver::class.java).apply { + action = ACTION_UPDATE_CONFIGURATION + putExtra(EXTRA_CONFIGURATION, configuration) + }, context) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/StatusInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/StatusInfo.kt deleted file mode 100644 index 1797ca6c..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/StatusInfo.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.gcm - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.util.Log -import java.io.Serializable -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -private const val ACTION_STATUS_INFO_REQUEST = "org.microg.gms.STATUS_INFO_REQUEST" -private const val ACTION_STATUS_INFO_RESPONSE = "org.microg.gms.STATUS_INFO_RESPONSE" -private const val EXTRA_STATUS_INFO = "org.microg.gms.STATUS_INFO" -private const val TAG = "GmsGcmStatusInfo" - -data class StatusInfo(val connected: Boolean, val startTimestamp: Long) : Serializable - -class StatusInfoProvider : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - try { - context.sendOrderedBroadcast(Intent(ACTION_STATUS_INFO_RESPONSE).apply { - setPackage(context.packageName) - putExtra(EXTRA_STATUS_INFO, StatusInfo(McsService.isConnected(), McsService.getStartTimestamp())) - }, null) - } catch (e: Exception) { - Log.w(TAG, e) - } - } -} - -suspend fun getStatusInfo(context: Context): StatusInfo? = suspendCoroutine { - context.registerReceiver(object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent?) { - try { - it.resume(intent?.getSerializableExtra(EXTRA_STATUS_INFO) as? StatusInfo) - } catch (e: Exception) { - Log.w(TAG, e) - } - context.unregisterReceiver(this) - } - }, IntentFilter(ACTION_STATUS_INFO_RESPONSE)) - try { - context.sendOrderedBroadcast(Intent(context, StatusInfoProvider::class.java), null) - } catch (e: Exception) { - it.resume(null) - } -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/snet/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/snet/ServiceInfo.kt new file mode 100644 index 00000000..2c045316 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/snet/ServiceInfo.kt @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.snet + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.util.Log +import java.io.Serializable +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +private const val ACTION_SERVICE_INFO_REQUEST = "org.microg.gms.snet.SERVICE_INFO_REQUEST" +private const val ACTION_UPDATE_CONFIGURATION = "org.microg.gms.snet.UPDATE_CONFIGURATION" +private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.snet.SERVICE_INFO_RESPONSE" +private const val EXTRA_SERVICE_INFO = "org.microg.gms.snet.SERVICE_INFO" +private const val EXTRA_CONFIGURATION = "org.microg.gms.snet.CONFIGURATION" +private const val TAG = "GmsSafetyNetStatusInfo" + +data class ServiceInfo(val configuration: ServiceConfiguration) : Serializable + +data class ServiceConfiguration(val enabled: Boolean) : Serializable { + fun saveToPrefs(context: Context) { + SafetyNetPrefs.get(context).isEnabled = enabled + } +} + +private fun SafetyNetPrefs.toConfiguration(): ServiceConfiguration = ServiceConfiguration(isEnabled) + +class ServiceInfoReceiver : BroadcastReceiver() { + private fun sendInfoResponse(context: Context) { + context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { + setPackage(context.packageName) + putExtra(EXTRA_SERVICE_INFO, ServiceInfo(SafetyNetPrefs.get(context).toConfiguration())) + }, null) + } + + override fun onReceive(context: Context, intent: Intent) { + try { + when (intent.action) { + ACTION_UPDATE_CONFIGURATION -> { + (intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context) + } + } + sendInfoResponse(context) + } catch (e: Exception) { + Log.w(TAG, e) + } + } +} + +private suspend fun sendToServiceInfoReceiver(intent: Intent, context: Context): ServiceInfo = suspendCoroutine { + context.registerReceiver(object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + context.unregisterReceiver(this) + val serviceInfo = try { + intent.getSerializableExtra(EXTRA_SERVICE_INFO) as ServiceInfo + } catch (e: Exception) { + it.resumeWithException(e) + return + } + try { + it.resume(serviceInfo) + } catch (e: Exception) { + Log.w(TAG, e) + } + } + }, IntentFilter(ACTION_SERVICE_INFO_RESPONSE)) + try { + context.sendOrderedBroadcast(intent, null) + } catch (e: Exception) { + it.resumeWithException(e) + } +} + +suspend fun getSafetyNetServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver( + Intent(context, ServiceInfoReceiver::class.java).apply { + action = ACTION_SERVICE_INFO_REQUEST + }, context) + +suspend fun setSafetyNetServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver( + Intent(context, ServiceInfoReceiver::class.java).apply { + action = ACTION_UPDATE_CONFIGURATION + putExtra(EXTRA_CONFIGURATION, configuration) + }, context) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt index 5cece629..b56ec0dc 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt @@ -13,7 +13,9 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.google.android.gms.R import com.google.android.gms.databinding.DeviceRegistrationFragmentBinding -import org.microg.gms.checkin.CheckinPrefs +import org.microg.gms.checkin.ServiceInfo +import org.microg.gms.checkin.getCheckinServiceInfo +import org.microg.gms.checkin.setCheckinServiceConfiguration class DeviceRegistrationFragment : Fragment(R.layout.device_registration_fragment) { private lateinit var binding: DeviceRegistrationFragmentBinding @@ -22,17 +24,28 @@ class DeviceRegistrationFragment : Fragment(R.layout.device_registration_fragmen binding = DeviceRegistrationFragmentBinding.inflate(inflater, container, false) binding.switchBarCallback = object : PreferenceSwitchBarCallback { override fun onChecked(newStatus: Boolean) { - CheckinPrefs.setEnabled(context, newStatus) - binding.checkinEnabled = newStatus + setEnabled(newStatus) } } return binding.root } + fun setEnabled(newStatus: Boolean) { + lifecycleScope.launchWhenResumed { + val info = getCheckinServiceInfo(requireContext()) + val newConfiguration = info.configuration.copy(enabled = newStatus) + displayServiceInfo(setCheckinServiceConfiguration(requireContext(), newConfiguration)) + } + } + + fun displayServiceInfo(serviceInfo: ServiceInfo) { + binding.checkinEnabled = serviceInfo.configuration.enabled + } + override fun onResume() { super.onResume() lifecycleScope.launchWhenResumed { - binding.checkinEnabled = CheckinPrefs.get(context).isEnabled + displayServiceInfo(getCheckinServiceInfo(requireContext())) } } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt index b1f53234..a9aa42ed 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt @@ -8,16 +8,17 @@ package org.microg.gms.ui import android.os.Bundle import android.os.Handler import android.text.format.DateUtils +import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import com.google.android.gms.R -import org.microg.gms.checkin.CheckinPrefs -import org.microg.gms.checkin.LastCheckinInfo +import org.microg.gms.checkin.getCheckinServiceInfo class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { private lateinit var statusCategory: PreferenceCategory private lateinit var status: Preference + private lateinit var androidId: Preference private val handler = Handler() private val updateRunnable = Runnable { updateStatus() } @@ -28,6 +29,7 @@ class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { override fun onBindPreferences() { statusCategory = preferenceScreen.findPreference("prefcat_device_registration_status") ?: statusCategory status = preferenceScreen.findPreference("pref_device_registration_status") ?: status + androidId = preferenceScreen.findPreference("pref_device_registration_android_id") ?: androidId } override fun onResume() { @@ -42,12 +44,17 @@ class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { private fun updateStatus() { handler.postDelayed(updateRunnable, UPDATE_INTERVAL) - statusCategory.isVisible = CheckinPrefs.get(context).isEnabled - val checkinInfo = LastCheckinInfo.read(requireContext()) - status.summary = if (checkinInfo.lastCheckin > 0) { - getString(R.string.checkin_last_registration, DateUtils.getRelativeTimeSpanString(checkinInfo.lastCheckin, System.currentTimeMillis(), 0)) - } else { - getString(R.string.checkin_not_registered) + lifecycleScope.launchWhenResumed { + val serviceInfo = getCheckinServiceInfo(requireContext()) + statusCategory.isVisible = serviceInfo.configuration.enabled + if (serviceInfo.lastCheckin > 0) { + status.summary = getString(R.string.checkin_last_registration, DateUtils.getRelativeTimeSpanString(serviceInfo.lastCheckin, System.currentTimeMillis(), 0)) + androidId.isVisible = true + androidId.summary = serviceInfo.androidId.toString(16) + } else { + status.summary = getString(R.string.checkin_not_registered) + androidId.isVisible = false + } } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsFragment.kt index 127c0db9..99051bcf 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsFragment.kt @@ -13,7 +13,9 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.google.android.gms.R import com.google.android.gms.databinding.ExposureNotificationsFragmentBinding -import org.microg.gms.nearby.exposurenotification.ExposurePreferences +import org.microg.gms.nearby.exposurenotification.ServiceInfo +import org.microg.gms.nearby.exposurenotification.getExposureNotificationsServiceInfo +import org.microg.gms.nearby.exposurenotification.setExposureNotificationsServiceConfiguration class ExposureNotificationsFragment : Fragment(R.layout.exposure_notifications_fragment) { private lateinit var binding: ExposureNotificationsFragmentBinding @@ -22,17 +24,28 @@ class ExposureNotificationsFragment : Fragment(R.layout.exposure_notifications_f binding = ExposureNotificationsFragmentBinding.inflate(inflater, container, false) binding.switchBarCallback = object : PreferenceSwitchBarCallback { override fun onChecked(newStatus: Boolean) { - ExposurePreferences(requireContext()).scannerEnabled = newStatus - binding.scannerEnabled = newStatus + setEnabled(newStatus) } } return binding.root } + fun setEnabled(newStatus: Boolean) { + lifecycleScope.launchWhenResumed { + val info = getExposureNotificationsServiceInfo(requireContext()) + val newConfiguration = info.configuration.copy(enabled = newStatus) + displayServiceInfo(setExposureNotificationsServiceConfiguration(requireContext(), newConfiguration)) + } + } + + fun displayServiceInfo(serviceInfo: ServiceInfo) { + binding.scannerEnabled = serviceInfo.configuration.enabled + } + override fun onResume() { super.onResume() lifecycleScope.launchWhenResumed { - binding.scannerEnabled = ExposurePreferences(requireContext()).scannerEnabled + displayServiceInfo(getExposureNotificationsServiceInfo(requireContext())) } } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsPreferencesFragment.kt index 69bf37c5..e8666daf 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsPreferencesFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsPreferencesFragment.kt @@ -17,7 +17,7 @@ import com.google.android.gms.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.microg.gms.nearby.exposurenotification.ExposureDatabase -import org.microg.gms.nearby.exposurenotification.ExposurePreferences +import org.microg.gms.nearby.exposurenotification.getExposureNotificationsServiceInfo class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() { private lateinit var exposureEnableInfo: Preference @@ -62,9 +62,9 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() { private fun updateStatus() { lifecycleScope.launchWhenResumed { handler.postDelayed(updateStatusRunnable, UPDATE_STATUS_INTERVAL) - val preferences = ExposurePreferences(requireContext()) - exposureEnableInfo.isVisible = !preferences.scannerEnabled - advertisingId.isVisible = preferences.advertiserEnabled + val enabled = getExposureNotificationsServiceInfo(requireContext()).configuration.enabled + exposureEnableInfo.isVisible = !enabled + advertisingId.isVisible = enabled } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt index 2af9e2cf..f0e78ec2 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt @@ -11,8 +11,10 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.google.android.gms.R import com.google.android.gms.databinding.PushNotificationFragmentBinding -import org.microg.gms.checkin.CheckinPrefs -import org.microg.gms.gcm.GcmPrefs +import org.microg.gms.checkin.getCheckinServiceInfo +import org.microg.gms.gcm.ServiceInfo +import org.microg.gms.gcm.getGcmServiceInfo +import org.microg.gms.gcm.setGcmServiceConfiguration class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { lateinit var binding: PushNotificationFragmentBinding @@ -21,18 +23,29 @@ class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { binding = PushNotificationFragmentBinding.inflate(inflater, container, false) binding.switchBarCallback = object : PreferenceSwitchBarCallback { override fun onChecked(newStatus: Boolean) { - GcmPrefs.setEnabled(context, newStatus) - binding.gcmEnabled = newStatus + setEnabled(newStatus) } } return binding.root } + fun setEnabled(newStatus: Boolean) { + lifecycleScope.launchWhenResumed { + val info = getGcmServiceInfo(requireContext()) + val newConfiguration = info.configuration.copy(enabled = newStatus) + displayServiceInfo(setGcmServiceConfiguration(requireContext(), newConfiguration)) + } + } + + fun displayServiceInfo(serviceInfo: ServiceInfo) { + binding.gcmEnabled = serviceInfo.configuration.enabled + } + override fun onResume() { super.onResume() lifecycleScope.launchWhenResumed { - binding.gcmEnabled = GcmPrefs.get(context).isEnabled - binding.checkinEnabled = CheckinPrefs.get(context).isEnabled + displayServiceInfo(getGcmServiceInfo(requireContext())) + binding.checkinEnabled = getCheckinServiceInfo(requireContext()).configuration.enabled } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt index 9b5f8281..a0283ab9 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt @@ -18,9 +18,7 @@ import com.google.android.gms.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.microg.gms.gcm.GcmDatabase -import org.microg.gms.gcm.GcmPrefs -import org.microg.gms.gcm.McsService -import org.microg.gms.gcm.getStatusInfo +import org.microg.gms.gcm.getGcmServiceInfo class PushNotificationPreferencesFragment : PreferenceFragmentCompat() { private lateinit var pushStatusCategory: PreferenceCategory @@ -67,9 +65,9 @@ class PushNotificationPreferencesFragment : PreferenceFragmentCompat() { private fun updateStatus() { handler.postDelayed(updateRunnable, UPDATE_INTERVAL) - pushStatusCategory.isVisible = GcmPrefs.get(context).isEnabled lifecycleScope.launchWhenStarted { - val statusInfo = getStatusInfo(requireContext()) + val statusInfo = getGcmServiceInfo(requireContext()) + pushStatusCategory.isVisible = statusInfo.configuration.enabled pushStatus.summary = if (statusInfo != null && statusInfo.connected) { getString(R.string.gcm_network_state_connected, DateUtils.getRelativeTimeSpanString(statusInfo.startTimestamp, System.currentTimeMillis(), 0)) } else { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt index 7daf880d..030af985 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt @@ -8,11 +8,14 @@ package org.microg.gms.ui import android.os.Bundle import android.view.* import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.google.android.gms.R import com.google.android.gms.databinding.SafetyNetFragmentBinding -import org.microg.gms.checkin.CheckinPrefs -import org.microg.gms.snet.SafetyNetPrefs +import org.microg.gms.checkin.getCheckinServiceInfo +import org.microg.gms.snet.ServiceInfo +import org.microg.gms.snet.getSafetyNetServiceInfo +import org.microg.gms.snet.setSafetyNetServiceConfiguration class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { @@ -22,17 +25,30 @@ class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { binding = SafetyNetFragmentBinding.inflate(inflater, container, false) binding.switchBarCallback = object : PreferenceSwitchBarCallback { override fun onChecked(newStatus: Boolean) { - SafetyNetPrefs.get(requireContext()).isEnabled = newStatus - binding.safetynetEnabled = newStatus + setEnabled(newStatus) } } return binding.root } + fun setEnabled(newStatus: Boolean) { + lifecycleScope.launchWhenResumed { + val info = getSafetyNetServiceInfo(requireContext()) + val newConfiguration = info.configuration.copy(enabled = newStatus) + displayServiceInfo(setSafetyNetServiceConfiguration(requireContext(), newConfiguration)) + } + } + + fun displayServiceInfo(serviceInfo: ServiceInfo) { + binding.safetynetEnabled = serviceInfo.configuration.enabled + } + override fun onResume() { super.onResume() - binding.checkinEnabled = CheckinPrefs.get(requireContext()).isEnabled - binding.safetynetEnabled = SafetyNetPrefs.get(requireContext()).isEnabled + lifecycleScope.launchWhenResumed { + binding.checkinEnabled = getCheckinServiceInfo(requireContext()).configuration.enabled + displayServiceInfo(getSafetyNetServiceInfo(requireContext())) + } } override fun onActivityCreated(savedInstanceState: Bundle?) { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt new file mode 100644 index 00000000..8f296434 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.ui + +import android.os.Build +import android.os.Bundle +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import androidx.preference.Preference +import com.google.android.gms.R +import org.microg.gms.checkin.getCheckinServiceInfo +import org.microg.gms.gcm.GcmDatabase +import org.microg.gms.gcm.getGcmServiceInfo +import org.microg.gms.nearby.exposurenotification.getExposureNotificationsServiceInfo +import org.microg.gms.snet.getSafetyNetServiceInfo +import org.microg.nlp.client.UnifiedLocationClient +import org.microg.tools.ui.ResourceSettingsFragment + +class SettingsFragment : ResourceSettingsFragment() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + super.onCreatePreferences(savedInstanceState, rootKey) + + findPreference(PREF_CHECKIN)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openCheckinSettings) + true + } + findPreference(PREF_GCM)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openGcmSettings) + true + } + findPreference(PREF_SNET)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openSafetyNetSettings) + true + } + findPreference(PREF_UNIFIEDNLP)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openUnifiedNlpSettings) + true + } + findPreference(PREF_EXPOSURE)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openExposureNotificationSettings) + true + } + findPreference(PREF_ABOUT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openAbout) + true + } + findPreference(PREF_ABOUT)!!.summary = getString(R.string.about_version_str, AboutFragment.getSelfVersion(context)) + } + + override fun onResume() { + super.onResume() + lifecycleScope.launchWhenResumed { + updateDetails() + } + } + + private suspend fun updateDetails() { + if (getGcmServiceInfo(requireContext()).configuration.enabled) { + val database = GcmDatabase(context) + val regCount = database.registrationList.size + database.close() + findPreference(PREF_GCM)!!.summary = getString(R.string.service_status_enabled_short) + " - " + resources.getQuantityString(R.plurals.gcm_registered_apps_counter, regCount, regCount) + } else { + findPreference(PREF_GCM)!!.setSummary(R.string.service_status_disabled_short) + } + + findPreference(PREF_CHECKIN)!!.setSummary(if (getCheckinServiceInfo(requireContext()).configuration.enabled) R.string.service_status_enabled_short else R.string.service_status_disabled_short) + findPreference(PREF_SNET)!!.setSummary(if (getSafetyNetServiceInfo(requireContext()).configuration.enabled) R.string.service_status_enabled_short else R.string.service_status_disabled_short) + + val backendCount = UnifiedLocationClient[requireContext()].getLocationBackends().size + UnifiedLocationClient[requireContext()].getGeocoderBackends().size + findPreference(PREF_UNIFIEDNLP)!!.summary = resources.getQuantityString(R.plurals.pref_unifiednlp_summary, backendCount, backendCount); + + if (Build.VERSION.SDK_INT >= 21) { + findPreference(PREF_EXPOSURE)!!.isVisible = true + if (getExposureNotificationsServiceInfo(requireContext()).configuration.enabled) { + findPreference(PREF_EXPOSURE)!!.summary = getString(R.string.service_status_enabled_short) + } else { + findPreference(PREF_EXPOSURE)!!.setSummary(R.string.service_status_disabled_short) + } + } else { + findPreference(PREF_EXPOSURE)!!.isVisible = false + } + } + + companion object { + const val PREF_ABOUT = "pref_about" + const val PREF_GCM = "pref_gcm" + const val PREF_SNET = "pref_snet" + const val PREF_UNIFIEDNLP = "pref_unifiednlp" + const val PREF_CHECKIN = "pref_checkin" + const val PREF_EXPOSURE = "pref_exposure" + } + + init { + preferencesResource = R.xml.preferences_start + } +} diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index 6fcc35a8..70e991a2 100644 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -140,6 +140,7 @@ This can take a couple of minutes." When enabled, all applications on this device will be able to see email address of your Google Accounts without prior authorization. Registers your device to Google services and creates a unique device identifier. microG strips identifying bits other than your Google account name from registration data. + Android ID Not registered Last registration: %1$s diff --git a/play-services-core/src/main/res/xml/preferences_device_registration.xml b/play-services-core/src/main/res/xml/preferences_device_registration.xml index 7773de62..1f73af33 100644 --- a/play-services-core/src/main/res/xml/preferences_device_registration.xml +++ b/play-services-core/src/main/res/xml/preferences_device_registration.xml @@ -14,6 +14,11 @@ android:selectable="false" android:title="@string/pref_info_status" tools:summary="Last registration: 13 hours ago" /> + Date: Thu, 3 Sep 2020 10:01:58 +0200 Subject: [PATCH 13/24] Update UnifiedNlp to 2.0-alpha3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b3002c34..e3480137 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ */ buildscript { - ext.nlpVersion = '2.0-alpha2' + ext.nlpVersion = '2.0-alpha3' ext.remoteDroidGuardVersion = '0.1.2' ext.safeParcelVersion = '1.6.0' ext.wearableVersion = '0.1.1' From 876e32acd5a7e306466c43dc7bcfd0b80695f636 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 4 Sep 2020 00:13:11 +0200 Subject: [PATCH 14/24] EN: Make internal structures closer to ExposureWindow mode --- .../src/main/AndroidManifest.xml | 1 + .../exposurenotification/AdvertiserService.kt | 2 +- .../exposurenotification/ExposureDatabase.kt | 33 ++++------ .../ExposureNotificationServiceImpl.kt | 8 +-- .../exposurenotification/MeasuredExposure.kt | 60 +++++++++++++++---- .../exposurenotification/ScannerService.kt | 2 +- .../exposurenotification/CryptoTest.java | 1 - 7 files changed, 69 insertions(+), 38 deletions(-) diff --git a/play-services-nearby-core/src/main/AndroidManifest.xml b/play-services-nearby-core/src/main/AndroidManifest.xml index e40ecda8..1e597fbb 100644 --- a/play-services-nearby-core/src/main/AndroidManifest.xml +++ b/play-services-nearby-core/src/main/AndroidManifest.xml @@ -12,6 +12,7 @@ + diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt index d3e0b2e8..b1802a1b 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt @@ -36,7 +36,7 @@ class AdvertiserService : LifecycleService() { private var looping = false private var callback: AdvertiseCallback? = null private val advertiser: BluetoothLeAdvertiser? - get() = BluetoothAdapter.getDefaultAdapter().bluetoothLeAdvertiser + get() = BluetoothAdapter.getDefaultAdapter()?.bluetoothLeAdvertiser private lateinit var database: ExposureDatabase private val trigger = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { 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 4f435efe..b8b89527 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 @@ -12,15 +12,11 @@ import android.database.sqlite.SQLiteCursor import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE import android.database.sqlite.SQLiteOpenHelper -import android.database.sqlite.SQLiteStatement -import android.os.Build import android.os.Parcel import android.os.Parcelable -import android.text.TextUtils import android.util.Log import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey -import org.microg.gms.common.PackageUtils import java.nio.ByteBuffer import java.util.* import java.util.concurrent.Future @@ -224,7 +220,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit val keys = listDiagnosisKeysPendingSearch(packageName, token) val oldestRpi = oldestRpi for (key in keys) { - if (oldestRpi == null || key.rollingStartIntervalNumber * ROLLING_WINDOW_LENGTH_MS - ALLOWED_KEY_OFFSET_MS < oldestRpi) { + if (oldestRpi == null || (key.rollingStartIntervalNumber + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS < oldestRpi) { // Early ignore because key is older than since we started scanning. applyDiagnosisKeySearchResult(key, false) } else { @@ -250,50 +246,48 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } fun findMeasuredExposures(key: TemporaryExposureKey): List { - val list = arrayListOf() val allRpis = key.generateAllRpiIds() val rpis = (0 until key.rollingPeriod).map { i -> val pos = i * 16 allRpis.sliceArray(pos until (pos + 16)) } - val measures = findMeasuredExposures(rpis, key.rollingStartIntervalNumber.toLong() * ROLLING_WINDOW_LENGTH_MS - ALLOWED_KEY_OFFSET_MS, (key.rollingStartIntervalNumber.toLong() + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS) - measures.filter { - val index = rpis.indexOf(it.rpi) + val measures = findExposures(rpis, key.rollingStartIntervalNumber.toLong() * ROLLING_WINDOW_LENGTH_MS - ALLOWED_KEY_OFFSET_MS, (key.rollingStartIntervalNumber.toLong() + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS) + return measures.filter { + val index = rpis.indexOfFirst { rpi -> rpi.contentEquals(it.rpi) } val targetTimestamp = (key.rollingStartIntervalNumber + index).toLong() * ROLLING_WINDOW_LENGTH_MS - it.timestamp > targetTimestamp - ALLOWED_KEY_OFFSET_MS && it.timestamp < targetTimestamp + ALLOWED_KEY_OFFSET_MS + it.timestamp >= targetTimestamp - ALLOWED_KEY_OFFSET_MS && it.timestamp <= targetTimestamp + ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS }.mapNotNull { val decrypted = key.cryptAem(it.rpi, it.aem) if (decrypted[0] == 0x40.toByte() || decrypted[0] == 0x50.toByte()) { val txPower = decrypted[1] - it.copy(key = key, notCorrectedAttenuation = txPower - it.rssi) + MeasuredExposure(it.timestamp, it.duration, it.rssi, txPower.toInt(), key) } else { Log.w(TAG, "Unknown AEM version ${decrypted[0]}, ignoring") null } } - return list } - fun findMeasuredExposures(rpis: List, minTime: Long, maxTime: Long): List = readableDatabase.run { + fun findExposures(rpis: List, minTime: Long, maxTime: Long): List = readableDatabase.run { if (rpis.isEmpty()) return emptyList() val qs = rpis.map { "?" }.joinToString(",") queryWithFactory({ _, cursorDriver, editTable, query -> query.bindLong(1, minTime) query.bindLong(2, maxTime) - for (i in (3..(rpis.size + 2))) { - query.bindBlob(i, rpis[i - 3]) + rpis.forEachIndexed { index, rpi -> + query.bindBlob(index + 3, rpi) } SQLiteCursor(cursorDriver, editTable, query) }, false, TABLE_ADVERTISEMENTS, arrayOf("rpi", "aem", "timestamp", "duration", "rssi"), "timestamp > ? AND timestamp < ? AND rpi IN ($qs)", null, null, null, null, null).use { cursor -> - val list = arrayListOf() + val list = arrayListOf() while (cursor.moveToNext()) { - list.add(MeasuredExposure(cursor.getBlob(1), cursor.getBlob(2), cursor.getLong(3), cursor.getLong(4), cursor.getInt(5))) + list.add(PlainExposure(cursor.getBlob(0), cursor.getBlob(1), cursor.getLong(2), cursor.getLong(3), cursor.getInt(4))) } list } } - fun findMeasuredExposure(rpi: ByteArray, minTime: Long, maxTime: Long): MeasuredExposure? = readableDatabase.run { + fun findExposure(rpi: ByteArray, minTime: Long, maxTime: Long): PlainExposure? = readableDatabase.run { queryWithFactory({ _, cursorDriver, editTable, query -> query.bindBlob(1, rpi) query.bindLong(2, minTime) @@ -301,7 +295,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit SQLiteCursor(cursorDriver, editTable, query) }, false, TABLE_ADVERTISEMENTS, arrayOf("aem", "timestamp", "duration", "rssi"), "rpi = ? AND timestamp > ? AND timestamp < ?", null, null, null, null, null).use { cursor -> if (cursor.moveToNext()) { - MeasuredExposure(rpi, cursor.getBlob(0), cursor.getLong(1), cursor.getLong(2), cursor.getInt(3)) + PlainExposure(rpi, cursor.getBlob(0), cursor.getLong(1), cursor.getLong(2), cursor.getInt(3)) } else { null } @@ -518,4 +512,3 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } } } - diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index a14bf339..28aac77b 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -171,7 +171,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } if (totalBytesRead == prefix.size && String(prefix).trim() == "EK Export v1") { val fileKeys = storeDiagnosisKeyExport(params.token, TemporaryExposureKeyExport.ADAPTER.decode(stream)) - keys + fileKeys + keys += fileKeys } else { Log.d(TAG, "export.bin had invalid prefix") } @@ -209,7 +209,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val intent.putExtra(EXTRA_TOKEN, params.token) intent.`package` = packageName Log.d(TAG, "Sending $intent") - context.sendOrderedBroadcast(intent, PERMISSION_EXPOSURE_CALLBACK) + context.sendOrderedBroadcast(intent, null) } catch (e: Exception) { Log.w(TAG, "Callback failed", e) } @@ -227,7 +227,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } return@with } - val exposures = database.findAllMeasuredExposures(packageName, params.token) + val exposures = database.findAllMeasuredExposures(packageName, params.token).merge() val response = ExposureSummary.ExposureSummaryBuilder() .setDaysSinceLastExposure(exposures.map { it.daysSinceExposure }.min()?.toInt() ?: 0) .setMatchedKeyCount(exposures.map { it.key }.distinct().size) @@ -268,7 +268,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } return@with } - val response = database.findAllMeasuredExposures(packageName, params.token).map { + val response = database.findAllMeasuredExposures(packageName, params.token).merge().map { it.toExposureInformation(configuration) } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/MeasuredExposure.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/MeasuredExposure.kt index 7bb4a324..415baecf 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/MeasuredExposure.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/MeasuredExposure.kt @@ -11,19 +11,51 @@ import com.google.android.gms.nearby.exposurenotification.RiskLevel import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import java.util.concurrent.TimeUnit -data class MeasuredExposure(val rpi: ByteArray, val aem: ByteArray, val timestamp: Long, val duration: Long, val rssi: Int, val notCorrectedAttenuation: Int = 0, val key: TemporaryExposureKey? = null) { +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, val key: TemporaryExposureKey) { + val attenuation + get() = txPower - (rssi + currentDeviceInfo.rssiCorrection) +} + +fun List.merge(): List { + val keys = map { it.key }.distinct() + val result = arrayListOf() + for (key in keys) { + var merged: MergedExposure? = null + for (exposure in filter { it.key == key }.sortedBy { it.timestamp }) { + if (merged == null) { + merged = MergedExposure(key, exposure.timestamp, listOf(MergedSubExposure(exposure.attenuation, exposure.duration))) + } else if (merged.timestamp + MergedExposure.MAXIMUM_DURATION + ROLLING_WINDOW_LENGTH_MS > exposure.timestamp) { + merged += exposure + } + if (merged.durationInMinutes > 30) { + result.add(merged) + merged = null + } + } + if (merged != null) { + result.add(merged) + } + } + return result +} + +internal data class MergedSubExposure(val attenuation: Int, val duration: Long) + +data class MergedExposure internal constructor(val key: TemporaryExposureKey, val timestamp: Long, internal val subs: List) { @RiskLevel val transmissionRiskLevel: Int - get() = key?.transmissionRiskLevel ?: RiskLevel.RISK_LEVEL_INVALID - + get() = key.transmissionRiskLevel + val durationInMinutes - get() = TimeUnit.MILLISECONDS.toMinutes(duration) + get() = TimeUnit.MILLISECONDS.toMinutes(subs.map { it.duration }.sum()) val daysSinceExposure get() = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - timestamp) val attenuation - get() = notCorrectedAttenuation - currentDeviceInfo.rssiCorrection + get() = subs.map { it.attenuation }.min()!! fun getAttenuationRiskScore(configuration: ExposureConfiguration): Int { return when { @@ -83,20 +115,26 @@ data class MeasuredExposure(val rpi: ByteArray, val aem: ByteArray, val timestam } fun getAttenuationDurations(configuration: ExposureConfiguration): IntArray { - return when { - attenuation < configuration.durationAtAttenuationThresholds[0] -> intArrayOf(durationInMinutes.toInt(), 0, 0) - attenuation < configuration.durationAtAttenuationThresholds[1] -> intArrayOf(0, durationInMinutes.toInt(), 0) - else -> intArrayOf(0, 0, durationInMinutes.toInt()) - } + return intArrayOf( + TimeUnit.MILLISECONDS.toMinutes(subs.filter { it.attenuation < configuration.durationAtAttenuationThresholds[0] }.map { it.duration }.sum()).toInt(), + TimeUnit.MILLISECONDS.toMinutes(subs.filter { it.attenuation >= configuration.durationAtAttenuationThresholds[0] && it.attenuation < configuration.durationAtAttenuationThresholds[1] }.map { it.duration }.sum()).toInt(), + TimeUnit.MILLISECONDS.toMinutes(subs.filter { it.attenuation >= configuration.durationAtAttenuationThresholds[1] }.map { it.duration }.sum()).toInt() + ) } fun toExposureInformation(configuration: ExposureConfiguration): ExposureInformation = ExposureInformation.ExposureInformationBuilder() - .setDateMillisSinceEpoch(timestamp) + .setDateMillisSinceEpoch(key.rollingStartIntervalNumber.toLong() * ROLLING_WINDOW_LENGTH_MS) .setDurationMinutes(durationInMinutes.toInt()) .setAttenuationValue(attenuation) .setTransmissionRiskLevel(transmissionRiskLevel) .setTotalRiskScore(getRiskScore(configuration)) .setAttenuationDurations(getAttenuationDurations(configuration)) .build() + + operator fun plus(exposure: MeasuredExposure): MergedExposure = copy(subs = subs + MergedSubExposure(exposure.attenuation, exposure.duration)) + + companion object { + const val MAXIMUM_DURATION = 30 * 60 * 1000 + } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt index 470beca7..0e7a2ef0 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt @@ -57,7 +57,7 @@ class ScannerService : Service() { } private val scanner: BluetoothLeScanner? - get() = getDefaultAdapter().bluetoothLeScanner + get() = getDefaultAdapter()?.bluetoothLeScanner override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { ForegroundServiceContext.completeForegroundService(this, intent, TAG) diff --git a/play-services-nearby-core/src/test/java/org/microg/gms/nearby/exposurenotification/CryptoTest.java b/play-services-nearby-core/src/test/java/org/microg/gms/nearby/exposurenotification/CryptoTest.java index 8b9b78a4..dc92ffbe 100644 --- a/play-services-nearby-core/src/test/java/org/microg/gms/nearby/exposurenotification/CryptoTest.java +++ b/play-services-nearby-core/src/test/java/org/microg/gms/nearby/exposurenotification/CryptoTest.java @@ -8,7 +8,6 @@ package org.microg.gms.nearby.exposurenotification; import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey; -import junit.framework.Test; import junit.framework.TestCase; import org.junit.Assert; From fd6d915f0ad1b4b35e17e0b5d844c6fc4a5efcf8 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 4 Sep 2020 10:37:02 +0200 Subject: [PATCH 15/24] EN: Display last exposure report in settings --- ...sureNotificationsAppPreferencesFragment.kt | 16 +++++++++++++ .../src/main/res/values/plurals.xml | 6 ++++- ...preferences_exposure_notifications_app.xml | 2 +- .../exposurenotification/ExposureDatabase.kt | 10 ++++++++ .../ExposureNotificationServiceImpl.kt | 24 ++++++------------- .../exposurenotification/MeasuredExposure.kt | 2 +- 6 files changed, 40 insertions(+), 20 deletions(-) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsAppPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsAppPreferencesFragment.kt index bfdfbe1a..bc54805c 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsAppPreferencesFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsAppPreferencesFragment.kt @@ -8,10 +8,13 @@ package org.microg.gms.ui import android.content.Intent import android.os.Bundle import android.text.format.DateUtils +import android.util.JsonReader import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.google.android.gms.R +import org.json.JSONObject import org.microg.gms.nearby.exposurenotification.ExposureDatabase +import java.util.concurrent.TimeUnit class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() { private lateinit var open: Preference @@ -53,6 +56,19 @@ class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() { 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)) } + 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) { + } + } checks.summary = str } } diff --git a/play-services-core/src/main/res/values/plurals.xml b/play-services-core/src/main/res/values/plurals.xml index e12bc924..c5b8ef5a 100644 --- a/play-services-core/src/main/res/values/plurals.xml +++ b/play-services-core/src/main/res/values/plurals.xml @@ -32,4 +32,8 @@ Request missing permission Request missing permissions - \ No newline at end of file + + Last report: %1$d match, %2$d days ago + Last report: %1$d matches, latest %2$d days ago + + diff --git a/play-services-core/src/main/res/xml/preferences_exposure_notifications_app.xml b/play-services-core/src/main/res/xml/preferences_exposure_notifications_app.xml index c5aa4767..26caa9d8 100644 --- a/play-services-core/src/main/res/xml/preferences_exposure_notifications_app.xml +++ b/play-services-core/src/main/res/xml/preferences_exposure_notifications_app.xml @@ -16,6 +16,6 @@ + tools:summary="7 checks in past 14 days\nLast check: 3 hours ago\nLast report: 2 matches, latest 3 days ago" /> 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 b8b89527..04515f29 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 @@ -444,6 +444,16 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } } + fun lastMethodCallArgs(packageName: String, method: String): String? = readableDatabase.run { + query(TABLE_APP_LOG, arrayOf("args"), "package = ? AND method = ?", arrayOf(packageName, method), null, null, "timestamp DESC", "1").use { cursor -> + if (cursor.moveToNext()) { + cursor.getString(0) + } else { + null + } + } + } + private val currentTemporaryExposureKey: TemporaryExposureKey get() = findOwnKeyAt(currentRollingStartNumber.toInt()) ?: storeOwnKey(generateCurrentTemporaryExposureKey()) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index 28aac77b..8d573bb1 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -72,24 +72,14 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun stop(params: StopParams) { - if (!ExposurePreferences(context).enabled) { - params.callback.onResult(Status.SUCCESS) - return + ExposurePreferences(context).enabled = false + ExposureDatabase.with(context) { database -> + database.noteAppAction(packageName, "stop") } - confirm(CONFIRM_ACTION_STOP) { resultCode, _ -> - if (resultCode == SUCCESS) { - ExposurePreferences(context).enabled = false - } - ExposureDatabase.with(context) { database -> - database.noteAppAction(packageName, "stop", JSONObject().apply { - put("result", resultCode) - }.toString()) - } - try { - params.callback.onResult(Status.SUCCESS) - } catch (e: Exception) { - Log.w(TAG, "Callback failed", e) - } + try { + params.callback.onResult(Status.SUCCESS) + } catch (e: Exception) { + Log.w(TAG, "Callback failed", e) } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/MeasuredExposure.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/MeasuredExposure.kt index 415baecf..18113f1f 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/MeasuredExposure.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/MeasuredExposure.kt @@ -55,7 +55,7 @@ data class MergedExposure internal constructor(val key: TemporaryExposureKey, va get() = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - timestamp) val attenuation - get() = subs.map { it.attenuation }.min()!! + get() = (subs.map { it.attenuation * it.duration }.sum().toDouble() / subs.map { it.duration }.sum().toDouble()).toInt() fun getAttenuationRiskScore(configuration: ExposureConfiguration): Int { return when { From d33391ebce1997012283eacb50f114f305e0df3b Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 5 Sep 2020 23:51:00 +0200 Subject: [PATCH 16/24] EN: Handle confirmation via resolution/pending intent instead of new task --- .../ExposureNotificationsConfirmActivity.kt | 20 +++-- .../nearby/exposurenotification/Constants.kt | 1 + .../exposurenotification/ExposureDatabase.kt | 24 ++++- .../ExposureNotificationServiceImpl.kt | 87 ++++++++++--------- 4 files changed, 82 insertions(+), 50 deletions(-) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsConfirmActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsConfirmActivity.kt index 3b2988c7..2c8fcaa4 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsConfirmActivity.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsConfirmActivity.kt @@ -11,12 +11,14 @@ import android.widget.Button import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import com.google.android.gms.R -import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes.* import org.microg.gms.nearby.exposurenotification.* class ExposureNotificationsConfirmActivity : AppCompatActivity() { - private var resultCode: Int = FAILED - private val resultData: Bundle = Bundle() + private var resultCode: Int = RESULT_CANCELED + set(value) { + setResult(value) + field = value + } private val receiver: ResultReceiver? get() = intent.getParcelableExtra(KEY_CONFIRM_RECEIVER) private val action: String? @@ -47,22 +49,22 @@ class ExposureNotificationsConfirmActivity : AppCompatActivity() { findViewById