From bd20634bd985355628f22ea9ace5e7d78c5e2fbe Mon Sep 17 00:00:00 2001 From: Christian Grigis Date: Fri, 4 Sep 2020 19:28:57 +0200 Subject: [PATCH 01/28] Add provideDiagnosisKeys() implementation --- .../internal/ProvideDiagnosisKeysParams.java | 12 +++++ .../ExposureNotificationClientImpl.java | 47 ++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/ProvideDiagnosisKeysParams.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/ProvideDiagnosisKeysParams.java index e47f3478..d291d8c9 100644 --- a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/ProvideDiagnosisKeysParams.java +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/ProvideDiagnosisKeysParams.java @@ -27,5 +27,17 @@ public class ProvideDiagnosisKeysParams extends AutoSafeParcelable { @Field(5) public String token; + public ProvideDiagnosisKeysParams(IStatusCallback callback, List keys, List keyFiles, ExposureConfiguration configuration, String token) { + this(callback, keyFiles, configuration, token); + this.keys = keys; + } + + public ProvideDiagnosisKeysParams(IStatusCallback callback, List keyFiles, ExposureConfiguration configuration, String token) { + this.callback = callback; + this.keyFiles = keyFiles; + this.configuration = configuration; + this.token = token; + } + public static final Creator CREATOR = new AutoCreator<>(ProvideDiagnosisKeysParams.class); } diff --git a/play-services-nearby/src/main/java/org/microg/gms/nearby/ExposureNotificationClientImpl.java b/play-services-nearby/src/main/java/org/microg/gms/nearby/ExposureNotificationClientImpl.java index 54afe746..5dec37ea 100644 --- a/play-services-nearby/src/main/java/org/microg/gms/nearby/ExposureNotificationClientImpl.java +++ b/play-services-nearby/src/main/java/org/microg/gms/nearby/ExposureNotificationClientImpl.java @@ -6,6 +6,8 @@ package org.microg.gms.nearby; import android.content.Context; +import android.os.ParcelFileDescriptor; +import android.util.Log; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApi; @@ -25,6 +27,7 @@ import com.google.android.gms.nearby.exposurenotification.internal.IExposureInfo import com.google.android.gms.nearby.exposurenotification.internal.IExposureSummaryCallback; import com.google.android.gms.nearby.exposurenotification.internal.ITemporaryExposureKeyListCallback; import com.google.android.gms.nearby.exposurenotification.internal.IsEnabledParams; +import com.google.android.gms.nearby.exposurenotification.internal.ProvideDiagnosisKeysParams; import com.google.android.gms.nearby.exposurenotification.internal.StartParams; import com.google.android.gms.nearby.exposurenotification.internal.StopParams; import com.google.android.gms.tasks.Task; @@ -32,6 +35,9 @@ import com.google.android.gms.tasks.Task; import org.microg.gms.common.api.PendingGoogleApiCall; import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; import java.util.List; public class ExposureNotificationClientImpl extends GoogleApi implements ExposureNotificationClient { @@ -41,6 +47,8 @@ public class ExposureNotificationClientImpl extends GoogleApi start() { return scheduleTask((PendingGoogleApiCall) (client, completionSource) -> { @@ -126,8 +134,43 @@ public class ExposureNotificationClientImpl extends GoogleApi provideDiagnosisKeys(List keys, ExposureConfiguration configuration, String token) { - return null; + public Task provideDiagnosisKeys(List keyFiles, ExposureConfiguration configuration, String token) { + return scheduleTask((PendingGoogleApiCall) (client, completionSource) -> { + List fds = new ArrayList<>(keyFiles.size()); + for (File kf: keyFiles) { + ParcelFileDescriptor fd; + try { + fd = ParcelFileDescriptor.open(kf, ParcelFileDescriptor.MODE_READ_ONLY); + } catch (FileNotFoundException e) { + for (ParcelFileDescriptor ofd : fds) { + try { + ofd.close(); + } catch (IOException e2) { + Log.w(TAG, "Failed to close file", e2); + } + } + completionSource.setException(e); + return; + } + fds.add(fd); + } + + ProvideDiagnosisKeysParams params = new ProvideDiagnosisKeysParams(new IStatusCallback.Stub() { + @Override + public void onResult(Status status) { + if (status == Status.SUCCESS) { + completionSource.setResult(null); + } else { + completionSource.setException(new RuntimeException("Status: " + status)); + } + } + }, fds, configuration, token); + try { + client.provideDiagnosisKeys(params); + } catch (Exception e) { + completionSource.setException(e); + } + }); } @Override From 832ee232eaa0da367e284cc800ef62e3e6cc4a93 Mon Sep 17 00:00:00 2001 From: Christian Grigis Date: Fri, 11 Sep 2020 17:12:10 +0200 Subject: [PATCH 02/28] Add ApiException and use it in EN client impl --- .../android/gms/common/api/ApiException.java | 57 +++++++++++++++++++ .../ExposureNotificationClientImpl.java | 15 ++--- 2 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/api/ApiException.java diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/api/ApiException.java b/play-services-basement/src/main/java/com/google/android/gms/common/api/ApiException.java new file mode 100644 index 00000000..c508b301 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/api/ApiException.java @@ -0,0 +1,57 @@ +package com.google.android.gms.common.api; + +import com.google.android.gms.common.api.Status; + +import org.microg.gms.common.PublicApi; + +/** + * Exception to be returned by a Task when a call to Google Play services has failed. + */ +@PublicApi +public class ApiException extends Exception { + /** + * @deprecated use {@link #getStatus()} instead + */ + @PublicApi + protected final Status mStatus; + + /** + * Create an ApiException from a {@link com.google.android.gms.common.api.Status}. + * @param status the Status instance containing a message and code. + */ + @PublicApi + public ApiException(Status status) { + mStatus = status; + } + + /** + * Returns the status of the operation. + */ + @PublicApi + public Status getStatus() { + return mStatus; + } + + /** + * Indicates the status of the operation. + * @return Status code resulting from the operation. + * The value is one of the constants in {@link com.google.android.gms.common.api.CommonStatusCodes} or specific to the API in use. + */ + @PublicApi + public int getStatusCode() { + return mStatus.getStatusCode(); + } + + /** + * @deprecated use {@link #getMessage()} for a summary of the cause. + */ + @PublicApi + public String getStatusMessage() { + return getMessage(); + } + + @Override + public String getMessage() { + return mStatus.getStatusMessage(); + } +} diff --git a/play-services-nearby/src/main/java/org/microg/gms/nearby/ExposureNotificationClientImpl.java b/play-services-nearby/src/main/java/org/microg/gms/nearby/ExposureNotificationClientImpl.java index 5dec37ea..ab89f2ca 100644 --- a/play-services-nearby/src/main/java/org/microg/gms/nearby/ExposureNotificationClientImpl.java +++ b/play-services-nearby/src/main/java/org/microg/gms/nearby/ExposureNotificationClientImpl.java @@ -10,6 +10,7 @@ import android.os.ParcelFileDescriptor; import android.util.Log; import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.api.ApiException; import com.google.android.gms.common.api.GoogleApi; import com.google.android.gms.common.api.Status; import com.google.android.gms.common.api.internal.ApiKey; @@ -58,7 +59,7 @@ public class ExposureNotificationClientImpl extends GoogleApi Date: Fri, 18 Sep 2020 14:33:54 +0200 Subject: [PATCH 03/28] TemporaryExposureKey: safely initialize with null data --- .../gms/nearby/exposurenotification/TemporaryExposureKey.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 96243d30..15f02f4d 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 @@ -27,7 +27,7 @@ public class TemporaryExposureKey extends AutoSafeParcelable { } TemporaryExposureKey(byte[] keyData, int rollingStartIntervalNumber, @RiskLevel int transmissionRiskLevel, int rollingPeriod) { - this.keyData = keyData; + this.keyData = (keyData == null ? new byte[0] : keyData); this.rollingStartIntervalNumber = rollingStartIntervalNumber; this.transmissionRiskLevel = transmissionRiskLevel; this.rollingPeriod = rollingPeriod; From 8065e873cede086afa65c7cfc5724e99eeb4eeac Mon Sep 17 00:00:00 2001 From: Christian Grigis Date: Thu, 24 Sep 2020 19:43:09 +0200 Subject: [PATCH 04/28] Add ResolvableApiException --- .../common/api/ResolvableApiException.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/api/ResolvableApiException.java diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/api/ResolvableApiException.java b/play-services-basement/src/main/java/com/google/android/gms/common/api/ResolvableApiException.java new file mode 100644 index 00000000..22dc6ed0 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/api/ResolvableApiException.java @@ -0,0 +1,46 @@ +package com.google.android.gms.common.api; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentSender; + +import org.microg.gms.common.PublicApi; + +/** + * Exception to be returned by a Task when a call to Google Play services has failed with a + * possible resolution. + */ +@PublicApi +public class ResolvableApiException extends ApiException { + @PublicApi + public ResolvableApiException(Status status) { + super(status); + } + + /** + * A pending intent to resolve the failure. This intent can be started with + * {@link android.app.Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} + * to present UI to solve the issue. + * @return The pending intent to resolve the failure. + */ + @PublicApi + public PendingIntent getResolution() { + return mStatus.getResolution(); + } + + /** + * Resolves an error by starting any intents requiring user interaction. + * See {@link com.google.android.gms.common.api.CommonStatusCodes#SIGN_IN_REQUIRED}, and + * {@link com.google.android.gms.common.api.CommonStatusCodes#RESOLUTION_REQUIRED}. + * @param activity An Activity context to use to resolve the issue. The activity's + * onActivityResult method will be invoked after the user is done. + * If the resultCode is {@link android.app.Activity#RESULT_OK}, + * the application should try to connect again. + * @param requestCode The request code to pass to onActivityResult. + */ + @PublicApi + public void startResolutionForResult(Activity activity, int requestCode) throws IntentSender.SendIntentException { + mStatus.startResolutionForResult(activity, requestCode); + } +} From 03237fc86b65ba208a382c3968e36e859f14cf27 Mon Sep 17 00:00:00 2001 From: Pdevo Date: Wed, 23 Sep 2020 14:41:37 +0200 Subject: [PATCH 05/28] Update plurals.xml Just a small typo --- play-services-core/src/main/res/values-it/plurals.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/play-services-core/src/main/res/values-it/plurals.xml b/play-services-core/src/main/res/values-it/plurals.xml index 413e2a0a..0ee58003 100644 --- a/play-services-core/src/main/res/values-it/plurals.xml +++ b/play-services-core/src/main/res/values-it/plurals.xml @@ -17,16 +17,16 @@ - %1$d fornitore configurato. - %1$d fornitori configurati. + %1$d fornitore configurato + %1$d fornitori configurati %1$d applicazione registrata %1$d applicazioni registrate - Un permesso necessario al corretto funzionamento di microG non è stato concesso. - Più permessi necessari al corretto funzionamento di microG non sono stati concessi. + Un permesso necessario al corretto funzionamento di microG non è stato concesso + Più permessi necessari al corretto funzionamento di microG non sono stati concessi Richiedi il permesso mancante From 04c955e9981459e3c1fd36af9c6a856a2dad326d Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 15 Sep 2020 15:56:24 +0200 Subject: [PATCH 06/28] Self-Check: Add missing permission check, allow to grant FakeStore spoofing perm Fixes #1178 --- .../tools/selfcheck/PermissionCheckGroup.java | 8 +------ .../org/microg/gms/ui/SelfCheckFragment.java | 24 ++++++++++++++----- .../selfcheck/InstalledPackagesChecks.java | 23 ++++++++++++++++-- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/PermissionCheckGroup.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/PermissionCheckGroup.java index 22543802..0fe6c2b0 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/PermissionCheckGroup.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/PermissionCheckGroup.java @@ -63,13 +63,7 @@ public class PermissionCheckGroup implements SelfCheckGroup { collector.addResult(context.getString(R.string.self_check_name_permission, permLabel), context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED ? Positive : Negative, context.getString(R.string.self_check_resolution_permission, groupLabel), - new SelfCheckGroup.CheckResolver() { - - @Override - public void tryResolve(Fragment fragment) { - fragment.requestPermissions(new String[]{permission}, 0); - } - }); + fragment -> fragment.requestPermissions(new String[]{permission}, 0)); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, e); } diff --git a/play-services-core/src/main/java/org/microg/gms/ui/SelfCheckFragment.java b/play-services-core/src/main/java/org/microg/gms/ui/SelfCheckFragment.java index 4a8e3a84..10d0d83a 100644 --- a/play-services-core/src/main/java/org/microg/gms/ui/SelfCheckFragment.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/SelfCheckFragment.java @@ -33,10 +33,14 @@ import org.microg.tools.selfcheck.SystemChecks; import org.microg.tools.ui.AbstractSelfCheckFragment; import org.microg.tools.ui.AbstractSettingsActivity; +import java.util.ArrayList; import java.util.List; +import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.GET_ACCOUNTS; +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.READ_PHONE_STATE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.os.Build.VERSION.SDK_INT; @@ -49,9 +53,19 @@ public class SelfCheckFragment extends AbstractSelfCheckFragment { checks.add(new RomSpoofSignatureChecks()); checks.add(new InstalledPackagesChecks()); if (SDK_INT > LOLLIPOP_MR1) { - checks.add(new PermissionCheckGroup(ACCESS_COARSE_LOCATION, "android.permission.ACCESS_BACKGROUND_LOCATION", WRITE_EXTERNAL_STORAGE, GET_ACCOUNTS, READ_PHONE_STATE)); + List permissions = new ArrayList<>(); + permissions.add(ACCESS_COARSE_LOCATION); + permissions.add(ACCESS_FINE_LOCATION); + if (SDK_INT >= 29) { + permissions.add(ACCESS_BACKGROUND_LOCATION); + } + permissions.add(READ_EXTERNAL_STORAGE); + permissions.add(WRITE_EXTERNAL_STORAGE); + permissions.add(GET_ACCOUNTS); + permissions.add(READ_PHONE_STATE); + checks.add(new PermissionCheckGroup(permissions.toArray(new String[0]))); } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (SDK_INT >= Build.VERSION_CODES.M) { checks.add(new SystemChecks()); } // checks.add(new NlpOsCompatChecks()); @@ -65,10 +79,8 @@ public class SelfCheckFragment extends AbstractSelfCheckFragment { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == SystemChecks.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) - reset(LayoutInflater.from(getContext())); - else - super.onActivityResult(requestCode, resultCode, data); + reset(LayoutInflater.from(getContext())); + super.onActivityResult(requestCode, resultCode, data); } public static class AsActivity extends AbstractSettingsActivity { diff --git a/play-services-core/src/main/java/org/microg/tools/selfcheck/InstalledPackagesChecks.java b/play-services-core/src/main/java/org/microg/tools/selfcheck/InstalledPackagesChecks.java index e34d3dd0..7b685fb6 100644 --- a/play-services-core/src/main/java/org/microg/tools/selfcheck/InstalledPackagesChecks.java +++ b/play-services-core/src/main/java/org/microg/tools/selfcheck/InstalledPackagesChecks.java @@ -16,8 +16,13 @@ package org.microg.tools.selfcheck; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; +import android.util.Log; + +import androidx.fragment.app.Fragment; import com.google.android.gms.R; @@ -49,11 +54,25 @@ public class InstalledPackagesChecks implements SelfCheckGroup { private boolean addPackageSignedResult(Context context, ResultCollector collector, String nicePackageName, String androidPackageName, String signatureHash) { boolean hashMatches = signatureHash.equals(PackageUtils.firstSignatureDigest(context, androidPackageName)); - collector.addResult(context.getString(R.string.self_check_name_correct_sig, nicePackageName), hashMatches ? Positive : Negative, - context.getString(R.string.self_check_resolution_correct_sig, nicePackageName)); + collector.addResult(context.getString(R.string.self_check_name_correct_sig, nicePackageName), + hashMatches ? Positive : Negative, + context.getString(R.string.self_check_resolution_correct_sig, nicePackageName), + fragment -> tryGrantFakeSignaturePermissionActivity(fragment, androidPackageName)); return hashMatches; } + private void tryGrantFakeSignaturePermissionActivity(Fragment fragment, String androidPackageName) { + ComponentName grantPermissionActivity = new ComponentName(androidPackageName, androidPackageName + ".GrantFakeSignaturePermissionActivity"); + try { + Intent intent = new Intent(); + intent.setPackage(androidPackageName); + intent.setComponent(grantPermissionActivity); + fragment.startActivityForResult(intent, 1); + } catch (Exception e) { + Log.w("SelfCheck", e); + } + } + private boolean addPackageInstalledResult(Context context, ResultCollector collector, String nicePackageName, String androidPackageName) { boolean packageExists = true; try { From c965e3cd0b25e4c131604f5938d9ded3b4793116 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 15 Sep 2020 15:57:36 +0200 Subject: [PATCH 07/28] Add some messing API fields --- .../gms/measurement/internal/AppMetadata.java | 44 ++++++++++++++++++- .../ConditionalUserPropertyParcel.java | 23 ++++++++++ .../common/internal/GetServiceRequest.java | 40 +++++++++-------- 3 files changed, 87 insertions(+), 20 deletions(-) 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 index 754f31b5..be233570 100644 --- 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 @@ -7,13 +7,55 @@ package com.google.android.gms.measurement.internal; import org.microg.safeparcel.AutoSafeParcelable; +import java.util.List; + public class AppMetadata extends AutoSafeParcelable { @Field(2) public String packageName; + @Field(3) + public String googleAppId; @Field(4) public String versionName; + @Field(5) + public String installerPackageName; + @Field(6) + private long field6; + @Field(7) + private long field7; + @Field(8) + private String field8; + @Field(9) + private boolean field9 = true; + @Field(10) + private boolean field10; @Field(11) - public long versionCode; + public long versionCode = Integer.MIN_VALUE; + @Field(12) + private String field12; + @Field(13) + private long field13; + @Field(14) + private long field14; + @Field(15) + public int appType; + @Field(16) + private boolean field16; + @Field(17) + public boolean ssaidCollectionEnabled = true; + @Field(18) + public boolean deferredAnalyticsCollection; + @Field(19) + public String admobAppId; + @Field(21) + public Boolean allowAdPersonalization; + @Field(22) + private long field22; + @Field(23) + public List safelistedEvents; + @Field(24) + public String gaAppId; + @Field(25) + private String field25; 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 index e0594ca8..108c8efe 100644 --- 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 @@ -8,5 +8,28 @@ package com.google.android.gms.measurement.internal; import org.microg.safeparcel.AutoSafeParcelable; public class ConditionalUserPropertyParcel extends AutoSafeParcelable { + @Field(2) + public String appId; + @Field(3) + public String origin; + @Field(4) + public UserAttributeParcel userAttribute; + @Field(5) + public long creationTimestamp; + @Field(6) + public boolean active; + @Field(7) + public String triggerEventName; + @Field(8) + public EventParcel timedOutEvent; + @Field(9) + public long triggerTimeout; + @Field(10) + public EventParcel triggerEvent; + @Field(11) + public long timeToLive; + @Field(12) + public EventParcel expiredEvent; + public static final Creator CREATOR = new AutoCreator<>(ConditionalUserPropertyParcel.class); } diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java index 488feba9..6abc7687 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java @@ -20,6 +20,7 @@ import android.accounts.Account; import android.os.Bundle; import android.os.IBinder; +import com.google.android.gms.common.Feature; import com.google.android.gms.common.api.Scope; import org.microg.gms.common.Constants; @@ -30,32 +31,32 @@ import org.microg.safeparcel.SafeParceled; import java.util.Arrays; public class GetServiceRequest extends AutoSafeParcelable { - @SafeParceled(1) - private int versionCode = 3; - - @SafeParceled(2) + @Field(1) + private int versionCode = 4; + @Field(2) public final int serviceId; - - @SafeParceled(3) + @Field(3) public int gmsVersion; - - @SafeParceled(4) + @Field(4) public String packageName; - - @SafeParceled(5) + @Field(5) public IBinder accountAccessor; - - @SafeParceled(6) + @Field(6) public Scope[] scopes; - - @SafeParceled(7) + @Field(7) public Bundle extras; - - @SafeParceled(8) + @Field(8) public Account account; - - @SafeParceled(9) - public long long1; + @Field(9) + private long field9; + @Field(10) + public Feature[] defaultFeatures; + @Field(11) + public Feature[] apiFeatures; + @Field(12) + private boolean field12; + @Field(13) + private int field13; private GetServiceRequest() { serviceId = -1; @@ -65,6 +66,7 @@ public class GetServiceRequest extends AutoSafeParcelable { public GetServiceRequest(int serviceId) { this.serviceId = serviceId; this.gmsVersion = Constants.MAX_REFERENCE_VERSION; + this.field12 = true; } @Override From e70e6bc956a4d31ca1953324456621562c04b837 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 15 Sep 2020 16:43:54 +0200 Subject: [PATCH 08/28] Mapbox: Track info window shown --- .../org/microg/gms/maps/mapbox/model/Marker.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 129157c0..5d298d8f 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 @@ -16,6 +16,7 @@ package org.microg.gms.maps.mapbox.model +import android.os.Parcel import android.util.Log import com.google.android.gms.dynamic.IObjectWrapper import com.google.android.gms.dynamic.ObjectWrapper @@ -42,6 +43,8 @@ class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options private var draggable: Boolean = options.isDraggable private var tag: IObjectWrapper? = null + private var infoWindowShown = false + override var annotation: Symbol? = null override var removed: Boolean = false override val annotationOptions: SymbolOptions @@ -108,15 +111,17 @@ class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options override fun showInfoWindow() { Log.d(TAG, "unimplemented Method: showInfoWindow") + infoWindowShown = true } override fun hideInfoWindow() { Log.d(TAG, "unimplemented Method: hideInfoWindow") + infoWindowShown = false } override fun isInfoWindowShown(): Boolean { Log.d(TAG, "unimplemented Method: isInfoWindowShow") - return false + return infoWindowShown } override fun setVisible(visible: Boolean) { @@ -202,6 +207,13 @@ class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options override fun getTag(): IObjectWrapper = tag ?: ObjectWrapper.wrap(null) + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + companion object { private val TAG = "GmsMapMarker" } From cab09cb2389b5656ce2144a17d343b417d2ce84b Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 15 Sep 2020 16:46:06 +0200 Subject: [PATCH 09/28] Mapbox: Add dummy ground and tile overlays --- .../org/microg/gms/maps/mapbox/GoogleMap.kt | 7 +- .../gms/maps/mapbox/model/GroundOverlay.kt | 126 ++++++++++++++++++ .../gms/maps/mapbox/model/TileOverlay.kt | 14 ++ 3 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/GroundOverlay.kt create mode 100644 play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/TileOverlay.kt 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 ca565a05..fb0b4f83 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 @@ -112,6 +112,9 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) val markers = mutableMapOf() var markerId = 0L + var groundId = 0L + var tileId = 0L + var storedMapType: Int = options.mapType val waitingCameraUpdates = mutableListOf() @@ -287,12 +290,12 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun addGroundOverlay(options: GroundOverlayOptions): IGroundOverlayDelegate? { Log.d(TAG, "unimplemented Method: addGroundOverlay") - return null + return GroundOverlayImpl(this, "g${groundId++}", options) } override fun addTileOverlay(options: TileOverlayOptions): ITileOverlayDelegate? { Log.d(TAG, "unimplemented Method: addTileOverlay") - return null + return TileOverlayImpl(this, "t${tileId++}", options) } override fun addCircle(options: CircleOptions): ICircleDelegate? { diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/GroundOverlay.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/GroundOverlay.kt new file mode 100644 index 00000000..1dbab951 --- /dev/null +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/GroundOverlay.kt @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.mapbox.model + +import android.os.Parcel +import android.util.Log +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.maps.model.GroundOverlayOptions +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.gms.maps.model.internal.IGroundOverlayDelegate +import org.microg.gms.maps.mapbox.GoogleMapImpl + +class GroundOverlayImpl(private val map: GoogleMapImpl, private val id: String, options: GroundOverlayOptions) : IGroundOverlayDelegate.Stub() { + private var location: LatLng? = options.location + private var width: Float = options.width + private var height: Float = options.height + private var bounds: LatLngBounds? = options.bounds + private var bearing: Float = options.bearing + private var zIndex: Float = options.zIndex + private var visible: Boolean = options.isVisible + private var transparency: Float = options.transparency + + override fun getId(): String { + return id + } + + override fun getPosition(): LatLng? { + return location + } + + override fun setPosition(pos: LatLng?) { + this.location = pos + } + + override fun getWidth(): Float { + return width + } + + override fun getHeight(): Float { + return height + } + + override fun setDimensions(width: Float, height: Float) { + this.width = width + this.height = height + } + + override fun getBounds(): LatLngBounds? { + return bounds + } + + override fun getBearing(): Float { + return bearing + } + + override fun setBearing(bearing: Float) { + this.bearing = bearing + } + + override fun setZIndex(zIndex: Float) { + this.zIndex = zIndex + } + + override fun getZIndex(): Float { + return zIndex + } + + override fun isVisible(): Boolean { + return visible + } + + override fun setVisible(visible: Boolean) { + this.visible = visible + } + + override fun getTransparency(): Float { + return transparency + } + + override fun setTransparency(transparency: Float) { + this.transparency = transparency + } + + override fun setDimension(dimension: Float) { + Log.w(TAG, "unimplemented Method: setDimension") + } + + override fun setPositionFromBounds(bounds: LatLngBounds?) { + this.bounds = bounds + } + + override fun equalsRemote(other: IGroundOverlayDelegate?): Boolean { + return this.equals(other) + } + + override fun hashCode(): Int { + return id.hashCode() + } + + override fun hashCodeRemote(): Int { + return hashCode() + } + + override fun remove() { + Log.w(TAG, "unimplemented Method: remove") + } + + override fun todo(obj: IObjectWrapper?) { + Log.w(TAG, "unimplemented Method: todo") + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + private val TAG = "GmsMapMarker" + } +} diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/TileOverlay.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/TileOverlay.kt new file mode 100644 index 00000000..dfa4cb49 --- /dev/null +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/TileOverlay.kt @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.mapbox.model + +import com.google.android.gms.maps.model.TileOverlayOptions +import com.google.android.gms.maps.model.internal.ITileOverlayDelegate +import org.microg.gms.maps.mapbox.GoogleMapImpl + +class TileOverlayImpl(private val map: GoogleMapImpl, private val id: String, options: TileOverlayOptions) : ITileOverlayDelegate.Stub() { + +} From 6afcca0396be456995fa9f960ec1b581f778a942 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 27 Sep 2020 11:33:02 +0200 Subject: [PATCH 10/28] EN: Add latest API details, improve performance --- .../android/gms/common/api/ApiException.java | 10 +- .../common/api/ResolvableApiException.java | 8 + play-services-nearby-api/build.gradle | 1 - .../exposurenotification/DailySummary.aidl | 8 + .../DiagnosisKeysDataMapping.aidl | 8 + .../exposurenotification/ExposureWindow.aidl | 8 + .../GetCalibrationConfidenceParams.aidl | 8 + .../internal/GetDailySummariesParams.aidl | 8 + .../GetDiagnosisKeysDataMappingParams.aidl | 8 + .../internal/GetExposureWindowsParams.aidl | 8 + .../internal/GetVersionParams.aidl | 8 + .../internal/IDailySummaryListCallback.aidl | 13 + .../IDiagnosisKeysDataMappingCallback.aidl | 13 + .../internal/IExposureWindowListCallback.aidl | 13 + .../internal/IIntCallback.aidl | 12 + .../internal/ILongCallback.aidl | 12 + .../INearbyExposureNotificationService.aidl | 13 + .../SetDiagnosisKeysDataMappingParams.aidl | 8 + .../CalibrationConfidence.java | 37 ++ .../DailySummariesConfig.java | 244 +++++++++++++ .../exposurenotification/DailySummary.java | 158 ++++++++ .../DiagnosisKeysDataMapping.java | 116 ++++++ .../exposurenotification/ExposureWindow.java | 165 +++++++++ .../exposurenotification/Infectiousness.java | 24 ++ .../exposurenotification/ReportType.java | 27 ++ .../exposurenotification/RiskLevel.java | 9 + .../exposurenotification/ScanInstance.java | 93 +++++ .../GetCalibrationConfidenceParams.java | 21 ++ .../internal/GetDailySummariesParams.java | 26 ++ .../GetDiagnosisKeysDataMappingParams.java | 21 ++ .../internal/GetExposureWindowsParams.java | 24 ++ .../internal/GetVersionParams.java | 22 ++ .../internal/ProvideDiagnosisKeysParams.java | 3 + .../SetDiagnosisKeysDataMappingParams.java | 27 ++ .../exposurenotification/AdvertiserService.kt | 26 +- .../exposurenotification/CleanupService.kt | 13 +- .../nearby/exposurenotification/Constants.kt | 29 +- .../nearby/exposurenotification/DeviceInfo.kt | 9 +- .../exposurenotification/ExposureDatabase.kt | 338 +++++++++++++----- .../ExposureNotificationService.kt | 11 +- .../ExposureNotificationServiceImpl.kt | 206 +++++++---- proguard.flags | 2 + 42 files changed, 1602 insertions(+), 216 deletions(-) create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/DailySummary.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/DiagnosisKeysDataMapping.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/ExposureWindow.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetCalibrationConfidenceParams.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetDailySummariesParams.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetDiagnosisKeysDataMappingParams.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetExposureWindowsParams.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetVersionParams.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IDailySummaryListCallback.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IDiagnosisKeysDataMappingCallback.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IExposureWindowListCallback.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IIntCallback.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/ILongCallback.aidl create mode 100644 play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/SetDiagnosisKeysDataMappingParams.aidl create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/CalibrationConfidence.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/DailySummariesConfig.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/DailySummary.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/DiagnosisKeysDataMapping.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureWindow.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/Infectiousness.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ReportType.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ScanInstance.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetCalibrationConfidenceParams.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetDailySummariesParams.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetDiagnosisKeysDataMappingParams.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetExposureWindowsParams.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetVersionParams.java create mode 100644 play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/SetDiagnosisKeysDataMappingParams.java diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/api/ApiException.java b/play-services-basement/src/main/java/com/google/android/gms/common/api/ApiException.java index c508b301..52cccd3c 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/api/ApiException.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/api/ApiException.java @@ -1,6 +1,12 @@ -package com.google.android.gms.common.api; +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.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. + */ -import com.google.android.gms.common.api.Status; +package com.google.android.gms.common.api; import org.microg.gms.common.PublicApi; diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/api/ResolvableApiException.java b/play-services-basement/src/main/java/com/google/android/gms/common/api/ResolvableApiException.java index 22dc6ed0..d46480d3 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/api/ResolvableApiException.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/api/ResolvableApiException.java @@ -1,3 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.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.common.api; import android.app.Activity; diff --git a/play-services-nearby-api/build.gradle b/play-services-nearby-api/build.gradle index 8efb9225..650fe429 100644 --- a/play-services-nearby-api/build.gradle +++ b/play-services-nearby-api/build.gradle @@ -23,6 +23,5 @@ android { dependencies { api project(':play-services-basement') - api project(':play-services-base-api') } diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/DailySummary.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/DailySummary.aidl new file mode 100644 index 00000000..4c7d25b6 --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/DailySummary.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification; + +parcelable DailySummary; diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/DiagnosisKeysDataMapping.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/DiagnosisKeysDataMapping.aidl new file mode 100644 index 00000000..390638fb --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/DiagnosisKeysDataMapping.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification; + +parcelable DiagnosisKeysDataMapping; diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/ExposureWindow.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/ExposureWindow.aidl new file mode 100644 index 00000000..b6247e34 --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/ExposureWindow.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification; + +parcelable ExposureWindow; diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetCalibrationConfidenceParams.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetCalibrationConfidenceParams.aidl new file mode 100644 index 00000000..641a2237 --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetCalibrationConfidenceParams.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +parcelable GetCalibrationConfidenceParams; diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetDailySummariesParams.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetDailySummariesParams.aidl new file mode 100644 index 00000000..fd47500b --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetDailySummariesParams.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +parcelable GetDailySummariesParams; diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetDiagnosisKeysDataMappingParams.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetDiagnosisKeysDataMappingParams.aidl new file mode 100644 index 00000000..07316df1 --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetDiagnosisKeysDataMappingParams.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +parcelable GetDiagnosisKeysDataMappingParams; diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetExposureWindowsParams.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetExposureWindowsParams.aidl new file mode 100644 index 00000000..1df7e95c --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetExposureWindowsParams.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +parcelable GetExposureWindowsParams; diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetVersionParams.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetVersionParams.aidl new file mode 100644 index 00000000..4c0acbf8 --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/GetVersionParams.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +parcelable GetVersionParams; diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IDailySummaryListCallback.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IDailySummaryListCallback.aidl new file mode 100644 index 00000000..4f52acb3 --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IDailySummaryListCallback.aidl @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +import com.google.android.gms.common.api.Status; +import com.google.android.gms.nearby.exposurenotification.DailySummary; + +interface IDailySummaryListCallback { + void onResult(in Status status, in List result); +} diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IDiagnosisKeysDataMappingCallback.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IDiagnosisKeysDataMappingCallback.aidl new file mode 100644 index 00000000..61f3e1ff --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IDiagnosisKeysDataMappingCallback.aidl @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +import com.google.android.gms.common.api.Status; +import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping; + +interface IDiagnosisKeysDataMappingCallback { + void onResult(in Status status, in DiagnosisKeysDataMapping result); +} diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IExposureWindowListCallback.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IExposureWindowListCallback.aidl new file mode 100644 index 00000000..2f314877 --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IExposureWindowListCallback.aidl @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +import com.google.android.gms.common.api.Status; +import com.google.android.gms.nearby.exposurenotification.ExposureWindow; + +interface IExposureWindowListCallback { + void onResult(in Status status, in List result); +} diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IIntCallback.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IIntCallback.aidl new file mode 100644 index 00000000..3dc8faf3 --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/IIntCallback.aidl @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +import com.google.android.gms.common.api.Status; + +interface IIntCallback { + void onResult(in Status status, int result); +} diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/ILongCallback.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/ILongCallback.aidl new file mode 100644 index 00000000..a17a61df --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/ILongCallback.aidl @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +import com.google.android.gms.common.api.Status; + +interface ILongCallback { + void onResult(in Status status, long result); +} diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/INearbyExposureNotificationService.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/INearbyExposureNotificationService.aidl index 8d13de35..b5dcfbe8 100644 --- a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/INearbyExposureNotificationService.aidl +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/INearbyExposureNotificationService.aidl @@ -12,6 +12,12 @@ import com.google.android.gms.nearby.exposurenotification.internal.GetTemporaryE import com.google.android.gms.nearby.exposurenotification.internal.ProvideDiagnosisKeysParams; import com.google.android.gms.nearby.exposurenotification.internal.GetExposureSummaryParams; import com.google.android.gms.nearby.exposurenotification.internal.GetExposureInformationParams; +import com.google.android.gms.nearby.exposurenotification.internal.GetExposureWindowsParams; +import com.google.android.gms.nearby.exposurenotification.internal.GetVersionParams; +import com.google.android.gms.nearby.exposurenotification.internal.GetCalibrationConfidenceParams; +import com.google.android.gms.nearby.exposurenotification.internal.GetDailySummariesParams; +import com.google.android.gms.nearby.exposurenotification.internal.SetDiagnosisKeysDataMappingParams; +import com.google.android.gms.nearby.exposurenotification.internal.GetDiagnosisKeysDataMappingParams; interface INearbyExposureNotificationService{ void start(in StartParams params) = 0; @@ -22,4 +28,11 @@ interface INearbyExposureNotificationService{ void getExposureSummary(in GetExposureSummaryParams params) = 6; void getExposureInformation(in GetExposureInformationParams params) = 7; + + void getExposureWindows(in GetExposureWindowsParams params) = 12; + void getVersion(in GetVersionParams params) = 13; + void getCalibrationConfidence(in GetCalibrationConfidenceParams params) = 14; + void getDailySummaries(in GetDailySummariesParams params) = 15; + void setDiagnosisKeysDataMapping(in SetDiagnosisKeysDataMappingParams params) = 16; + void getDiagnosisKeysDataMapping(in GetDiagnosisKeysDataMappingParams params) = 17; } diff --git a/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/SetDiagnosisKeysDataMappingParams.aidl b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/SetDiagnosisKeysDataMappingParams.aidl new file mode 100644 index 00000000..82fb3193 --- /dev/null +++ b/play-services-nearby-api/src/main/aidl/com/google/android/gms/nearby/exposurenotification/internal/SetDiagnosisKeysDataMappingParams.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +parcelable SetDiagnosisKeysDataMappingParams; diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/CalibrationConfidence.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/CalibrationConfidence.java new file mode 100644 index 00000000..f350b622 --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/CalibrationConfidence.java @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.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; + +import org.microg.gms.common.PublicApi; + +/** + * Calibration confidence defined for an {@link ExposureWindow}. + */ +@PublicApi +public @interface CalibrationConfidence { + /** + * No calibration data, using fleet-wide as default options. + */ + int LOWEST = 0; + /** + * Using average calibration over models from manufacturer. + */ + int LOW = 1; + /** + * Using single-antenna orientation for a similar model. + */ + int MEDIUM = 2; + /** + * Using significant calibration data for this model. + */ + int HIGH = 3; + + @PublicApi(exclude = true) + int VALUES = 4; +} diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/DailySummariesConfig.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/DailySummariesConfig.java new file mode 100644 index 00000000..d1b72102 --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/DailySummariesConfig.java @@ -0,0 +1,244 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.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; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Configuration of per-day summary of exposures. + *

+ * During summarization the following are computed for each ExposureWindows: + *

    + *
  • a weighted duration, computed as + * {@code ( immediateDurationSeconds * immediateDurationWeight ) + ( nearDurationSeconds * nearDurationWeight ) + ( mediumDurationSeconds * mediumDurationWeight ) + ( otherDurationSeconds * otherDurationWeight )}
  • + *
  • a score, computed as + * {@code reportTypeWeights[Tek.reportType] * infectiousnessWeights[infectiousness] * weightedDuration} + * where infectiousness and reportType are set based on the ExposureWindow's diagnosis key and the DiagnosisKeysDataMapping
  • + *
+ *

+ * The {@link ExposureWindow}s are then filtered, removing those with score lower than {@link #getMinimumWindowScore()}. + *

+ * Scores and weighted durations of the {@link ExposureWindow}s that pass the {@link #getMinimumWindowScore()} are then aggregated over a day to compute the maximum and cumulative scores and duration: + *

    + *
  • sumScore = sum(score of ExposureWindows)
  • + *
  • maxScore = max(score of ExposureWindows)
  • + *
  • weightedDurationSum = sum(weighted duration of ExposureWindow)
  • + *
+ * Note that when the weights are typically around 100% (1.0), both the scores and the weightedDurationSum can be considered as being expressed in seconds. For example, 15 minutes of exposure with all weights equal to 1.0 would be 60 * 15 = 900 (seconds). + */ +@PublicApi +public class DailySummariesConfig extends AutoSafeParcelable { + @Field(1) + private List reportTypeWeights; + @Field(2) + private List infectiousnessWeights; + @Field(3) + private List attenuationBucketThresholdDb; + @Field(4) + private List attenuationBucketWeights; + @Field(5) + private int daysSinceExposureThreshold; + @Field(6) + private double minimumWindowScore; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DailySummariesConfig)) return false; + + DailySummariesConfig that = (DailySummariesConfig) o; + + if (daysSinceExposureThreshold != that.daysSinceExposureThreshold) return false; + if (Double.compare(that.minimumWindowScore, minimumWindowScore) != 0) return false; + if (reportTypeWeights != null ? !reportTypeWeights.equals(that.reportTypeWeights) : that.reportTypeWeights != null) + return false; + if (infectiousnessWeights != null ? !infectiousnessWeights.equals(that.infectiousnessWeights) : that.infectiousnessWeights != null) + return false; + if (attenuationBucketThresholdDb != null ? !attenuationBucketThresholdDb.equals(that.attenuationBucketThresholdDb) : that.attenuationBucketThresholdDb != null) + return false; + return attenuationBucketWeights != null ? attenuationBucketWeights.equals(that.attenuationBucketWeights) : that.attenuationBucketWeights == null; + } + + /** + * Thresholds defining the BLE attenuation buckets edges. + *

+ * This list must have 3 elements: the immediate, near, and medium thresholds. See attenuationBucketWeights for more information. + *

+ * These elements must be between 0 and 255 and come in increasing order. + */ + public List getAttenuationBucketThresholdDb() { + return attenuationBucketThresholdDb; + } + + /** + * Scoring weights to associate with ScanInstances depending on the attenuation bucket in which their typicalAttenuationDb falls. + *

+ * This list must have 4 elements, corresponding to the weights for the 4 buckets. + *

    + *
  • immediate bucket: -infinity < attenuation <= immediate threshold
  • + *
  • near bucket: immediate threshold < attenuation <= near threshold
  • + *
  • medium bucket: near threshold < attenuation <= medium threshold
  • + *
  • other bucket: medium threshold < attenuation < +infinity
  • + *
+ * Each element must be between 0 and 2.5. + */ + public List getAttenuationBucketWeights() { + return attenuationBucketWeights; + } + + /** + * Reserved for future use, behavior will be changed in future revisions. No value should be set, or else 0 should be used. + */ + public int getDaysSinceExposureThreshold() { + return daysSinceExposureThreshold; + } + + /** + * Scoring weights to associate with exposures with different Infectiousness. + *

+ * This map can include weights for the following Infectiousness values: + *

    + *
  • STANDARD
  • + *
  • HIGH
  • + *
+ * Each element must be between 0 and 2.5. + */ + public Map getInfectiousnessWeights() { + HashMap map = new HashMap<>(); + for (int i = 0; i < infectiousnessWeights.size(); i++) { + map.put(i, infectiousnessWeights.get(i)); + } + return map; + } + + /** + * Minimum score that {@link ExposureWindow}s must reach in order to be included in the {@link DailySummary.ExposureSummaryData}. + *

+ * Use 0 to consider all {@link ExposureWindow}s (recommended). + */ + public double getMinimumWindowScore() { + return minimumWindowScore; + } + + /** + * Scoring weights to associate with exposures with different ReportTypes. + *

+ * This map can include weights for the following ReportTypes: + *

    + *
  • CONFIRMED_TEST
  • + *
  • CONFIRMED_CLINICAL_DIAGNOSIS
  • + *
  • SELF_REPORT
  • + *
  • RECURSIVE (reserved for future use)
  • + *
+ * Each element must be between 0 and 2.5. + */ + public Map getReportTypeWeights() { + HashMap map = new HashMap<>(); + for (int i = 0; i < reportTypeWeights.size(); i++) { + map.put(i, reportTypeWeights.get(i)); + } + return map; + } + + @Override + public int hashCode() { + int result; + long temp; + result = reportTypeWeights != null ? reportTypeWeights.hashCode() : 0; + result = 31 * result + (infectiousnessWeights != null ? infectiousnessWeights.hashCode() : 0); + result = 31 * result + (attenuationBucketThresholdDb != null ? attenuationBucketThresholdDb.hashCode() : 0); + result = 31 * result + (attenuationBucketWeights != null ? attenuationBucketWeights.hashCode() : 0); + result = 31 * result + daysSinceExposureThreshold; + temp = Double.doubleToLongBits(minimumWindowScore); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + /** + * A builder for {@link DailySummariesConfig}. + */ + public static class DailySummariesConfigBuilder { + private Double[] reportTypeWeights = new Double[ReportType.VALUES]; + private Double[] infectiousnessWeights = new Double[Infectiousness.VALUES]; + private List attenuationBucketThresholdDb; + private List attenuationBucketWeights; + private int daysSinceExposureThreshold; + private double minimumWindowScore; + + public DailySummariesConfigBuilder() { + Arrays.fill(reportTypeWeights, 0.0); + Arrays.fill(infectiousnessWeights, 0.0); + } + + public DailySummariesConfig build() { + if (attenuationBucketThresholdDb == null) + throw new IllegalStateException("Must set attenuationBucketThresholdDb"); + if (attenuationBucketWeights == null) + throw new IllegalStateException("Must set attenuationBucketWeights"); + DailySummariesConfig config = new DailySummariesConfig(); + config.reportTypeWeights = Arrays.asList(reportTypeWeights); + config.infectiousnessWeights = Arrays.asList(infectiousnessWeights); + config.attenuationBucketThresholdDb = attenuationBucketThresholdDb; + config.attenuationBucketWeights = attenuationBucketWeights; + config.daysSinceExposureThreshold = daysSinceExposureThreshold; + config.minimumWindowScore = minimumWindowScore; + return config; + } + + /** + * See {@link #getAttenuationBucketThresholdDb()} and {@link #getAttenuationBucketWeights()} + */ + public DailySummariesConfigBuilder setAttenuationBuckets(List thresholds, List weights) { + attenuationBucketThresholdDb = new ArrayList<>(thresholds); + attenuationBucketWeights = new ArrayList<>(weights); + return this; + } + + /** + * See {@link #getDaysSinceExposureThreshold()} + */ + public DailySummariesConfigBuilder setDaysSinceExposureThreshold(int daysSinceExposureThreshold) { + this.daysSinceExposureThreshold = daysSinceExposureThreshold; + return this; + } + + /** + * See {@link #getInfectiousnessWeights()} + */ + public DailySummariesConfigBuilder setInfectiousnessWeight(@Infectiousness int infectiousness, double weight) { + infectiousnessWeights[infectiousness] = weight; + return this; + } + + /** + * See {@link #getMinimumWindowScore()} + */ + public DailySummariesConfigBuilder setMinimumWindowScore(double minimumWindowScore) { + this.minimumWindowScore = minimumWindowScore; + return this; + } + + /** + * See {@link #getReportTypeWeights()} + */ + public DailySummariesConfigBuilder setReportTypeWeight(@ReportType int reportType, double weight) { + reportTypeWeights[reportType] = weight; + return this; + } + } + + public static final Creator CREATOR = new AutoCreator<>(DailySummariesConfig.class); +} diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/DailySummary.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/DailySummary.java new file mode 100644 index 00000000..340bc087 --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/DailySummary.java @@ -0,0 +1,158 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.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; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.List; + +/** + * Daily exposure summary to pass to client side. + */ +@PublicApi +public class DailySummary extends AutoSafeParcelable { + @Field(1) + private int daysSinceEpoch; + @Field(2) + private List reportSummaries; + @Field(3) + private ExposureSummaryData summaryData; + + private DailySummary() { + } + + @PublicApi(exclude = true) + public DailySummary(int daysSinceEpoch, List reportSummaries, ExposureSummaryData summaryData) { + this.daysSinceEpoch = daysSinceEpoch; + this.reportSummaries = reportSummaries; + this.summaryData = summaryData; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DailySummary)) return false; + + DailySummary that = (DailySummary) o; + + if (daysSinceEpoch != that.daysSinceEpoch) return false; + if (reportSummaries != null ? !reportSummaries.equals(that.reportSummaries) : that.reportSummaries != null) + return false; + return summaryData != null ? summaryData.equals(that.summaryData) : that.summaryData == null; + } + + /** + * Returns days since epoch of the {@link ExposureWindow}s that went into this summary. + */ + public int getDaysSinceEpoch() { + return daysSinceEpoch; + } + + /** + * Summary of all exposures on this day. + */ + public ExposureSummaryData getSummaryData() { + return summaryData; + } + + /** + * Summary of all exposures on this day of a specific diagnosis {@link ReportType}. + */ + public ExposureSummaryData getSummaryDataForReportType(@ReportType int reportType) { + return reportSummaries.get(reportType); + } + + @Override + public int hashCode() { + int result = daysSinceEpoch; + result = 31 * result + (reportSummaries != null ? reportSummaries.hashCode() : 0); + result = 31 * result + (summaryData != null ? summaryData.hashCode() : 0); + return result; + } + + /** + * Stores different scores for specific {@link ReportType}. + */ + public static class ExposureSummaryData extends AutoSafeParcelable { + @Field(1) + private double maximumScore; + @Field(2) + private double scoreSum; + @Field(3) + private double weightedDurationSum; + + private ExposureSummaryData() { + } + + @PublicApi(exclude = true) + public ExposureSummaryData(double maximumScore, double scoreSum, double weightedDurationSum) { + this.maximumScore = maximumScore; + this.scoreSum = scoreSum; + this.weightedDurationSum = weightedDurationSum; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ExposureSummaryData)) return false; + + ExposureSummaryData that = (ExposureSummaryData) o; + + if (Double.compare(that.maximumScore, maximumScore) != 0) return false; + if (Double.compare(that.scoreSum, scoreSum) != 0) return false; + return Double.compare(that.weightedDurationSum, weightedDurationSum) == 0; + } + + /** + * Highest score of all {@link ExposureWindow}s aggregated into this summary. + *

+ * See {@link DailySummariesConfig} for more information about how the per-{@link ExposureWindow} score is computed. + */ + public double getMaximumScore() { + return maximumScore; + } + + /** + * Sum of scores for all {@link ExposureWindow}s aggregated into this summary. + *

+ * See {@link DailySummariesConfig} for more information about how the per-{@link ExposureWindow} score is computed. + */ + public double getScoreSum() { + return scoreSum; + } + + + /** + * Sum of weighted durations for all {@link ExposureWindow}s aggregated into this summary. + *

+ * See {@link DailySummariesConfig} for more information about how the per-{@link ExposureWindow} score is computed. + */ + public double getWeightedDurationSum() { + return weightedDurationSum; + } + + @Override + public int hashCode() { + int result; + long temp; + temp = Double.doubleToLongBits(maximumScore); + result = (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(scoreSum); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(weightedDurationSum); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + public static final Creator CREATOR = new AutoCreator<>(ExposureSummaryData.class); + } + + public static final Creator CREATOR = new AutoCreator<>(DailySummary.class); +} diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/DiagnosisKeysDataMapping.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/DiagnosisKeysDataMapping.java new file mode 100644 index 00000000..71c79fa0 --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/DiagnosisKeysDataMapping.java @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.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; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Mappings from diagnosis keys data to concepts returned by the API. + */ +@PublicApi +public class DiagnosisKeysDataMapping extends AutoSafeParcelable { + @Field(1) + private List daysSinceOnsetToInfectiousness; + @Field(2) + @ReportType + private int reportTypeWhenMissing; + @Field(3) + @Infectiousness + private int infectiousnessWhenDaysSinceOnsetMissing; + + /** + * Mapping from diagnosisKey.daysSinceOnsetOfSymptoms to {@link Infectiousness}. + *

+ * Infectiousness is computed from this mapping and the tek metadata as - daysSinceOnsetToInfectiousness[{@link TemporaryExposureKey#getDaysSinceOnsetOfSymptoms()}], or - {@link #getInfectiousnessWhenDaysSinceOnsetMissing()} if {@link TemporaryExposureKey#getDaysSinceOnsetOfSymptoms()} is {@link TemporaryExposureKey#DAYS_SINCE_ONSET_OF_SYMPTOMS_UNKNOWN}. + *

+ * Values of DaysSinceOnsetOfSymptoms that aren't represented in this map are given {@link Infectiousness#NONE} as infectiousness. Exposures with infectiousness equal to {@link Infectiousness#NONE} are dropped. + */ + public Map getDaysSinceOnsetToInfectiousness() { + HashMap map = new HashMap<>(); + for (int i = 0; i < daysSinceOnsetToInfectiousness.size(); i++) { + map.put(i, daysSinceOnsetToInfectiousness.get(i)); + } + return map; + } + + /** + * Infectiousness of TEKs for which onset of symptoms is not set. + *

+ * See {@link #getDaysSinceOnsetToInfectiousness()} for more info. + */ + public int getInfectiousnessWhenDaysSinceOnsetMissing() { + return infectiousnessWhenDaysSinceOnsetMissing; + } + + /** + * Report type to default to when a TEK has no report type set. + *

+ * This report type gets used when creating the {@link ExposureWindow}s and the {@link DailySummary}s. The system will treat TEKs with missing report types as if they had this provided report type. + */ + public int getReportTypeWhenMissing() { + return reportTypeWhenMissing; + } + + /** + * A builder for {@link DiagnosisKeysDataMapping}. + */ + public static class DiagnosisKeysDataMappingBuilder { + private final static int MAX_DAYS = 29; + private List daysSinceOnsetToInfectiousness; + @ReportType + private int reportTypeWhenMissing = ReportType.UNKNOWN; + @Infectiousness + private Integer infectiousnessWhenDaysSinceOnsetMissing; + + public DiagnosisKeysDataMapping build() { + if (daysSinceOnsetToInfectiousness == null) + throw new IllegalStateException("Must set daysSinceOnsetToInfectiousness"); + if (reportTypeWhenMissing == ReportType.UNKNOWN) + throw new IllegalStateException("Must set reportTypeWhenMissing"); + if (infectiousnessWhenDaysSinceOnsetMissing == null) + throw new IllegalStateException("Must set infectiousnessWhenDaysSinceOnsetMissing"); + DiagnosisKeysDataMapping mapping = new DiagnosisKeysDataMapping(); + mapping.daysSinceOnsetToInfectiousness = daysSinceOnsetToInfectiousness; + mapping.reportTypeWhenMissing = reportTypeWhenMissing; + mapping.infectiousnessWhenDaysSinceOnsetMissing = infectiousnessWhenDaysSinceOnsetMissing; + return mapping; + } + + public DiagnosisKeysDataMappingBuilder setDaysSinceOnsetToInfectiousness(Map daysSinceOnsetToInfectiousness) { + if (daysSinceOnsetToInfectiousness.size() > MAX_DAYS) + throw new IllegalArgumentException("daysSinceOnsetToInfectiousness exceeds " + MAX_DAYS + " days"); + Integer[] values = new Integer[MAX_DAYS]; + Arrays.fill(values, 0); + for (Map.Entry entry : daysSinceOnsetToInfectiousness.entrySet()) { + if (entry.getKey() > 14) throw new IllegalArgumentException("invalid day since onset"); + values[entry.getKey() + 14] = entry.getValue(); + } + this.daysSinceOnsetToInfectiousness = Arrays.asList(values); + return this; + } + + public DiagnosisKeysDataMappingBuilder setInfectiousnessWhenDaysSinceOnsetMissing(@Infectiousness int infectiousnessWhenDaysSinceOnsetMissing) { + this.infectiousnessWhenDaysSinceOnsetMissing = infectiousnessWhenDaysSinceOnsetMissing; + return this; + } + + public DiagnosisKeysDataMappingBuilder setReportTypeWhenMissing(@ReportType int reportTypeWhenMissing) { + this.reportTypeWhenMissing = reportTypeWhenMissing; + return this; + } + } + + public static final Creator CREATOR = new AutoCreator<>(DiagnosisKeysDataMapping.class); +} diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureWindow.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureWindow.java new file mode 100644 index 00000000..55bac873 --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureWindow.java @@ -0,0 +1,165 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.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; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * A duration of up to 30 minutes during which beacons from a TEK were observed. + *

+ * Each {@link ExposureWindow} corresponds to a single TEK, but one TEK can lead to several {@link ExposureWindow} due to random 15-30 minutes cuts. See {@link ExposureNotificationClient#getExposureWindows()} for more info. + *

+ * The TEK itself isn't exposed by the API. + */ +@PublicApi +public class ExposureWindow extends AutoSafeParcelable { + @Field(1) + private long dateMillisSinceEpoch; + @Field(2) + private List scanInstances; + @Field(3) + @ReportType + private int reportType; + @Field(4) + @Infectiousness + private int infectiousness; + @Field(5) + @CalibrationConfidence + private int calibrationConfidence; + + private ExposureWindow() { + } + + private ExposureWindow(long dateMillisSinceEpoch, List scanInstances, int reportType, int infectiousness, int calibrationConfidence) { + this.dateMillisSinceEpoch = dateMillisSinceEpoch; + this.scanInstances = scanInstances; + this.reportType = reportType; + this.infectiousness = infectiousness; + this.calibrationConfidence = calibrationConfidence; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ExposureWindow)) return false; + + ExposureWindow that = (ExposureWindow) o; + + if (dateMillisSinceEpoch != that.dateMillisSinceEpoch) return false; + if (reportType != that.reportType) return false; + if (infectiousness != that.infectiousness) return false; + if (calibrationConfidence != that.calibrationConfidence) return false; + return scanInstances != null ? scanInstances.equals(that.scanInstances) : that.scanInstances == null; + } + + /** + * Confidence of the BLE Transmit power calibration of the transmitting device. + */ + @CalibrationConfidence + public int getCalibrationConfidence() { + return calibrationConfidence; + } + + /** + * Returns the epoch time in milliseconds the exposure occurred. This will represent the start of a day in UTC. + */ + public long getDateMillisSinceEpoch() { + return dateMillisSinceEpoch; + } + + /** + * Infectiousness of the TEK that caused this exposure, computed from the days since onset of symptoms using the daysToInfectiousnessMapping. + */ + @Infectiousness + public int getInfectiousness() { + return infectiousness; + } + + /** + * Report Type of the TEK that caused this exposure + *

+ * TEKs with no report type set are returned with reportType=CONFIRMED_TEST. + *

+ * TEKs with RECURSIVE report type may be dropped because this report type is reserved for future use. + *

+ * TEKs with REVOKED or invalid report types do not lead to exposures. + */ + @ReportType + public int getReportType() { + return reportType; + } + + /** + * Sightings of this ExposureWindow, time-ordered. + *

+ * Each sighting corresponds to a scan (of a few seconds) during which a beacon with the TEK causing this exposure was observed. + */ + public List getScanInstances() { + return scanInstances; + } + + @Override + public int hashCode() { + int result = (int) (dateMillisSinceEpoch ^ (dateMillisSinceEpoch >>> 32)); + result = 31 * result + (scanInstances != null ? scanInstances.hashCode() : 0); + result = 31 * result + reportType; + result = 31 * result + infectiousness; + result = 31 * result + calibrationConfidence; + return result; + } + + /** + * Builder for ExposureWindow. + */ + public static class Builder { + private long dateMillisSinceEpoch; + private List scanInstances; + @ReportType + private int reportType; + @Infectiousness + private int infectiousness; + @CalibrationConfidence + private int calibrationConfidence; + + public ExposureWindow build() { + return new ExposureWindow(dateMillisSinceEpoch, scanInstances, reportType, infectiousness, calibrationConfidence); + } + + public Builder setCalibrationConfidence(int calibrationConfidence) { + this.calibrationConfidence = calibrationConfidence; + return this; + } + + public Builder setDateMillisSinceEpoch(long dateMillisSinceEpoch) { + this.dateMillisSinceEpoch = dateMillisSinceEpoch; + return this; + } + + public Builder setInfectiousness(@Infectiousness int infectiousness) { + this.infectiousness = infectiousness; + return this; + } + + public Builder setReportType(@ReportType int reportType) { + this.reportType = reportType; + return this; + } + + public Builder setScanInstances(List scanInstances) { + this.scanInstances = new ArrayList<>(scanInstances); + return this; + } + } + + public static final Creator CREATOR = new AutoCreator<>(ExposureWindow.class); +} diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/Infectiousness.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/Infectiousness.java new file mode 100644 index 00000000..b63031d3 --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/Infectiousness.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.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; + +import org.microg.gms.common.PublicApi; + +/** + * Infectiousness defined for an {@link ExposureWindow}. + */ +@PublicApi +public @interface Infectiousness { + int NONE = 0; + int STANDARD = 1; + int HIGH = 2; + + @PublicApi(exclude = true) + int VALUES = 3; +} diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ReportType.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ReportType.java new file mode 100644 index 00000000..e14d2bea --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ReportType.java @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.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; + +import org.microg.gms.common.PublicApi; + +/** + * Report type defined for a {@link TemporaryExposureKey}. + */ +@PublicApi +public @interface ReportType { + int UNKNOWN = 0; + int CONFIRMED_TEST = 1; + int CONFIRMED_CLINICAL_DIAGNOSIS = 2; + int SELF_REPORT = 3; + int RECURSIVE = 4; + int REVOKED = 5; + + @PublicApi(exclude = true) + int VALUES = 6; +} 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 6e7c288d..f41dfbb2 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 @@ -8,6 +8,12 @@ package com.google.android.gms.nearby.exposurenotification; +import org.microg.gms.common.PublicApi; + +/** + * Risk level defined for an {@link TemporaryExposureKey}. + */ +@PublicApi public @interface RiskLevel { int RISK_LEVEL_INVALID = 0; int RISK_LEVEL_LOWEST = 1; @@ -18,4 +24,7 @@ public @interface RiskLevel { int RISK_LEVEL_HIGH = 6; int RISK_LEVEL_VERY_HIGH = 7; int RISK_LEVEL_HIGHEST = 8; + + @PublicApi(exclude = true) + int VALUES = 9; } diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ScanInstance.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ScanInstance.java new file mode 100644 index 00000000..c2de9411 --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ScanInstance.java @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.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; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +/** + * Information about the sighting of a TEK within a BLE scan (of a few seconds). + *

+ * The TEK itself isn't exposed by the API. + */ +@PublicApi +public class ScanInstance extends AutoSafeParcelable { + @Field(1) + private int typicalAttenuationDb; + @Field(2) + private int minAttenuationDb; + @Field(3) + private int secondsSinceLastScan; + + private ScanInstance() { + } + + private ScanInstance(int typicalAttenuationDb, int minAttenuationDb, int secondsSinceLastScan) { + this.typicalAttenuationDb = typicalAttenuationDb; + this.minAttenuationDb = minAttenuationDb; + this.secondsSinceLastScan = secondsSinceLastScan; + } + + /** + * Minimum attenuation of all of this TEK's beacons received during the scan, in dB. + */ + public int getMinAttenuationDb() { + return minAttenuationDb; + } + + /** + * Seconds elapsed since the previous scan, typically used as a weight. + *

+ * Two example uses: + * - Summing those values over all sightings of an exposure provides the duration of that exposure. + * - Summing those values over all sightings in a given attenuation range and over all exposures recreates the durationAtBuckets of v1. + *

+ * Note that the previous scan may not have led to a sighting of that TEK. + */ + public int getSecondsSinceLastScan() { + return secondsSinceLastScan; + } + + /** + * Aggregation of the attenuations of all of this TEK's beacons received during the scan, in dB. This is most likely to be an average in the dB domain. + */ + public int getTypicalAttenuationDb() { + return typicalAttenuationDb; + } + + /** + * Builder for {@link ScanInstance}. + */ + public static class Builder { + private int typicalAttenuationDb; + private int minAttenuationDb; + private int secondsSinceLastScan; + + public ScanInstance build() { + return new ScanInstance(typicalAttenuationDb, minAttenuationDb, secondsSinceLastScan); + } + + public ScanInstance.Builder setMinAttenuationDb(int minAttenuationDb) { + this.minAttenuationDb = minAttenuationDb; + return this; + } + + public ScanInstance.Builder setSecondsSinceLastScan(int secondsSinceLastScan) { + this.secondsSinceLastScan = secondsSinceLastScan; + return this; + } + + public ScanInstance.Builder setTypicalAttenuationDb(int typicalAttenuationDb) { + this.typicalAttenuationDb = typicalAttenuationDb; + return this; + } + } + + public static final Creator CREATOR = new AutoCreator<>(ScanInstance.class); +} diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetCalibrationConfidenceParams.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetCalibrationConfidenceParams.java new file mode 100644 index 00000000..8002b4b3 --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetCalibrationConfidenceParams.java @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class GetCalibrationConfidenceParams extends AutoSafeParcelable { + @Field(1) + public IIntCallback callback; + + private GetCalibrationConfidenceParams() {} + + public GetCalibrationConfidenceParams(IIntCallback callback) { + this.callback = callback; + } + + public static final Creator CREATOR = new AutoCreator<>(GetCalibrationConfidenceParams.class); +} diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetDailySummariesParams.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetDailySummariesParams.java new file mode 100644 index 00000000..e57c13be --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetDailySummariesParams.java @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +import com.google.android.gms.nearby.exposurenotification.DailySummariesConfig; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class GetDailySummariesParams extends AutoSafeParcelable { + @Field(1) + public IDailySummaryListCallback callback; + @Field(2) + public DailySummariesConfig config; + + private GetDailySummariesParams() {} + + public GetDailySummariesParams(IDailySummaryListCallback callback, DailySummariesConfig config) { + this.callback = callback; + this.config = config; + } + + public static final Creator CREATOR = new AutoCreator<>(GetDailySummariesParams.class); +} diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetDiagnosisKeysDataMappingParams.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetDiagnosisKeysDataMappingParams.java new file mode 100644 index 00000000..9e9293e5 --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetDiagnosisKeysDataMappingParams.java @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class GetDiagnosisKeysDataMappingParams extends AutoSafeParcelable { + @Field(1) + public IDiagnosisKeysDataMappingCallback callback; + + private GetDiagnosisKeysDataMappingParams() {} + + public GetDiagnosisKeysDataMappingParams(IDiagnosisKeysDataMappingCallback callback) { + this.callback = callback; + } + + public static final Creator CREATOR = new AutoCreator<>(GetDiagnosisKeysDataMappingParams.class); +} diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetExposureWindowsParams.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetExposureWindowsParams.java new file mode 100644 index 00000000..e4cf4954 --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetExposureWindowsParams.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class GetExposureWindowsParams extends AutoSafeParcelable { + @Field(1) + public IExposureWindowListCallback callback; + @Field(2) + public String token; + + private GetExposureWindowsParams() {} + + public GetExposureWindowsParams(IExposureWindowListCallback callback, String token) { + this.callback = callback; + this.token = token; + } + + public static final Creator CREATOR = new AutoCreator<>(GetExposureWindowsParams.class); +} diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetVersionParams.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetVersionParams.java new file mode 100644 index 00000000..696d9377 --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/GetVersionParams.java @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class GetVersionParams extends AutoSafeParcelable { + @Field(1) + public ILongCallback callback; + + private GetVersionParams() { + } + + public GetVersionParams(ILongCallback callback) { + this.callback = callback; + } + + public static final Creator CREATOR = new AutoCreator<>(GetVersionParams.class); +} diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/ProvideDiagnosisKeysParams.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/ProvideDiagnosisKeysParams.java index d291d8c9..f429d712 100644 --- a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/ProvideDiagnosisKeysParams.java +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/ProvideDiagnosisKeysParams.java @@ -27,6 +27,9 @@ public class ProvideDiagnosisKeysParams extends AutoSafeParcelable { @Field(5) public String token; + private ProvideDiagnosisKeysParams() { + } + public ProvideDiagnosisKeysParams(IStatusCallback callback, List keys, List keyFiles, ExposureConfiguration configuration, String token) { this(callback, keyFiles, configuration, token); this.keys = keys; diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/SetDiagnosisKeysDataMappingParams.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/SetDiagnosisKeysDataMappingParams.java new file mode 100644 index 00000000..cfc868b7 --- /dev/null +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/internal/SetDiagnosisKeysDataMappingParams.java @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.nearby.exposurenotification.internal; + +import com.google.android.gms.common.api.internal.IStatusCallback; +import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class SetDiagnosisKeysDataMappingParams extends AutoSafeParcelable { + @Field(1) + public IStatusCallback callback; + @Field(2) + public DiagnosisKeysDataMapping mapping; + + private SetDiagnosisKeysDataMappingParams() {} + + public SetDiagnosisKeysDataMappingParams(IStatusCallback callback, DiagnosisKeysDataMapping mapping) { + this.callback = callback; + this.mapping = mapping; + } + + public static final Creator CREATOR = new AutoCreator<>(SetDiagnosisKeysDataMappingParams.class); +} 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 052da82c..87373f1c 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 @@ -48,7 +48,7 @@ class AdvertiserService : Service() { } @TargetApi(23) - private var setCallback: AdvertisingSetCallback? = null + private var setCallback: Any? = null private val trigger = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == "android.bluetooth.adapter.action.STATE_CHANGED") { @@ -83,6 +83,7 @@ class AdvertiserService : Service() { super.onDestroy() unregisterReceiver(trigger) stopOrRestartAdvertising() + handler.removeCallbacks(startLaterRunnable) database.unref() } @@ -114,7 +115,7 @@ class AdvertiserService : Service() { 0x00 // Reserved ) VERSION_1_1 -> byteArrayOf( - (version + currentDeviceInfo.confidence * 4).toByte(), // Version and flags + (version + currentDeviceInfo.confidence.toByte() * 4).toByte(), // Version and flags (currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power 0x00, // Reserved 0x00 // Reserved @@ -134,7 +135,7 @@ class AdvertiserService : Service() { .setTxPowerLevel(AdvertisingSetParameters.TX_POWER_LOW) .setConnectable(false) .build() - advertiser.startAdvertisingSet(params, data, null, null, null, setCallback) + advertiser.startAdvertisingSet(params, data, null, null, null, setCallback as AdvertisingSetCallback) } else { nextSend = nextSend.coerceAtMost(180000) val settings = Builder() @@ -189,21 +190,13 @@ class AdvertiserService : Service() { advertising = false if (Build.VERSION.SDK_INT >= 26) { wantStartAdvertising = true - advertiser?.stopAdvertisingSet(setCallback) + advertiser?.stopAdvertisingSet(setCallback as AdvertisingSetCallback) } else { advertiser?.stopAdvertising(callback) } handler.postDelayed(startLaterRunnable, 1000) } - companion object { - private const val ACTION_RESTART_ADVERTISING = "org.microg.gms.nearby.exposurenotification.RESTART_ADVERTISING" - - fun isNeeded(context: Context): Boolean { - return ExposurePreferences(context).enabled - } - } - @TargetApi(26) inner class SetCallback : AdvertisingSetCallback() { override fun onAdvertisingSetStarted(advertisingSet: AdvertisingSet?, txPower: Int, status: Int) { @@ -219,4 +212,13 @@ class AdvertiserService : Service() { } } } + + + companion object { + private const val ACTION_RESTART_ADVERTISING = "org.microg.gms.nearby.exposurenotification.RESTART_ADVERTISING" + + fun isNeeded(context: Context): Boolean { + 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 3303c5f1..fad33772 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 @@ -5,6 +5,8 @@ package org.microg.gms.nearby.exposurenotification +import android.app.AlarmManager +import android.app.PendingIntent import android.content.Context import android.content.Intent import androidx.lifecycle.LifecycleService @@ -25,14 +27,21 @@ class CleanupService : LifecycleService() { } ExposurePreferences(this@CleanupService).lastCleanup = System.currentTimeMillis() } - stopSelf() + stop() } } else { - stopSelf() + stop() } return START_NOT_STICKY } + fun stop() { + val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager + val pendingIntent = PendingIntent.getService(applicationContext, CleanupService::class.java.name.hashCode(), Intent(applicationContext, CleanupService::class.java), PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT) + alarmManager.set(AlarmManager.RTC, ExposurePreferences(this).lastCleanup + CLEANUP_INTERVAL, pendingIntent) + stopSelf() + } + companion object { fun isNeeded(context: Context): Boolean { return ExposurePreferences(context).let { 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 b700aece..ca695aac 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 @@ -6,6 +6,7 @@ package org.microg.gms.nearby.exposurenotification import android.os.ParcelUuid +import com.google.android.gms.nearby.exposurenotification.CalibrationConfidence import java.util.* const val TAG = "ExposureNotification" @@ -17,10 +18,10 @@ const val SCANNING_TIME = 20 // Google uses 4s + 13s (if Bluetooth is used by so const val SCANNING_TIME_MS = SCANNING_TIME * 1000L const val ROLLING_WINDOW_LENGTH = 10 * 60 -const val ROLLING_WINDOW_LENGTH_MS = ROLLING_WINDOW_LENGTH * 1000 +const val ROLLING_WINDOW_LENGTH_MS = ROLLING_WINDOW_LENGTH * 1000L const val ROLLING_PERIOD = 144 -const val ALLOWED_KEY_OFFSET_MS = 60 * 60 * 1000 -const val MINIMUM_EXPOSURE_DURATION_MS = 0 +const val ALLOWED_KEY_OFFSET_MS = 60 * 60 * 1000L +const val MINIMUM_EXPOSURE_DURATION_MS = 0L const val KEEP_DAYS = 14 const val ACTION_CONFIRM = "org.microg.gms.nearby.exposurenotification.CONFIRM" @@ -37,27 +38,7 @@ 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 CLEANUP_INTERVAL = 24 * 60 * 60 * 1000L 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 e16c4c20..5d9927e2 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 @@ -10,14 +10,15 @@ package org.microg.gms.nearby.exposurenotification import android.os.Build import android.util.Log +import com.google.android.gms.nearby.exposurenotification.CalibrationConfidence import kotlin.math.roundToInt -data class DeviceInfo(val oem: String, val model: String, val txPowerCorrection: Byte, val rssiCorrection: Byte, val confidence: Byte = CONFIDENCE_MEDIUM) +data class DeviceInfo(val oem: String, val model: String, val txPowerCorrection: Byte, val rssiCorrection: Byte, @CalibrationConfidence val confidence: Int = CalibrationConfidence.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) +fun averageCurrentDeviceInfo(oem: String, model: String, deviceInfos: List, @CalibrationConfidence confidence: Int = CalibrationConfidence.LOW): DeviceInfo = + DeviceInfo(oem, model, deviceInfos.map { it.txPowerCorrection }.average().roundToInt().toByte(), deviceInfos.map { it.txPowerCorrection }.average().roundToInt().toByte(), CalibrationConfidence.LOW) val currentDeviceInfo: DeviceInfo get() { @@ -36,7 +37,7 @@ val currentDeviceInfo: DeviceInfo } else -> { // Fallback to all device average - averageCurrentDeviceInfo(Build.MANUFACTURER, Build.MODEL, allDeviceInfos, CONFIDENCE_LOWEST) + averageCurrentDeviceInfo(Build.MANUFACTURER, Build.MODEL, allDeviceInfos, CalibrationConfidence.LOWEST) } } Log.i(TAG, "Selected $deviceInfo") 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 a99a2ec7..0790776a 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 @@ -17,12 +17,11 @@ import android.os.Parcelable import android.util.Log import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey +import okio.ByteString +import java.io.File 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.* import kotlin.math.roundToInt @TargetApi(21) @@ -43,28 +42,41 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + Log.d(TAG, "Upgrading database from $oldVersion to $newVersion") if (oldVersion < 1) { + Log.d(TAG, "Creating tables for version >= 1") db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_ADVERTISEMENTS(rpi BLOB NOT NULL, aem BLOB NOT NULL, timestamp INTEGER NOT NULL, rssi INTEGER NOT NULL, duration INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(rpi, timestamp));") db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_ADVERTISEMENTS}_rpi ON $TABLE_ADVERTISEMENTS(rpi);") db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_ADVERTISEMENTS}_timestamp ON $TABLE_ADVERTISEMENTS(timestamp);") 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_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);") } if (oldVersion < 3) { + Log.d(TAG, "Creating tables for version >= 3") db.execSQL("CREATE TABLE $TABLE_APP_PERMS(package TEXT NOT NULL, sig TEXT NOT NULL, perm TEXT NOT NULL, timestamp INTEGER NOT NULL);") } - if (oldVersion < 4) { - db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_DIAGNOSIS}_tcid ON $TABLE_DIAGNOSIS(tcid);") + if (oldVersion < 5) { + Log.d(TAG, "Dropping legacy tables") + db.execSQL("DROP TABLE IF EXISTS $TABLE_CONFIGURATIONS;") + db.execSQL("DROP TABLE IF EXISTS $TABLE_DIAGNOSIS;") + db.execSQL("DROP TABLE IF EXISTS $TABLE_TEK_CHECK;") + Log.d(TAG, "Creating tables for version >= 3") + db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TOKENS(tid INTEGER PRIMARY KEY, package TEXT NOT NULL, token TEXT NOT NULL, timestamp INTEGER NOT NULL, configuration BLOB);") + db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TOKENS}_package_token ON $TABLE_TOKENS(package, token);") + db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_SINGLE(tcsid INTEGER PRIMARY KEY, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, matched INTEGER);") + db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_SINGLE}_key ON $TABLE_TEK_CHECK_SINGLE(keyData, rollingStartNumber, rollingPeriod);") + db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_SINGLE_TOKEN(tcsid INTEGER REFERENCES $TABLE_TEK_CHECK_SINGLE(tcsid) ON DELETE CASCADE, tid INTEGER REFERENCES $TABLE_TOKENS(tid) ON DELETE CASCADE, transmissionRiskLevel INTEGER NOT NULL, UNIQUE(tcsid, tid));") + db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_SINGLE_TOKEN}_tid ON $TABLE_TEK_CHECK_SINGLE_TOKEN(tid);") + db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE(tcfid INTEGER PRIMARY KEY, hash TEXT NOT NULL, endTimestamp INTEGER NOT NULL, keys INTEGER NOT NULL);") + db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE}_hash ON $TABLE_TEK_CHECK_FILE(hash);") + db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE_TOKEN(tcfid INTEGER REFERENCES $TABLE_TEK_CHECK_FILE(tcfid) ON DELETE CASCADE, tid INTEGER REFERENCES $TABLE_TOKENS(tid) ON DELETE CASCADE, UNIQUE(tcfid, tid));") + db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_TOKEN}_tid ON $TABLE_TEK_CHECK_FILE_TOKEN(tid);") + db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE_MATCH(tcfid INTEGER REFERENCES $TABLE_TEK_CHECK_FILE(tcfid) ON DELETE CASCADE, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, transmissionRiskLevel INTEGER NOT NULL, UNIQUE(tcfid, keyData, rollingStartNumber, rollingPeriod));") + db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_MATCH}_tcfid ON $TABLE_TEK_CHECK_FILE_MATCH(tcfid);") + db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_MATCH}_key ON $TABLE_TEK_CHECK_FILE_MATCH(keyData, rollingStartNumber, rollingPeriod);") } + Log.d(TAG, "Finished database upgrade") } fun SQLiteDatabase.delete(table: String, whereClause: String, args: LongArray): Int = @@ -74,23 +86,21 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } fun dailyCleanup() = writableDatabase.run { - beginTransaction() - try { - val rollingStartTime = currentRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong()) - val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime)) - Log.d(TAG, "Deleted on daily cleanup: $advertisements adv") - val appLogEntries = delete(TABLE_APP_LOG, "timestamp < ?", longArrayOf(rollingStartTime)) - Log.d(TAG, "Deleted on daily cleanup: $appLogEntries applogs") - val temporaryExposureKeys = delete(TABLE_TEK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS)) - Log.d(TAG, "Deleted on daily cleanup: $temporaryExposureKeys teks") - val checkedTemporaryExposureKeys = delete(TABLE_TEK_CHECK, "rollingStartNumber < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS - ROLLING_PERIOD)) - Log.d(TAG, "Deleted on daily cleanup: $checkedTemporaryExposureKeys cteks") - val appPerms = delete(TABLE_APP_PERMS, "timestamp < ?", longArrayOf(System.currentTimeMillis() - CONFIRM_PERMISSION_VALIDITY)) - Log.d(TAG, "Deleted on daily cleanup: $appPerms perms") - setTransactionSuccessful() - } finally { - endTransaction() - } + val rollingStartTime = currentRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong()) + val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime)) + Log.d(TAG, "Deleted on daily cleanup: $advertisements adv") + val appLogEntries = delete(TABLE_APP_LOG, "timestamp < ?", longArrayOf(rollingStartTime)) + Log.d(TAG, "Deleted on daily cleanup: $appLogEntries applogs") + val temporaryExposureKeys = delete(TABLE_TEK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS)) + Log.d(TAG, "Deleted on daily cleanup: $temporaryExposureKeys teks") + val singleCheckedTemporaryExposureKeys = delete(TABLE_TEK_CHECK_SINGLE, "rollingStartNumber < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS - ROLLING_PERIOD)) + Log.d(TAG, "Deleted on daily cleanup: $singleCheckedTemporaryExposureKeys tcss") + val fileCheckedTemporaryExposureKeys = delete(TABLE_TEK_CHECK_FILE, "endTimestamp < ?", longArrayOf(rollingStartTime)) + Log.d(TAG, "Deleted on daily cleanup: $fileCheckedTemporaryExposureKeys tcfs") + val appPerms = delete(TABLE_APP_PERMS, "timestamp < ?", longArrayOf(System.currentTimeMillis() - CONFIRM_PERMISSION_VALIDITY)) + Log.d(TAG, "Deleted on daily cleanup: $appPerms perms") + execSQL("VACUUM;") + Log.d(TAG, "Done vacuuming") } fun grantPermission(packageName: String, signatureDigest: String, permission: String, timestamp: Long = System.currentTimeMillis()) = writableDatabase.run { @@ -132,7 +142,11 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit fun deleteAllCollectedAdvertisements() = writableDatabase.run { delete(TABLE_ADVERTISEMENTS, null, null) - update(TABLE_TEK_CHECK, ContentValues().apply { + delete(TABLE_TEK_CHECK_FILE_MATCH, null, null) + update(TABLE_TEK_CHECK_SINGLE, ContentValues().apply { + put("matched", 0) + }, null, null) + update(TABLE_TEK_CHECK_FILE, ContentValues().apply { put("matched", 0) }, null, null) } @@ -155,15 +169,15 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit }) } - private fun getTekCheckId(key: TemporaryExposureKey, mayInsert: Boolean = false, database: SQLiteDatabase = if (mayInsert) writableDatabase else readableDatabase): Long? = database.run { + private fun getTekCheckSingleId(key: TemporaryExposureKey, mayInsert: Boolean = false, database: SQLiteDatabase = if (mayInsert) writableDatabase else readableDatabase): Long? = database.run { if (mayInsert) { - insertWithOnConflict(TABLE_TEK_CHECK, "NULL", ContentValues().apply { + insertWithOnConflict(TABLE_TEK_CHECK_SINGLE, "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 { + compileStatement("SELECT tcsid FROM $TABLE_TEK_CHECK_SINGLE WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?").use { it.bindBlob(1, key.keyData) it.bindLong(2, key.rollingStartIntervalNumber.toLong()) it.bindLong(3, key.rollingPeriod.toLong()) @@ -171,57 +185,77 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } } - fun storeDiagnosisKey(packageName: String, token: String, key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run { - val tcid = getTekCheckId(key, true, database) - insert(TABLE_DIAGNOSIS, "NULL", ContentValues().apply { - put("package", packageName) - put("token", token) - put("tcid", tcid) + fun getTokenId(packageName: String, token: String, database: SQLiteDatabase = readableDatabase) = database.run { + query(TABLE_TOKENS, arrayOf("tid"), "package = ? AND token = ?", arrayOf(packageName, token), null, null, null, null).use { cursor -> + if (cursor.moveToNext()) { + cursor.getLong(0) + } else { + null + } + } + } + + private fun storeSingleDiagnosisKey(tid: Long, key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run { + val tcsid = getTekCheckSingleId(key, true, database) + insert(TABLE_TEK_CHECK_SINGLE_TOKEN, "NULL", ContentValues().apply { + put("tid", tid) + put("tcsid", tcsid) put("transmissionRiskLevel", key.transmissionRiskLevel) }) } - fun batchStoreDiagnosisKey(packageName: String, token: String, keys: List, database: SQLiteDatabase = writableDatabase) = database.run { + fun batchStoreSingleDiagnosisKey(tid: Long, keys: List, database: SQLiteDatabase = writableDatabase) = database.run { beginTransaction() try { - keys.forEach { storeDiagnosisKey(packageName, token, it, database) } + keys.forEach { storeSingleDiagnosisKey(tid, it, database) } setTransactionSuccessful() } finally { endTransaction() } } - fun updateDiagnosisKey(packageName: String, token: String, key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run { - val tcid = getTekCheckId(key, false, database) ?: 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 getDiagnosisFileId(hash: ByteArray, database: SQLiteDatabase = readableDatabase) = database.run { + val hexHash = ByteString.of(*hash).hex() + query(TABLE_TEK_CHECK_FILE, arrayOf("tcfid"), "hash = ?", arrayOf(hexHash), null, null, null, null).use { cursor -> + if (cursor.moveToNext()) { + cursor.getLong(0) + } else { + null + } } } - fun batchUpdateDiagnosisKey(packageName: String, token: String, keys: List, database: SQLiteDatabase = writableDatabase) = database.run { - beginTransaction() - try { - keys.forEach { updateDiagnosisKey(packageName, token, it, database) } - setTransactionSuccessful() - } finally { - endTransaction() + fun storeDiagnosisFileUsed(tid: Long, tcfid: Long, database: SQLiteDatabase = writableDatabase) = database.run { + insert(TABLE_TEK_CHECK_FILE_TOKEN, "NULL", ContentValues().apply { + put("tid", tid) + put("tcfid", tcfid) + }) + } + + fun storeDiagnosisFileUsed(tid: Long, hash: ByteArray, database: SQLiteDatabase = writableDatabase) = database.run { + val hexHash = ByteString.of(*hash).hex() + query(TABLE_TEK_CHECK_FILE, arrayOf("tcfid", "keys"), "hash = ?", arrayOf(hexHash), null, null, null, null).use { cursor -> + if (cursor.moveToNext()) { + insert(TABLE_TEK_CHECK_FILE_TOKEN, "NULL", ContentValues().apply { + put("tid", tid) + put("tcfid", cursor.getLong(0)) + }) + cursor.getLong(1) + } else { + null + } } } - private fun listDiagnosisKeysPendingSearch(packageName: String, token: String, database: SQLiteDatabase = readableDatabase) = database.run { + private fun listSingleDiagnosisKeysPendingSearch(tid: Long, database: SQLiteDatabase = readableDatabase) = database.run { rawQuery(""" - SELECT $TABLE_TEK_CHECK.keyData, $TABLE_TEK_CHECK.rollingStartNumber, $TABLE_TEK_CHECK.rollingPeriod - FROM $TABLE_DIAGNOSIS - LEFT JOIN $TABLE_TEK_CHECK ON $TABLE_DIAGNOSIS.tcid = $TABLE_TEK_CHECK.tcid + SELECT $TABLE_TEK_CHECK_SINGLE.keyData, $TABLE_TEK_CHECK_SINGLE.rollingStartNumber, $TABLE_TEK_CHECK_SINGLE.rollingPeriod + FROM $TABLE_TEK_CHECK_SINGLE_TOKEN + LEFT JOIN $TABLE_TEK_CHECK_SINGLE ON $TABLE_TEK_CHECK_SINGLE.tcsid = $TABLE_TEK_CHECK_SINGLE_TOKEN.tcsid WHERE - $TABLE_DIAGNOSIS.package = ? AND - $TABLE_DIAGNOSIS.token = ? AND - $TABLE_TEK_CHECK.matched IS NULL - """, arrayOf(packageName, token)).use { cursor -> + $TABLE_TEK_CHECK_SINGLE_TOKEN.tid = ? AND + $TABLE_TEK_CHECK_SINGLE.matched IS NULL + """, arrayOf(tid.toString())).use { cursor -> val list = arrayListOf() while (cursor.moveToNext()) { list.add(TemporaryExposureKey.TemporaryExposureKeyBuilder() @@ -234,8 +268,8 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } } - private fun applyDiagnosisKeySearchResult(key: TemporaryExposureKey, matched: Boolean, database: SQLiteDatabase = writableDatabase) = database.run { - compileStatement("UPDATE $TABLE_TEK_CHECK SET matched = ? WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?;").use { + private fun applySingleDiagnosisKeySearchResult(key: TemporaryExposureKey, matched: Boolean, database: SQLiteDatabase = writableDatabase) = database.run { + compileStatement("UPDATE $TABLE_TEK_CHECK_SINGLE 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()) @@ -244,16 +278,25 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } } - private fun listMatchedDiagnosisKeys(packageName: String, token: String, database: SQLiteDatabase = readableDatabase) = database.run { + private fun applyDiagnosisFileKeySearchResult(tcfid: Long, key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run { + insert(TABLE_TEK_CHECK_FILE_MATCH, "NULL", ContentValues().apply { + put("tcfid", tcfid) + put("keyData", key.keyData) + put("rollingStartNumber", key.rollingStartIntervalNumber) + put("rollingPeriod", key.rollingPeriod) + put("transmissionRiskLevel", key.transmissionRiskLevel) + }) + } + + private fun listMatchedSingleDiagnosisKeys(tid: Long, database: SQLiteDatabase = readableDatabase) = database.run { rawQuery(""" - 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.tcid = $TABLE_TEK_CHECK.tcid + SELECT $TABLE_TEK_CHECK_SINGLE.keyData, $TABLE_TEK_CHECK_SINGLE.rollingStartNumber, $TABLE_TEK_CHECK_SINGLE.rollingPeriod, $TABLE_TEK_CHECK_SINGLE_TOKEN.transmissionRiskLevel + FROM $TABLE_TEK_CHECK_SINGLE_TOKEN + JOIN $TABLE_TEK_CHECK_SINGLE ON $TABLE_TEK_CHECK_SINGLE.tcsid = $TABLE_TEK_CHECK_SINGLE_TOKEN.tcsid WHERE - $TABLE_DIAGNOSIS.package = ? AND - $TABLE_DIAGNOSIS.token = ? AND - $TABLE_TEK_CHECK.matched = 1 - """, arrayOf(packageName, token)).use { cursor -> + $TABLE_TEK_CHECK_SINGLE_TOKEN.tid = ? AND + $TABLE_TEK_CHECK_SINGLE.matched = 1 + """, arrayOf(tid.toString())).use { cursor -> val list = arrayListOf() while (cursor.moveToNext()) { list.add(TemporaryExposureKey.TemporaryExposureKeyBuilder() @@ -267,36 +310,118 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } } - fun finishMatching(packageName: String, token: String, database: SQLiteDatabase = writableDatabase) { - val start = System.currentTimeMillis() + private fun listMatchedFileDiagnosisKeys(tid: Long, database: SQLiteDatabase = readableDatabase) = database.run { + rawQuery(""" + SELECT $TABLE_TEK_CHECK_FILE_MATCH.keyData, $TABLE_TEK_CHECK_FILE_MATCH.rollingStartNumber, $TABLE_TEK_CHECK_FILE_MATCH.rollingPeriod, $TABLE_TEK_CHECK_FILE_MATCH.transmissionRiskLevel + FROM $TABLE_TEK_CHECK_FILE_TOKEN + JOIN $TABLE_TEK_CHECK_FILE_MATCH ON $TABLE_TEK_CHECK_FILE_MATCH.tcfid = $TABLE_TEK_CHECK_FILE_TOKEN.tcfid + WHERE + $TABLE_TEK_CHECK_FILE_TOKEN.tid = ? + """, arrayOf(tid.toString())).use { cursor -> + val list = arrayListOf() + while (cursor.moveToNext()) { + list.add(TemporaryExposureKey.TemporaryExposureKeyBuilder() + .setKeyData(cursor.getBlob(0)) + .setRollingStartIntervalNumber(cursor.getLong(1).toInt()) + .setRollingPeriod(cursor.getLong(2).toInt()) + .setTransmissionRiskLevel(cursor.getLong(3).toInt()) + .build()) + } + list + } + } + + fun finishSingleMatching(tid: Long, database: SQLiteDatabase = writableDatabase): Int { val workQueue = LinkedBlockingQueue() val poolSize = Runtime.getRuntime().availableProcessors() val executor = ThreadPoolExecutor(poolSize, poolSize, 1, TimeUnit.SECONDS, workQueue) val futures = arrayListOf>() - val keys = listDiagnosisKeysPendingSearch(packageName, token, database) + val keys = listSingleDiagnosisKeysPendingSearch(tid, database) val oldestRpi = oldestRpi for (key in keys) { - if (oldestRpi == null || (key.rollingStartIntervalNumber + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS < oldestRpi) { + if ((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, database) + applySingleDiagnosisKeySearchResult(key, false, database) } else { futures.add(executor.submit { - applyDiagnosisKeySearchResult(key, findMeasuredExposures(key).isNotEmpty(), database) + applySingleDiagnosisKeySearchResult(key, findMeasuredExposures(key).isNotEmpty(), database) }) } } for (future in futures) { future.get() } - val time = (System.currentTimeMillis() - start).toDouble() / 1000.0 executor.shutdown() - Log.d(TAG, "Processed ${keys.size} new keys in ${time}s -> ${(keys.size.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s") + return keys.size } - fun findAllMeasuredExposures(packageName: String, token: String, database: SQLiteDatabase = readableDatabase): List { - return listMatchedDiagnosisKeys(packageName, token, database).flatMap { findMeasuredExposures(it, database) } + fun finishFileMatching(tid: Long, hash: ByteArray, endTimestamp: Long, keys: List, updates: List, database: SQLiteDatabase = writableDatabase) = database.run { + beginTransaction() + try { + insert(TABLE_TEK_CHECK_FILE, "NULL", ContentValues().apply { + put("hash", ByteString.of(*hash).hex()) + put("endTimestamp", endTimestamp) + put("keys", keys.size + updates.size) + }) + val tcfid = getDiagnosisFileId(hash, this) ?: return + val workQueue = LinkedBlockingQueue() + val poolSize = Runtime.getRuntime().availableProcessors() + val executor = ThreadPoolExecutor(poolSize, poolSize, 1, TimeUnit.SECONDS, workQueue) + val futures = arrayListOf>() + val oldestRpi = oldestRpi + var ignored = 0 + var processed = 0 + var found = 0 + for (key in keys) { + if ((key.rollingStartIntervalNumber + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS < oldestRpi) { + // Early ignore because key is older than since we started scanning. + ignored++; + } else { + futures.add(executor.submit { + if (findMeasuredExposures(key).isNotEmpty()) { + applyDiagnosisFileKeySearchResult(tcfid, key, this) + found++; + } + processed++ + }) + } + } + for (future in futures) { + future.get() + } + Log.d(TAG, "Processed $processed keys, found $found matches, ignored $ignored keys that are older than our scanning efforts ($oldestRpi)") + executor.shutdown() + for (update in updates) { + val matched = compileStatement("SELECT COUNT(tcsid) FROM $TABLE_TEK_CHECK_FILE_MATCH WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?").use { + it.bindBlob(1, update.keyData) + it.bindLong(2, update.rollingStartIntervalNumber.toLong()) + it.bindLong(3, update.rollingPeriod.toLong()) + it.simpleQueryForLong() + } + if (matched > 0) { + applyDiagnosisFileKeySearchResult(tcfid, update, this) + } + } + insert(TABLE_TEK_CHECK_FILE_TOKEN, "NULL", ContentValues().apply { + put("tid", tid) + put("tcfid", tcfid) + }) + setTransactionSuccessful() + } finally { + endTransaction() + } } + fun findAllSingleMeasuredExposures(tid: Long, database: SQLiteDatabase = readableDatabase): List { + return listMatchedSingleDiagnosisKeys(tid, database).flatMap { findMeasuredExposures(it, database) } + } + + fun findAllFileMeasuredExposures(tid: Long, database: SQLiteDatabase = readableDatabase): List { + return listMatchedFileDiagnosisKeys(tid, database).flatMap { findMeasuredExposures(it, database) } + } + + fun findAllMeasuredExposures(tid: Long, database: SQLiteDatabase = readableDatabase) = findAllSingleMeasuredExposures(tid, database) + findAllFileMeasuredExposures(tid, database) + private fun findMeasuredExposures(key: TemporaryExposureKey, database: SQLiteDatabase = readableDatabase): List { val allRpis = key.generateAllRpiIds() val rpis = (0 until key.rollingPeriod).map { i -> @@ -385,21 +510,23 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit return res } - fun storeConfiguration(packageName: String, token: String, configuration: ExposureConfiguration) = writableDatabase.run { - val update = update(TABLE_CONFIGURATIONS, ContentValues().apply { put("configuration", configuration.marshall()) }, "package = ? AND token = ?", arrayOf(packageName, token)) + fun storeConfiguration(packageName: String, token: String, configuration: ExposureConfiguration, database: SQLiteDatabase = writableDatabase) = database.run { + val update = update(TABLE_TOKENS, ContentValues().apply { put("configuration", configuration.marshall()) }, "package = ? AND token = ?", arrayOf(packageName, token)) if (update <= 0) { - insert(TABLE_CONFIGURATIONS, "NULL", ContentValues().apply { + insert(TABLE_TOKENS, "NULL", ContentValues().apply { put("package", packageName) put("token", token) + put("timestamp", System.currentTimeMillis()) put("configuration", configuration.marshall()) }) } + getTokenId(packageName, token, database) } - fun loadConfiguration(packageName: String, token: String): ExposureConfiguration? = readableDatabase.run { - query(TABLE_CONFIGURATIONS, arrayOf("configuration"), "package = ? AND token = ?", arrayOf(packageName, token), null, null, null, null).use { cursor -> + fun loadConfiguration(packageName: String, token: String, database: SQLiteDatabase = readableDatabase): Pair? = database.run { + query(TABLE_TOKENS, arrayOf("tid", "configuration"), "package = ? AND token = ?", arrayOf(packageName, token), null, null, null, null).use { cursor -> if (cursor.moveToNext()) { - ExposureConfiguration.CREATOR.unmarshall(cursor.getBlob(0)) + cursor.getLong(0) to ExposureConfiguration.CREATOR.unmarshall(cursor.getBlob(1)) } else { null } @@ -454,13 +581,13 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } } - val oldestRpi: Long? + val oldestRpi: Long get() = readableDatabase.run { query(TABLE_ADVERTISEMENTS, arrayOf("MIN(timestamp)"), null, null, null, null, null).use { cursor -> if (cursor.moveToNext()) { cursor.getLong(0) } else { - null + System.currentTimeMillis() } } } @@ -532,7 +659,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit override fun getWritableDatabase(): SQLiteDatabase { if (this != instance) { - throw IllegalStateException("Tried to open writable database from secondary instance") + throw IllegalStateException("Tried to open writable database from secondary instance. We are ${hashCode()} but primary is ${instance?.hashCode()}") } return super.getWritableDatabase() } @@ -560,19 +687,32 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit companion object { private const val DB_NAME = "exposure.db" - private const val DB_VERSION = 4 + private const val DB_VERSION = 5 private const val TABLE_ADVERTISEMENTS = "advertisements" private const val TABLE_APP_LOG = "app_log" private const val TABLE_TEK = "tek" - private const val TABLE_TEK_CHECK = "tek_check" - private const val TABLE_DIAGNOSIS = "diagnosis" - private const val TABLE_CONFIGURATIONS = "configurations" private const val TABLE_APP_PERMS = "app_perms" + private const val TABLE_TOKENS = "tokens" + private const val TABLE_TEK_CHECK_SINGLE = "tek_check_single" + private const val TABLE_TEK_CHECK_SINGLE_TOKEN = "tek_check_single_token" + private const val TABLE_TEK_CHECK_FILE = "tek_check_file" + private const val TABLE_TEK_CHECK_FILE_TOKEN = "tek_check_file_token" + private const val TABLE_TEK_CHECK_FILE_MATCH = "tek_check_file_match" + + @Deprecated(message = "No longer supported") + private const val TABLE_TEK_CHECK = "tek_check" + + @Deprecated(message = "No longer supported") + private const val TABLE_DIAGNOSIS = "diagnosis" + + @Deprecated(message = "No longer supported") + private const val TABLE_CONFIGURATIONS = "configurations" private var instance: ExposureDatabase? = null fun ref(context: Context): ExposureDatabase = synchronized(this) { if (instance == null) { instance = ExposureDatabase(context.applicationContext) + Log.d(TAG, "Created instance ${instance?.hashCode()} of database for ${context.javaClass.name}") } instance!!.ref() } 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 f62e2152..6e443fa1 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 @@ -29,8 +29,10 @@ class ExposureNotificationService : BaseService(TAG, GmsService.NEARBY_EXPOSURE) return permission } - checkPermission("android.permission.BLUETOOTH") ?: return - checkPermission("android.permission.INTERNET") ?: return + if (request.packageName != packageName) { + checkPermission("android.permission.BLUETOOTH") ?: return + checkPermission("android.permission.INTERNET") ?: return + } if (Build.VERSION.SDK_INT < 21) { callback.onPostInitComplete(FAILED_NOT_SUPPORTED, null, null) @@ -39,7 +41,10 @@ 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", 3)) + features = arrayOf( + Feature("nearby_exposure_notification", 3), + Feature("nearby_exposure_notification_get_version", 1) + ) }) } } 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 331ec5b3..a054a336 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 @@ -10,25 +10,29 @@ import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.Intent.* import android.os.* import android.util.Log -import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.common.api.Status -import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes +import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration +import com.google.android.gms.nearby.exposurenotification.ExposureInformation import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes.* import com.google.android.gms.nearby.exposurenotification.ExposureSummary 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.common.Constants import org.microg.gms.common.PackageUtils 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.io.File +import java.io.InputStream +import java.security.MessageDigest import java.util.* -import java.util.zip.ZipInputStream +import java.util.zip.ZipFile import kotlin.math.roundToInt +import kotlin.random.Random class ExposureNotificationServiceImpl(private val context: Context, private val packageName: String) : INearbyExposureNotificationService.Stub() { private fun pendingConfirm(permission: String): PendingIntent { @@ -65,6 +69,14 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } } + override fun getVersion(params: GetVersionParams) { + params.callback.onResult(Status.SUCCESS, Constants.MAX_REFERENCE_VERSION.toLong()) + } + + override fun getCalibrationConfidence(params: GetCalibrationConfidenceParams) { + params.callback.onResult(Status.SUCCESS, currentDeviceInfo.confidence) + } + override fun start(params: StartParams) { if (ExposurePreferences(context).enabled) return val status = confirmPermission(CONFIRM_ACTION_START) @@ -90,7 +102,6 @@ class ExposureNotificationServiceImpl(private val context: Context, private val Log.w(TAG, "Callback failed", e) } } - override fun isEnabled(params: IsEnabledParams) { try { params.callback.onResult(Status.SUCCESS, ExposurePreferences(context).enabled) @@ -129,67 +140,73 @@ class ExposureNotificationServiceImpl(private val context: Context, private val .setTransmissionRiskLevel(transmission_risk_level ?: 0) .build() - private fun storeDiagnosisKeyExport(token: String, export: TemporaryExposureKeyExport): Int = ExposureDatabase.with(context) { database -> - Log.d(TAG, "Importing keys from file ${export.start_timestamp?.let { Date(it * 1000) }} to ${export.end_timestamp?.let { Date(it * 1000) }}") - database.batchStoreDiagnosisKey(packageName, token, export.keys.map { it.toKey() }) - database.batchUpdateDiagnosisKey(packageName, token, export.revised_keys.map { it.toKey() }) - export.keys.size + export.revised_keys.size + private fun InputStream.copyToFile(outputFile: File) { + outputFile.outputStream().use { output -> + copyTo(output) + output.flush() + } + } + + private fun MessageDigest.digest(file: File): ByteArray = file.inputStream().use { input -> + val buf = ByteArray(4096) + var bytes = input.read(buf) + while (bytes != -1) { + update(buf, 0, bytes) + bytes = input.read(buf) + } + digest() } override fun provideDiagnosisKeys(params: ProvideDiagnosisKeysParams) { + Log.w(TAG, "provideDiagnosisKeys() with $packageName/${params.token}") + val tid = ExposureDatabase.with(context) { database -> + if (params.configuration != null) { + database.storeConfiguration(packageName, params.token, params.configuration) + } else { + database.getTokenId(packageName, params.token) + } + } + if (tid == null) { + Log.w(TAG, "Unknown token without configuration: $packageName/${params.token}") + try { + params.callback.onResult(Status.INTERNAL_ERROR) + } catch (e: Exception) { + Log.w(TAG, "Callback failed", e) + } + return + } Thread(Runnable { ExposureDatabase.with(context) { database -> - if (params.configuration != null) { - database.storeConfiguration(packageName, params.token, params.configuration) - } - val start = System.currentTimeMillis() // keys - params.keys?.let { database.batchStoreDiagnosisKey(packageName, params.token, it) } + params.keys?.let { database.batchStoreSingleDiagnosisKey(tid, it) } + + var keys = params.keys?.size ?: 0 // Key files - var keys = params.keys?.size ?: 0 + val todoKeyFiles = arrayListOf>() for (file in params.keyFiles.orEmpty()) { try { - ZipInputStream(ParcelFileDescriptor.AutoCloseInputStream(file)).use { stream -> - do { - val entry = stream.nextEntry ?: break - if (entry.name == "export.bin") { - val prefix = ByteArray(16) - var totalBytesRead = 0 - var bytesRead = 0 - while (bytesRead != -1 && totalBytesRead < prefix.size) { - bytesRead = stream.read(prefix, totalBytesRead, prefix.size - totalBytesRead) - if (bytesRead > 0) { - totalBytesRead += bytesRead - } - } - if (totalBytesRead == prefix.size && String(prefix).trim() == "EK Export v1") { - val fileKeys = storeDiagnosisKeyExport(params.token, TemporaryExposureKeyExport.ADAPTER.decode(stream)) - keys += fileKeys - } else { - Log.d(TAG, "export.bin had invalid prefix") - } - } - stream.closeEntry() - } while (true); + val cacheFile = File(context.cacheDir, "en-keyfile-${System.currentTimeMillis()}-${Random.nextInt()}.zip") + ParcelFileDescriptor.AutoCloseInputStream(file).use { it.copyToFile(cacheFile) } + val hash = MessageDigest.getInstance("SHA-256").digest(cacheFile) + val storedKeys = database.storeDiagnosisFileUsed(tid, hash) + if (storedKeys != null) { + keys += storedKeys.toInt() + cacheFile.delete() + } else { + todoKeyFiles.add(cacheFile to hash) } } catch (e: Exception) { Log.w(TAG, "Failed parsing file", e) } } - val time = (System.currentTimeMillis() - start).toDouble() / 1000.0 - Log.d(TAG, "$packageName/${params.token} provided $keys keys in ${time}s -> ${(keys.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s") - database.noteAppAction(packageName, "provideDiagnosisKeys", JSONObject().apply { - put("request_token", params.token) - put("request_keys_size", params.keys?.size) - put("request_keyFiles_size", params.keyFiles?.size) - put("request_keys_count", keys) - }.toString()) - - database.finishMatching(packageName, params.token) + if (todoKeyFiles.size > 0) { + val time = (System.currentTimeMillis() - start).toDouble() / 1000.0 + Log.d(TAG, "$packageName/${params.token} processed $keys keys (${todoKeyFiles.size} files pending) in ${time}s -> ${(keys.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s") + } Handler(Looper.getMainLooper()).post { try { @@ -199,7 +216,47 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } } - val match = database.findAllMeasuredExposures(packageName, params.token).isNotEmpty() + var newKeys = if (params.keys != null) database.finishSingleMatching(tid) else 0 + for ((cacheFile, hash) in todoKeyFiles) { + ZipFile(cacheFile).use { zip -> + for (entry in zip.entries()) { + if (entry.name == "export.bin") { + val stream = zip.getInputStream(entry) + val prefix = ByteArray(16) + var totalBytesRead = 0 + var bytesRead = 0 + while (bytesRead != -1 && totalBytesRead < prefix.size) { + bytesRead = stream.read(prefix, totalBytesRead, prefix.size - totalBytesRead) + if (bytesRead > 0) { + totalBytesRead += bytesRead + } + } + if (totalBytesRead == prefix.size && String(prefix).trim() == "EK Export v1") { + val export = TemporaryExposureKeyExport.ADAPTER.decode(stream) + database.finishFileMatching(tid, hash, export.end_timestamp?.let { it * 1000 } + ?: System.currentTimeMillis(), export.keys.map { it.toKey() }, export.revised_keys.map { it.toKey() }) + keys += export.keys.size + export.revised_keys.size + newKeys += export.keys.size + } else { + Log.d(TAG, "export.bin had invalid prefix") + } + } + } + } + cacheFile.delete() + } + + val time = (System.currentTimeMillis() - start).toDouble() / 1000.0 + Log.d(TAG, "$packageName/${params.token} processed $keys keys ($newKeys new) in ${time}s -> ${(keys.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s") + + database.noteAppAction(packageName, "provideDiagnosisKeys", JSONObject().apply { + put("request_token", params.token) + put("request_keys_size", params.keys?.size) + put("request_keyFiles_size", params.keyFiles?.size) + put("request_keys_count", keys) + }.toString()) + + val match = database.findAllMeasuredExposures(tid).isNotEmpty() try { val intent = Intent(if (match) ACTION_EXPOSURE_STATE_UPDATED else ACTION_EXPOSURE_NOT_FOUND) @@ -215,16 +272,12 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun getExposureSummary(params: GetExposureSummaryParams): Unit = ExposureDatabase.with(context) { database -> - val configuration = database.loadConfiguration(packageName, params.token) - if (configuration == null) { - try { - params.callback.onResult(Status.INTERNAL_ERROR, null) - } catch (e: Exception) { - Log.w(TAG, "Callback failed", e) - } - return@with + val pair = database.loadConfiguration(packageName, params.token) + val (configuration, exposures) = if (pair != null) { + pair.second to database.findAllMeasuredExposures(pair.first).merge() + } else { + ExposureConfiguration.ExposureConfigurationBuilder().build() to emptyList() } - 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) @@ -255,18 +308,13 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun getExposureInformation(params: GetExposureInformationParams): Unit = ExposureDatabase.with(context) { database -> - // TODO: Notify user? - val configuration = database.loadConfiguration(packageName, params.token) - if (configuration == null) { - try { - params.callback.onResult(Status.INTERNAL_ERROR, null) - } catch (e: Exception) { - Log.w(TAG, "Callback failed", e) + val pair = database.loadConfiguration(packageName, params.token) + val response = if (pair != null) { + database.findAllMeasuredExposures(pair.first).merge().map { + it.toExposureInformation(pair.second) } - return@with - } - val response = database.findAllMeasuredExposures(packageName, params.token).merge().map { - it.toExposureInformation(configuration) + } else { + emptyList() } database.noteAppAction(packageName, "getExposureInformation", JSONObject().apply { @@ -280,6 +328,26 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } } + override fun getExposureWindows(params: GetExposureWindowsParams) { + Log.w(TAG, "Not yet implemented: getExposureWindows") + params.callback.onResult(Status.INTERNAL_ERROR, emptyList()) + } + + override fun getDailySummaries(params: GetDailySummariesParams) { + Log.w(TAG, "Not yet implemented: getDailySummaries") + params.callback.onResult(Status.INTERNAL_ERROR, emptyList()) + } + + override fun setDiagnosisKeysDataMapping(params: SetDiagnosisKeysDataMappingParams) { + Log.w(TAG, "Not yet implemented: setDiagnosisKeysDataMapping") + params.callback.onResult(Status.INTERNAL_ERROR) + } + + override fun getDiagnosisKeysDataMapping(params: GetDiagnosisKeysDataMappingParams) { + Log.w(TAG, "Not yet implemented: getDiagnosisKeysDataMapping") + params.callback.onResult(Status.INTERNAL_ERROR, null) + } + 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") diff --git a/proguard.flags b/proguard.flags index 1b6700d5..6cc3f1f2 100644 --- a/proguard.flags +++ b/proguard.flags @@ -28,6 +28,8 @@ # Keep AutoSafeParcelables -keep public class * extends org.microg.safeparcel.AutoSafeParcelable { @org.microg.safeparcel.SafeParceled *; + @org.microg.safeparcel.SafeParcelable.Field *; + (...); } # Keep form data From 7601b37ee4f4852a4bb093f0f8057874ff0a0e20 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 27 Sep 2020 15:27:21 +0200 Subject: [PATCH 11/28] EN: Display 0 RPIs as none in graph --- .../org/microg/gms/ui/ExposureNotificationsRpisFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 eba85a4e..949ec1ab 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 @@ -72,7 +72,7 @@ class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() { } histogramCategory.title = getString(R.string.prefcat_exposure_rpis_histogram_title, totalRpiCount) histogram.labelsFormatter = { it.roundToInt().toString() } - histogram.scale = Scale(0f, rpiHistogram.values.max() ?: 0f) + histogram.scale = Scale(0f, rpiHistogram.values.max()?.coerceAtLeast(0.1f) ?: 0.1f) histogram.data = rpiHistogram } } From 4486ff52efe3f94794f203c7837f0cbe37b5a756 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 7 Oct 2020 23:16:53 +0200 Subject: [PATCH 12/28] EN: Improve some database bits --- .../exposurenotification/CleanupService.kt | 9 ++- .../exposurenotification/ExposureDatabase.kt | 59 ++++++++++++------- .../exposurenotification/MeasuredExposure.kt | 5 +- 3 files changed, 49 insertions(+), 24 deletions(-) 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 fad33772..bcc2bf58 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 @@ -12,6 +12,7 @@ import android.content.Intent import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.microg.gms.common.ForegroundServiceContext @@ -22,8 +23,12 @@ class CleanupService : LifecycleService() { if (isNeeded(this)) { lifecycleScope.launchWhenStarted { launch(Dispatchers.IO) { - ExposureDatabase.with(this@CleanupService) { - it.dailyCleanup() + var workPending = true + while (workPending) { + ExposureDatabase.with(this@CleanupService) { + workPending = !it.dailyCleanup() + } + if (workPending) delay(5000L) } ExposurePreferences(this@CleanupService).lastCleanup = System.currentTimeMillis() } 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 0790776a..a21fecf8 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 @@ -79,28 +79,35 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit Log.d(TAG, "Finished database upgrade") } - fun SQLiteDatabase.delete(table: String, whereClause: String, args: LongArray): Int = - compileStatement("DELETE FROM $table WHERE $whereClause").use { + fun SQLiteDatabase.delete(table: String, whereClause: String, args: LongArray, limit: Int): Int = + compileStatement("DELETE FROM $table WHERE $whereClause LIMIT $limit").use { args.forEachIndexed { idx, l -> it.bindLong(idx + 1, l) } it.executeUpdateDelete() } - fun dailyCleanup() = writableDatabase.run { + fun dailyCleanup(): Boolean = writableDatabase.run { val rollingStartTime = currentRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong()) - val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime)) + val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime), DELETE_LIMIT) Log.d(TAG, "Deleted on daily cleanup: $advertisements adv") - val appLogEntries = delete(TABLE_APP_LOG, "timestamp < ?", longArrayOf(rollingStartTime)) + if (advertisements == DELETE_LIMIT) return@run false + val appLogEntries = delete(TABLE_APP_LOG, "timestamp < ?", longArrayOf(rollingStartTime), DELETE_LIMIT) Log.d(TAG, "Deleted on daily cleanup: $appLogEntries applogs") - val temporaryExposureKeys = delete(TABLE_TEK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS)) + if (appLogEntries == DELETE_LIMIT) return@run false + val temporaryExposureKeys = delete(TABLE_TEK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS), DELETE_LIMIT) Log.d(TAG, "Deleted on daily cleanup: $temporaryExposureKeys teks") - val singleCheckedTemporaryExposureKeys = delete(TABLE_TEK_CHECK_SINGLE, "rollingStartNumber < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS - ROLLING_PERIOD)) + if (temporaryExposureKeys == DELETE_LIMIT) return@run false + val singleCheckedTemporaryExposureKeys = delete(TABLE_TEK_CHECK_SINGLE, "rollingStartNumber < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS - ROLLING_PERIOD), DELETE_LIMIT) Log.d(TAG, "Deleted on daily cleanup: $singleCheckedTemporaryExposureKeys tcss") - val fileCheckedTemporaryExposureKeys = delete(TABLE_TEK_CHECK_FILE, "endTimestamp < ?", longArrayOf(rollingStartTime)) + if (singleCheckedTemporaryExposureKeys == DELETE_LIMIT) return@run false + val fileCheckedTemporaryExposureKeys = delete(TABLE_TEK_CHECK_FILE, "endTimestamp < ?", longArrayOf(rollingStartTime), DELETE_LIMIT) Log.d(TAG, "Deleted on daily cleanup: $fileCheckedTemporaryExposureKeys tcfs") - val appPerms = delete(TABLE_APP_PERMS, "timestamp < ?", longArrayOf(System.currentTimeMillis() - CONFIRM_PERMISSION_VALIDITY)) + if (fileCheckedTemporaryExposureKeys == DELETE_LIMIT) return@run false + val appPerms = delete(TABLE_APP_PERMS, "timestamp < ?", longArrayOf(System.currentTimeMillis() - CONFIRM_PERMISSION_VALIDITY), DELETE_LIMIT) Log.d(TAG, "Deleted on daily cleanup: $appPerms perms") + if (appPerms == DELETE_LIMIT) return@run false execSQL("VACUUM;") Log.d(TAG, "Done vacuuming") + return@run true } fun grantPermission(packageName: String, signatureDigest: String, permission: String, timestamp: Long = System.currentTimeMillis()) = writableDatabase.run { @@ -205,7 +212,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } fun batchStoreSingleDiagnosisKey(tid: Long, keys: List, database: SQLiteDatabase = writableDatabase) = database.run { - beginTransaction() + beginTransactionNonExclusive() try { keys.forEach { storeSingleDiagnosisKey(tid, it, database) } setTransactionSuccessful() @@ -356,7 +363,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } fun finishFileMatching(tid: Long, hash: ByteArray, endTimestamp: Long, keys: List, updates: List, database: SQLiteDatabase = writableDatabase) = database.run { - beginTransaction() + beginTransactionNonExclusive() try { insert(TABLE_TEK_CHECK_FILE, "NULL", ContentValues().apply { put("hash", ByteString.of(*hash).hex()) @@ -367,27 +374,36 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit val workQueue = LinkedBlockingQueue() val poolSize = Runtime.getRuntime().availableProcessors() val executor = ThreadPoolExecutor(poolSize, poolSize, 1, TimeUnit.SECONDS, workQueue) - val futures = arrayListOf>() + val futures = arrayListOf>() val oldestRpi = oldestRpi var ignored = 0 var processed = 0 var found = 0 + var riskLogged = 0 for (key in keys) { + if (key.transmissionRiskLevel > riskLogged) { + riskLogged = key.transmissionRiskLevel + Log.d(TAG, "First key with risk ${key.transmissionRiskLevel}: ${ByteString.of(*key.keyData).hex()} starts ${key.rollingStartIntervalNumber}") + } if ((key.rollingStartIntervalNumber + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS < oldestRpi) { // Early ignore because key is older than since we started scanning. ignored++; } else { - futures.add(executor.submit { - if (findMeasuredExposures(key).isNotEmpty()) { - applyDiagnosisFileKeySearchResult(tcfid, key, this) - found++; - } + futures.add(executor.submit(Callable { processed++ - }) + if (findMeasuredExposures(key).isNotEmpty()) { + key + } else { + null + } + })) } } for (future in futures) { - future.get() + future.get()?.let { + applyDiagnosisFileKeySearchResult(tcfid, it, this) + found++ + } } Log.d(TAG, "Processed $processed keys, found $found matches, ignored $ignored keys that are older than our scanning efforts ($oldestRpi)") executor.shutdown() @@ -585,7 +601,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit get() = readableDatabase.run { query(TABLE_ADVERTISEMENTS, arrayOf("MIN(timestamp)"), null, null, null, null, null).use { cursor -> if (cursor.moveToNext()) { - cursor.getLong(0) + cursor.getLong(0).let { if (it == 0L) System.currentTimeMillis() else it } } else { System.currentTimeMillis() } @@ -634,7 +650,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } private fun ensureTemporaryExposureKey(): TemporaryExposureKey = writableDatabase.let { database -> - database.beginTransaction() + database.beginTransactionNonExclusive() try { var key = findOwnKeyAt(currentRollingStartNumber.toInt(), database) if (key == null) { @@ -688,6 +704,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit companion object { private const val DB_NAME = "exposure.db" private const val DB_VERSION = 5 + private const val DELETE_LIMIT = 1000 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/MeasuredExposure.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/MeasuredExposure.kt index 18113f1f..1ef24849 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 @@ -5,6 +5,7 @@ package org.microg.gms.nearby.exposurenotification +import android.util.Log import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.ExposureInformation import com.google.android.gms.nearby.exposurenotification.RiskLevel @@ -111,7 +112,9 @@ data class MergedExposure internal constructor(val key: TemporaryExposureKey, va } fun getRiskScore(configuration: ExposureConfiguration): Int { - return getAttenuationRiskScore(configuration) * getDaysSinceLastExposureRiskScore(configuration) * getDurationRiskScore(configuration) * getTransmissionRiskScore(configuration) + val risk = getAttenuationRiskScore(configuration) * getDaysSinceLastExposureRiskScore(configuration) * getDurationRiskScore(configuration) * getTransmissionRiskScore(configuration) + Log.d(TAG, "Risk score calculation: ${getAttenuationRiskScore(configuration)} * ${getDaysSinceLastExposureRiskScore(configuration)} * ${getDurationRiskScore(configuration)} * ${getTransmissionRiskScore(configuration)} = $risk") + return risk } fun getAttenuationDurations(configuration: ExposureConfiguration): IntArray { From d0668d3c830afab2d87e948511ea236152035e41 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 7 Oct 2020 18:14:41 +0200 Subject: [PATCH 13/28] Make nearby module optional, move UI bits in own module --- play-services-base-core-ui/build.gradle | 58 ++++++++++++++++++ .../src/main/AndroidManifest.xml | 9 +++ .../org/microg/gms/ui/AppIconPreference.kt | 0 .../kotlin/org/microg/gms/ui/Constants.kt | 0 .../org/microg/gms/ui/PreferenceSwitchBar.kt | 0 .../org/microg/gms/ui/TextPreference.kt | 0 .../main/kotlin/org/microg/gms/ui/Utils.kt | 18 ++++++ .../src/main/res/drawable/ic_expand_apps.xml | 0 .../src/main/res/drawable/ic_info_outline.xml | 0 .../src/main/res/drawable/ic_open.xml | 0 .../src/main/res/layout/list_no_item.xml | 0 .../layout/preference_category_no_label.xml | 0 .../main/res/layout/preference_switch_bar.xml | 0 .../src/main/res/values-de/strings.xml | 14 +++++ .../src/main/res/values-fr/strings.xml | 11 ++++ .../src/main/res/values-it/strings.xml | 12 ++++ .../src/main/res/values-pl/strings.xml | 11 ++++ .../src/main/res/values-pt-rBR/strings.xml | 11 ++++ .../src/main/res/values-ru/strings.xml | 11 ++++ .../src/main/res/values-sr/strings.xml | 11 ++++ .../src/main/res/values-uk/strings.xml | 11 ++++ .../src/main/res/values-zh-rTW/strings.xml | 11 ++++ .../src/main/res/values/layout.xml | 13 ++++ .../src/main/res/values/strings.xml | 14 +++++ play-services-core/build.gradle | 35 ++++++++--- .../src/main/AndroidManifest.xml | 14 ----- .../org/microg/gms/ui/SettingsActivity.java | 7 +-- .../org/microg/gms/ui/SettingsFragment.kt | 16 +---- .../src/main/res/navigation/nav_settings.xml | 35 +---------- .../src/main/res/values-de/strings.xml | 39 ------------ .../src/main/res/values-fr/strings.xml | 3 - .../src/main/res/values-it/strings.xml | 4 -- .../src/main/res/values-pl/strings.xml | 3 - .../src/main/res/values-pt-rBR/strings.xml | 2 - .../src/main/res/values-ru/strings.xml | 3 - .../src/main/res/values-sr/strings.xml | 2 - .../src/main/res/values-uk/strings.xml | 3 - .../src/main/res/values-zh-rTW/strings.xml | 2 - .../src/main/res/values/plurals.xml | 4 -- .../src/main/res/values/strings.xml | 40 ------------- .../src/main/res/values/themes.xml | 5 -- .../src/main/res/xml/preferences_start.xml | 3 +- .../gms/ui/NearbyPreferencesIntegration.kt | 33 ++++++++++ .../gms/ui/NearbyPreferencesIntegration.kt | 19 ++++++ .../res/navigation/nav_nearby.xml | 6 ++ .../src/withoutNearby/res/values/strings.xml | 8 +++ play-services-nearby-core-ui/build.gradle | 60 +++++++++++++++++++ .../src/main/AndroidManifest.xml | 23 +++++++ .../gms/nearby/core}/ui/BarChartPreference.kt | 3 +- .../ui/ExposureNotificationsAppFragment.kt | 8 +-- ...sureNotificationsAppPreferencesFragment.kt | 4 +- .../ExposureNotificationsConfirmActivity.kt | 5 +- .../core}/ui/ExposureNotificationsFragment.kt | 7 ++- ...xposureNotificationsPreferencesFragment.kt | 6 +- .../ui/ExposureNotificationsRpisFragment.kt | 5 +- .../org/microg/gms/nearby/core/ui/Utils.kt | 14 +++++ .../exposure_notifications_app_fragment.xml | 4 +- ...xposure_notifications_confirm_activity.xml | 0 .../exposure_notifications_fragment.xml | 7 +-- .../main/res/layout/preference_bar_chart.xml | 0 .../src/main/res/navigation/nav_nearby.xml | 40 +++++++++++++ .../src/main/res/values-de/strings.xml | 41 +++++++++++++ .../src/main/res/values/plurals.xml | 23 +++++++ .../src/main/res/values/strings.xml | 51 ++++++++++++++++ .../preferences_exposure_notifications.xml | 0 ...preferences_exposure_notifications_app.xml | 0 ...references_exposure_notifications_rpis.xml | 2 +- settings.gradle | 3 + 68 files changed, 593 insertions(+), 214 deletions(-) create mode 100644 play-services-base-core-ui/build.gradle create mode 100644 play-services-base-core-ui/src/main/AndroidManifest.xml rename {play-services-core => play-services-base-core-ui}/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt (100%) rename {play-services-core => play-services-base-core-ui}/src/main/kotlin/org/microg/gms/ui/Constants.kt (100%) rename {play-services-core => play-services-base-core-ui}/src/main/kotlin/org/microg/gms/ui/PreferenceSwitchBar.kt (100%) rename {play-services-core => play-services-base-core-ui}/src/main/kotlin/org/microg/gms/ui/TextPreference.kt (100%) rename {play-services-core => play-services-base-core-ui}/src/main/kotlin/org/microg/gms/ui/Utils.kt (73%) rename {play-services-core => play-services-base-core-ui}/src/main/res/drawable/ic_expand_apps.xml (100%) rename {play-services-core => play-services-base-core-ui}/src/main/res/drawable/ic_info_outline.xml (100%) rename {play-services-core => play-services-base-core-ui}/src/main/res/drawable/ic_open.xml (100%) rename {play-services-core => play-services-base-core-ui}/src/main/res/layout/list_no_item.xml (100%) rename {play-services-core => play-services-base-core-ui}/src/main/res/layout/preference_category_no_label.xml (100%) rename {play-services-core => play-services-base-core-ui}/src/main/res/layout/preference_switch_bar.xml (100%) create mode 100644 play-services-base-core-ui/src/main/res/values-de/strings.xml create mode 100644 play-services-base-core-ui/src/main/res/values-fr/strings.xml create mode 100644 play-services-base-core-ui/src/main/res/values-it/strings.xml create mode 100644 play-services-base-core-ui/src/main/res/values-pl/strings.xml create mode 100644 play-services-base-core-ui/src/main/res/values-pt-rBR/strings.xml create mode 100644 play-services-base-core-ui/src/main/res/values-ru/strings.xml create mode 100644 play-services-base-core-ui/src/main/res/values-sr/strings.xml create mode 100644 play-services-base-core-ui/src/main/res/values-uk/strings.xml create mode 100644 play-services-base-core-ui/src/main/res/values-zh-rTW/strings.xml create mode 100644 play-services-base-core-ui/src/main/res/values/layout.xml create mode 100644 play-services-base-core-ui/src/main/res/values/strings.xml create mode 100644 play-services-core/src/withNearby/kotlin/org/microg/gms/ui/NearbyPreferencesIntegration.kt create mode 100644 play-services-core/src/withoutNearby/kotlin/org/microg/gms/ui/NearbyPreferencesIntegration.kt create mode 100644 play-services-core/src/withoutNearby/res/navigation/nav_nearby.xml create mode 100644 play-services-core/src/withoutNearby/res/values/strings.xml create mode 100644 play-services-nearby-core-ui/build.gradle create mode 100644 play-services-nearby-core-ui/src/main/AndroidManifest.xml rename {play-services-core/src/main/kotlin/org/microg/gms => play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core}/ui/BarChartPreference.kt (96%) rename {play-services-core/src/main/kotlin/org/microg/gms => play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core}/ui/ExposureNotificationsAppFragment.kt (87%) rename {play-services-core/src/main/kotlin/org/microg/gms => play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core}/ui/ExposureNotificationsAppPreferencesFragment.kt (97%) rename {play-services-core/src/main/kotlin/org/microg/gms => play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core}/ui/ExposureNotificationsConfirmActivity.kt (95%) rename {play-services-core/src/main/kotlin/org/microg/gms => play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core}/ui/ExposureNotificationsFragment.kt (89%) rename {play-services-core/src/main/kotlin/org/microg/gms => play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core}/ui/ExposureNotificationsPreferencesFragment.kt (96%) rename {play-services-core/src/main/kotlin/org/microg/gms => play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core}/ui/ExposureNotificationsRpisFragment.kt (96%) create mode 100644 play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/Utils.kt rename {play-services-core => play-services-nearby-core-ui}/src/main/res/layout/exposure_notifications_app_fragment.xml (93%) rename {play-services-core => play-services-nearby-core-ui}/src/main/res/layout/exposure_notifications_confirm_activity.xml (100%) rename {play-services-core => play-services-nearby-core-ui}/src/main/res/layout/exposure_notifications_fragment.xml (82%) rename {play-services-core => play-services-nearby-core-ui}/src/main/res/layout/preference_bar_chart.xml (100%) create mode 100644 play-services-nearby-core-ui/src/main/res/navigation/nav_nearby.xml create mode 100644 play-services-nearby-core-ui/src/main/res/values-de/strings.xml create mode 100644 play-services-nearby-core-ui/src/main/res/values/plurals.xml create mode 100644 play-services-nearby-core-ui/src/main/res/values/strings.xml rename {play-services-core => play-services-nearby-core-ui}/src/main/res/xml/preferences_exposure_notifications.xml (100%) rename {play-services-core => play-services-nearby-core-ui}/src/main/res/xml/preferences_exposure_notifications_app.xml (100%) rename {play-services-core => play-services-nearby-core-ui}/src/main/res/xml/preferences_exposure_notifications_rpis.xml (95%) diff --git a/play-services-base-core-ui/build.gradle b/play-services-base-core-ui/build.gradle new file mode 100644 index 00000000..8abe5ce5 --- /dev/null +++ b/play-services-base-core-ui/build.gradle @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' + +dependencies { + api project(':play-services-base') + + // AndroidX UI + implementation "androidx.multidex:multidex:$multidexVersion" + implementation "androidx.appcompat:appcompat:$appcompatVersion" + implementation "androidx.mediarouter:mediarouter:$mediarouterVersion" + implementation "androidx.preference:preference:$preferenceVersion" + + // Navigation + implementation "androidx.navigation:navigation-fragment:$navigationVersion" + implementation "androidx.navigation:navigation-ui:$navigationVersion" + implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion" + implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion" + + implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + dataBinding { + enabled = true + } + + sourceSets { + main { + java.srcDirs += 'src/main/kotlin' + } + } + + lintOptions { + disable 'MissingTranslation' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } +} diff --git a/play-services-base-core-ui/src/main/AndroidManifest.xml b/play-services-base-core-ui/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c034f80a --- /dev/null +++ b/play-services-base-core-ui/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt b/play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt similarity index 100% rename from play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt rename to play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/Constants.kt b/play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/Constants.kt similarity index 100% rename from play-services-core/src/main/kotlin/org/microg/gms/ui/Constants.kt rename to play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/Constants.kt diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PreferenceSwitchBar.kt b/play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/PreferenceSwitchBar.kt similarity index 100% rename from play-services-core/src/main/kotlin/org/microg/gms/ui/PreferenceSwitchBar.kt rename to play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/PreferenceSwitchBar.kt diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/TextPreference.kt b/play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/TextPreference.kt similarity index 100% rename from play-services-core/src/main/kotlin/org/microg/gms/ui/TextPreference.kt rename to play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/TextPreference.kt diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/Utils.kt b/play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/Utils.kt similarity index 73% rename from play-services-core/src/main/kotlin/org/microg/gms/ui/Utils.kt rename to play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/Utils.kt index 9bfce06a..5df78d3d 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/Utils.kt +++ b/play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/Utils.kt @@ -12,7 +12,13 @@ import android.os.Build import android.os.Bundle import android.provider.Settings import android.util.Log +import android.util.TypedValue +import android.view.View +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt import androidx.annotation.IdRes +import androidx.core.content.ContextCompat +import androidx.databinding.BindingAdapter import androidx.navigation.NavController import androidx.navigation.navOptions import androidx.navigation.ui.R @@ -50,3 +56,15 @@ val Context.systemAnimationsEnabled: Boolean } return duration != 0f && transition != 0f } + + +@ColorInt +fun Context.resolveColor(@AttrRes resid: Int): Int? { + val typedValue = TypedValue() + if (!theme.resolveAttribute(resid, typedValue, true)) return null + val colorRes = if (typedValue.resourceId != 0) typedValue.resourceId else typedValue.data + return ContextCompat.getColor(this, colorRes) +} + +@BindingAdapter("app:backgroundColorAttr") +fun View.setBackgroundColorAttribute(@AttrRes resId: Int) = context.resolveColor(resId)?.let { setBackgroundColor(it) } diff --git a/play-services-core/src/main/res/drawable/ic_expand_apps.xml b/play-services-base-core-ui/src/main/res/drawable/ic_expand_apps.xml similarity index 100% rename from play-services-core/src/main/res/drawable/ic_expand_apps.xml rename to play-services-base-core-ui/src/main/res/drawable/ic_expand_apps.xml diff --git a/play-services-core/src/main/res/drawable/ic_info_outline.xml b/play-services-base-core-ui/src/main/res/drawable/ic_info_outline.xml similarity index 100% rename from play-services-core/src/main/res/drawable/ic_info_outline.xml rename to play-services-base-core-ui/src/main/res/drawable/ic_info_outline.xml diff --git a/play-services-core/src/main/res/drawable/ic_open.xml b/play-services-base-core-ui/src/main/res/drawable/ic_open.xml similarity index 100% rename from play-services-core/src/main/res/drawable/ic_open.xml rename to play-services-base-core-ui/src/main/res/drawable/ic_open.xml diff --git a/play-services-core/src/main/res/layout/list_no_item.xml b/play-services-base-core-ui/src/main/res/layout/list_no_item.xml similarity index 100% rename from play-services-core/src/main/res/layout/list_no_item.xml rename to play-services-base-core-ui/src/main/res/layout/list_no_item.xml diff --git a/play-services-core/src/main/res/layout/preference_category_no_label.xml b/play-services-base-core-ui/src/main/res/layout/preference_category_no_label.xml similarity index 100% rename from play-services-core/src/main/res/layout/preference_category_no_label.xml rename to play-services-base-core-ui/src/main/res/layout/preference_category_no_label.xml diff --git a/play-services-core/src/main/res/layout/preference_switch_bar.xml b/play-services-base-core-ui/src/main/res/layout/preference_switch_bar.xml similarity index 100% rename from play-services-core/src/main/res/layout/preference_switch_bar.xml rename to play-services-base-core-ui/src/main/res/layout/preference_switch_bar.xml diff --git a/play-services-base-core-ui/src/main/res/values-de/strings.xml b/play-services-base-core-ui/src/main/res/values-de/strings.xml new file mode 100644 index 00000000..f5c8a774 --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values-de/strings.xml @@ -0,0 +1,14 @@ + + + + + Erweitert + + Keine + Alle anzeigen + + Öffnen + diff --git a/play-services-base-core-ui/src/main/res/values-fr/strings.xml b/play-services-base-core-ui/src/main/res/values-fr/strings.xml new file mode 100644 index 00000000..15d20d07 --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values-fr/strings.xml @@ -0,0 +1,11 @@ + + + + + Avancé + + Aucun + diff --git a/play-services-base-core-ui/src/main/res/values-it/strings.xml b/play-services-base-core-ui/src/main/res/values-it/strings.xml new file mode 100644 index 00000000..e506c9f8 --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values-it/strings.xml @@ -0,0 +1,12 @@ + + + + + Impostazioni avanzate + + Nessuna + Mostra tutte + diff --git a/play-services-base-core-ui/src/main/res/values-pl/strings.xml b/play-services-base-core-ui/src/main/res/values-pl/strings.xml new file mode 100644 index 00000000..2af5a5ae --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values-pl/strings.xml @@ -0,0 +1,11 @@ + + + + + Zaawansowane + + Brak + diff --git a/play-services-base-core-ui/src/main/res/values-pt-rBR/strings.xml b/play-services-base-core-ui/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 00000000..1576820f --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,11 @@ + + + + + Avançado + + Nenhum + diff --git a/play-services-base-core-ui/src/main/res/values-ru/strings.xml b/play-services-base-core-ui/src/main/res/values-ru/strings.xml new file mode 100644 index 00000000..4fe5f59b --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values-ru/strings.xml @@ -0,0 +1,11 @@ + + + + + Дополнительно + + Пусто + diff --git a/play-services-base-core-ui/src/main/res/values-sr/strings.xml b/play-services-base-core-ui/src/main/res/values-sr/strings.xml new file mode 100644 index 00000000..8f69bd21 --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values-sr/strings.xml @@ -0,0 +1,11 @@ + + + + + Напредно + + Ништа + diff --git a/play-services-base-core-ui/src/main/res/values-uk/strings.xml b/play-services-base-core-ui/src/main/res/values-uk/strings.xml new file mode 100644 index 00000000..ad2ff3d1 --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values-uk/strings.xml @@ -0,0 +1,11 @@ + + + + + Додатково + + Порожньо + diff --git a/play-services-base-core-ui/src/main/res/values-zh-rTW/strings.xml b/play-services-base-core-ui/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 00000000..3222db57 --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,11 @@ + + + + + 進階 + + + diff --git a/play-services-base-core-ui/src/main/res/values/layout.xml b/play-services-base-core-ui/src/main/res/values/layout.xml new file mode 100644 index 00000000..a70af9e5 --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values/layout.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/play-services-base-core-ui/src/main/res/values/strings.xml b/play-services-base-core-ui/src/main/res/values/strings.xml new file mode 100644 index 00000000..560182d8 --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values/strings.xml @@ -0,0 +1,14 @@ + + + + + Advanced + + None + See all + + Open + diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 03a0b700..b798bce6 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -16,17 +16,19 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android-extensions' configurations { - mapboxImplementation - vtmImplementation + withMapboxImplementation + withVtmImplementation + withNearbyImplementation + withoutNearbyImplementation } dependencies { implementation "com.squareup.wire:wire-runtime:$wireVersion" implementation "de.hdodenhof:circleimageview:1.3.0" - implementation "com.diogobernardino:williamchart:3.7.1" implementation "org.conscrypt:conscrypt-android:2.1.0" // TODO: Switch to upstream once raw requests are merged // https://github.com/vitalidze/chromecast-java-api-v2/pull/99 @@ -40,8 +42,10 @@ dependencies { implementation project(':firebase-dynamic-links-api') implementation project(':play-services-base-core') + implementation project(':play-services-base-core-ui') implementation project(':play-services-location-core') - implementation project(':play-services-nearby-core') + withNearbyImplementation project(':play-services-nearby-core') + withNearbyImplementation project(':play-services-nearby-core-ui') implementation project(':play-services-core-proto') implementation project(':play-services-core:microg-ui-tools') // deprecated implementation project(':play-services-api') @@ -50,8 +54,8 @@ dependencies { implementation "org.microg:wearable:$wearableVersion" implementation "org.microg.gms:remote-droid-guard:$remoteDroidGuardVersion" - mapboxImplementation project(':play-services-maps-core-mapbox') - vtmImplementation project(':play-services-maps-core-vtm') + withMapboxImplementation project(':play-services-maps-core-mapbox') + withVtmImplementation project(':play-services-maps-core-vtm') // AndroidX UI implementation "androidx.multidex:multidex:$multidexVersion" @@ -96,6 +100,12 @@ android { main { java.srcDirs += 'src/main/kotlin' } + withNearby { + java.srcDirs += 'src/withNearby/kotlin' + } + withoutNearby { + java.srcDirs += 'src/withoutNearby/kotlin' + } } lintOptions { @@ -109,15 +119,22 @@ android { } } - flavorDimensions 'maps' + flavorDimensions 'maps', 'nearby' productFlavors { - mapbox { + withMapbox { dimension 'maps' } - vtm { + withVtm { dimension 'maps' versionNameSuffix '-vtm' } + withNearby { + dimension 'nearby' + } + withoutNearby { + dimension 'nearby' + versionNameSuffix '-noen' + } } compileOptions { diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index d2305954..b096c4c6 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -107,8 +107,6 @@ android:name="android.permission.UPDATE_APP_OPS_STATS" tools:ignore="ProtectedPermissions" /> - - - - - - - - - - (PREF_EXPOSURE)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - findNavController().navigate(requireContext(), R.id.openExposureNotificationSettings) + findNavController().navigate(requireContext(), NearbyPreferencesIntegration.exposureNotificationNavigationId) true } findPreference(PREF_ABOUT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { @@ -73,16 +71,8 @@ class SettingsFragment : ResourceSettingsFragment() { 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 - } + findPreference(PREF_EXPOSURE)!!.isVisible = NearbyPreferencesIntegration.isAvailable + findPreference(PREF_EXPOSURE)!!.summary = NearbyPreferencesIntegration.getExposurePreferenceSummary(requireContext()) } companion object { diff --git a/play-services-core/src/main/res/navigation/nav_settings.xml b/play-services-core/src/main/res/navigation/nav_settings.xml index b5393954..b3f83da4 100644 --- a/play-services-core/src/main/res/navigation/nav_settings.xml +++ b/play-services-core/src/main/res/navigation/nav_settings.xml @@ -29,7 +29,7 @@ app:destination="@id/googleMoreFragment" /> + app:destination="@id/nav_nearby" /> @@ -100,38 +100,7 @@ android:label="@string/gms_settings_name" /> - - - - - - - - - - - - - + Google Geräte-Registrierung Google Cloud Messaging Google SafetyNet - Exposure Notifications Deaktiviert Aktiviert @@ -67,12 +66,6 @@ Dies kann einige Minuten dauern." Ein Aus - Erweitert - Keine - Alle anzeigen - - Öffnen - Google Play Games %1$s möchte Play Games benutzen Um Play Games zu nutzen, ist die Installation der Google Play Games App erforderlich. Diese App funktioniert eventuell auch ohne Play Games, verhält sich dabei unter Umständen aber ungewöhnlich. @@ -145,38 +138,6 @@ Dies kann einige Minuten dauern." Letzte Registierung: %1$s Gerät registrieren - Öffne eine App, die Exposure Notifications unterstützt, um diese zu aktivieren. - Apps, die Exposure Notifications nutzen - Gesammelte IDs - %1$d IDs in den letzten 60 Minuten - Aktuell verwendete ID - %1$d Prüfungen während der letzen 14 Tage - Letzte Prüfung: %1$s - %1$d gesammelte IDs - Alle gesammelten IDs löschen - "Die Exposure Notifications API ermöglicht es Apps, dich zu warnen, falls du Kontakt zu einer positiv getesteten Person hattest. - -Das Datum, die Zeitdauer und die Signalstärke, die dem Kontakt zugeordnet sind, werden mit der zugehörigen App geteilt." - "Während die Exposure Notification API aktiviert ist, sammelt das Gerät passiv IDs (Rolling Proximity Identifiers bzw. RPIs) von Geräten in der Nähe. - -Sobald die Eigentümer von Geräten positiv getestet wurden, können ihre IDs geteilt werden. Dein Gerät prüft, ob eine gespeicherte ID zu einer positiv getesteten Person gehört und berechnet das Infektionsrisiko." - - Exposure Notifications aktivieren - Exposure Notifications aktivieren? - "Dein Smartphone benötigt Bluetooth, um IDs von anderen Personen sicher zu teilen und zu speichern. - -%1$s kann dich benachrichtigen, falls du Kontakt zu einer positiv getesteten Person hattest. - -Das Datum, die Zeitdauer und die Signalstärke, die einem Kontakt zugeordnet wurden werden mit der App geteilt." - Einschalten - Exposure Notifications deaktivieren? - Nach dem Deaktivieren wirst du nicht mehr benachrichtigt, falls du Kontakt mit einer positiv getesteten Person hattest. - Deaktivieren - IDs mit %1$s teilen? - "Deine IDs der letzten Tage werden genutzt, um Nutzer, die in den letzten 14 Tagen mit dir Kontakt hatten, zu benachrichtigen. - -Deine Identität oder das Testergebnis werden nicht geteilt." - Teilen Status Mehr diff --git a/play-services-core/src/main/res/values-fr/strings.xml b/play-services-core/src/main/res/values-fr/strings.xml index 6b5471ca..a922d560 100644 --- a/play-services-core/src/main/res/values-fr/strings.xml +++ b/play-services-core/src/main/res/values-fr/strings.xml @@ -54,9 +54,6 @@ Ceci peut prendre plusieurs minutes." Automatique Manuel - Avancé - Aucun - Google Play Jeux %1$s voudrait utiliser Play Jeux Pour utiliser Play Jeux il est requis d’installer l’application Google Play Jeux. L’application peut poursuivre sans Play Jeux, mais il est possible qu’elle se comporte de manière imprévue. diff --git a/play-services-core/src/main/res/values-it/strings.xml b/play-services-core/src/main/res/values-it/strings.xml index 9c222171..7476d4d5 100644 --- a/play-services-core/src/main/res/values-it/strings.xml +++ b/play-services-core/src/main/res/values-it/strings.xml @@ -66,10 +66,6 @@ Questa operazione può richiedere alcuni minuti." Abilitato Disabilitato - Impostazioni avanzate - Nessuna - Mostra tutte - Google Play Giochi %1$s vorrebbe utilizzare Play Giochi Per usare Play Giochi è necessario installare l\'applicazione di Play Giochi. L\'applicazione può continuare ad essere utilizzata senza Play Giochi, ma è possibile che funzioni in modo anomalo. diff --git a/play-services-core/src/main/res/values-pl/strings.xml b/play-services-core/src/main/res/values-pl/strings.xml index b9f8a0cc..7c89c214 100644 --- a/play-services-core/src/main/res/values-pl/strings.xml +++ b/play-services-core/src/main/res/values-pl/strings.xml @@ -54,9 +54,6 @@ To zajmie kilka minut. Automatyczny Ręczny - Zaawansowane - Brak - Gry Google Play %1$s chce wykorzystać Gry Google Play Aby korzystać z gier Google Play wymagana jest instalacja aplikacji Gry Google Play . Aplikacja może dalej działać bez Gry Google Play, ale możliwe, że nie będzie działać prawidłowo. diff --git a/play-services-core/src/main/res/values-pt-rBR/strings.xml b/play-services-core/src/main/res/values-pt-rBR/strings.xml index fa0b522a..72921f5f 100644 --- a/play-services-core/src/main/res/values-pt-rBR/strings.xml +++ b/play-services-core/src/main/res/values-pt-rBR/strings.xml @@ -47,8 +47,6 @@ Isso pode demorar alguns minutos." Registro de dispositivo do Google Google Cloud Messaging - Avançado - Nenhum Google Play Games %1$s gostaria de usar Play Games diff --git a/play-services-core/src/main/res/values-ru/strings.xml b/play-services-core/src/main/res/values-ru/strings.xml index 85ce68d5..ff794a18 100644 --- a/play-services-core/src/main/res/values-ru/strings.xml +++ b/play-services-core/src/main/res/values-ru/strings.xml @@ -61,9 +61,6 @@ Активно Автоматически Руководство - - Дополнительно - Пусто Google Play Игры %1$s хочет использовать Play Игры diff --git a/play-services-core/src/main/res/values-sr/strings.xml b/play-services-core/src/main/res/values-sr/strings.xml index 3a026a49..e5549abf 100644 --- a/play-services-core/src/main/res/values-sr/strings.xml +++ b/play-services-core/src/main/res/values-sr/strings.xml @@ -47,8 +47,6 @@ Пријава уређаја на Гугл Гуглове облак поруке - Напредно - Ништа Гугл Плеј игре %1$s жели да користи Плеј игре diff --git a/play-services-core/src/main/res/values-uk/strings.xml b/play-services-core/src/main/res/values-uk/strings.xml index 1db30562..0e46faad 100644 --- a/play-services-core/src/main/res/values-uk/strings.xml +++ b/play-services-core/src/main/res/values-uk/strings.xml @@ -61,9 +61,6 @@ Автоматично Вручну - Додатково - Порожньо - Ігри Google Play %1$s бажає використовувати Play ігри Для використання можливостей Play Games, необхідно встановити додаток Google Play Games. Додаток може працювати і без нього, але можливі проблеми під час роботи. diff --git a/play-services-core/src/main/res/values-zh-rTW/strings.xml b/play-services-core/src/main/res/values-zh-rTW/strings.xml index 5f297719..1a9c71cd 100644 --- a/play-services-core/src/main/res/values-zh-rTW/strings.xml +++ b/play-services-core/src/main/res/values-zh-rTW/strings.xml @@ -47,8 +47,6 @@ 註冊為Google設備 Google雲端訊息(GCM) - 進階 - Google Play遊戲 %1$s將要使用Play遊戲 diff --git a/play-services-core/src/main/res/values/plurals.xml b/play-services-core/src/main/res/values/plurals.xml index c5b8ef5a..146c4f24 100644 --- a/play-services-core/src/main/res/values/plurals.xml +++ b/play-services-core/src/main/res/values/plurals.xml @@ -32,8 +32,4 @@ Request missing permission Request missing permissions - - 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/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index 70e991a2..8911f9c7 100644 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -58,7 +58,6 @@ This can take a couple of minutes." Google device registration Google Cloud Messaging Google SafetyNet - Exposure Notifications Disabled Enabled @@ -67,12 +66,6 @@ This can take a couple of minutes." On Off - Advanced - None - See all - - Open - Google Play Games %1$s would like to use Play Games To use Play Games it is required to install the Google Play Games app. The application might continue without Play Games, but it is possible that it will behave unexpectedly. @@ -146,39 +139,6 @@ This can take a couple of minutes." Last registration: %1$s Register device - To enable Exposure Notifications, open any app supporting it. - Apps using Exposure Notifications - Collected IDs - %1$d IDs in last hour - Currently broadcasted ID - %1$d checks in past 14 days - Last check: %1$s - %1$d IDs collected - Delete all collected IDs - "Exposure Notifications API allows apps to notify you if you were exposed to someone who reported to be diagnosed positive. - -The date, duration, and signal strength associated with an exposure will be shared with the corresponding app." - "While Exposure Notification API is enabled, your device passively collects IDs (called Rolling Proximity Identifiers, or RPIs) from nearby devices. - -When device owners report to be diagnosed positive, their IDs can be shared. Your device checks if any of the known diagnosed IDs matches any of the collected IDs and calculates your infection risk." - - Use Exposure Notifications - Turn on Exposure Notifications? - "Your phone needs to use Bluetooth to securely collect and share IDs with other phones that are nearby. - -%1$s can notify you if you were exposed to someone who reported to be diagnosed positive. - -The date, duration, and signal strength associated with an exposure will be shared with the app." - Turn on - Turn off Exposure Notifications? - After disabling Exposure Notifications, you will no longer be notified when you were exposed to someone who reported to be diagnosed positive. - Turn off - Share your IDs with %1$s? - "Your IDs from the last 14 days will be used to help notify others that you've been near about potential exposure. - -Your identity or test result won't be shared with other people." - Share - Status More diff --git a/play-services-core/src/main/res/values/themes.xml b/play-services-core/src/main/res/values/themes.xml index f89fef86..9653901c 100644 --- a/play-services-core/src/main/res/values/themes.xml +++ b/play-services-core/src/main/res/values/themes.xml @@ -16,11 +16,6 @@ - - +