Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f40e61b9 authored by Jayden Kim's avatar Jayden Kim
Browse files

Extend scan timeout logic to downgrade regular scan

- Apply scan timeout for the filtered regular scan
- Bug fix for scan timeout logic
- Add unit test cases to verify scan control logic

Tag: #feature
Bug: 233614676
Test: atest BluetoothInstrumentationTests:ScanManagerTest
Ignore-AOSP-First: T-QPR1 change

Change-Id: I5a8faaa3f12661750ee580b20a58a07ec11b4efc
Merged-In: I5a8faaa3f12661750ee580b20a58a07ec11b4efc
parent 06579c2d
Loading
Loading
Loading
Loading
+18 −21
Original line number Diff line number Diff line
@@ -5176,33 +5176,30 @@ public class AdapterService extends Service {
        }
    }

    public static int getScanQuotaCount() {
        if (sAdapterService == null) {
            return DeviceConfigListener.DEFAULT_SCAN_QUOTA_COUNT;
        }

        synchronized (sAdapterService.mDeviceConfigLock) {
            return sAdapterService.mScanQuotaCount;
        }
    /**
     * Returns scan quota count.
     */
    public int getScanQuotaCount() {
        synchronized (mDeviceConfigLock) {
            return mScanQuotaCount;
        }

    public static long getScanQuotaWindowMillis() {
        if (sAdapterService == null) {
            return DeviceConfigListener.DEFAULT_SCAN_QUOTA_WINDOW_MILLIS;
    }

        synchronized (sAdapterService.mDeviceConfigLock) {
            return sAdapterService.mScanQuotaWindowMillis;
        }
    /**
     * Returns scan quota window in millis.
     */
    public long getScanQuotaWindowMillis() {
        synchronized (mDeviceConfigLock) {
            return mScanQuotaWindowMillis;
        }

    public static long getScanTimeoutMillis() {
        if (sAdapterService == null) {
            return DeviceConfigListener.DEFAULT_SCAN_TIMEOUT_MILLIS;
    }

        synchronized (sAdapterService.mDeviceConfigLock) {
            return sAdapterService.mScanTimeoutMillis;
    /**
     * Returns scan timeout in millis.
     */
    public long getScanTimeoutMillis() {
        synchronized (mDeviceConfigLock) {
            return mScanTimeoutMillis;
        }
    }

+75 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 The Android Open Source Project
 *
 * 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.android.bluetooth.btservice;

import android.bluetooth.BluetoothAdapter;
import android.util.Log;

import com.android.bluetooth.Utils;
import com.android.internal.annotations.VisibleForTesting;

/**
 * A proxy class that facilitates testing of the ScanManager.
 *
 * This is necessary due to the "final" attribute of the BluetoothAdapter class. In order to
 * test the correct functioning of the ScanManager class, the final class must be put
 * into a container that can be mocked correctly.
 */
public class BluetoothAdapterProxy {
    private static final String TAG = BluetoothAdapterProxy.class.getSimpleName();
    private static BluetoothAdapterProxy sInstance;
    private static final Object INSTANCE_LOCK = new Object();

    private BluetoothAdapterProxy() {}

    /**
     * Get the singleton instance of proxy.
     *
     * @return the singleton instance, guaranteed not null
     */
    public static BluetoothAdapterProxy getInstance() {
        synchronized (INSTANCE_LOCK) {
            if (sInstance == null) {
                sInstance = new BluetoothAdapterProxy();
            }
            return sInstance;
        }
    }

    /**
     * Proxy function that calls {@link BluetoothAdapter#isOffloadedFilteringSupported()}.
     *
     * @return whether the offloaded scan filtering is supported
     */
    public boolean isOffloadedScanFilteringSupported() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        return adapter.isOffloadedFilteringSupported();
    }

    /**
     * Allow unit tests to substitute BluetoothAdapterProxy with a test instance
     *
     * @param proxy a test instance of the BluetoothAdapterProxy
     */
    @VisibleForTesting
    public static void setInstanceForTesting(BluetoothAdapterProxy proxy) {
        Utils.enforceInstrumentationTestMode();
        synchronized (INSTANCE_LOCK) {
            Log.d(TAG, "setInstanceForTesting(), set to " + proxy);
            sInstance = proxy;
        }
    }
}
+0 −23
Original line number Diff line number Diff line
@@ -105,29 +105,6 @@ import java.util.Objects;
            this.filterString = "";
        }
    }

    static int getNumScanDurationsKept() {
        return AdapterService.getScanQuotaCount();
    }

    // This constant defines the time window an app can scan multiple times.
    // Any single app can scan up to |NUM_SCAN_DURATIONS_KEPT| times during
    // this window. Once they reach this limit, they must wait until their
    // earliest recorded scan exits this window.
    static long getExcessiveScanningPeriodMillis() {
        return AdapterService.getScanQuotaWindowMillis();
    }

    // Maximum msec before scan gets downgraded to opportunistic
    static long getScanTimeoutMillis() {
        return AdapterService.getScanTimeoutMillis();
    }

    // Scan mode upgrade duration after scanStart()
    static long getScanUpgradeDurationMillis() {
        return AdapterService.getAdapterService().getScanUpgradeDurationMillis();
    }

    public String appName;
    public WorkSource mWorkSource; // Used for BatteryStatsManager
    public final WorkSourceUtil mWorkSourceUtil; // Used for BluetoothStatsLog
+13 −1
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AbstractionLayer;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.BluetoothAdapterProxy;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.util.NumberUtils;
import com.android.internal.annotations.VisibleForTesting;
@@ -262,6 +263,7 @@ public class GattService extends ProfileService {
    private final HashMap<String, AtomicBoolean> mPermits = new HashMap<>();

    private AdapterService mAdapterService;
    private BluetoothAdapterProxy mBluetoothAdapterProxy;
    private AdvertiseManager mAdvertiseManager;
    private PeriodicScanManager mPeriodicScanManager;
    private ScanManager mScanManager;
@@ -319,12 +321,13 @@ public class GattService extends ProfileService {

        initializeNative();
        mAdapterService = AdapterService.getAdapterService();
        mBluetoothAdapterProxy = BluetoothAdapterProxy.getInstance();
        mCompanionManager = getSystemService(CompanionDeviceManager.class);
        mAppOps = getSystemService(AppOpsManager.class);
        mAdvertiseManager = new AdvertiseManager(this, mAdapterService);
        mAdvertiseManager.start();

        mScanManager = new ScanManager(this, mAdapterService);
        mScanManager = new ScanManager(this, mAdapterService, mBluetoothAdapterProxy);
        mScanManager.start();

        mPeriodicScanManager = new PeriodicScanManager(mAdapterService);
@@ -426,6 +429,15 @@ public class GattService extends ProfileService {
        return sGattService;
    }

    @VisibleForTesting
    ScanManager getScanManager() {
        if (mScanManager == null) {
            Log.w(TAG, "getScanManager(): scan manager is null");
            return null;
        }
        return mScanManager;
    }

    private static synchronized void setGattService(GattService instance) {
        if (DBG) {
            Log.d(TAG, "setGattService(): set to: " + instance);
+80 −32
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
@@ -45,6 +44,8 @@ import android.view.Display;

import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.BluetoothAdapterProxy;
import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayDeque;
import java.util.Collections;
@@ -86,17 +87,18 @@ public class ScanManager {
    static final int SCAN_RESULT_TYPE_FULL = 2;
    static final int SCAN_RESULT_TYPE_BOTH = 3;

    // Internal messages for handling BLE scan operations.
    private static final int MSG_START_BLE_SCAN = 0;
    private static final int MSG_STOP_BLE_SCAN = 1;
    private static final int MSG_FLUSH_BATCH_RESULTS = 2;
    private static final int MSG_SCAN_TIMEOUT = 3;
    private static final int MSG_SUSPEND_SCANS = 4;
    private static final int MSG_RESUME_SCANS = 5;
    private static final int MSG_IMPORTANCE_CHANGE = 6;
    private static final int MSG_SCREEN_ON = 7;
    private static final int MSG_SCREEN_OFF = 8;
    private static final int MSG_REVERT_SCAN_MODE_UPGRADE = 9;
    // Messages for handling BLE scan operations.
    @VisibleForTesting
    static final int MSG_START_BLE_SCAN = 0;
    static final int MSG_STOP_BLE_SCAN = 1;
    static final int MSG_FLUSH_BATCH_RESULTS = 2;
    static final int MSG_SCAN_TIMEOUT = 3;
    static final int MSG_SUSPEND_SCANS = 4;
    static final int MSG_RESUME_SCANS = 5;
    static final int MSG_IMPORTANCE_CHANGE = 6;
    static final int MSG_SCREEN_ON = 7;
    static final int MSG_SCREEN_OFF = 8;
    static final int MSG_REVERT_SCAN_MODE_UPGRADE = 9;
    private static final String ACTION_REFRESH_BATCHED_SCAN =
            "com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN";

@@ -115,6 +117,7 @@ public class ScanManager {
    private boolean mBatchAlarmReceiverRegistered;
    private ScanNative mScanNative;
    private volatile ClientHandler mHandler;
    private BluetoothAdapterProxy mBluetoothAdapterProxy;

    private Set<ScanClient> mRegularScanClients;
    private Set<ScanClient> mBatchClients;
@@ -134,7 +137,8 @@ public class ScanManager {
    private final SparseBooleanArray mIsUidForegroundMap = new SparseBooleanArray();
    private boolean mScreenOn = false;

    private class UidImportance {
    @VisibleForTesting
    static class UidImportance {
        public int uid;
        public int importance;

@@ -144,7 +148,8 @@ public class ScanManager {
        }
    }

    ScanManager(GattService service, AdapterService adapterService) {
    ScanManager(GattService service, AdapterService adapterService,
            BluetoothAdapterProxy bluetoothAdapterProxy) {
        mRegularScanClients =
                Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>());
        mBatchClients = Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>());
@@ -157,6 +162,7 @@ public class ScanManager {
        mActivityManager = mService.getSystemService(ActivityManager.class);
        mLocationManager = mService.getSystemService(LocationManager.class);
        mAdapterService = adapterService;
        mBluetoothAdapterProxy = bluetoothAdapterProxy;

        mPriorityMap.put(ScanSettings.SCAN_MODE_OPPORTUNISTIC, 0);
        mPriorityMap.put(ScanSettings.SCAN_MODE_SCREEN_OFF, 1);
@@ -175,6 +181,7 @@ public class ScanManager {
        if (mDm != null) {
            mDm.registerDisplayListener(mDisplayListener, null);
        }
        mScreenOn = isScreenOn();
        if (mActivityManager != null) {
            mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
                    FOREGROUND_IMPORTANCE_CUTOFF);
@@ -234,6 +241,13 @@ public class ScanManager {
        return mRegularScanClients;
    }

    /**
     * Returns the suspended scan queue.
     */
    Set<ScanClient> getSuspendedScanQueue() {
        return mSuspendedScanClients;
    }

    /**
     * Returns batch scan queue.
     */
@@ -298,8 +312,11 @@ public class ScanManager {
    }

    private boolean isFilteringSupported() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        return adapter.isOffloadedFilteringSupported();
        if (mBluetoothAdapterProxy == null) {
            Log.e(TAG, "mBluetoothAdapterProxy is null");
            return false;
        }
        return mBluetoothAdapterProxy.isOffloadedScanFilteringSupported();
    }

    // Handler class that handles BLE scan operations.
@@ -363,7 +380,7 @@ public class ScanManager {
                return;
            }

            if (requiresScreenOn(client) && !isScreenOn()) {
            if (requiresScreenOn(client) && !mScreenOn) {
                Log.w(TAG, "Cannot start unfiltered scan in screen-off. This scan will be resumed "
                        + "later: " + client.scannerId);
                mSuspendedScanClients.add(client);
@@ -400,6 +417,11 @@ public class ScanManager {
                        msg.obj = client;
                        // Only one timeout message should exist at any time
                        sendMessageDelayed(msg, mAdapterService.getScanTimeoutMillis());
                        if (DBG) {
                            Log.d(TAG,
                                    "apply scan timeout (" + mAdapterService.getScanTimeoutMillis()
                                            + ")" + "to scannerId " + client.scannerId);
                        }
                    }
                }
            }
@@ -429,13 +451,10 @@ public class ScanManager {
                mSuspendedScanClients.remove(client);
            }
            removeMessages(MSG_REVERT_SCAN_MODE_UPGRADE, client);
            removeMessages(MSG_SCAN_TIMEOUT, client);
            if (mRegularScanClients.contains(client)) {
                mScanNative.stopRegularScan(client);

                if (mScanNative.numRegularScanClients() == 0) {
                    removeMessages(MSG_SCAN_TIMEOUT);
                }

                if (!mScanNative.isOpportunisticScanClient(client)) {
                    mScanNative.configureRegularScanParams();
                }
@@ -493,7 +512,7 @@ public class ScanManager {
        @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
        void handleSuspendScans() {
            for (ScanClient client : mRegularScanClients) {
                if ((requiresScreenOn(client) && !isScreenOn())
                if ((requiresScreenOn(client) && !mScreenOn)
                        || (requiresLocationOn(client) && !mLocationManager.isLocationEnabled())) {
                    /*Suspend unfiltered scans*/
                    if (client.stats != null) {
@@ -524,6 +543,9 @@ public class ScanManager {
        }

        private boolean updateScanModeScreenOff(ScanClient client) {
            if (mScanNative.isTimeoutScanClient(client)) {
                return false;
            }
            if (!isAppForeground(client) && !mScanNative.isOpportunisticScanClient(client)) {
                return client.updateScanMode(ScanSettings.SCAN_MODE_SCREEN_OFF);
            }
@@ -555,7 +577,7 @@ public class ScanManager {
            if (upgradeScanModeBeforeStart(client)) {
                return true;
            }
            if (isScreenOn()) {
            if (mScreenOn) {
                return updateScanModeScreenOn(client);
            } else {
                return updateScanModeScreenOff(client);
@@ -613,6 +635,10 @@ public class ScanManager {
        }

        private boolean updateScanModeScreenOn(ScanClient client) {
            if (mScanNative.isTimeoutScanClient(client)) {
                return false;
            }

            int newScanMode =  (isAppForeground(client)
                    || mScanNative.isOpportunisticScanClient(client))
                    ? client.scanModeApp : SCAN_MODE_APP_IN_BACKGROUND;
@@ -633,7 +659,7 @@ public class ScanManager {

        void handleResumeScans() {
            for (ScanClient client : mSuspendedScanClients) {
                if ((!requiresScreenOn(client) || isScreenOn())
                if ((!requiresScreenOn(client) || mScreenOn)
                        && (!requiresLocationOn(client) || mLocationManager.isLocationEnabled())) {
                    if (client.stats != null) {
                        client.stats.recordScanResume(client.scannerId);
@@ -871,14 +897,19 @@ public class ScanManager {
        }

        private boolean isExemptFromScanDowngrade(ScanClient client) {
            return isOpportunisticScanClient(client) || isFirstMatchScanClient(client)
                    || !shouldUseAllPassFilter(client);
            return isOpportunisticScanClient(client) || isFirstMatchScanClient(client);
        }

        private boolean isOpportunisticScanClient(ScanClient client) {
            return client.settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
        }

        private boolean isTimeoutScanClient(ScanClient client) {
            return (client.stats != null)
                    && (client.stats.getScanFromScannerId(client.scannerId) != null)
                    && (client.stats.getScanFromScannerId(client.scannerId).isTimeout);
        }

        private boolean isFirstMatchScanClient(ScanClient client) {
            return (client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
                    != 0;
@@ -1046,11 +1077,23 @@ public class ScanManager {

        void regularScanTimeout(ScanClient client) {
            if (!isExemptFromScanDowngrade(client) && client.stats.isScanningTooLong()) {
                if (DBG) {
                    Log.d(TAG, "regularScanTimeout - client scan time was too long");
                }
                if (client.filters == null || client.filters.isEmpty()) {
                    Log.w(TAG,
                        "Moving scan client to opportunistic (scannerId " + client.scannerId + ")");
                            "Moving unfiltered scan client to opportunistic scan (scannerId "
                                    + client.scannerId + ")");
                    setOpportunisticScanClient(client);
                    removeScanFilters(client.scannerId);
                    client.stats.setScanTimeout(client.scannerId);
                } else {
                    Log.w(TAG,
                            "Moving filtered scan client to downgraded scan (scannerId "
                                    + client.scannerId + ")");
                    client.updateScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
                    client.stats.setScanTimeout(client.scannerId);
                }
            }

            // The scan should continue for background scans
@@ -1527,6 +1570,11 @@ public class ScanManager {
        private native void gattClientReadScanReportsNative(int clientIf, int scanType);
    }

    @VisibleForTesting
    ClientHandler getClientHandler() {
        return mHandler;
    }

    private boolean isScreenOn() {
        Display[] displays = mDm.getDisplays();

@@ -1605,7 +1653,7 @@ public class ScanManager {
        }

        for (ScanClient client : mRegularScanClients) {
            if (client.appUid != uid) {
            if (client.appUid != uid || mScanNative.isTimeoutScanClient(client)) {
                continue;
            }
            if (isForeground) {
Loading