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