public class PlatformVersion {
    public static boolean isAtLeastM() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
    }

    public static boolean isAtLeastO() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
    }
} public class Log {
    private static final String TAG = "ExposureNotification";
    public static final Log log = new Log();

    public Logger atVerbose() {
        return new Logger(android.util.Log::v);
    }

    public Logger atFine() {
        return new Logger(android.util.Log::d);
    }

    public Logger atDebug() {
        return new Logger(android.util.Log::d);
    }

    public Logger atInfo() {
        return new Logger(android.util.Log::i);
    }

    public Logger atWarning() {
        return new Logger(android.util.Log::w);
    }

    public Logger atSevere() {
        return new Logger(android.util.Log::e);
    }

    private Log() {
    }

    public static final class Logger {
        @NonNull
        private final LogFunction<String, String, Throwable> out;
        @Nullable
        private Throwable cause;

        Logger(@NonNull LogFunction<String, String, Throwable> out) {
            this.out = out;
        }

        public Logger withCause(@NonNull Throwable cause) {
            this.cause = cause;
            return this;
        }

        public void log(@NonNull String message) {
            out.invoke(TAG, message, cause);
        }

        public void log(@NonNull String format, Object... args) {
            log(String.format(format, args));
        }
    }

    private interface LogFunction<P1, P2, P3> {
        void invoke(P1 p1, P2 p2, P3 p3);
    }
} public interface BleDatabaseWriter {
    /**
     * Writes sighting contact tracing ID into database.
     */
    void writeBleSighting(
            byte[] rollingProximityId,
            int rssi,
            byte[] associatedEncryptedMetadata,
            int previousScanEpochSeconds);

    /**
     * Writes new generated contact tracing ID into database.
     */
    void writeNewTemporaryExposureKey(TemporaryExposureKey temporaryExposureKey);
} public interface BleExecutors {
    /**
     * Schedules a {@code command} with specific delay.
     *
     * @return the canceller of this scheduled task
     */
    ScheduledTask schedule(Runnable command, long delay, TimeUnit unit);

    /**
     * Schedules a {@code command} with specific delay with {@link ScheduledExecutorService}.
     *
     * @return the canceller of this scheduled task
     */
    ScheduledTask scheduleWithScheduledExecutor(Runnable command, long delay, TimeUnit unit);

    /**
     * Schedules a {@code command} with specific delay. OpportunisticSchedule will not be executed
     * exactly on time, but will be checked everytime when {@link BleExecutors#schedule(Runnable,
     * long, TimeUnit)} called or its scheduled task fired. If current time is larger than target time
     * when checking, the opportunisticSchedule will be executed.
     */
    ScheduledTask opportunisticSchedule(Runnable runnable, long delay, TimeUnit unit);

    void start();

    void stop();

    /**
     * Allows scheduled runnable to be cancelled, and also provides status check for cancellation.
     */
    interface ScheduledTask {
        boolean isCancelable();

        boolean cancel();
    }
} interface ScheduledTask {
    boolean isCancelable();

    boolean cancel();
} public interface BleScanner {
    /**
     * Checks if current device is able to scan.
     */
    StatusCode supportScanning();

    /**
     * Starts advertising. Should only be called when user opt in, or reboot completed.
     */
    StatusCode startScanning();

    /**
     * Starts advertising. Should only be called when user opt out, or contact tracing is no longer
     * necessary.
     */
    StatusCode stopScanning();

    /**
     * Indicates current scanner's state is scanning or not.
     */
    boolean isScanning();
} @SuppressWarnings("MissingPermission")
public class BluetoothUtils {
    public static boolean isBluetoothProfileInUsing(List<Integer> profiles) {
        /**
         * FIXME: Get the device's Bluetooth Adapter from
         * {@link ContextCompat#getSystemService(Context, Class)} with the class
         * {@link BluetoothManager}
         */
        BluetoothAdapter adapter = null;
        if (adapter == null || !adapter.isEnabled()) {
            return false;
        }

        for (int profile : profiles) {
            int connectingState = adapter.getProfileConnectionState(profile);
            if (connectingState == BluetoothAdapter.STATE_CONNECTED
                    || connectingState == BluetoothAdapter.STATE_CONNECTING) {
                return true;
            }
        }
        return false;
    }

    private BluetoothUtils() {
    }
} public class Constants {
    public static final int METADATA_LENGTH = 4;
    public static final ParcelUuid CONTACT_TRACER_UUID = new ParcelUuid(to128BitUuid((short) 0xFD6F));
    private static final int BIT_INDEX_OF_16_BIT_UUID = 32;

    public static UUID to128BitUuid(short shortUuid) {
        UUID baseUuid = UUID.fromString("00000000-0000-1000-8000-00805F9B34FB");
        return new UUID(
                ((shortUuid & 0xFFFFL) << BIT_INDEX_OF_16_BIT_UUID) | baseUuid.getMostSignificantBits(),
                baseUuid.getLeastSignificantBits());
    }

    public static String fromIdByteArrayToString(byte[] idByteArray) {
        return base16().encode(idByteArray);
    }

    /** The result of start or stop scanning & advertising. */
    public enum StatusCode {
        UNKNOWN(ExposureNotificationStatusCodes.FAILED),
        OK(ExposureNotificationStatusCodes.SUCCESS),
        ERROR_BLUETOOTH_NOT_SUPPORTED(ExposureNotificationStatusCodes.FAILED_NOT_SUPPORTED),
        ERROR_BLUETOOTH_DISABLED(ExposureNotificationStatusCodes.FAILED_BLUETOOTH_DISABLED),
        ERROR_BLUETOOTH_UNKNOWN(ExposureNotificationStatusCodes.INTERNAL_ERROR);

        private final int apiStatusCode;

        StatusCode(int apiStatusCode) {
            this.apiStatusCode = apiStatusCode;
        }

        public int getApiStatusCode() {
            return apiStatusCode;
        }
    }

    private Constants() {}
} public enum StatusCode {
    UNKNOWN(ExposureNotificationStatusCodes.FAILED),
    OK(ExposureNotificationStatusCodes.SUCCESS),
    ERROR_BLUETOOTH_NOT_SUPPORTED(ExposureNotificationStatusCodes.FAILED_NOT_SUPPORTED),
    ERROR_BLUETOOTH_DISABLED(ExposureNotificationStatusCodes.FAILED_BLUETOOTH_DISABLED),
    ERROR_BLUETOOTH_UNKNOWN(ExposureNotificationStatusCodes.INTERNAL_ERROR);

    private final int apiStatusCode;

    StatusCode(int apiStatusCode) {
        this.apiStatusCode = apiStatusCode;
    }

    public int getApiStatusCode() {
        return apiStatusCode;
    }
} public class ContactTracingFeature {
    /**
     * The input parameter as HKDF info to generate AEMK.
     */
    public static String aemkHkdfInfoString() {
        return "EN-AEMK";
    }

    /**
     * The length of associated metadata encryption key in bytes.
     */
    public static int associatedMetadataEncryptionKeySizeBytes() {
        return 16;
    }

    /**
     * The length for which a Temporary Tracing Key is valid in multiple of id rolling period.
     */
    public static int tkRollingPeriodMultipleOfIdRollingPeriod() {
        return 144;
    }

    /**
     * The length of temporary tracing key in bytes.
     */
    public static int temporaryTracingKeySizeBytes() {
        return 16;
    }

    /**
     * The number of rolling periods that can be used before and after an RPIs validity time
     * during matching.
     */
    public static int tkMatchingClockDriftRollingPeriods() {
        return 12;
    }

    /**
     * The length for which an advertising ID is valid.
     */
    public static int idRollingPeriodMinutes() {
        return 10;
    }

    /**
     * The length of rolling proximity ID key in bytes.
     */
    public static int rollingProximityIdKeySizeBytes() {
        return 16;
    }

    /**
     * The input parameter as HKDF info to generate RPIK.
     */
    public static String rpikHkdfInfoString() {
        return "EN-RPIK";
    }

    /**
     * The padded constant for generating rolling proximity IDs
     */
    public static String rpidAesPaddedString() {
        return "EN-RPI";
    }

    /**
     * The number of (full) days to keep the contact record data
     */
    public static int contactRecordDaysToKeep() {
        return 14;
    }

    /**
     * The transmit power as would be reported by an average iPhone
     * transmitting at the same power as the Android device running with
     * ADVERTISE_TX_POWER_LOW
     */
    public static int txCalibrationPower() {
        return 0;
    }

    /**
     * Offset to add to the tx calibration power to obtain the tx
     * power to publish in the payload
     */
    public static int txCalibrationPowerToTxPower() {
        return 0;
    }

    /**
     * If enabled, the v1.1 metadata format will be advertised which
     * includes the calibration confidence value.
     */
    public static boolean advertisementMetadataV11() {
        return true;
    }

    /**
     * The confidence value for tx_calibration_power and rssi_offset.
     * Higher value means higher confidence.
     * Sees: exposurenotification/src/main/proto/calibration_enums.proto
     */
    public static byte calibrationConfidence() {
        return 0;
    }

    /**
     * The length of contact ID.
     */
    public static int contactIdLength() {
        return 16;
    }

    /**
     * Value of ADVERTISE_MODE_X enum that should be used.
     */
    public static int advertiseMode() {
        return 1; // BALANCED (250ms)
    }

    /**
     * Value of ADVERTISE_TX_POWER_X enum that should be used
     */
    public static int advertiseTxPower() {
        return 1;  // LOW (-15dB)
    }

    /**
     * Whether the advertisement should be connectable
     */
    public static boolean advertiseConnectable() {
        return false;
    }

    /**
     * Whether the BLE advertiser is legacy mode, will only take effect for O and above
     */
    public static boolean advertiseLegacyMode() {
        return true;
    }

    /**
     * The BLE advertising interval, will only take effect for O and above
     */
    public static int advertiseInterval() {
        return 400;  // AdvertisingSetParameters.INTERVAL_MEDIUM
    }

    /**
     * Value of ADVERTISE_TX_POWER_X enum that should be used
     */
    public static int advertiseTxPowerO() {
        return -15; // AdvertisingSetParameters.TX_POWER_LOW
    }

    /**
     * Whether the BLE advertiser is scannable, will only take effect for O and above
     */
    public static boolean advertiseScannable() {
        return false;
    }

    /**
     * Whether using startAdvertisingSet to start advertising for O and above
     */
    public static boolean advertiseUseOreoAdvertiser() {
        return false;
    }

    /**
     * File name of signature inside diagnosis keys zip
     */
    public static String diagnosisKeySignatureFileName() {
        return "export.sig";
    }

    /**
     * File name of key data binary inside diagnosis keys zip
     */
    public static String diagnosisKeyBinFileName() {
        return "export.bin";
    }

    /**
     * Calculates the hash of all keys when provideDiagnosisKeys is called.
     */
    public static boolean calculateDiagnosisKeyHash() {
        return true;
    }

    /**
     * Maximum permissible batch size group size of signature
     */
    public static int signatureMaxAllowedBatchGroupSize() {
        return 1_000_000;
    }

    /**
     * If enabled, signature verification would accept multiple whole batches
     */
    public static boolean enableMultipleWholeBatchSignatureVerification() {
        return true;
    }

    /**
     * Allow signature files to have signatures without a matching public key, or more matching
     * public keys than signatures, during verification.
     */
    public static boolean ignoreUnmatchedSignatures() {
        return true;
    }

    /**
     * A list of partner public keys for diagnosis key signature verification.
     */
    public static List<String> partnerPublicKeys() {
        return Arrays.asList(TEST_PARTNER_PUBLIC_KEYS);
    }

    /**
     * each list entry is a string formatted as:
     * <my.package.name>:<COSIGN_SET>|<COSIGN_SET>|...
     * <p>
     * COSIGN_SET:
     * <PUBLIC_KEY>&<PUBLIC_KEY>&...
     * <p>
     * PUBLIC_KEY:
     * <NAME>,<BASE64KEYVALUE>
     * <p>
     * NAME:
     * <KEY_ID>-<KEY_VERSION>
     * <p>
     * KEY_ID and KEY_VERSION:
     * arbitrary alphanumeric identifiers. Match verification_key_id
     * and verification_key_version in SignatureInfo fields of the
     * diagnosis export proto. Should definitely not contain any separator
     * character used in the flag definition. A name must be defined here, but
     * verification_key_id may be omitted in the proto if there is only a
     * single signature in the proto.
     * <p>
     * each co-signing set may include multiple key values, where all values
     * within a set are verified together. multiple co-signing sets may be
     * specified in order to support key rotations.
     * <p>
     * FIXME: Deliver authorized PHA public keys to devices.
     */
    private static final String[] TEST_PARTNER_PUBLIC_KEYS = {
            "com.google.android.apps.exposurenotification:ExampleServer-v1,MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUczyMAkfSeoU77Nmcb1G7t7xyGCAhQqMOIVDFLFas3J+elP7CiotovigCLWj706F07j1EPL27ThRzZl7Ha9uOA==|310-v1,MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE49JY6kekDgxj3Crm4y6kEHdfoKQFSNDM4mV9cgDb+e5nOAw0GeRoRThCu9/wX5wDT2QloFoOjl2pGZHI0f3C3w=="
    };

    /**
     * If enabled, will read full header
     */
    public static boolean readFullMetadataHeader() {
        return true;
    }

    /**
     * The Settings.Global name for the BLE low latency scan window value.
     */
    public static String lowLatencyScanWindowSettingsName() {
        return "ble_scan_low_latency_window_ms";
    }

    /**
     * The Settings.Global name for the BLE low latency scan interval value.
     */
    public static String lowLatencyScanIntervalSettingsName() {
        return "ble_scan_low_latency_interval_ms";
    }

    /**
     * The default low latency scan window value to be set when scanning for EN is inactive.
     */
    public static int lowLatencyScanWindowDefaultValueMillis() {
        return 4096;
    }

    /**
     * The default low latency scan interval value to be set when scanning for EN is inactive.
     */
    public static int lowLatencyScanIntervalDefaultValueMillis() {
        return 4096;
    }

    /**
     * Minimum Scan interval between each scan.
     */
    public static int minScanIntervalSeconds() {
        return 60;
    }

    /**
     * Scan interval between each scan.
     */
    public static int scanIntervalSeconds() {
        return 300; // 5 * 60
    }

    /**
     * Ratio for scan interval while audio playback. [0.1-1]
     */
    public static float scanIntervalRatioWhenAudioPlayback() {
        return 0.6f;
    }

    /**
     * Ratio for scan interval while screen on. [0.1-1]
     */
    public static float scanIntervalRatioWhenScreenOn() {
        return 0.8f;
    }

    /**
     * If enabled, the BLE scanning interval and window will be shrunk so that over the course of
     * the BLE scan, we use all 3 channels instead of only 1.
     */
    public static boolean bleScanningAdjustIntervalAndWindowValues() {
        return true;
    }

    /**
     * Offset to add to the measured RSSI to obtain a calibrated RSS.
     * <p>
     * The offset in measured rssi between an average iphone and a test
     * device. Experimentally determined on the most popular devices, and
     * extrapolated for similar models.
     * calibrated_rssi = measured_rssi + rssi_offset
     */
    public static int rssiOffset() {
        return 0;
    }

    /**
     * Random parameter for scan interval. The actual scan interval will be
     * scan_interval_seconds - RandomInt() % scan_interval_random_range_seconds,
     * and by default it could be 210 ~ 300 seconds.
     */
    public static int scanIntervalRandomRangeSeconds() {
        return 90; // 1.5 * 60
    }

    /**
     * Scan time for piggyback scan with other app's wake ups.
     */
    public static int piggybackScanTimeSeconds() {
        return 2;
    }

    /**
     * Scan time for each scan.
     */
    public static int scanTimeSeconds() {
        return 4;
    }

    /**
     * Additional scan time while some specific bluetooth profile is in use. If condition
     * matches, the total scan time will be scan_time_seconds +
     * scan_time_extend_for_profile_in_use_seconds,
     * by default it will be 15 seconds.
     */
    public static int scanTimeExtendForProfileInUseSeconds() {
        return 13;
    }

    /**
     * Only extend scan while bluetooth audio streaming or while in calls.
     */
    public static boolean extendScanOnlyForBtAudioOrCalls() {
        return true;
    }

    /**
     * Interval for piggyback scan with other app's wake ups.
     */
    public static int piggybackScanIntervalSeconds() {
        return 60;
    }

    /**
     * Delay for scheduled piggyback scan task while device idle, in seconds.
     */
    public static int piggybackScanDelayWhileDeviceIdleSeconds() {
        return 5;
    }

    /**
     * Delay for scheduled piggyback scan task while screen off, in seconds.
     */
    public static int piggybackScanDelayWhileScreenOffSeconds() {
        return 15;
    }

    /**
     * Threshold to determine that if the piggyback task is executed while cpu sleeping. If the
     * task is delayed for more than this value, then we assume that cpu is during idle mode
     * recently.
     */
    public static int piggybackAccetableMinDelaySeconds() {
        return 1;
    }

    /**
     * Master switch for piggyback scan.
     */
    public static boolean enablePiggybackScan() {
        return false;
    }

    /**
     * Whether the BLE scanner accepts only legacy advertising or not, will only take effect for
     * O and above
     */
    public static boolean scanOnlyLegacy() {
        return false;
    }

    /**
     * Hold wakelock for scanning to prevent cpu going to sleep while scanning and delay the stop
     * scan schedule.
     */
    public static boolean wakelockForScan() {
        return true;
    }

    /**
     * Use opportunistic scan between regular scans.
     */
    public static boolean enableOpportunisticScan() {
        return false;
    }

    /**
     * Scan mode for contact scan.
     */
    public static int scanMode() {
        return ScanSettings.SCAN_MODE_LOW_LATENCY;
    }

    /**
     * By design, every opportunistic scan will reschedule the low latency scan to 5 minutes
     * later, but it will reschedule too frequently if too many opportunistic scan happen in a in
     * short period. This constant is used for the minimal reschedule interval.
     */
    public static int rescheduleStartScanMinIntervalMs() {
        return 10000; // 10 * 1000
    }

    /**
     * Bucket bounds for risk score latency days.
     */
    public static List<Integer> riskScoreLatencyDaysBuckets() {
        int[] riskScoreLatencyDaysBuckets = new int[]{
                14,
                12,
                10,
                8,
                6,
                4,
                2,
        };
        return Ints.asList(riskScoreLatencyDaysBuckets);
    }

    /**
     * Bucket bounds for risk score duration.
     */
    public static List<Integer> riskScoreDurationBuckets() {
        int[] riskScoreDurationBuckets = new int[]{
                0,
                5,
                10,
                15,
                20,
                25,
                30,
        };
        return Ints.asList(riskScoreDurationBuckets);
    }

    public static List<Integer> riskScoreAttenuationValueBuckets() {
        int[] riskScoreAttenuationValueBuckets = new int[]{
                73,
                63,
                51,
                33,
                27,
                15,
                10,
        };
        return Ints.asList(riskScoreAttenuationValueBuckets);
    }

    /**
     * Minimum estimated time two devices need to be in range of each other before considered a
     * valid exposure.
     */
    public static long minExposureBucketizedDurationSeconds() {
        return 1;
    }

    /**
     * The default minimum attenuation value that must be reached within an exposure duration.
     */
    public static int defaultMinExposureAttenuationValue() {
        return 47; // ~2m distance
    }

    /**
     * Max threshold duration between scans to be considered continuous.
     * <p>
     * Note that this duration is not bucketized by scan interval, and is strictly
     * measured against the duration between two consecutive ble scans.
     */
    public static int maxInterpolationDurationSeconds() {
        // Set to be a little larger than scan interval,
        // which when the phone is in idle mode, can be up
        // to 9 minutes despite scan interval's value, to
        // make sure consecutive scans are not considered
        // individual encounters.
        return 10 * 60;
    }

    /**
     * Maximum duration (in minutes) of ExposureWindow.
     */
    public static int maxExposureWindowDurationMinutes() {
        return 30;
    }

    /**
     * Maximum minutes since last scan in ExposureWindow.
     */
    public static int maxMinutesSinceLastScan() {
        return 5;
    }

    /**
     * Default minutes since last scan in ExposureWindow, used when previous scan time is unknown.
     */
    public static int defaultMinutesSinceLastScan() {
        return 2;
    }

    /**
     * When true, clock drift (embargo) leeway will not be considered when matching near the end
     * of a key's validity period.
     */
    public static boolean ignoreEmbargoPeriodWhenMatchingNearKeyEdges() {
        return false;
    }

    /**
     * Whether to use native code to do matching
     */
    public static boolean matchingWithNative() {
        return true;
    }

    /**
     * Pre processing to filter non-matched IDs, then do the matching.
     */
    public static boolean useMatchingPreFilter() {
        return true;
    }

    /**
     * Whether to allow RECURSIVE report type in TEK.
     */
    public static boolean enableRecursiveTekReportType() {
        return false;
    }

    /**
     * Print more log for matching for debugging
     */
    public static boolean moreLogForMatching() {
        return false;
    }

    /**
     * Whether sightings of the same RPI in a single scan instance should be aggregated and only
     * min attenuation used.
     */
    public static boolean aggregateSightingsFromSingleScan() {
        return true;
    }

    /**
     * Maximum number of keys that will be passed to native for matching in one native matching call
     */
    public static int matchingWithNativeBufferKeySize() {
        return 10000;
    }

    /**
     * If enabled, will start supporting revocation and change of status for report type.
     */
    public static boolean supportRevocationAndChangeStatusReportType() {
        return true;
    }

    /**
     * If enabled, revoked key matches will be stored in the match database (but only when
     * using ExposureWindows).
     */
    public static boolean storeMatchesForRevokedKeys() {
        return true;
    }

    /**
     * Keep existing matching logic, only add a quick filter for non-matched IDs (which should be
     * 99.9% cases).
     */
    public static boolean useMatchingFilter() {
        return true;
    }

    /**
     * Matched RPIs with a TX power higher than this bound will be rejected as outside reasonable
     * range and potentially malicious.
     */
    public static int matchingTxPowerUpperBound() {
        return 20; // dB
    }

    /**
     * Matched RPIs with a TX power lower than this bound will be rejected as outside reasonable
     * range and potentially malicious.
     */
    public static int matchingTxPowerLowerBound() {
        return -50; // dB
    }

    /**
     * Whether the native matching should use the native key file parser
     */
    public static boolean useNativeKeyParser() {
        return true;
    }

    /**
     * If enabled, all exposure results will be written at the end of matching instead of
     * intermittedly throughout the process.
     */
    public static boolean storeExposureResultsInTransaction() {
        return true;
    }

    /**
     * The number of times Report Type can be changed.
     */
    public static int allowedReportTypeTransitions() {
        return 1;
    }

    /**
     * Whether to enable the compact storage format for contact record
     * data store (sightings storage). Instead storing all information for
     * individual received BLE packet, compact format aggregates some fields
     * while keeping other required packet-specific information unaggregated.
     */
    public static boolean contactRecordStoreCompactFormatEnabled() {
        return false;
    }
}
import android.annotation.TargetApi;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanFilter.Builder;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.ContentResolver;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build.VERSION_CODES;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.WorkSource;
import android.provider.Settings;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.arch.core.util.Function;
import androidx.core.util.Supplier;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.samples.PlatformVersion;
import com.google.samples.exposurenotification.Log;
import com.google.samples.exposurenotification.ble.interfaces.BleDatabaseWriter;
import com.google.samples.exposurenotification.ble.interfaces.BleExecutors;
import com.google.samples.exposurenotification.ble.interfaces.BleExecutors.ScheduledTask;
import com.google.samples.exposurenotification.ble.interfaces.BleScanner;
import com.google.samples.exposurenotification.ble.utils.BluetoothUtils;
import com.google.samples.exposurenotification.ble.utils.Constants;
import com.google.samples.exposurenotification.ble.utils.Constants.StatusCode;
import com.google.samples.exposurenotification.features.ContactTracingFeature;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import static com.google.samples.exposurenotification.ble.utils.Constants.StatusCode.ERROR_BLUETOOTH_UNKNOWN;
import static com.google.samples.exposurenotification.ble.utils.Constants.StatusCode.OK;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
/*
 * Copyright 2020 Google LLC
 *
 * 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 com.google.samples.exposurenotification.ble.scanner;






/**
 * Implementation of {@link BleScanner}, used to scan BLE advertisement for contact tracing.
 */
@TargetApi(VERSION_CODES.LOLLIPOP)
@SuppressWarnings("MissingPermission")
public class BleScannerImpl implements BleScanner {
    private static final String WAKE_LOCK_TAG = "nearby:ExposureNotificationScanner";
    private static final int MAX_RSSI_VALUE = 126;
    private static final int MIN_RSSI_VALUE = -127;
    private static final ImmutableList<Integer> CHECKING_PROFILES =
            ImmutableList.of(BluetoothProfile.A2DP, BluetoothProfile.HEADSET);
    private static final int SCAN_CHANNEL_NUMBER = 3;

    private final Context context;
    private final BleExecutors bleExecutors;
    private final Supplier<Long> currentTimeMsSupplier;
    private final BleSightingCallback bleSightingCallback;
    private final AudioManager audioManager;
    private final PowerManager powerManager;
    @VisibleForTesting
    final ScanStatusManager scanStatusManager;
    private final ScanTimePersistence scanTimePersistence;

    @Nullable
    @VisibleForTesting
    ScheduledTask scheduledTask;
    @Nullable
    private ScheduledTask piggybackScheduledTask;
    private long lastScheduleTimeMsForStartScan;
    /**
     * Master WakeLock that keeps the cpu awake while scanning.
     */
    private final WakeLock wakeLock;

    @Nullable
    private WorkSource workSource;

    private Function<Integer, Integer> randomFunction =
            integer -> new SecureRandom().nextInt(integer);
    private Predicate<List<Integer>> profileInUsingPredicator =
            BluetoothUtils::isBluetoothProfileInUsing;

    private int defaultScanWindowMs;
    private int defaultScanIntervalMs;

    // This object will be fully initialized before rescheduleScanForOpportunisticScan() is called
    // incompatible types in assignment.
    public BleScannerImpl(
            Context context,
            BleDatabaseWriter databaseWriter,
            BleExecutors bleExecutors,
            Supplier<Long> currentTimeMsSupplier,
            Runnable startScanFailCallback) {
        this.context = context;
        this.bleExecutors = bleExecutors;
        this.currentTimeMsSupplier = currentTimeMsSupplier;
        this.scanTimePersistence = new ScanTimePersistence();
        this.scanStatusManager = new ScanStatusManager();
        this.bleSightingCallback =
                new BleSightingCallback(
                        databaseWriter,
                        () -> rescheduleScanForOpportunisticScan(/* ignoreFrequency= */ false),
                        scanStatusManager,
                        scanTimePersistence,
                        startScanFailCallback);
        this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        this.powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        // FIXME: Aquire a WakeLock
        this.wakeLock = null;
        this.defaultScanIntervalMs = getCurrentSettingsGlobalScanInterval();
        this.defaultScanWindowMs = getCurrentSettingsGlobalScanWindow();
    }

    /**
     * Verifies the device supports BLE scanning.
     * FIXME: Implement checks to ensure the device supports BLE Advertising
     */
    @Override
    public StatusCode supportScanning() {
        return OK;
    }

    @Override
    public boolean isScanning() {
        return !scanStatusManager.isStopped();
    }

    @VisibleForTesting
    @Nullable
    WorkSource getWorkSource() {
        return workSource;
    }

    @Override
    public StatusCode startScanning() {
        StatusCode statusCode = supportScanning();
        if (statusCode != OK) {
            return statusCode;
        }

        if (isScanning()) {
            return OK;
        }

        setActiveClientAsWorkSource();
        return startScanningInternal(/* isPiggyBackScan= */ false) ? OK : ERROR_BLUETOOTH_UNKNOWN;
    }

    private void setActiveClientAsWorkSource() {
        if (workSource != null) {
            // FIXME: Remove old WorkSource if needed
            workSource = null;
        }

        // FIXME: Get record of calling package
        String callingPackage = "com.google.android.apps.exposurenotification";
        // FIXME: Create a meaningful WorkSource
        workSource = new WorkSource();
        if (workSource != null) {
            wakeLock.setWorkSource(workSource);
            Log.log.atInfo().log("BleScanner set work source to %s", callingPackage);
        }
    }

    private synchronized boolean startScanningInternal(boolean isPiggyBackScan) {
        if (scanStatusManager.isScanning()) {
            Log.log.atInfo().log("Already scanning!");
            return false;
        }
        cancelAllScheduleTasks();

        maybeUpdateBleScannerInstance();

        scanStatusManager.setScanning();
        int scanMode = (int) ContactTracingFeature.scanMode();