mirror of
https://github.com/TeamVanced/VancedMicroG
synced 2025-01-14 13:17:31 +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;
|
||||
|
||||
|
@ -1,3 +1,11 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.common.api;
|
||||
|
||||
import android.app.Activity;
|
||||
|
@ -23,6 +23,5 @@ android {
|
||||
|
||||
dependencies {
|
||||
api project(':play-services-basement')
|
||||
|
||||
api project(':play-services-base-api')
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.nearby.exposurenotification;
|
||||
|
||||
parcelable 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.GetExposureSummaryParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureInformationParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetExposureWindowsParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetVersionParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetCalibrationConfidenceParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetDailySummariesParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.SetDiagnosisKeysDataMappingParams;
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.GetDiagnosisKeysDataMappingParams;
|
||||
|
||||
interface INearbyExposureNotificationService{
|
||||
void start(in StartParams params) = 0;
|
||||
@ -22,4 +28,11 @@ interface INearbyExposureNotificationService{
|
||||
|
||||
void getExposureSummary(in GetExposureSummaryParams params) = 6;
|
||||
void getExposureInformation(in GetExposureInformationParams params) = 7;
|
||||
|
||||
void getExposureWindows(in GetExposureWindowsParams params) = 12;
|
||||
void getVersion(in GetVersionParams params) = 13;
|
||||
void getCalibrationConfidence(in GetCalibrationConfidenceParams params) = 14;
|
||||
void getDailySummaries(in GetDailySummariesParams params) = 15;
|
||||
void setDiagnosisKeysDataMapping(in SetDiagnosisKeysDataMappingParams params) = 16;
|
||||
void getDiagnosisKeysDataMapping(in GetDiagnosisKeysDataMappingParams params) = 17;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
import org.microg.gms.common.PublicApi;
|
||||
|
||||
/**
|
||||
* Risk level defined for an {@link TemporaryExposureKey}.
|
||||
*/
|
||||
@PublicApi
|
||||
public @interface RiskLevel {
|
||||
int RISK_LEVEL_INVALID = 0;
|
||||
int RISK_LEVEL_LOWEST = 1;
|
||||
@ -18,4 +24,7 @@ public @interface RiskLevel {
|
||||
int RISK_LEVEL_HIGH = 6;
|
||||
int RISK_LEVEL_VERY_HIGH = 7;
|
||||
int RISK_LEVEL_HIGHEST = 8;
|
||||
|
||||
@PublicApi(exclude = true)
|
||||
int VALUES = 9;
|
||||
}
|
||||
|
@ -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)
|
||||
public String token;
|
||||
|
||||
private ProvideDiagnosisKeysParams() {
|
||||
}
|
||||
|
||||
public ProvideDiagnosisKeysParams(IStatusCallback callback, List<TemporaryExposureKey> keys, List<ParcelFileDescriptor> keyFiles, ExposureConfiguration configuration, String token) {
|
||||
this(callback, keyFiles, configuration, token);
|
||||
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)
|
||||
private var setCallback: AdvertisingSetCallback? = null
|
||||
private var setCallback: Any? = null
|
||||
private val trigger = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.action == "android.bluetooth.adapter.action.STATE_CHANGED") {
|
||||
@ -83,6 +83,7 @@ class AdvertiserService : Service() {
|
||||
super.onDestroy()
|
||||
unregisterReceiver(trigger)
|
||||
stopOrRestartAdvertising()
|
||||
handler.removeCallbacks(startLaterRunnable)
|
||||
database.unref()
|
||||
}
|
||||
|
||||
@ -114,7 +115,7 @@ class AdvertiserService : Service() {
|
||||
0x00 // Reserved
|
||||
)
|
||||
VERSION_1_1 -> byteArrayOf(
|
||||
(version + currentDeviceInfo.confidence * 4).toByte(), // Version and flags
|
||||
(version + currentDeviceInfo.confidence.toByte() * 4).toByte(), // Version and flags
|
||||
(currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power
|
||||
0x00, // Reserved
|
||||
0x00 // Reserved
|
||||
@ -134,7 +135,7 @@ class AdvertiserService : Service() {
|
||||
.setTxPowerLevel(AdvertisingSetParameters.TX_POWER_LOW)
|
||||
.setConnectable(false)
|
||||
.build()
|
||||
advertiser.startAdvertisingSet(params, data, null, null, null, setCallback)
|
||||
advertiser.startAdvertisingSet(params, data, null, null, null, setCallback as AdvertisingSetCallback)
|
||||
} else {
|
||||
nextSend = nextSend.coerceAtMost(180000)
|
||||
val settings = Builder()
|
||||
@ -189,21 +190,13 @@ class AdvertiserService : Service() {
|
||||
advertising = false
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
wantStartAdvertising = true
|
||||
advertiser?.stopAdvertisingSet(setCallback)
|
||||
advertiser?.stopAdvertisingSet(setCallback as AdvertisingSetCallback)
|
||||
} else {
|
||||
advertiser?.stopAdvertising(callback)
|
||||
}
|
||||
handler.postDelayed(startLaterRunnable, 1000)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ACTION_RESTART_ADVERTISING = "org.microg.gms.nearby.exposurenotification.RESTART_ADVERTISING"
|
||||
|
||||
fun isNeeded(context: Context): Boolean {
|
||||
return ExposurePreferences(context).enabled
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(26)
|
||||
inner class SetCallback : AdvertisingSetCallback() {
|
||||
override fun onAdvertisingSetStarted(advertisingSet: AdvertisingSet?, txPower: Int, status: Int) {
|
||||
@ -219,4 +212,13 @@ class AdvertiserService : Service() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private const val ACTION_RESTART_ADVERTISING = "org.microg.gms.nearby.exposurenotification.RESTART_ADVERTISING"
|
||||
|
||||
fun isNeeded(context: Context): Boolean {
|
||||
return ExposurePreferences(context).enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
package org.microg.gms.nearby.exposurenotification
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.LifecycleService
|
||||
@ -25,14 +27,21 @@ class CleanupService : LifecycleService() {
|
||||
}
|
||||
ExposurePreferences(this@CleanupService).lastCleanup = System.currentTimeMillis()
|
||||
}
|
||||
stopSelf()
|
||||
stop()
|
||||
}
|
||||
} else {
|
||||
stopSelf()
|
||||
stop()
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
val pendingIntent = PendingIntent.getService(applicationContext, CleanupService::class.java.name.hashCode(), Intent(applicationContext, CleanupService::class.java), PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
alarmManager.set(AlarmManager.RTC, ExposurePreferences(this).lastCleanup + CLEANUP_INTERVAL, pendingIntent)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun isNeeded(context: Context): Boolean {
|
||||
return ExposurePreferences(context).let {
|
||||
|
@ -6,6 +6,7 @@
|
||||
package org.microg.gms.nearby.exposurenotification
|
||||
|
||||
import android.os.ParcelUuid
|
||||
import com.google.android.gms.nearby.exposurenotification.CalibrationConfidence
|
||||
import java.util.*
|
||||
|
||||
const val TAG = "ExposureNotification"
|
||||
@ -17,10 +18,10 @@ const val SCANNING_TIME = 20 // Google uses 4s + 13s (if Bluetooth is used by so
|
||||
const val SCANNING_TIME_MS = SCANNING_TIME * 1000L
|
||||
|
||||
const val ROLLING_WINDOW_LENGTH = 10 * 60
|
||||
const val ROLLING_WINDOW_LENGTH_MS = ROLLING_WINDOW_LENGTH * 1000
|
||||
const val ROLLING_WINDOW_LENGTH_MS = ROLLING_WINDOW_LENGTH * 1000L
|
||||
const val ROLLING_PERIOD = 144
|
||||
const val ALLOWED_KEY_OFFSET_MS = 60 * 60 * 1000
|
||||
const val MINIMUM_EXPOSURE_DURATION_MS = 0
|
||||
const val ALLOWED_KEY_OFFSET_MS = 60 * 60 * 1000L
|
||||
const val MINIMUM_EXPOSURE_DURATION_MS = 0L
|
||||
const val KEEP_DAYS = 14
|
||||
|
||||
const val ACTION_CONFIRM = "org.microg.gms.nearby.exposurenotification.CONFIRM"
|
||||
@ -37,27 +38,7 @@ const val PERMISSION_EXPOSURE_CALLBACK = "com.google.android.gms.nearby.exposure
|
||||
const val TX_POWER_LOW = -15
|
||||
|
||||
const val ADVERTISER_OFFSET = 60 * 1000
|
||||
const val CLEANUP_INTERVAL = 24 * 60 * 60 * 1000
|
||||
const val CLEANUP_INTERVAL = 24 * 60 * 60 * 1000L
|
||||
|
||||
const val VERSION_1_0: Byte = 0x40
|
||||
const val VERSION_1_1: Byte = 0x50
|
||||
|
||||
/**
|
||||
* No calibration data, using fleet-wide as default options.
|
||||
*/
|
||||
const val CONFIDENCE_LOWEST: Byte = 0
|
||||
|
||||
/**
|
||||
* Using average calibration over models from manufacturer.
|
||||
*/
|
||||
const val CONFIDENCE_LOW: Byte = 1
|
||||
|
||||
/**
|
||||
* Using single-antenna orientation for a similar model.
|
||||
*/
|
||||
const val CONFIDENCE_MEDIUM: Byte = 2
|
||||
|
||||
/**
|
||||
* Using significant calibration data for this model.
|
||||
*/
|
||||
const val CONFIDENCE_HIGH: Byte = 3
|
||||
|
@ -10,14 +10,15 @@ package org.microg.gms.nearby.exposurenotification
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.google.android.gms.nearby.exposurenotification.CalibrationConfidence
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
data class DeviceInfo(val oem: String, val model: String, val txPowerCorrection: Byte, val rssiCorrection: Byte, val confidence: Byte = CONFIDENCE_MEDIUM)
|
||||
data class DeviceInfo(val oem: String, val model: String, val txPowerCorrection: Byte, val rssiCorrection: Byte, @CalibrationConfidence val confidence: Int = CalibrationConfidence.MEDIUM)
|
||||
|
||||
private var knownDeviceInfo: DeviceInfo? = null
|
||||
|
||||
fun averageCurrentDeviceInfo(oem: String, model: String, deviceInfos: List<DeviceInfo>, confidence: Byte = CONFIDENCE_LOW): DeviceInfo =
|
||||
DeviceInfo(oem, model, deviceInfos.map { it.txPowerCorrection }.average().roundToInt().toByte(), deviceInfos.map { it.txPowerCorrection }.average().roundToInt().toByte(), CONFIDENCE_LOW)
|
||||
fun averageCurrentDeviceInfo(oem: String, model: String, deviceInfos: List<DeviceInfo>, @CalibrationConfidence confidence: Int = CalibrationConfidence.LOW): DeviceInfo =
|
||||
DeviceInfo(oem, model, deviceInfos.map { it.txPowerCorrection }.average().roundToInt().toByte(), deviceInfos.map { it.txPowerCorrection }.average().roundToInt().toByte(), CalibrationConfidence.LOW)
|
||||
|
||||
val currentDeviceInfo: DeviceInfo
|
||||
get() {
|
||||
@ -36,7 +37,7 @@ val currentDeviceInfo: DeviceInfo
|
||||
}
|
||||
else -> {
|
||||
// Fallback to all device average
|
||||
averageCurrentDeviceInfo(Build.MANUFACTURER, Build.MODEL, allDeviceInfos, CONFIDENCE_LOWEST)
|
||||
averageCurrentDeviceInfo(Build.MANUFACTURER, Build.MODEL, allDeviceInfos, CalibrationConfidence.LOWEST)
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Selected $deviceInfo")
|
||||
|
@ -17,12 +17,11 @@ import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
|
||||
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
||||
import okio.ByteString
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.ThreadPoolExecutor
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@TargetApi(21)
|
||||
@ -43,28 +42,41 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
Log.d(TAG, "Upgrading database from $oldVersion to $newVersion")
|
||||
if (oldVersion < 1) {
|
||||
Log.d(TAG, "Creating tables for version >= 1")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_ADVERTISEMENTS(rpi BLOB NOT NULL, aem BLOB NOT NULL, timestamp INTEGER NOT NULL, rssi INTEGER NOT NULL, duration INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(rpi, timestamp));")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_ADVERTISEMENTS}_rpi ON $TABLE_ADVERTISEMENTS(rpi);")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_ADVERTISEMENTS}_timestamp ON $TABLE_ADVERTISEMENTS(timestamp);")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_APP_LOG(package TEXT NOT NULL, timestamp INTEGER NOT NULL, method TEXT NOT NULL, args TEXT);")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_APP_LOG}_package_timestamp ON $TABLE_APP_LOG(package, timestamp);")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK(keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL);")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_CONFIGURATIONS(package TEXT NOT NULL, token TEXT NOT NULL, configuration BLOB, PRIMARY KEY(package, token))")
|
||||
}
|
||||
if (oldVersion < 2) {
|
||||
db.execSQL("DROP TABLE IF EXISTS $TABLE_TEK_CHECK;")
|
||||
db.execSQL("DROP TABLE IF EXISTS $TABLE_DIAGNOSIS;")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK(tcid INTEGER PRIMARY KEY, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, matched INTEGER, UNIQUE(keyData, rollingStartNumber, rollingPeriod));")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_DIAGNOSIS(package TEXT NOT NULL, token TEXT NOT NULL, tcid INTEGER REFERENCES $TABLE_TEK_CHECK(tcid) ON DELETE CASCADE, transmissionRiskLevel INTEGER NOT NULL);")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_DIAGNOSIS}_package_token ON $TABLE_DIAGNOSIS(package, token);")
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
Log.d(TAG, "Creating tables for version >= 3")
|
||||
db.execSQL("CREATE TABLE $TABLE_APP_PERMS(package TEXT NOT NULL, sig TEXT NOT NULL, perm TEXT NOT NULL, timestamp INTEGER NOT NULL);")
|
||||
}
|
||||
if (oldVersion < 4) {
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_DIAGNOSIS}_tcid ON $TABLE_DIAGNOSIS(tcid);")
|
||||
if (oldVersion < 5) {
|
||||
Log.d(TAG, "Dropping legacy tables")
|
||||
db.execSQL("DROP TABLE IF EXISTS $TABLE_CONFIGURATIONS;")
|
||||
db.execSQL("DROP TABLE IF EXISTS $TABLE_DIAGNOSIS;")
|
||||
db.execSQL("DROP TABLE IF EXISTS $TABLE_TEK_CHECK;")
|
||||
Log.d(TAG, "Creating tables for version >= 3")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TOKENS(tid INTEGER PRIMARY KEY, package TEXT NOT NULL, token TEXT NOT NULL, timestamp INTEGER NOT NULL, configuration BLOB);")
|
||||
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TOKENS}_package_token ON $TABLE_TOKENS(package, token);")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_SINGLE(tcsid INTEGER PRIMARY KEY, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, matched INTEGER);")
|
||||
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_SINGLE}_key ON $TABLE_TEK_CHECK_SINGLE(keyData, rollingStartNumber, rollingPeriod);")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_SINGLE_TOKEN(tcsid INTEGER REFERENCES $TABLE_TEK_CHECK_SINGLE(tcsid) ON DELETE CASCADE, tid INTEGER REFERENCES $TABLE_TOKENS(tid) ON DELETE CASCADE, transmissionRiskLevel INTEGER NOT NULL, UNIQUE(tcsid, tid));")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_SINGLE_TOKEN}_tid ON $TABLE_TEK_CHECK_SINGLE_TOKEN(tid);")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE(tcfid INTEGER PRIMARY KEY, hash TEXT NOT NULL, endTimestamp INTEGER NOT NULL, keys INTEGER NOT NULL);")
|
||||
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE}_hash ON $TABLE_TEK_CHECK_FILE(hash);")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE_TOKEN(tcfid INTEGER REFERENCES $TABLE_TEK_CHECK_FILE(tcfid) ON DELETE CASCADE, tid INTEGER REFERENCES $TABLE_TOKENS(tid) ON DELETE CASCADE, UNIQUE(tcfid, tid));")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_TOKEN}_tid ON $TABLE_TEK_CHECK_FILE_TOKEN(tid);")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE_MATCH(tcfid INTEGER REFERENCES $TABLE_TEK_CHECK_FILE(tcfid) ON DELETE CASCADE, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, transmissionRiskLevel INTEGER NOT NULL, UNIQUE(tcfid, keyData, rollingStartNumber, rollingPeriod));")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_MATCH}_tcfid ON $TABLE_TEK_CHECK_FILE_MATCH(tcfid);")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_MATCH}_key ON $TABLE_TEK_CHECK_FILE_MATCH(keyData, rollingStartNumber, rollingPeriod);")
|
||||
}
|
||||
Log.d(TAG, "Finished database upgrade")
|
||||
}
|
||||
|
||||
fun SQLiteDatabase.delete(table: String, whereClause: String, args: LongArray): Int =
|
||||
@ -74,23 +86,21 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
}
|
||||
|
||||
fun dailyCleanup() = writableDatabase.run {
|
||||
beginTransaction()
|
||||
try {
|
||||
val rollingStartTime = currentRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong())
|
||||
val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime))
|
||||
Log.d(TAG, "Deleted on daily cleanup: $advertisements adv")
|
||||
val appLogEntries = delete(TABLE_APP_LOG, "timestamp < ?", longArrayOf(rollingStartTime))
|
||||
Log.d(TAG, "Deleted on daily cleanup: $appLogEntries applogs")
|
||||
val temporaryExposureKeys = delete(TABLE_TEK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS))
|
||||
Log.d(TAG, "Deleted on daily cleanup: $temporaryExposureKeys teks")
|
||||
val checkedTemporaryExposureKeys = delete(TABLE_TEK_CHECK, "rollingStartNumber < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS - ROLLING_PERIOD))
|
||||
Log.d(TAG, "Deleted on daily cleanup: $checkedTemporaryExposureKeys cteks")
|
||||
val appPerms = delete(TABLE_APP_PERMS, "timestamp < ?", longArrayOf(System.currentTimeMillis() - CONFIRM_PERMISSION_VALIDITY))
|
||||
Log.d(TAG, "Deleted on daily cleanup: $appPerms perms")
|
||||
setTransactionSuccessful()
|
||||
} finally {
|
||||
endTransaction()
|
||||
}
|
||||
val rollingStartTime = currentRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong())
|
||||
val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime))
|
||||
Log.d(TAG, "Deleted on daily cleanup: $advertisements adv")
|
||||
val appLogEntries = delete(TABLE_APP_LOG, "timestamp < ?", longArrayOf(rollingStartTime))
|
||||
Log.d(TAG, "Deleted on daily cleanup: $appLogEntries applogs")
|
||||
val temporaryExposureKeys = delete(TABLE_TEK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS))
|
||||
Log.d(TAG, "Deleted on daily cleanup: $temporaryExposureKeys teks")
|
||||
val singleCheckedTemporaryExposureKeys = delete(TABLE_TEK_CHECK_SINGLE, "rollingStartNumber < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS - ROLLING_PERIOD))
|
||||
Log.d(TAG, "Deleted on daily cleanup: $singleCheckedTemporaryExposureKeys tcss")
|
||||
val fileCheckedTemporaryExposureKeys = delete(TABLE_TEK_CHECK_FILE, "endTimestamp < ?", longArrayOf(rollingStartTime))
|
||||
Log.d(TAG, "Deleted on daily cleanup: $fileCheckedTemporaryExposureKeys tcfs")
|
||||
val appPerms = delete(TABLE_APP_PERMS, "timestamp < ?", longArrayOf(System.currentTimeMillis() - CONFIRM_PERMISSION_VALIDITY))
|
||||
Log.d(TAG, "Deleted on daily cleanup: $appPerms perms")
|
||||
execSQL("VACUUM;")
|
||||
Log.d(TAG, "Done vacuuming")
|
||||
}
|
||||
|
||||
fun grantPermission(packageName: String, signatureDigest: String, permission: String, timestamp: Long = System.currentTimeMillis()) = writableDatabase.run {
|
||||
@ -132,7 +142,11 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
|
||||
fun deleteAllCollectedAdvertisements() = writableDatabase.run {
|
||||
delete(TABLE_ADVERTISEMENTS, null, null)
|
||||
update(TABLE_TEK_CHECK, ContentValues().apply {
|
||||
delete(TABLE_TEK_CHECK_FILE_MATCH, null, null)
|
||||
update(TABLE_TEK_CHECK_SINGLE, ContentValues().apply {
|
||||
put("matched", 0)
|
||||
}, null, null)
|
||||
update(TABLE_TEK_CHECK_FILE, ContentValues().apply {
|
||||
put("matched", 0)
|
||||
}, null, null)
|
||||
}
|
||||
@ -155,15 +169,15 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
})
|
||||
}
|
||||
|
||||
private fun getTekCheckId(key: TemporaryExposureKey, mayInsert: Boolean = false, database: SQLiteDatabase = if (mayInsert) writableDatabase else readableDatabase): Long? = database.run {
|
||||
private fun getTekCheckSingleId(key: TemporaryExposureKey, mayInsert: Boolean = false, database: SQLiteDatabase = if (mayInsert) writableDatabase else readableDatabase): Long? = database.run {
|
||||
if (mayInsert) {
|
||||
insertWithOnConflict(TABLE_TEK_CHECK, "NULL", ContentValues().apply {
|
||||
insertWithOnConflict(TABLE_TEK_CHECK_SINGLE, "NULL", ContentValues().apply {
|
||||
put("keyData", key.keyData)
|
||||
put("rollingStartNumber", key.rollingStartIntervalNumber)
|
||||
put("rollingPeriod", key.rollingPeriod)
|
||||
}, CONFLICT_IGNORE)
|
||||
}
|
||||
compileStatement("SELECT tcid FROM $TABLE_TEK_CHECK WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?").use {
|
||||
compileStatement("SELECT tcsid FROM $TABLE_TEK_CHECK_SINGLE WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?").use {
|
||||
it.bindBlob(1, key.keyData)
|
||||
it.bindLong(2, key.rollingStartIntervalNumber.toLong())
|
||||
it.bindLong(3, key.rollingPeriod.toLong())
|
||||
@ -171,57 +185,77 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
}
|
||||
}
|
||||
|
||||
fun storeDiagnosisKey(packageName: String, token: String, key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
val tcid = getTekCheckId(key, true, database)
|
||||
insert(TABLE_DIAGNOSIS, "NULL", ContentValues().apply {
|
||||
put("package", packageName)
|
||||
put("token", token)
|
||||
put("tcid", tcid)
|
||||
fun getTokenId(packageName: String, token: String, database: SQLiteDatabase = readableDatabase) = database.run {
|
||||
query(TABLE_TOKENS, arrayOf("tid"), "package = ? AND token = ?", arrayOf(packageName, token), null, null, null, null).use { cursor ->
|
||||
if (cursor.moveToNext()) {
|
||||
cursor.getLong(0)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeSingleDiagnosisKey(tid: Long, key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
val tcsid = getTekCheckSingleId(key, true, database)
|
||||
insert(TABLE_TEK_CHECK_SINGLE_TOKEN, "NULL", ContentValues().apply {
|
||||
put("tid", tid)
|
||||
put("tcsid", tcsid)
|
||||
put("transmissionRiskLevel", key.transmissionRiskLevel)
|
||||
})
|
||||
}
|
||||
|
||||
fun batchStoreDiagnosisKey(packageName: String, token: String, keys: List<TemporaryExposureKey>, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
fun batchStoreSingleDiagnosisKey(tid: Long, keys: List<TemporaryExposureKey>, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
beginTransaction()
|
||||
try {
|
||||
keys.forEach { storeDiagnosisKey(packageName, token, it, database) }
|
||||
keys.forEach { storeSingleDiagnosisKey(tid, it, database) }
|
||||
setTransactionSuccessful()
|
||||
} finally {
|
||||
endTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateDiagnosisKey(packageName: String, token: String, key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
val tcid = getTekCheckId(key, false, database) ?: return 0
|
||||
compileStatement("UPDATE $TABLE_DIAGNOSIS SET transmissionRiskLevel = ? WHERE package = ? AND token = ? AND tcid = ?;").use {
|
||||
it.bindLong(1, key.transmissionRiskLevel.toLong())
|
||||
it.bindString(2, packageName)
|
||||
it.bindString(3, token)
|
||||
it.bindLong(4, tcid)
|
||||
it.executeUpdateDelete()
|
||||
fun getDiagnosisFileId(hash: ByteArray, database: SQLiteDatabase = readableDatabase) = database.run {
|
||||
val hexHash = ByteString.of(*hash).hex()
|
||||
query(TABLE_TEK_CHECK_FILE, arrayOf("tcfid"), "hash = ?", arrayOf(hexHash), null, null, null, null).use { cursor ->
|
||||
if (cursor.moveToNext()) {
|
||||
cursor.getLong(0)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun batchUpdateDiagnosisKey(packageName: String, token: String, keys: List<TemporaryExposureKey>, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
beginTransaction()
|
||||
try {
|
||||
keys.forEach { updateDiagnosisKey(packageName, token, it, database) }
|
||||
setTransactionSuccessful()
|
||||
} finally {
|
||||
endTransaction()
|
||||
fun storeDiagnosisFileUsed(tid: Long, tcfid: Long, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
insert(TABLE_TEK_CHECK_FILE_TOKEN, "NULL", ContentValues().apply {
|
||||
put("tid", tid)
|
||||
put("tcfid", tcfid)
|
||||
})
|
||||
}
|
||||
|
||||
fun storeDiagnosisFileUsed(tid: Long, hash: ByteArray, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
val hexHash = ByteString.of(*hash).hex()
|
||||
query(TABLE_TEK_CHECK_FILE, arrayOf("tcfid", "keys"), "hash = ?", arrayOf(hexHash), null, null, null, null).use { cursor ->
|
||||
if (cursor.moveToNext()) {
|
||||
insert(TABLE_TEK_CHECK_FILE_TOKEN, "NULL", ContentValues().apply {
|
||||
put("tid", tid)
|
||||
put("tcfid", cursor.getLong(0))
|
||||
})
|
||||
cursor.getLong(1)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun listDiagnosisKeysPendingSearch(packageName: String, token: String, database: SQLiteDatabase = readableDatabase) = database.run {
|
||||
private fun listSingleDiagnosisKeysPendingSearch(tid: Long, database: SQLiteDatabase = readableDatabase) = database.run {
|
||||
rawQuery("""
|
||||
SELECT $TABLE_TEK_CHECK.keyData, $TABLE_TEK_CHECK.rollingStartNumber, $TABLE_TEK_CHECK.rollingPeriod
|
||||
FROM $TABLE_DIAGNOSIS
|
||||
LEFT JOIN $TABLE_TEK_CHECK ON $TABLE_DIAGNOSIS.tcid = $TABLE_TEK_CHECK.tcid
|
||||
SELECT $TABLE_TEK_CHECK_SINGLE.keyData, $TABLE_TEK_CHECK_SINGLE.rollingStartNumber, $TABLE_TEK_CHECK_SINGLE.rollingPeriod
|
||||
FROM $TABLE_TEK_CHECK_SINGLE_TOKEN
|
||||
LEFT JOIN $TABLE_TEK_CHECK_SINGLE ON $TABLE_TEK_CHECK_SINGLE.tcsid = $TABLE_TEK_CHECK_SINGLE_TOKEN.tcsid
|
||||
WHERE
|
||||
$TABLE_DIAGNOSIS.package = ? AND
|
||||
$TABLE_DIAGNOSIS.token = ? AND
|
||||
$TABLE_TEK_CHECK.matched IS NULL
|
||||
""", arrayOf(packageName, token)).use { cursor ->
|
||||
$TABLE_TEK_CHECK_SINGLE_TOKEN.tid = ? AND
|
||||
$TABLE_TEK_CHECK_SINGLE.matched IS NULL
|
||||
""", arrayOf(tid.toString())).use { cursor ->
|
||||
val list = arrayListOf<TemporaryExposureKey>()
|
||||
while (cursor.moveToNext()) {
|
||||
list.add(TemporaryExposureKey.TemporaryExposureKeyBuilder()
|
||||
@ -234,8 +268,8 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyDiagnosisKeySearchResult(key: TemporaryExposureKey, matched: Boolean, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
compileStatement("UPDATE $TABLE_TEK_CHECK SET matched = ? WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?;").use {
|
||||
private fun applySingleDiagnosisKeySearchResult(key: TemporaryExposureKey, matched: Boolean, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
compileStatement("UPDATE $TABLE_TEK_CHECK_SINGLE SET matched = ? WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?;").use {
|
||||
it.bindLong(1, if (matched) 1 else 0)
|
||||
it.bindBlob(2, key.keyData)
|
||||
it.bindLong(3, key.rollingStartIntervalNumber.toLong())
|
||||
@ -244,16 +278,25 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
}
|
||||
}
|
||||
|
||||
private fun listMatchedDiagnosisKeys(packageName: String, token: String, database: SQLiteDatabase = readableDatabase) = database.run {
|
||||
private fun applyDiagnosisFileKeySearchResult(tcfid: Long, key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
insert(TABLE_TEK_CHECK_FILE_MATCH, "NULL", ContentValues().apply {
|
||||
put("tcfid", tcfid)
|
||||
put("keyData", key.keyData)
|
||||
put("rollingStartNumber", key.rollingStartIntervalNumber)
|
||||
put("rollingPeriod", key.rollingPeriod)
|
||||
put("transmissionRiskLevel", key.transmissionRiskLevel)
|
||||
})
|
||||
}
|
||||
|
||||
private fun listMatchedSingleDiagnosisKeys(tid: Long, database: SQLiteDatabase = readableDatabase) = database.run {
|
||||
rawQuery("""
|
||||
SELECT $TABLE_TEK_CHECK.keyData, $TABLE_TEK_CHECK.rollingStartNumber, $TABLE_TEK_CHECK.rollingPeriod, $TABLE_DIAGNOSIS.transmissionRiskLevel
|
||||
FROM $TABLE_DIAGNOSIS
|
||||
LEFT JOIN $TABLE_TEK_CHECK ON $TABLE_DIAGNOSIS.tcid = $TABLE_TEK_CHECK.tcid
|
||||
SELECT $TABLE_TEK_CHECK_SINGLE.keyData, $TABLE_TEK_CHECK_SINGLE.rollingStartNumber, $TABLE_TEK_CHECK_SINGLE.rollingPeriod, $TABLE_TEK_CHECK_SINGLE_TOKEN.transmissionRiskLevel
|
||||
FROM $TABLE_TEK_CHECK_SINGLE_TOKEN
|
||||
JOIN $TABLE_TEK_CHECK_SINGLE ON $TABLE_TEK_CHECK_SINGLE.tcsid = $TABLE_TEK_CHECK_SINGLE_TOKEN.tcsid
|
||||
WHERE
|
||||
$TABLE_DIAGNOSIS.package = ? AND
|
||||
$TABLE_DIAGNOSIS.token = ? AND
|
||||
$TABLE_TEK_CHECK.matched = 1
|
||||
""", arrayOf(packageName, token)).use { cursor ->
|
||||
$TABLE_TEK_CHECK_SINGLE_TOKEN.tid = ? AND
|
||||
$TABLE_TEK_CHECK_SINGLE.matched = 1
|
||||
""", arrayOf(tid.toString())).use { cursor ->
|
||||
val list = arrayListOf<TemporaryExposureKey>()
|
||||
while (cursor.moveToNext()) {
|
||||
list.add(TemporaryExposureKey.TemporaryExposureKeyBuilder()
|
||||
@ -267,36 +310,118 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
}
|
||||
}
|
||||
|
||||
fun finishMatching(packageName: String, token: String, database: SQLiteDatabase = writableDatabase) {
|
||||
val start = System.currentTimeMillis()
|
||||
private fun listMatchedFileDiagnosisKeys(tid: Long, database: SQLiteDatabase = readableDatabase) = database.run {
|
||||
rawQuery("""
|
||||
SELECT $TABLE_TEK_CHECK_FILE_MATCH.keyData, $TABLE_TEK_CHECK_FILE_MATCH.rollingStartNumber, $TABLE_TEK_CHECK_FILE_MATCH.rollingPeriod, $TABLE_TEK_CHECK_FILE_MATCH.transmissionRiskLevel
|
||||
FROM $TABLE_TEK_CHECK_FILE_TOKEN
|
||||
JOIN $TABLE_TEK_CHECK_FILE_MATCH ON $TABLE_TEK_CHECK_FILE_MATCH.tcfid = $TABLE_TEK_CHECK_FILE_TOKEN.tcfid
|
||||
WHERE
|
||||
$TABLE_TEK_CHECK_FILE_TOKEN.tid = ?
|
||||
""", arrayOf(tid.toString())).use { cursor ->
|
||||
val list = arrayListOf<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 poolSize = Runtime.getRuntime().availableProcessors()
|
||||
val executor = ThreadPoolExecutor(poolSize, poolSize, 1, TimeUnit.SECONDS, workQueue)
|
||||
val futures = arrayListOf<Future<*>>()
|
||||
val keys = listDiagnosisKeysPendingSearch(packageName, token, database)
|
||||
val keys = listSingleDiagnosisKeysPendingSearch(tid, database)
|
||||
val oldestRpi = oldestRpi
|
||||
for (key in keys) {
|
||||
if (oldestRpi == null || (key.rollingStartIntervalNumber + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS < oldestRpi) {
|
||||
if ((key.rollingStartIntervalNumber + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS < oldestRpi) {
|
||||
// Early ignore because key is older than since we started scanning.
|
||||
applyDiagnosisKeySearchResult(key, false, database)
|
||||
applySingleDiagnosisKeySearchResult(key, false, database)
|
||||
} else {
|
||||
futures.add(executor.submit {
|
||||
applyDiagnosisKeySearchResult(key, findMeasuredExposures(key).isNotEmpty(), database)
|
||||
applySingleDiagnosisKeySearchResult(key, findMeasuredExposures(key).isNotEmpty(), database)
|
||||
})
|
||||
}
|
||||
}
|
||||
for (future in futures) {
|
||||
future.get()
|
||||
}
|
||||
val time = (System.currentTimeMillis() - start).toDouble() / 1000.0
|
||||
executor.shutdown()
|
||||
Log.d(TAG, "Processed ${keys.size} new keys in ${time}s -> ${(keys.size.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s")
|
||||
return keys.size
|
||||
}
|
||||
|
||||
fun findAllMeasuredExposures(packageName: String, token: String, database: SQLiteDatabase = readableDatabase): List<MeasuredExposure> {
|
||||
return listMatchedDiagnosisKeys(packageName, token, database).flatMap { findMeasuredExposures(it, database) }
|
||||
fun finishFileMatching(tid: Long, hash: ByteArray, endTimestamp: Long, keys: List<TemporaryExposureKey>, updates: List<TemporaryExposureKey>, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
beginTransaction()
|
||||
try {
|
||||
insert(TABLE_TEK_CHECK_FILE, "NULL", ContentValues().apply {
|
||||
put("hash", ByteString.of(*hash).hex())
|
||||
put("endTimestamp", endTimestamp)
|
||||
put("keys", keys.size + updates.size)
|
||||
})
|
||||
val tcfid = getDiagnosisFileId(hash, this) ?: return
|
||||
val workQueue = LinkedBlockingQueue<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> {
|
||||
val allRpis = key.generateAllRpiIds()
|
||||
val rpis = (0 until key.rollingPeriod).map { i ->
|
||||
@ -385,21 +510,23 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
return res
|
||||
}
|
||||
|
||||
fun storeConfiguration(packageName: String, token: String, configuration: ExposureConfiguration) = writableDatabase.run {
|
||||
val update = update(TABLE_CONFIGURATIONS, ContentValues().apply { put("configuration", configuration.marshall()) }, "package = ? AND token = ?", arrayOf(packageName, token))
|
||||
fun storeConfiguration(packageName: String, token: String, configuration: ExposureConfiguration, database: SQLiteDatabase = writableDatabase) = database.run {
|
||||
val update = update(TABLE_TOKENS, ContentValues().apply { put("configuration", configuration.marshall()) }, "package = ? AND token = ?", arrayOf(packageName, token))
|
||||
if (update <= 0) {
|
||||
insert(TABLE_CONFIGURATIONS, "NULL", ContentValues().apply {
|
||||
insert(TABLE_TOKENS, "NULL", ContentValues().apply {
|
||||
put("package", packageName)
|
||||
put("token", token)
|
||||
put("timestamp", System.currentTimeMillis())
|
||||
put("configuration", configuration.marshall())
|
||||
})
|
||||
}
|
||||
getTokenId(packageName, token, database)
|
||||
}
|
||||
|
||||
fun loadConfiguration(packageName: String, token: String): ExposureConfiguration? = readableDatabase.run {
|
||||
query(TABLE_CONFIGURATIONS, arrayOf("configuration"), "package = ? AND token = ?", arrayOf(packageName, token), null, null, null, null).use { cursor ->
|
||||
fun loadConfiguration(packageName: String, token: String, database: SQLiteDatabase = readableDatabase): Pair<Long, ExposureConfiguration>? = database.run {
|
||||
query(TABLE_TOKENS, arrayOf("tid", "configuration"), "package = ? AND token = ?", arrayOf(packageName, token), null, null, null, null).use { cursor ->
|
||||
if (cursor.moveToNext()) {
|
||||
ExposureConfiguration.CREATOR.unmarshall(cursor.getBlob(0))
|
||||
cursor.getLong(0) to ExposureConfiguration.CREATOR.unmarshall(cursor.getBlob(1))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@ -454,13 +581,13 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
}
|
||||
}
|
||||
|
||||
val oldestRpi: Long?
|
||||
val oldestRpi: Long
|
||||
get() = readableDatabase.run {
|
||||
query(TABLE_ADVERTISEMENTS, arrayOf("MIN(timestamp)"), null, null, null, null, null).use { cursor ->
|
||||
if (cursor.moveToNext()) {
|
||||
cursor.getLong(0)
|
||||
} else {
|
||||
null
|
||||
System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -532,7 +659,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
|
||||
override fun getWritableDatabase(): SQLiteDatabase {
|
||||
if (this != instance) {
|
||||
throw IllegalStateException("Tried to open writable database from secondary instance")
|
||||
throw IllegalStateException("Tried to open writable database from secondary instance. We are ${hashCode()} but primary is ${instance?.hashCode()}")
|
||||
}
|
||||
return super.getWritableDatabase()
|
||||
}
|
||||
@ -560,19 +687,32 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
||||
|
||||
companion object {
|
||||
private const val DB_NAME = "exposure.db"
|
||||
private const val DB_VERSION = 4
|
||||
private const val DB_VERSION = 5
|
||||
private const val TABLE_ADVERTISEMENTS = "advertisements"
|
||||
private const val TABLE_APP_LOG = "app_log"
|
||||
private const val TABLE_TEK = "tek"
|
||||
private const val TABLE_TEK_CHECK = "tek_check"
|
||||
private const val TABLE_DIAGNOSIS = "diagnosis"
|
||||
private const val TABLE_CONFIGURATIONS = "configurations"
|
||||
private const val TABLE_APP_PERMS = "app_perms"
|
||||
private const val TABLE_TOKENS = "tokens"
|
||||
private const val TABLE_TEK_CHECK_SINGLE = "tek_check_single"
|
||||
private const val TABLE_TEK_CHECK_SINGLE_TOKEN = "tek_check_single_token"
|
||||
private const val TABLE_TEK_CHECK_FILE = "tek_check_file"
|
||||
private const val TABLE_TEK_CHECK_FILE_TOKEN = "tek_check_file_token"
|
||||
private const val TABLE_TEK_CHECK_FILE_MATCH = "tek_check_file_match"
|
||||
|
||||
@Deprecated(message = "No longer supported")
|
||||
private const val TABLE_TEK_CHECK = "tek_check"
|
||||
|
||||
@Deprecated(message = "No longer supported")
|
||||
private const val TABLE_DIAGNOSIS = "diagnosis"
|
||||
|
||||
@Deprecated(message = "No longer supported")
|
||||
private const val TABLE_CONFIGURATIONS = "configurations"
|
||||
|
||||
private var instance: ExposureDatabase? = null
|
||||
fun ref(context: Context): ExposureDatabase = synchronized(this) {
|
||||
if (instance == null) {
|
||||
instance = ExposureDatabase(context.applicationContext)
|
||||
Log.d(TAG, "Created instance ${instance?.hashCode()} of database for ${context.javaClass.name}")
|
||||
}
|
||||
instance!!.ref()
|
||||
}
|
||||
|
@ -29,8 +29,10 @@ class ExposureNotificationService : BaseService(TAG, GmsService.NEARBY_EXPOSURE)
|
||||
return permission
|
||||
}
|
||||
|
||||
checkPermission("android.permission.BLUETOOTH") ?: return
|
||||
checkPermission("android.permission.INTERNET") ?: return
|
||||
if (request.packageName != packageName) {
|
||||
checkPermission("android.permission.BLUETOOTH") ?: return
|
||||
checkPermission("android.permission.INTERNET") ?: return
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
callback.onPostInitComplete(FAILED_NOT_SUPPORTED, null, null)
|
||||
@ -39,7 +41,10 @@ class ExposureNotificationService : BaseService(TAG, GmsService.NEARBY_EXPOSURE)
|
||||
|
||||
Log.d(TAG, "handleServiceRequest: " + request.packageName)
|
||||
callback.onPostInitCompleteWithConnectionInfo(SUCCESS, ExposureNotificationServiceImpl(this, request.packageName), ConnectionInfo().apply {
|
||||
features = arrayOf(Feature("nearby_exposure_notification", 3))
|
||||
features = arrayOf(
|
||||
Feature("nearby_exposure_notification", 3),
|
||||
Feature("nearby_exposure_notification_get_version", 1)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -10,25 +10,29 @@ import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.*
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
import com.google.android.gms.common.api.CommonStatusCodes
|
||||
import com.google.android.gms.common.api.Status
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureInformation
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes.*
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureSummary
|
||||
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
||||
import com.google.android.gms.nearby.exposurenotification.internal.*
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.microg.gms.common.Constants
|
||||
import org.microg.gms.common.PackageUtils
|
||||
import org.microg.gms.nearby.exposurenotification.Constants.*
|
||||
import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyExport
|
||||
import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyProto
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.random.Random
|
||||
|
||||
class ExposureNotificationServiceImpl(private val context: Context, private val packageName: String) : INearbyExposureNotificationService.Stub() {
|
||||
private fun pendingConfirm(permission: String): PendingIntent {
|
||||
@ -65,6 +69,14 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVersion(params: GetVersionParams) {
|
||||
params.callback.onResult(Status.SUCCESS, Constants.MAX_REFERENCE_VERSION.toLong())
|
||||
}
|
||||
|
||||
override fun getCalibrationConfidence(params: GetCalibrationConfidenceParams) {
|
||||
params.callback.onResult(Status.SUCCESS, currentDeviceInfo.confidence)
|
||||
}
|
||||
|
||||
override fun start(params: StartParams) {
|
||||
if (ExposurePreferences(context).enabled) return
|
||||
val status = confirmPermission(CONFIRM_ACTION_START)
|
||||
@ -90,7 +102,6 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
Log.w(TAG, "Callback failed", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isEnabled(params: IsEnabledParams) {
|
||||
try {
|
||||
params.callback.onResult(Status.SUCCESS, ExposurePreferences(context).enabled)
|
||||
@ -129,67 +140,73 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
.setTransmissionRiskLevel(transmission_risk_level ?: 0)
|
||||
.build()
|
||||
|
||||
private fun storeDiagnosisKeyExport(token: String, export: TemporaryExposureKeyExport): Int = ExposureDatabase.with(context) { database ->
|
||||
Log.d(TAG, "Importing keys from file ${export.start_timestamp?.let { Date(it * 1000) }} to ${export.end_timestamp?.let { Date(it * 1000) }}")
|
||||
database.batchStoreDiagnosisKey(packageName, token, export.keys.map { it.toKey() })
|
||||
database.batchUpdateDiagnosisKey(packageName, token, export.revised_keys.map { it.toKey() })
|
||||
export.keys.size + export.revised_keys.size
|
||||
private fun InputStream.copyToFile(outputFile: File) {
|
||||
outputFile.outputStream().use { output ->
|
||||
copyTo(output)
|
||||
output.flush()
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageDigest.digest(file: File): ByteArray = file.inputStream().use { input ->
|
||||
val buf = ByteArray(4096)
|
||||
var bytes = input.read(buf)
|
||||
while (bytes != -1) {
|
||||
update(buf, 0, bytes)
|
||||
bytes = input.read(buf)
|
||||
}
|
||||
digest()
|
||||
}
|
||||
|
||||
override fun provideDiagnosisKeys(params: ProvideDiagnosisKeysParams) {
|
||||
Log.w(TAG, "provideDiagnosisKeys() with $packageName/${params.token}")
|
||||
val tid = ExposureDatabase.with(context) { database ->
|
||||
if (params.configuration != null) {
|
||||
database.storeConfiguration(packageName, params.token, params.configuration)
|
||||
} else {
|
||||
database.getTokenId(packageName, params.token)
|
||||
}
|
||||
}
|
||||
if (tid == null) {
|
||||
Log.w(TAG, "Unknown token without configuration: $packageName/${params.token}")
|
||||
try {
|
||||
params.callback.onResult(Status.INTERNAL_ERROR)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Callback failed", e)
|
||||
}
|
||||
return
|
||||
}
|
||||
Thread(Runnable {
|
||||
ExposureDatabase.with(context) { database ->
|
||||
if (params.configuration != null) {
|
||||
database.storeConfiguration(packageName, params.token, params.configuration)
|
||||
}
|
||||
|
||||
val start = System.currentTimeMillis()
|
||||
|
||||
// keys
|
||||
params.keys?.let { database.batchStoreDiagnosisKey(packageName, params.token, it) }
|
||||
params.keys?.let { database.batchStoreSingleDiagnosisKey(tid, it) }
|
||||
|
||||
var keys = params.keys?.size ?: 0
|
||||
|
||||
// Key files
|
||||
var keys = params.keys?.size ?: 0
|
||||
val todoKeyFiles = arrayListOf<Pair<File, ByteArray>>()
|
||||
for (file in params.keyFiles.orEmpty()) {
|
||||
try {
|
||||
ZipInputStream(ParcelFileDescriptor.AutoCloseInputStream(file)).use { stream ->
|
||||
do {
|
||||
val entry = stream.nextEntry ?: break
|
||||
if (entry.name == "export.bin") {
|
||||
val prefix = ByteArray(16)
|
||||
var totalBytesRead = 0
|
||||
var bytesRead = 0
|
||||
while (bytesRead != -1 && totalBytesRead < prefix.size) {
|
||||
bytesRead = stream.read(prefix, totalBytesRead, prefix.size - totalBytesRead)
|
||||
if (bytesRead > 0) {
|
||||
totalBytesRead += bytesRead
|
||||
}
|
||||
}
|
||||
if (totalBytesRead == prefix.size && String(prefix).trim() == "EK Export v1") {
|
||||
val fileKeys = storeDiagnosisKeyExport(params.token, TemporaryExposureKeyExport.ADAPTER.decode(stream))
|
||||
keys += fileKeys
|
||||
} else {
|
||||
Log.d(TAG, "export.bin had invalid prefix")
|
||||
}
|
||||
}
|
||||
stream.closeEntry()
|
||||
} while (true);
|
||||
val cacheFile = File(context.cacheDir, "en-keyfile-${System.currentTimeMillis()}-${Random.nextInt()}.zip")
|
||||
ParcelFileDescriptor.AutoCloseInputStream(file).use { it.copyToFile(cacheFile) }
|
||||
val hash = MessageDigest.getInstance("SHA-256").digest(cacheFile)
|
||||
val storedKeys = database.storeDiagnosisFileUsed(tid, hash)
|
||||
if (storedKeys != null) {
|
||||
keys += storedKeys.toInt()
|
||||
cacheFile.delete()
|
||||
} else {
|
||||
todoKeyFiles.add(cacheFile to hash)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed parsing file", e)
|
||||
}
|
||||
}
|
||||
val time = (System.currentTimeMillis() - start).toDouble() / 1000.0
|
||||
Log.d(TAG, "$packageName/${params.token} provided $keys keys in ${time}s -> ${(keys.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s")
|
||||
|
||||
database.noteAppAction(packageName, "provideDiagnosisKeys", JSONObject().apply {
|
||||
put("request_token", params.token)
|
||||
put("request_keys_size", params.keys?.size)
|
||||
put("request_keyFiles_size", params.keyFiles?.size)
|
||||
put("request_keys_count", keys)
|
||||
}.toString())
|
||||
|
||||
database.finishMatching(packageName, params.token)
|
||||
if (todoKeyFiles.size > 0) {
|
||||
val time = (System.currentTimeMillis() - start).toDouble() / 1000.0
|
||||
Log.d(TAG, "$packageName/${params.token} processed $keys keys (${todoKeyFiles.size} files pending) in ${time}s -> ${(keys.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s")
|
||||
}
|
||||
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
try {
|
||||
@ -199,7 +216,47 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
}
|
||||
}
|
||||
|
||||
val match = database.findAllMeasuredExposures(packageName, params.token).isNotEmpty()
|
||||
var newKeys = if (params.keys != null) database.finishSingleMatching(tid) else 0
|
||||
for ((cacheFile, hash) in todoKeyFiles) {
|
||||
ZipFile(cacheFile).use { zip ->
|
||||
for (entry in zip.entries()) {
|
||||
if (entry.name == "export.bin") {
|
||||
val stream = zip.getInputStream(entry)
|
||||
val prefix = ByteArray(16)
|
||||
var totalBytesRead = 0
|
||||
var bytesRead = 0
|
||||
while (bytesRead != -1 && totalBytesRead < prefix.size) {
|
||||
bytesRead = stream.read(prefix, totalBytesRead, prefix.size - totalBytesRead)
|
||||
if (bytesRead > 0) {
|
||||
totalBytesRead += bytesRead
|
||||
}
|
||||
}
|
||||
if (totalBytesRead == prefix.size && String(prefix).trim() == "EK Export v1") {
|
||||
val export = TemporaryExposureKeyExport.ADAPTER.decode(stream)
|
||||
database.finishFileMatching(tid, hash, export.end_timestamp?.let { it * 1000 }
|
||||
?: System.currentTimeMillis(), export.keys.map { it.toKey() }, export.revised_keys.map { it.toKey() })
|
||||
keys += export.keys.size + export.revised_keys.size
|
||||
newKeys += export.keys.size
|
||||
} else {
|
||||
Log.d(TAG, "export.bin had invalid prefix")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cacheFile.delete()
|
||||
}
|
||||
|
||||
val time = (System.currentTimeMillis() - start).toDouble() / 1000.0
|
||||
Log.d(TAG, "$packageName/${params.token} processed $keys keys ($newKeys new) in ${time}s -> ${(keys.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s")
|
||||
|
||||
database.noteAppAction(packageName, "provideDiagnosisKeys", JSONObject().apply {
|
||||
put("request_token", params.token)
|
||||
put("request_keys_size", params.keys?.size)
|
||||
put("request_keyFiles_size", params.keyFiles?.size)
|
||||
put("request_keys_count", keys)
|
||||
}.toString())
|
||||
|
||||
val match = database.findAllMeasuredExposures(tid).isNotEmpty()
|
||||
|
||||
try {
|
||||
val intent = Intent(if (match) ACTION_EXPOSURE_STATE_UPDATED else ACTION_EXPOSURE_NOT_FOUND)
|
||||
@ -215,16 +272,12 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
}
|
||||
|
||||
override fun getExposureSummary(params: GetExposureSummaryParams): Unit = ExposureDatabase.with(context) { database ->
|
||||
val configuration = database.loadConfiguration(packageName, params.token)
|
||||
if (configuration == null) {
|
||||
try {
|
||||
params.callback.onResult(Status.INTERNAL_ERROR, null)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Callback failed", e)
|
||||
}
|
||||
return@with
|
||||
val pair = database.loadConfiguration(packageName, params.token)
|
||||
val (configuration, exposures) = if (pair != null) {
|
||||
pair.second to database.findAllMeasuredExposures(pair.first).merge()
|
||||
} else {
|
||||
ExposureConfiguration.ExposureConfigurationBuilder().build() to emptyList()
|
||||
}
|
||||
val exposures = database.findAllMeasuredExposures(packageName, params.token).merge()
|
||||
val response = ExposureSummary.ExposureSummaryBuilder()
|
||||
.setDaysSinceLastExposure(exposures.map { it.daysSinceExposure }.min()?.toInt() ?: 0)
|
||||
.setMatchedKeyCount(exposures.map { it.key }.distinct().size)
|
||||
@ -255,18 +308,13 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
}
|
||||
|
||||
override fun getExposureInformation(params: GetExposureInformationParams): Unit = ExposureDatabase.with(context) { database ->
|
||||
// TODO: Notify user?
|
||||
val configuration = database.loadConfiguration(packageName, params.token)
|
||||
if (configuration == null) {
|
||||
try {
|
||||
params.callback.onResult(Status.INTERNAL_ERROR, null)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Callback failed", e)
|
||||
val pair = database.loadConfiguration(packageName, params.token)
|
||||
val response = if (pair != null) {
|
||||
database.findAllMeasuredExposures(pair.first).merge().map {
|
||||
it.toExposureInformation(pair.second)
|
||||
}
|
||||
return@with
|
||||
}
|
||||
val response = database.findAllMeasuredExposures(packageName, params.token).merge().map {
|
||||
it.toExposureInformation(configuration)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
database.noteAppAction(packageName, "getExposureInformation", JSONObject().apply {
|
||||
@ -280,6 +328,26 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||
}
|
||||
}
|
||||
|
||||
override fun getExposureWindows(params: GetExposureWindowsParams) {
|
||||
Log.w(TAG, "Not yet implemented: getExposureWindows")
|
||||
params.callback.onResult(Status.INTERNAL_ERROR, emptyList())
|
||||
}
|
||||
|
||||
override fun getDailySummaries(params: GetDailySummariesParams) {
|
||||
Log.w(TAG, "Not yet implemented: getDailySummaries")
|
||||
params.callback.onResult(Status.INTERNAL_ERROR, emptyList())
|
||||
}
|
||||
|
||||
override fun setDiagnosisKeysDataMapping(params: SetDiagnosisKeysDataMappingParams) {
|
||||
Log.w(TAG, "Not yet implemented: setDiagnosisKeysDataMapping")
|
||||
params.callback.onResult(Status.INTERNAL_ERROR)
|
||||
}
|
||||
|
||||
override fun getDiagnosisKeysDataMapping(params: GetDiagnosisKeysDataMappingParams) {
|
||||
Log.w(TAG, "Not yet implemented: getDiagnosisKeysDataMapping")
|
||||
params.callback.onResult(Status.INTERNAL_ERROR, null)
|
||||
}
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||
if (super.onTransact(code, data, reply, flags)) return true
|
||||
Log.d(TAG, "onTransact [unknown]: $code, $data, $flags")
|
||||
|
@ -28,6 +28,8 @@
|
||||
# Keep AutoSafeParcelables
|
||||
-keep public class * extends org.microg.safeparcel.AutoSafeParcelable {
|
||||
@org.microg.safeparcel.SafeParceled *;
|
||||
@org.microg.safeparcel.SafeParcelable.Field *;
|
||||
<init>(...);
|
||||
}
|
||||
|
||||
# Keep form data
|
||||
|
Loading…
x
Reference in New Issue
Block a user