EN API: Update

This commit is contained in:
Marvin W 2020-11-18 17:06:29 +01:00
parent 8006b2c8a9
commit 4c2ef04364
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
44 changed files with 1085 additions and 75 deletions

View 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
}
}
}

View 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
}
}
}

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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')

View File

@ -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;

View File

@ -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'

View File

@ -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')

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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++);
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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'

View File

@ -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'

View File

@ -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>" }
}

View File

@ -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() {

View File

@ -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"

View File

@ -56,4 +56,6 @@ The date, duration, and signal strength associated with an exposure will be shar
Your identity or test result won&apos;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>

View File

@ -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'

View File

@ -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>

View File

@ -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()

View File

@ -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 {

View File

@ -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)

View File

@ -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"

View File

@ -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)
)
})
}

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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))
}
}

View File

@ -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')

View File

@ -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 youll 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 youll 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 youll 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();
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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'