diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9cb6ee01..3ce1f798 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,10 +6,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
+## [2.12.5] - 2021-11-01
+### Changed
+- Fixes, improvements and extensions of config notifications [#744](https://github.com/rokwire/safer-illinois-app/issues/744).
+
+## [2.12.4] - 2021-10-29
+### Added
+- Implemented config notifications [#744](https://github.com/rokwire/safer-illinois-app/issues/744).
+### Deleted
+- Removed Exposure plugins, service and related UI [#742](https://github.com/rokwire/safer-illinois-app/issues/742).
+### Changed
+- Update encrypt plugin to latest available version [#745](https://github.com/rokwire/safer-illinois-app/issues/745).
+
+## [2.12.3] - 2021-10-06
+### Changed
+- Handled exempt of vaccination and vaccintation suspended status in vaccination widget [#739](https://github.com/rokwire/safer-illinois-app/issues/739).
+
+## [2.12.2] - 2021-10-04
+### Added
+- Added vaccination exempt support in UIN Overrides [#733](https://github.com/rokwire/safer-illinois-app/issues/733).
+- Check for expired vaccines in vaccination widget and isVaccinated getter [#729](https://github.com/rokwire/safer-illinois-app/issues/729).
+### Changed
+- Simplify VaccineBoosterInterval logic until we really need different intervals for different manufacturers [#737](https://github.com/rokwire/safer-illinois-app/issues/737).
+### Deleted
+- Remove "max-weekdays-extent" from test monitor interval until it is required [#737](https://github.com/rokwire/safer-illinois-app/issues/737).
+
## [2.11.5] - 2021-10-01
### Fixed
- Fixed null pointer crash [#725](https://github.com/rokwire/safer-illinois-app/issues/725).
+## [2.12.1] - 2021-10-01
+### Added
+- Added SECURITY.md.
+- Added booster intervals for vaccines [#729](https://github.com/rokwire/safer-illinois-app/issues/729).
+
+## [2.12.0] - 2021-09-30
+
## [2.11.4] - 2021-10-01
### Changed
- Show when the vaccine will become effective in vaccination widget [#720](https://github.com/rokwire/safer-illinois-app/issues/720).
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 00000000..01b02b78
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,21 @@
+# Security Policy
+
+## Supported Versions
+
+Patches for [ **safer-illinois-app** ] will only be applied to the following versions:
+
+| Version | Supported |
+| ------- | ------------------ |
+| 2.12.0 | :white_check_mark: |
+| 2.11.3 | :white_check_mark: |
+| 2.11.2 | :x: |
+| 2.11.1 | :x: |
+| 2.11.0 | :x: |
+| 2.10.38 | :white_check_mark: |
+| < 2.10.38 | :x: |
+
+## Reporting a Bug or Vulnerability
+
+Vulnerabilities can be responsibly disclosed to [securitysupport@illinois.edu](mailto:securitysupport@illinois.edu).
+
+Bugs can be reported in a GIT repository via GIT issues.
\ No newline at end of file
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 6e76aac2..641c68da 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -163,9 +163,6 @@ dependencies {
implementation 'com.google.zxing:core:3.3.0' //Use zxing 3.3.0 because we have minSdk < 24
implementation ('com.journeyapps:zxing-android-embedded:4.1.0@aar') { transitive = false }
- // BLESSED - BLE library used for Exposures
- implementation 'com.github.weliem:blessed-android:1.19'
-
implementation 'com.google.android.gms:play-services-vision-common:19.0.2'
// Temporary fix Gradle 4.2.0 & https://stackoverflow.com/questions/67612499/could-not-find-com-google-firebasefirebase-ml-vision
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 6fb21f5d..16a6aed6 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -28,21 +28,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/app/src/main/java/at/favre/lib/crypto/HKDF.java b/android/app/src/main/java/at/favre/lib/crypto/HKDF.java
deleted file mode 100644
index 5ddf1f50..00000000
--- a/android/app/src/main/java/at/favre/lib/crypto/HKDF.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright 2017 Patrick Favre-Bulle
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package at.favre.lib.crypto;
-
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import java.nio.ByteBuffer;
-
-/**
- * A standards-compliant implementation of RFC 5869
- * for HMAC-based Key Derivation Function.
- *
- * HKDF follows the "extract-then-expand" paradigm, where the KDF
- * logically consists of two modules. The first stage takes the input
- * keying material and "extracts" from it a fixed-length pseudorandom
- * key K. The second stage "expands" the key K into several additional
- * pseudorandom keys (the output of the KDF).
- *
- * HKDF was first described by Hugo Krawczyk.
- *
- * This implementation is thread safe without the need for synchronization.
- *
- * Simple Example:
- *
- * byte[] pseudoRandomKey = HKDF.fromHmacSha256().extract(null, lowEntropyInput);
- * byte[] outputKeyingMaterial = HKDF.fromHmacSha256().expand(pseudoRandomKey, null, 64);
- *
- *
- * @see RFC 5869
- * @see Cryptographic Extraction and Key Derivation:
- * The HKDF Scheme
- * @see Wikipedia: HKDF
- */
-@SuppressWarnings("WeakerAccess")
-public final class HKDF {
- /**
- * Cache instances
- */
- private static HKDF hkdfHmacSha256;
- private static HKDF hkdfHmacSha512;
-
- private final HkdfMacFactory macFactory;
-
- private HKDF(HkdfMacFactory macFactory) {
- this.macFactory = macFactory;
- }
-
- /**
- * Return a shared instance using HMAC with Sha256.
- * Even though shared, this instance is thread-safe.
- *
- * @return HKDF instance
- */
- public static HKDF fromHmacSha256() {
- if (hkdfHmacSha256 == null) {
- hkdfHmacSha256 = from(HkdfMacFactory.Default.hmacSha256());
- }
- return hkdfHmacSha256;
- }
-
- /**
- * Return a shared instance using HMAC with Sha512.
- * Even though shared, this instance is thread-safe.
- *
- * @return HKDF instance
- */
- public static HKDF fromHmacSha512() {
- if (hkdfHmacSha512 == null) {
- hkdfHmacSha512 = from(HkdfMacFactory.Default.hmacSha512());
- }
- return hkdfHmacSha512;
- }
-
- /**
- * Create a new HKDF instance for given macFactory.
- *
- * @param macFactory used for HKDF
- * @return a new instance of HKDF
- */
- public static HKDF from(HkdfMacFactory macFactory) {
- return new HKDF(macFactory);
- }
-
- /**
- * Step 1 of RFC 5869 (Section 2.2)
- *
- * The first stage takes the input keying material and "extracts" from it a fixed-length pseudorandom
- * key K. The goal of the "extract" stage is to "concentrate" and provide a more uniformly unbiased and higher entropy but smaller output.
- * This is done by utilising the diffusion properties of cryptographic MACs.
- *
- * About Salts (from RFC 5869):
- *
- * HKDF is defined to operate with and without random salt. This is
- * done to accommodate applications where a salt value is not available.
- * We stress, however, that the use of salt adds significantly to the
- * strength of HKDF, ensuring independence between different uses of the
- * hash function, supporting "source-independent" extraction, and
- * strengthening the analytical results that back the HKDF design.
- *
- *
- * @param salt optional salt value (a non-secret random value) (can be null)
- * if not provided, it is set to an array of hash length of zeros.
- * @param inputKeyingMaterial data to be extracted (IKM)
- *
- * @return a new byte array pseudo random key (of hash length in bytes) (PRK) which can be used to expand
- * @see RFC 5869 Section 2.2
- */
- public byte[] extract(byte[] salt, byte[] inputKeyingMaterial) {
- return extract(macFactory.createSecretKey(salt), inputKeyingMaterial);
- }
-
- /**
- * Use this if you require {@link SecretKey} types by your security framework. See also
- * {@link HkdfMacFactory#createSecretKey(byte[])}.
- *
- * See {@link #extract(byte[], byte[])} for description.
- *
- * @param salt optional salt value (a non-secret random value) (can be null)
- * @param inputKeyingMaterial data to be extracted (IKM)
- * @return a new byte array pseudo random key (of hash length in bytes) (PRK) which can be used to expand
- */
- public byte[] extract(SecretKey salt, byte[] inputKeyingMaterial) {
- return new Extractor(macFactory).execute(salt, inputKeyingMaterial);
- }
-
- /**
- * Step 2 of RFC 5869 (Section 2.3)
- *
- * To "expand" the generated output of an already reasonably random input such as an existing shared key into a larger
- * cryptographically independent output, thereby producing multiple keys deterministically from that initial shared key,
- * so that the same process may produce those same secret keys safely on multiple devices, as long as the same inputs
- * are used.
- *
- * About Info (from RFC 5869):
- *
- * While the 'info' value is optional in the definition of HKDF, it is
- * often of great importance in applications. Its main objective is to
- * bind the derived key material to application- and context-specific
- * information. For example, 'info' may contain a protocol number,
- * algorithm identifiers, user identities, etc. In particular, it may
- * prevent the derivation of the same keying material for different
- * contexts (when the same input key material (IKM) is used in such
- * different contexts).
- *
- *
- * @param pseudoRandomKey a pseudo random key of at least hmac hash length in bytes (usually, the output from the extract step)
- * @param info optional context and application specific information; may be null
- * @param outLengthBytes length of output keying material in bytes
- * @return new byte array of output keying material (OKM)
- * @see RFC 5869 Section 2.3
- */
- public byte[] expand(byte[] pseudoRandomKey, byte[] info, int outLengthBytes) {
- return expand(macFactory.createSecretKey(pseudoRandomKey), info, outLengthBytes);
- }
-
- /**
- * Use this if you require {@link SecretKey} types by your security framework. See also
- * {@link HkdfMacFactory#createSecretKey(byte[])}.
- *
- * See {@link #expand(byte[], byte[], int)} for description.
- *
- * @param pseudoRandomKey a pseudo random key of at least hmac hash length in bytes (usually, the output from the extract step)
- * @param info optional context and application specific information; may be null
- * @param outLengthBytes length of output keying material in bytes
- * @return new byte array of output keying material (OKM)
- */
- public byte[] expand(SecretKey pseudoRandomKey, byte[] info, int outLengthBytes) {
- return new Expander(macFactory).execute(pseudoRandomKey, info, outLengthBytes);
- }
-
- /**
- * Convenience method for extract & expand in a single method
- *
- * @param saltExtract optional salt value (a non-secret random value);
- * @param inputKeyingMaterial data to be extracted (IKM)
- * @param infoExpand optional context and application specific information; may be null
- * @param outLengthByte length of output keying material in bytes
- * @return new byte array of output keying material (OKM)
- */
- public byte[] extractAndExpand(byte[] saltExtract, byte[] inputKeyingMaterial, byte[] infoExpand, int outLengthByte) {
- return extractAndExpand(macFactory.createSecretKey(saltExtract), inputKeyingMaterial, infoExpand, outLengthByte);
- }
-
- /**
- * Convenience method for extract & expand in a single method
- *
- * @param saltExtract optional salt value (a non-secret random value);
- * @param inputKeyingMaterial data to be extracted (IKM)
- * @param infoExpand optional context and application specific information; may be null
- * @param outLengthByte length of output keying material in bytes
- * @return new byte array of output keying material (OKM)
- */
- public byte[] extractAndExpand(SecretKey saltExtract, byte[] inputKeyingMaterial, byte[] infoExpand, int outLengthByte) {
- return new Expander(macFactory).execute(macFactory.createSecretKey(
- new Extractor(macFactory).execute(saltExtract, inputKeyingMaterial)),
- infoExpand, outLengthByte);
- }
-
- /**
- * Get the used mac factory
- *
- * @return factory
- */
- HkdfMacFactory getMacFactory() {
- return macFactory;
- }
-
- /* ************************************************************************** IMPL */
-
- static final class Extractor {
- private final HkdfMacFactory macFactory;
-
- Extractor(HkdfMacFactory macFactory) {
- this.macFactory = macFactory;
- }
-
- /**
- * Step 1 of RFC 5869
- *
- * @param salt optional salt value (a non-secret random value);
- * if not provided, it is set to an array of hash length of zeros.
- * @param inputKeyingMaterial data to be extracted (IKM)
- *
- * @return a new byte array pseudorandom key (of hash length in bytes) (PRK) which can be used to expand
- */
- byte[] execute(SecretKey salt, byte[] inputKeyingMaterial) {
- if (salt == null) {
- salt = macFactory.createSecretKey(new byte[macFactory.getMacLengthBytes()]);
- }
-
- if (inputKeyingMaterial == null || inputKeyingMaterial.length <= 0) {
- throw new IllegalArgumentException("provided inputKeyingMaterial must be at least of size 1 and not null");
- }
-
- Mac mac = macFactory.createInstance(salt);
- return mac.doFinal(inputKeyingMaterial);
- }
- }
-
- static final class Expander {
- private final HkdfMacFactory macFactory;
-
- Expander(HkdfMacFactory macFactory) {
- this.macFactory = macFactory;
- }
-
- /**
- * Step 2 of RFC 5869.
- *
- * @param pseudoRandomKey a pseudorandom key of at least hmac hash length in bytes (usually, the output from the extract step)
- * @param info optional context and application specific information; may be null
- * @param outLengthBytes length of output keying material in bytes (must be <= 255 * mac hash length)
- * @return new byte array of output keying material (OKM)
- */
- byte[] execute(SecretKey pseudoRandomKey, byte[] info, int outLengthBytes) {
-
- if (outLengthBytes <= 0) {
- throw new IllegalArgumentException("out length bytes must be at least 1");
- }
-
- if (pseudoRandomKey == null) {
- throw new IllegalArgumentException("provided pseudoRandomKey must not be null");
- }
-
- Mac hmacHasher = macFactory.createInstance(pseudoRandomKey);
-
- if (info == null) {
- info = new byte[0];
- }
-
- /*
- The output OKM is calculated as follows:
- N = ceil(L/HashLen)
- T = T(1) | T(2) | T(3) | ... | T(N)
- OKM = first L bytes of T
- where:
- T(0) = empty string (zero length)
- T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
- T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
- T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
- ...
- */
-
- byte[] blockN = new byte[0];
-
- int iterations = (int) Math.ceil(((double) outLengthBytes) / ((double) hmacHasher.getMacLength()));
-
- if (iterations > 255) {
- throw new IllegalArgumentException("out length must be maximal 255 * hash-length; requested: " + outLengthBytes + " bytes");
- }
-
- ByteBuffer buffer = ByteBuffer.allocate(outLengthBytes);
- int remainingBytes = outLengthBytes;
- int stepSize;
-
- for (int i = 0; i < iterations; i++) {
- hmacHasher.update(blockN);
- hmacHasher.update(info);
- hmacHasher.update((byte) (i + 1));
-
- blockN = hmacHasher.doFinal();
-
- stepSize = Math.min(remainingBytes, blockN.length);
-
- buffer.put(blockN, 0, stepSize);
- remainingBytes -= stepSize;
- }
-
- return buffer.array();
- }
- }
-}
\ No newline at end of file
diff --git a/android/app/src/main/java/at/favre/lib/crypto/HkdfMacFactory.java b/android/app/src/main/java/at/favre/lib/crypto/HkdfMacFactory.java
deleted file mode 100644
index 29833713..00000000
--- a/android/app/src/main/java/at/favre/lib/crypto/HkdfMacFactory.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright 2017 Patrick Favre-Bulle
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package at.favre.lib.crypto;
-
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.Provider;
-
-/**
- * Factory class for creating {@link Mac} hashers
- */
-public interface HkdfMacFactory {
-
- /**
- * Creates a new instance of Hmac with given key, i.e. it must already be initialized
- * with {@link Mac#init(Key)}.
- *
- * @param key the key used, must not be null
- * @return a new mac instance
- */
- Mac createInstance(SecretKey key);
-
- /**
- * Get the length of the mac output in bytes
- *
- * @return the length of mac output in bytes
- */
- int getMacLengthBytes();
-
- /**
- * Creates a secret key from a byte raw key material to be used with {@link #createInstance(SecretKey)}
- *
- * @param rawKeyMaterial the raw key
- * @return wrapped as secret key instance or null if input is null or empty
- */
- SecretKey createSecretKey(byte[] rawKeyMaterial);
-
- /**
- * Default implementation
- */
- @SuppressWarnings("WeakerAccess")
- final class Default implements HkdfMacFactory {
- private final String macAlgorithmName;
- private final Provider provider;
-
- /**
- * Creates a factory creating HMAC with SHA-256
- *
- * @return factory
- */
- public static HkdfMacFactory hmacSha256() {
- return new Default("HmacSHA256", null);
- }
-
- /**
- * Creates a factory creating HMAC with SHA-512
- *
- * @return factory
- */
- public static HkdfMacFactory hmacSha512() {
- return new Default("HmacSHA512", null);
- }
-
- /**
- * Creates a factory creating HMAC with SHA-1
- *
- * @return factory
- * @deprecated sha1 with HMAC should be fine, but not recommended for new protocols; see https://crypto.stackexchange.com/questions/26510/why-is-hmac-sha1-still-considered-secure
- */
- @Deprecated
- public static HkdfMacFactory hmacSha1() {
- return new Default("HmacSHA1", null);
- }
-
- /**
- * Creates a mac factory
- *
- * @param macAlgorithmName as used by {@link Mac#getInstance(String)}
- */
- public Default(String macAlgorithmName) {
- this(macAlgorithmName, null);
- }
-
- /**
- * Creates a mac factory
- *
- * @param macAlgorithmName as used by {@link Mac#getInstance(String)}
- * @param provider the security provider, see {@link Mac#getInstance(String, Provider)}; may be null to use default
- */
- public Default(String macAlgorithmName, Provider provider) {
- this.macAlgorithmName = macAlgorithmName;
- this.provider = provider;
- }
-
- @Override
- public Mac createInstance(SecretKey key) {
- try {
- Mac mac = createMacInstance();
- mac.init(key);
- return mac;
- } catch (Exception e) {
- throw new IllegalStateException("could not make hmac hasher in hkdf", e);
- }
- }
-
- private Mac createMacInstance() {
- try {
- Mac hmacInstance;
-
- if (provider == null) {
- hmacInstance = Mac.getInstance(macAlgorithmName);
- } else {
- hmacInstance = Mac.getInstance(macAlgorithmName, provider);
- }
-
- return hmacInstance;
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("defined mac algorithm was not found", e);
- } catch (Exception e) {
- throw new IllegalStateException("could not create mac instance in hkdf", e);
- }
- }
-
- @Override
- public int getMacLengthBytes() {
- return createMacInstance().getMacLength();
- }
-
- @Override
- public SecretKey createSecretKey(byte[] rawKeyMaterial) {
- if (rawKeyMaterial == null || rawKeyMaterial.length <= 0) {
- return null;
- }
- return new SecretKeySpec(rawKeyMaterial, macAlgorithmName);
- }
- }
-}
\ No newline at end of file
diff --git a/android/app/src/main/java/edu/illinois/covid/AppBackupAgent.java b/android/app/src/main/java/edu/illinois/covid/AppBackupAgent.java
index d5740355..a013f3d2 100644
--- a/android/app/src/main/java/edu/illinois/covid/AppBackupAgent.java
+++ b/android/app/src/main/java/edu/illinois/covid/AppBackupAgent.java
@@ -32,9 +32,6 @@ public void onCreate() {
SharedPreferencesBackupHelper encryptionHelper = new SharedPreferencesBackupHelper(this, Constants.ENCRYPTION_SHARED_PREFS_FILE_NAME);
addHelper(PREFS_BACKUP_KEY, encryptionHelper);
-
- SharedPreferencesBackupHelper exposureTeksHelper = new SharedPreferencesBackupHelper(this, Constants.EXPOSURE_TEKS_SHARED_PREFS_FILE_NAME);
- addHelper(PREFS_BACKUP_KEY, exposureTeksHelper);
}
public static void requestBackup(Context context) {
diff --git a/android/app/src/main/java/edu/illinois/covid/Constants.java b/android/app/src/main/java/edu/illinois/covid/Constants.java
index 6865be2a..832e782c 100644
--- a/android/app/src/main/java/edu/illinois/covid/Constants.java
+++ b/android/app/src/main/java/edu/illinois/covid/Constants.java
@@ -16,12 +16,8 @@
package edu.illinois.covid;
-import android.os.ParcelUuid;
-
import com.google.android.gms.maps.model.LatLng;
-import java.util.UUID;
-
public class Constants {
//Flutter communication methods
@@ -49,9 +45,6 @@ public class Constants {
static final float SECOND_THRESHOLD_MARKER_ZOOM = 16.89f;
static final int MARKER_TITLE_MAX_SYMBOLS_NUMBER = 15;
public static final double EXPLORE_LOCATION_THRESHOLD_DISTANCE = 200.0; //meters
- public static final float INDOORS_BUILDING_ZOOM = 17.0f;
- public static final String ANALYTICS_ROUTE_LOCATION_FORMAT = "{\"latitude\":%f,\"longitude\":%f,\"floor\":%d}";
- public static final String ANALYTICS_USER_LOCATION_FORMAT = "{\"latitude\":%f,\"longitude\":%f,\"floor\":%d,\"timestamp\":%d}";
//Health
static final String HEALTH_SHARED_PREFS_FILE_NAME = "health_shared_prefs";
@@ -59,42 +52,6 @@ public class Constants {
//Encryption Key
static final String ENCRYPTION_SHARED_PREFS_FILE_NAME = "encryption_shared_prefs";
- //Exposure
- public static final String EXPOSURE_PLUGIN_METHOD_NAME_START = "start";
- public static final String EXPOSURE_PLUGIN_METHOD_NAME_STOP = "stop";
- public static final String EXPOSURE_PLUGIN_METHOD_NAME_TEKS = "TEKs";
- public static final String EXPOSURE_PLUGIN_METHOD_NAME_TEK_RPIS = "tekRPIs";
- public static final String EXPOSURE_PLUGIN_METHOD_NAME_RPI_LOG = "exposureRPILog";
- public static final String EXPOSURE_PLUGIN_METHOD_NAME_THICK = "exposureThick";
- public static final String EXPOSURE_PLUGIN_METHOD_NAME_RSSI_LOG = "exposureRSSILog";
- public static final String EXPOSURE_PLUGIN_METHOD_NAME_EXPIRE_TEK = "expireTEK";
- public static final String EXPOSURE_PLUGIN_METHOD_EXP_UP_TIME = "exposureUpTime";
- public static final String EXPOSURE_PLUGIN_SETTINGS_PARAM_NAME = "settings";
- public static final String EXPOSURE_PLUGIN_RPI_PARAM_NAME = "rpi";
- public static final String EXPOSURE_PLUGIN_TEK_METHOD_NAME = "tek";
- public static final String EXPOSURE_PLUGIN_TEK_PARAM_NAME = "tek";
- public static final String EXPOSURE_PLUGIN_TIMESTAMP_PARAM_NAME = "timestamp";
- public static final String EXPOSURE_PLUGIN_UP_TIME_WIN_PARAM_NAME = "upTimeWindow";
- public static final String EXPOSURE_PLUGIN_EXPOSURE_METHOD_NAME = "exposure";
- public static final String EXPOSURE_PLUGIN_DURATION_PARAM_NAME = "duration";
- public static final String EXPOSURE_PLUGIN_RSSI_PARAM_NAME = "rssi";
- public static final String EXPOSURE_PLUGIN_ADDRESS_PARAM_NAME = "address";
- public static final String EXPOSURE_PLUGIN_IOS_RECORD_PARAM_NAME = "isiOSRecord";
- public static final String EXPOSURE_PLUGIN_PERIPHERAL_UUID_PARAM_NAME = "peripheralUuid";
- public static final String EXPOSURE_PLUGIN_TEK_EXPIRE_PARAM_NAME = "expirestamp";
- public static final String EXPOSURE_BLE_DEVICE_FOUND = "edu.illinois.rokwire.exposure.ble.FOUND_DEVICE";
- public static final String EXPOSURE_BLE_ACTION_FOUND = "edu.illinois.rokwire.exposure.ble.scan.ACTION_FOUND";
- public static final int EXPOSURE_NO_RSSI_VALUE = 127;
- public static final int EXPOSURE_MIN_RSSI_VALUE = -50;
- public static final int EXPOSURE_MIN_DURATION_MILLIS = 0; // 0 minute
- public static final UUID EXPOSURE_UUID_SERVICE = UUID.fromString("0000CD19-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid EXPOSURE_PARCEL_SERVICE_UUID = new ParcelUuid(EXPOSURE_UUID_SERVICE);
- public static final UUID EXPOSURE_UUID_CHARACTERISTIC = UUID.fromString("1f5bb1de-cdf0-4424-9d43-d8cc81a7f207");
- public static final int EXPOSURE_CONTRACT_NUMBER_LENGTH = 20;
- public static final String EXPOSURE_TEKS_SHARED_PREFS_FILE_NAME = "exposure_teks_shared_prefs";
- public static final String EXPOSURE_TEKS_SHARED_PREFS_KEY = "exposure_teks";
- public static final String EXPOSURE_TEK_VERSION = "tekDatabaseVersion";
-
//Gallery
public static final String GALLERY_PLUGIN_METHOD_NAME_STORE = "store";
public static final String GALLERY_PLUGIN_PARAM_BYTES = "bytes";
diff --git a/android/app/src/main/java/edu/illinois/covid/MainActivity.java b/android/app/src/main/java/edu/illinois/covid/MainActivity.java
index 7337604c..e1c0601d 100644
--- a/android/app/src/main/java/edu/illinois/covid/MainActivity.java
+++ b/android/app/src/main/java/edu/illinois/covid/MainActivity.java
@@ -52,7 +52,6 @@
import java.util.Set;
import java.util.UUID;
-import edu.illinois.covid.exposure.ExposurePlugin;
import edu.illinois.covid.gallery.GalleryPlugin;
import edu.illinois.covid.maps.MapActivity;
@@ -74,8 +73,6 @@ public class MainActivity extends FlutterActivity implements MethodChannel.Metho
private static final String NATIVE_CHANNEL = "edu.illinois.covid/core";
private static MainActivity instance = null;
- private ExposurePlugin exposurePlugin;
-
private HashMap keys;
private int preferredScreenOrientation;
@@ -94,15 +91,6 @@ protected void onCreate(Bundle savedInstanceState) {
initScreenOrientation();
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- // when activity is killed by user through the app manager, stop all exposure-related services
- if (exposurePlugin != null) {
- exposurePlugin.handleStop();
- }
- }
-
public static MainActivity getInstance() {
return instance;
}
@@ -152,8 +140,6 @@ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
.getRegistry()
.registerViewFactory("mapview", new MapViewFactory(this, flutterEngine.getDartExecutor().getBinaryMessenger()));
- exposurePlugin = new ExposurePlugin(this);
- flutterEngine.getPlugins().add(exposurePlugin);
galleryPlugin = new GalleryPlugin(this);
flutterEngine.getPlugins().add(galleryPlugin);
}
@@ -224,10 +210,6 @@ private void requestLocationPermission(MethodChannel.Result result) {
public void onResult(boolean granted) {
if (granted) {
result.success("allowed");
-
- if (exposurePlugin != null) {
- exposurePlugin.onLocationPermissionGranted();
- }
} else {
result.success("denied");
}
diff --git a/android/app/src/main/java/edu/illinois/covid/Utils.java b/android/app/src/main/java/edu/illinois/covid/Utils.java
index 125fe03e..28483196 100644
--- a/android/app/src/main/java/edu/illinois/covid/Utils.java
+++ b/android/app/src/main/java/edu/illinois/covid/Utils.java
@@ -17,7 +17,6 @@
package edu.illinois.covid;
import android.app.AlertDialog;
-import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
@@ -77,13 +76,6 @@ public static void showDialog(Context context, String title, String message,
alertDialog.show();
}
- public static void enabledBluetooth() {
- BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- if (bluetoothAdapter != null) {
- bluetoothAdapter.enable();
- }
- }
-
public static class DateTime {
static Date getDateTime(String dateTimeString) {
diff --git a/android/app/src/main/java/edu/illinois/covid/exposure/ExposurePlugin.java b/android/app/src/main/java/edu/illinois/covid/exposure/ExposurePlugin.java
deleted file mode 100644
index 79005a1f..00000000
--- a/android/app/src/main/java/edu/illinois/covid/exposure/ExposurePlugin.java
+++ /dev/null
@@ -1,1487 +0,0 @@
-/*
- * Copyright 2020 Board of Trustees of the University of Illinois.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package edu.illinois.covid.exposure;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCallback;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattService;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.le.BluetoothLeScanner;
-import android.bluetooth.le.ScanCallback;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanRecord;
-import android.bluetooth.le.ScanResult;
-import android.bluetooth.le.ScanSettings;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.ParcelUuid;
-import android.util.Log;
-
-import com.welie.blessed.BluetoothCentral;
-import com.welie.blessed.BluetoothPeripheral;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.nio.ByteBuffer;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.UUID;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.IOException;
-import java.util.concurrent.ConcurrentHashMap;
-
-import androidx.annotation.NonNull;
-import at.favre.lib.crypto.HKDF;
-import edu.illinois.covid.Constants;
-import edu.illinois.covid.MainActivity;
-import edu.illinois.covid.R;
-import edu.illinois.covid.Utils;
-import edu.illinois.covid.exposure.ble.ExposureClient;
-import edu.illinois.covid.exposure.ble.ExposureServer;
-import edu.illinois.covid.exposure.crypto.AES;
-import edu.illinois.covid.exposure.crypto.AES_CTR;
-import io.flutter.embedding.engine.plugins.FlutterPlugin;
-import io.flutter.plugin.common.BinaryMessenger;
-import io.flutter.plugin.common.MethodCall;
-import io.flutter.plugin.common.MethodChannel;
-
-public class ExposurePlugin implements MethodChannel.MethodCallHandler, FlutterPlugin {
-
- private static final String TAG = "ExposurePlugin";
-
- private MainActivity activityContext;
- private MethodChannel methodChannel;
-
- private MethodChannel.Result startedResult;
- private Object settings;
-
- private ExposureServer exposureServer;
- private boolean serverStarted;
- private ExposureClient androidExposureClient;
- private boolean clientStarted;
-
- // iOS specific scanner/client
- private BluetoothCentral iosExposureCentral;
- private Handler handler = new Handler();
- private Map peripherals;
- private Map peripheralsRPIs;
- private Map iosExposures;
-
- // iOS background variable scanner
- private static final int iosbgManufacturerID = 76;
- private static final byte[] manufacturerDataMask = Utils.Str.hexStringToByteArray("ff00000000000000000000000000002000");
- private static final byte[] manufacturerData = Utils.Str.hexStringToByteArray("0100000000000000000000000000002000");
- private BluetoothAdapter iosBgBluetoothAdapter;
- private BluetoothLeScanner iosBgBluoetoothLeScanner;
- private List iosBgScanFilters;
- private ScanSettings iosBgScanSettings;
- private Map peripherals_bg;
- private Handler ios_bg_handler = new Handler();
- private Handler mainHandler = new Handler(Looper.getMainLooper());
- private Handler callbackHandler = new Handler();
- private static final String CCC_DESCRIPTOR_UUID = "00002902-0000-1000-8000-00805f9b34fb"; // characteristic notification
- private Runnable iosBgScanTimeoutRunnable; // for restarting scan in android 9
-
- private Timer expUpTimeTimer;
- private Map expUpTimeMap;
- private long expStartTime = 0;
-
- // RPI
- private byte[] rpi;
- private Timer rpiTimer;
- private static final int RPI_REFRESH_INTERVAL_SECS = 10 * 60; // 10 minutes
- private static final int TEKRollingPeriod = 144;
-
- private Timer exposuresTimer;
- private long lastNotifyExposureTickTimestamp;
- private Map androidExposures;
-
- private Map> i_TEK_map;
-
- // Exposure Constants
- private static final int EXPOSURE_TIMEOUT_INTERVAL_MILLIS = 2 * 60 * 1000; // 2 minutes
- private static final int EXPOSURE_PING_INTERVAL_MILLIS = 60 * 1000; // 1 minute
- private static final int EXPOSURE_PROCESS_INTERVAL_MILLIS = 10 * 1000; // 10 secs
- private static final int EXPOSURE_NOTIFY_TICK_INTERVAL_MILLIS = 1000; // 1 secs
-
- // Exposure Settings
- private int exposureTimeoutIntervalInMillis;
- private int exposurePingIntervalInMillis;
- private int exposureProcessIntervalInMillis;
- private int exposureMinDurationInMillis;
- private int exposureMinRssi = Constants.EXPOSURE_MIN_RSSI_VALUE;
- private int exposureExpireDays;
-
- // Helper constants
- private static final String TEK_MAP_KEY = "tek";
- private static final String RPI_MAP_KEY = "rpi";
- private static final String EN_INTERVAL_NUMBER_MAP_KEY = "ENIntervalNumber";
- private static final String I_MAP_KEY = "i";
- private static final String DATABASE_VERSION_KEY = "databaseversion";
-
- public ExposurePlugin(MainActivity activity) {
- this.activityContext = activity;
-
- this.peripherals = new HashMap<>();
- this.peripheralsRPIs = new HashMap<>();
- this.iosExposures = new ConcurrentHashMap<>();
- this.androidExposures = new ConcurrentHashMap<>();
-
- this.i_TEK_map = loadTeksFromStorage();
- this.peripherals_bg = new HashMap<>();
-
- loadExpUpTimeFromStorage();
- }
-
- //region Public APIs Implementation
-
- private void handleStart(@NonNull MethodChannel.Result result, Object settings) {
- Log.d(TAG, "handleStart: start plugin");
- startedResult = result;
- initSettings(settings);
- bindExposureServer();
- bindExposureClient();
- }
-
- public void handleStop() {
- Log.d(TAG, "handleStop: stop plugin");
- startedResult = null;
- stop();
- }
-
- private void start() {
- Log.d(TAG, "start");
- refreshRpi();
- startAdvertise();
- startRpiTimer();
- startExpUpTimeTimer();
- startScan();
- }
-
- private void stop() {
- Log.d(TAG, "stop");
- stopAdvertise();
- stopScan();
- clearRpi();
- clearExposures();
- stopRpiTimer();
- stopExpUpTimeTimer();
- expUpTimeHit();
- unBindExposureServer();
- unBindExposureClient();
- }
-
- private void refreshRpi() {
- Log.d(TAG, "refreshRpi");
- Map retVal = generateRpi();
- rpi = (byte[]) retVal.get(RPI_MAP_KEY);
- byte[] tek = (byte[]) retVal.get(TEK_MAP_KEY);
- int i = (int) retVal.get(I_MAP_KEY);
- int enIntervalNumber = (int) retVal.get(EN_INTERVAL_NUMBER_MAP_KEY);
- Log.d(TAG, "Logged - tek: " + Utils.Base64.encode(tek) + ", i: " + i + ", enIntervalNumber: " + enIntervalNumber);
- uploadRPIUpdate(rpi, tek, Utils.DateTime.getCurrentTimeMillisSince1970(), i, enIntervalNumber);
- String rpiEncoded = Utils.Base64.encode(rpi);
- Log.d(TAG, "final rpi = " + rpiEncoded + "size = " + (rpi != null ? rpi.length : "null"));
- if (exposureServer != null) {
- exposureServer.setRpi(rpi);
- }
- }
-
- private Map generateRpi() {
- long currentTimestampInMillis = Utils.DateTime.getCurrentTimeMillisSince1970();
- long currentTimeStampInSecs = currentTimestampInMillis / 1000;
- int timestamp = (int) currentTimeStampInSecs;
- int ENIntervalNumber = timestamp / RPI_REFRESH_INTERVAL_SECS;
- Log.d(TAG, "ENIntervalNumber = " + ENIntervalNumber);
- int i = (ENIntervalNumber / TEKRollingPeriod) * TEKRollingPeriod;
- int expireIntervalNumber = i + TEKRollingPeriod;
- Log.d(TAG, "i = " + i);
-
- /* if new day, generate a new tek */
- /* if in the rest of the day, using last valid TEK */
- if ((i_TEK_map != null) && !i_TEK_map.isEmpty()) {
- Integer lastTimestamp = Collections.max(i_TEK_map.keySet());
- Map lastTek = i_TEK_map.get(lastTimestamp);
- Integer lastExpireTime = (lastTek != null) ? lastTek.get(lastTek.keySet().iterator().next()) : null;
- if ((lastExpireTime != null) && (lastExpireTime == expireIntervalNumber)) {
- i = lastTimestamp;
- } else {
- i = ENIntervalNumber;
- }
- }
-
- Map tek = new HashMap<>();
- if (i_TEK_map.isEmpty() || !i_TEK_map.containsKey(i)) {
- byte[] bytes = new byte[16];
- SecureRandom rand = new SecureRandom(); // generating TEK with a cryptographic random number generator
- rand.nextBytes(bytes);
- tek.put(bytes, expireIntervalNumber);
- i_TEK_map.put(i, tek); // putting the TEK map as a value for the current i
-
- // handling more than 14 (exposureExpireDays) i values in the map
- if (i_TEK_map.size() >= exposureExpireDays) {
- Iterator it = i_TEK_map.keySet().iterator();
- while (it.hasNext()) {
- int key = it.next();
- if (key <= i - exposureExpireDays)
- it.remove();
- }
- }
-
- // Save TEKs to storage
- saveTeksToStorage(i_TEK_map);
-
- // Notify TEK
- long notifyTimestampInMillis = (long) i * RPI_REFRESH_INTERVAL_SECS * 1000; // in millis
- long expireTime = (long) expireIntervalNumber * RPI_REFRESH_INTERVAL_SECS * 1000;
- byte[] tekData = tek.keySet().iterator().next();
- notifyTek(tekData, notifyTimestampInMillis, expireTime);
- } else {
- tek = i_TEK_map.get(i);
- }
- byte[] rpiTek = (tek != null) ? tek.keySet().iterator().next() : null;
- byte[] rpi = generateRpiForIntervalNumber(ENIntervalNumber, rpiTek);
- Map retVal = new HashMap<>();
- retVal.put(RPI_MAP_KEY, rpi);
- retVal.put(TEK_MAP_KEY, rpiTek);
- retVal.put(EN_INTERVAL_NUMBER_MAP_KEY, ENIntervalNumber);
- retVal.put(I_MAP_KEY, i);
- return retVal;
- }
-
- private byte[] generateRpiForIntervalNumber(int enIntervalNumber, byte[] tek) {
- // generating RPIK with salt as null and passing in the correct parameters to the extractandexpand function of HKDF class
- // in the file HKDF.java
- byte[] salt = null;
- byte[] info = "EN-RPIK".getBytes();
- byte[] RPIK = HKDF.fromHmacSha256().extractAndExpand(salt, tek, info, 16);
-
- // generating AEMK using the same method
- info = "EN-AEMK".getBytes();
- byte[] AEMK = HKDF.fromHmacSha256().extractAndExpand(salt, tek, info, 16);
-
- // creating the padded data for AES encryption of RPIK to get RPI
- byte[] padded_data = new byte[16];
- byte[] EN_RPI = "EN-RPI".getBytes();
- ByteBuffer bb = ByteBuffer.allocate(4);
- bb.putInt(enIntervalNumber);
- byte[] ENIN = bb.array();
- int j = 0;
- for (byte b : EN_RPI) {
- padded_data[j] = b;
- j++;
- }
- for (j = 6; j <= 11; j++) {
- padded_data[j] = 0;
- }
- for (byte b : ENIN) {
- padded_data[j] = b;
- j++;
- }
-
- byte[] rpi_byte = AES.encrypt(RPIK, padded_data);
-
- if (rpi_byte == null) {
- Log.w(TAG, "Newly generated rpi_byte is null");
- return null;
- }
-
- byte[] metadata = new byte[4];
- byte[] AEM_byte = new byte[4];
- try {
- AEM_byte = AES_CTR.encrypt(AEMK, rpi_byte, metadata);
- } catch (Exception e) {
- System.out.println("Error while encrypting: " + e.toString());
- }
-
- byte[] bluetoothpayload = new byte[20];
- System.arraycopy(rpi_byte, 0, bluetoothpayload, 0, rpi_byte.length);
- System.arraycopy(AEM_byte, 0, bluetoothpayload, rpi_byte.length, AEM_byte.length);
-
- return bluetoothpayload;
- }
-
- private void uploadRPIUpdate(byte[] rpi, byte[] parentTek, long updateTime, int i, int ENInvertalNumber) {
- String tekString = Utils.Base64.encode(parentTek);
- String rpiString = Utils.Base64.encode(rpi);
- Map rpiParams = new HashMap<>();
- rpiParams.put(Constants.EXPOSURE_PLUGIN_TIMESTAMP_PARAM_NAME, updateTime);
- rpiParams.put(Constants.EXPOSURE_PLUGIN_TEK_PARAM_NAME, tekString);
- rpiParams.put(Constants.EXPOSURE_PLUGIN_RPI_PARAM_NAME, rpiString);
- rpiParams.put("updateType", "");
- rpiParams.put("_i", i);
- rpiParams.put("ENInvertalNumber", ENInvertalNumber);
- invokeFlutterMethod(Constants.EXPOSURE_PLUGIN_METHOD_NAME_RPI_LOG, rpiParams);
- }
-
- private void clearRpi() {
- rpi = null;
- }
-
- private void startAdvertise() {
- if (exposureServer != null) {
- exposureServer.start();
- }
- }
-
- private void stopAdvertise() {
- if (exposureServer != null) {
- exposureServer.stop();
- }
- }
-
- private void startScan() {
- if (androidExposureClient != null) {
- androidExposureClient.startScan();
- }
- startIosScan();
- }
-
- private void stopScan() {
- if (androidExposureClient != null) {
- androidExposureClient.stopScan();
- }
- stopIosScan();
- processExposures();
- }
-
- private void initSettings(Object settings) {
- this.settings = settings;
-
- // Exposure Timeout Interval
- int timeoutIntervalInSecs = Utils.Map.getValueFromPath(settings, "covid19ExposureServiceTimeoutInterval", (EXPOSURE_TIMEOUT_INTERVAL_MILLIS / 1000)); // in seconds
- this.exposureTimeoutIntervalInMillis = timeoutIntervalInSecs * 1000; //in millis
-
- // Exposure Ping Interval
- int pingIntervalInSecs = Utils.Map.getValueFromPath(settings, "covid19ExposureServicePingInterval", (EXPOSURE_PING_INTERVAL_MILLIS / 1000)); // in seconds
- this.exposurePingIntervalInMillis = pingIntervalInSecs * 1000; //in millis
-
- // Exposure Process Interval
- int processIntervalInSecs = Utils.Map.getValueFromPath(settings, "covid19ExposureServiceProcessInterval", (EXPOSURE_PROCESS_INTERVAL_MILLIS / 1000)); // in seconds
- this.exposureProcessIntervalInMillis = processIntervalInSecs * 1000; //in millis
-
- // Exposure Min Duration Interval
- int minDurationInSecs = Utils.Map.getValueFromPath(settings, "covid19ExposureServiceLogMinDuration", (Constants.EXPOSURE_MIN_DURATION_MILLIS / 1000)); // in seconds
- this.exposureMinDurationInMillis = minDurationInSecs * 1000; //in millis
-
- // Exposure Min RSSI
- this.exposureMinRssi = Utils.Map.getValueFromPath(settings, "covid19ExposureServiceMinRSSI", Constants.EXPOSURE_MIN_RSSI_VALUE);
-
- // Exposure Expire Days
- this.exposureExpireDays = Utils.Map.getValueFromPath(settings, "covid19ExposureExpireDays", 14);
- }
-
- //endregion
-
- //region Single Instance
-
- int getExposureMinRssi() {
- return exposureMinRssi;
- }
-
- //endregion
-
- //region External RPI implementation
-
- private void logAndroidExposure(String rpi, int rssi, String deviceAddress) {
- long currentTimeStamp = Utils.DateTime.getCurrentTimeMillisSince1970();
- ExposureRecord record = androidExposures.get(rpi);
- if (record == null) {
- Log.d(TAG, "registered android rpi: " + rpi);
- record = new ExposureRecord(currentTimeStamp, rssi);
- androidExposures.put(rpi, record);
- updateExposuresTimer();
- } else {
- record.updateTimeStamp(currentTimeStamp, rssi, exposureMinRssi);
- }
- notifyExposureTick(rpi, rssi);
- notifyExposureRssiLog(rpi, currentTimeStamp, rssi, false, deviceAddress);
- }
-
- private void logIosExposure(String peripheralAddress, int rssi) {
- if (Utils.Str.isEmpty(peripheralAddress)) {
- return;
- }
- long currentTimestamp = Utils.DateTime.getCurrentTimeMillisSince1970();
- ExposureRecord record = iosExposures.get(peripheralAddress);
- if (record == null) {
- // Create new
- Log.d(TAG, "Registered ios peripheral: " + peripheralAddress);
- record = new ExposureRecord(currentTimestamp, rssi);
- iosExposures.put(peripheralAddress, record);
- updateExposuresTimer();
- } else {
- // Update existing
- record.updateTimeStamp(currentTimestamp, rssi, exposureMinRssi);
- }
- byte[] rpi = peripheralsRPIs.get(peripheralAddress);
- String encodedRpi = "";
- if (rpi != null) {
- encodedRpi = Utils.Base64.encode(rpi);
- notifyExposureTick(encodedRpi, rssi);
- }
- notifyExposureRssiLog(encodedRpi, currentTimestamp, rssi, true, peripheralAddress);
- }
-
- private void notifyExposureTick(String rpi, int rssi) {
- if (Utils.Str.isEmpty(rpi)) {
- return;
- }
- long currentTimestamp = Utils.DateTime.getCurrentTimeMillisSince1970();
- // Do not allow more than one notification per second
- if (EXPOSURE_NOTIFY_TICK_INTERVAL_MILLIS <= (currentTimestamp - lastNotifyExposureTickTimestamp)) {
- Map exposureTickParams = new HashMap<>();
- exposureTickParams.put(Constants.EXPOSURE_PLUGIN_TIMESTAMP_PARAM_NAME, currentTimestamp);
- exposureTickParams.put(Constants.EXPOSURE_PLUGIN_RPI_PARAM_NAME, rpi);
- exposureTickParams.put(Constants.EXPOSURE_PLUGIN_RSSI_PARAM_NAME, rssi);
- invokeFlutterMethod(Constants.EXPOSURE_PLUGIN_METHOD_NAME_THICK, exposureTickParams);
- lastNotifyExposureTickTimestamp = currentTimestamp;
- }
- }
-
- private void notifyExposureRssiLog(String encodedRpi, long currentTimeStamp, int rssi, boolean isiOS, String address) {
- Map rssiParams = new HashMap<>();
- rssiParams.put(Constants.EXPOSURE_PLUGIN_RPI_PARAM_NAME, encodedRpi);
- rssiParams.put(Constants.EXPOSURE_PLUGIN_TIMESTAMP_PARAM_NAME, currentTimeStamp);
- rssiParams.put(Constants.EXPOSURE_PLUGIN_RSSI_PARAM_NAME, rssi);
- rssiParams.put(Constants.EXPOSURE_PLUGIN_IOS_RECORD_PARAM_NAME, isiOS);
- rssiParams.put(Constants.EXPOSURE_PLUGIN_ADDRESS_PARAM_NAME, address);
- invokeFlutterMethod(Constants.EXPOSURE_PLUGIN_METHOD_NAME_RSSI_LOG, rssiParams);
- }
-
- private void processExposures() {
- Log.d(TAG, "Process Exposures");
- long currentTimestamp = Utils.DateTime.getCurrentTimeMillisSince1970();
- Set expiredPeripheralAddress = null;
-
- // 1. Collect all iOS expired records (not updated after exposureTimeoutIntervalInMillis)
- if ((iosExposures != null) && !iosExposures.isEmpty()) {
- Iterator iosExposuresIterator = iosExposures.keySet().iterator();
- while (iosExposuresIterator.hasNext()) {
- String peripheralAddress = iosExposuresIterator.next();
- ExposureRecord record = iosExposures.get(peripheralAddress);
- if (record != null) {
- long lastHeardInterval = currentTimestamp - record.getTimestampUpdated();
- if (exposureTimeoutIntervalInMillis <= lastHeardInterval) {
- Log.d(TAG, "Expired ios exposure: " + peripheralAddress);
- if (expiredPeripheralAddress == null) {
- expiredPeripheralAddress = new HashSet<>();
- }
- expiredPeripheralAddress.add(peripheralAddress);
- } else if (exposurePingIntervalInMillis <= lastHeardInterval) {
- Log.d(TAG, "ios exposure ping: " + peripheralAddress);
- BluetoothPeripheral peripheral = (peripherals != null) ? peripherals.get(peripheralAddress) : null;
- if (peripheral != null) {
- peripheral.readRemoteRssi();
- }
- }
- }
- }
- }
-
- if ((expiredPeripheralAddress != null) && !expiredPeripheralAddress.isEmpty()) {
- // Create copy so that to prevent crash with ConcurrentModificationException.
- Set expiredPeripheralAddressCopy = new HashSet<>(expiredPeripheralAddress);
- // remove expired records from iosExposures
- Iterator expiredPeripheralIterator = expiredPeripheralAddressCopy.iterator();
- while (expiredPeripheralIterator.hasNext()) {
- String address = expiredPeripheralIterator.next();
- disconnectIosPeripheral(address);
- }
- }
-
- // 2. Collect all Android expired records (not updated after exposureTimeoutIntervalInMillis)
- Set expiredRPIs = null;
- if((androidExposures != null) && !androidExposures.isEmpty()) {
- Iterator androidExposuresIterator = androidExposures.keySet().iterator();
- while (androidExposuresIterator.hasNext()) {
- String encodedRpi = androidExposuresIterator.next();
- ExposureRecord record = androidExposures.get(encodedRpi);
- if (record != null) {
- long lastHeardInterval = currentTimestamp - record.getTimestampUpdated();
- if (exposureTimeoutIntervalInMillis <= lastHeardInterval) {
- Log.d(TAG, "Expired android exposure: " + encodedRpi);
- if (expiredRPIs == null) {
- expiredRPIs = new HashSet<>();
- }
- expiredRPIs.add(encodedRpi);
- }
- }
- }
- }
-
- if (expiredRPIs != null) {
- // Create copy so that to prevent crash with ConcurrentModificationException.
- Set expiredRPIsCopy = new HashSet<>(expiredRPIs);
- // remove expired records from androidExposures
- Iterator expiredRPIsIterator = expiredRPIsCopy.iterator();
- while (expiredRPIsIterator.hasNext()) {
- String encodedRpi = expiredRPIsIterator.next();
- removeAndroidRpi(encodedRpi);
- }
- }
- }
-
- private void clearExposures() {
- if ((iosExposures != null) && !iosExposures.isEmpty()) {
- Map iosExposureCopy = new ConcurrentHashMap<>(iosExposures);
- for (String address : iosExposureCopy.keySet()) {
- disconnectIosPeripheral(address);
- }
- }
- if ((androidExposures != null) && !androidExposures.isEmpty()) {
- Map androidExposureCopy = new ConcurrentHashMap<>(androidExposures);
- for (String encodedRpi : androidExposureCopy.keySet()) {
- removeAndroidRpi(encodedRpi);
- }
- }
- }
-
- private void disconnectIosPeripheral(String peripheralAddress) {
- disconnectIosBgPeripheral(peripheralAddress);
- }
-
- private void removeIosPeripheral(String address) {
- if (Utils.Str.isEmpty(address) || (peripherals == null) || (peripherals.isEmpty())) {
- return;
- }
- peripherals.remove(address);
- byte[] rpi = (peripheralsRPIs != null) ? peripheralsRPIs.get(address) : null;
- if (rpi != null) {
- peripheralsRPIs.remove(address);
- }
- if ((iosExposures == null) || iosExposures.isEmpty()) {
- return;
- }
- ExposureRecord record = iosExposures.get(address);
- if (record != null) {
- iosExposures.remove(address);
- updateExposuresTimer();
- }
- if ((rpi != null) && (record != null)) {
- String encodedRpi = Utils.Base64.encode(rpi);
- notifyExposure(record, encodedRpi, true, address);
- }
- }
-
- private void removeAndroidRpi(String rpi) {
- if ((androidExposures == null) || androidExposures.isEmpty()) {
- return;
- }
- ExposureRecord record = androidExposures.get(rpi);
- if (record != null) {
- androidExposures.remove(rpi);
- updateExposuresTimer();
- }
-
- if ((rpi != null) && (record != null)) {
- notifyExposure(record, rpi, false, "");
- }
- }
-
- private void notifyExposure(ExposureRecord record, String rpi, boolean isiOS, String peripheralUuid) {
- if ((record != null) && (exposureMinDurationInMillis <= record.getDuration())) {
- Map exposureParams = new HashMap<>();
- exposureParams.put(Constants.EXPOSURE_PLUGIN_TIMESTAMP_PARAM_NAME, record.getTimestampCreated());
- exposureParams.put(Constants.EXPOSURE_PLUGIN_RPI_PARAM_NAME, rpi);
- exposureParams.put(Constants.EXPOSURE_PLUGIN_DURATION_PARAM_NAME, record.getDuration());
- exposureParams.put(Constants.EXPOSURE_PLUGIN_IOS_RECORD_PARAM_NAME, isiOS);
- exposureParams.put(Constants.EXPOSURE_PLUGIN_PERIPHERAL_UUID_PARAM_NAME, peripheralUuid);
- invokeFlutterMethod(Constants.EXPOSURE_PLUGIN_EXPOSURE_METHOD_NAME, exposureParams);
- }
- }
-
- //endregion
-
- //region TEKs
-
- private void changeTekExpireTime() {
- i_TEK_map = loadTeksFromStorage();
- if (i_TEK_map != null) {
- Integer currentI = Collections.max(i_TEK_map.keySet());
- Map oldTEK = i_TEK_map.get(currentI);
- byte[] tek = (oldTEK != null) ? oldTEK.keySet().iterator().next() : null;
-
- long currentTimestampInMillis = Utils.DateTime.getCurrentTimeMillisSince1970();
- long currentTimeStampInSecs = currentTimestampInMillis / 1000;
- int timestamp = (int) currentTimeStampInSecs;
- int ENIntervalNumber = timestamp / RPI_REFRESH_INTERVAL_SECS;
- Map newTEK = new HashMap<>();
- newTEK.put(tek, (ENIntervalNumber + 1));
-
- i_TEK_map.replace(currentI, newTEK);
- saveTeksToStorage(i_TEK_map);
- }
- }
-
- private void saveTeksToStorage(Map> teks) {
- if (teks != null) {
- Map storageTeks = new HashMap<>();
- for (Integer key : teks.keySet()) {
- String storageKey = key != null ? Integer.toString(key) : null;
- Map value = teks.get(key);
- byte[] tek = (value != null) ? value.keySet().iterator().next() : null;
- Integer expire = (value != null) ? value.get(tek) : null;
- Map tekAndExpireTime = new HashMap<>();
- tekAndExpireTime.put(Utils.Base64.encode(tek), (expire != null ? Integer.toString(expire) : null));
- JSONObject jsonTekAndExpireTime = new JSONObject(tekAndExpireTime);
- String storageValue = jsonTekAndExpireTime.toString();
- if (storageKey != null) {
- storageTeks.put(storageKey, storageValue);
- }
- }
- JSONObject teksJson = new JSONObject(storageTeks);
- String teksString = teksJson.toString();
- Utils.BackupStorage.saveString(activityContext, Constants.EXPOSURE_TEKS_SHARED_PREFS_FILE_NAME, Constants.EXPOSURE_TEKS_SHARED_PREFS_KEY, teksString);
- } else {
- Utils.BackupStorage.remove(activityContext, Constants.EXPOSURE_TEKS_SHARED_PREFS_FILE_NAME, Constants.EXPOSURE_TEKS_SHARED_PREFS_KEY);
- }
- }
-
- private Map> loadTeksFromStorage() {
- Log.d(TAG, "entering loadTeksFromStorage function");
-
- //checking database version
- boolean dataBaseChangeVersion = false;
- String databaseVersion = Utils.BackupStorage.getString(activityContext, Constants.EXPOSURE_TEKS_SHARED_PREFS_FILE_NAME, Constants.EXPOSURE_TEK_VERSION);
- if (Utils.Str.isEmpty(databaseVersion)) {
- Log.d(TAG, "no database version found");
- dataBaseChangeVersion = true;
- } else {
- JSONObject jsonDatabaseVersion = null;
- try {
- jsonDatabaseVersion = new JSONObject(databaseVersion);
- } catch (JSONException e) {
- Log.e(TAG, "Failed to parse database version string to json!");
- e.printStackTrace();
- }
- if (jsonDatabaseVersion != null) {
- String version = jsonDatabaseVersion.optString(DATABASE_VERSION_KEY);
- Log.d(TAG, "current TEK database version is " + version);
- if (Utils.Str.isEmpty(version) || Integer.parseInt(version) != 2) {
- dataBaseChangeVersion = true;
- }
- }
- }
-
- if (dataBaseChangeVersion) {
- Utils.BackupStorage.remove(activityContext, Constants.EXPOSURE_TEKS_SHARED_PREFS_FILE_NAME, Constants.EXPOSURE_TEKS_SHARED_PREFS_KEY);
- }
-
- Map> teks = new HashMap<>();
- String teksString = Utils.BackupStorage.getString(activityContext, Constants.EXPOSURE_TEKS_SHARED_PREFS_FILE_NAME, Constants.EXPOSURE_TEKS_SHARED_PREFS_KEY);
- if (!Utils.Str.isEmpty(teksString)) {
- JSONObject teksJson = null;
- try {
- teksJson = new JSONObject(teksString);
- } catch (JSONException e) {
- Log.e(TAG, "Failed to parse TEKs string to json!");
- e.printStackTrace();
- }
- if (teksJson != null) {
- Iterator iterator = teksJson.keys();
- while (iterator.hasNext()) {
- String storageKey = iterator.next();
- String storageValue = teksJson.optString(storageKey);
- Map tekAndExpireTime = new HashMap<>();
- if (!Utils.Str.isEmpty(storageValue)) {
- JSONObject jsonTekAndExpireTime = null;
- try {
- jsonTekAndExpireTime = new JSONObject(storageValue);
- } catch (JSONException e) {
- Log.e(TAG, "Failed to parse TEK map string to json!");
- e.printStackTrace();
- }
- if (jsonTekAndExpireTime != null) {
- Log.d(TAG, "LoadTEK: Found Nested Map");
- Iterator tekIterator = jsonTekAndExpireTime.keys();
- if (tekIterator.hasNext()) {
- String tekString = tekIterator.next();
- String expireString = jsonTekAndExpireTime.optString(tekString);
- tekAndExpireTime.put(Utils.Base64.decode(tekString), Integer.parseInt(expireString));
- }
- }
- }
- teks.put(Integer.parseInt(storageKey), tekAndExpireTime);
- }
- }
- }
-
- // update database version
-
- if (dataBaseChangeVersion) {
- Map databaseVersionToStore = new HashMap<>();
- databaseVersionToStore.put(DATABASE_VERSION_KEY, Integer.toString(2));
- JSONObject jsonDatabaseVersion = new JSONObject(databaseVersionToStore);
- String databaseVersionString = jsonDatabaseVersion.toString();
- Utils.BackupStorage.saveString(activityContext, Constants.EXPOSURE_TEKS_SHARED_PREFS_FILE_NAME, Constants.EXPOSURE_TEK_VERSION, databaseVersionString);
- }
- return teks;
- }
-
- private List