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

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

Adds LE Scan Downgrade API for concurrent operations

- Avoids maximum LE scan duty cycle during the concurrent operations
- Adds unit test cases to simulate the concurrent operations

Tag: #feature
Bug: 237351651
Test: atest BluetoothInstrumentationTests:ScanManagerTest
Test: manual test for BT connection with scanning
Change-Id: I34ef6faf83c59ecee547cfc0ceef71571b077c3c
parent b5de90c4
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -5369,6 +5369,9 @@ public class AdapterService extends Service {
    private int mScanUpgradeDurationMillis =
            DeviceConfigListener.DEFAULT_SCAN_UPGRADE_DURATION_MILLIS;
    @GuardedBy("mDeviceConfigLock")
    private int mScanDowngradeDurationMillis =
            DeviceConfigListener.DEFAULT_SCAN_DOWNGRADE_DURATION_BT_CONNECTING_MILLIS;
    @GuardedBy("mDeviceConfigLock")
    private int mScreenOffLowPowerWindowMillis =
            ScanManager.SCAN_MODE_SCREEN_OFF_LOW_POWER_WINDOW_MS;
    @GuardedBy("mDeviceConfigLock")
@@ -5435,6 +5438,15 @@ public class AdapterService extends Service {
        }
    }

    /**
     * Returns scan downgrade duration in millis.
     */
    public long getScanDowngradeDurationMillis() {
        synchronized (mDeviceConfigLock) {
            return mScanDowngradeDurationMillis;
        }
    }

    /**
     * Returns SCREEN_OFF_BALANCED scan window in millis.
     */
@@ -5488,6 +5500,8 @@ public class AdapterService extends Service {
                "scan_timeout_millis";
        private static final String SCAN_UPGRADE_DURATION_MILLIS =
                "scan_upgrade_duration_millis";
        private static final String SCAN_DOWNGRADE_DURATION_MILLIS =
                "scan_downgrade_duration_millis";
        private static final String SCREEN_OFF_LOW_POWER_WINDOW_MILLIS =
                "screen_off_low_power_window_millis";
        private static final String SCREEN_OFF_LOW_POWER_INTERVAL_MILLIS =
@@ -5507,6 +5521,8 @@ public class AdapterService extends Service {
        private static final long DEFAULT_SCAN_QUOTA_WINDOW_MILLIS = 30 * SECOND_IN_MILLIS;
        private static final long DEFAULT_SCAN_TIMEOUT_MILLIS = 30 * MINUTE_IN_MILLIS;
        private static final int DEFAULT_SCAN_UPGRADE_DURATION_MILLIS = (int) SECOND_IN_MILLIS * 6;
        private static final int DEFAULT_SCAN_DOWNGRADE_DURATION_BT_CONNECTING_MILLIS =
                (int) SECOND_IN_MILLIS * 6;

        @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG)
        public void start() {
@@ -5535,6 +5551,8 @@ public class AdapterService extends Service {
                        DEFAULT_SCAN_TIMEOUT_MILLIS);
                mScanUpgradeDurationMillis = properties.getInt(SCAN_UPGRADE_DURATION_MILLIS,
                        DEFAULT_SCAN_UPGRADE_DURATION_MILLIS);
                mScanDowngradeDurationMillis = properties.getInt(SCAN_DOWNGRADE_DURATION_MILLIS,
                        DEFAULT_SCAN_DOWNGRADE_DURATION_BT_CONNECTING_MILLIS);
                mScreenOffLowPowerWindowMillis = properties.getInt(
                        SCREEN_OFF_LOW_POWER_WINDOW_MILLIS,
                        ScanManager.SCAN_MODE_SCREEN_OFF_LOW_POWER_WINDOW_MS);
+24 −3
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ import java.util.Objects;
        public long timestamp;
        public boolean isOpportunisticScan;
        public boolean isTimeout;
        public boolean isDowngraded;
        public boolean isBackgroundScan;
        public boolean isFilterScan;
        public boolean isCallbackScan;
@@ -91,6 +92,7 @@ import java.util.Objects;
            this.timestamp = timestamp;
            this.isOpportunisticScan = false;
            this.isTimeout = false;
            this.isDowngraded = false;
            this.isBackgroundScan = false;
            this.isFilterScan = isFilterScan;
            this.isCallbackScan = isCallbackScan;
@@ -172,11 +174,19 @@ import java.util.Objects;
    }

    synchronized boolean isScanTimeout(int scannerId) {
        LastScan onGoingScan = getScanFromScannerId(scannerId);
        if (onGoingScan == null) {
        LastScan scan = getScanFromScannerId(scannerId);
        if (scan == null) {
            return false;
        }
        return onGoingScan.isTimeout;
        return scan.isTimeout;
    }

    synchronized boolean isScanDowngraded(int scannerId) {
        LastScan scan = getScanFromScannerId(scannerId);
        if (scan == null) {
            return false;
        }
        return scan.isDowngraded;
    }

    synchronized void recordScanStart(ScanSettings settings, List<ScanFilter> filters,
@@ -342,6 +352,17 @@ import java.util.Objects;
        }
    }

    synchronized void setScanDowngrade(int scannerId, boolean isDowngrade) {
        if (!isScanning()) {
            return;
        }

        LastScan scan = getScanFromScannerId(scannerId);
        if (scan != null) {
            scan.isDowngraded = isDowngrade;
        }
    }

    synchronized boolean isScanningTooFrequently() {
        if (mLastScans.size() < mAdapterService.getScanQuotaCount()) {
            return false;
+245 −7
Original line number Diff line number Diff line
@@ -20,6 +20,13 @@ import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothA2dpSink;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
@@ -99,6 +106,8 @@ public class ScanManager {
    static final int MSG_SCREEN_ON = 7;
    static final int MSG_SCREEN_OFF = 8;
    static final int MSG_REVERT_SCAN_MODE_UPGRADE = 9;
    static final int MSG_START_CONNECTING = 10;
    static final int MSG_STOP_CONNECTING = 11;
    private static final String ACTION_REFRESH_BATCHED_SCAN =
            "com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN";

@@ -136,6 +145,8 @@ public class ScanManager {
    private static final int SCAN_MODE_APP_IN_BACKGROUND = ScanSettings.SCAN_MODE_LOW_POWER;
    private final SparseBooleanArray mIsUidForegroundMap = new SparseBooleanArray();
    private boolean mScreenOn = false;
    private boolean mIsConnecting;
    private int mProfilesConnecting, mProfilesConnected, mProfilesDisconnecting;

    @VisibleForTesting
    static class UidImportance {
@@ -163,6 +174,7 @@ public class ScanManager {
        mLocationManager = mService.getSystemService(LocationManager.class);
        mAdapterService = adapterService;
        mBluetoothAdapterProxy = bluetoothAdapterProxy;
        mIsConnecting = false;

        mPriorityMap.put(ScanSettings.SCAN_MODE_OPPORTUNISTIC, 0);
        mPriorityMap.put(ScanSettings.SCAN_MODE_SCREEN_OFF, 1);
@@ -188,6 +200,8 @@ public class ScanManager {
        }
        IntentFilter locationIntentFilter = new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
        mService.registerReceiver(mLocationReceiver, locationIntentFilter);
        mService.registerReceiver(mBluetoothConnectionReceiver,
                getBluetoothConnectionIntentFilter());
    }

    void cleanup() {
@@ -223,6 +237,13 @@ public class ScanManager {
        } catch (IllegalArgumentException e) {
            Log.w(TAG, "exception when invoking unregisterReceiver(mLocationReceiver)", e);
        }

        try {
            mService.unregisterReceiver(mBluetoothConnectionReceiver);
        } catch (IllegalArgumentException e) {
            Log.w(TAG, "exception when invoking unregisterReceiver(mBluetoothConnectionReceiver)",
                    e);
        }
    }

    void registerScanner(UUID uuid) {
@@ -299,6 +320,25 @@ public class ScanManager {
        // TODO: add a callback for scan failure.
    }

    void onConnectingState(boolean isConnecting) {
        if (isConnecting) {
            sendMessage(MSG_START_CONNECTING, null);
        } else {
            sendMessage(MSG_STOP_CONNECTING, null);
        }
    }

    private IntentFilter getBluetoothConnectionIntentFilter() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
        return filter;
    }

    private void sendMessage(int what, ScanClient client) {
        final ClientHandler handler = mHandler;
        if (handler == null) {
@@ -359,6 +399,12 @@ public class ScanManager {
                case MSG_IMPORTANCE_CHANGE:
                    handleImportanceChange((UidImportance) msg.obj);
                    break;
                case MSG_START_CONNECTING:
                    handleConnectingState();
                    break;
                case MSG_STOP_CONNECTING:
                    handleClearConnectingState();
                    break;
                default:
                    // Shouldn't happen.
                    Log.e(TAG, "received an unkown message : " + msg.what);
@@ -407,12 +453,13 @@ public class ScanManager {
                mScanNative.startBatchScan(client);
            } else {
                updateScanModeBeforeStart(client);
                updateScanModeConcurrency(client);
                mRegularScanClients.add(client);
                mScanNative.startRegularScan(client);
                if (!mScanNative.isOpportunisticScanClient(client)) {
                    mScanNative.configureRegularScanParams();

                    if (!mScanNative.isExemptFromScanDowngrade(client)) {
                    if (!mScanNative.isExemptFromScanTimeout(client)) {
                        Message msg = obtainMessage(MSG_SCAN_TIMEOUT);
                        msg.obj = client;
                        // Only one timeout message should exist at any time
@@ -509,6 +556,55 @@ public class ScanManager {
            updateRegularScanClientsScreenOff();
        }

        void handleConnectingState() {
            if (mAdapterService.getScanDowngradeDurationMillis() == 0) {
                return;
            }
            boolean updatedScanParams = false;
            mIsConnecting = true;
            if (DBG) {
                Log.d(TAG, "handleConnectingState()");
            }
            for (ScanClient client : mRegularScanClients) {
                if (downgradeScanModeFromMaxDuty(client)) {
                    updatedScanParams = true;
                    if (DBG) {
                        Log.d(TAG, "scanMode is downgraded by connecting for " + client);
                    }
                }
            }
            if (updatedScanParams) {
                mScanNative.configureRegularScanParams();
            }
            removeMessages(MSG_STOP_CONNECTING);
            Message msg = obtainMessage(MSG_STOP_CONNECTING);
            sendMessageDelayed(msg, mAdapterService.getScanDowngradeDurationMillis());
        }

        void handleClearConnectingState() {
            if (!mIsConnecting) {
                Log.e(TAG, "handleClearConnectingState() - not connecting state");
                return;
            }
            if (DBG) {
                Log.d(TAG, "handleClearConnectingState()");
            }
            boolean updatedScanParams = false;
            for (ScanClient client : mRegularScanClients) {
                if (revertDowngradeScanModeFromMaxDuty(client)) {
                    updatedScanParams = true;
                    if (DBG) {
                        Log.d(TAG, "downgraded scanMode is reverted for " + client);
                    }
                }
            }
            if (updatedScanParams) {
                mScanNative.configureRegularScanParams();
            }
            removeMessages(MSG_STOP_CONNECTING);
            mIsConnecting = false;
        }

        @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
        void handleSuspendScans() {
            for (ScanClient client : mRegularScanClients) {
@@ -543,7 +639,7 @@ public class ScanManager {
        }

        private boolean updateScanModeScreenOff(ScanClient client) {
            if (mScanNative.isTimeoutScanClient(client)) {
            if (mScanNative.isForceDowngradedScanClient(client)) {
                return false;
            }
            if (!isAppForeground(client) && !mScanNative.isOpportunisticScanClient(client)) {
@@ -558,8 +654,9 @@ public class ScanManager {
                case ScanSettings.SCAN_MODE_BALANCED:
                case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
                    return client.updateScanMode(ScanSettings.SCAN_MODE_SCREEN_OFF_BALANCED);
                case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
                case ScanSettings.SCAN_MODE_LOW_LATENCY:
                    return client.updateScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
                case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
                default:
                    return false;
            }
@@ -584,6 +681,13 @@ public class ScanManager {
            }
        }

        private boolean updateScanModeConcurrency(ScanClient client) {
            if (mIsConnecting) {
                return downgradeScanModeFromMaxDuty(client);
            }
            return false;
        }

        private boolean upgradeScanModeBeforeStart(ScanClient client) {
            if (client.started || mAdapterService.getScanUpgradeDurationMillis() == 0) {
                return false;
@@ -635,7 +739,7 @@ public class ScanManager {
        }

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

@@ -645,6 +749,36 @@ public class ScanManager {
            return client.updateScanMode(newScanMode);
        }

        private boolean downgradeScanModeFromMaxDuty(ScanClient client) {
            if (mAdapterService.getScanDowngradeDurationMillis() == 0) {
                return false;
            }
            if (ScanSettings.SCAN_MODE_LOW_LATENCY == client.settings.getScanMode()) {
                client.updateScanMode(ScanSettings.SCAN_MODE_BALANCED);
                client.stats.setScanDowngrade(client.scannerId, true);
                if (DBG) {
                    Log.d(TAG, "downgradeScanModeFromMaxDuty() for " + client);
                }
                return true;
            }
            return false;
        }

        private boolean revertDowngradeScanModeFromMaxDuty(ScanClient client) {
            if (!mScanNative.isDowngradedScanClient(client)) {
                return false;
            }
            client.stats.setScanDowngrade(client.scannerId, false);
            if (DBG) {
                Log.d(TAG, "revertDowngradeScanModeFromMaxDuty() for " + client);
            }
            if (mScreenOn) {
                return updateScanModeScreenOn(client);
            } else {
                return updateScanModeScreenOff(client);
            }
        }

        void handleScreenOn() {
            if (mScreenOn) {
                return;
@@ -896,7 +1030,7 @@ public class ScanManager {
            }
        }

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

@@ -908,6 +1042,14 @@ public class ScanManager {
            return (client.stats != null) && client.stats.isScanTimeout(client.scannerId);
        }

        private boolean isDowngradedScanClient(ScanClient client) {
            return (client.stats != null) && client.stats.isScanDowngraded(client.scannerId);
        }

        private boolean isForceDowngradedScanClient(ScanClient client) {
            return isTimeoutScanClient(client) || isDowngradedScanClient(client);
        }

        private boolean isFirstMatchScanClient(ScanClient client) {
            return (client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
                    != 0;
@@ -1074,7 +1216,7 @@ public class ScanManager {
        }

        void regularScanTimeout(ScanClient client) {
            if (!isExemptFromScanDowngrade(client) && client.stats.isScanningTooLong()) {
            if (!isExemptFromScanTimeout(client) && client.stats.isScanningTooLong()) {
                if (DBG) {
                    Log.d(TAG, "regularScanTimeout - client scan time was too long");
                }
@@ -1636,6 +1778,102 @@ public class ScanManager {
                }
            };

    private BroadcastReceiver mBluetoothConnectionReceiver =
            new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    String action = intent.getAction();
                    if (action == null) {
                        Log.w(TAG, "Received intent with null action");
                        return;
                    }
                    switch (action) {
                        case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
                        case BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED:
                        case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
                        case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
                        case BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED:
                        case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
                            int prevState = intent.getIntExtra(
                                    BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
                            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
                            if (DBG) {
                                Log.d(TAG, "PROFILE_CONNECTION_STATE_CHANGE: action="
                                        + action + ", prevState=" + prevState + ", state=" + state);
                            }
                            boolean updatedConnectingState =
                                    updateCountersAndCheckForConnectingState(state, prevState);
                            if (DBG) {
                                Log.d(TAG, "updatedConnectingState = " + updatedConnectingState);
                            }
                            if (updatedConnectingState) {
                                if (!mIsConnecting) {
                                    onConnectingState(true);
                                }
                            } else {
                                if (mIsConnecting) {
                                    onConnectingState(false);
                                }
                            }
                            break;
                        default:
                            Log.w(TAG, "Received unknown intent " + intent);
                            break;
                    }
                }
            };

    private boolean updateCountersAndCheckForConnectingState(int state, int prevState) {
        switch (prevState) {
            case BluetoothProfile.STATE_CONNECTING:
                if (mProfilesConnecting > 0) {
                    mProfilesConnecting--;
                } else {
                    Log.e(TAG, "mProfilesConnecting " + mProfilesConnecting);
                    throw new IllegalStateException(
                            "Invalid state transition, " + prevState + " -> " + state);
                }
                break;
            case BluetoothProfile.STATE_CONNECTED:
                if (mProfilesConnected > 0) {
                    mProfilesConnected--;
                } else {
                    Log.e(TAG, "mProfilesConnected " + mProfilesConnected);
                    throw new IllegalStateException(
                            "Invalid state transition, " + prevState + " -> " + state);
                }
                break;
            case BluetoothProfile.STATE_DISCONNECTING:
                if (mProfilesDisconnecting > 0) {
                    mProfilesDisconnecting--;
                } else {
                    Log.e(TAG, "mProfilesDisconnecting " + mProfilesDisconnecting);
                    throw new IllegalStateException(
                            "Invalid state transition, " + prevState + " -> " + state);
                }
                break;
        }
        switch (state) {
            case BluetoothProfile.STATE_CONNECTING:
                mProfilesConnecting++;
                break;
            case BluetoothProfile.STATE_CONNECTED:
                mProfilesConnected++;
                break;
            case BluetoothProfile.STATE_DISCONNECTING:
                mProfilesDisconnecting++;
                break;
            case BluetoothProfile.STATE_DISCONNECTED:
                break;
            default:
        }
        if (DBG) {
            Log.d(TAG, "mProfilesConnecting " + mProfilesConnecting + ", mProfilesConnected "
                    + mProfilesConnected + ", mProfilesDisconnecting " + mProfilesDisconnecting);
        }
        return (mProfilesConnecting > 0);
    }

    private void handleImportanceChange(UidImportance imp) {
        if (imp == null) {
            return;
@@ -1651,7 +1889,7 @@ public class ScanManager {
        }

        for (ScanClient client : mRegularScanClients) {
            if (client.appUid != uid || mScanNative.isTimeoutScanClient(client)) {
            if (client.appUid != uid || mScanNative.isForceDowngradedScanClient(client)) {
                continue;
            }
            if (isForeground) {
+217 −0

File changed.

Preview size limit exceeded, changes collapsed.