mirror of
https://github.com/TeamVanced/VancedMicroG
synced 2025-01-28 11:47:32 +01:00
EN: Add latest API details, improve performance
This commit is contained in:
parent
cab09cb238
commit
6afcca0396
@ -1,6 +1,12 @@
|
|||||||
package com.google.android.gms.common.api;
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||||
|
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||||
|
* See https://developers.google.com/readme/policies for details.
|
||||||
|
*/
|
||||||
|
|
||||||
import com.google.android.gms.common.api.Status;
|
package com.google.android.gms.common.api;
|
||||||
|
|
||||||
import org.microg.gms.common.PublicApi;
|
import org.microg.gms.common.PublicApi;
|
||||||
|
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||||
|
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||||
|
* See https://developers.google.com/readme/policies for details.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.google.android.gms.common.api;
|
package com.google.android.gms.common.api;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
@ -23,6 +23,5 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(':play-services-basement')
|
api project(':play-services-basement')
|
||||||
|
|
||||||
api project(':play-services-base-api')
|
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 DailySummary;
|
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification;
|
||||||
|
|
||||||
|
parcelable DiagnosisKeysDataMapping;
|
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification;
|
||||||
|
|
||||||
|
parcelable ExposureWindow;
|
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
parcelable GetCalibrationConfidenceParams;
|
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
parcelable GetDailySummariesParams;
|
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
parcelable GetDiagnosisKeysDataMappingParams;
|
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
parcelable GetExposureWindowsParams;
|
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
parcelable GetVersionParams;
|
@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
import com.google.android.gms.common.api.Status;
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.DailySummary;
|
||||||
|
|
||||||
|
interface IDailySummaryListCallback {
|
||||||
|
void onResult(in Status status, in List<DailySummary> result);
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
import com.google.android.gms.common.api.Status;
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping;
|
||||||
|
|
||||||
|
interface IDiagnosisKeysDataMappingCallback {
|
||||||
|
void onResult(in Status status, in DiagnosisKeysDataMapping result);
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
import com.google.android.gms.common.api.Status;
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.ExposureWindow;
|
||||||
|
|
||||||
|
interface IExposureWindowListCallback {
|
||||||
|
void onResult(in Status status, in List<ExposureWindow> result);
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
import com.google.android.gms.common.api.Status;
|
||||||
|
|
||||||
|
interface IIntCallback {
|
||||||
|
void onResult(in Status status, int result);
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
import com.google.android.gms.common.api.Status;
|
||||||
|
|
||||||
|
interface ILongCallback {
|
||||||
|
void onResult(in Status status, long result);
|
||||||
|
}
|
@ -12,6 +12,12 @@ import com.google.android.gms.nearby.exposurenotification.internal.GetTemporaryE
|
|||||||
import com.google.android.gms.nearby.exposurenotification.internal.ProvideDiagnosisKeysParams;
|
import com.google.android.gms.nearby.exposurenotification.internal.ProvideDiagnosisKeysParams;
|
||||||
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureSummaryParams;
|
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureSummaryParams;
|
||||||
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureInformationParams;
|
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureInformationParams;
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureWindowsParams;
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.internal.GetVersionParams;
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.internal.GetCalibrationConfidenceParams;
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.internal.GetDailySummariesParams;
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.internal.SetDiagnosisKeysDataMappingParams;
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.internal.GetDiagnosisKeysDataMappingParams;
|
||||||
|
|
||||||
interface INearbyExposureNotificationService{
|
interface INearbyExposureNotificationService{
|
||||||
void start(in StartParams params) = 0;
|
void start(in StartParams params) = 0;
|
||||||
@ -22,4 +28,11 @@ interface INearbyExposureNotificationService{
|
|||||||
|
|
||||||
void getExposureSummary(in GetExposureSummaryParams params) = 6;
|
void getExposureSummary(in GetExposureSummaryParams params) = 6;
|
||||||
void getExposureInformation(in GetExposureInformationParams params) = 7;
|
void getExposureInformation(in GetExposureInformationParams params) = 7;
|
||||||
|
|
||||||
|
void getExposureWindows(in GetExposureWindowsParams params) = 12;
|
||||||
|
void getVersion(in GetVersionParams params) = 13;
|
||||||
|
void getCalibrationConfidence(in GetCalibrationConfidenceParams params) = 14;
|
||||||
|
void getDailySummaries(in GetDailySummariesParams params) = 15;
|
||||||
|
void setDiagnosisKeysDataMapping(in SetDiagnosisKeysDataMappingParams params) = 16;
|
||||||
|
void getDiagnosisKeysDataMapping(in GetDiagnosisKeysDataMappingParams params) = 17;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
parcelable SetDiagnosisKeysDataMappingParams;
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||||
|
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||||
|
* See https://developers.google.com/readme/policies for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification;
|
||||||
|
|
||||||
|
import org.microg.gms.common.PublicApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calibration confidence defined for an {@link ExposureWindow}.
|
||||||
|
*/
|
||||||
|
@PublicApi
|
||||||
|
public @interface CalibrationConfidence {
|
||||||
|
/**
|
||||||
|
* No calibration data, using fleet-wide as default options.
|
||||||
|
*/
|
||||||
|
int LOWEST = 0;
|
||||||
|
/**
|
||||||
|
* Using average calibration over models from manufacturer.
|
||||||
|
*/
|
||||||
|
int LOW = 1;
|
||||||
|
/**
|
||||||
|
* Using single-antenna orientation for a similar model.
|
||||||
|
*/
|
||||||
|
int MEDIUM = 2;
|
||||||
|
/**
|
||||||
|
* Using significant calibration data for this model.
|
||||||
|
*/
|
||||||
|
int HIGH = 3;
|
||||||
|
|
||||||
|
@PublicApi(exclude = true)
|
||||||
|
int VALUES = 4;
|
||||||
|
}
|
@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||||
|
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||||
|
* See https://developers.google.com/readme/policies for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification;
|
||||||
|
|
||||||
|
import org.microg.gms.common.PublicApi;
|
||||||
|
import org.microg.safeparcel.AutoSafeParcelable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration of per-day summary of exposures.
|
||||||
|
* <p>
|
||||||
|
* During summarization the following are computed for each ExposureWindows:
|
||||||
|
* <ul>
|
||||||
|
* <li>a weighted duration, computed as
|
||||||
|
* {@code ( immediateDurationSeconds * immediateDurationWeight ) + ( nearDurationSeconds * nearDurationWeight ) + ( mediumDurationSeconds * mediumDurationWeight ) + ( otherDurationSeconds * otherDurationWeight )}</li>
|
||||||
|
* <li>a score, computed as
|
||||||
|
* {@code reportTypeWeights[Tek.reportType] * infectiousnessWeights[infectiousness] * weightedDuration}
|
||||||
|
* where infectiousness and reportType are set based on the ExposureWindow's diagnosis key and the DiagnosisKeysDataMapping</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* The {@link ExposureWindow}s are then filtered, removing those with score lower than {@link #getMinimumWindowScore()}.
|
||||||
|
* <p>
|
||||||
|
* Scores and weighted durations of the {@link ExposureWindow}s that pass the {@link #getMinimumWindowScore()} are then aggregated over a day to compute the maximum and cumulative scores and duration:
|
||||||
|
* <ul>
|
||||||
|
* <li>sumScore = sum(score of ExposureWindows)</li>
|
||||||
|
* <li>maxScore = max(score of ExposureWindows)</li>
|
||||||
|
* <li>weightedDurationSum = sum(weighted duration of ExposureWindow)</li>
|
||||||
|
* </ul>
|
||||||
|
* Note that when the weights are typically around 100% (1.0), both the scores and the weightedDurationSum can be considered as being expressed in seconds. For example, 15 minutes of exposure with all weights equal to 1.0 would be 60 * 15 = 900 (seconds).
|
||||||
|
*/
|
||||||
|
@PublicApi
|
||||||
|
public class DailySummariesConfig extends AutoSafeParcelable {
|
||||||
|
@Field(1)
|
||||||
|
private List<Double> reportTypeWeights;
|
||||||
|
@Field(2)
|
||||||
|
private List<Double> infectiousnessWeights;
|
||||||
|
@Field(3)
|
||||||
|
private List<Integer> attenuationBucketThresholdDb;
|
||||||
|
@Field(4)
|
||||||
|
private List<Double> attenuationBucketWeights;
|
||||||
|
@Field(5)
|
||||||
|
private int daysSinceExposureThreshold;
|
||||||
|
@Field(6)
|
||||||
|
private double minimumWindowScore;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof DailySummariesConfig)) return false;
|
||||||
|
|
||||||
|
DailySummariesConfig that = (DailySummariesConfig) o;
|
||||||
|
|
||||||
|
if (daysSinceExposureThreshold != that.daysSinceExposureThreshold) return false;
|
||||||
|
if (Double.compare(that.minimumWindowScore, minimumWindowScore) != 0) return false;
|
||||||
|
if (reportTypeWeights != null ? !reportTypeWeights.equals(that.reportTypeWeights) : that.reportTypeWeights != null)
|
||||||
|
return false;
|
||||||
|
if (infectiousnessWeights != null ? !infectiousnessWeights.equals(that.infectiousnessWeights) : that.infectiousnessWeights != null)
|
||||||
|
return false;
|
||||||
|
if (attenuationBucketThresholdDb != null ? !attenuationBucketThresholdDb.equals(that.attenuationBucketThresholdDb) : that.attenuationBucketThresholdDb != null)
|
||||||
|
return false;
|
||||||
|
return attenuationBucketWeights != null ? attenuationBucketWeights.equals(that.attenuationBucketWeights) : that.attenuationBucketWeights == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thresholds defining the BLE attenuation buckets edges.
|
||||||
|
* <p>
|
||||||
|
* This list must have 3 elements: the immediate, near, and medium thresholds. See attenuationBucketWeights for more information.
|
||||||
|
* <p>
|
||||||
|
* These elements must be between 0 and 255 and come in increasing order.
|
||||||
|
*/
|
||||||
|
public List<Integer> getAttenuationBucketThresholdDb() {
|
||||||
|
return attenuationBucketThresholdDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scoring weights to associate with ScanInstances depending on the attenuation bucket in which their typicalAttenuationDb falls.
|
||||||
|
* <p>
|
||||||
|
* This list must have 4 elements, corresponding to the weights for the 4 buckets.
|
||||||
|
* <ul>
|
||||||
|
* <li>immediate bucket: -infinity < attenuation <= immediate threshold</li>
|
||||||
|
* <li>near bucket: immediate threshold < attenuation <= near threshold</li>
|
||||||
|
* <li>medium bucket: near threshold < attenuation <= medium threshold</li>
|
||||||
|
* <li>other bucket: medium threshold < attenuation < +infinity</li>
|
||||||
|
* </ul>
|
||||||
|
* Each element must be between 0 and 2.5.
|
||||||
|
*/
|
||||||
|
public List<Double> getAttenuationBucketWeights() {
|
||||||
|
return attenuationBucketWeights;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserved for future use, behavior will be changed in future revisions. No value should be set, or else 0 should be used.
|
||||||
|
*/
|
||||||
|
public int getDaysSinceExposureThreshold() {
|
||||||
|
return daysSinceExposureThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scoring weights to associate with exposures with different Infectiousness.
|
||||||
|
* <p>
|
||||||
|
* This map can include weights for the following Infectiousness values:
|
||||||
|
* <ul>
|
||||||
|
* <li>STANDARD</li>
|
||||||
|
* <li>HIGH</li>
|
||||||
|
* </ul>
|
||||||
|
* Each element must be between 0 and 2.5.
|
||||||
|
*/
|
||||||
|
public Map<Integer, Double> getInfectiousnessWeights() {
|
||||||
|
HashMap<Integer, Double> map = new HashMap<>();
|
||||||
|
for (int i = 0; i < infectiousnessWeights.size(); i++) {
|
||||||
|
map.put(i, infectiousnessWeights.get(i));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum score that {@link ExposureWindow}s must reach in order to be included in the {@link DailySummary.ExposureSummaryData}.
|
||||||
|
* <p>
|
||||||
|
* Use 0 to consider all {@link ExposureWindow}s (recommended).
|
||||||
|
*/
|
||||||
|
public double getMinimumWindowScore() {
|
||||||
|
return minimumWindowScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scoring weights to associate with exposures with different ReportTypes.
|
||||||
|
* <p>
|
||||||
|
* This map can include weights for the following ReportTypes:
|
||||||
|
* <ul>
|
||||||
|
* <li>CONFIRMED_TEST</li>
|
||||||
|
* <li>CONFIRMED_CLINICAL_DIAGNOSIS</li>
|
||||||
|
* <li>SELF_REPORT</li>
|
||||||
|
* <li>RECURSIVE (reserved for future use)</li>
|
||||||
|
* </ul>
|
||||||
|
* Each element must be between 0 and 2.5.
|
||||||
|
*/
|
||||||
|
public Map<Integer, Double> getReportTypeWeights() {
|
||||||
|
HashMap<Integer, Double> map = new HashMap<>();
|
||||||
|
for (int i = 0; i < reportTypeWeights.size(); i++) {
|
||||||
|
map.put(i, reportTypeWeights.get(i));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result;
|
||||||
|
long temp;
|
||||||
|
result = reportTypeWeights != null ? reportTypeWeights.hashCode() : 0;
|
||||||
|
result = 31 * result + (infectiousnessWeights != null ? infectiousnessWeights.hashCode() : 0);
|
||||||
|
result = 31 * result + (attenuationBucketThresholdDb != null ? attenuationBucketThresholdDb.hashCode() : 0);
|
||||||
|
result = 31 * result + (attenuationBucketWeights != null ? attenuationBucketWeights.hashCode() : 0);
|
||||||
|
result = 31 * result + daysSinceExposureThreshold;
|
||||||
|
temp = Double.doubleToLongBits(minimumWindowScore);
|
||||||
|
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for {@link DailySummariesConfig}.
|
||||||
|
*/
|
||||||
|
public static class DailySummariesConfigBuilder {
|
||||||
|
private Double[] reportTypeWeights = new Double[ReportType.VALUES];
|
||||||
|
private Double[] infectiousnessWeights = new Double[Infectiousness.VALUES];
|
||||||
|
private List<Integer> attenuationBucketThresholdDb;
|
||||||
|
private List<Double> attenuationBucketWeights;
|
||||||
|
private int daysSinceExposureThreshold;
|
||||||
|
private double minimumWindowScore;
|
||||||
|
|
||||||
|
public DailySummariesConfigBuilder() {
|
||||||
|
Arrays.fill(reportTypeWeights, 0.0);
|
||||||
|
Arrays.fill(infectiousnessWeights, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DailySummariesConfig build() {
|
||||||
|
if (attenuationBucketThresholdDb == null)
|
||||||
|
throw new IllegalStateException("Must set attenuationBucketThresholdDb");
|
||||||
|
if (attenuationBucketWeights == null)
|
||||||
|
throw new IllegalStateException("Must set attenuationBucketWeights");
|
||||||
|
DailySummariesConfig config = new DailySummariesConfig();
|
||||||
|
config.reportTypeWeights = Arrays.asList(reportTypeWeights);
|
||||||
|
config.infectiousnessWeights = Arrays.asList(infectiousnessWeights);
|
||||||
|
config.attenuationBucketThresholdDb = attenuationBucketThresholdDb;
|
||||||
|
config.attenuationBucketWeights = attenuationBucketWeights;
|
||||||
|
config.daysSinceExposureThreshold = daysSinceExposureThreshold;
|
||||||
|
config.minimumWindowScore = minimumWindowScore;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link #getAttenuationBucketThresholdDb()} and {@link #getAttenuationBucketWeights()}
|
||||||
|
*/
|
||||||
|
public DailySummariesConfigBuilder setAttenuationBuckets(List<Integer> thresholds, List<Double> weights) {
|
||||||
|
attenuationBucketThresholdDb = new ArrayList<>(thresholds);
|
||||||
|
attenuationBucketWeights = new ArrayList<>(weights);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link #getDaysSinceExposureThreshold()}
|
||||||
|
*/
|
||||||
|
public DailySummariesConfigBuilder setDaysSinceExposureThreshold(int daysSinceExposureThreshold) {
|
||||||
|
this.daysSinceExposureThreshold = daysSinceExposureThreshold;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link #getInfectiousnessWeights()}
|
||||||
|
*/
|
||||||
|
public DailySummariesConfigBuilder setInfectiousnessWeight(@Infectiousness int infectiousness, double weight) {
|
||||||
|
infectiousnessWeights[infectiousness] = weight;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link #getMinimumWindowScore()}
|
||||||
|
*/
|
||||||
|
public DailySummariesConfigBuilder setMinimumWindowScore(double minimumWindowScore) {
|
||||||
|
this.minimumWindowScore = minimumWindowScore;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link #getReportTypeWeights()}
|
||||||
|
*/
|
||||||
|
public DailySummariesConfigBuilder setReportTypeWeight(@ReportType int reportType, double weight) {
|
||||||
|
reportTypeWeights[reportType] = weight;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<DailySummariesConfig> CREATOR = new AutoCreator<>(DailySummariesConfig.class);
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||||
|
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||||
|
* See https://developers.google.com/readme/policies for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification;
|
||||||
|
|
||||||
|
import org.microg.gms.common.PublicApi;
|
||||||
|
import org.microg.safeparcel.AutoSafeParcelable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Daily exposure summary to pass to client side.
|
||||||
|
*/
|
||||||
|
@PublicApi
|
||||||
|
public class DailySummary extends AutoSafeParcelable {
|
||||||
|
@Field(1)
|
||||||
|
private int daysSinceEpoch;
|
||||||
|
@Field(2)
|
||||||
|
private List<ExposureSummaryData> reportSummaries;
|
||||||
|
@Field(3)
|
||||||
|
private ExposureSummaryData summaryData;
|
||||||
|
|
||||||
|
private DailySummary() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@PublicApi(exclude = true)
|
||||||
|
public DailySummary(int daysSinceEpoch, List<ExposureSummaryData> reportSummaries, ExposureSummaryData summaryData) {
|
||||||
|
this.daysSinceEpoch = daysSinceEpoch;
|
||||||
|
this.reportSummaries = reportSummaries;
|
||||||
|
this.summaryData = summaryData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof DailySummary)) return false;
|
||||||
|
|
||||||
|
DailySummary that = (DailySummary) o;
|
||||||
|
|
||||||
|
if (daysSinceEpoch != that.daysSinceEpoch) return false;
|
||||||
|
if (reportSummaries != null ? !reportSummaries.equals(that.reportSummaries) : that.reportSummaries != null)
|
||||||
|
return false;
|
||||||
|
return summaryData != null ? summaryData.equals(that.summaryData) : that.summaryData == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns days since epoch of the {@link ExposureWindow}s that went into this summary.
|
||||||
|
*/
|
||||||
|
public int getDaysSinceEpoch() {
|
||||||
|
return daysSinceEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summary of all exposures on this day.
|
||||||
|
*/
|
||||||
|
public ExposureSummaryData getSummaryData() {
|
||||||
|
return summaryData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summary of all exposures on this day of a specific diagnosis {@link ReportType}.
|
||||||
|
*/
|
||||||
|
public ExposureSummaryData getSummaryDataForReportType(@ReportType int reportType) {
|
||||||
|
return reportSummaries.get(reportType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = daysSinceEpoch;
|
||||||
|
result = 31 * result + (reportSummaries != null ? reportSummaries.hashCode() : 0);
|
||||||
|
result = 31 * result + (summaryData != null ? summaryData.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores different scores for specific {@link ReportType}.
|
||||||
|
*/
|
||||||
|
public static class ExposureSummaryData extends AutoSafeParcelable {
|
||||||
|
@Field(1)
|
||||||
|
private double maximumScore;
|
||||||
|
@Field(2)
|
||||||
|
private double scoreSum;
|
||||||
|
@Field(3)
|
||||||
|
private double weightedDurationSum;
|
||||||
|
|
||||||
|
private ExposureSummaryData() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@PublicApi(exclude = true)
|
||||||
|
public ExposureSummaryData(double maximumScore, double scoreSum, double weightedDurationSum) {
|
||||||
|
this.maximumScore = maximumScore;
|
||||||
|
this.scoreSum = scoreSum;
|
||||||
|
this.weightedDurationSum = weightedDurationSum;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof ExposureSummaryData)) return false;
|
||||||
|
|
||||||
|
ExposureSummaryData that = (ExposureSummaryData) o;
|
||||||
|
|
||||||
|
if (Double.compare(that.maximumScore, maximumScore) != 0) return false;
|
||||||
|
if (Double.compare(that.scoreSum, scoreSum) != 0) return false;
|
||||||
|
return Double.compare(that.weightedDurationSum, weightedDurationSum) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highest score of all {@link ExposureWindow}s aggregated into this summary.
|
||||||
|
* <p>
|
||||||
|
* See {@link DailySummariesConfig} for more information about how the per-{@link ExposureWindow} score is computed.
|
||||||
|
*/
|
||||||
|
public double getMaximumScore() {
|
||||||
|
return maximumScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sum of scores for all {@link ExposureWindow}s aggregated into this summary.
|
||||||
|
* <p>
|
||||||
|
* See {@link DailySummariesConfig} for more information about how the per-{@link ExposureWindow} score is computed.
|
||||||
|
*/
|
||||||
|
public double getScoreSum() {
|
||||||
|
return scoreSum;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sum of weighted durations for all {@link ExposureWindow}s aggregated into this summary.
|
||||||
|
* <p>
|
||||||
|
* See {@link DailySummariesConfig} for more information about how the per-{@link ExposureWindow} score is computed.
|
||||||
|
*/
|
||||||
|
public double getWeightedDurationSum() {
|
||||||
|
return weightedDurationSum;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result;
|
||||||
|
long temp;
|
||||||
|
temp = Double.doubleToLongBits(maximumScore);
|
||||||
|
result = (int) (temp ^ (temp >>> 32));
|
||||||
|
temp = Double.doubleToLongBits(scoreSum);
|
||||||
|
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
temp = Double.doubleToLongBits(weightedDurationSum);
|
||||||
|
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<ExposureSummaryData> CREATOR = new AutoCreator<>(ExposureSummaryData.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<DailySummary> CREATOR = new AutoCreator<>(DailySummary.class);
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||||
|
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||||
|
* See https://developers.google.com/readme/policies for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification;
|
||||||
|
|
||||||
|
import org.microg.gms.common.PublicApi;
|
||||||
|
import org.microg.safeparcel.AutoSafeParcelable;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mappings from diagnosis keys data to concepts returned by the API.
|
||||||
|
*/
|
||||||
|
@PublicApi
|
||||||
|
public class DiagnosisKeysDataMapping extends AutoSafeParcelable {
|
||||||
|
@Field(1)
|
||||||
|
private List<Integer> daysSinceOnsetToInfectiousness;
|
||||||
|
@Field(2)
|
||||||
|
@ReportType
|
||||||
|
private int reportTypeWhenMissing;
|
||||||
|
@Field(3)
|
||||||
|
@Infectiousness
|
||||||
|
private int infectiousnessWhenDaysSinceOnsetMissing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping from diagnosisKey.daysSinceOnsetOfSymptoms to {@link Infectiousness}.
|
||||||
|
* <p>
|
||||||
|
* Infectiousness is computed from this mapping and the tek metadata as - daysSinceOnsetToInfectiousness[{@link TemporaryExposureKey#getDaysSinceOnsetOfSymptoms()}], or - {@link #getInfectiousnessWhenDaysSinceOnsetMissing()} if {@link TemporaryExposureKey#getDaysSinceOnsetOfSymptoms()} is {@link TemporaryExposureKey#DAYS_SINCE_ONSET_OF_SYMPTOMS_UNKNOWN}.
|
||||||
|
* <p>
|
||||||
|
* Values of DaysSinceOnsetOfSymptoms that aren't represented in this map are given {@link Infectiousness#NONE} as infectiousness. Exposures with infectiousness equal to {@link Infectiousness#NONE} are dropped.
|
||||||
|
*/
|
||||||
|
public Map<Integer, Integer> getDaysSinceOnsetToInfectiousness() {
|
||||||
|
HashMap<Integer, Integer> map = new HashMap<>();
|
||||||
|
for (int i = 0; i < daysSinceOnsetToInfectiousness.size(); i++) {
|
||||||
|
map.put(i, daysSinceOnsetToInfectiousness.get(i));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infectiousness of TEKs for which onset of symptoms is not set.
|
||||||
|
* <p>
|
||||||
|
* See {@link #getDaysSinceOnsetToInfectiousness()} for more info.
|
||||||
|
*/
|
||||||
|
public int getInfectiousnessWhenDaysSinceOnsetMissing() {
|
||||||
|
return infectiousnessWhenDaysSinceOnsetMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report type to default to when a TEK has no report type set.
|
||||||
|
* <p>
|
||||||
|
* This report type gets used when creating the {@link ExposureWindow}s and the {@link DailySummary}s. The system will treat TEKs with missing report types as if they had this provided report type.
|
||||||
|
*/
|
||||||
|
public int getReportTypeWhenMissing() {
|
||||||
|
return reportTypeWhenMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for {@link DiagnosisKeysDataMapping}.
|
||||||
|
*/
|
||||||
|
public static class DiagnosisKeysDataMappingBuilder {
|
||||||
|
private final static int MAX_DAYS = 29;
|
||||||
|
private List<Integer> daysSinceOnsetToInfectiousness;
|
||||||
|
@ReportType
|
||||||
|
private int reportTypeWhenMissing = ReportType.UNKNOWN;
|
||||||
|
@Infectiousness
|
||||||
|
private Integer infectiousnessWhenDaysSinceOnsetMissing;
|
||||||
|
|
||||||
|
public DiagnosisKeysDataMapping build() {
|
||||||
|
if (daysSinceOnsetToInfectiousness == null)
|
||||||
|
throw new IllegalStateException("Must set daysSinceOnsetToInfectiousness");
|
||||||
|
if (reportTypeWhenMissing == ReportType.UNKNOWN)
|
||||||
|
throw new IllegalStateException("Must set reportTypeWhenMissing");
|
||||||
|
if (infectiousnessWhenDaysSinceOnsetMissing == null)
|
||||||
|
throw new IllegalStateException("Must set infectiousnessWhenDaysSinceOnsetMissing");
|
||||||
|
DiagnosisKeysDataMapping mapping = new DiagnosisKeysDataMapping();
|
||||||
|
mapping.daysSinceOnsetToInfectiousness = daysSinceOnsetToInfectiousness;
|
||||||
|
mapping.reportTypeWhenMissing = reportTypeWhenMissing;
|
||||||
|
mapping.infectiousnessWhenDaysSinceOnsetMissing = infectiousnessWhenDaysSinceOnsetMissing;
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiagnosisKeysDataMappingBuilder setDaysSinceOnsetToInfectiousness(Map<Integer, Integer> daysSinceOnsetToInfectiousness) {
|
||||||
|
if (daysSinceOnsetToInfectiousness.size() > MAX_DAYS)
|
||||||
|
throw new IllegalArgumentException("daysSinceOnsetToInfectiousness exceeds " + MAX_DAYS + " days");
|
||||||
|
Integer[] values = new Integer[MAX_DAYS];
|
||||||
|
Arrays.fill(values, 0);
|
||||||
|
for (Map.Entry<Integer, Integer> entry : daysSinceOnsetToInfectiousness.entrySet()) {
|
||||||
|
if (entry.getKey() > 14) throw new IllegalArgumentException("invalid day since onset");
|
||||||
|
values[entry.getKey() + 14] = entry.getValue();
|
||||||
|
}
|
||||||
|
this.daysSinceOnsetToInfectiousness = Arrays.asList(values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiagnosisKeysDataMappingBuilder setInfectiousnessWhenDaysSinceOnsetMissing(@Infectiousness int infectiousnessWhenDaysSinceOnsetMissing) {
|
||||||
|
this.infectiousnessWhenDaysSinceOnsetMissing = infectiousnessWhenDaysSinceOnsetMissing;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiagnosisKeysDataMappingBuilder setReportTypeWhenMissing(@ReportType int reportTypeWhenMissing) {
|
||||||
|
this.reportTypeWhenMissing = reportTypeWhenMissing;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<DiagnosisKeysDataMapping> CREATOR = new AutoCreator<>(DiagnosisKeysDataMapping.class);
|
||||||
|
}
|
@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||||
|
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||||
|
* See https://developers.google.com/readme/policies for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification;
|
||||||
|
|
||||||
|
import org.microg.gms.common.PublicApi;
|
||||||
|
import org.microg.safeparcel.AutoSafeParcelable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A duration of up to 30 minutes during which beacons from a TEK were observed.
|
||||||
|
* <p>
|
||||||
|
* Each {@link ExposureWindow} corresponds to a single TEK, but one TEK can lead to several {@link ExposureWindow} due to random 15-30 minutes cuts. See {@link ExposureNotificationClient#getExposureWindows()} for more info.
|
||||||
|
* <p>
|
||||||
|
* The TEK itself isn't exposed by the API.
|
||||||
|
*/
|
||||||
|
@PublicApi
|
||||||
|
public class ExposureWindow extends AutoSafeParcelable {
|
||||||
|
@Field(1)
|
||||||
|
private long dateMillisSinceEpoch;
|
||||||
|
@Field(2)
|
||||||
|
private List<ScanInstance> scanInstances;
|
||||||
|
@Field(3)
|
||||||
|
@ReportType
|
||||||
|
private int reportType;
|
||||||
|
@Field(4)
|
||||||
|
@Infectiousness
|
||||||
|
private int infectiousness;
|
||||||
|
@Field(5)
|
||||||
|
@CalibrationConfidence
|
||||||
|
private int calibrationConfidence;
|
||||||
|
|
||||||
|
private ExposureWindow() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExposureWindow(long dateMillisSinceEpoch, List<ScanInstance> scanInstances, int reportType, int infectiousness, int calibrationConfidence) {
|
||||||
|
this.dateMillisSinceEpoch = dateMillisSinceEpoch;
|
||||||
|
this.scanInstances = scanInstances;
|
||||||
|
this.reportType = reportType;
|
||||||
|
this.infectiousness = infectiousness;
|
||||||
|
this.calibrationConfidence = calibrationConfidence;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof ExposureWindow)) return false;
|
||||||
|
|
||||||
|
ExposureWindow that = (ExposureWindow) o;
|
||||||
|
|
||||||
|
if (dateMillisSinceEpoch != that.dateMillisSinceEpoch) return false;
|
||||||
|
if (reportType != that.reportType) return false;
|
||||||
|
if (infectiousness != that.infectiousness) return false;
|
||||||
|
if (calibrationConfidence != that.calibrationConfidence) return false;
|
||||||
|
return scanInstances != null ? scanInstances.equals(that.scanInstances) : that.scanInstances == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confidence of the BLE Transmit power calibration of the transmitting device.
|
||||||
|
*/
|
||||||
|
@CalibrationConfidence
|
||||||
|
public int getCalibrationConfidence() {
|
||||||
|
return calibrationConfidence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the epoch time in milliseconds the exposure occurred. This will represent the start of a day in UTC.
|
||||||
|
*/
|
||||||
|
public long getDateMillisSinceEpoch() {
|
||||||
|
return dateMillisSinceEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infectiousness of the TEK that caused this exposure, computed from the days since onset of symptoms using the daysToInfectiousnessMapping.
|
||||||
|
*/
|
||||||
|
@Infectiousness
|
||||||
|
public int getInfectiousness() {
|
||||||
|
return infectiousness;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report Type of the TEK that caused this exposure
|
||||||
|
* <p>
|
||||||
|
* TEKs with no report type set are returned with reportType=CONFIRMED_TEST.
|
||||||
|
* <p>
|
||||||
|
* TEKs with RECURSIVE report type may be dropped because this report type is reserved for future use.
|
||||||
|
* <p>
|
||||||
|
* TEKs with REVOKED or invalid report types do not lead to exposures.
|
||||||
|
*/
|
||||||
|
@ReportType
|
||||||
|
public int getReportType() {
|
||||||
|
return reportType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sightings of this ExposureWindow, time-ordered.
|
||||||
|
* <p>
|
||||||
|
* Each sighting corresponds to a scan (of a few seconds) during which a beacon with the TEK causing this exposure was observed.
|
||||||
|
*/
|
||||||
|
public List<ScanInstance> getScanInstances() {
|
||||||
|
return scanInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = (int) (dateMillisSinceEpoch ^ (dateMillisSinceEpoch >>> 32));
|
||||||
|
result = 31 * result + (scanInstances != null ? scanInstances.hashCode() : 0);
|
||||||
|
result = 31 * result + reportType;
|
||||||
|
result = 31 * result + infectiousness;
|
||||||
|
result = 31 * result + calibrationConfidence;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for ExposureWindow.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private long dateMillisSinceEpoch;
|
||||||
|
private List<ScanInstance> scanInstances;
|
||||||
|
@ReportType
|
||||||
|
private int reportType;
|
||||||
|
@Infectiousness
|
||||||
|
private int infectiousness;
|
||||||
|
@CalibrationConfidence
|
||||||
|
private int calibrationConfidence;
|
||||||
|
|
||||||
|
public ExposureWindow build() {
|
||||||
|
return new ExposureWindow(dateMillisSinceEpoch, scanInstances, reportType, infectiousness, calibrationConfidence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setCalibrationConfidence(int calibrationConfidence) {
|
||||||
|
this.calibrationConfidence = calibrationConfidence;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setDateMillisSinceEpoch(long dateMillisSinceEpoch) {
|
||||||
|
this.dateMillisSinceEpoch = dateMillisSinceEpoch;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setInfectiousness(@Infectiousness int infectiousness) {
|
||||||
|
this.infectiousness = infectiousness;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setReportType(@ReportType int reportType) {
|
||||||
|
this.reportType = reportType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setScanInstances(List<ScanInstance> scanInstances) {
|
||||||
|
this.scanInstances = new ArrayList<>(scanInstances);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<ExposureWindow> CREATOR = new AutoCreator<>(ExposureWindow.class);
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||||
|
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||||
|
* See https://developers.google.com/readme/policies for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification;
|
||||||
|
|
||||||
|
import org.microg.gms.common.PublicApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infectiousness defined for an {@link ExposureWindow}.
|
||||||
|
*/
|
||||||
|
@PublicApi
|
||||||
|
public @interface Infectiousness {
|
||||||
|
int NONE = 0;
|
||||||
|
int STANDARD = 1;
|
||||||
|
int HIGH = 2;
|
||||||
|
|
||||||
|
@PublicApi(exclude = true)
|
||||||
|
int VALUES = 3;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||||
|
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||||
|
* See https://developers.google.com/readme/policies for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification;
|
||||||
|
|
||||||
|
import org.microg.gms.common.PublicApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report type defined for a {@link TemporaryExposureKey}.
|
||||||
|
*/
|
||||||
|
@PublicApi
|
||||||
|
public @interface ReportType {
|
||||||
|
int UNKNOWN = 0;
|
||||||
|
int CONFIRMED_TEST = 1;
|
||||||
|
int CONFIRMED_CLINICAL_DIAGNOSIS = 2;
|
||||||
|
int SELF_REPORT = 3;
|
||||||
|
int RECURSIVE = 4;
|
||||||
|
int REVOKED = 5;
|
||||||
|
|
||||||
|
@PublicApi(exclude = true)
|
||||||
|
int VALUES = 6;
|
||||||
|
}
|
@ -8,6 +8,12 @@
|
|||||||
|
|
||||||
package com.google.android.gms.nearby.exposurenotification;
|
package com.google.android.gms.nearby.exposurenotification;
|
||||||
|
|
||||||
|
import org.microg.gms.common.PublicApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Risk level defined for an {@link TemporaryExposureKey}.
|
||||||
|
*/
|
||||||
|
@PublicApi
|
||||||
public @interface RiskLevel {
|
public @interface RiskLevel {
|
||||||
int RISK_LEVEL_INVALID = 0;
|
int RISK_LEVEL_INVALID = 0;
|
||||||
int RISK_LEVEL_LOWEST = 1;
|
int RISK_LEVEL_LOWEST = 1;
|
||||||
@ -18,4 +24,7 @@ public @interface RiskLevel {
|
|||||||
int RISK_LEVEL_HIGH = 6;
|
int RISK_LEVEL_HIGH = 6;
|
||||||
int RISK_LEVEL_VERY_HIGH = 7;
|
int RISK_LEVEL_VERY_HIGH = 7;
|
||||||
int RISK_LEVEL_HIGHEST = 8;
|
int RISK_LEVEL_HIGHEST = 8;
|
||||||
|
|
||||||
|
@PublicApi(exclude = true)
|
||||||
|
int VALUES = 9;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||||
|
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||||
|
* See https://developers.google.com/readme/policies for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification;
|
||||||
|
|
||||||
|
import org.microg.gms.common.PublicApi;
|
||||||
|
import org.microg.safeparcel.AutoSafeParcelable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about the sighting of a TEK within a BLE scan (of a few seconds).
|
||||||
|
* <p>
|
||||||
|
* The TEK itself isn't exposed by the API.
|
||||||
|
*/
|
||||||
|
@PublicApi
|
||||||
|
public class ScanInstance extends AutoSafeParcelable {
|
||||||
|
@Field(1)
|
||||||
|
private int typicalAttenuationDb;
|
||||||
|
@Field(2)
|
||||||
|
private int minAttenuationDb;
|
||||||
|
@Field(3)
|
||||||
|
private int secondsSinceLastScan;
|
||||||
|
|
||||||
|
private ScanInstance() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScanInstance(int typicalAttenuationDb, int minAttenuationDb, int secondsSinceLastScan) {
|
||||||
|
this.typicalAttenuationDb = typicalAttenuationDb;
|
||||||
|
this.minAttenuationDb = minAttenuationDb;
|
||||||
|
this.secondsSinceLastScan = secondsSinceLastScan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum attenuation of all of this TEK's beacons received during the scan, in dB.
|
||||||
|
*/
|
||||||
|
public int getMinAttenuationDb() {
|
||||||
|
return minAttenuationDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seconds elapsed since the previous scan, typically used as a weight.
|
||||||
|
* <p>
|
||||||
|
* Two example uses:
|
||||||
|
* - Summing those values over all sightings of an exposure provides the duration of that exposure.
|
||||||
|
* - Summing those values over all sightings in a given attenuation range and over all exposures recreates the durationAtBuckets of v1.
|
||||||
|
* <p>
|
||||||
|
* Note that the previous scan may not have led to a sighting of that TEK.
|
||||||
|
*/
|
||||||
|
public int getSecondsSinceLastScan() {
|
||||||
|
return secondsSinceLastScan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregation of the attenuations of all of this TEK's beacons received during the scan, in dB. This is most likely to be an average in the dB domain.
|
||||||
|
*/
|
||||||
|
public int getTypicalAttenuationDb() {
|
||||||
|
return typicalAttenuationDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for {@link ScanInstance}.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private int typicalAttenuationDb;
|
||||||
|
private int minAttenuationDb;
|
||||||
|
private int secondsSinceLastScan;
|
||||||
|
|
||||||
|
public ScanInstance build() {
|
||||||
|
return new ScanInstance(typicalAttenuationDb, minAttenuationDb, secondsSinceLastScan);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScanInstance.Builder setMinAttenuationDb(int minAttenuationDb) {
|
||||||
|
this.minAttenuationDb = minAttenuationDb;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScanInstance.Builder setSecondsSinceLastScan(int secondsSinceLastScan) {
|
||||||
|
this.secondsSinceLastScan = secondsSinceLastScan;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScanInstance.Builder setTypicalAttenuationDb(int typicalAttenuationDb) {
|
||||||
|
this.typicalAttenuationDb = typicalAttenuationDb;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<ScanInstance> CREATOR = new AutoCreator<>(ScanInstance.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 GetCalibrationConfidenceParams extends AutoSafeParcelable {
|
||||||
|
@Field(1)
|
||||||
|
public IIntCallback callback;
|
||||||
|
|
||||||
|
private GetCalibrationConfidenceParams() {}
|
||||||
|
|
||||||
|
public GetCalibrationConfidenceParams(IIntCallback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<GetCalibrationConfidenceParams> CREATOR = new AutoCreator<>(GetCalibrationConfidenceParams.class);
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.DailySummariesConfig;
|
||||||
|
|
||||||
|
import org.microg.safeparcel.AutoSafeParcelable;
|
||||||
|
|
||||||
|
public class GetDailySummariesParams extends AutoSafeParcelable {
|
||||||
|
@Field(1)
|
||||||
|
public IDailySummaryListCallback callback;
|
||||||
|
@Field(2)
|
||||||
|
public DailySummariesConfig config;
|
||||||
|
|
||||||
|
private GetDailySummariesParams() {}
|
||||||
|
|
||||||
|
public GetDailySummariesParams(IDailySummaryListCallback callback, DailySummariesConfig config) {
|
||||||
|
this.callback = callback;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<GetDailySummariesParams> CREATOR = new AutoCreator<>(GetDailySummariesParams.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 GetDiagnosisKeysDataMappingParams extends AutoSafeParcelable {
|
||||||
|
@Field(1)
|
||||||
|
public IDiagnosisKeysDataMappingCallback callback;
|
||||||
|
|
||||||
|
private GetDiagnosisKeysDataMappingParams() {}
|
||||||
|
|
||||||
|
public GetDiagnosisKeysDataMappingParams(IDiagnosisKeysDataMappingCallback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<GetDiagnosisKeysDataMappingParams> CREATOR = new AutoCreator<>(GetDiagnosisKeysDataMappingParams.class);
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
import org.microg.safeparcel.AutoSafeParcelable;
|
||||||
|
|
||||||
|
public class GetExposureWindowsParams extends AutoSafeParcelable {
|
||||||
|
@Field(1)
|
||||||
|
public IExposureWindowListCallback callback;
|
||||||
|
@Field(2)
|
||||||
|
public String token;
|
||||||
|
|
||||||
|
private GetExposureWindowsParams() {}
|
||||||
|
|
||||||
|
public GetExposureWindowsParams(IExposureWindowListCallback callback, String token) {
|
||||||
|
this.callback = callback;
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<GetExposureWindowsParams> CREATOR = new AutoCreator<>(GetExposureWindowsParams.class);
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
import org.microg.safeparcel.AutoSafeParcelable;
|
||||||
|
|
||||||
|
public class GetVersionParams extends AutoSafeParcelable {
|
||||||
|
@Field(1)
|
||||||
|
public ILongCallback callback;
|
||||||
|
|
||||||
|
private GetVersionParams() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetVersionParams(ILongCallback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<GetVersionParams> CREATOR = new AutoCreator<>(GetVersionParams.class);
|
||||||
|
}
|
@ -27,6 +27,9 @@ public class ProvideDiagnosisKeysParams extends AutoSafeParcelable {
|
|||||||
@Field(5)
|
@Field(5)
|
||||||
public String token;
|
public String token;
|
||||||
|
|
||||||
|
private ProvideDiagnosisKeysParams() {
|
||||||
|
}
|
||||||
|
|
||||||
public ProvideDiagnosisKeysParams(IStatusCallback callback, List<TemporaryExposureKey> keys, List<ParcelFileDescriptor> keyFiles, ExposureConfiguration configuration, String token) {
|
public ProvideDiagnosisKeysParams(IStatusCallback callback, List<TemporaryExposureKey> keys, List<ParcelFileDescriptor> keyFiles, ExposureConfiguration configuration, String token) {
|
||||||
this(callback, keyFiles, configuration, token);
|
this(callback, keyFiles, configuration, token);
|
||||||
this.keys = keys;
|
this.keys = keys;
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.nearby.exposurenotification.internal;
|
||||||
|
|
||||||
|
import com.google.android.gms.common.api.internal.IStatusCallback;
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping;
|
||||||
|
|
||||||
|
import org.microg.safeparcel.AutoSafeParcelable;
|
||||||
|
|
||||||
|
public class SetDiagnosisKeysDataMappingParams extends AutoSafeParcelable {
|
||||||
|
@Field(1)
|
||||||
|
public IStatusCallback callback;
|
||||||
|
@Field(2)
|
||||||
|
public DiagnosisKeysDataMapping mapping;
|
||||||
|
|
||||||
|
private SetDiagnosisKeysDataMappingParams() {}
|
||||||
|
|
||||||
|
public SetDiagnosisKeysDataMappingParams(IStatusCallback callback, DiagnosisKeysDataMapping mapping) {
|
||||||
|
this.callback = callback;
|
||||||
|
this.mapping = mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<SetDiagnosisKeysDataMappingParams> CREATOR = new AutoCreator<>(SetDiagnosisKeysDataMappingParams.class);
|
||||||
|
}
|
@ -48,7 +48,7 @@ class AdvertiserService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(23)
|
@TargetApi(23)
|
||||||
private var setCallback: AdvertisingSetCallback? = null
|
private var setCallback: Any? = null
|
||||||
private val trigger = object : BroadcastReceiver() {
|
private val trigger = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
if (intent?.action == "android.bluetooth.adapter.action.STATE_CHANGED") {
|
if (intent?.action == "android.bluetooth.adapter.action.STATE_CHANGED") {
|
||||||
@ -83,6 +83,7 @@ class AdvertiserService : Service() {
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
unregisterReceiver(trigger)
|
unregisterReceiver(trigger)
|
||||||
stopOrRestartAdvertising()
|
stopOrRestartAdvertising()
|
||||||
|
handler.removeCallbacks(startLaterRunnable)
|
||||||
database.unref()
|
database.unref()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +115,7 @@ class AdvertiserService : Service() {
|
|||||||
0x00 // Reserved
|
0x00 // Reserved
|
||||||
)
|
)
|
||||||
VERSION_1_1 -> byteArrayOf(
|
VERSION_1_1 -> byteArrayOf(
|
||||||
(version + currentDeviceInfo.confidence * 4).toByte(), // Version and flags
|
(version + currentDeviceInfo.confidence.toByte() * 4).toByte(), // Version and flags
|
||||||
(currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power
|
(currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power
|
||||||
0x00, // Reserved
|
0x00, // Reserved
|
||||||
0x00 // Reserved
|
0x00 // Reserved
|
||||||
@ -134,7 +135,7 @@ class AdvertiserService : Service() {
|
|||||||
.setTxPowerLevel(AdvertisingSetParameters.TX_POWER_LOW)
|
.setTxPowerLevel(AdvertisingSetParameters.TX_POWER_LOW)
|
||||||
.setConnectable(false)
|
.setConnectable(false)
|
||||||
.build()
|
.build()
|
||||||
advertiser.startAdvertisingSet(params, data, null, null, null, setCallback)
|
advertiser.startAdvertisingSet(params, data, null, null, null, setCallback as AdvertisingSetCallback)
|
||||||
} else {
|
} else {
|
||||||
nextSend = nextSend.coerceAtMost(180000)
|
nextSend = nextSend.coerceAtMost(180000)
|
||||||
val settings = Builder()
|
val settings = Builder()
|
||||||
@ -189,21 +190,13 @@ class AdvertiserService : Service() {
|
|||||||
advertising = false
|
advertising = false
|
||||||
if (Build.VERSION.SDK_INT >= 26) {
|
if (Build.VERSION.SDK_INT >= 26) {
|
||||||
wantStartAdvertising = true
|
wantStartAdvertising = true
|
||||||
advertiser?.stopAdvertisingSet(setCallback)
|
advertiser?.stopAdvertisingSet(setCallback as AdvertisingSetCallback)
|
||||||
} else {
|
} else {
|
||||||
advertiser?.stopAdvertising(callback)
|
advertiser?.stopAdvertising(callback)
|
||||||
}
|
}
|
||||||
handler.postDelayed(startLaterRunnable, 1000)
|
handler.postDelayed(startLaterRunnable, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val ACTION_RESTART_ADVERTISING = "org.microg.gms.nearby.exposurenotification.RESTART_ADVERTISING"
|
|
||||||
|
|
||||||
fun isNeeded(context: Context): Boolean {
|
|
||||||
return ExposurePreferences(context).enabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(26)
|
@TargetApi(26)
|
||||||
inner class SetCallback : AdvertisingSetCallback() {
|
inner class SetCallback : AdvertisingSetCallback() {
|
||||||
override fun onAdvertisingSetStarted(advertisingSet: AdvertisingSet?, txPower: Int, status: Int) {
|
override fun onAdvertisingSetStarted(advertisingSet: AdvertisingSet?, txPower: Int, status: Int) {
|
||||||
@ -219,4 +212,13 @@ class AdvertiserService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ACTION_RESTART_ADVERTISING = "org.microg.gms.nearby.exposurenotification.RESTART_ADVERTISING"
|
||||||
|
|
||||||
|
fun isNeeded(context: Context): Boolean {
|
||||||
|
return ExposurePreferences(context).enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
package org.microg.gms.nearby.exposurenotification
|
package org.microg.gms.nearby.exposurenotification
|
||||||
|
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.lifecycle.LifecycleService
|
import androidx.lifecycle.LifecycleService
|
||||||
@ -25,14 +27,21 @@ class CleanupService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
ExposurePreferences(this@CleanupService).lastCleanup = System.currentTimeMillis()
|
ExposurePreferences(this@CleanupService).lastCleanup = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
stopSelf()
|
stop()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stopSelf()
|
stop()
|
||||||
}
|
}
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
val pendingIntent = PendingIntent.getService(applicationContext, CleanupService::class.java.name.hashCode(), Intent(applicationContext, CleanupService::class.java), PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
alarmManager.set(AlarmManager.RTC, ExposurePreferences(this).lastCleanup + CLEANUP_INTERVAL, pendingIntent)
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun isNeeded(context: Context): Boolean {
|
fun isNeeded(context: Context): Boolean {
|
||||||
return ExposurePreferences(context).let {
|
return ExposurePreferences(context).let {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
package org.microg.gms.nearby.exposurenotification
|
package org.microg.gms.nearby.exposurenotification
|
||||||
|
|
||||||
import android.os.ParcelUuid
|
import android.os.ParcelUuid
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.CalibrationConfidence
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
const val TAG = "ExposureNotification"
|
const val TAG = "ExposureNotification"
|
||||||
@ -17,10 +18,10 @@ const val SCANNING_TIME = 20 // Google uses 4s + 13s (if Bluetooth is used by so
|
|||||||
const val SCANNING_TIME_MS = SCANNING_TIME * 1000L
|
const val SCANNING_TIME_MS = SCANNING_TIME * 1000L
|
||||||
|
|
||||||
const val ROLLING_WINDOW_LENGTH = 10 * 60
|
const val ROLLING_WINDOW_LENGTH = 10 * 60
|
||||||
const val ROLLING_WINDOW_LENGTH_MS = ROLLING_WINDOW_LENGTH * 1000
|
const val ROLLING_WINDOW_LENGTH_MS = ROLLING_WINDOW_LENGTH * 1000L
|
||||||
const val ROLLING_PERIOD = 144
|
const val ROLLING_PERIOD = 144
|
||||||
const val ALLOWED_KEY_OFFSET_MS = 60 * 60 * 1000
|
const val ALLOWED_KEY_OFFSET_MS = 60 * 60 * 1000L
|
||||||
const val MINIMUM_EXPOSURE_DURATION_MS = 0
|
const val MINIMUM_EXPOSURE_DURATION_MS = 0L
|
||||||
const val KEEP_DAYS = 14
|
const val KEEP_DAYS = 14
|
||||||
|
|
||||||
const val ACTION_CONFIRM = "org.microg.gms.nearby.exposurenotification.CONFIRM"
|
const val ACTION_CONFIRM = "org.microg.gms.nearby.exposurenotification.CONFIRM"
|
||||||
@ -37,27 +38,7 @@ const val PERMISSION_EXPOSURE_CALLBACK = "com.google.android.gms.nearby.exposure
|
|||||||
const val TX_POWER_LOW = -15
|
const val TX_POWER_LOW = -15
|
||||||
|
|
||||||
const val ADVERTISER_OFFSET = 60 * 1000
|
const val ADVERTISER_OFFSET = 60 * 1000
|
||||||
const val CLEANUP_INTERVAL = 24 * 60 * 60 * 1000
|
const val CLEANUP_INTERVAL = 24 * 60 * 60 * 1000L
|
||||||
|
|
||||||
const val VERSION_1_0: Byte = 0x40
|
const val VERSION_1_0: Byte = 0x40
|
||||||
const val VERSION_1_1: Byte = 0x50
|
const val VERSION_1_1: Byte = 0x50
|
||||||
|
|
||||||
/**
|
|
||||||
* No calibration data, using fleet-wide as default options.
|
|
||||||
*/
|
|
||||||
const val CONFIDENCE_LOWEST: Byte = 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Using average calibration over models from manufacturer.
|
|
||||||
*/
|
|
||||||
const val CONFIDENCE_LOW: Byte = 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Using single-antenna orientation for a similar model.
|
|
||||||
*/
|
|
||||||
const val CONFIDENCE_MEDIUM: Byte = 2
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Using significant calibration data for this model.
|
|
||||||
*/
|
|
||||||
const val CONFIDENCE_HIGH: Byte = 3
|
|
||||||
|
@ -10,14 +10,15 @@ package org.microg.gms.nearby.exposurenotification
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.CalibrationConfidence
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
data class DeviceInfo(val oem: String, val model: String, val txPowerCorrection: Byte, val rssiCorrection: Byte, val confidence: Byte = CONFIDENCE_MEDIUM)
|
data class DeviceInfo(val oem: String, val model: String, val txPowerCorrection: Byte, val rssiCorrection: Byte, @CalibrationConfidence val confidence: Int = CalibrationConfidence.MEDIUM)
|
||||||
|
|
||||||
private var knownDeviceInfo: DeviceInfo? = null
|
private var knownDeviceInfo: DeviceInfo? = null
|
||||||
|
|
||||||
fun averageCurrentDeviceInfo(oem: String, model: String, deviceInfos: List<DeviceInfo>, confidence: Byte = CONFIDENCE_LOW): DeviceInfo =
|
fun averageCurrentDeviceInfo(oem: String, model: String, deviceInfos: List<DeviceInfo>, @CalibrationConfidence confidence: Int = CalibrationConfidence.LOW): DeviceInfo =
|
||||||
DeviceInfo(oem, model, deviceInfos.map { it.txPowerCorrection }.average().roundToInt().toByte(), deviceInfos.map { it.txPowerCorrection }.average().roundToInt().toByte(), CONFIDENCE_LOW)
|
DeviceInfo(oem, model, deviceInfos.map { it.txPowerCorrection }.average().roundToInt().toByte(), deviceInfos.map { it.txPowerCorrection }.average().roundToInt().toByte(), CalibrationConfidence.LOW)
|
||||||
|
|
||||||
val currentDeviceInfo: DeviceInfo
|
val currentDeviceInfo: DeviceInfo
|
||||||
get() {
|
get() {
|
||||||
@ -36,7 +37,7 @@ val currentDeviceInfo: DeviceInfo
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// Fallback to all device average
|
// Fallback to all device average
|
||||||
averageCurrentDeviceInfo(Build.MANUFACTURER, Build.MODEL, allDeviceInfos, CONFIDENCE_LOWEST)
|
averageCurrentDeviceInfo(Build.MANUFACTURER, Build.MODEL, allDeviceInfos, CalibrationConfidence.LOWEST)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.i(TAG, "Selected $deviceInfo")
|
Log.i(TAG, "Selected $deviceInfo")
|
||||||
|
@ -17,12 +17,11 @@ import android.os.Parcelable
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
|
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
|
||||||
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
||||||
|
import okio.ByteString
|
||||||
|
import java.io.File
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.*
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
@ -43,28 +42,41 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
|
Log.d(TAG, "Upgrading database from $oldVersion to $newVersion")
|
||||||
if (oldVersion < 1) {
|
if (oldVersion < 1) {
|
||||||
|
Log.d(TAG, "Creating tables for version >= 1")
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_ADVERTISEMENTS(rpi BLOB NOT NULL, aem BLOB NOT NULL, timestamp INTEGER NOT NULL, rssi INTEGER NOT NULL, duration INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(rpi, timestamp));")
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_ADVERTISEMENTS(rpi BLOB NOT NULL, aem BLOB NOT NULL, timestamp INTEGER NOT NULL, rssi INTEGER NOT NULL, duration INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(rpi, timestamp));")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_ADVERTISEMENTS}_rpi ON $TABLE_ADVERTISEMENTS(rpi);")
|
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_ADVERTISEMENTS}_rpi ON $TABLE_ADVERTISEMENTS(rpi);")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_ADVERTISEMENTS}_timestamp ON $TABLE_ADVERTISEMENTS(timestamp);")
|
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_ADVERTISEMENTS}_timestamp ON $TABLE_ADVERTISEMENTS(timestamp);")
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_APP_LOG(package TEXT NOT NULL, timestamp INTEGER NOT NULL, method TEXT NOT NULL, args TEXT);")
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_APP_LOG(package TEXT NOT NULL, timestamp INTEGER NOT NULL, method TEXT NOT NULL, args TEXT);")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_APP_LOG}_package_timestamp ON $TABLE_APP_LOG(package, timestamp);")
|
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_APP_LOG}_package_timestamp ON $TABLE_APP_LOG(package, timestamp);")
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK(keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL);")
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK(keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL);")
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_CONFIGURATIONS(package TEXT NOT NULL, token TEXT NOT NULL, configuration BLOB, PRIMARY KEY(package, token))")
|
|
||||||
}
|
|
||||||
if (oldVersion < 2) {
|
|
||||||
db.execSQL("DROP TABLE IF EXISTS $TABLE_TEK_CHECK;")
|
|
||||||
db.execSQL("DROP TABLE IF EXISTS $TABLE_DIAGNOSIS;")
|
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK(tcid INTEGER PRIMARY KEY, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, matched INTEGER, UNIQUE(keyData, rollingStartNumber, rollingPeriod));")
|
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_DIAGNOSIS(package TEXT NOT NULL, token TEXT NOT NULL, tcid INTEGER REFERENCES $TABLE_TEK_CHECK(tcid) ON DELETE CASCADE, transmissionRiskLevel INTEGER NOT NULL);")
|
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_DIAGNOSIS}_package_token ON $TABLE_DIAGNOSIS(package, token);")
|
|
||||||
}
|
}
|
||||||
if (oldVersion < 3) {
|
if (oldVersion < 3) {
|
||||||
|
Log.d(TAG, "Creating tables for version >= 3")
|
||||||
db.execSQL("CREATE TABLE $TABLE_APP_PERMS(package TEXT NOT NULL, sig TEXT NOT NULL, perm TEXT NOT NULL, timestamp INTEGER NOT NULL);")
|
db.execSQL("CREATE TABLE $TABLE_APP_PERMS(package TEXT NOT NULL, sig TEXT NOT NULL, perm TEXT NOT NULL, timestamp INTEGER NOT NULL);")
|
||||||
}
|
}
|
||||||
if (oldVersion < 4) {
|
if (oldVersion < 5) {
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_DIAGNOSIS}_tcid ON $TABLE_DIAGNOSIS(tcid);")
|
Log.d(TAG, "Dropping legacy tables")
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS $TABLE_CONFIGURATIONS;")
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS $TABLE_DIAGNOSIS;")
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS $TABLE_TEK_CHECK;")
|
||||||
|
Log.d(TAG, "Creating tables for version >= 3")
|
||||||
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TOKENS(tid INTEGER PRIMARY KEY, package TEXT NOT NULL, token TEXT NOT NULL, timestamp INTEGER NOT NULL, configuration BLOB);")
|
||||||
|
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TOKENS}_package_token ON $TABLE_TOKENS(package, token);")
|
||||||
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_SINGLE(tcsid INTEGER PRIMARY KEY, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, matched INTEGER);")
|
||||||
|
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_SINGLE}_key ON $TABLE_TEK_CHECK_SINGLE(keyData, rollingStartNumber, rollingPeriod);")
|
||||||
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_SINGLE_TOKEN(tcsid INTEGER REFERENCES $TABLE_TEK_CHECK_SINGLE(tcsid) ON DELETE CASCADE, tid INTEGER REFERENCES $TABLE_TOKENS(tid) ON DELETE CASCADE, transmissionRiskLevel INTEGER NOT NULL, UNIQUE(tcsid, tid));")
|
||||||
|
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_SINGLE_TOKEN}_tid ON $TABLE_TEK_CHECK_SINGLE_TOKEN(tid);")
|
||||||
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE(tcfid INTEGER PRIMARY KEY, hash TEXT NOT NULL, endTimestamp INTEGER NOT NULL, keys INTEGER NOT NULL);")
|
||||||
|
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE}_hash ON $TABLE_TEK_CHECK_FILE(hash);")
|
||||||
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE_TOKEN(tcfid INTEGER REFERENCES $TABLE_TEK_CHECK_FILE(tcfid) ON DELETE CASCADE, tid INTEGER REFERENCES $TABLE_TOKENS(tid) ON DELETE CASCADE, UNIQUE(tcfid, tid));")
|
||||||
|
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_TOKEN}_tid ON $TABLE_TEK_CHECK_FILE_TOKEN(tid);")
|
||||||
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE_MATCH(tcfid INTEGER REFERENCES $TABLE_TEK_CHECK_FILE(tcfid) ON DELETE CASCADE, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, transmissionRiskLevel INTEGER NOT NULL, UNIQUE(tcfid, keyData, rollingStartNumber, rollingPeriod));")
|
||||||
|
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_MATCH}_tcfid ON $TABLE_TEK_CHECK_FILE_MATCH(tcfid);")
|
||||||
|
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_MATCH}_key ON $TABLE_TEK_CHECK_FILE_MATCH(keyData, rollingStartNumber, rollingPeriod);")
|
||||||
}
|
}
|
||||||
|
Log.d(TAG, "Finished database upgrade")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun SQLiteDatabase.delete(table: String, whereClause: String, args: LongArray): Int =
|
fun SQLiteDatabase.delete(table: String, whereClause: String, args: LongArray): Int =
|
||||||
@ -74,23 +86,21 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun dailyCleanup() = writableDatabase.run {
|
fun dailyCleanup() = writableDatabase.run {
|
||||||
beginTransaction()
|
val rollingStartTime = currentRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong())
|
||||||
try {
|
val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime))
|
||||||
val rollingStartTime = currentRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong())
|
Log.d(TAG, "Deleted on daily cleanup: $advertisements adv")
|
||||||
val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime))
|
val appLogEntries = delete(TABLE_APP_LOG, "timestamp < ?", longArrayOf(rollingStartTime))
|
||||||
Log.d(TAG, "Deleted on daily cleanup: $advertisements adv")
|
Log.d(TAG, "Deleted on daily cleanup: $appLogEntries applogs")
|
||||||
val appLogEntries = delete(TABLE_APP_LOG, "timestamp < ?", longArrayOf(rollingStartTime))
|
val temporaryExposureKeys = delete(TABLE_TEK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS))
|
||||||
Log.d(TAG, "Deleted on daily cleanup: $appLogEntries applogs")
|
Log.d(TAG, "Deleted on daily cleanup: $temporaryExposureKeys teks")
|
||||||
val temporaryExposureKeys = delete(TABLE_TEK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS))
|
val singleCheckedTemporaryExposureKeys = delete(TABLE_TEK_CHECK_SINGLE, "rollingStartNumber < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS - ROLLING_PERIOD))
|
||||||
Log.d(TAG, "Deleted on daily cleanup: $temporaryExposureKeys teks")
|
Log.d(TAG, "Deleted on daily cleanup: $singleCheckedTemporaryExposureKeys tcss")
|
||||||
val checkedTemporaryExposureKeys = delete(TABLE_TEK_CHECK, "rollingStartNumber < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS - ROLLING_PERIOD))
|
val fileCheckedTemporaryExposureKeys = delete(TABLE_TEK_CHECK_FILE, "endTimestamp < ?", longArrayOf(rollingStartTime))
|
||||||
Log.d(TAG, "Deleted on daily cleanup: $checkedTemporaryExposureKeys cteks")
|
Log.d(TAG, "Deleted on daily cleanup: $fileCheckedTemporaryExposureKeys tcfs")
|
||||||
val appPerms = delete(TABLE_APP_PERMS, "timestamp < ?", longArrayOf(System.currentTimeMillis() - CONFIRM_PERMISSION_VALIDITY))
|
val appPerms = delete(TABLE_APP_PERMS, "timestamp < ?", longArrayOf(System.currentTimeMillis() - CONFIRM_PERMISSION_VALIDITY))
|
||||||
Log.d(TAG, "Deleted on daily cleanup: $appPerms perms")
|
Log.d(TAG, "Deleted on daily cleanup: $appPerms perms")
|
||||||
setTransactionSuccessful()
|
execSQL("VACUUM;")
|
||||||
} finally {
|
Log.d(TAG, "Done vacuuming")
|
||||||
endTransaction()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun grantPermission(packageName: String, signatureDigest: String, permission: String, timestamp: Long = System.currentTimeMillis()) = writableDatabase.run {
|
fun grantPermission(packageName: String, signatureDigest: String, permission: String, timestamp: Long = System.currentTimeMillis()) = writableDatabase.run {
|
||||||
@ -132,7 +142,11 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
|
|
||||||
fun deleteAllCollectedAdvertisements() = writableDatabase.run {
|
fun deleteAllCollectedAdvertisements() = writableDatabase.run {
|
||||||
delete(TABLE_ADVERTISEMENTS, null, null)
|
delete(TABLE_ADVERTISEMENTS, null, null)
|
||||||
update(TABLE_TEK_CHECK, ContentValues().apply {
|
delete(TABLE_TEK_CHECK_FILE_MATCH, null, null)
|
||||||
|
update(TABLE_TEK_CHECK_SINGLE, ContentValues().apply {
|
||||||
|
put("matched", 0)
|
||||||
|
}, null, null)
|
||||||
|
update(TABLE_TEK_CHECK_FILE, ContentValues().apply {
|
||||||
put("matched", 0)
|
put("matched", 0)
|
||||||
}, null, null)
|
}, null, null)
|
||||||
}
|
}
|
||||||
@ -155,15 +169,15 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTekCheckId(key: TemporaryExposureKey, mayInsert: Boolean = false, database: SQLiteDatabase = if (mayInsert) writableDatabase else readableDatabase): Long? = database.run {
|
private fun getTekCheckSingleId(key: TemporaryExposureKey, mayInsert: Boolean = false, database: SQLiteDatabase = if (mayInsert) writableDatabase else readableDatabase): Long? = database.run {
|
||||||
if (mayInsert) {
|
if (mayInsert) {
|
||||||
insertWithOnConflict(TABLE_TEK_CHECK, "NULL", ContentValues().apply {
|
insertWithOnConflict(TABLE_TEK_CHECK_SINGLE, "NULL", ContentValues().apply {
|
||||||
put("keyData", key.keyData)
|
put("keyData", key.keyData)
|
||||||
put("rollingStartNumber", key.rollingStartIntervalNumber)
|
put("rollingStartNumber", key.rollingStartIntervalNumber)
|
||||||
put("rollingPeriod", key.rollingPeriod)
|
put("rollingPeriod", key.rollingPeriod)
|
||||||
}, CONFLICT_IGNORE)
|
}, CONFLICT_IGNORE)
|
||||||
}
|
}
|
||||||
compileStatement("SELECT tcid FROM $TABLE_TEK_CHECK WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?").use {
|
compileStatement("SELECT tcsid FROM $TABLE_TEK_CHECK_SINGLE WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?").use {
|
||||||
it.bindBlob(1, key.keyData)
|
it.bindBlob(1, key.keyData)
|
||||||
it.bindLong(2, key.rollingStartIntervalNumber.toLong())
|
it.bindLong(2, key.rollingStartIntervalNumber.toLong())
|
||||||
it.bindLong(3, key.rollingPeriod.toLong())
|
it.bindLong(3, key.rollingPeriod.toLong())
|
||||||
@ -171,57 +185,77 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeDiagnosisKey(packageName: String, token: String, key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run {
|
fun getTokenId(packageName: String, token: String, database: SQLiteDatabase = readableDatabase) = database.run {
|
||||||
val tcid = getTekCheckId(key, true, database)
|
query(TABLE_TOKENS, arrayOf("tid"), "package = ? AND token = ?", arrayOf(packageName, token), null, null, null, null).use { cursor ->
|
||||||
insert(TABLE_DIAGNOSIS, "NULL", ContentValues().apply {
|
if (cursor.moveToNext()) {
|
||||||
put("package", packageName)
|
cursor.getLong(0)
|
||||||
put("token", token)
|
} else {
|
||||||
put("tcid", tcid)
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun storeSingleDiagnosisKey(tid: Long, key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||||
|
val tcsid = getTekCheckSingleId(key, true, database)
|
||||||
|
insert(TABLE_TEK_CHECK_SINGLE_TOKEN, "NULL", ContentValues().apply {
|
||||||
|
put("tid", tid)
|
||||||
|
put("tcsid", tcsid)
|
||||||
put("transmissionRiskLevel", key.transmissionRiskLevel)
|
put("transmissionRiskLevel", key.transmissionRiskLevel)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun batchStoreDiagnosisKey(packageName: String, token: String, keys: List<TemporaryExposureKey>, database: SQLiteDatabase = writableDatabase) = database.run {
|
fun batchStoreSingleDiagnosisKey(tid: Long, keys: List<TemporaryExposureKey>, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||||
beginTransaction()
|
beginTransaction()
|
||||||
try {
|
try {
|
||||||
keys.forEach { storeDiagnosisKey(packageName, token, it, database) }
|
keys.forEach { storeSingleDiagnosisKey(tid, it, database) }
|
||||||
setTransactionSuccessful()
|
setTransactionSuccessful()
|
||||||
} finally {
|
} finally {
|
||||||
endTransaction()
|
endTransaction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateDiagnosisKey(packageName: String, token: String, key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run {
|
fun getDiagnosisFileId(hash: ByteArray, database: SQLiteDatabase = readableDatabase) = database.run {
|
||||||
val tcid = getTekCheckId(key, false, database) ?: return 0
|
val hexHash = ByteString.of(*hash).hex()
|
||||||
compileStatement("UPDATE $TABLE_DIAGNOSIS SET transmissionRiskLevel = ? WHERE package = ? AND token = ? AND tcid = ?;").use {
|
query(TABLE_TEK_CHECK_FILE, arrayOf("tcfid"), "hash = ?", arrayOf(hexHash), null, null, null, null).use { cursor ->
|
||||||
it.bindLong(1, key.transmissionRiskLevel.toLong())
|
if (cursor.moveToNext()) {
|
||||||
it.bindString(2, packageName)
|
cursor.getLong(0)
|
||||||
it.bindString(3, token)
|
} else {
|
||||||
it.bindLong(4, tcid)
|
null
|
||||||
it.executeUpdateDelete()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun batchUpdateDiagnosisKey(packageName: String, token: String, keys: List<TemporaryExposureKey>, database: SQLiteDatabase = writableDatabase) = database.run {
|
fun storeDiagnosisFileUsed(tid: Long, tcfid: Long, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||||
beginTransaction()
|
insert(TABLE_TEK_CHECK_FILE_TOKEN, "NULL", ContentValues().apply {
|
||||||
try {
|
put("tid", tid)
|
||||||
keys.forEach { updateDiagnosisKey(packageName, token, it, database) }
|
put("tcfid", tcfid)
|
||||||
setTransactionSuccessful()
|
})
|
||||||
} finally {
|
}
|
||||||
endTransaction()
|
|
||||||
|
fun storeDiagnosisFileUsed(tid: Long, hash: ByteArray, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||||
|
val hexHash = ByteString.of(*hash).hex()
|
||||||
|
query(TABLE_TEK_CHECK_FILE, arrayOf("tcfid", "keys"), "hash = ?", arrayOf(hexHash), null, null, null, null).use { cursor ->
|
||||||
|
if (cursor.moveToNext()) {
|
||||||
|
insert(TABLE_TEK_CHECK_FILE_TOKEN, "NULL", ContentValues().apply {
|
||||||
|
put("tid", tid)
|
||||||
|
put("tcfid", cursor.getLong(0))
|
||||||
|
})
|
||||||
|
cursor.getLong(1)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun listDiagnosisKeysPendingSearch(packageName: String, token: String, database: SQLiteDatabase = readableDatabase) = database.run {
|
private fun listSingleDiagnosisKeysPendingSearch(tid: Long, database: SQLiteDatabase = readableDatabase) = database.run {
|
||||||
rawQuery("""
|
rawQuery("""
|
||||||
SELECT $TABLE_TEK_CHECK.keyData, $TABLE_TEK_CHECK.rollingStartNumber, $TABLE_TEK_CHECK.rollingPeriod
|
SELECT $TABLE_TEK_CHECK_SINGLE.keyData, $TABLE_TEK_CHECK_SINGLE.rollingStartNumber, $TABLE_TEK_CHECK_SINGLE.rollingPeriod
|
||||||
FROM $TABLE_DIAGNOSIS
|
FROM $TABLE_TEK_CHECK_SINGLE_TOKEN
|
||||||
LEFT JOIN $TABLE_TEK_CHECK ON $TABLE_DIAGNOSIS.tcid = $TABLE_TEK_CHECK.tcid
|
LEFT JOIN $TABLE_TEK_CHECK_SINGLE ON $TABLE_TEK_CHECK_SINGLE.tcsid = $TABLE_TEK_CHECK_SINGLE_TOKEN.tcsid
|
||||||
WHERE
|
WHERE
|
||||||
$TABLE_DIAGNOSIS.package = ? AND
|
$TABLE_TEK_CHECK_SINGLE_TOKEN.tid = ? AND
|
||||||
$TABLE_DIAGNOSIS.token = ? AND
|
$TABLE_TEK_CHECK_SINGLE.matched IS NULL
|
||||||
$TABLE_TEK_CHECK.matched IS NULL
|
""", arrayOf(tid.toString())).use { cursor ->
|
||||||
""", arrayOf(packageName, token)).use { cursor ->
|
|
||||||
val list = arrayListOf<TemporaryExposureKey>()
|
val list = arrayListOf<TemporaryExposureKey>()
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
list.add(TemporaryExposureKey.TemporaryExposureKeyBuilder()
|
list.add(TemporaryExposureKey.TemporaryExposureKeyBuilder()
|
||||||
@ -234,8 +268,8 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyDiagnosisKeySearchResult(key: TemporaryExposureKey, matched: Boolean, database: SQLiteDatabase = writableDatabase) = database.run {
|
private fun applySingleDiagnosisKeySearchResult(key: TemporaryExposureKey, matched: Boolean, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||||
compileStatement("UPDATE $TABLE_TEK_CHECK SET matched = ? WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?;").use {
|
compileStatement("UPDATE $TABLE_TEK_CHECK_SINGLE SET matched = ? WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?;").use {
|
||||||
it.bindLong(1, if (matched) 1 else 0)
|
it.bindLong(1, if (matched) 1 else 0)
|
||||||
it.bindBlob(2, key.keyData)
|
it.bindBlob(2, key.keyData)
|
||||||
it.bindLong(3, key.rollingStartIntervalNumber.toLong())
|
it.bindLong(3, key.rollingStartIntervalNumber.toLong())
|
||||||
@ -244,16 +278,25 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun listMatchedDiagnosisKeys(packageName: String, token: String, database: SQLiteDatabase = readableDatabase) = database.run {
|
private fun applyDiagnosisFileKeySearchResult(tcfid: Long, key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||||
|
insert(TABLE_TEK_CHECK_FILE_MATCH, "NULL", ContentValues().apply {
|
||||||
|
put("tcfid", tcfid)
|
||||||
|
put("keyData", key.keyData)
|
||||||
|
put("rollingStartNumber", key.rollingStartIntervalNumber)
|
||||||
|
put("rollingPeriod", key.rollingPeriod)
|
||||||
|
put("transmissionRiskLevel", key.transmissionRiskLevel)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun listMatchedSingleDiagnosisKeys(tid: Long, database: SQLiteDatabase = readableDatabase) = database.run {
|
||||||
rawQuery("""
|
rawQuery("""
|
||||||
SELECT $TABLE_TEK_CHECK.keyData, $TABLE_TEK_CHECK.rollingStartNumber, $TABLE_TEK_CHECK.rollingPeriod, $TABLE_DIAGNOSIS.transmissionRiskLevel
|
SELECT $TABLE_TEK_CHECK_SINGLE.keyData, $TABLE_TEK_CHECK_SINGLE.rollingStartNumber, $TABLE_TEK_CHECK_SINGLE.rollingPeriod, $TABLE_TEK_CHECK_SINGLE_TOKEN.transmissionRiskLevel
|
||||||
FROM $TABLE_DIAGNOSIS
|
FROM $TABLE_TEK_CHECK_SINGLE_TOKEN
|
||||||
LEFT JOIN $TABLE_TEK_CHECK ON $TABLE_DIAGNOSIS.tcid = $TABLE_TEK_CHECK.tcid
|
JOIN $TABLE_TEK_CHECK_SINGLE ON $TABLE_TEK_CHECK_SINGLE.tcsid = $TABLE_TEK_CHECK_SINGLE_TOKEN.tcsid
|
||||||
WHERE
|
WHERE
|
||||||
$TABLE_DIAGNOSIS.package = ? AND
|
$TABLE_TEK_CHECK_SINGLE_TOKEN.tid = ? AND
|
||||||
$TABLE_DIAGNOSIS.token = ? AND
|
$TABLE_TEK_CHECK_SINGLE.matched = 1
|
||||||
$TABLE_TEK_CHECK.matched = 1
|
""", arrayOf(tid.toString())).use { cursor ->
|
||||||
""", arrayOf(packageName, token)).use { cursor ->
|
|
||||||
val list = arrayListOf<TemporaryExposureKey>()
|
val list = arrayListOf<TemporaryExposureKey>()
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
list.add(TemporaryExposureKey.TemporaryExposureKeyBuilder()
|
list.add(TemporaryExposureKey.TemporaryExposureKeyBuilder()
|
||||||
@ -267,36 +310,118 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun finishMatching(packageName: String, token: String, database: SQLiteDatabase = writableDatabase) {
|
private fun listMatchedFileDiagnosisKeys(tid: Long, database: SQLiteDatabase = readableDatabase) = database.run {
|
||||||
val start = System.currentTimeMillis()
|
rawQuery("""
|
||||||
|
SELECT $TABLE_TEK_CHECK_FILE_MATCH.keyData, $TABLE_TEK_CHECK_FILE_MATCH.rollingStartNumber, $TABLE_TEK_CHECK_FILE_MATCH.rollingPeriod, $TABLE_TEK_CHECK_FILE_MATCH.transmissionRiskLevel
|
||||||
|
FROM $TABLE_TEK_CHECK_FILE_TOKEN
|
||||||
|
JOIN $TABLE_TEK_CHECK_FILE_MATCH ON $TABLE_TEK_CHECK_FILE_MATCH.tcfid = $TABLE_TEK_CHECK_FILE_TOKEN.tcfid
|
||||||
|
WHERE
|
||||||
|
$TABLE_TEK_CHECK_FILE_TOKEN.tid = ?
|
||||||
|
""", arrayOf(tid.toString())).use { cursor ->
|
||||||
|
val list = arrayListOf<TemporaryExposureKey>()
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
list.add(TemporaryExposureKey.TemporaryExposureKeyBuilder()
|
||||||
|
.setKeyData(cursor.getBlob(0))
|
||||||
|
.setRollingStartIntervalNumber(cursor.getLong(1).toInt())
|
||||||
|
.setRollingPeriod(cursor.getLong(2).toInt())
|
||||||
|
.setTransmissionRiskLevel(cursor.getLong(3).toInt())
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finishSingleMatching(tid: Long, database: SQLiteDatabase = writableDatabase): Int {
|
||||||
val workQueue = LinkedBlockingQueue<Runnable>()
|
val workQueue = LinkedBlockingQueue<Runnable>()
|
||||||
val poolSize = Runtime.getRuntime().availableProcessors()
|
val poolSize = Runtime.getRuntime().availableProcessors()
|
||||||
val executor = ThreadPoolExecutor(poolSize, poolSize, 1, TimeUnit.SECONDS, workQueue)
|
val executor = ThreadPoolExecutor(poolSize, poolSize, 1, TimeUnit.SECONDS, workQueue)
|
||||||
val futures = arrayListOf<Future<*>>()
|
val futures = arrayListOf<Future<*>>()
|
||||||
val keys = listDiagnosisKeysPendingSearch(packageName, token, database)
|
val keys = listSingleDiagnosisKeysPendingSearch(tid, database)
|
||||||
val oldestRpi = oldestRpi
|
val oldestRpi = oldestRpi
|
||||||
for (key in keys) {
|
for (key in keys) {
|
||||||
if (oldestRpi == null || (key.rollingStartIntervalNumber + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS < oldestRpi) {
|
if ((key.rollingStartIntervalNumber + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS < oldestRpi) {
|
||||||
// Early ignore because key is older than since we started scanning.
|
// Early ignore because key is older than since we started scanning.
|
||||||
applyDiagnosisKeySearchResult(key, false, database)
|
applySingleDiagnosisKeySearchResult(key, false, database)
|
||||||
} else {
|
} else {
|
||||||
futures.add(executor.submit {
|
futures.add(executor.submit {
|
||||||
applyDiagnosisKeySearchResult(key, findMeasuredExposures(key).isNotEmpty(), database)
|
applySingleDiagnosisKeySearchResult(key, findMeasuredExposures(key).isNotEmpty(), database)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (future in futures) {
|
for (future in futures) {
|
||||||
future.get()
|
future.get()
|
||||||
}
|
}
|
||||||
val time = (System.currentTimeMillis() - start).toDouble() / 1000.0
|
|
||||||
executor.shutdown()
|
executor.shutdown()
|
||||||
Log.d(TAG, "Processed ${keys.size} new keys in ${time}s -> ${(keys.size.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s")
|
return keys.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findAllMeasuredExposures(packageName: String, token: String, database: SQLiteDatabase = readableDatabase): List<MeasuredExposure> {
|
fun finishFileMatching(tid: Long, hash: ByteArray, endTimestamp: Long, keys: List<TemporaryExposureKey>, updates: List<TemporaryExposureKey>, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||||
return listMatchedDiagnosisKeys(packageName, token, database).flatMap { findMeasuredExposures(it, database) }
|
beginTransaction()
|
||||||
|
try {
|
||||||
|
insert(TABLE_TEK_CHECK_FILE, "NULL", ContentValues().apply {
|
||||||
|
put("hash", ByteString.of(*hash).hex())
|
||||||
|
put("endTimestamp", endTimestamp)
|
||||||
|
put("keys", keys.size + updates.size)
|
||||||
|
})
|
||||||
|
val tcfid = getDiagnosisFileId(hash, this) ?: return
|
||||||
|
val workQueue = LinkedBlockingQueue<Runnable>()
|
||||||
|
val poolSize = Runtime.getRuntime().availableProcessors()
|
||||||
|
val executor = ThreadPoolExecutor(poolSize, poolSize, 1, TimeUnit.SECONDS, workQueue)
|
||||||
|
val futures = arrayListOf<Future<*>>()
|
||||||
|
val oldestRpi = oldestRpi
|
||||||
|
var ignored = 0
|
||||||
|
var processed = 0
|
||||||
|
var found = 0
|
||||||
|
for (key in keys) {
|
||||||
|
if ((key.rollingStartIntervalNumber + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS < oldestRpi) {
|
||||||
|
// Early ignore because key is older than since we started scanning.
|
||||||
|
ignored++;
|
||||||
|
} else {
|
||||||
|
futures.add(executor.submit {
|
||||||
|
if (findMeasuredExposures(key).isNotEmpty()) {
|
||||||
|
applyDiagnosisFileKeySearchResult(tcfid, key, this)
|
||||||
|
found++;
|
||||||
|
}
|
||||||
|
processed++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (future in futures) {
|
||||||
|
future.get()
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Processed $processed keys, found $found matches, ignored $ignored keys that are older than our scanning efforts ($oldestRpi)")
|
||||||
|
executor.shutdown()
|
||||||
|
for (update in updates) {
|
||||||
|
val matched = compileStatement("SELECT COUNT(tcsid) FROM $TABLE_TEK_CHECK_FILE_MATCH WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?").use {
|
||||||
|
it.bindBlob(1, update.keyData)
|
||||||
|
it.bindLong(2, update.rollingStartIntervalNumber.toLong())
|
||||||
|
it.bindLong(3, update.rollingPeriod.toLong())
|
||||||
|
it.simpleQueryForLong()
|
||||||
|
}
|
||||||
|
if (matched > 0) {
|
||||||
|
applyDiagnosisFileKeySearchResult(tcfid, update, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
insert(TABLE_TEK_CHECK_FILE_TOKEN, "NULL", ContentValues().apply {
|
||||||
|
put("tid", tid)
|
||||||
|
put("tcfid", tcfid)
|
||||||
|
})
|
||||||
|
setTransactionSuccessful()
|
||||||
|
} finally {
|
||||||
|
endTransaction()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun findAllSingleMeasuredExposures(tid: Long, database: SQLiteDatabase = readableDatabase): List<MeasuredExposure> {
|
||||||
|
return listMatchedSingleDiagnosisKeys(tid, database).flatMap { findMeasuredExposures(it, database) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findAllFileMeasuredExposures(tid: Long, database: SQLiteDatabase = readableDatabase): List<MeasuredExposure> {
|
||||||
|
return listMatchedFileDiagnosisKeys(tid, database).flatMap { findMeasuredExposures(it, database) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findAllMeasuredExposures(tid: Long, database: SQLiteDatabase = readableDatabase) = findAllSingleMeasuredExposures(tid, database) + findAllFileMeasuredExposures(tid, database)
|
||||||
|
|
||||||
private fun findMeasuredExposures(key: TemporaryExposureKey, database: SQLiteDatabase = readableDatabase): List<MeasuredExposure> {
|
private fun findMeasuredExposures(key: TemporaryExposureKey, database: SQLiteDatabase = readableDatabase): List<MeasuredExposure> {
|
||||||
val allRpis = key.generateAllRpiIds()
|
val allRpis = key.generateAllRpiIds()
|
||||||
val rpis = (0 until key.rollingPeriod).map { i ->
|
val rpis = (0 until key.rollingPeriod).map { i ->
|
||||||
@ -385,21 +510,23 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeConfiguration(packageName: String, token: String, configuration: ExposureConfiguration) = writableDatabase.run {
|
fun storeConfiguration(packageName: String, token: String, configuration: ExposureConfiguration, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||||
val update = update(TABLE_CONFIGURATIONS, ContentValues().apply { put("configuration", configuration.marshall()) }, "package = ? AND token = ?", arrayOf(packageName, token))
|
val update = update(TABLE_TOKENS, ContentValues().apply { put("configuration", configuration.marshall()) }, "package = ? AND token = ?", arrayOf(packageName, token))
|
||||||
if (update <= 0) {
|
if (update <= 0) {
|
||||||
insert(TABLE_CONFIGURATIONS, "NULL", ContentValues().apply {
|
insert(TABLE_TOKENS, "NULL", ContentValues().apply {
|
||||||
put("package", packageName)
|
put("package", packageName)
|
||||||
put("token", token)
|
put("token", token)
|
||||||
|
put("timestamp", System.currentTimeMillis())
|
||||||
put("configuration", configuration.marshall())
|
put("configuration", configuration.marshall())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
getTokenId(packageName, token, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadConfiguration(packageName: String, token: String): ExposureConfiguration? = readableDatabase.run {
|
fun loadConfiguration(packageName: String, token: String, database: SQLiteDatabase = readableDatabase): Pair<Long, ExposureConfiguration>? = database.run {
|
||||||
query(TABLE_CONFIGURATIONS, arrayOf("configuration"), "package = ? AND token = ?", arrayOf(packageName, token), null, null, null, null).use { cursor ->
|
query(TABLE_TOKENS, arrayOf("tid", "configuration"), "package = ? AND token = ?", arrayOf(packageName, token), null, null, null, null).use { cursor ->
|
||||||
if (cursor.moveToNext()) {
|
if (cursor.moveToNext()) {
|
||||||
ExposureConfiguration.CREATOR.unmarshall(cursor.getBlob(0))
|
cursor.getLong(0) to ExposureConfiguration.CREATOR.unmarshall(cursor.getBlob(1))
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -454,13 +581,13 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val oldestRpi: Long?
|
val oldestRpi: Long
|
||||||
get() = readableDatabase.run {
|
get() = readableDatabase.run {
|
||||||
query(TABLE_ADVERTISEMENTS, arrayOf("MIN(timestamp)"), null, null, null, null, null).use { cursor ->
|
query(TABLE_ADVERTISEMENTS, arrayOf("MIN(timestamp)"), null, null, null, null, null).use { cursor ->
|
||||||
if (cursor.moveToNext()) {
|
if (cursor.moveToNext()) {
|
||||||
cursor.getLong(0)
|
cursor.getLong(0)
|
||||||
} else {
|
} else {
|
||||||
null
|
System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -532,7 +659,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
|
|
||||||
override fun getWritableDatabase(): SQLiteDatabase {
|
override fun getWritableDatabase(): SQLiteDatabase {
|
||||||
if (this != instance) {
|
if (this != instance) {
|
||||||
throw IllegalStateException("Tried to open writable database from secondary instance")
|
throw IllegalStateException("Tried to open writable database from secondary instance. We are ${hashCode()} but primary is ${instance?.hashCode()}")
|
||||||
}
|
}
|
||||||
return super.getWritableDatabase()
|
return super.getWritableDatabase()
|
||||||
}
|
}
|
||||||
@ -560,19 +687,32 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val DB_NAME = "exposure.db"
|
private const val DB_NAME = "exposure.db"
|
||||||
private const val DB_VERSION = 4
|
private const val DB_VERSION = 5
|
||||||
private const val TABLE_ADVERTISEMENTS = "advertisements"
|
private const val TABLE_ADVERTISEMENTS = "advertisements"
|
||||||
private const val TABLE_APP_LOG = "app_log"
|
private const val TABLE_APP_LOG = "app_log"
|
||||||
private const val TABLE_TEK = "tek"
|
private const val TABLE_TEK = "tek"
|
||||||
private const val TABLE_TEK_CHECK = "tek_check"
|
|
||||||
private const val TABLE_DIAGNOSIS = "diagnosis"
|
|
||||||
private const val TABLE_CONFIGURATIONS = "configurations"
|
|
||||||
private const val TABLE_APP_PERMS = "app_perms"
|
private const val TABLE_APP_PERMS = "app_perms"
|
||||||
|
private const val TABLE_TOKENS = "tokens"
|
||||||
|
private const val TABLE_TEK_CHECK_SINGLE = "tek_check_single"
|
||||||
|
private const val TABLE_TEK_CHECK_SINGLE_TOKEN = "tek_check_single_token"
|
||||||
|
private const val TABLE_TEK_CHECK_FILE = "tek_check_file"
|
||||||
|
private const val TABLE_TEK_CHECK_FILE_TOKEN = "tek_check_file_token"
|
||||||
|
private const val TABLE_TEK_CHECK_FILE_MATCH = "tek_check_file_match"
|
||||||
|
|
||||||
|
@Deprecated(message = "No longer supported")
|
||||||
|
private const val TABLE_TEK_CHECK = "tek_check"
|
||||||
|
|
||||||
|
@Deprecated(message = "No longer supported")
|
||||||
|
private const val TABLE_DIAGNOSIS = "diagnosis"
|
||||||
|
|
||||||
|
@Deprecated(message = "No longer supported")
|
||||||
|
private const val TABLE_CONFIGURATIONS = "configurations"
|
||||||
|
|
||||||
private var instance: ExposureDatabase? = null
|
private var instance: ExposureDatabase? = null
|
||||||
fun ref(context: Context): ExposureDatabase = synchronized(this) {
|
fun ref(context: Context): ExposureDatabase = synchronized(this) {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
instance = ExposureDatabase(context.applicationContext)
|
instance = ExposureDatabase(context.applicationContext)
|
||||||
|
Log.d(TAG, "Created instance ${instance?.hashCode()} of database for ${context.javaClass.name}")
|
||||||
}
|
}
|
||||||
instance!!.ref()
|
instance!!.ref()
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,10 @@ class ExposureNotificationService : BaseService(TAG, GmsService.NEARBY_EXPOSURE)
|
|||||||
return permission
|
return permission
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPermission("android.permission.BLUETOOTH") ?: return
|
if (request.packageName != packageName) {
|
||||||
checkPermission("android.permission.INTERNET") ?: return
|
checkPermission("android.permission.BLUETOOTH") ?: return
|
||||||
|
checkPermission("android.permission.INTERNET") ?: return
|
||||||
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < 21) {
|
if (Build.VERSION.SDK_INT < 21) {
|
||||||
callback.onPostInitComplete(FAILED_NOT_SUPPORTED, null, null)
|
callback.onPostInitComplete(FAILED_NOT_SUPPORTED, null, null)
|
||||||
@ -39,7 +41,10 @@ class ExposureNotificationService : BaseService(TAG, GmsService.NEARBY_EXPOSURE)
|
|||||||
|
|
||||||
Log.d(TAG, "handleServiceRequest: " + request.packageName)
|
Log.d(TAG, "handleServiceRequest: " + request.packageName)
|
||||||
callback.onPostInitCompleteWithConnectionInfo(SUCCESS, ExposureNotificationServiceImpl(this, request.packageName), ConnectionInfo().apply {
|
callback.onPostInitCompleteWithConnectionInfo(SUCCESS, ExposureNotificationServiceImpl(this, request.packageName), ConnectionInfo().apply {
|
||||||
features = arrayOf(Feature("nearby_exposure_notification", 3))
|
features = arrayOf(
|
||||||
|
Feature("nearby_exposure_notification", 3),
|
||||||
|
Feature("nearby_exposure_notification_get_version", 1)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,25 +10,29 @@ import android.app.PendingIntent
|
|||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.Intent.*
|
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.android.gms.common.api.CommonStatusCodes
|
|
||||||
import com.google.android.gms.common.api.Status
|
import com.google.android.gms.common.api.Status
|
||||||
import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes
|
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
|
||||||
|
import com.google.android.gms.nearby.exposurenotification.ExposureInformation
|
||||||
import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes.*
|
import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes.*
|
||||||
import com.google.android.gms.nearby.exposurenotification.ExposureSummary
|
import com.google.android.gms.nearby.exposurenotification.ExposureSummary
|
||||||
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
||||||
import com.google.android.gms.nearby.exposurenotification.internal.*
|
import com.google.android.gms.nearby.exposurenotification.internal.*
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import org.microg.gms.common.Constants
|
||||||
import org.microg.gms.common.PackageUtils
|
import org.microg.gms.common.PackageUtils
|
||||||
import org.microg.gms.nearby.exposurenotification.Constants.*
|
import org.microg.gms.nearby.exposurenotification.Constants.*
|
||||||
import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyExport
|
import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyExport
|
||||||
import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyProto
|
import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyProto
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.security.MessageDigest
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipFile
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
class ExposureNotificationServiceImpl(private val context: Context, private val packageName: String) : INearbyExposureNotificationService.Stub() {
|
class ExposureNotificationServiceImpl(private val context: Context, private val packageName: String) : INearbyExposureNotificationService.Stub() {
|
||||||
private fun pendingConfirm(permission: String): PendingIntent {
|
private fun pendingConfirm(permission: String): PendingIntent {
|
||||||
@ -65,6 +69,14 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getVersion(params: GetVersionParams) {
|
||||||
|
params.callback.onResult(Status.SUCCESS, Constants.MAX_REFERENCE_VERSION.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCalibrationConfidence(params: GetCalibrationConfidenceParams) {
|
||||||
|
params.callback.onResult(Status.SUCCESS, currentDeviceInfo.confidence)
|
||||||
|
}
|
||||||
|
|
||||||
override fun start(params: StartParams) {
|
override fun start(params: StartParams) {
|
||||||
if (ExposurePreferences(context).enabled) return
|
if (ExposurePreferences(context).enabled) return
|
||||||
val status = confirmPermission(CONFIRM_ACTION_START)
|
val status = confirmPermission(CONFIRM_ACTION_START)
|
||||||
@ -90,7 +102,6 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
|||||||
Log.w(TAG, "Callback failed", e)
|
Log.w(TAG, "Callback failed", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isEnabled(params: IsEnabledParams) {
|
override fun isEnabled(params: IsEnabledParams) {
|
||||||
try {
|
try {
|
||||||
params.callback.onResult(Status.SUCCESS, ExposurePreferences(context).enabled)
|
params.callback.onResult(Status.SUCCESS, ExposurePreferences(context).enabled)
|
||||||
@ -129,67 +140,73 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
|||||||
.setTransmissionRiskLevel(transmission_risk_level ?: 0)
|
.setTransmissionRiskLevel(transmission_risk_level ?: 0)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private fun storeDiagnosisKeyExport(token: String, export: TemporaryExposureKeyExport): Int = ExposureDatabase.with(context) { database ->
|
private fun InputStream.copyToFile(outputFile: File) {
|
||||||
Log.d(TAG, "Importing keys from file ${export.start_timestamp?.let { Date(it * 1000) }} to ${export.end_timestamp?.let { Date(it * 1000) }}")
|
outputFile.outputStream().use { output ->
|
||||||
database.batchStoreDiagnosisKey(packageName, token, export.keys.map { it.toKey() })
|
copyTo(output)
|
||||||
database.batchUpdateDiagnosisKey(packageName, token, export.revised_keys.map { it.toKey() })
|
output.flush()
|
||||||
export.keys.size + export.revised_keys.size
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MessageDigest.digest(file: File): ByteArray = file.inputStream().use { input ->
|
||||||
|
val buf = ByteArray(4096)
|
||||||
|
var bytes = input.read(buf)
|
||||||
|
while (bytes != -1) {
|
||||||
|
update(buf, 0, bytes)
|
||||||
|
bytes = input.read(buf)
|
||||||
|
}
|
||||||
|
digest()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun provideDiagnosisKeys(params: ProvideDiagnosisKeysParams) {
|
override fun provideDiagnosisKeys(params: ProvideDiagnosisKeysParams) {
|
||||||
|
Log.w(TAG, "provideDiagnosisKeys() with $packageName/${params.token}")
|
||||||
|
val tid = ExposureDatabase.with(context) { database ->
|
||||||
|
if (params.configuration != null) {
|
||||||
|
database.storeConfiguration(packageName, params.token, params.configuration)
|
||||||
|
} else {
|
||||||
|
database.getTokenId(packageName, params.token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tid == null) {
|
||||||
|
Log.w(TAG, "Unknown token without configuration: $packageName/${params.token}")
|
||||||
|
try {
|
||||||
|
params.callback.onResult(Status.INTERNAL_ERROR)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Callback failed", e)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
Thread(Runnable {
|
Thread(Runnable {
|
||||||
ExposureDatabase.with(context) { database ->
|
ExposureDatabase.with(context) { database ->
|
||||||
if (params.configuration != null) {
|
|
||||||
database.storeConfiguration(packageName, params.token, params.configuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
|
|
||||||
// keys
|
// keys
|
||||||
params.keys?.let { database.batchStoreDiagnosisKey(packageName, params.token, it) }
|
params.keys?.let { database.batchStoreSingleDiagnosisKey(tid, it) }
|
||||||
|
|
||||||
|
var keys = params.keys?.size ?: 0
|
||||||
|
|
||||||
// Key files
|
// Key files
|
||||||
var keys = params.keys?.size ?: 0
|
val todoKeyFiles = arrayListOf<Pair<File, ByteArray>>()
|
||||||
for (file in params.keyFiles.orEmpty()) {
|
for (file in params.keyFiles.orEmpty()) {
|
||||||
try {
|
try {
|
||||||
ZipInputStream(ParcelFileDescriptor.AutoCloseInputStream(file)).use { stream ->
|
val cacheFile = File(context.cacheDir, "en-keyfile-${System.currentTimeMillis()}-${Random.nextInt()}.zip")
|
||||||
do {
|
ParcelFileDescriptor.AutoCloseInputStream(file).use { it.copyToFile(cacheFile) }
|
||||||
val entry = stream.nextEntry ?: break
|
val hash = MessageDigest.getInstance("SHA-256").digest(cacheFile)
|
||||||
if (entry.name == "export.bin") {
|
val storedKeys = database.storeDiagnosisFileUsed(tid, hash)
|
||||||
val prefix = ByteArray(16)
|
if (storedKeys != null) {
|
||||||
var totalBytesRead = 0
|
keys += storedKeys.toInt()
|
||||||
var bytesRead = 0
|
cacheFile.delete()
|
||||||
while (bytesRead != -1 && totalBytesRead < prefix.size) {
|
} else {
|
||||||
bytesRead = stream.read(prefix, totalBytesRead, prefix.size - totalBytesRead)
|
todoKeyFiles.add(cacheFile to hash)
|
||||||
if (bytesRead > 0) {
|
|
||||||
totalBytesRead += bytesRead
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (totalBytesRead == prefix.size && String(prefix).trim() == "EK Export v1") {
|
|
||||||
val fileKeys = storeDiagnosisKeyExport(params.token, TemporaryExposureKeyExport.ADAPTER.decode(stream))
|
|
||||||
keys += fileKeys
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "export.bin had invalid prefix")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stream.closeEntry()
|
|
||||||
} while (true);
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "Failed parsing file", e)
|
Log.w(TAG, "Failed parsing file", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val time = (System.currentTimeMillis() - start).toDouble() / 1000.0
|
|
||||||
Log.d(TAG, "$packageName/${params.token} provided $keys keys in ${time}s -> ${(keys.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s")
|
|
||||||
|
|
||||||
database.noteAppAction(packageName, "provideDiagnosisKeys", JSONObject().apply {
|
if (todoKeyFiles.size > 0) {
|
||||||
put("request_token", params.token)
|
val time = (System.currentTimeMillis() - start).toDouble() / 1000.0
|
||||||
put("request_keys_size", params.keys?.size)
|
Log.d(TAG, "$packageName/${params.token} processed $keys keys (${todoKeyFiles.size} files pending) in ${time}s -> ${(keys.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s")
|
||||||
put("request_keyFiles_size", params.keyFiles?.size)
|
}
|
||||||
put("request_keys_count", keys)
|
|
||||||
}.toString())
|
|
||||||
|
|
||||||
database.finishMatching(packageName, params.token)
|
|
||||||
|
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
try {
|
try {
|
||||||
@ -199,7 +216,47 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val match = database.findAllMeasuredExposures(packageName, params.token).isNotEmpty()
|
var newKeys = if (params.keys != null) database.finishSingleMatching(tid) else 0
|
||||||
|
for ((cacheFile, hash) in todoKeyFiles) {
|
||||||
|
ZipFile(cacheFile).use { zip ->
|
||||||
|
for (entry in zip.entries()) {
|
||||||
|
if (entry.name == "export.bin") {
|
||||||
|
val stream = zip.getInputStream(entry)
|
||||||
|
val prefix = ByteArray(16)
|
||||||
|
var totalBytesRead = 0
|
||||||
|
var bytesRead = 0
|
||||||
|
while (bytesRead != -1 && totalBytesRead < prefix.size) {
|
||||||
|
bytesRead = stream.read(prefix, totalBytesRead, prefix.size - totalBytesRead)
|
||||||
|
if (bytesRead > 0) {
|
||||||
|
totalBytesRead += bytesRead
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (totalBytesRead == prefix.size && String(prefix).trim() == "EK Export v1") {
|
||||||
|
val export = TemporaryExposureKeyExport.ADAPTER.decode(stream)
|
||||||
|
database.finishFileMatching(tid, hash, export.end_timestamp?.let { it * 1000 }
|
||||||
|
?: System.currentTimeMillis(), export.keys.map { it.toKey() }, export.revised_keys.map { it.toKey() })
|
||||||
|
keys += export.keys.size + export.revised_keys.size
|
||||||
|
newKeys += export.keys.size
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "export.bin had invalid prefix")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cacheFile.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
val time = (System.currentTimeMillis() - start).toDouble() / 1000.0
|
||||||
|
Log.d(TAG, "$packageName/${params.token} processed $keys keys ($newKeys new) in ${time}s -> ${(keys.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s")
|
||||||
|
|
||||||
|
database.noteAppAction(packageName, "provideDiagnosisKeys", JSONObject().apply {
|
||||||
|
put("request_token", params.token)
|
||||||
|
put("request_keys_size", params.keys?.size)
|
||||||
|
put("request_keyFiles_size", params.keyFiles?.size)
|
||||||
|
put("request_keys_count", keys)
|
||||||
|
}.toString())
|
||||||
|
|
||||||
|
val match = database.findAllMeasuredExposures(tid).isNotEmpty()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val intent = Intent(if (match) ACTION_EXPOSURE_STATE_UPDATED else ACTION_EXPOSURE_NOT_FOUND)
|
val intent = Intent(if (match) ACTION_EXPOSURE_STATE_UPDATED else ACTION_EXPOSURE_NOT_FOUND)
|
||||||
@ -215,16 +272,12 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getExposureSummary(params: GetExposureSummaryParams): Unit = ExposureDatabase.with(context) { database ->
|
override fun getExposureSummary(params: GetExposureSummaryParams): Unit = ExposureDatabase.with(context) { database ->
|
||||||
val configuration = database.loadConfiguration(packageName, params.token)
|
val pair = database.loadConfiguration(packageName, params.token)
|
||||||
if (configuration == null) {
|
val (configuration, exposures) = if (pair != null) {
|
||||||
try {
|
pair.second to database.findAllMeasuredExposures(pair.first).merge()
|
||||||
params.callback.onResult(Status.INTERNAL_ERROR, null)
|
} else {
|
||||||
} catch (e: Exception) {
|
ExposureConfiguration.ExposureConfigurationBuilder().build() to emptyList()
|
||||||
Log.w(TAG, "Callback failed", e)
|
|
||||||
}
|
|
||||||
return@with
|
|
||||||
}
|
}
|
||||||
val exposures = database.findAllMeasuredExposures(packageName, params.token).merge()
|
|
||||||
val response = ExposureSummary.ExposureSummaryBuilder()
|
val response = ExposureSummary.ExposureSummaryBuilder()
|
||||||
.setDaysSinceLastExposure(exposures.map { it.daysSinceExposure }.min()?.toInt() ?: 0)
|
.setDaysSinceLastExposure(exposures.map { it.daysSinceExposure }.min()?.toInt() ?: 0)
|
||||||
.setMatchedKeyCount(exposures.map { it.key }.distinct().size)
|
.setMatchedKeyCount(exposures.map { it.key }.distinct().size)
|
||||||
@ -255,18 +308,13 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getExposureInformation(params: GetExposureInformationParams): Unit = ExposureDatabase.with(context) { database ->
|
override fun getExposureInformation(params: GetExposureInformationParams): Unit = ExposureDatabase.with(context) { database ->
|
||||||
// TODO: Notify user?
|
val pair = database.loadConfiguration(packageName, params.token)
|
||||||
val configuration = database.loadConfiguration(packageName, params.token)
|
val response = if (pair != null) {
|
||||||
if (configuration == null) {
|
database.findAllMeasuredExposures(pair.first).merge().map {
|
||||||
try {
|
it.toExposureInformation(pair.second)
|
||||||
params.callback.onResult(Status.INTERNAL_ERROR, null)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Callback failed", e)
|
|
||||||
}
|
}
|
||||||
return@with
|
} else {
|
||||||
}
|
emptyList()
|
||||||
val response = database.findAllMeasuredExposures(packageName, params.token).merge().map {
|
|
||||||
it.toExposureInformation(configuration)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
database.noteAppAction(packageName, "getExposureInformation", JSONObject().apply {
|
database.noteAppAction(packageName, "getExposureInformation", JSONObject().apply {
|
||||||
@ -280,6 +328,26 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getExposureWindows(params: GetExposureWindowsParams) {
|
||||||
|
Log.w(TAG, "Not yet implemented: getExposureWindows")
|
||||||
|
params.callback.onResult(Status.INTERNAL_ERROR, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDailySummaries(params: GetDailySummariesParams) {
|
||||||
|
Log.w(TAG, "Not yet implemented: getDailySummaries")
|
||||||
|
params.callback.onResult(Status.INTERNAL_ERROR, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setDiagnosisKeysDataMapping(params: SetDiagnosisKeysDataMappingParams) {
|
||||||
|
Log.w(TAG, "Not yet implemented: setDiagnosisKeysDataMapping")
|
||||||
|
params.callback.onResult(Status.INTERNAL_ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDiagnosisKeysDataMapping(params: GetDiagnosisKeysDataMappingParams) {
|
||||||
|
Log.w(TAG, "Not yet implemented: getDiagnosisKeysDataMapping")
|
||||||
|
params.callback.onResult(Status.INTERNAL_ERROR, null)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||||
if (super.onTransact(code, data, reply, flags)) return true
|
if (super.onTransact(code, data, reply, flags)) return true
|
||||||
Log.d(TAG, "onTransact [unknown]: $code, $data, $flags")
|
Log.d(TAG, "onTransact [unknown]: $code, $data, $flags")
|
||||||
|
@ -28,6 +28,8 @@
|
|||||||
# Keep AutoSafeParcelables
|
# Keep AutoSafeParcelables
|
||||||
-keep public class * extends org.microg.safeparcel.AutoSafeParcelable {
|
-keep public class * extends org.microg.safeparcel.AutoSafeParcelable {
|
||||||
@org.microg.safeparcel.SafeParceled *;
|
@org.microg.safeparcel.SafeParceled *;
|
||||||
|
@org.microg.safeparcel.SafeParcelable.Field *;
|
||||||
|
<init>(...);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Keep form data
|
# Keep form data
|
||||||
|
Loading…
x
Reference in New Issue
Block a user