1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetooth.le_scan; 18 19 import static com.android.bluetooth.Utils.checkCallerTargetSdk; 20 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; 21 22 import android.annotation.RequiresPermission; 23 import android.annotation.SuppressLint; 24 import android.app.AppOpsManager; 25 import android.app.PendingIntent; 26 import android.bluetooth.BluetoothAdapter; 27 import android.bluetooth.BluetoothDevice; 28 import android.bluetooth.le.BluetoothLeScanner; 29 import android.bluetooth.le.IPeriodicAdvertisingCallback; 30 import android.bluetooth.le.IScannerCallback; 31 import android.bluetooth.le.ScanCallback; 32 import android.bluetooth.le.ScanFilter; 33 import android.bluetooth.le.ScanRecord; 34 import android.bluetooth.le.ScanResult; 35 import android.bluetooth.le.ScanSettings; 36 import android.companion.AssociationInfo; 37 import android.companion.CompanionDeviceManager; 38 import android.content.AttributionSource; 39 import android.content.Context; 40 import android.content.Intent; 41 import android.net.MacAddress; 42 import android.os.Binder; 43 import android.os.Build; 44 import android.os.IBinder; 45 import android.os.Looper; 46 import android.os.RemoteException; 47 import android.os.SystemClock; 48 import android.os.UserHandle; 49 import android.os.WorkSource; 50 import android.provider.DeviceConfig; 51 import android.util.Log; 52 53 import com.android.bluetooth.BluetoothMetricsProto; 54 import com.android.bluetooth.R; 55 import com.android.bluetooth.Utils; 56 import com.android.bluetooth.btservice.AdapterService; 57 import com.android.bluetooth.btservice.BluetoothAdapterProxy; 58 import com.android.bluetooth.flags.Flags; 59 import com.android.bluetooth.gatt.ContextMap; 60 import com.android.bluetooth.gatt.GattServiceConfig; 61 import com.android.bluetooth.util.NumberUtils; 62 import com.android.internal.annotations.VisibleForTesting; 63 64 import java.util.ArrayDeque; 65 import java.util.ArrayList; 66 import java.util.Arrays; 67 import java.util.Collections; 68 import java.util.HashSet; 69 import java.util.List; 70 import java.util.Set; 71 import java.util.UUID; 72 import java.util.concurrent.TimeUnit; 73 import java.util.function.Predicate; 74 75 /** 76 * A helper class which contains all scan related functions extracted from {@link 77 * com.android.bluetooth.gatt.GattService}. The purpose of this class is to preserve scan 78 * functionality within GattService and provide the same functionality in a new scan dedicated 79 * {@link com.android.bluetooth.btservice.ProfileService} when introduced. 80 */ 81 public class TransitionalScanHelper { 82 private static final String TAG = GattServiceConfig.TAG_PREFIX + "ScanHelper"; 83 84 // Batch scan related constants. 85 private static final int TRUNCATED_RESULT_SIZE = 11; 86 static final int SCAN_FILTER_ENABLED = 1; 87 static final int SCAN_FILTER_MODIFIED = 2; 88 89 /** The default floor value for LE batch scan report delays greater than 0 */ 90 @VisibleForTesting static final long DEFAULT_REPORT_DELAY_FLOOR = 5000; 91 92 private static final int NUM_SCAN_EVENTS_KEPT = 20; 93 94 // onFoundLost related constants 95 @VisibleForTesting static final int ADVT_STATE_ONFOUND = 0; 96 private static final int ADVT_STATE_ONLOST = 1; 97 98 private static final int ET_LEGACY_MASK = 0x10; 99 100 /** List of our registered scanners. */ 101 // TODO(b/327849650): Remove as this class adds no value. Using generics this ways is considered 102 // an anti-pattern. 103 public static class ScannerMap extends ContextMap<IScannerCallback, PendingIntentInfo> {} 104 105 /** Keep the arguments passed in for the PendingIntent. */ 106 public static class PendingIntentInfo { 107 public PendingIntent intent; 108 public ScanSettings settings; 109 public List<ScanFilter> filters; 110 public String callingPackage; 111 public int callingUid; 112 113 @Override equals(Object other)114 public boolean equals(Object other) { 115 if (!(other instanceof PendingIntentInfo)) { 116 return false; 117 } 118 return intent.equals(((PendingIntentInfo) other).intent); 119 } 120 121 @Override hashCode()122 public int hashCode() { 123 return intent == null ? 0 : intent.hashCode(); 124 } 125 } 126 127 public interface TestModeAccessor { 128 /** Indicates if bluetooth test mode is enabled. */ isTestModeEnabled()129 boolean isTestModeEnabled(); 130 } 131 132 private final PendingIntent.CancelListener mScanIntentCancelListener = 133 new PendingIntent.CancelListener() { 134 public void onCanceled(PendingIntent intent) { 135 Log.d(TAG, "scanning PendingIntent canceled"); 136 stopScan(intent, mContext.getAttributionSource()); 137 } 138 }; 139 140 private Context mContext; 141 private TestModeAccessor mTestModeAccessor; 142 143 private AppOpsManager mAppOps; 144 private CompanionDeviceManager mCompanionManager; 145 private PeriodicScanManager mPeriodicScanManager; 146 private ScanManager mScanManager; 147 private AdapterService mAdapterService; 148 149 private ScannerMap mScannerMap = new ScannerMap(); 150 private String mExposureNotificationPackage; 151 getScannerMap()152 public ScannerMap getScannerMap() { 153 return mScannerMap; 154 } 155 156 @VisibleForTesting setScannerMap(ScannerMap scannerMap)157 public void setScannerMap(ScannerMap scannerMap) { 158 mScannerMap = scannerMap; 159 } 160 161 /** Internal list of scan events to use with the proto */ 162 private final ArrayDeque<BluetoothMetricsProto.ScanEvent> mScanEvents = 163 new ArrayDeque<>(NUM_SCAN_EVENTS_KEPT); 164 165 /** */ 166 private final Predicate<ScanResult> mLocationDenylistPredicate = 167 (scanResult) -> { 168 final MacAddress parsedAddress = 169 MacAddress.fromString(scanResult.getDevice().getAddress()); 170 if (mAdapterService.getLocationDenylistMac().test(parsedAddress.toByteArray())) { 171 Log.v(TAG, "Skipping device matching denylist: " + scanResult.getDevice()); 172 return true; 173 } 174 final ScanRecord scanRecord = scanResult.getScanRecord(); 175 if (scanRecord.matchesAnyField( 176 mAdapterService.getLocationDenylistAdvertisingData())) { 177 Log.v(TAG, "Skipping data matching denylist: " + scanRecord); 178 return true; 179 } 180 return false; 181 }; 182 TransitionalScanHelper(Context context, TestModeAccessor testModeAccessor)183 public TransitionalScanHelper(Context context, TestModeAccessor testModeAccessor) { 184 mContext = context; 185 mTestModeAccessor = testModeAccessor; 186 } 187 188 /** 189 * Starts the LE scanning component. 190 * 191 * @param looper for scan operations 192 */ start(Looper looper)193 public void start(Looper looper) { 194 mExposureNotificationPackage = mContext.getString(R.string.exposure_notification_package); 195 mAppOps = mContext.getSystemService(AppOpsManager.class); 196 mCompanionManager = mContext.getSystemService(CompanionDeviceManager.class); 197 mAdapterService = AdapterService.getAdapterService(); 198 mScanManager = 199 ScanObjectsFactory.getInstance() 200 .createScanManager( 201 mContext, 202 this, 203 mAdapterService, 204 BluetoothAdapterProxy.getInstance(), 205 looper); 206 207 mPeriodicScanManager = 208 ScanObjectsFactory.getInstance().createPeriodicScanManager(mAdapterService); 209 } 210 211 /** Stops the scanning component. */ stop()212 public void stop() { 213 mScannerMap.clear(); 214 } 215 216 /** Cleans up the scanning component. */ cleanup()217 public void cleanup() { 218 if (mScanManager != null) { 219 mScanManager.cleanup(); 220 } 221 if (mPeriodicScanManager != null) { 222 mPeriodicScanManager.cleanup(); 223 } 224 } 225 226 /** Notifies scan manager of bluetooth profile connection state changes */ notifyProfileConnectionStateChange(int profile, int fromState, int toState)227 public void notifyProfileConnectionStateChange(int profile, int fromState, int toState) { 228 if (mScanManager == null) { 229 Log.w(TAG, "scan manager is null"); 230 return; 231 } 232 mScanManager.handleBluetoothProfileConnectionStateChanged(profile, fromState, toState); 233 } 234 getCurrentUsedTrackingAdvertisement()235 public int getCurrentUsedTrackingAdvertisement() { 236 return mScanManager.getCurrentUsedTrackingAdvertisement(); 237 } 238 239 /************************************************************************** 240 * Callback functions - CLIENT 241 *************************************************************************/ 242 243 // EN format defined here: 244 // https://blog.google/documents/70/Exposure_Notification_-_Bluetooth_Specification_v1.2.2.pdf 245 private static final byte[] EXPOSURE_NOTIFICATION_FLAGS_PREAMBLE = 246 new byte[] { 247 // size 2, flag field, flags byte (value is not important) 248 (byte) 0x02, (byte) 0x01 249 }; 250 251 private static final int EXPOSURE_NOTIFICATION_FLAGS_LENGTH = 0x2 + 1; 252 private static final byte[] EXPOSURE_NOTIFICATION_PAYLOAD_PREAMBLE = 253 new byte[] { 254 // size 3, complete 16 bit UUID, EN UUID 255 (byte) 0x03, (byte) 0x03, (byte) 0x6F, (byte) 0xFD, 256 // size 23, data for 16 bit UUID, EN UUID 257 (byte) 0x17, (byte) 0x16, (byte) 0x6F, (byte) 0xFD, 258 // ...payload 259 }; 260 private static final int EXPOSURE_NOTIFICATION_PAYLOAD_LENGTH = 0x03 + 0x17 + 2; 261 arrayStartsWith(byte[] array, byte[] prefix)262 private static boolean arrayStartsWith(byte[] array, byte[] prefix) { 263 if (array.length < prefix.length) { 264 return false; 265 } 266 for (int i = 0; i < prefix.length; i++) { 267 if (prefix[i] != array[i]) { 268 return false; 269 } 270 } 271 return true; 272 } 273 getSanitizedExposureNotification(ScanResult result)274 private ScanResult getSanitizedExposureNotification(ScanResult result) { 275 ScanRecord record = result.getScanRecord(); 276 // Remove the flags part of the payload, if present 277 if (record.getBytes().length > EXPOSURE_NOTIFICATION_FLAGS_LENGTH 278 && arrayStartsWith(record.getBytes(), EXPOSURE_NOTIFICATION_FLAGS_PREAMBLE)) { 279 record = 280 ScanRecord.parseFromBytes( 281 Arrays.copyOfRange( 282 record.getBytes(), 283 EXPOSURE_NOTIFICATION_FLAGS_LENGTH, 284 record.getBytes().length)); 285 } 286 287 if (record.getBytes().length != EXPOSURE_NOTIFICATION_PAYLOAD_LENGTH) { 288 return null; 289 } 290 if (!arrayStartsWith(record.getBytes(), EXPOSURE_NOTIFICATION_PAYLOAD_PREAMBLE)) { 291 return null; 292 } 293 294 return new ScanResult(null, 0, 0, 0, 0, 0, result.getRssi(), 0, record, 0); 295 } 296 297 /** Callback method for a scan result. */ onScanResult( int eventType, int addressType, String address, int primaryPhy, int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvInt, byte[] advData, String originalAddress)298 public void onScanResult( 299 int eventType, 300 int addressType, 301 String address, 302 int primaryPhy, 303 int secondaryPhy, 304 int advertisingSid, 305 int txPower, 306 int rssi, 307 int periodicAdvInt, 308 byte[] advData, 309 String originalAddress) { 310 // When in testing mode, ignore all real-world events 311 if (mTestModeAccessor.isTestModeEnabled()) return; 312 313 AppScanStats.recordScanRadioResultCount(); 314 onScanResultInternal( 315 eventType, 316 addressType, 317 address, 318 primaryPhy, 319 secondaryPhy, 320 advertisingSid, 321 txPower, 322 rssi, 323 periodicAdvInt, 324 advData, 325 originalAddress); 326 } 327 328 // TODO(b/327849650): Refactor to reduce the visibility of this method. onScanResultInternal( int eventType, int addressType, String address, int primaryPhy, int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvInt, byte[] advData, String originalAddress)329 public void onScanResultInternal( 330 int eventType, 331 int addressType, 332 String address, 333 int primaryPhy, 334 int secondaryPhy, 335 int advertisingSid, 336 int txPower, 337 int rssi, 338 int periodicAdvInt, 339 byte[] advData, 340 String originalAddress) { 341 Log.v( 342 TAG, 343 "onScanResult() - eventType=0x" 344 + Integer.toHexString(eventType) 345 + ", addressType=" 346 + addressType 347 + ", address=" 348 + address 349 + ", primaryPhy=" 350 + primaryPhy 351 + ", secondaryPhy=" 352 + secondaryPhy 353 + ", advertisingSid=0x" 354 + Integer.toHexString(advertisingSid) 355 + ", txPower=" 356 + txPower 357 + ", rssi=" 358 + rssi 359 + ", periodicAdvInt=0x" 360 + Integer.toHexString(periodicAdvInt) 361 + ", originalAddress=" 362 + originalAddress); 363 364 String identityAddress = mAdapterService.getIdentityAddress(address); 365 if (!address.equals(identityAddress)) { 366 Log.v( 367 TAG, 368 "found identityAddress of " 369 + address 370 + ", replace originalAddress as " 371 + identityAddress); 372 originalAddress = identityAddress; 373 } 374 375 byte[] legacyAdvData = Arrays.copyOfRange(advData, 0, 62); 376 377 for (ScanClient client : mScanManager.getRegularScanQueue()) { 378 ContextMap<IScannerCallback, PendingIntentInfo>.App app = 379 mScannerMap.getById(client.scannerId); 380 if (app == null) { 381 Log.v(TAG, "App is null; skip."); 382 continue; 383 } 384 385 BluetoothDevice device = 386 BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(address, addressType); 387 388 ScanSettings settings = client.settings; 389 byte[] scanRecordData; 390 // This is for compatibility with applications that assume fixed size scan data. 391 if (settings.getLegacy()) { 392 if ((eventType & ET_LEGACY_MASK) == 0) { 393 // If this is legacy scan, but nonlegacy result - skip. 394 Log.v(TAG, "Legacy scan, non legacy result; skip."); 395 continue; 396 } else { 397 // Some apps are used to fixed-size advertise data. 398 scanRecordData = legacyAdvData; 399 } 400 } else { 401 scanRecordData = advData; 402 } 403 404 ScanRecord scanRecord = ScanRecord.parseFromBytes(scanRecordData); 405 ScanResult result = 406 new ScanResult( 407 device, 408 eventType, 409 primaryPhy, 410 secondaryPhy, 411 advertisingSid, 412 txPower, 413 rssi, 414 periodicAdvInt, 415 scanRecord, 416 SystemClock.elapsedRealtimeNanos()); 417 418 if (client.hasDisavowedLocation) { 419 if (mLocationDenylistPredicate.test(result)) { 420 Log.i(TAG, "Skipping client for location deny list"); 421 continue; 422 } 423 } 424 425 boolean hasPermission = hasScanResultPermission(client); 426 if (!hasPermission) { 427 for (String associatedDevice : client.associatedDevices) { 428 if (associatedDevice.equalsIgnoreCase(address)) { 429 hasPermission = true; 430 break; 431 } 432 } 433 } 434 if (!hasPermission && client.eligibleForSanitizedExposureNotification) { 435 ScanResult sanitized = getSanitizedExposureNotification(result); 436 if (sanitized != null) { 437 hasPermission = true; 438 result = sanitized; 439 } 440 } 441 boolean matchResult = matchesFilters(client, result, originalAddress); 442 if (!hasPermission || !matchResult) { 443 Log.v( 444 TAG, 445 "Skipping client: permission=" + hasPermission + " matches=" + matchResult); 446 continue; 447 } 448 449 int callbackType = settings.getCallbackType(); 450 if (!(callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES 451 || callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH)) { 452 Log.v(TAG, "Skipping client: CALLBACK_TYPE_ALL_MATCHES"); 453 continue; 454 } 455 456 try { 457 app.appScanStats.addResult(client.scannerId); 458 if (app.callback != null) { 459 app.callback.onScanResult(result); 460 } else { 461 // Send the PendingIntent 462 ArrayList<ScanResult> results = new ArrayList<>(); 463 results.add(result); 464 sendResultsByPendingIntent( 465 app.info, results, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); 466 } 467 } catch (RemoteException | PendingIntent.CanceledException e) { 468 Log.e(TAG, "Exception: " + e); 469 if (Flags.leScanFixRemoteException()) { 470 handleDeadScanClient(client); 471 } else { 472 mScannerMap.remove(client.scannerId); 473 mScanManager.stopScan(client.scannerId); 474 } 475 } 476 } 477 } 478 sendResultByPendingIntent( PendingIntentInfo pii, ScanResult result, int callbackType, ScanClient client)479 private void sendResultByPendingIntent( 480 PendingIntentInfo pii, ScanResult result, int callbackType, ScanClient client) { 481 ArrayList<ScanResult> results = new ArrayList<>(); 482 results.add(result); 483 try { 484 sendResultsByPendingIntent(pii, results, callbackType); 485 } catch (PendingIntent.CanceledException e) { 486 final long token = Binder.clearCallingIdentity(); 487 try { 488 stopScan(client.scannerId, mContext.getAttributionSource()); 489 unregisterScanner(client.scannerId, mContext.getAttributionSource()); 490 } finally { 491 Binder.restoreCallingIdentity(token); 492 } 493 } 494 } 495 sendResultsByPendingIntent( PendingIntentInfo pii, ArrayList<ScanResult> results, int callbackType)496 private void sendResultsByPendingIntent( 497 PendingIntentInfo pii, ArrayList<ScanResult> results, int callbackType) 498 throws PendingIntent.CanceledException { 499 Intent extrasIntent = new Intent(); 500 extrasIntent.putParcelableArrayListExtra( 501 BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT, results); 502 extrasIntent.putExtra(BluetoothLeScanner.EXTRA_CALLBACK_TYPE, callbackType); 503 pii.intent.send(mContext, 0, extrasIntent); 504 } 505 sendErrorByPendingIntent(PendingIntentInfo pii, int errorCode)506 private void sendErrorByPendingIntent(PendingIntentInfo pii, int errorCode) 507 throws PendingIntent.CanceledException { 508 Intent extrasIntent = new Intent(); 509 extrasIntent.putExtra(BluetoothLeScanner.EXTRA_ERROR_CODE, errorCode); 510 pii.intent.send(mContext, 0, extrasIntent); 511 } 512 513 /** Callback method for scanner registration. */ onScannerRegistered(int status, int scannerId, long uuidLsb, long uuidMsb)514 public void onScannerRegistered(int status, int scannerId, long uuidLsb, long uuidMsb) 515 throws RemoteException { 516 UUID uuid = new UUID(uuidMsb, uuidLsb); 517 Log.d( 518 TAG, 519 "onScannerRegistered() - UUID=" 520 + uuid 521 + ", scannerId=" 522 + scannerId 523 + ", status=" 524 + status); 525 526 // First check the callback map 527 ContextMap<IScannerCallback, PendingIntentInfo>.App cbApp = mScannerMap.getByUuid(uuid); 528 if (cbApp != null) { 529 if (status == 0) { 530 cbApp.id = scannerId; 531 // If app is callback based, setup a death recipient. App will initiate the start. 532 // Otherwise, if PendingIntent based, start the scan directly. 533 if (cbApp.callback != null) { 534 cbApp.linkToDeath(new ScannerDeathRecipient(scannerId, cbApp.name)); 535 } else { 536 continuePiStartScan(scannerId, cbApp); 537 } 538 } else { 539 mScannerMap.remove(scannerId); 540 } 541 if (cbApp.callback != null) { 542 cbApp.callback.onScannerRegistered(status, scannerId); 543 } 544 } 545 } 546 547 /** Determines if the given scan client has the appropriate permissions to receive callbacks. */ hasScanResultPermission(final ScanClient client)548 private boolean hasScanResultPermission(final ScanClient client) { 549 if (client.hasNetworkSettingsPermission 550 || client.hasNetworkSetupWizardPermission 551 || client.hasScanWithoutLocationPermission) { 552 return true; 553 } 554 if (client.hasDisavowedLocation) { 555 return true; 556 } 557 return client.hasLocationPermission 558 && !Utils.blockedByLocationOff(mContext, client.userHandle); 559 } 560 561 // Check if a scan record matches a specific filters. matchesFilters(ScanClient client, ScanResult scanResult)562 private boolean matchesFilters(ScanClient client, ScanResult scanResult) { 563 return matchesFilters(client, scanResult, null); 564 } 565 566 // Check if a scan record matches a specific filters or original address matchesFilters( ScanClient client, ScanResult scanResult, String originalAddress)567 private boolean matchesFilters( 568 ScanClient client, ScanResult scanResult, String originalAddress) { 569 if (client.filters == null || client.filters.isEmpty()) { 570 // TODO: Do we really wanna return true here? 571 return true; 572 } 573 for (ScanFilter filter : client.filters) { 574 // Need to check the filter matches, and the original address without changing the API 575 if (filter.matches(scanResult)) { 576 return true; 577 } 578 if (originalAddress != null 579 && originalAddress.equalsIgnoreCase(filter.getDeviceAddress())) { 580 return true; 581 } 582 } 583 return false; 584 } 585 handleDeadScanClient(ScanClient client)586 private void handleDeadScanClient(ScanClient client) { 587 if (client.appDied) { 588 Log.w(TAG, "Already dead client " + client.scannerId); 589 return; 590 } 591 client.appDied = true; 592 stopScan(client.scannerId, mContext.getAttributionSource()); 593 } 594 595 /** Callback method for scan filter enablement/disablement. */ onScanFilterEnableDisabled(int action, int status, int clientIf)596 public void onScanFilterEnableDisabled(int action, int status, int clientIf) { 597 Log.d( 598 TAG, 599 "onScanFilterEnableDisabled() - clientIf=" 600 + clientIf 601 + ", status=" 602 + status 603 + ", action=" 604 + action); 605 mScanManager.callbackDone(clientIf, status); 606 } 607 608 /** Callback method for configuration of scan filter params. */ onScanFilterParamsConfigured( int action, int status, int clientIf, int availableSpace)609 public void onScanFilterParamsConfigured( 610 int action, int status, int clientIf, int availableSpace) { 611 Log.d( 612 TAG, 613 "onScanFilterParamsConfigured() - clientIf=" 614 + clientIf 615 + ", status=" 616 + status 617 + ", action=" 618 + action 619 + ", availableSpace=" 620 + availableSpace); 621 mScanManager.callbackDone(clientIf, status); 622 } 623 624 /** Callback method for configuration of scan filter. */ onScanFilterConfig( int action, int status, int clientIf, int filterType, int availableSpace)625 public void onScanFilterConfig( 626 int action, int status, int clientIf, int filterType, int availableSpace) { 627 Log.d( 628 TAG, 629 "onScanFilterConfig() - clientIf=" 630 + clientIf 631 + ", action = " 632 + action 633 + " status = " 634 + status 635 + ", filterType=" 636 + filterType 637 + ", availableSpace=" 638 + availableSpace); 639 640 mScanManager.callbackDone(clientIf, status); 641 } 642 643 /** Callback method for configuration of batch scan storage. */ onBatchScanStorageConfigured(int status, int clientIf)644 public void onBatchScanStorageConfigured(int status, int clientIf) { 645 Log.d(TAG, "onBatchScanStorageConfigured() - clientIf=" + clientIf + ", status=" + status); 646 mScanManager.callbackDone(clientIf, status); 647 } 648 649 /** Callback method for start/stop of batch scan. */ 650 // TODO: split into two different callbacks : onBatchScanStarted and onBatchScanStopped. onBatchScanStartStopped(int startStopAction, int status, int clientIf)651 public void onBatchScanStartStopped(int startStopAction, int status, int clientIf) { 652 Log.d( 653 TAG, 654 "onBatchScanStartStopped() - clientIf=" 655 + clientIf 656 + ", status=" 657 + status 658 + ", startStopAction=" 659 + startStopAction); 660 mScanManager.callbackDone(clientIf, status); 661 } 662 findBatchScanClientById(int scannerId)663 ScanClient findBatchScanClientById(int scannerId) { 664 for (ScanClient client : mScanManager.getBatchScanQueue()) { 665 if (client.scannerId == scannerId) { 666 return client; 667 } 668 } 669 return null; 670 } 671 672 /** Callback method for batch scan reports */ onBatchScanReports( int status, int scannerId, int reportType, int numRecords, byte[] recordData)673 public void onBatchScanReports( 674 int status, int scannerId, int reportType, int numRecords, byte[] recordData) 675 throws RemoteException { 676 // When in testing mode, ignore all real-world events 677 if (mTestModeAccessor.isTestModeEnabled()) return; 678 679 AppScanStats.recordBatchScanRadioResultCount(numRecords); 680 onBatchScanReportsInternal(status, scannerId, reportType, numRecords, recordData); 681 } 682 683 @VisibleForTesting onBatchScanReportsInternal( int status, int scannerId, int reportType, int numRecords, byte[] recordData)684 void onBatchScanReportsInternal( 685 int status, int scannerId, int reportType, int numRecords, byte[] recordData) 686 throws RemoteException { 687 Log.d( 688 TAG, 689 "onBatchScanReports() - scannerId=" 690 + scannerId 691 + ", status=" 692 + status 693 + ", reportType=" 694 + reportType 695 + ", numRecords=" 696 + numRecords); 697 698 Set<ScanResult> results = parseBatchScanResults(numRecords, reportType, recordData); 699 if (reportType == ScanManager.SCAN_RESULT_TYPE_TRUNCATED) { 700 // We only support single client for truncated mode. 701 ContextMap<IScannerCallback, PendingIntentInfo>.App app = 702 mScannerMap.getById(scannerId); 703 if (app == null) { 704 return; 705 } 706 707 ScanClient client = findBatchScanClientById(scannerId); 708 if (client == null) { 709 return; 710 } 711 712 ArrayList<ScanResult> permittedResults; 713 if (hasScanResultPermission(client)) { 714 permittedResults = new ArrayList<ScanResult>(results); 715 } else { 716 permittedResults = new ArrayList<ScanResult>(); 717 for (ScanResult scanResult : results) { 718 for (String associatedDevice : client.associatedDevices) { 719 if (associatedDevice.equalsIgnoreCase( 720 scanResult.getDevice().getAddress())) { 721 permittedResults.add(scanResult); 722 } 723 } 724 } 725 if (permittedResults.isEmpty()) { 726 return; 727 } 728 } 729 730 if (client.hasDisavowedLocation) { 731 permittedResults.removeIf(mLocationDenylistPredicate); 732 } 733 734 if (app.callback != null) { 735 app.callback.onBatchScanResults(permittedResults); 736 } else { 737 // PendingIntent based 738 try { 739 sendResultsByPendingIntent( 740 app.info, permittedResults, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); 741 } catch (PendingIntent.CanceledException e) { 742 } 743 } 744 } else { 745 for (ScanClient client : mScanManager.getFullBatchScanQueue()) { 746 // Deliver results for each client. 747 deliverBatchScan(client, results); 748 } 749 } 750 mScanManager.callbackDone(scannerId, status); 751 } 752 sendBatchScanResults( ContextMap<IScannerCallback, PendingIntentInfo>.App app, ScanClient client, ArrayList<ScanResult> results)753 private void sendBatchScanResults( 754 ContextMap<IScannerCallback, PendingIntentInfo>.App app, 755 ScanClient client, 756 ArrayList<ScanResult> results) { 757 try { 758 if (app.callback != null) { 759 if (mScanManager.isAutoBatchScanClientEnabled(client)) { 760 Log.d(TAG, "sendBatchScanResults() to onScanResult()" + client); 761 for (ScanResult result : results) { 762 app.appScanStats.addResult(client.scannerId); 763 app.callback.onScanResult(result); 764 } 765 } else { 766 Log.d(TAG, "sendBatchScanResults() to onBatchScanResults()" + client); 767 app.callback.onBatchScanResults(results); 768 } 769 } else { 770 sendResultsByPendingIntent( 771 app.info, results, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); 772 } 773 } catch (RemoteException | PendingIntent.CanceledException e) { 774 Log.e(TAG, "Exception: " + e); 775 if (Flags.leScanFixRemoteException()) { 776 handleDeadScanClient(client); 777 } else { 778 mScannerMap.remove(client.scannerId); 779 mScanManager.stopScan(client.scannerId); 780 } 781 } 782 } 783 784 // Check and deliver scan results for different scan clients. deliverBatchScan(ScanClient client, Set<ScanResult> allResults)785 private void deliverBatchScan(ScanClient client, Set<ScanResult> allResults) 786 throws RemoteException { 787 ContextMap.App app = mScannerMap.getById(client.scannerId); 788 if (app == null) { 789 return; 790 } 791 792 ArrayList<ScanResult> permittedResults; 793 if (hasScanResultPermission(client)) { 794 permittedResults = new ArrayList<ScanResult>(allResults); 795 } else { 796 permittedResults = new ArrayList<ScanResult>(); 797 for (ScanResult scanResult : allResults) { 798 for (String associatedDevice : client.associatedDevices) { 799 if (associatedDevice.equalsIgnoreCase(scanResult.getDevice().getAddress())) { 800 permittedResults.add(scanResult); 801 } 802 } 803 } 804 if (permittedResults.isEmpty()) { 805 return; 806 } 807 } 808 809 if (client.filters == null || client.filters.isEmpty()) { 810 sendBatchScanResults(app, client, permittedResults); 811 // TODO: Question to reviewer: Shouldn't there be a return here? 812 } 813 // Reconstruct the scan results. 814 ArrayList<ScanResult> results = new ArrayList<ScanResult>(); 815 for (ScanResult scanResult : permittedResults) { 816 if (matchesFilters(client, scanResult)) { 817 results.add(scanResult); 818 } 819 } 820 sendBatchScanResults(app, client, results); 821 } 822 parseBatchScanResults( int numRecords, int reportType, byte[] batchRecord)823 private Set<ScanResult> parseBatchScanResults( 824 int numRecords, int reportType, byte[] batchRecord) { 825 if (numRecords == 0) { 826 return Collections.emptySet(); 827 } 828 Log.d(TAG, "current time is " + SystemClock.elapsedRealtimeNanos()); 829 if (reportType == ScanManager.SCAN_RESULT_TYPE_TRUNCATED) { 830 return parseTruncatedResults(numRecords, batchRecord); 831 } else { 832 return parseFullResults(numRecords, batchRecord); 833 } 834 } 835 parseTruncatedResults(int numRecords, byte[] batchRecord)836 private Set<ScanResult> parseTruncatedResults(int numRecords, byte[] batchRecord) { 837 Log.d(TAG, "batch record " + Arrays.toString(batchRecord)); 838 Set<ScanResult> results = new HashSet<ScanResult>(numRecords); 839 long now = SystemClock.elapsedRealtimeNanos(); 840 for (int i = 0; i < numRecords; ++i) { 841 byte[] record = 842 extractBytes(batchRecord, i * TRUNCATED_RESULT_SIZE, TRUNCATED_RESULT_SIZE); 843 byte[] address = extractBytes(record, 0, 6); 844 reverse(address); 845 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 846 int rssi = record[8]; 847 long timestampNanos = now - parseTimestampNanos(extractBytes(record, 9, 2)); 848 results.add( 849 new ScanResult( 850 device, ScanRecord.parseFromBytes(new byte[0]), rssi, timestampNanos)); 851 } 852 return results; 853 } 854 855 @VisibleForTesting parseTimestampNanos(byte[] data)856 long parseTimestampNanos(byte[] data) { 857 long timestampUnit = NumberUtils.littleEndianByteArrayToInt(data); 858 // Timestamp is in every 50 ms. 859 return TimeUnit.MILLISECONDS.toNanos(timestampUnit * 50); 860 } 861 parseFullResults(int numRecords, byte[] batchRecord)862 private Set<ScanResult> parseFullResults(int numRecords, byte[] batchRecord) { 863 Log.d(TAG, "Batch record : " + Arrays.toString(batchRecord)); 864 Set<ScanResult> results = new HashSet<ScanResult>(numRecords); 865 int position = 0; 866 long now = SystemClock.elapsedRealtimeNanos(); 867 while (position < batchRecord.length) { 868 byte[] address = extractBytes(batchRecord, position, 6); 869 // TODO: remove temp hack. 870 reverse(address); 871 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 872 position += 6; 873 // Skip address type. 874 position++; 875 // Skip tx power level. 876 position++; 877 int rssi = batchRecord[position++]; 878 long timestampNanos = now - parseTimestampNanos(extractBytes(batchRecord, position, 2)); 879 position += 2; 880 881 // Combine advertise packet and scan response packet. 882 int advertisePacketLen = batchRecord[position++]; 883 byte[] advertiseBytes = extractBytes(batchRecord, position, advertisePacketLen); 884 position += advertisePacketLen; 885 int scanResponsePacketLen = batchRecord[position++]; 886 byte[] scanResponseBytes = extractBytes(batchRecord, position, scanResponsePacketLen); 887 position += scanResponsePacketLen; 888 byte[] scanRecord = new byte[advertisePacketLen + scanResponsePacketLen]; 889 System.arraycopy(advertiseBytes, 0, scanRecord, 0, advertisePacketLen); 890 System.arraycopy( 891 scanResponseBytes, 0, scanRecord, advertisePacketLen, scanResponsePacketLen); 892 Log.d(TAG, "ScanRecord : " + Arrays.toString(scanRecord)); 893 results.add( 894 new ScanResult( 895 device, ScanRecord.parseFromBytes(scanRecord), rssi, timestampNanos)); 896 } 897 return results; 898 } 899 900 // Reverse byte array. reverse(byte[] address)901 private void reverse(byte[] address) { 902 int len = address.length; 903 for (int i = 0; i < len / 2; ++i) { 904 byte b = address[i]; 905 address[i] = address[len - 1 - i]; 906 address[len - 1 - i] = b; 907 } 908 } 909 910 // Helper method to extract bytes from byte array. extractBytes(byte[] scanRecord, int start, int length)911 private static byte[] extractBytes(byte[] scanRecord, int start, int length) { 912 byte[] bytes = new byte[length]; 913 System.arraycopy(scanRecord, start, bytes, 0, length); 914 return bytes; 915 } 916 917 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) onBatchScanThresholdCrossed(int clientIf)918 public void onBatchScanThresholdCrossed(int clientIf) { 919 Log.d(TAG, "onBatchScanThresholdCrossed() - clientIf=" + clientIf); 920 flushPendingBatchResults(clientIf, mContext.getAttributionSource()); 921 } 922 createOnTrackAdvFoundLostObject( int clientIf, int advPktLen, byte[] advPkt, int scanRspLen, byte[] scanRsp, int filtIndex, int advState, int advInfoPresent, String address, int addrType, int txPower, int rssiValue, int timeStamp)923 public AdvtFilterOnFoundOnLostInfo createOnTrackAdvFoundLostObject( 924 int clientIf, 925 int advPktLen, 926 byte[] advPkt, 927 int scanRspLen, 928 byte[] scanRsp, 929 int filtIndex, 930 int advState, 931 int advInfoPresent, 932 String address, 933 int addrType, 934 int txPower, 935 int rssiValue, 936 int timeStamp) { 937 938 return new AdvtFilterOnFoundOnLostInfo( 939 clientIf, 940 advPktLen, 941 advPkt, 942 scanRspLen, 943 scanRsp, 944 filtIndex, 945 advState, 946 advInfoPresent, 947 address, 948 addrType, 949 txPower, 950 rssiValue, 951 timeStamp); 952 } 953 onTrackAdvFoundLost(AdvtFilterOnFoundOnLostInfo trackingInfo)954 public void onTrackAdvFoundLost(AdvtFilterOnFoundOnLostInfo trackingInfo) 955 throws RemoteException { 956 Log.d( 957 TAG, 958 "onTrackAdvFoundLost() - scannerId= " 959 + trackingInfo.getClientIf() 960 + " address = " 961 + trackingInfo.getAddress() 962 + " addressType = " 963 + trackingInfo.getAddressType() 964 + " adv_state = " 965 + trackingInfo.getAdvState()); 966 967 ContextMap<IScannerCallback, PendingIntentInfo>.App app = 968 mScannerMap.getById(trackingInfo.getClientIf()); 969 if (app == null || (app.callback == null && app.info == null)) { 970 Log.e(TAG, "app or callback is null"); 971 return; 972 } 973 974 BluetoothDevice device; 975 if (Flags.leScanUseAddressType()) { 976 device = 977 BluetoothAdapter.getDefaultAdapter() 978 .getRemoteLeDevice( 979 trackingInfo.getAddress(), trackingInfo.getAddressType()); 980 } else { 981 device = 982 BluetoothAdapter.getDefaultAdapter().getRemoteDevice(trackingInfo.getAddress()); 983 } 984 int advertiserState = trackingInfo.getAdvState(); 985 ScanResult result = 986 new ScanResult( 987 device, 988 ScanRecord.parseFromBytes(trackingInfo.getResult()), 989 trackingInfo.getRSSIValue(), 990 SystemClock.elapsedRealtimeNanos()); 991 992 for (ScanClient client : mScanManager.getRegularScanQueue()) { 993 if (client.scannerId == trackingInfo.getClientIf()) { 994 ScanSettings settings = client.settings; 995 if ((advertiserState == ADVT_STATE_ONFOUND) 996 && ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) 997 != 0)) { 998 if (app.callback != null) { 999 app.callback.onFoundOrLost(true, result); 1000 } else { 1001 sendResultByPendingIntent( 1002 app.info, result, ScanSettings.CALLBACK_TYPE_FIRST_MATCH, client); 1003 } 1004 } else if ((advertiserState == ADVT_STATE_ONLOST) 1005 && ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) 1006 != 0)) { 1007 if (app.callback != null) { 1008 app.callback.onFoundOrLost(false, result); 1009 } else { 1010 sendResultByPendingIntent( 1011 app.info, result, ScanSettings.CALLBACK_TYPE_MATCH_LOST, client); 1012 } 1013 } else { 1014 Log.d( 1015 TAG, 1016 "Not reporting onlost/onfound : " 1017 + advertiserState 1018 + " scannerId = " 1019 + client.scannerId 1020 + " callbackType " 1021 + settings.getCallbackType()); 1022 } 1023 } 1024 } 1025 } 1026 onScanParamSetupCompleted(int status, int scannerId)1027 public void onScanParamSetupCompleted(int status, int scannerId) throws RemoteException { 1028 ContextMap.App app = mScannerMap.getById(scannerId); 1029 if (app == null || app.callback == null) { 1030 Log.e(TAG, "Advertise app or callback is null"); 1031 return; 1032 } 1033 Log.d(TAG, "onScanParamSetupCompleted : " + status); 1034 } 1035 1036 // callback from ScanManager for dispatch of errors apps. onScanManagerErrorCallback(int scannerId, int errorCode)1037 public void onScanManagerErrorCallback(int scannerId, int errorCode) throws RemoteException { 1038 ContextMap<IScannerCallback, PendingIntentInfo>.App app = mScannerMap.getById(scannerId); 1039 if (app == null || (app.callback == null && app.info == null)) { 1040 Log.e(TAG, "App or callback is null"); 1041 return; 1042 } 1043 if (app.callback != null) { 1044 app.callback.onScanManagerErrorCallback(errorCode); 1045 } else { 1046 try { 1047 sendErrorByPendingIntent(app.info, errorCode); 1048 } catch (PendingIntent.CanceledException e) { 1049 Log.e(TAG, "Error sending error code via PendingIntent:" + e); 1050 } 1051 } 1052 } 1053 1054 /************************************************************************** 1055 * GATT Service functions - Shared CLIENT/SERVER 1056 *************************************************************************/ 1057 1058 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) registerScanner( IScannerCallback callback, WorkSource workSource, AttributionSource attributionSource)1059 public void registerScanner( 1060 IScannerCallback callback, WorkSource workSource, AttributionSource attributionSource) { 1061 if (!Utils.checkScanPermissionForDataDelivery( 1062 mContext, attributionSource, "ScanHelper registerScanner")) { 1063 return; 1064 } 1065 1066 UUID uuid = UUID.randomUUID(); 1067 Log.d(TAG, "registerScanner() - UUID=" + uuid); 1068 1069 enforceImpersonatationPermissionIfNeeded(workSource); 1070 1071 AppScanStats app = mScannerMap.getAppScanStatsByUid(Binder.getCallingUid()); 1072 if (app != null 1073 && app.isScanningTooFrequently() 1074 && !Utils.checkCallerHasPrivilegedPermission(mContext)) { 1075 Log.e(TAG, "App '" + app.appName + "' is scanning too frequently"); 1076 try { 1077 callback.onScannerRegistered(ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY, -1); 1078 } catch (RemoteException e) { 1079 Log.e(TAG, "Exception: " + e); 1080 } 1081 return; 1082 } 1083 1084 mScannerMap.add(uuid, workSource, callback, null, mContext, this); 1085 mScanManager.registerScanner(uuid); 1086 } 1087 1088 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) unregisterScanner(int scannerId, AttributionSource attributionSource)1089 public void unregisterScanner(int scannerId, AttributionSource attributionSource) { 1090 if (!Utils.checkScanPermissionForDataDelivery( 1091 mContext, attributionSource, "ScanHelper unregisterScanner")) { 1092 return; 1093 } 1094 1095 Log.d(TAG, "unregisterScanner() - scannerId=" + scannerId); 1096 mScannerMap.remove(scannerId); 1097 mScanManager.unregisterScanner(scannerId); 1098 } 1099 getAssociatedDevices(String callingPackage)1100 private List<String> getAssociatedDevices(String callingPackage) { 1101 if (mCompanionManager == null) { 1102 return Collections.emptyList(); 1103 } 1104 1105 List<String> macAddresses = new ArrayList(); 1106 1107 final long identity = Binder.clearCallingIdentity(); 1108 try { 1109 for (AssociationInfo info : mCompanionManager.getAllAssociations()) { 1110 if (info.getPackageName().equals(callingPackage) 1111 && !info.isSelfManaged() 1112 && info.getDeviceMacAddress() != null) { 1113 macAddresses.add(info.getDeviceMacAddress().toString()); 1114 } 1115 } 1116 } catch (SecurityException se) { 1117 // Not an app with associated devices 1118 } catch (Exception e) { 1119 Log.e(TAG, "Cannot check device associations for " + callingPackage, e); 1120 } finally { 1121 Binder.restoreCallingIdentity(identity); 1122 } 1123 return macAddresses; 1124 } 1125 1126 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) startScan( int scannerId, ScanSettings settings, List<ScanFilter> filters, AttributionSource attributionSource)1127 public void startScan( 1128 int scannerId, 1129 ScanSettings settings, 1130 List<ScanFilter> filters, 1131 AttributionSource attributionSource) { 1132 Log.d(TAG, "start scan with filters"); 1133 1134 if (!Utils.checkScanPermissionForDataDelivery( 1135 mContext, attributionSource, "Starting GATT scan.")) { 1136 return; 1137 } 1138 1139 enforcePrivilegedPermissionIfNeeded(settings); 1140 String callingPackage = attributionSource.getPackageName(); 1141 settings = enforceReportDelayFloor(settings); 1142 enforcePrivilegedPermissionIfNeeded(filters); 1143 final ScanClient scanClient = new ScanClient(scannerId, settings, filters); 1144 scanClient.userHandle = Binder.getCallingUserHandle(); 1145 mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); 1146 scanClient.eligibleForSanitizedExposureNotification = 1147 callingPackage.equals(mExposureNotificationPackage); 1148 1149 scanClient.hasDisavowedLocation = 1150 Utils.hasDisavowedLocationForScan( 1151 mContext, attributionSource, mTestModeAccessor.isTestModeEnabled()); 1152 1153 scanClient.isQApp = checkCallerTargetSdk(mContext, callingPackage, Build.VERSION_CODES.Q); 1154 if (!scanClient.hasDisavowedLocation) { 1155 if (scanClient.isQApp) { 1156 scanClient.hasLocationPermission = 1157 Utils.checkCallerHasFineLocation( 1158 mContext, attributionSource, scanClient.userHandle); 1159 } else { 1160 scanClient.hasLocationPermission = 1161 Utils.checkCallerHasCoarseOrFineLocation( 1162 mContext, attributionSource, scanClient.userHandle); 1163 } 1164 } 1165 scanClient.hasNetworkSettingsPermission = 1166 Utils.checkCallerHasNetworkSettingsPermission(mContext); 1167 scanClient.hasNetworkSetupWizardPermission = 1168 Utils.checkCallerHasNetworkSetupWizardPermission(mContext); 1169 scanClient.hasScanWithoutLocationPermission = 1170 Utils.checkCallerHasScanWithoutLocationPermission(mContext); 1171 scanClient.associatedDevices = getAssociatedDevices(callingPackage); 1172 1173 AppScanStats app = mScannerMap.getAppScanStatsById(scannerId); 1174 ContextMap.App cbApp = mScannerMap.getById(scannerId); 1175 if (app != null) { 1176 scanClient.stats = app; 1177 boolean isFilteredScan = (filters != null) && !filters.isEmpty(); 1178 boolean isCallbackScan = false; 1179 if (cbApp != null) { 1180 isCallbackScan = cbApp.callback != null; 1181 } 1182 app.recordScanStart(settings, filters, isFilteredScan, isCallbackScan, scannerId); 1183 } 1184 1185 mScanManager.startScan(scanClient); 1186 } 1187 1188 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) registerPiAndStartScan( PendingIntent pendingIntent, ScanSettings settings, List<ScanFilter> filters, AttributionSource attributionSource)1189 public void registerPiAndStartScan( 1190 PendingIntent pendingIntent, 1191 ScanSettings settings, 1192 List<ScanFilter> filters, 1193 AttributionSource attributionSource) { 1194 Log.d(TAG, "start scan with filters, for PendingIntent"); 1195 1196 if (!Utils.checkScanPermissionForDataDelivery( 1197 mContext, attributionSource, "Starting GATT scan.")) { 1198 return; 1199 } 1200 enforcePrivilegedPermissionIfNeeded(settings); 1201 settings = enforceReportDelayFloor(settings); 1202 enforcePrivilegedPermissionIfNeeded(filters); 1203 UUID uuid = UUID.randomUUID(); 1204 String callingPackage = attributionSource.getPackageName(); 1205 int callingUid = attributionSource.getUid(); 1206 PendingIntentInfo piInfo = new PendingIntentInfo(); 1207 piInfo.intent = pendingIntent; 1208 piInfo.settings = settings; 1209 piInfo.filters = filters; 1210 piInfo.callingPackage = callingPackage; 1211 piInfo.callingUid = callingUid; 1212 Log.d( 1213 TAG, 1214 "startScan(PI) -" 1215 + (" UUID=" + uuid) 1216 + (" Package=" + callingPackage) 1217 + (" UID=" + callingUid)); 1218 1219 // Don't start scan if the Pi scan already in mScannerMap. 1220 if (mScannerMap.getByContextInfo(piInfo) != null) { 1221 Log.d(TAG, "Don't startScan(PI) since the same Pi scan already in mScannerMap."); 1222 return; 1223 } 1224 1225 ContextMap.App app = mScannerMap.add(uuid, null, null, piInfo, mContext, this); 1226 1227 app.mUserHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid()); 1228 mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); 1229 app.mEligibleForSanitizedExposureNotification = 1230 callingPackage.equals(mExposureNotificationPackage); 1231 1232 app.mHasDisavowedLocation = 1233 Utils.hasDisavowedLocationForScan( 1234 mContext, attributionSource, mTestModeAccessor.isTestModeEnabled()); 1235 1236 if (!app.mHasDisavowedLocation) { 1237 try { 1238 if (checkCallerTargetSdk(mContext, callingPackage, Build.VERSION_CODES.Q)) { 1239 app.hasLocationPermission = 1240 Utils.checkCallerHasFineLocation( 1241 mContext, attributionSource, app.mUserHandle); 1242 } else { 1243 app.hasLocationPermission = 1244 Utils.checkCallerHasCoarseOrFineLocation( 1245 mContext, attributionSource, app.mUserHandle); 1246 } 1247 } catch (SecurityException se) { 1248 // No need to throw here. Just mark as not granted. 1249 app.hasLocationPermission = false; 1250 } 1251 } 1252 app.mHasNetworkSettingsPermission = Utils.checkCallerHasNetworkSettingsPermission(mContext); 1253 app.mHasNetworkSetupWizardPermission = 1254 Utils.checkCallerHasNetworkSetupWizardPermission(mContext); 1255 app.mHasScanWithoutLocationPermission = 1256 Utils.checkCallerHasScanWithoutLocationPermission(mContext); 1257 app.mAssociatedDevices = getAssociatedDevices(callingPackage); 1258 mScanManager.registerScanner(uuid); 1259 1260 // If this fails, we should stop the scan immediately. 1261 if (!pendingIntent.addCancelListener(Runnable::run, mScanIntentCancelListener)) { 1262 Log.d(TAG, "scanning PendingIntent is already cancelled, stopping scan."); 1263 stopScan(pendingIntent, attributionSource); 1264 } 1265 } 1266 continuePiStartScan( int scannerId, ContextMap<IScannerCallback, PendingIntentInfo>.App app)1267 public void continuePiStartScan( 1268 int scannerId, ContextMap<IScannerCallback, PendingIntentInfo>.App app) { 1269 final PendingIntentInfo piInfo = app.info; 1270 final ScanClient scanClient = 1271 new ScanClient(scannerId, piInfo.settings, piInfo.filters, piInfo.callingUid); 1272 scanClient.hasLocationPermission = app.hasLocationPermission; 1273 scanClient.userHandle = app.mUserHandle; 1274 scanClient.isQApp = checkCallerTargetSdk(mContext, app.name, Build.VERSION_CODES.Q); 1275 scanClient.eligibleForSanitizedExposureNotification = 1276 app.mEligibleForSanitizedExposureNotification; 1277 scanClient.hasNetworkSettingsPermission = app.mHasNetworkSettingsPermission; 1278 scanClient.hasNetworkSetupWizardPermission = app.mHasNetworkSetupWizardPermission; 1279 scanClient.hasScanWithoutLocationPermission = app.mHasScanWithoutLocationPermission; 1280 scanClient.associatedDevices = app.mAssociatedDevices; 1281 scanClient.hasDisavowedLocation = app.mHasDisavowedLocation; 1282 1283 AppScanStats scanStats = mScannerMap.getAppScanStatsById(scannerId); 1284 if (scanStats != null) { 1285 scanClient.stats = scanStats; 1286 boolean isFilteredScan = (piInfo.filters != null) && !piInfo.filters.isEmpty(); 1287 scanStats.recordScanStart( 1288 piInfo.settings, piInfo.filters, isFilteredScan, false, scannerId); 1289 } 1290 1291 mScanManager.startScan(scanClient); 1292 } 1293 1294 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) flushPendingBatchResults(int scannerId, AttributionSource attributionSource)1295 public void flushPendingBatchResults(int scannerId, AttributionSource attributionSource) { 1296 if (!Utils.checkScanPermissionForDataDelivery( 1297 mContext, attributionSource, "ScanHelper flushPendingBatchResults")) { 1298 return; 1299 } 1300 Log.d(TAG, "flushPendingBatchResults - scannerId=" + scannerId); 1301 mScanManager.flushBatchScanResults(new ScanClient(scannerId)); 1302 } 1303 1304 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) stopScan(int scannerId, AttributionSource attributionSource)1305 public void stopScan(int scannerId, AttributionSource attributionSource) { 1306 if (!Utils.checkScanPermissionForDataDelivery( 1307 mContext, attributionSource, "ScanHelper stopScan")) { 1308 return; 1309 } 1310 int scanQueueSize = 1311 mScanManager.getBatchScanQueue().size() + mScanManager.getRegularScanQueue().size(); 1312 Log.d(TAG, "stopScan() - queue size =" + scanQueueSize); 1313 1314 AppScanStats app = mScannerMap.getAppScanStatsById(scannerId); 1315 if (app != null) { 1316 app.recordScanStop(scannerId); 1317 } 1318 1319 mScanManager.stopScan(scannerId); 1320 } 1321 1322 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) stopScan(PendingIntent intent, AttributionSource attributionSource)1323 public void stopScan(PendingIntent intent, AttributionSource attributionSource) { 1324 if (!Utils.checkScanPermissionForDataDelivery( 1325 mContext, attributionSource, "ScanHelper stopScan")) { 1326 return; 1327 } 1328 PendingIntentInfo pii = new PendingIntentInfo(); 1329 pii.intent = intent; 1330 ContextMap.App app = mScannerMap.getByContextInfo(pii); 1331 Log.v(TAG, "stopScan(PendingIntent): app found = " + app); 1332 if (app != null) { 1333 intent.removeCancelListener(mScanIntentCancelListener); 1334 final int scannerId = app.id; 1335 stopScan(scannerId, attributionSource); 1336 // Also unregister the scanner 1337 unregisterScanner(scannerId, attributionSource); 1338 } 1339 } 1340 1341 /************************************************************************** 1342 * PERIODIC SCANNING 1343 *************************************************************************/ 1344 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) registerSync( ScanResult scanResult, int skip, int timeout, IPeriodicAdvertisingCallback callback, AttributionSource attributionSource)1345 public void registerSync( 1346 ScanResult scanResult, 1347 int skip, 1348 int timeout, 1349 IPeriodicAdvertisingCallback callback, 1350 AttributionSource attributionSource) { 1351 if (!Utils.checkScanPermissionForDataDelivery( 1352 mContext, attributionSource, "ScanHelper registerSync")) { 1353 return; 1354 } 1355 mPeriodicScanManager.startSync(scanResult, skip, timeout, callback); 1356 } 1357 1358 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) unregisterSync( IPeriodicAdvertisingCallback callback, AttributionSource attributionSource)1359 public void unregisterSync( 1360 IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) { 1361 if (!Utils.checkScanPermissionForDataDelivery( 1362 mContext, attributionSource, "ScanHelper unregisterSync")) { 1363 return; 1364 } 1365 mPeriodicScanManager.stopSync(callback); 1366 } 1367 transferSync( BluetoothDevice bda, int serviceData, int syncHandle, AttributionSource attributionSource)1368 public void transferSync( 1369 BluetoothDevice bda, 1370 int serviceData, 1371 int syncHandle, 1372 AttributionSource attributionSource) { 1373 if (!Utils.checkScanPermissionForDataDelivery( 1374 mContext, attributionSource, "ScanHelper transferSync")) { 1375 return; 1376 } 1377 mPeriodicScanManager.transferSync(bda, serviceData, syncHandle); 1378 } 1379 transferSetInfo( BluetoothDevice bda, int serviceData, int advHandle, IPeriodicAdvertisingCallback callback, AttributionSource attributionSource)1380 public void transferSetInfo( 1381 BluetoothDevice bda, 1382 int serviceData, 1383 int advHandle, 1384 IPeriodicAdvertisingCallback callback, 1385 AttributionSource attributionSource) { 1386 if (!Utils.checkScanPermissionForDataDelivery( 1387 mContext, attributionSource, "ScanHelper transferSetInfo")) { 1388 return; 1389 } 1390 mPeriodicScanManager.transferSetInfo(bda, serviceData, advHandle, callback); 1391 } 1392 1393 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) numHwTrackFiltersAvailable(AttributionSource attributionSource)1394 public int numHwTrackFiltersAvailable(AttributionSource attributionSource) { 1395 if (!Utils.checkConnectPermissionForDataDelivery( 1396 mContext, attributionSource, "ScanHelper numHwTrackFiltersAvailable")) { 1397 return 0; 1398 } 1399 return (AdapterService.getAdapterService().getTotalNumOfTrackableAdvertisements() 1400 - getCurrentUsedTrackingAdvertisement()); 1401 } 1402 1403 /** 1404 * DeathRecipient handler used to unregister applications that disconnect ungracefully (ie. 1405 * crash or forced close). 1406 */ 1407 class ScannerDeathRecipient implements IBinder.DeathRecipient { 1408 int mScannerId; 1409 private String mPackageName; 1410 ScannerDeathRecipient(int scannerId, String packageName)1411 ScannerDeathRecipient(int scannerId, String packageName) { 1412 mScannerId = scannerId; 1413 mPackageName = packageName; 1414 } 1415 1416 @Override binderDied()1417 public void binderDied() { 1418 Log.d( 1419 TAG, 1420 "Binder is dead - unregistering scanner (" 1421 + mPackageName 1422 + " " 1423 + mScannerId 1424 + ")!"); 1425 1426 ScanClient client = getScanClient(mScannerId); 1427 if (client != null) { 1428 if (Flags.leScanFixRemoteException()) { 1429 handleDeadScanClient(client); 1430 } else { 1431 client.appDied = true; 1432 stopScan(client.scannerId, mContext.getAttributionSource()); 1433 } 1434 } 1435 } 1436 getScanClient(int clientIf)1437 private ScanClient getScanClient(int clientIf) { 1438 for (ScanClient client : mScanManager.getRegularScanQueue()) { 1439 if (client.scannerId == clientIf) { 1440 return client; 1441 } 1442 } 1443 for (ScanClient client : mScanManager.getBatchScanQueue()) { 1444 if (client.scannerId == clientIf) { 1445 return client; 1446 } 1447 } 1448 return null; 1449 } 1450 } 1451 needsPrivilegedPermissionForScan(ScanSettings settings)1452 private boolean needsPrivilegedPermissionForScan(ScanSettings settings) { 1453 // BLE scan only mode needs special permission. 1454 if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) { 1455 return true; 1456 } 1457 1458 // Regular scan, no special permission. 1459 if (settings == null) { 1460 return false; 1461 } 1462 1463 // Ambient discovery mode, needs privileged permission. 1464 if (settings.getScanMode() == ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY) { 1465 return true; 1466 } 1467 1468 // Regular scan, no special permission. 1469 if (settings.getReportDelayMillis() == 0) { 1470 return false; 1471 } 1472 1473 // Batch scan, truncated mode needs permission. 1474 return settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_ABBREVIATED; 1475 } 1476 1477 /* 1478 * The {@link ScanFilter#setDeviceAddress} API overloads are @SystemApi access methods. This 1479 * requires that the permissions be BLUETOOTH_PRIVILEGED. 1480 */ 1481 @SuppressLint("AndroidFrameworkRequiresPermission") enforcePrivilegedPermissionIfNeeded(List<ScanFilter> filters)1482 private void enforcePrivilegedPermissionIfNeeded(List<ScanFilter> filters) { 1483 Log.d(TAG, "enforcePrivilegedPermissionIfNeeded(" + filters + ")"); 1484 // Some 3p API cases may have null filters, need to allow 1485 if (filters != null) { 1486 for (ScanFilter filter : filters) { 1487 // The only case to enforce here is if there is an address 1488 // If there is an address, enforce if the correct combination criteria is met. 1489 if (filter.getDeviceAddress() != null) { 1490 // At this point we have an address, that means a caller used the 1491 // setDeviceAddress(address) public API for the ScanFilter 1492 // We don't want to enforce if the type is PUBLIC and the IRK is null 1493 // However, if we have a different type that means the caller used a new 1494 // @SystemApi such as setDeviceAddress(address, type) or 1495 // setDeviceAddress(address, type, irk) which are both @SystemApi and require 1496 // permissions to be enforced 1497 if (filter.getAddressType() == BluetoothDevice.ADDRESS_TYPE_PUBLIC 1498 && filter.getIrk() == null) { 1499 // Do not enforce 1500 } else { 1501 enforceBluetoothPrivilegedPermission(mContext); 1502 } 1503 } 1504 } 1505 } 1506 } 1507 1508 @SuppressLint("AndroidFrameworkRequiresPermission") enforcePrivilegedPermissionIfNeeded(ScanSettings settings)1509 private void enforcePrivilegedPermissionIfNeeded(ScanSettings settings) { 1510 if (needsPrivilegedPermissionForScan(settings)) { 1511 enforceBluetoothPrivilegedPermission(mContext); 1512 } 1513 } 1514 1515 // Enforce caller has UPDATE_DEVICE_STATS permission, which allows the caller to blame other 1516 // apps for Bluetooth usage. A {@link SecurityException} will be thrown if the caller app does 1517 // not have UPDATE_DEVICE_STATS permission. 1518 @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) enforceImpersonatationPermission()1519 private void enforceImpersonatationPermission() { 1520 mContext.enforceCallingOrSelfPermission( 1521 android.Manifest.permission.UPDATE_DEVICE_STATS, 1522 "Need UPDATE_DEVICE_STATS permission"); 1523 } 1524 1525 @SuppressLint("AndroidFrameworkRequiresPermission") enforceImpersonatationPermissionIfNeeded(WorkSource workSource)1526 private void enforceImpersonatationPermissionIfNeeded(WorkSource workSource) { 1527 if (workSource != null) { 1528 enforceImpersonatationPermission(); 1529 } 1530 } 1531 1532 /** 1533 * Ensures the report delay is either 0 or at least the floor value (5000ms) 1534 * 1535 * @param settings are the scan settings passed into a request to start le scanning 1536 * @return the passed in ScanSettings object if the report delay is 0 or above the floor value; 1537 * a new ScanSettings object with the report delay being the floor value if the original 1538 * report delay was between 0 and the floor value (exclusive of both) 1539 */ 1540 @VisibleForTesting enforceReportDelayFloor(ScanSettings settings)1541 ScanSettings enforceReportDelayFloor(ScanSettings settings) { 1542 if (settings.getReportDelayMillis() == 0) { 1543 return settings; 1544 } 1545 1546 // Need to clear identity to pass device config permission check 1547 final long callerToken = Binder.clearCallingIdentity(); 1548 try { 1549 long floor = 1550 DeviceConfig.getLong( 1551 DeviceConfig.NAMESPACE_BLUETOOTH, 1552 "report_delay", 1553 DEFAULT_REPORT_DELAY_FLOOR); 1554 1555 if (settings.getReportDelayMillis() > floor) { 1556 return settings; 1557 } else { 1558 return new ScanSettings.Builder() 1559 .setCallbackType(settings.getCallbackType()) 1560 .setLegacy(settings.getLegacy()) 1561 .setMatchMode(settings.getMatchMode()) 1562 .setNumOfMatches(settings.getNumOfMatches()) 1563 .setPhy(settings.getPhy()) 1564 .setReportDelay(floor) 1565 .setScanMode(settings.getScanMode()) 1566 .setScanResultType(settings.getScanResultType()) 1567 .build(); 1568 } 1569 } finally { 1570 Binder.restoreCallingIdentity(callerToken); 1571 } 1572 } 1573 addScanEvent(BluetoothMetricsProto.ScanEvent event)1574 public void addScanEvent(BluetoothMetricsProto.ScanEvent event) { 1575 synchronized (mScanEvents) { 1576 if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT) { 1577 mScanEvents.remove(); 1578 } 1579 mScanEvents.add(event); 1580 } 1581 } 1582 dumpProto(BluetoothMetricsProto.BluetoothLog.Builder builder)1583 public void dumpProto(BluetoothMetricsProto.BluetoothLog.Builder builder) { 1584 synchronized (mScanEvents) { 1585 builder.addAllScanEvent(mScanEvents); 1586 } 1587 } 1588 } 1589