Loading android/app/src/com/android/bluetooth/gatt/AppScanStats.java +23 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ import java.util.Objects; public boolean isFilterScan; public boolean isCallbackScan; public boolean isBatchScan; public boolean isAutoBatchScan; public int results; public int scannerId; public int scanMode; Loading @@ -97,6 +98,7 @@ import java.util.Objects; this.isFilterScan = isFilterScan; this.isCallbackScan = isCallbackScan; this.isBatchScan = false; this.isAutoBatchScan = false; this.scanMode = scanMode; this.scanCallbackType = scanCallbackType; this.results = 0; Loading Loading @@ -189,6 +191,14 @@ import java.util.Objects; return scan.isDowngraded; } synchronized boolean isAutoBatchScan(int scannerId) { LastScan scan = getScanFromScannerId(scannerId); if (scan == null) { return false; } return scan.isAutoBatchScan; } synchronized void recordScanStart(ScanSettings settings, List<ScanFilter> filters, boolean isFilterScan, boolean isCallbackScan, int scannerId) { LastScan existingScan = getScanFromScannerId(scannerId); Loading Loading @@ -363,6 +373,13 @@ import java.util.Objects; } } synchronized void setAutoBatchScan(int scannerId, boolean isBatchScan) { LastScan scan = getScanFromScannerId(scannerId); if (scan != null) { scan.isAutoBatchScan = isBatchScan; } } synchronized boolean isScanningTooFrequently() { if (mLastScans.size() < mAdapterService.getScanQuotaCount()) { return false; Loading Loading @@ -477,6 +494,8 @@ import java.util.Objects; return "FIRST_MATCH"; case ScanSettings.CALLBACK_TYPE_MATCH_LOST: return "LOST"; case ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH: return "ALL_MATCHES_AUTO_BATCH"; default: return callbackType == (ScanSettings.CALLBACK_TYPE_FIRST_MATCH | ScanSettings.CALLBACK_TYPE_MATCH_LOST) ? "[FIRST_MATCH | LOST]" : "UNKNOWN: " Loading Loading @@ -591,6 +610,8 @@ import java.util.Objects; } if (scan.isBatchScan) { sb.append("Batch Scan"); } else if (scan.isAutoBatchScan) { sb.append("Auto Batch Scan"); } else { sb.append("Regular Scan"); } Loading Loading @@ -639,6 +660,8 @@ import java.util.Objects; } if (scan.isBatchScan) { sb.append("Batch Scan"); } else if (scan.isAutoBatchScan) { sb.append("Auto Batch Scan"); } else { sb.append("Regular Scan"); } Loading android/app/src/com/android/bluetooth/gatt/GattService.java +19 −3 Original line number Diff line number Diff line Loading @@ -1921,7 +1921,9 @@ public class GattService extends ProfileService { continue; } if ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_ALL_MATCHES) == 0) { int callbackType = settings.getCallbackType(); if (!(callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES || callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH)) { if (VDBG) { Log.d(TAG, "Skipping client: CALLBACK_TYPE_ALL_MATCHES"); } Loading Loading @@ -2612,7 +2614,7 @@ public class GattService extends ProfileService { Log.d(TAG, "onBatchScanReports() - scannerId=" + scannerId + ", status=" + status + ", reportType=" + reportType + ", numRecords=" + numRecords); } mScanManager.callbackDone(scannerId, status); Set<ScanResult> results = parseBatchScanResults(numRecords, reportType, recordData); if (reportType == ScanManager.SCAN_RESULT_TYPE_TRUNCATED) { // We only support single client for truncated mode. Loading Loading @@ -2664,13 +2666,27 @@ public class GattService extends ProfileService { deliverBatchScan(client, results); } } mScanManager.callbackDone(scannerId, status); } private void sendBatchScanResults(ScannerMap.App app, ScanClient client, ArrayList<ScanResult> results) { try { if (app.callback != null) { if (mScanManager.isAutoBatchScanClientEnabled(client)) { if (DBG) { Log.d(TAG, "sendBatchScanResults() to onScanResult()" + client); } for (ScanResult result : results) { app.appScanStats.addResult(client.scannerId); app.callback.onScanResult(result); } } else { if (DBG) { Log.d(TAG, "sendBatchScanResults() to onBatchScanResults()" + client); } app.callback.onBatchScanResults(results); } } else { sendResultsByPendingIntent(app.info, results, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); Loading android/app/src/com/android/bluetooth/gatt/ScanManager.java +101 −1 Original line number Diff line number Diff line Loading @@ -354,6 +354,10 @@ public class ScanManager { return mBluetoothAdapterProxy.isOffloadedScanFilteringSupported(); } boolean isAutoBatchScanClientEnabled(ScanClient client) { return mScanNative.isAutoBatchScanClientEnabled(client); } // Handler class that handles BLE scan operations. private class ClientHandler extends Handler { Loading Loading @@ -442,8 +446,16 @@ public class ScanManager { return; } if (!mScanNative.isExemptFromAutoBatchScanUpdate(client)) { if (mScreenOn) { clearAutoBatchScanClient(client); } else { setAutoBatchScanClient(client); } } // Begin scan operations. if (isBatchClient(client)) { if (isBatchClient(client) || isAutoBatchScanClientEnabled(client)) { mBatchClients.add(client); mScanNative.startBatchScan(client); } else { Loading Loading @@ -501,6 +513,9 @@ public class ScanManager { mScanNative.configureRegularScanParams(); } } else { if (isAutoBatchScanClientEnabled(client)) { handleFlushBatchResults(client); } mScanNative.stopBatchScan(client); } if (client.appDied) { Loading @@ -512,7 +527,13 @@ public class ScanManager { } void handleFlushBatchResults(ScanClient client) { if (DBG) { Log.d(TAG, "handleFlushBatchResults() " + client); } if (!mBatchClients.contains(client)) { if (DBG) { Log.d(TAG, "There is no batch scan client to flush " + client); } return; } mScanNative.flushBatchResults(client.scannerId); Loading Loading @@ -549,6 +570,7 @@ public class ScanManager { } handleSuspendScans(); updateRegularScanClientsScreenOff(); updateRegularScanToBatchScanClients(); } void handleConnectingState() { Loading Loading @@ -618,6 +640,62 @@ public class ScanManager { } } private void updateRegularScanToBatchScanClients() { boolean updatedScanParams = false; for (ScanClient client : mRegularScanClients) { if (!mScanNative.isExemptFromAutoBatchScanUpdate(client)) { if (DBG) { Log.d(TAG, "Updating regular scan to batch scan" + client); } handleStopScan(client); setAutoBatchScanClient(client); handleStartScan(client); updatedScanParams = true; } } if (updatedScanParams) { mScanNative.configureRegularScanParams(); } } private void updateBatchScanToRegularScanClients() { boolean updatedScanParams = false; for (ScanClient client : mBatchClients) { if (!mScanNative.isExemptFromAutoBatchScanUpdate(client)) { if (DBG) { Log.d(TAG, "Updating batch scan to regular scan" + client); } handleStopScan(client); clearAutoBatchScanClient(client); handleStartScan(client); updatedScanParams = true; } } if (updatedScanParams) { mScanNative.configureRegularScanParams(); } } private void setAutoBatchScanClient(ScanClient client) { if (isAutoBatchScanClientEnabled(client)) { return; } client.updateScanMode(ScanSettings.SCAN_MODE_SCREEN_OFF); if (client.stats != null) { client.stats.setAutoBatchScan(client.scannerId, true); } } private void clearAutoBatchScanClient(ScanClient client) { if (!isAutoBatchScanClientEnabled(client)) { return; } client.updateScanMode(client.scanModeApp); if (client.stats != null) { client.stats.setAutoBatchScan(client.scannerId, false); } } private void updateRegularScanClientsScreenOff() { boolean updatedScanParams = false; for (ScanClient client : mRegularScanClients) { Loading Loading @@ -782,6 +860,7 @@ public class ScanManager { if (DBG) { Log.d(TAG, "handleScreenOn()"); } updateBatchScanToRegularScanClients(); handleResumeScans(); updateRegularScanClientsScreenOn(); } Loading Loading @@ -1039,6 +1118,19 @@ public class ScanManager { return isOpportunisticScanClient(client) || isFirstMatchScanClient(client); } private boolean isExemptFromAutoBatchScanUpdate(ScanClient client) { return isOpportunisticScanClient(client) || !isAllMatchesAutoBatchScanClient(client); } private boolean isAutoBatchScanClientEnabled(ScanClient client) { return client.stats != null && client.stats.isAutoBatchScan(client.scannerId); } private boolean isAllMatchesAutoBatchScanClient(ScanClient client) { return client.settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH; } private boolean isOpportunisticScanClient(ScanClient client) { return client.settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC; } Loading Loading @@ -1148,6 +1240,8 @@ public class ScanManager { resolver, Settings.Global.BLE_SCAN_BALANCED_WINDOW_MS, SCAN_MODE_BALANCED_WINDOW_MS); case ScanSettings.SCAN_MODE_SCREEN_OFF: return mAdapterService.getScreenOffLowPowerWindowMillis(); default: return Settings.Global.getInt( resolver, Loading @@ -1164,6 +1258,8 @@ public class ScanManager { resolver, Settings.Global.BLE_SCAN_BALANCED_INTERVAL_MS, SCAN_MODE_BALANCED_INTERVAL_MS); case ScanSettings.SCAN_MODE_SCREEN_OFF: return mAdapterService.getScreenOffLowPowerIntervalMillis(); default: return Settings.Global.getInt( resolver, Loading Loading @@ -1526,6 +1622,10 @@ public class ScanManager { || (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { return DELIVERY_MODE_ON_FOUND_LOST; } if (isAllMatchesAutoBatchScanClient(client)) { return isAutoBatchScanClientEnabled(client) ? DELIVERY_MODE_BATCH : DELIVERY_MODE_IMMEDIATE; } return settings.getReportDelayMillis() == 0 ? DELIVERY_MODE_IMMEDIATE : DELIVERY_MODE_BATCH; } Loading android/app/tests/unit/src/com/android/bluetooth/gatt/ScanManagerTest.java +114 −8 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.bluetooth.gatt; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH; import static android.bluetooth.le.ScanSettings.SCAN_MODE_OPPORTUNISTIC; import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED; Loading Loading @@ -87,6 +89,7 @@ public class ScanManagerTest { private ScanManager mScanManager; private Handler mHandler; private CountDownLatch mLatch; private long mScanReportDelay; @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @Mock private AdapterService mAdapterService; Loading Loading @@ -139,6 +142,8 @@ public class ScanManagerTest { mLatch = new CountDownLatch(1); assertThat(mLatch).isNotNull(); mScanReportDelay = DEFAULT_SCAN_REPORT_DELAY_MS; } @After Loading Loading @@ -174,9 +179,9 @@ public class ScanManagerTest { } private ScanClient createScanClient(int id, boolean isFiltered, int scanMode, boolean isBatch) { boolean isBatch, boolean isAutoBatch) { List<ScanFilter> scanFilterList = createScanFilterList(isFiltered); ScanSettings scanSettings = createScanSettings(scanMode, isBatch); ScanSettings scanSettings = createScanSettings(scanMode, isBatch, isAutoBatch); ScanClient client = new ScanClient(id, scanSettings, scanFilterList); client.stats = new AppScanStats("Test", null, null, mService); Loading @@ -185,7 +190,7 @@ public class ScanManagerTest { } private ScanClient createScanClient(int id, boolean isFiltered, int scanMode) { return createScanClient(id, isFiltered, scanMode, false); return createScanClient(id, isFiltered, scanMode, false, false); } private List<ScanFilter> createScanFilterList(boolean isFiltered) { Loading @@ -197,12 +202,17 @@ public class ScanManagerTest { return scanFilterList; } private ScanSettings createScanSettings(int scanMode, boolean isBatch) { private ScanSettings createScanSettings(int scanMode, boolean isBatch, boolean isAutoBatch) { ScanSettings scanSettings = null; if (isBatch) { if (isBatch && isAutoBatch) { int autoCallbackType = CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH; scanSettings = new ScanSettings.Builder().setScanMode(scanMode) .setReportDelay(mScanReportDelay).setCallbackType(autoCallbackType) .build(); } else if (isBatch) { scanSettings = new ScanSettings.Builder().setScanMode(scanMode) .setReportDelay(DEFAULT_SCAN_REPORT_DELAY_MS).build(); .setReportDelay(mScanReportDelay).build(); } else { scanSettings = new ScanSettings.Builder().setScanMode(scanMode).build(); } Loading Loading @@ -806,6 +816,7 @@ public class ScanManagerTest { // Set filtered and batch scan flag final boolean isFiltered = false; final boolean isBatch = true; final boolean isAutoBatch = false; // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} SparseIntArray scanModeMap = new SparseIntArray(); scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_LOW_POWER); Loading @@ -822,7 +833,7 @@ public class ScanManagerTest { // Turn off screen sendMessageWaitForProcessed(createScreenOnOffMessage(false)); // Create scan client ScanClient client = createScanClient(i, isFiltered, ScanMode, isBatch); ScanClient client = createScanClient(i, isFiltered, ScanMode, isBatch, isAutoBatch); // Start scan sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); Loading @@ -842,6 +853,7 @@ public class ScanManagerTest { // Set filtered and batch scan flag final boolean isFiltered = true; final boolean isBatch = true; final boolean isAutoBatch = false; // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} SparseIntArray scanModeMap = new SparseIntArray(); scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_LOW_POWER); Loading @@ -858,7 +870,94 @@ public class ScanManagerTest { // Turn off screen sendMessageWaitForProcessed(createScreenOnOffMessage(false)); // Create scan client ScanClient client = createScanClient(i, isFiltered, ScanMode, isBatch); ScanClient client = createScanClient(i, isFiltered, ScanMode, isBatch, isAutoBatch); // Start scan sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isTrue(); assertThat(mScanManager.getBatchScanParams().scanMode).isEqualTo(expectedScanMode); // Turn on screen sendMessageWaitForProcessed(createScreenOnOffMessage(true)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isTrue(); assertThat(mScanManager.getBatchScanParams().scanMode).isEqualTo(expectedScanMode); } } @Test public void testUnfilteredAutoBatchScan() { // Set filtered and batch scan flag final boolean isFiltered = false; final boolean isBatch = true; final boolean isAutoBatch = true; // Set report delay for auto batch scan callback type mScanReportDelay = ScanSettings.AUTO_BATCH_MIN_REPORT_DELAY_MILLIS; // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} SparseIntArray scanModeMap = new SparseIntArray(); scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_SCREEN_OFF); scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_SCREEN_OFF); scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_SCREEN_OFF); scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_SCREEN_OFF); for (int i = 0; i < scanModeMap.size(); i++) { int ScanMode = scanModeMap.keyAt(i); int expectedScanMode = scanModeMap.get(ScanMode); Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + " expectedScanMode: " + String.valueOf(expectedScanMode)); // Turn off screen sendMessageWaitForProcessed(createScreenOnOffMessage(false)); // Create scan client ScanClient client = createScanClient(i, isFiltered, ScanMode, isBatch, isAutoBatch); // Start scan sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isTrue(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanParams()).isNull(); // Turn on screen sendMessageWaitForProcessed(createScreenOnOffMessage(true)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanParams()).isNull(); // Turn off screen sendMessageWaitForProcessed(createScreenOnOffMessage(false)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isTrue(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanParams()).isNull(); } } @Test public void testFilteredAutoBatchScan() { // Set filtered and batch scan flag final boolean isFiltered = true; final boolean isBatch = true; final boolean isAutoBatch = true; // Set report delay for auto batch scan callback type mScanReportDelay = ScanSettings.AUTO_BATCH_MIN_REPORT_DELAY_MILLIS; // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} SparseIntArray scanModeMap = new SparseIntArray(); scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_SCREEN_OFF); scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_SCREEN_OFF); scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_SCREEN_OFF); scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_SCREEN_OFF); for (int i = 0; i < scanModeMap.size(); i++) { int ScanMode = scanModeMap.keyAt(i); int expectedScanMode = scanModeMap.get(ScanMode); Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + " expectedScanMode: " + String.valueOf(expectedScanMode)); // Turn off screen sendMessageWaitForProcessed(createScreenOnOffMessage(false)); // Create scan client ScanClient client = createScanClient(i, isFiltered, ScanMode, isBatch, isAutoBatch); // Start scan sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); Loading @@ -867,6 +966,13 @@ public class ScanManagerTest { assertThat(mScanManager.getBatchScanParams().scanMode).isEqualTo(expectedScanMode); // Turn on screen sendMessageWaitForProcessed(createScreenOnOffMessage(true)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanParams()).isNull(); // Turn off screen sendMessageWaitForProcessed(createScreenOnOffMessage(false)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isTrue(); Loading framework/api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -1396,7 +1396,9 @@ package android.bluetooth.le { method public int getScanMode(); method public int getScanResultType(); method public void writeToParcel(android.os.Parcel, int); field public static final long AUTO_BATCH_MIN_REPORT_DELAY_MILLIS = 600000L; // 0x927c0L field public static final int CALLBACK_TYPE_ALL_MATCHES = 1; // 0x1 field public static final int CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH = 8; // 0x8 field public static final int CALLBACK_TYPE_FIRST_MATCH = 2; // 0x2 field public static final int CALLBACK_TYPE_MATCH_LOST = 4; // 0x4 field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.ScanSettings> CREATOR; Loading Loading
android/app/src/com/android/bluetooth/gatt/AppScanStats.java +23 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ import java.util.Objects; public boolean isFilterScan; public boolean isCallbackScan; public boolean isBatchScan; public boolean isAutoBatchScan; public int results; public int scannerId; public int scanMode; Loading @@ -97,6 +98,7 @@ import java.util.Objects; this.isFilterScan = isFilterScan; this.isCallbackScan = isCallbackScan; this.isBatchScan = false; this.isAutoBatchScan = false; this.scanMode = scanMode; this.scanCallbackType = scanCallbackType; this.results = 0; Loading Loading @@ -189,6 +191,14 @@ import java.util.Objects; return scan.isDowngraded; } synchronized boolean isAutoBatchScan(int scannerId) { LastScan scan = getScanFromScannerId(scannerId); if (scan == null) { return false; } return scan.isAutoBatchScan; } synchronized void recordScanStart(ScanSettings settings, List<ScanFilter> filters, boolean isFilterScan, boolean isCallbackScan, int scannerId) { LastScan existingScan = getScanFromScannerId(scannerId); Loading Loading @@ -363,6 +373,13 @@ import java.util.Objects; } } synchronized void setAutoBatchScan(int scannerId, boolean isBatchScan) { LastScan scan = getScanFromScannerId(scannerId); if (scan != null) { scan.isAutoBatchScan = isBatchScan; } } synchronized boolean isScanningTooFrequently() { if (mLastScans.size() < mAdapterService.getScanQuotaCount()) { return false; Loading Loading @@ -477,6 +494,8 @@ import java.util.Objects; return "FIRST_MATCH"; case ScanSettings.CALLBACK_TYPE_MATCH_LOST: return "LOST"; case ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH: return "ALL_MATCHES_AUTO_BATCH"; default: return callbackType == (ScanSettings.CALLBACK_TYPE_FIRST_MATCH | ScanSettings.CALLBACK_TYPE_MATCH_LOST) ? "[FIRST_MATCH | LOST]" : "UNKNOWN: " Loading Loading @@ -591,6 +610,8 @@ import java.util.Objects; } if (scan.isBatchScan) { sb.append("Batch Scan"); } else if (scan.isAutoBatchScan) { sb.append("Auto Batch Scan"); } else { sb.append("Regular Scan"); } Loading Loading @@ -639,6 +660,8 @@ import java.util.Objects; } if (scan.isBatchScan) { sb.append("Batch Scan"); } else if (scan.isAutoBatchScan) { sb.append("Auto Batch Scan"); } else { sb.append("Regular Scan"); } Loading
android/app/src/com/android/bluetooth/gatt/GattService.java +19 −3 Original line number Diff line number Diff line Loading @@ -1921,7 +1921,9 @@ public class GattService extends ProfileService { continue; } if ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_ALL_MATCHES) == 0) { int callbackType = settings.getCallbackType(); if (!(callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES || callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH)) { if (VDBG) { Log.d(TAG, "Skipping client: CALLBACK_TYPE_ALL_MATCHES"); } Loading Loading @@ -2612,7 +2614,7 @@ public class GattService extends ProfileService { Log.d(TAG, "onBatchScanReports() - scannerId=" + scannerId + ", status=" + status + ", reportType=" + reportType + ", numRecords=" + numRecords); } mScanManager.callbackDone(scannerId, status); Set<ScanResult> results = parseBatchScanResults(numRecords, reportType, recordData); if (reportType == ScanManager.SCAN_RESULT_TYPE_TRUNCATED) { // We only support single client for truncated mode. Loading Loading @@ -2664,13 +2666,27 @@ public class GattService extends ProfileService { deliverBatchScan(client, results); } } mScanManager.callbackDone(scannerId, status); } private void sendBatchScanResults(ScannerMap.App app, ScanClient client, ArrayList<ScanResult> results) { try { if (app.callback != null) { if (mScanManager.isAutoBatchScanClientEnabled(client)) { if (DBG) { Log.d(TAG, "sendBatchScanResults() to onScanResult()" + client); } for (ScanResult result : results) { app.appScanStats.addResult(client.scannerId); app.callback.onScanResult(result); } } else { if (DBG) { Log.d(TAG, "sendBatchScanResults() to onBatchScanResults()" + client); } app.callback.onBatchScanResults(results); } } else { sendResultsByPendingIntent(app.info, results, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); Loading
android/app/src/com/android/bluetooth/gatt/ScanManager.java +101 −1 Original line number Diff line number Diff line Loading @@ -354,6 +354,10 @@ public class ScanManager { return mBluetoothAdapterProxy.isOffloadedScanFilteringSupported(); } boolean isAutoBatchScanClientEnabled(ScanClient client) { return mScanNative.isAutoBatchScanClientEnabled(client); } // Handler class that handles BLE scan operations. private class ClientHandler extends Handler { Loading Loading @@ -442,8 +446,16 @@ public class ScanManager { return; } if (!mScanNative.isExemptFromAutoBatchScanUpdate(client)) { if (mScreenOn) { clearAutoBatchScanClient(client); } else { setAutoBatchScanClient(client); } } // Begin scan operations. if (isBatchClient(client)) { if (isBatchClient(client) || isAutoBatchScanClientEnabled(client)) { mBatchClients.add(client); mScanNative.startBatchScan(client); } else { Loading Loading @@ -501,6 +513,9 @@ public class ScanManager { mScanNative.configureRegularScanParams(); } } else { if (isAutoBatchScanClientEnabled(client)) { handleFlushBatchResults(client); } mScanNative.stopBatchScan(client); } if (client.appDied) { Loading @@ -512,7 +527,13 @@ public class ScanManager { } void handleFlushBatchResults(ScanClient client) { if (DBG) { Log.d(TAG, "handleFlushBatchResults() " + client); } if (!mBatchClients.contains(client)) { if (DBG) { Log.d(TAG, "There is no batch scan client to flush " + client); } return; } mScanNative.flushBatchResults(client.scannerId); Loading Loading @@ -549,6 +570,7 @@ public class ScanManager { } handleSuspendScans(); updateRegularScanClientsScreenOff(); updateRegularScanToBatchScanClients(); } void handleConnectingState() { Loading Loading @@ -618,6 +640,62 @@ public class ScanManager { } } private void updateRegularScanToBatchScanClients() { boolean updatedScanParams = false; for (ScanClient client : mRegularScanClients) { if (!mScanNative.isExemptFromAutoBatchScanUpdate(client)) { if (DBG) { Log.d(TAG, "Updating regular scan to batch scan" + client); } handleStopScan(client); setAutoBatchScanClient(client); handleStartScan(client); updatedScanParams = true; } } if (updatedScanParams) { mScanNative.configureRegularScanParams(); } } private void updateBatchScanToRegularScanClients() { boolean updatedScanParams = false; for (ScanClient client : mBatchClients) { if (!mScanNative.isExemptFromAutoBatchScanUpdate(client)) { if (DBG) { Log.d(TAG, "Updating batch scan to regular scan" + client); } handleStopScan(client); clearAutoBatchScanClient(client); handleStartScan(client); updatedScanParams = true; } } if (updatedScanParams) { mScanNative.configureRegularScanParams(); } } private void setAutoBatchScanClient(ScanClient client) { if (isAutoBatchScanClientEnabled(client)) { return; } client.updateScanMode(ScanSettings.SCAN_MODE_SCREEN_OFF); if (client.stats != null) { client.stats.setAutoBatchScan(client.scannerId, true); } } private void clearAutoBatchScanClient(ScanClient client) { if (!isAutoBatchScanClientEnabled(client)) { return; } client.updateScanMode(client.scanModeApp); if (client.stats != null) { client.stats.setAutoBatchScan(client.scannerId, false); } } private void updateRegularScanClientsScreenOff() { boolean updatedScanParams = false; for (ScanClient client : mRegularScanClients) { Loading Loading @@ -782,6 +860,7 @@ public class ScanManager { if (DBG) { Log.d(TAG, "handleScreenOn()"); } updateBatchScanToRegularScanClients(); handleResumeScans(); updateRegularScanClientsScreenOn(); } Loading Loading @@ -1039,6 +1118,19 @@ public class ScanManager { return isOpportunisticScanClient(client) || isFirstMatchScanClient(client); } private boolean isExemptFromAutoBatchScanUpdate(ScanClient client) { return isOpportunisticScanClient(client) || !isAllMatchesAutoBatchScanClient(client); } private boolean isAutoBatchScanClientEnabled(ScanClient client) { return client.stats != null && client.stats.isAutoBatchScan(client.scannerId); } private boolean isAllMatchesAutoBatchScanClient(ScanClient client) { return client.settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH; } private boolean isOpportunisticScanClient(ScanClient client) { return client.settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC; } Loading Loading @@ -1148,6 +1240,8 @@ public class ScanManager { resolver, Settings.Global.BLE_SCAN_BALANCED_WINDOW_MS, SCAN_MODE_BALANCED_WINDOW_MS); case ScanSettings.SCAN_MODE_SCREEN_OFF: return mAdapterService.getScreenOffLowPowerWindowMillis(); default: return Settings.Global.getInt( resolver, Loading @@ -1164,6 +1258,8 @@ public class ScanManager { resolver, Settings.Global.BLE_SCAN_BALANCED_INTERVAL_MS, SCAN_MODE_BALANCED_INTERVAL_MS); case ScanSettings.SCAN_MODE_SCREEN_OFF: return mAdapterService.getScreenOffLowPowerIntervalMillis(); default: return Settings.Global.getInt( resolver, Loading Loading @@ -1526,6 +1622,10 @@ public class ScanManager { || (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { return DELIVERY_MODE_ON_FOUND_LOST; } if (isAllMatchesAutoBatchScanClient(client)) { return isAutoBatchScanClientEnabled(client) ? DELIVERY_MODE_BATCH : DELIVERY_MODE_IMMEDIATE; } return settings.getReportDelayMillis() == 0 ? DELIVERY_MODE_IMMEDIATE : DELIVERY_MODE_BATCH; } Loading
android/app/tests/unit/src/com/android/bluetooth/gatt/ScanManagerTest.java +114 −8 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.bluetooth.gatt; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH; import static android.bluetooth.le.ScanSettings.SCAN_MODE_OPPORTUNISTIC; import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED; Loading Loading @@ -87,6 +89,7 @@ public class ScanManagerTest { private ScanManager mScanManager; private Handler mHandler; private CountDownLatch mLatch; private long mScanReportDelay; @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @Mock private AdapterService mAdapterService; Loading Loading @@ -139,6 +142,8 @@ public class ScanManagerTest { mLatch = new CountDownLatch(1); assertThat(mLatch).isNotNull(); mScanReportDelay = DEFAULT_SCAN_REPORT_DELAY_MS; } @After Loading Loading @@ -174,9 +179,9 @@ public class ScanManagerTest { } private ScanClient createScanClient(int id, boolean isFiltered, int scanMode, boolean isBatch) { boolean isBatch, boolean isAutoBatch) { List<ScanFilter> scanFilterList = createScanFilterList(isFiltered); ScanSettings scanSettings = createScanSettings(scanMode, isBatch); ScanSettings scanSettings = createScanSettings(scanMode, isBatch, isAutoBatch); ScanClient client = new ScanClient(id, scanSettings, scanFilterList); client.stats = new AppScanStats("Test", null, null, mService); Loading @@ -185,7 +190,7 @@ public class ScanManagerTest { } private ScanClient createScanClient(int id, boolean isFiltered, int scanMode) { return createScanClient(id, isFiltered, scanMode, false); return createScanClient(id, isFiltered, scanMode, false, false); } private List<ScanFilter> createScanFilterList(boolean isFiltered) { Loading @@ -197,12 +202,17 @@ public class ScanManagerTest { return scanFilterList; } private ScanSettings createScanSettings(int scanMode, boolean isBatch) { private ScanSettings createScanSettings(int scanMode, boolean isBatch, boolean isAutoBatch) { ScanSettings scanSettings = null; if (isBatch) { if (isBatch && isAutoBatch) { int autoCallbackType = CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH; scanSettings = new ScanSettings.Builder().setScanMode(scanMode) .setReportDelay(mScanReportDelay).setCallbackType(autoCallbackType) .build(); } else if (isBatch) { scanSettings = new ScanSettings.Builder().setScanMode(scanMode) .setReportDelay(DEFAULT_SCAN_REPORT_DELAY_MS).build(); .setReportDelay(mScanReportDelay).build(); } else { scanSettings = new ScanSettings.Builder().setScanMode(scanMode).build(); } Loading Loading @@ -806,6 +816,7 @@ public class ScanManagerTest { // Set filtered and batch scan flag final boolean isFiltered = false; final boolean isBatch = true; final boolean isAutoBatch = false; // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} SparseIntArray scanModeMap = new SparseIntArray(); scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_LOW_POWER); Loading @@ -822,7 +833,7 @@ public class ScanManagerTest { // Turn off screen sendMessageWaitForProcessed(createScreenOnOffMessage(false)); // Create scan client ScanClient client = createScanClient(i, isFiltered, ScanMode, isBatch); ScanClient client = createScanClient(i, isFiltered, ScanMode, isBatch, isAutoBatch); // Start scan sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); Loading @@ -842,6 +853,7 @@ public class ScanManagerTest { // Set filtered and batch scan flag final boolean isFiltered = true; final boolean isBatch = true; final boolean isAutoBatch = false; // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} SparseIntArray scanModeMap = new SparseIntArray(); scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_LOW_POWER); Loading @@ -858,7 +870,94 @@ public class ScanManagerTest { // Turn off screen sendMessageWaitForProcessed(createScreenOnOffMessage(false)); // Create scan client ScanClient client = createScanClient(i, isFiltered, ScanMode, isBatch); ScanClient client = createScanClient(i, isFiltered, ScanMode, isBatch, isAutoBatch); // Start scan sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isTrue(); assertThat(mScanManager.getBatchScanParams().scanMode).isEqualTo(expectedScanMode); // Turn on screen sendMessageWaitForProcessed(createScreenOnOffMessage(true)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isTrue(); assertThat(mScanManager.getBatchScanParams().scanMode).isEqualTo(expectedScanMode); } } @Test public void testUnfilteredAutoBatchScan() { // Set filtered and batch scan flag final boolean isFiltered = false; final boolean isBatch = true; final boolean isAutoBatch = true; // Set report delay for auto batch scan callback type mScanReportDelay = ScanSettings.AUTO_BATCH_MIN_REPORT_DELAY_MILLIS; // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} SparseIntArray scanModeMap = new SparseIntArray(); scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_SCREEN_OFF); scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_SCREEN_OFF); scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_SCREEN_OFF); scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_SCREEN_OFF); for (int i = 0; i < scanModeMap.size(); i++) { int ScanMode = scanModeMap.keyAt(i); int expectedScanMode = scanModeMap.get(ScanMode); Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + " expectedScanMode: " + String.valueOf(expectedScanMode)); // Turn off screen sendMessageWaitForProcessed(createScreenOnOffMessage(false)); // Create scan client ScanClient client = createScanClient(i, isFiltered, ScanMode, isBatch, isAutoBatch); // Start scan sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isTrue(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanParams()).isNull(); // Turn on screen sendMessageWaitForProcessed(createScreenOnOffMessage(true)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanParams()).isNull(); // Turn off screen sendMessageWaitForProcessed(createScreenOnOffMessage(false)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isTrue(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanParams()).isNull(); } } @Test public void testFilteredAutoBatchScan() { // Set filtered and batch scan flag final boolean isFiltered = true; final boolean isBatch = true; final boolean isAutoBatch = true; // Set report delay for auto batch scan callback type mScanReportDelay = ScanSettings.AUTO_BATCH_MIN_REPORT_DELAY_MILLIS; // Set scan mode map {original scan mode (ScanMode) : expected scan mode (expectedScanMode)} SparseIntArray scanModeMap = new SparseIntArray(); scanModeMap.put(SCAN_MODE_LOW_POWER, SCAN_MODE_SCREEN_OFF); scanModeMap.put(SCAN_MODE_BALANCED, SCAN_MODE_SCREEN_OFF); scanModeMap.put(SCAN_MODE_LOW_LATENCY, SCAN_MODE_SCREEN_OFF); scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, SCAN_MODE_SCREEN_OFF); for (int i = 0; i < scanModeMap.size(); i++) { int ScanMode = scanModeMap.keyAt(i); int expectedScanMode = scanModeMap.get(ScanMode); Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode) + " expectedScanMode: " + String.valueOf(expectedScanMode)); // Turn off screen sendMessageWaitForProcessed(createScreenOnOffMessage(false)); // Create scan client ScanClient client = createScanClient(i, isFiltered, ScanMode, isBatch, isAutoBatch); // Start scan sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); Loading @@ -867,6 +966,13 @@ public class ScanManagerTest { assertThat(mScanManager.getBatchScanParams().scanMode).isEqualTo(expectedScanMode); // Turn on screen sendMessageWaitForProcessed(createScreenOnOffMessage(true)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isTrue(); assertThat(client.settings.getScanMode()).isEqualTo(ScanMode); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanParams()).isNull(); // Turn off screen sendMessageWaitForProcessed(createScreenOnOffMessage(false)); assertThat(mScanManager.getRegularScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse(); assertThat(mScanManager.getBatchScanQueue().contains(client)).isTrue(); Loading
framework/api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -1396,7 +1396,9 @@ package android.bluetooth.le { method public int getScanMode(); method public int getScanResultType(); method public void writeToParcel(android.os.Parcel, int); field public static final long AUTO_BATCH_MIN_REPORT_DELAY_MILLIS = 600000L; // 0x927c0L field public static final int CALLBACK_TYPE_ALL_MATCHES = 1; // 0x1 field public static final int CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH = 8; // 0x8 field public static final int CALLBACK_TYPE_FIRST_MATCH = 2; // 0x2 field public static final int CALLBACK_TYPE_MATCH_LOST = 4; // 0x4 field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.ScanSettings> CREATOR; Loading