mirror of
https://github.com/TeamVanced/VancedMicroG
synced 2024-11-19 02:29:25 +01:00
EN API: Update
This commit is contained in:
parent
8006b2c8a9
commit
4c2ef04364
67
gradle/publish-android.gradle
Normal file
67
gradle/publish-android.gradle
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
task androidSourcesJar(type: Jar) {
|
||||
archiveClassifier.set("sources")
|
||||
from android.sourceSets.main.java.source
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives androidSourcesJar
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
release(MavenPublication) {
|
||||
pom {
|
||||
name = project.name
|
||||
url = 'https://github.com/microg/GmsCore'
|
||||
licenses {
|
||||
license {
|
||||
name = 'The Apache Software License, Version 2.0'
|
||||
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'microg'
|
||||
name = 'microG Team'
|
||||
}
|
||||
developer {
|
||||
id = 'mar-v-in'
|
||||
name = 'Marvin W.'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = 'https://github.com/microg/GmsCore'
|
||||
connection = 'scm:git:https://github.com/microg/GmsCore.git'
|
||||
developerConnection = 'scm:git:ssh://github.com/microg/GmsCore.git'
|
||||
}
|
||||
}
|
||||
|
||||
from components.release
|
||||
artifact androidSourcesJar
|
||||
}
|
||||
}
|
||||
if (project.hasProperty('sonatype.username')) {
|
||||
repositories {
|
||||
maven {
|
||||
name = 'sonatype'
|
||||
url = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
|
||||
credentials {
|
||||
username project.getProperty('sonatype.username')
|
||||
password project.getProperty('sonatype.password')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (project.hasProperty('signing.keyId')) {
|
||||
signing {
|
||||
sign publishing.publications
|
||||
}
|
||||
}
|
||||
}
|
57
gradle/publish-java.gradle
Normal file
57
gradle/publish-java.gradle
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
release(MavenPublication) {
|
||||
pom {
|
||||
name = project.name
|
||||
url = 'https://github.com/microg/GmsCore'
|
||||
licenses {
|
||||
license {
|
||||
name = 'The Apache Software License, Version 2.0'
|
||||
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'microg'
|
||||
name = 'microG Team'
|
||||
}
|
||||
developer {
|
||||
id = 'mar-v-in'
|
||||
name = 'Marvin W.'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = 'https://github.com/microg/GmsCore'
|
||||
connection = 'scm:git:https://github.com/microg/GmsCore.git'
|
||||
developerConnection = 'scm:git:ssh://github.com/microg/GmsCore.git'
|
||||
}
|
||||
}
|
||||
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
if (project.hasProperty('sonatype.username')) {
|
||||
repositories {
|
||||
maven {
|
||||
name = 'sonatype'
|
||||
url = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
|
||||
credentials {
|
||||
username project.getProperty('sonatype.username')
|
||||
password project.getProperty('sonatype.password')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (project.hasProperty('signing.keyId')) {
|
||||
signing {
|
||||
sign publishing.publications
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
dependencies {
|
||||
api project(':play-services-basement')
|
||||
@ -38,3 +40,5 @@ android {
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-android.gradle'
|
||||
|
@ -7,6 +7,8 @@ apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
dependencies {
|
||||
api project(':play-services-base')
|
||||
@ -56,3 +58,5 @@ android {
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-android.gradle'
|
||||
|
@ -4,6 +4,8 @@
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
dependencies {
|
||||
api project(':play-services-basement')
|
||||
@ -27,3 +29,5 @@ android {
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-android.gradle'
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
android {
|
||||
compileSdkVersion androidCompileSdk
|
||||
@ -32,6 +34,8 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-android.gradle'
|
||||
|
||||
dependencies {
|
||||
api project(':play-services-basement')
|
||||
api project(':play-services-tasks')
|
||||
|
@ -122,7 +122,20 @@ public class MultiConnectionKeeper {
|
||||
@SuppressLint("InlinedApi")
|
||||
public void bind() {
|
||||
Log.d(TAG, "Connection(" + actionString + ") : bind()");
|
||||
Intent intent = new Intent(actionString).setPackage(GMS_PACKAGE_NAME);
|
||||
Intent gmsIntent = new Intent(actionString).setPackage(GMS_PACKAGE_NAME);
|
||||
Intent selfIntent = new Intent(actionString).setPackage(context.getPackageName());
|
||||
Intent intent;
|
||||
if (context.getPackageManager().resolveService(gmsIntent, 0) == null) {
|
||||
Log.w(TAG, "No GMS service found for " + actionString);
|
||||
if (context.getPackageManager().resolveService(selfIntent, 0) != null) {
|
||||
Log.d(TAG, "Found service for "+actionString+" in self package, using it instead");
|
||||
intent = selfIntent;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
intent = gmsIntent;
|
||||
}
|
||||
int flags = Context.BIND_AUTO_CREATE;
|
||||
if (SDK_INT >= ICE_CREAM_SANDWICH) {
|
||||
flags |= Context.BIND_ADJUST_WITH_ACTIVITY;
|
||||
|
@ -17,6 +17,8 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
dependencies {
|
||||
api "org.microg:safe-parcel:$safeParcelVersion"
|
||||
@ -49,3 +51,5 @@ android {
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-android.gradle'
|
||||
|
@ -4,6 +4,8 @@
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
android {
|
||||
compileSdkVersion androidCompileSdk
|
||||
@ -21,6 +23,8 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-android.gradle'
|
||||
|
||||
dependencies {
|
||||
api project(':play-services-basement')
|
||||
api project(':play-services-base-api')
|
||||
|
@ -0,0 +1,8 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.nearby.exposurenotification;
|
||||
|
||||
parcelable PackageConfiguration;
|
@ -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 GetPackageConfigurationParams;
|
@ -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 GetStatusParams;
|
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||
|
||||
interface IDiagnosisKeyFileSupplier {
|
||||
boolean hasNext();
|
||||
ParcelFileDescriptor next();
|
||||
boolean isAvailable();
|
||||
}
|
@ -18,6 +18,8 @@ import com.google.android.gms.nearby.exposurenotification.internal.GetCalibratio
|
||||
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;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetStatusParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetPackageConfigurationParams;
|
||||
|
||||
interface INearbyExposureNotificationService{
|
||||
void start(in StartParams params) = 0;
|
||||
@ -35,4 +37,6 @@ interface INearbyExposureNotificationService{
|
||||
void getDailySummaries(in GetDailySummariesParams params) = 15;
|
||||
void setDiagnosisKeysDataMapping(in SetDiagnosisKeysDataMappingParams params) = 16;
|
||||
void getDiagnosisKeysDataMapping(in GetDiagnosisKeysDataMappingParams params) = 17;
|
||||
void getStatus(in GetStatusParams params) = 18;
|
||||
void getPackageConfiguration(in GetPackageConfigurationParams params) = 19;
|
||||
}
|
||||
|
@ -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.PackageConfiguration;
|
||||
|
||||
interface IPackageConfigurationCallback {
|
||||
void onResult(in Status status, in PackageConfiguration result);
|
||||
}
|
@ -19,13 +19,20 @@ import java.util.List;
|
||||
*/
|
||||
@PublicApi
|
||||
public class DiagnosisKeyFileProvider {
|
||||
private int index;
|
||||
private List<File> files;
|
||||
|
||||
public DiagnosisKeyFileProvider(List<File> files) {
|
||||
this.files = new ArrayList<>(files);
|
||||
}
|
||||
|
||||
public List<File> getFiles() {
|
||||
return files;
|
||||
@PublicApi(exclude = true)
|
||||
public boolean hasNext() {
|
||||
return files.size() > index;
|
||||
}
|
||||
|
||||
@PublicApi(exclude = true)
|
||||
public File next() {
|
||||
return files.get(index++);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Detail status for exposure notification service.
|
||||
*/
|
||||
@PublicApi
|
||||
public enum ExposureNotificationStatus {
|
||||
/**
|
||||
* Exposure notification is running.
|
||||
*/
|
||||
ACTIVATED,
|
||||
/**
|
||||
* Exposure notification is not running.
|
||||
*/
|
||||
INACTIVATED,
|
||||
/**
|
||||
* Bluetooth is not enabled.
|
||||
*/
|
||||
BLUETOOTH_DISABLED,
|
||||
/**
|
||||
* Location is not enabled.
|
||||
*/
|
||||
LOCATION_DISABLED,
|
||||
/**
|
||||
* User is not consent for the client.
|
||||
*/
|
||||
NO_CONSENT,
|
||||
/**
|
||||
* The client is not in approved client list.
|
||||
*/
|
||||
NOT_IN_WHITELIST,
|
||||
/**
|
||||
* Can't detected the BLE supporting of this device due to bluetooth is not enabled.
|
||||
*/
|
||||
BLUETOOTH_SUPPORT_UNKNOWN,
|
||||
/**
|
||||
* Hardware of this device doesn't support exposure notification.
|
||||
*/
|
||||
HW_NOT_SUPPORT,
|
||||
/**
|
||||
* There is another client running as active client.
|
||||
*/
|
||||
FOCUS_LOST,
|
||||
/**
|
||||
* Device storage is not sufficient for exposure notification.
|
||||
*/
|
||||
LOW_STORAGE,
|
||||
/**
|
||||
* Current status is unknown.
|
||||
*/
|
||||
UNKNOWN,
|
||||
/**
|
||||
* Exposure notification is not supported.
|
||||
*/
|
||||
EN_NOT_SUPPORT,
|
||||
/**
|
||||
* Exposure notification is not supported for current user profile.
|
||||
*/
|
||||
USER_PROFILE_NOT_SUPPORT
|
||||
;
|
||||
|
||||
private long flag() {
|
||||
return 1 << ordinal();
|
||||
}
|
||||
|
||||
@PublicApi(exclude = true)
|
||||
public static long setToFlags(Set<ExposureNotificationStatus> set) {
|
||||
long res = 0;
|
||||
for (ExposureNotificationStatus status : set) {
|
||||
res |= status.flag();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@PublicApi(exclude = true)
|
||||
public static Set<ExposureNotificationStatus> flagsToSet(long flags) {
|
||||
Set<ExposureNotificationStatus> set = new HashSet<>();
|
||||
for (ExposureNotificationStatus status : values()) {
|
||||
if ((flags & status.flag()) > 0) {
|
||||
set.add(status);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.nearby.exposurenotification;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.microg.gms.common.PublicApi;
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
/**
|
||||
* Holds configuration values that can be passed onto the client app after it has finished installing via {@link ExposureNotificationClient#getPackageConfiguration()}.
|
||||
*/
|
||||
@PublicApi
|
||||
public class PackageConfiguration extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
private Bundle values;
|
||||
|
||||
@PublicApi(exclude = true)
|
||||
public PackageConfiguration() {
|
||||
}
|
||||
|
||||
@PublicApi(exclude = true)
|
||||
public PackageConfiguration(Bundle values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public Bundle getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link PackageConfiguration}.
|
||||
*/
|
||||
public static final class PackageConfigurationBuilder {
|
||||
private Bundle values;
|
||||
|
||||
/**
|
||||
* Sets a Bundle containing configuration options.
|
||||
*/
|
||||
public PackageConfigurationBuilder setValues(Bundle values) {
|
||||
this.values = values;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link PackageConfiguration}.
|
||||
*/
|
||||
public PackageConfiguration build() {
|
||||
return new PackageConfiguration(values);
|
||||
}
|
||||
}
|
||||
|
||||
public static final Creator<PackageConfiguration> CREATOR = new AutoCreator<>(PackageConfiguration.class);
|
||||
}
|
@ -13,6 +13,9 @@ import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A key generated for advertising over a window of time.
|
||||
*/
|
||||
public class TemporaryExposureKey extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
private byte[] keyData;
|
||||
@ -29,6 +32,11 @@ public class TemporaryExposureKey extends AutoSafeParcelable {
|
||||
@Field(6)
|
||||
int daysSinceOnsetOfSymptoms;
|
||||
|
||||
/**
|
||||
* The default value for {@link #getDaysSinceOnsetOfSymptoms()}.
|
||||
*
|
||||
* See {@link DiagnosisKeysDataMapping#getDaysSinceOnsetToInfectiousness()} for more information.
|
||||
*/
|
||||
public static final int DAYS_SINCE_ONSET_OF_SYMPTOMS_UNKNOWN = Constants.DAYS_SINCE_ONSET_OF_SYMPTOMS_UNKNOWN;
|
||||
|
||||
private TemporaryExposureKey() {
|
||||
@ -43,29 +51,49 @@ public class TemporaryExposureKey extends AutoSafeParcelable {
|
||||
this.daysSinceOnsetOfSymptoms = daysSinceOnsetOfSymptoms;
|
||||
}
|
||||
|
||||
/**
|
||||
* The randomly generated Temporary Exposure Key information.
|
||||
*/
|
||||
public byte[] getKeyData() {
|
||||
return Arrays.copyOf(keyData, keyData.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* A number describing when a key starts. It is equal to startTimeOfKeySinceEpochInSecs / (60 * 10).
|
||||
*/
|
||||
public int getRollingStartIntervalNumber() {
|
||||
return rollingStartIntervalNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Risk of transmission associated with the person this key came from.
|
||||
*/
|
||||
@RiskLevel
|
||||
public int getTransmissionRiskLevel() {
|
||||
return transmissionRiskLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* A number describing how long a key is valid. It is expressed in increments of 10 minutes (e.g. 144 for 24 hours).
|
||||
*/
|
||||
public int getRollingPeriod() {
|
||||
return rollingPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of diagnosis associated with a key.
|
||||
*/
|
||||
@ReportType
|
||||
public int getReportType() {
|
||||
return reportType;
|
||||
}
|
||||
|
||||
int getDaysSinceOnsetOfSymptoms() {
|
||||
/**
|
||||
* Number of days elapsed between symptom onset and the key being used.
|
||||
* <p>
|
||||
* E.g. 2 means the key is 2 days after onset of symptoms.
|
||||
*/
|
||||
public int getDaysSinceOnsetOfSymptoms() {
|
||||
return daysSinceOnsetOfSymptoms;
|
||||
}
|
||||
|
||||
@ -107,6 +135,9 @@ public class TemporaryExposureKey extends AutoSafeParcelable {
|
||||
'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link TemporaryExposureKey}.
|
||||
*/
|
||||
public static class TemporaryExposureKeyBuilder {
|
||||
private byte[] keyData;
|
||||
private int rollingStartIntervalNumber;
|
||||
|
@ -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 GetPackageConfigurationParams extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
public IPackageConfigurationCallback callback;
|
||||
|
||||
private GetPackageConfigurationParams() {}
|
||||
|
||||
public GetPackageConfigurationParams(IPackageConfigurationCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public static final Creator<GetPackageConfigurationParams> CREATOR = new AutoCreator<>(GetPackageConfigurationParams.class);
|
||||
}
|
@ -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 GetStatusParams extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
public ILongCallback callback;
|
||||
|
||||
private GetStatusParams() {}
|
||||
|
||||
public GetStatusParams(ILongCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public static final Creator<GetStatusParams> CREATOR = new AutoCreator<>(GetStatusParams.class);
|
||||
}
|
@ -26,6 +26,8 @@ public class ProvideDiagnosisKeysParams extends AutoSafeParcelable {
|
||||
public ExposureConfiguration configuration;
|
||||
@Field(5)
|
||||
public String token;
|
||||
@Field(6)
|
||||
public IDiagnosisKeyFileSupplier keyFileSupplier;
|
||||
|
||||
private ProvideDiagnosisKeysParams() {
|
||||
}
|
||||
@ -42,5 +44,10 @@ public class ProvideDiagnosisKeysParams extends AutoSafeParcelable {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public ProvideDiagnosisKeysParams(IStatusCallback callback, IDiagnosisKeyFileSupplier keyFileSupplier) {
|
||||
this.callback = callback;
|
||||
this.keyFileSupplier = keyFileSupplier;
|
||||
}
|
||||
|
||||
public static final Creator<ProvideDiagnosisKeysParams> CREATOR = new AutoCreator<>(ProvideDiagnosisKeysParams.class);
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
apply plugin: 'com.squareup.wire'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
dependencies {
|
||||
implementation "com.squareup.wire:wire-runtime:$wireVersion"
|
||||
@ -21,3 +23,5 @@ compileKotlin {
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = 1.8
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-java.gradle'
|
||||
|
@ -7,6 +7,8 @@ apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
dependencies {
|
||||
implementation project(':play-services-nearby-core')
|
||||
@ -17,11 +19,9 @@ dependencies {
|
||||
// AndroidX UI
|
||||
implementation "androidx.multidex:multidex:$multidexVersion"
|
||||
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||
implementation "androidx.preference:preference:$preferenceVersion"
|
||||
implementation "androidx.preference:preference-ktx:$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"
|
||||
|
||||
@ -62,3 +62,5 @@ android {
|
||||
jvmTarget = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-android.gradle'
|
||||
|
@ -12,6 +12,7 @@ import androidx.core.text.HtmlCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
|
||||
import org.json.JSONObject
|
||||
import org.microg.gms.nearby.exposurenotification.ExposureDatabase
|
||||
import org.microg.gms.nearby.exposurenotification.merge
|
||||
@ -50,6 +51,9 @@ class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() {
|
||||
updateContent()
|
||||
}
|
||||
|
||||
private fun ExposureConfiguration?.orDefault() = this
|
||||
?: ExposureConfiguration.ExposureConfigurationBuilder().build()
|
||||
|
||||
fun updateContent() {
|
||||
packageName?.let { packageName ->
|
||||
lifecycleScope.launchWhenResumed {
|
||||
@ -70,7 +74,7 @@ class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() {
|
||||
getString(R.string.pref_exposure_app_last_report_summary_encounters_no)
|
||||
} else {
|
||||
database.findAllMeasuredExposures(config.first).merge().map {
|
||||
val riskScore = it.getRiskScore(config.second)
|
||||
val riskScore = it.getRiskScore(config.second.orDefault())
|
||||
"· " + getString(R.string.pref_exposure_app_last_report_summary_encounters_line, DateUtils.formatDateRange(requireContext(), it.timestamp, it.timestamp + it.durationInMinutes * 60 * 1000L, DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE), riskScore)
|
||||
}.joinToString("<br>").let { getString(R.string.pref_exposure_app_last_report_summary_encounters_prefix, merged.size) + "<br>$it<br><i>" + getString(R.string.pref_exposure_app_last_report_summary_encounters_suffix) + "</i>" }
|
||||
}
|
||||
|
@ -5,12 +5,15 @@
|
||||
|
||||
package org.microg.gms.nearby.core.ui
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.ResultReceiver
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import org.microg.gms.nearby.core.ui.R
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.microg.gms.nearby.exposurenotification.*
|
||||
import org.microg.gms.ui.getApplicationInfoIfExists
|
||||
|
||||
@ -31,12 +34,16 @@ class ExposureNotificationsConfirmActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.exposure_notifications_confirm_activity)
|
||||
val applicationInfo = packageManager.getApplicationInfoIfExists(targetPackageName)
|
||||
val selfApplicationInfo = packageManager.getApplicationInfoIfExists(packageName)
|
||||
when (action) {
|
||||
CONFIRM_ACTION_START -> {
|
||||
findViewById<TextView>(android.R.id.title).text = getString(R.string.exposure_confirm_start_title)
|
||||
findViewById<TextView>(android.R.id.summary).text = getString(R.string.exposure_confirm_start_summary, applicationInfo?.loadLabel(packageManager)
|
||||
?: targetPackageName)
|
||||
findViewById<Button>(android.R.id.button1).text = getString(R.string.exposure_confirm_start_button)
|
||||
findViewById<TextView>(R.id.grant_permission_summary).text = getString(R.string.exposure_confirm_permission_description, selfApplicationInfo?.loadLabel(packageManager)
|
||||
?: packageName)
|
||||
checkPermissions()
|
||||
}
|
||||
CONFIRM_ACTION_STOP -> {
|
||||
findViewById<TextView>(android.R.id.title).text = getString(R.string.exposure_confirm_stop_title)
|
||||
@ -62,6 +69,34 @@ class ExposureNotificationsConfirmActivity : AppCompatActivity() {
|
||||
resultCode = RESULT_CANCELED
|
||||
finish()
|
||||
}
|
||||
findViewById<Button>(R.id.grant_permission_button).setOnClickListener {
|
||||
requestPermissions()
|
||||
}
|
||||
}
|
||||
|
||||
private val permissions by lazy {
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
arrayOf("android.permission.ACCESS_BACKGROUND_LOCATION", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION")
|
||||
} else {
|
||||
arrayOf("android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION")
|
||||
}
|
||||
}
|
||||
private var requestCode = 33
|
||||
private fun checkPermissions() {
|
||||
val needRequest = Build.VERSION.SDK_INT >= 23 && permissions.any { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED }
|
||||
findViewById<Button>(android.R.id.button1).isEnabled = !needRequest
|
||||
findViewById<View>(R.id.grant_permission_view).visibility = if (needRequest) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun requestPermissions() {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
requestPermissions(permissions, ++requestCode)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == this.requestCode) checkPermissions()
|
||||
}
|
||||
|
||||
override fun finish() {
|
||||
|
@ -8,6 +8,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
@ -30,6 +31,36 @@
|
||||
android:padding="16dp"
|
||||
tools:text="Your phone needs to use Bluetooth to securely collect and share IDs with other phones that are nearby.\n\nCorona Warn can notify you if you were exposed to someone who reported to be diagnosed positive.\n\nThe date, duration, and signal strength associated with an exposure will be shared with the app." />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/grant_permission_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="?attr/colorAccent"
|
||||
android:clipToPadding="false"
|
||||
android:padding="16dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/grant_permission_summary"
|
||||
style="@style/TextAppearance.AppCompat.Small.Inverse"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/exposure_confirm_permission_description" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/grant_permission_button"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginRight="-8dp"
|
||||
android:text="@string/exposure_confirm_permission_button"
|
||||
android:textColor="?android:attr/textColorPrimaryInverse" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -56,4 +56,6 @@ The date, duration, and signal strength associated with an exposure will be shar
|
||||
|
||||
Your identity or test result won't be shared with other people."</string>
|
||||
<string name="exposure_confirm_keys_button">Share</string>
|
||||
<string name="exposure_confirm_permission_description">You need to grant permissions to <xliff:g example="microG Services Core">%1$s</xliff:g> for Exposure Notifications to function correctly.</string>
|
||||
<string name="exposure_confirm_permission_button">Grant</string>
|
||||
</resources>
|
||||
|
@ -6,6 +6,8 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
dependencies {
|
||||
api project(':play-services-nearby-api')
|
||||
@ -48,3 +50,5 @@ android {
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-android.gradle'
|
||||
|
@ -12,6 +12,9 @@
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="com.google.android.gms.nearby.exposurenotification.EXPOSURE_CALLBACK" />
|
||||
|
||||
<application>
|
||||
|
@ -72,6 +72,7 @@ class AdvertiserService : LifecycleService() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
ForegroundServiceContext.completeForegroundService(this, intent, TAG)
|
||||
Log.d(TAG, "AdvertisingService.start: $intent")
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
if (intent?.action == ACTION_RESTART_ADVERTISING && advertising) {
|
||||
stopOrRestartAdvertising()
|
||||
|
@ -9,6 +9,7 @@ import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -20,6 +21,7 @@ import org.microg.gms.common.ForegroundServiceContext
|
||||
class CleanupService : LifecycleService() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
ForegroundServiceContext.completeForegroundService(this, intent, TAG)
|
||||
Log.d(TAG, "CleanupService.start: $intent")
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
if (isNeeded(this)) {
|
||||
lifecycleScope.launchWhenStarted {
|
||||
|
@ -53,6 +53,9 @@ val currentDeviceInfo: DeviceInfo
|
||||
return deviceInfo
|
||||
}
|
||||
|
||||
val averageDeviceInfo: DeviceInfo
|
||||
get() = averageCurrentDeviceInfo(Build.MANUFACTURER, Build.DEVICE, Build.MODEL, allDeviceInfos, CalibrationConfidence.LOWEST)
|
||||
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
private fun String.equalsIgnoreCase(other: String): Boolean = (this as java.lang.String).equalsIgnoreCase(other)
|
||||
|
||||
|
@ -15,6 +15,8 @@ import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import com.google.android.gms.nearby.exposurenotification.CalibrationConfidence
|
||||
import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
|
||||
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
||||
import kotlinx.coroutines.*
|
||||
@ -25,6 +27,7 @@ import java.lang.Runnable
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
import kotlin.experimental.and
|
||||
|
||||
@TargetApi(21)
|
||||
class ExposureDatabase private constructor(private val context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
||||
@ -65,7 +68,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
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 TABLE IF NOT EXISTS $TABLE_TOKENS(tid INTEGER PRIMARY KEY, package TEXT NOT NULL, token TEXT NOT NULL, timestamp INTEGER NOT NULL, configuration BLOB, diagnosisKeysDataMap 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);")
|
||||
@ -85,6 +88,9 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
// RSSI of -100 is already extremely low and thus is a good "default" value
|
||||
db.execSQL("UPDATE $TABLE_ADVERTISEMENTS SET rssi = -100 WHERE rssi < -200;")
|
||||
}
|
||||
if (oldVersion in 5 until 7) {
|
||||
db.execSQL("ALTER TABLE $TABLE_TOKENS ADD COLUMN diagnosisKeysDataMap BLOB;")
|
||||
}
|
||||
Log.d(TAG, "Finished database upgrade")
|
||||
}
|
||||
|
||||
@ -250,10 +256,10 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
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 {
|
||||
insertWithOnConflict(TABLE_TEK_CHECK_FILE_TOKEN, "NULL", ContentValues().apply {
|
||||
put("tid", tid)
|
||||
put("tcfid", cursor.getLong(0))
|
||||
})
|
||||
}, CONFLICT_IGNORE)
|
||||
cursor.getLong(1)
|
||||
} else {
|
||||
null
|
||||
@ -386,7 +392,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
var ignored = 0
|
||||
var processed = 0
|
||||
var found = 0
|
||||
var riskLogged = 0
|
||||
var riskLogged = -1
|
||||
for (key in keys) {
|
||||
if (key.transmissionRiskLevel > riskLogged) {
|
||||
riskLogged = key.transmissionRiskLevel
|
||||
@ -435,11 +441,11 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
}
|
||||
}
|
||||
|
||||
fun findAllSingleMeasuredExposures(tid: Long, database: SQLiteDatabase = readableDatabase): List<MeasuredExposure> {
|
||||
private fun findAllSingleMeasuredExposures(tid: Long, database: SQLiteDatabase = readableDatabase): List<MeasuredExposure> {
|
||||
return listMatchedSingleDiagnosisKeys(tid, database).flatMap { findMeasuredExposures(it, database) }
|
||||
}
|
||||
|
||||
fun findAllFileMeasuredExposures(tid: Long, database: SQLiteDatabase = readableDatabase): List<MeasuredExposure> {
|
||||
private fun findAllFileMeasuredExposures(tid: Long, database: SQLiteDatabase = readableDatabase): List<MeasuredExposure> {
|
||||
return listMatchedFileDiagnosisKeys(tid, database).flatMap { findMeasuredExposures(it, database) }
|
||||
}
|
||||
|
||||
@ -458,13 +464,13 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
it.timestamp >= targetTimestamp - ALLOWED_KEY_OFFSET_MS && it.timestamp <= targetTimestamp + ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS
|
||||
}.mapNotNull {
|
||||
val decrypted = key.cryptAem(it.rpi, it.aem)
|
||||
if (decrypted[0] == 0x40.toByte() || decrypted[0] == 0x50.toByte()) {
|
||||
val txPower = decrypted[1]
|
||||
MeasuredExposure(it.timestamp, it.duration, it.rssi, txPower.toInt(), key)
|
||||
} else {
|
||||
Log.w(TAG, "Unknown AEM version ${decrypted[0]}, ignoring")
|
||||
null
|
||||
val version = (decrypted[0] and 0xf0.toByte())
|
||||
val txPower = if (decrypted.size >= 4 && version >= VERSION_1_0) decrypted[1].toInt() else (averageDeviceInfo.txPowerCorrection + TX_POWER_LOW)
|
||||
val confidence = if (decrypted.size >= 4 && version >= VERSION_1_1) ((decrypted[0] and 0xc) / 4) else (averageDeviceInfo.confidence)
|
||||
if (version > VERSION_1_1) {
|
||||
Log.w(TAG, "Unknown AEM version: 0x${version.toString(16)}")
|
||||
}
|
||||
MeasuredExposure(it.timestamp, it.duration, it.rssi, txPower, confidence, key)
|
||||
}
|
||||
}
|
||||
|
||||
@ -546,10 +552,25 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
getTokenId(packageName, token, database)
|
||||
}
|
||||
|
||||
fun loadConfiguration(packageName: String, token: String, database: SQLiteDatabase = readableDatabase): Pair<Long, ExposureConfiguration>? = database.run {
|
||||
query(TABLE_TOKENS, arrayOf("tid", "configuration"), "package = ? AND token = ?", arrayOf(packageName, token), null, null, null, null).use { cursor ->
|
||||
fun storeConfiguration(packageName: String, token: String, mapping: DiagnosisKeysDataMapping, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
val update = update(TABLE_TOKENS, ContentValues().apply { put("diagnosisKeysDataMap", mapping.marshall()) }, "package = ? AND token = ?", arrayOf(packageName, token))
|
||||
if (update <= 0) {
|
||||
insert(TABLE_TOKENS, "NULL", ContentValues().apply {
|
||||
put("package", packageName)
|
||||
put("token", token)
|
||||
put("timestamp", System.currentTimeMillis())
|
||||
put("diagnosisKeysDataMap", mapping.marshall())
|
||||
})
|
||||
}
|
||||
getTokenId(packageName, token, database)
|
||||
}
|
||||
|
||||
fun loadConfiguration(packageName: String, token: String, database: SQLiteDatabase = readableDatabase): Triple<Long, ExposureConfiguration?, DiagnosisKeysDataMapping?>? = database.run {
|
||||
query(TABLE_TOKENS, arrayOf("tid", "configuration", "diagnosisKeysDataMap"), "package = ? AND token = ?", arrayOf(packageName, token), null, null, null, null).use { cursor ->
|
||||
if (cursor.moveToNext()) {
|
||||
cursor.getLong(0) to ExposureConfiguration.CREATOR.unmarshall(cursor.getBlob(1))
|
||||
val configuration = try {ExposureConfiguration.CREATOR.unmarshall(cursor.getBlob(1)) } catch (e: Exception) { null }
|
||||
val diagnosisKeysDataMapping = try { DiagnosisKeysDataMapping.CREATOR.unmarshall(cursor.getBlob(2)) } catch (e: Exception) { null }
|
||||
Triple(cursor.getLong(0), configuration, diagnosisKeysDataMapping)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@ -732,7 +753,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
|
||||
companion object {
|
||||
private const val DB_NAME = "exposure.db"
|
||||
private const val DB_VERSION = 6
|
||||
private const val DB_VERSION = 7
|
||||
private const val DB_SIZE_TOO_LARGE = 256L * 1024 * 1024
|
||||
private const val MAX_DELETE_TIME = 5000L
|
||||
private const val TABLE_ADVERTISEMENTS = "advertisements"
|
||||
|
@ -31,7 +31,6 @@ class ExposureNotificationService : BaseService(TAG, GmsService.NEARBY_EXPOSURE)
|
||||
|
||||
if (request.packageName != packageName) {
|
||||
checkPermission("android.permission.BLUETOOTH") ?: return
|
||||
checkPermission("android.permission.INTERNET") ?: return
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
@ -41,9 +40,17 @@ class ExposureNotificationService : BaseService(TAG, GmsService.NEARBY_EXPOSURE)
|
||||
|
||||
Log.d(TAG, "handleServiceRequest: " + request.packageName)
|
||||
callback.onPostInitCompleteWithConnectionInfo(SUCCESS, ExposureNotificationServiceImpl(this, lifecycle, request.packageName), ConnectionInfo().apply {
|
||||
// Note: this is just a list of all possible features as of 1.7.1-eap
|
||||
features = arrayOf(
|
||||
Feature("nearby_exposure_notification", 3),
|
||||
Feature("nearby_exposure_notification_get_version", 1)
|
||||
Feature("nearby_exposure_notification_1p", 1),
|
||||
Feature("nearby_exposure_notification_get_version", 1),
|
||||
Feature("nearby_exposure_notification_get_calibration_confidence", 1),
|
||||
Feature("nearby_exposure_notification_get_day_summaries", 1),
|
||||
Feature("nearby_exposure_notification_get_status", 1),
|
||||
Feature("nearby_exposure_notification_diagnosis_keys_data_mapping", 1),
|
||||
Feature("nearby_exposure_notification_diagnosis_key_file_supplier", 1),
|
||||
Feature("nearby_exposure_notification_package_configuration", 1)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -16,10 +16,8 @@ import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.gms.common.api.Status
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
|
||||
import com.google.android.gms.nearby.exposurenotification.*
|
||||
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
|
||||
@ -32,6 +30,7 @@ import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.random.Random
|
||||
|
||||
@ -63,7 +62,6 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
}
|
||||
|
||||
private suspend fun confirmPermission(permission: String): Status {
|
||||
if (packageName == context.packageName) return Status.SUCCESS
|
||||
return ExposureDatabase.with(context) { database ->
|
||||
if (tempGrantedPermissions.contains(packageName to permission)) {
|
||||
database.grantPermission(packageName, PackageUtils.firstSignatureDigest(context, packageName)!!, permission)
|
||||
@ -175,10 +173,13 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
digest()
|
||||
}
|
||||
|
||||
private fun ExposureConfiguration?.orDefault() = this
|
||||
?: ExposureConfiguration.ExposureConfigurationBuilder().build()
|
||||
|
||||
private suspend fun buildExposureSummary(token: String): ExposureSummary = ExposureDatabase.with(context) { database ->
|
||||
val pair = database.loadConfiguration(packageName, token)
|
||||
val (configuration, exposures) = if (pair != null) {
|
||||
pair.second to database.findAllMeasuredExposures(pair.first).merge()
|
||||
pair.second.orDefault() to database.findAllMeasuredExposures(pair.first).merge()
|
||||
} else {
|
||||
ExposureConfiguration.ExposureConfigurationBuilder().build() to emptyList()
|
||||
}
|
||||
@ -241,6 +242,25 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
Log.w(TAG, "Failed parsing file", e)
|
||||
}
|
||||
}
|
||||
params.keyFileSupplier?.let { keyFileSupplier ->
|
||||
Log.d(TAG, "Using key file supplier")
|
||||
while (keyFileSupplier.isAvailable && keyFileSupplier.hasNext()) {
|
||||
try {
|
||||
val cacheFile = File(context.cacheDir, "en-keyfile-${System.currentTimeMillis()}-${Random.nextInt()}.zip")
|
||||
ParcelFileDescriptor.AutoCloseInputStream(keyFileSupplier.next()).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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (todoKeyFiles.size > 0) {
|
||||
val time = (System.currentTimeMillis() - start).toDouble() / 1000.0
|
||||
@ -285,7 +305,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
cacheFile.delete()
|
||||
}
|
||||
|
||||
val time = (System.currentTimeMillis() - start).toDouble() / 1000.0
|
||||
val time = (System.currentTimeMillis() - start).coerceAtLeast(1).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 {
|
||||
@ -344,7 +364,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
val pair = database.loadConfiguration(packageName, params.token)
|
||||
val response = if (pair != null) {
|
||||
database.findAllMeasuredExposures(pair.first).merge().map {
|
||||
it.toExposureInformation(pair.second)
|
||||
it.toExposureInformation(pair.second.orDefault())
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
@ -363,24 +383,159 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
}
|
||||
}
|
||||
|
||||
private fun ScanInstance.Builder.apply(subExposure: MergedSubExposure): ScanInstance.Builder {
|
||||
return this
|
||||
.setSecondsSinceLastScan(subExposure.duration.coerceAtMost(5 * 60 * 1000L).toInt())
|
||||
.setMinAttenuationDb(subExposure.attenuation) // FIXME: We use the average for both, because we don't store the minimum attenuation yet
|
||||
.setTypicalAttenuationDb(subExposure.attenuation)
|
||||
}
|
||||
|
||||
private fun List<MergedSubExposure>.toScanInstances(): List<ScanInstance> {
|
||||
val res = arrayListOf<ScanInstance>()
|
||||
for (subExposure in this) {
|
||||
res.add(ScanInstance.Builder().apply(subExposure).build())
|
||||
if (subExposure.duration > 5 * 60 * 1000L) {
|
||||
res.add(ScanInstance.Builder().apply(subExposure).setSecondsSinceLastScan((subExposure.duration - 5 * 60 * 1000L).coerceAtMost(5 * 60 * 1000L).toInt()).build())
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
private fun DiagnosisKeysDataMapping?.orDefault() = this ?: DiagnosisKeysDataMapping()
|
||||
|
||||
private suspend fun getExposureWindowsInternal(token: String = TOKEN_A): List<ExposureWindow> {
|
||||
val (exposures, mapping) = ExposureDatabase.with(context) { database ->
|
||||
val triple = database.loadConfiguration(packageName, token)
|
||||
if (triple != null) {
|
||||
database.findAllMeasuredExposures(triple.first).merge() to triple.third.orDefault()
|
||||
} else {
|
||||
emptyList<MergedExposure>() to DiagnosisKeysDataMapping()
|
||||
}
|
||||
}
|
||||
return exposures.map {
|
||||
val infectiousness =
|
||||
if (it.key.daysSinceOnsetOfSymptoms == DAYS_SINCE_ONSET_OF_SYMPTOMS_UNKNOWN)
|
||||
mapping.infectiousnessWhenDaysSinceOnsetMissing
|
||||
else
|
||||
mapping.daysSinceOnsetToInfectiousness[it.key.daysSinceOnsetOfSymptoms]
|
||||
?: Infectiousness.NONE
|
||||
val reportType =
|
||||
if (it.key.reportType == ReportType.UNKNOWN)
|
||||
mapping.reportTypeWhenMissing
|
||||
else
|
||||
it.key.reportType
|
||||
|
||||
ExposureWindow.Builder()
|
||||
.setCalibrationConfidence(it.confidence)
|
||||
.setDateMillisSinceEpoch(it.key.rollingStartIntervalNumber.toLong() * ROLLING_WINDOW_LENGTH_MS)
|
||||
.setInfectiousness(infectiousness)
|
||||
.setReportType(reportType)
|
||||
.setScanInstances(it.subs.toScanInstances())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getExposureWindows(params: GetExposureWindowsParams) {
|
||||
Log.w(TAG, "Not yet implemented: getExposureWindows")
|
||||
params.callback.onResult(Status.INTERNAL_ERROR, emptyList())
|
||||
lifecycleScope.launchWhenStarted {
|
||||
val response = getExposureWindowsInternal(params.token ?: TOKEN_A)
|
||||
|
||||
ExposureDatabase.with(context) { database ->
|
||||
database.noteAppAction(packageName, "getExposureWindows", JSONObject().apply {
|
||||
put("request_token", params.token)
|
||||
put("response_size", response.size)
|
||||
}.toString())
|
||||
}
|
||||
|
||||
params.callback.onResult(Status.SUCCESS, response)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DailySummariesConfig.bucketFor(attenuation: Int): Int {
|
||||
if (attenuation < attenuationBucketThresholdDb[0]) return 0
|
||||
if (attenuation < attenuationBucketThresholdDb[1]) return 1
|
||||
if (attenuation < attenuationBucketThresholdDb[2]) return 2
|
||||
return 3
|
||||
}
|
||||
|
||||
private fun DailySummariesConfig.weightedDurationFor(attenuation: Int, seconds: Int): Double {
|
||||
return attenuationBucketWeights[bucketFor(attenuation)] * seconds
|
||||
}
|
||||
|
||||
private fun Collection<DailySummary.ExposureSummaryData>.sum(): DailySummary.ExposureSummaryData {
|
||||
return DailySummary.ExposureSummaryData(map { it.maximumScore }.maxOrNull()
|
||||
?: 0.0, sumByDouble { it.scoreSum }, sumByDouble { it.weightedDurationSum })
|
||||
}
|
||||
|
||||
override fun getDailySummaries(params: GetDailySummariesParams) {
|
||||
Log.w(TAG, "Not yet implemented: getDailySummaries")
|
||||
params.callback.onResult(Status.INTERNAL_ERROR, emptyList())
|
||||
lifecycleScope.launchWhenStarted {
|
||||
val response = getExposureWindowsInternal().groupBy { it.dateMillisSinceEpoch }.map {
|
||||
val map = arrayListOf<DailySummary.ExposureSummaryData>()
|
||||
for (i in 0 until ReportType.VALUES) {
|
||||
map[i] = DailySummary.ExposureSummaryData(0.0, 0.0, 0.0)
|
||||
}
|
||||
for (entry in it.value.groupBy { it.reportType }) {
|
||||
for (window in entry.value) {
|
||||
val weightedDuration = window.scanInstances.map { params.config.weightedDurationFor(it.typicalAttenuationDb, it.secondsSinceLastScan) }.sum()
|
||||
val score = (params.config.reportTypeWeights[window.reportType] ?: 1.0) *
|
||||
(params.config.infectiousnessWeights[window.infectiousness] ?: 1.0) *
|
||||
weightedDuration
|
||||
if (score >= params.config.minimumWindowScore) {
|
||||
map[entry.key] = DailySummary.ExposureSummaryData(max(map[entry.key].maximumScore, score), map[entry.key].scoreSum + score, map[entry.key].weightedDurationSum + weightedDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
DailySummary((it.key / (1000L * 60 * 60 * 24)).toInt(), map, map.sum())
|
||||
}
|
||||
|
||||
ExposureDatabase.with(context) { database ->
|
||||
database.noteAppAction(packageName, "getDailySummaries", JSONObject().apply {
|
||||
put("response_size", response.size)
|
||||
}.toString())
|
||||
}
|
||||
|
||||
params.callback.onResult(Status.SUCCESS, response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDiagnosisKeysDataMapping(params: SetDiagnosisKeysDataMappingParams) {
|
||||
Log.w(TAG, "Not yet implemented: setDiagnosisKeysDataMapping")
|
||||
params.callback.onResult(Status.INTERNAL_ERROR)
|
||||
lifecycleScope.launchWhenStarted {
|
||||
ExposureDatabase.with(context) { database ->
|
||||
database.storeConfiguration(packageName, TOKEN_A, params.mapping)
|
||||
database.noteAppAction(packageName, "setDiagnosisKeysDataMapping")
|
||||
}
|
||||
params.callback.onResult(Status.SUCCESS)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDiagnosisKeysDataMapping(params: GetDiagnosisKeysDataMappingParams) {
|
||||
Log.w(TAG, "Not yet implemented: getDiagnosisKeysDataMapping")
|
||||
params.callback.onResult(Status.INTERNAL_ERROR, null)
|
||||
lifecycleScope.launchWhenStarted {
|
||||
val mapping = ExposureDatabase.with(context) { database ->
|
||||
val triple = database.loadConfiguration(packageName, TOKEN_A)
|
||||
database.noteAppAction(packageName, "getDiagnosisKeysDataMapping")
|
||||
triple?.third
|
||||
}
|
||||
params.callback.onResult(Status.SUCCESS, mapping.orDefault())
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPackageConfiguration(params: GetPackageConfigurationParams) {
|
||||
Log.w(TAG, "Not yet implemented: getPackageConfiguration")
|
||||
lifecycleScope.launchWhenStarted {
|
||||
ExposureDatabase.with(context) { database ->
|
||||
database.noteAppAction(packageName, "getPackageConfiguration")
|
||||
}
|
||||
params.callback.onResult(Status.SUCCESS, PackageConfiguration.PackageConfigurationBuilder().setValues(Bundle.EMPTY).build())
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStatus(params: GetStatusParams) {
|
||||
Log.w(TAG, "Not yet implemented: getStatus")
|
||||
lifecycleScope.launchWhenStarted {
|
||||
ExposureDatabase.with(context) { database ->
|
||||
database.noteAppAction(packageName, "getStatus")
|
||||
}
|
||||
params.callback.onResult(Status.SUCCESS, ExposureNotificationStatus.setToFlags(setOf(ExposureNotificationStatus.UNKNOWN)))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||
|
@ -6,15 +6,12 @@
|
||||
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
|
||||
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
||||
import com.google.android.gms.nearby.exposurenotification.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
data class PlainExposure(val rpi: ByteArray, val aem: ByteArray, val timestamp: Long, val duration: Long, val rssi: Int)
|
||||
|
||||
data class MeasuredExposure(val timestamp: Long, val duration: Long, val rssi: Int, val txPower: Int, val key: TemporaryExposureKey) {
|
||||
data class MeasuredExposure(val timestamp: Long, val duration: Long, val rssi: Int, val txPower: Int, @CalibrationConfidence val confidence: Int, val key: TemporaryExposureKey) {
|
||||
val attenuation
|
||||
get() = txPower - (rssi + currentDeviceInfo.rssiCorrection)
|
||||
}
|
||||
@ -31,7 +28,7 @@ fun List<MeasuredExposure>.merge(): List<MergedExposure> {
|
||||
if (merged != null) {
|
||||
result.add(merged)
|
||||
}
|
||||
merged = MergedExposure(key, exposure.timestamp, listOf(MergedSubExposure(exposure.attenuation, exposure.duration)))
|
||||
merged = MergedExposure(key, exposure.timestamp, exposure.txPower, exposure.confidence, listOf(MergedSubExposure(exposure.attenuation, exposure.duration)))
|
||||
}
|
||||
if (merged.durationInMinutes > 30) {
|
||||
result.add(merged)
|
||||
@ -47,7 +44,7 @@ fun List<MeasuredExposure>.merge(): List<MergedExposure> {
|
||||
|
||||
internal data class MergedSubExposure(val attenuation: Int, val duration: Long)
|
||||
|
||||
data class MergedExposure internal constructor(val key: TemporaryExposureKey, val timestamp: Long, internal val subs: List<MergedSubExposure>) {
|
||||
data class MergedExposure internal constructor(val key: TemporaryExposureKey, val timestamp: Long, val txPower: Int, @CalibrationConfidence val confidence: Int, internal val subs: List<MergedSubExposure>) {
|
||||
@RiskLevel
|
||||
val transmissionRiskLevel: Int
|
||||
get() = key.transmissionRiskLevel
|
||||
|
@ -75,6 +75,7 @@ class ScannerService : LifecycleService() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
ForegroundServiceContext.completeForegroundService(this, intent, TAG)
|
||||
Log.d(TAG, "ScannerService.start: $intent")
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
startScanIfNeeded()
|
||||
return START_STICKY
|
||||
|
@ -18,12 +18,15 @@ class ServiceTrigger : BroadcastReceiver() {
|
||||
Log.d(TAG, "ServiceTrigger: $intent")
|
||||
val serviceContext = ForegroundServiceContext(context)
|
||||
if (ScannerService.isNeeded(context)) {
|
||||
Log.d(TAG, "Trigger ${ScannerService::class.java}")
|
||||
serviceContext.startService(Intent(context, ScannerService::class.java))
|
||||
}
|
||||
if (AdvertiserService.isNeeded(context)) {
|
||||
Log.d(TAG, "Trigger ${AdvertiserService::class.java}")
|
||||
serviceContext.startService(Intent(context, AdvertiserService::class.java))
|
||||
}
|
||||
if (CleanupService.isNeeded(context)) {
|
||||
Log.d(TAG, "Trigger ${CleanupService::class.java}")
|
||||
serviceContext.startService(Intent(context, CleanupService::class.java))
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
android {
|
||||
compileSdkVersion androidCompileSdk
|
||||
@ -21,6 +23,8 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-android.gradle'
|
||||
|
||||
dependencies {
|
||||
api project(':play-services-base')
|
||||
api project(':play-services-nearby-api')
|
||||
|
@ -17,52 +17,224 @@ import org.microg.gms.nearby.exposurenotification.Constants;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Interface for contact tracing APIs.
|
||||
*/
|
||||
@PublicApi
|
||||
public interface ExposureNotificationClient extends HasApiKey<Api.ApiOptions.NoOptions> {
|
||||
/**
|
||||
* Activity action which shows the exposure notification settings screen.
|
||||
*/
|
||||
String ACTION_EXPOSURE_NOTIFICATION_SETTINGS = Constants.ACTION_EXPOSURE_NOTIFICATION_SETTINGS;
|
||||
/**
|
||||
* Action which will be invoked via a BroadcastReceiver as a callback when matching has finished and no matches were found.
|
||||
* Also see {@link #EXTRA_TOKEN}, which will be included in this broadcast.
|
||||
*/
|
||||
String ACTION_EXPOSURE_NOT_FOUND = Constants.ACTION_EXPOSURE_NOT_FOUND;
|
||||
/**
|
||||
* Action which will be invoked via a BroadcastReceiver as a callback when the user has an updated exposure status.
|
||||
* Also see {@link #EXTRA_EXPOSURE_SUMMARY} and {@link #EXTRA_TOKEN}, which will be included in this broadcast.
|
||||
*/
|
||||
String ACTION_EXPOSURE_STATE_UPDATED = Constants.ACTION_EXPOSURE_STATE_UPDATED;
|
||||
/**
|
||||
* Action which will be invoked via a BroadcastReceiver when the user modifies the state of exposure notifications via the Google Settings page.
|
||||
* {@link #EXTRA_SERVICE_STATE} will be included as part of this broadcast.
|
||||
*/
|
||||
String ACTION_SERVICE_STATE_UPDATED = Constants.ACTION_SERVICE_STATE_UPDATED;
|
||||
/**
|
||||
* Extra attached to the {@link #ACTION_EXPOSURE_STATE_UPDATED} broadcast, giving a summary of the exposure details detected.
|
||||
* Also see {@link #getExposureSummary(String)}.
|
||||
*
|
||||
* @deprecated {@link ExposureSummary} is no longer provided when using the {@link #getExposureWindows()} API. Instead, use {@link #getDailySummaries(DailySummariesConfig)}.
|
||||
*/
|
||||
@Deprecated
|
||||
String EXTRA_EXPOSURE_SUMMARY = Constants.EXTRA_EXPOSURE_SUMMARY;
|
||||
/**
|
||||
* Boolean extra attached to the {@link #ACTION_SERVICE_STATE_UPDATED} broadcast signifying whether the service is enabled or disabled.
|
||||
*/
|
||||
String EXTRA_SERVICE_STATE = Constants.EXTRA_SERVICE_STATE;
|
||||
/**
|
||||
* Extra attached to the {@link #ACTION_EXPOSURE_STATE_UPDATED} broadcast, providing the token associated with the {@link #provideDiagnosisKeys(DiagnosisKeyFileProvider)} request.
|
||||
*
|
||||
* @deprecated Tokens are no longer used. Instead, prefer using the tokenless versions of {@link #provideDiagnosisKeys(DiagnosisKeyFileProvider)}, {@link #getExposureWindows()}, and {@link #getDailySummaries(DailySummariesConfig)}.
|
||||
*/
|
||||
@Deprecated
|
||||
String EXTRA_TOKEN = Constants.EXTRA_TOKEN;
|
||||
/**
|
||||
* Token to be used with ExposureWindows API. Must be used with {@link #provideDiagnosisKeys(DiagnosisKeyFileProvider) }request when later using {@link #getExposureWindows()}.
|
||||
*
|
||||
* @deprecated Tokens are no longer used. Instead, prefer using the tokenless versions of {@link #provideDiagnosisKeys(DiagnosisKeyFileProvider)}, {@link #getExposureWindows()}, and {@link #getDailySummaries(DailySummariesConfig)}.
|
||||
*/
|
||||
@Deprecated
|
||||
String TOKEN_A = Constants.TOKEN_A;
|
||||
|
||||
Task<Long> getVersion();
|
||||
/**
|
||||
* Checks whether the device supports Exposure Notification BLE scanning without requiring location to be enabled first.
|
||||
*/
|
||||
boolean deviceSupportsLocationlessScanning();
|
||||
|
||||
/**
|
||||
* Gets {@link CalibrationConfidence} of the current device.
|
||||
*/
|
||||
Task<Integer> getCalibrationConfidence();
|
||||
|
||||
Task<Void> start();
|
||||
/**
|
||||
* Retrieves the per-day exposure summaries associated with the provided configuration.
|
||||
* <p>
|
||||
* A valid configuration must be provided to compute the summaries.
|
||||
*/
|
||||
Task<List<DailySummary>> getDailySummaries(DailySummariesConfig dailySummariesConfig);
|
||||
|
||||
Task<Void> stop();
|
||||
|
||||
Task<Boolean> isEnabled();
|
||||
|
||||
Task<List<TemporaryExposureKey>> getTemporaryExposureKeyHistory();
|
||||
|
||||
@Deprecated
|
||||
Task<Void> provideDiagnosisKeys(List<File> keys, ExposureConfiguration configuration, String token);
|
||||
@Deprecated
|
||||
Task<Void> provideDiagnosisKeys(List<File> keys);
|
||||
Task<Void> provideDiagnosisKeys(DiagnosisKeyFileProvider provider);
|
||||
|
||||
@Deprecated
|
||||
Task<ExposureSummary> getExposureSummary(String token);
|
||||
/**
|
||||
* Retrieves the current {@link DiagnosisKeysDataMapping}.
|
||||
*/
|
||||
Task<DiagnosisKeysDataMapping> getDiagnosisKeysDataMapping();
|
||||
|
||||
/**
|
||||
* Gets detailed information about exposures that have occurred related to the provided token, which should match the token provided in {@link #provideDiagnosisKeys(DiagnosisKeyFileProvider)}.
|
||||
* <p>
|
||||
* When multiple ExposureInformation objects are returned, they can be:
|
||||
* <ul>
|
||||
* <li>Multiple encounters with a single diagnosis key.</li>
|
||||
* <li>Multiple encounters with the same device across key rotation boundaries.</li>
|
||||
* <li>Encounters with multiple devices.</li>
|
||||
* </ul>
|
||||
* Records of calls to this method will be retained and viewable by the user.
|
||||
*
|
||||
* @deprecated When using the ExposureWindow API, use {@link #getExposureWindows()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
Task<List<ExposureInformation>> getExposureInformation(String token);
|
||||
|
||||
/**
|
||||
* Gets a summary of the exposure calculation for the token, which should match the token provided in {@link #provideDiagnosisKeys(DiagnosisKeyFileProvider)}.
|
||||
*
|
||||
* @deprecated When using the ExposureWindow API, use {@link #getDailySummaries(DailySummariesConfig)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
Task<List<ExposureWindow>> getExposureWindows(String token);
|
||||
Task<ExposureSummary> getExposureSummary(String token);
|
||||
|
||||
/**
|
||||
* Retrieves the list of exposure windows corresponding to the TEKs given to {@link #provideDiagnosisKeys(DiagnosisKeyFileProvider)}.
|
||||
* <p>
|
||||
* Long exposures to one TEK are split into windows of up to 30 minutes of scans, so a given TEK may lead to several exposure windows if beacon sightings for it spanned more than 30 minutes. The link between them (the fact that they all correspond to the same TEK) is lost because those windows are shuffled before being returned and the underlying TEKs are not exposed by the API.
|
||||
*/
|
||||
Task<List<ExposureWindow>> getExposureWindows();
|
||||
|
||||
Task<List<DailySummary>> getDailySummaries(DailySummariesConfig config);
|
||||
/**
|
||||
* Retrieves the list of exposure windows corresponding to the TEKs given to provideKeys with token=TOKEN_A.
|
||||
* <p>
|
||||
* Long exposures to one TEK are split into windows of up to 30 minutes of scans, so a given TEK may lead to several exposure windows if beacon sightings for it spanned more than 30 minutes. The link between them (the fact that they all correspond to the same TEK) is lost because those windows are shuffled before being returned and the underlying TEKs are not exposed by the API.
|
||||
* <p>
|
||||
* The provided token must be TOKEN_A.
|
||||
*
|
||||
* @deprecated Tokens are no longer used. Instead, prefer using the tokenless version of {@link #getExposureWindows()}.
|
||||
*/
|
||||
@Deprecated
|
||||
Task<List<ExposureWindow>> getExposureWindows(String token);
|
||||
|
||||
Task<Void> setDiagnosisKeysDataMapping(DiagnosisKeysDataMapping mapping);
|
||||
/**
|
||||
* Retrieves the associated {@link PackageConfiguration} for the calling package. Note that this value can be null if no configuration was when starting.
|
||||
*/
|
||||
Task<PackageConfiguration> getPackageConfiguration();
|
||||
|
||||
Task<DiagnosisKeysDataMapping> getDiagnosisKeysDataMapping();
|
||||
/**
|
||||
* Gets the current Exposure Notification status.
|
||||
*/
|
||||
Task<Set<ExposureNotificationStatus>> getStatus();
|
||||
|
||||
/**
|
||||
* Gets {@link TemporaryExposureKey} history to be stored on the server.
|
||||
* <p>
|
||||
* This should only be done after proper verification is performed on the client side that the user is diagnosed positive. Each key returned will have an unknown transmission risk level, clients should choose an appropriate risk level for these keys before uploading them to the server.
|
||||
* <p>
|
||||
* The keys provided here will only be from previous days; keys will not be released until after they are no longer an active exposure key.
|
||||
* <p>
|
||||
* This shows a user permission dialog for sharing and uploading data to the server.
|
||||
*/
|
||||
Task<List<TemporaryExposureKey>> getTemporaryExposureKeyHistory();
|
||||
|
||||
/**
|
||||
* Gets the current Exposure Notification version.
|
||||
*/
|
||||
Task<Long> getVersion();
|
||||
|
||||
/**
|
||||
* Indicates whether contact tracing is currently running for the requesting app.
|
||||
*/
|
||||
Task<Boolean> isEnabled();
|
||||
|
||||
/**
|
||||
* Provides a list of diagnosis key files for exposure checking. The files are to be synced from the server. Old diagnosis keys (for example older than 14 days), will be ignored.
|
||||
* <p>
|
||||
* Diagnosis keys will be stored and matching will be performed in the near future, after which you’ll receive a broadcast with the {@link #ACTION_EXPOSURE_STATE_UPDATED} action. If no matches are found, you'll receive an {@link #ACTION_EXPOSURE_NOT_FOUND} action.
|
||||
* <p>
|
||||
* The diagnosis key files must be signed appropriately. Results from this request can also be queried at any time via {@link #getExposureWindows()} and {@link #getDailySummaries(DailySummariesConfig)}.
|
||||
* <p>
|
||||
* After the result Task has returned, keyFiles can be deleted.
|
||||
* <p>
|
||||
* Results remain for 14 days.
|
||||
*
|
||||
* @deprecated Prefer the {@link DiagnosisKeyFileProvider} version of this method instead, which scales better when a large number of files are passed at the same time.
|
||||
*/
|
||||
@Deprecated
|
||||
Task<Void> provideDiagnosisKeys(List<File> keyFiles);
|
||||
|
||||
/**
|
||||
* Provides diagnosis key files for exposure checking. The files are to be synced from the server. Old diagnosis keys (for example older than 14 days), will be ignored.
|
||||
* <p>
|
||||
* Diagnosis keys will be stored and matching will be performed in the near future, after which you’ll receive a broadcast with the {@link #ACTION_EXPOSURE_STATE_UPDATED} action. If no matches are found, you'll receive an {@link #ACTION_EXPOSURE_NOT_FOUND} action.
|
||||
* <p>
|
||||
* The diagnosis key files must be signed appropriately. Results from this request can also be queried at any time via {@link #getExposureWindows()} and {@link #getDailySummaries(DailySummariesConfig)}.
|
||||
* <p>
|
||||
* After the result Task has returned, files can be deleted.
|
||||
* <p>
|
||||
* Results remain for 14 days.
|
||||
*/
|
||||
Task<Void> provideDiagnosisKeys(DiagnosisKeyFileProvider provider);
|
||||
|
||||
/**
|
||||
* Provides a list of diagnosis key files for exposure checking. The files are to be synced from the server. Old diagnosis keys (for example older than 14 days), will be ignored.
|
||||
* <p>
|
||||
* Diagnosis keys will be stored and matching will be performed in the near future, after which you’ll receive a broadcast with the {@link #ACTION_EXPOSURE_STATE_UPDATED} action. If no matches are found, you'll receive an {@link #ACTION_EXPOSURE_NOT_FOUND} action.
|
||||
* <p>
|
||||
* The diagnosis key files must be signed appropriately. Exposure configuration options can be provided to tune the matching algorithm. A unique token for this batch can also be provided, which will be used to associate the matches with this request as part of {@link #getExposureSummary(String)} and {@link #getExposureInformation(String)}. Alternatively, the same token can be passed in multiple times to concatenate results.
|
||||
* <p>
|
||||
* After the result Task has returned, keyFiles can be deleted.
|
||||
* <p>
|
||||
* Results for a given token remain for 14 days.
|
||||
*
|
||||
* @deprecated Tokens and configuration are no longer used. Instead, prefer using the tokenless, configuration-less version of {@link #provideDiagnosisKeys(DiagnosisKeyFileProvider)}.
|
||||
*/
|
||||
@Deprecated
|
||||
Task<Void> provideDiagnosisKeys(List<File> keys, ExposureConfiguration configuration, String token);
|
||||
|
||||
/**
|
||||
* Sets the diagnosis keys data mapping if it wasn't already changed recently.
|
||||
* <p>
|
||||
* If called twice within 7 days, the second call will have no effect and will raise an exception with status code FAILED_RATE_LIMITED.
|
||||
*/
|
||||
Task<Void> setDiagnosisKeysDataMapping(DiagnosisKeysDataMapping diagnosisKeysMetadataMapping);
|
||||
|
||||
/**
|
||||
* Starts BLE broadcasts and scanning based on the defined protocol.
|
||||
* <p>
|
||||
* If not previously started, this shows a user dialog for consent to start exposure detection and get permission.
|
||||
* <p>
|
||||
* Callbacks regarding exposure status will be provided via a BroadcastReceiver. Clients should register a receiver in their AndroidManifest which can handle the following action:
|
||||
* <ul>
|
||||
* <li>{@code com.google.android.gms.exposurenotification.ACTION_EXPOSURE_STATE_UPDATED}</li>
|
||||
* </ul>
|
||||
* This receiver should also be guarded by the {@code com.google.android.gms.nearby.exposurenotification.EXPOSURE_CALLBACK} permission so that other apps are not able to fake this broadcast.
|
||||
*/
|
||||
Task<Void> start();
|
||||
|
||||
/**
|
||||
* Disables advertising and scanning. Contents of the database and keys will remain.
|
||||
* <p>
|
||||
* If the client app has been uninstalled by the user, this will be automatically invoked and the database and keys will be wiped from the device.
|
||||
*/
|
||||
Task<Void> stop();
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ import com.google.android.gms.nearby.exposurenotification.internal.GetDiagnosisK
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureInformationParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureSummaryParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureWindowsParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetPackageConfigurationParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetStatusParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetTemporaryExposureKeyHistoryParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetVersionParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.INearbyExposureNotificationService;
|
||||
@ -91,4 +93,12 @@ public class ExposureNotificationApiClient extends GmsClient<INearbyExposureNoti
|
||||
public void getDiagnosisKeysDataMapping(GetDiagnosisKeysDataMappingParams params) throws RemoteException {
|
||||
getServiceInterface().getDiagnosisKeysDataMapping(params);
|
||||
}
|
||||
|
||||
public void getPackageConfiguration(GetPackageConfigurationParams params) throws RemoteException {
|
||||
getServiceInterface().getPackageConfiguration(params);
|
||||
}
|
||||
|
||||
public void getStatus(GetStatusParams params) throws RemoteException {
|
||||
getServiceInterface().getStatus(params);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ package org.microg.gms.nearby;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.common.api.Api;
|
||||
@ -22,8 +23,10 @@ import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMappi
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration;
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureInformation;
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient;
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatus;
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureSummary;
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureWindow;
|
||||
import com.google.android.gms.nearby.exposurenotification.PackageConfiguration;
|
||||
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetCalibrationConfidenceParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetDailySummariesParams;
|
||||
@ -31,16 +34,20 @@ import com.google.android.gms.nearby.exposurenotification.internal.GetDiagnosisK
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureInformationParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureSummaryParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureWindowsParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetPackageConfigurationParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetStatusParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetTemporaryExposureKeyHistoryParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetVersionParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.IBooleanCallback;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.IDailySummaryListCallback;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.IDiagnosisKeyFileSupplier;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.IDiagnosisKeysDataMappingCallback;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.IExposureInformationListCallback;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.IExposureSummaryCallback;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.IExposureWindowListCallback;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.IIntCallback;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.ILongCallback;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.IPackageConfigurationCallback;
|
||||
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;
|
||||
@ -56,6 +63,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class ExposureNotificationClientImpl extends GoogleApi<Api.ApiOptions.NoOptions> implements ExposureNotificationClient {
|
||||
private static final Api<Api.ApiOptions.NoOptions> API = new Api<>((options, context, looper, clientSettings, callbacks, connectionFailedListener) -> new ExposureNotificationApiClient(context, callbacks, connectionFailedListener));
|
||||
@ -239,8 +247,42 @@ public class ExposureNotificationClientImpl extends GoogleApi<Api.ApiOptions.NoO
|
||||
|
||||
@Override
|
||||
public Task<Void> provideDiagnosisKeys(DiagnosisKeyFileProvider provider) {
|
||||
// NOTE: This will probably need to be modified according to how the provider is used
|
||||
return provideDiagnosisKeys(provider.getFiles());
|
||||
return scheduleTask((PendingGoogleApiCall<Void, ExposureNotificationApiClient>) (client, completionSource) -> {
|
||||
ProvideDiagnosisKeysParams params = new ProvideDiagnosisKeysParams(new IStatusCallback.Stub() {
|
||||
@Override
|
||||
public void onResult(Status status) {
|
||||
if (status == Status.SUCCESS) {
|
||||
completionSource.setResult(null);
|
||||
} else {
|
||||
completionSource.setException(new ApiException(status));
|
||||
}
|
||||
}
|
||||
}, new IDiagnosisKeyFileSupplier.Stub() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return provider.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor next() {
|
||||
try {
|
||||
return ParcelFileDescriptor.open(provider.next(), ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
try {
|
||||
client.provideDiagnosisKeys(params);
|
||||
} catch (Exception e) {
|
||||
completionSource.setException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -312,7 +354,7 @@ public class ExposureNotificationClientImpl extends GoogleApi<Api.ApiOptions.NoO
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<List<DailySummary>> getDailySummaries(DailySummariesConfig config) {
|
||||
public Task<List<DailySummary>> getDailySummaries(DailySummariesConfig dailySummariesConfig) {
|
||||
return scheduleTask((PendingGoogleApiCall<List<DailySummary>, ExposureNotificationApiClient>) (client, completionSource) -> {
|
||||
GetDailySummariesParams params = new GetDailySummariesParams(new IDailySummaryListCallback.Stub() {
|
||||
@Override
|
||||
@ -323,7 +365,7 @@ public class ExposureNotificationClientImpl extends GoogleApi<Api.ApiOptions.NoO
|
||||
completionSource.setException(new ApiException(status));
|
||||
}
|
||||
}
|
||||
}, config);
|
||||
}, dailySummariesConfig);
|
||||
try {
|
||||
client.getDailySummaries(params);
|
||||
} catch (Exception e) {
|
||||
@ -333,7 +375,7 @@ public class ExposureNotificationClientImpl extends GoogleApi<Api.ApiOptions.NoO
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<Void> setDiagnosisKeysDataMapping(DiagnosisKeysDataMapping mapping) {
|
||||
public Task<Void> setDiagnosisKeysDataMapping(DiagnosisKeysDataMapping diagnosisKeysMetadataMapping) {
|
||||
return scheduleTask((PendingGoogleApiCall<Void, ExposureNotificationApiClient>) (client, completionSource) -> {
|
||||
SetDiagnosisKeysDataMappingParams params = new SetDiagnosisKeysDataMappingParams(new IStatusCallback.Stub() {
|
||||
@Override
|
||||
@ -344,7 +386,7 @@ public class ExposureNotificationClientImpl extends GoogleApi<Api.ApiOptions.NoO
|
||||
completionSource.setException(new ApiException(status));
|
||||
}
|
||||
}
|
||||
}, mapping);
|
||||
}, diagnosisKeysMetadataMapping);
|
||||
try {
|
||||
client.setDiagnosisKeysDataMapping(params);
|
||||
} catch (Exception e) {
|
||||
@ -374,6 +416,53 @@ public class ExposureNotificationClientImpl extends GoogleApi<Api.ApiOptions.NoO
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<PackageConfiguration> getPackageConfiguration() {
|
||||
return scheduleTask((PendingGoogleApiCall<PackageConfiguration, ExposureNotificationApiClient>) (client, completionSource) -> {
|
||||
GetPackageConfigurationParams params = new GetPackageConfigurationParams(new IPackageConfigurationCallback.Stub() {
|
||||
@Override
|
||||
public void onResult(Status status, PackageConfiguration result) {
|
||||
if (status == Status.SUCCESS) {
|
||||
completionSource.setResult(result);
|
||||
} else {
|
||||
completionSource.setException(new ApiException(status));
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
client.getPackageConfiguration(params);
|
||||
} catch (Exception e) {
|
||||
completionSource.setException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<Set<ExposureNotificationStatus>> getStatus() {
|
||||
return scheduleTask((PendingGoogleApiCall<Set<ExposureNotificationStatus>, ExposureNotificationApiClient>) (client, completionSource) -> {
|
||||
GetStatusParams params = new GetStatusParams(new ILongCallback.Stub() {
|
||||
@Override
|
||||
public void onResult(Status status, long flags) {
|
||||
if (status == Status.SUCCESS) {
|
||||
completionSource.setResult(ExposureNotificationStatus.flagsToSet(flags));
|
||||
} else {
|
||||
completionSource.setException(new ApiException(status));
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
client.getStatus(params);
|
||||
} catch (Exception e) {
|
||||
completionSource.setException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deviceSupportsLocationlessScanning() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiKey<Api.ApiOptions.NoOptions> getApiKey() {
|
||||
return null;
|
||||
|
@ -4,6 +4,8 @@
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
android {
|
||||
compileSdkVersion androidCompileSdk
|
||||
@ -24,3 +26,5 @@ android {
|
||||
dependencies {
|
||||
api project(':play-services-basement')
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-android.gradle'
|
||||
|
Loading…
Reference in New Issue
Block a user