732 lines
30 KiB
Java
732 lines
30 KiB
Java
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
* use this file except in compliance with the License. You may obtain a copy of
|
|
* the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations under
|
|
* the License.
|
|
*/
|
|
|
|
package android.bluetooth.le;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.annotation.RequiresNoPermission;
|
|
import android.annotation.RequiresPermission;
|
|
import android.annotation.SuppressLint;
|
|
import android.annotation.SystemApi;
|
|
import android.app.PendingIntent;
|
|
import android.bluetooth.Attributable;
|
|
import android.bluetooth.BluetoothAdapter;
|
|
import android.bluetooth.BluetoothGatt;
|
|
import android.bluetooth.IBluetoothGatt;
|
|
import android.bluetooth.IBluetoothScan;
|
|
import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
|
|
import android.bluetooth.annotations.RequiresBluetoothScanPermission;
|
|
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
|
|
import android.content.AttributionSource;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.os.RemoteException;
|
|
import android.os.WorkSource;
|
|
import android.util.Log;
|
|
|
|
import com.android.bluetooth.flags.Flags;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
|
|
/**
|
|
* This class provides methods to perform scan related operations for Bluetooth LE devices. An
|
|
* application can scan for a particular type of Bluetooth LE devices using {@link ScanFilter}. It
|
|
* can also request different types of callbacks for delivering the result.
|
|
*
|
|
* <p>Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of {@link
|
|
* BluetoothLeScanner}.
|
|
*
|
|
* @see ScanFilter
|
|
*/
|
|
public final class BluetoothLeScanner {
|
|
|
|
private static final String TAG = "BluetoothLeScanner";
|
|
private static final boolean DBG = true;
|
|
private static final boolean VDBG = false;
|
|
|
|
/**
|
|
* Extra containing a list of ScanResults. It can have one or more results if there was no
|
|
* error. In case of error, {@link #EXTRA_ERROR_CODE} will contain the error code and this extra
|
|
* will not be available.
|
|
*/
|
|
public static final String EXTRA_LIST_SCAN_RESULT =
|
|
"android.bluetooth.le.extra.LIST_SCAN_RESULT";
|
|
|
|
/**
|
|
* Optional extra indicating the error code, if any. The error code will be one of the
|
|
* SCAN_FAILED_* codes in {@link ScanCallback}.
|
|
*/
|
|
public static final String EXTRA_ERROR_CODE = "android.bluetooth.le.extra.ERROR_CODE";
|
|
|
|
/**
|
|
* Optional extra indicating the callback type, which will be one of CALLBACK_TYPE_* constants
|
|
* in {@link ScanSettings}.
|
|
*
|
|
* @see ScanCallback#onScanResult(int, ScanResult)
|
|
*/
|
|
public static final String EXTRA_CALLBACK_TYPE = "android.bluetooth.le.extra.CALLBACK_TYPE";
|
|
|
|
private final BluetoothAdapter mBluetoothAdapter;
|
|
private final AttributionSource mAttributionSource;
|
|
|
|
private final Handler mHandler;
|
|
private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients;
|
|
|
|
/**
|
|
* Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
|
|
*
|
|
* @hide
|
|
*/
|
|
public BluetoothLeScanner(BluetoothAdapter bluetoothAdapter) {
|
|
mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
|
|
mAttributionSource = mBluetoothAdapter.getAttributionSource();
|
|
mHandler = new Handler(Looper.getMainLooper());
|
|
mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>();
|
|
}
|
|
|
|
/**
|
|
* Start Bluetooth LE scan with default parameters and no filters. The scan results will be
|
|
* delivered through {@code callback}. For unfiltered scans, scanning is stopped on screen off
|
|
* to save power. Scanning is resumed when screen is turned on again. To avoid this, use {@link
|
|
* #startScan(List, ScanSettings, ScanCallback)} with desired {@link ScanFilter}.
|
|
*
|
|
* <p>An app must have {@link android.Manifest.permission#ACCESS_COARSE_LOCATION
|
|
* ACCESS_COARSE_LOCATION} permission in order to get results. An App targeting Android Q or
|
|
* later must have {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}
|
|
* permission in order to get results.
|
|
*
|
|
* @param callback Callback used to deliver scan results.
|
|
* @throws IllegalArgumentException If {@code callback} is null.
|
|
*/
|
|
@RequiresLegacyBluetoothAdminPermission
|
|
@RequiresBluetoothScanPermission
|
|
@RequiresBluetoothLocationPermission
|
|
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
|
public void startScan(final ScanCallback callback) {
|
|
startScan(null, new ScanSettings.Builder().build(), callback);
|
|
}
|
|
|
|
/**
|
|
* Start Bluetooth LE scan. The scan results will be delivered through {@code callback}. For
|
|
* unfiltered scans, scanning is stopped on screen off to save power. Scanning is resumed when
|
|
* screen is turned on again. To avoid this, do filtered scanning by using proper {@link
|
|
* ScanFilter}.
|
|
*
|
|
* <p>An app must have {@link android.Manifest.permission#ACCESS_COARSE_LOCATION
|
|
* ACCESS_COARSE_LOCATION} permission in order to get results. An App targeting Android Q or
|
|
* later must have {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}
|
|
* permission in order to get results.
|
|
*
|
|
* @param filters {@link ScanFilter}s for finding exact BLE devices.
|
|
* @param settings Settings for the scan.
|
|
* @param callback Callback used to deliver scan results.
|
|
* @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
|
|
*/
|
|
@RequiresLegacyBluetoothAdminPermission
|
|
@RequiresBluetoothScanPermission
|
|
@RequiresBluetoothLocationPermission
|
|
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
|
public void startScan(
|
|
List<ScanFilter> filters, ScanSettings settings, final ScanCallback callback) {
|
|
startScan(filters, settings, null, callback, /* callbackIntent= */ null);
|
|
}
|
|
|
|
/**
|
|
* Start Bluetooth LE scan using a {@link PendingIntent}. The scan results will be delivered via
|
|
* the PendingIntent. Use this method of scanning if your process is not always running and it
|
|
* should be started when scan results are available.
|
|
*
|
|
* <p>An app must have {@link android.Manifest.permission#ACCESS_COARSE_LOCATION
|
|
* ACCESS_COARSE_LOCATION} permission in order to get results. An App targeting Android Q or
|
|
* later must have {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}
|
|
* permission in order to get results.
|
|
*
|
|
* <p>When the PendingIntent is delivered, the Intent passed to the receiver or activity will
|
|
* contain one or more of the extras {@link #EXTRA_CALLBACK_TYPE}, {@link #EXTRA_ERROR_CODE} and
|
|
* {@link #EXTRA_LIST_SCAN_RESULT} to indicate the result of the scan.
|
|
*
|
|
* @param filters Optional list of ScanFilters for finding exact BLE devices.
|
|
* @param settings Optional settings for the scan.
|
|
* @param callbackIntent The PendingIntent to deliver the result to.
|
|
* @return Returns 0 for success or an error code from {@link ScanCallback} if the scan request
|
|
* could not be sent.
|
|
* @see #stopScan(PendingIntent)
|
|
*/
|
|
@RequiresLegacyBluetoothAdminPermission
|
|
@RequiresBluetoothScanPermission
|
|
@RequiresBluetoothLocationPermission
|
|
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
|
public int startScan(
|
|
@Nullable List<ScanFilter> filters,
|
|
@Nullable ScanSettings settings,
|
|
@NonNull PendingIntent callbackIntent) {
|
|
return startScan(
|
|
filters,
|
|
settings != null ? settings : new ScanSettings.Builder().build(),
|
|
null,
|
|
null,
|
|
callbackIntent);
|
|
}
|
|
|
|
/**
|
|
* Start Bluetooth LE scan. Same as {@link #startScan(ScanCallback)} but allows the caller to
|
|
* specify on behalf of which application(s) the work is being done.
|
|
*
|
|
* @param workSource {@link WorkSource} identifying the application(s) for which to blame for
|
|
* the scan.
|
|
* @param callback Callback used to deliver scan results.
|
|
* @hide
|
|
*/
|
|
@SystemApi
|
|
@RequiresLegacyBluetoothAdminPermission
|
|
@RequiresBluetoothScanPermission
|
|
@RequiresBluetoothLocationPermission
|
|
@RequiresPermission(
|
|
allOf = {
|
|
android.Manifest.permission.BLUETOOTH_SCAN,
|
|
android.Manifest.permission.UPDATE_DEVICE_STATS
|
|
})
|
|
public void startScanFromSource(final WorkSource workSource, final ScanCallback callback) {
|
|
startScanFromSource(null, new ScanSettings.Builder().build(), workSource, callback);
|
|
}
|
|
|
|
/**
|
|
* Start Bluetooth LE scan. Same as {@link #startScan(List, ScanSettings, ScanCallback)} but
|
|
* allows the caller to specify on behalf of which application(s) the work is being done.
|
|
*
|
|
* @param filters {@link ScanFilter}s for finding exact BLE devices.
|
|
* @param settings Settings for the scan.
|
|
* @param workSource {@link WorkSource} identifying the application(s) for which to blame for
|
|
* the scan.
|
|
* @param callback Callback used to deliver scan results.
|
|
* @hide
|
|
*/
|
|
@SystemApi
|
|
@RequiresLegacyBluetoothAdminPermission
|
|
@RequiresBluetoothScanPermission
|
|
@RequiresBluetoothLocationPermission
|
|
@RequiresPermission(
|
|
allOf = {
|
|
android.Manifest.permission.BLUETOOTH_SCAN,
|
|
android.Manifest.permission.UPDATE_DEVICE_STATS
|
|
})
|
|
@SuppressLint("AndroidFrameworkRequiresPermission")
|
|
public void startScanFromSource(
|
|
List<ScanFilter> filters,
|
|
ScanSettings settings,
|
|
final WorkSource workSource,
|
|
final ScanCallback callback) {
|
|
startScan(filters, settings, workSource, callback, null);
|
|
}
|
|
|
|
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
|
private int startScan(
|
|
List<ScanFilter> filters,
|
|
ScanSettings settings,
|
|
final WorkSource workSource,
|
|
final ScanCallback callback,
|
|
final PendingIntent callbackIntent) {
|
|
BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
|
|
if (callback == null && callbackIntent == null) {
|
|
throw new IllegalArgumentException("callback is null");
|
|
}
|
|
if (settings == null) {
|
|
throw new IllegalArgumentException("settings is null");
|
|
}
|
|
synchronized (mLeScanClients) {
|
|
if (callback != null && mLeScanClients.containsKey(callback)) {
|
|
return postCallbackErrorOrReturn(
|
|
callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED);
|
|
}
|
|
IBluetoothScan scan = null;
|
|
IBluetoothGatt gatt = null;
|
|
if (Flags.scanManagerRefactor()) {
|
|
scan = mBluetoothAdapter.getBluetoothScan();
|
|
if (scan == null) {
|
|
return postCallbackErrorOrReturn(
|
|
callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
|
|
}
|
|
} else {
|
|
gatt = mBluetoothAdapter.getBluetoothGatt();
|
|
if (gatt == null) {
|
|
return postCallbackErrorOrReturn(
|
|
callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
|
|
}
|
|
}
|
|
if (!isSettingsConfigAllowedForScan(settings)) {
|
|
return postCallbackErrorOrReturn(
|
|
callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
|
|
}
|
|
if (!isHardwareResourcesAvailableForScan(settings)) {
|
|
return postCallbackErrorOrReturn(
|
|
callback, ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES);
|
|
}
|
|
if (!isSettingsAndFilterComboAllowed(settings, filters)) {
|
|
return postCallbackErrorOrReturn(
|
|
callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
|
|
}
|
|
if (callback != null) {
|
|
BleScanCallbackWrapper wrapper =
|
|
new BleScanCallbackWrapper(
|
|
gatt, scan, filters, settings, workSource, callback);
|
|
wrapper.startRegistration();
|
|
} else {
|
|
try {
|
|
if (Flags.scanManagerRefactor()) {
|
|
scan.startScanForIntent(
|
|
callbackIntent, settings, filters, mAttributionSource);
|
|
} else {
|
|
gatt.startScanForIntent(
|
|
callbackIntent, settings, filters, mAttributionSource);
|
|
}
|
|
} catch (RemoteException e) {
|
|
return ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
}
|
|
return ScanCallback.NO_ERROR;
|
|
}
|
|
|
|
/** Stops an ongoing Bluetooth LE scan. */
|
|
@RequiresLegacyBluetoothAdminPermission
|
|
@RequiresBluetoothScanPermission
|
|
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
|
public void stopScan(ScanCallback callback) {
|
|
BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
|
|
synchronized (mLeScanClients) {
|
|
BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback);
|
|
if (wrapper == null) {
|
|
if (DBG) Log.d(TAG, "could not find callback wrapper");
|
|
return;
|
|
}
|
|
wrapper.stopLeScan();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops an ongoing Bluetooth LE scan started using a PendingIntent. When creating the
|
|
* PendingIntent parameter, please do not use the FLAG_CANCEL_CURRENT flag. Otherwise, the stop
|
|
* scan may have no effect.
|
|
*
|
|
* @param callbackIntent The PendingIntent that was used to start the scan.
|
|
* @see #startScan(List, ScanSettings, PendingIntent)
|
|
*/
|
|
@RequiresLegacyBluetoothAdminPermission
|
|
@RequiresBluetoothScanPermission
|
|
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
|
public void stopScan(PendingIntent callbackIntent) {
|
|
BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
|
|
try {
|
|
if (Flags.scanManagerRefactor()) {
|
|
IBluetoothScan scan = mBluetoothAdapter.getBluetoothScan();
|
|
if (scan == null) {
|
|
Log.w(TAG, "stopScan called after bluetooth has been turned off");
|
|
return;
|
|
}
|
|
scan.stopScanForIntent(callbackIntent, mAttributionSource);
|
|
} else {
|
|
IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt();
|
|
if (gatt == null) {
|
|
Log.w(TAG, "stopScan called after bluetooth has been turned off");
|
|
return;
|
|
}
|
|
gatt.stopScanForIntent(callbackIntent, mAttributionSource);
|
|
}
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Failed to stop scan", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth
|
|
* LE scan results batched on bluetooth controller. Returns immediately, batch scan results data
|
|
* will be delivered through the {@code callback}.
|
|
*
|
|
* @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
|
|
* used to start scan.
|
|
*/
|
|
@RequiresLegacyBluetoothAdminPermission
|
|
@RequiresBluetoothScanPermission
|
|
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
|
public void flushPendingScanResults(ScanCallback callback) {
|
|
BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
|
|
if (callback == null) {
|
|
throw new IllegalArgumentException("callback cannot be null!");
|
|
}
|
|
synchronized (mLeScanClients) {
|
|
BleScanCallbackWrapper wrapper = mLeScanClients.get(callback);
|
|
if (wrapper == null) {
|
|
return;
|
|
}
|
|
wrapper.flushPendingBatchResults();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start truncated scan.
|
|
*
|
|
* @deprecated this is not used anywhere
|
|
* @hide
|
|
*/
|
|
@Deprecated
|
|
@SystemApi
|
|
@RequiresBluetoothScanPermission
|
|
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
|
public void startTruncatedScan(
|
|
List<TruncatedFilter> truncatedFilters,
|
|
ScanSettings settings,
|
|
final ScanCallback callback) {
|
|
int filterSize = truncatedFilters.size();
|
|
List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize);
|
|
for (TruncatedFilter filter : truncatedFilters) {
|
|
scanFilters.add(filter.getFilter());
|
|
}
|
|
startScan(scanFilters, settings, null, callback, null);
|
|
}
|
|
|
|
/**
|
|
* Cleans up scan clients. Should be called when bluetooth is down.
|
|
*
|
|
* @hide
|
|
*/
|
|
@RequiresNoPermission
|
|
public void cleanup() {
|
|
mLeScanClients.clear();
|
|
}
|
|
|
|
/** Bluetooth GATT interface callbacks */
|
|
@SuppressLint("AndroidFrameworkRequiresPermission")
|
|
private class BleScanCallbackWrapper extends IScannerCallback.Stub {
|
|
private static final int REGISTRATION_CALLBACK_TIMEOUT_MILLIS = 2000;
|
|
|
|
private final ScanCallback mScanCallback;
|
|
private final List<ScanFilter> mFilters;
|
|
private final WorkSource mWorkSource;
|
|
private ScanSettings mSettings;
|
|
private IBluetoothGatt mBluetoothGatt;
|
|
private IBluetoothScan mBluetoothScan;
|
|
|
|
// mLeHandle 0: not registered
|
|
// -2: registration failed because app is scanning to frequently
|
|
// -1: scan stopped or registration failed
|
|
// > 0: registered and scan started
|
|
private int mScannerId;
|
|
|
|
public BleScanCallbackWrapper(
|
|
IBluetoothGatt bluetoothGatt,
|
|
IBluetoothScan bluetoothScan,
|
|
List<ScanFilter> filters,
|
|
ScanSettings settings,
|
|
WorkSource workSource,
|
|
ScanCallback scanCallback) {
|
|
mBluetoothGatt = bluetoothGatt;
|
|
mBluetoothScan = bluetoothScan;
|
|
mFilters = filters;
|
|
mSettings = settings;
|
|
mWorkSource = workSource;
|
|
mScanCallback = scanCallback;
|
|
mScannerId = 0;
|
|
}
|
|
|
|
@SuppressWarnings("WaitNotInLoop") // TODO(b/314811467)
|
|
public void startRegistration() {
|
|
synchronized (this) {
|
|
// Scan stopped.
|
|
if (mScannerId == -1 || mScannerId == -2) return;
|
|
try {
|
|
if (Flags.scanManagerRefactor()) {
|
|
mBluetoothScan.registerScanner(this, mWorkSource, mAttributionSource);
|
|
} else {
|
|
mBluetoothGatt.registerScanner(this, mWorkSource, mAttributionSource);
|
|
}
|
|
wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS);
|
|
} catch (InterruptedException | RemoteException e) {
|
|
Log.e(TAG, "application registration exception", e);
|
|
postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
|
|
}
|
|
if (mScannerId > 0) {
|
|
mLeScanClients.put(mScanCallback, this);
|
|
} else {
|
|
// Registration timed out or got exception, reset scannerId to -1 so no
|
|
// subsequent operations can proceed.
|
|
if (mScannerId == 0) mScannerId = -1;
|
|
|
|
// If scanning too frequently, don't report anything to the app.
|
|
if (mScannerId == -2) {
|
|
Log.e(TAG, "registration failed because app is scanning too frequently");
|
|
return;
|
|
}
|
|
|
|
postCallbackError(
|
|
mScanCallback,
|
|
ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED);
|
|
}
|
|
}
|
|
}
|
|
|
|
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
|
public void stopLeScan() {
|
|
synchronized (this) {
|
|
if (mScannerId <= 0) {
|
|
Log.e(TAG, "Error state, mLeHandle: " + mScannerId);
|
|
return;
|
|
}
|
|
try {
|
|
if (Flags.scanManagerRefactor()) {
|
|
mBluetoothScan.stopScan(mScannerId, mAttributionSource);
|
|
|
|
mBluetoothScan.unregisterScanner(mScannerId, mAttributionSource);
|
|
} else {
|
|
mBluetoothGatt.stopScan(mScannerId, mAttributionSource);
|
|
|
|
mBluetoothGatt.unregisterScanner(mScannerId, mAttributionSource);
|
|
}
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Failed to stop scan and unregister", e);
|
|
}
|
|
mScannerId = -1;
|
|
}
|
|
}
|
|
|
|
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
|
void flushPendingBatchResults() {
|
|
synchronized (this) {
|
|
if (mScannerId <= 0) {
|
|
Log.e(TAG, "Error state, mLeHandle: " + mScannerId);
|
|
return;
|
|
}
|
|
try {
|
|
if (Flags.scanManagerRefactor()) {
|
|
mBluetoothScan.flushPendingBatchResults(mScannerId, mAttributionSource);
|
|
} else {
|
|
mBluetoothGatt.flushPendingBatchResults(mScannerId, mAttributionSource);
|
|
}
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Failed to get pending scan results", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Application interface registered - app is ready to go */
|
|
@Override
|
|
public void onScannerRegistered(int status, int scannerId) {
|
|
Log.d(
|
|
TAG,
|
|
"onScannerRegistered() - status="
|
|
+ status
|
|
+ " scannerId="
|
|
+ scannerId
|
|
+ " mScannerId="
|
|
+ mScannerId);
|
|
synchronized (this) {
|
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
|
try {
|
|
if (mScannerId == -1) {
|
|
// Registration succeeds after timeout, unregister scanner.
|
|
if (Flags.scanManagerRefactor()) {
|
|
mBluetoothScan.unregisterScanner(scannerId, mAttributionSource);
|
|
} else {
|
|
mBluetoothGatt.unregisterScanner(scannerId, mAttributionSource);
|
|
}
|
|
} else {
|
|
mScannerId = scannerId;
|
|
if (Flags.scanManagerRefactor()) {
|
|
mBluetoothScan.startScan(
|
|
mScannerId, mSettings, mFilters, mAttributionSource);
|
|
} else {
|
|
mBluetoothGatt.startScan(
|
|
mScannerId, mSettings, mFilters, mAttributionSource);
|
|
}
|
|
}
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "fail to start le scan: " + e);
|
|
mScannerId = -1;
|
|
}
|
|
} else if (status == ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY) {
|
|
// application was scanning too frequently
|
|
mScannerId = -2;
|
|
} else {
|
|
// registration failed
|
|
mScannerId = -1;
|
|
}
|
|
notifyAll();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Callback reporting an LE scan result.
|
|
*
|
|
* @hide
|
|
*/
|
|
@Override
|
|
public void onScanResult(final ScanResult scanResult) {
|
|
Attributable.setAttributionSource(scanResult, mAttributionSource);
|
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
|
Log.d(TAG, "onScanResult() - mScannerId=" + mScannerId);
|
|
}
|
|
if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString());
|
|
|
|
// Check null in case the scan has been stopped
|
|
synchronized (this) {
|
|
if (mScannerId <= 0) {
|
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
|
Log.d(TAG, "Ignoring result as scan stopped.");
|
|
}
|
|
return;
|
|
}
|
|
;
|
|
}
|
|
Handler handler = new Handler(Looper.getMainLooper());
|
|
handler.post(
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
|
Log.d(TAG, "onScanResult() - handler run");
|
|
}
|
|
mScanCallback.onScanResult(
|
|
ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onBatchScanResults(final List<ScanResult> results) {
|
|
Attributable.setAttributionSource(results, mAttributionSource);
|
|
Handler handler = new Handler(Looper.getMainLooper());
|
|
handler.post(
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
mScanCallback.onBatchScanResults(results);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) {
|
|
Attributable.setAttributionSource(scanResult, mAttributionSource);
|
|
if (VDBG) {
|
|
Log.d(TAG, "onFoundOrLost() - onFound = " + onFound + " " + scanResult.toString());
|
|
}
|
|
|
|
// Check null in case the scan has been stopped
|
|
synchronized (this) {
|
|
if (mScannerId <= 0) {
|
|
return;
|
|
}
|
|
}
|
|
Handler handler = new Handler(Looper.getMainLooper());
|
|
handler.post(
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (onFound) {
|
|
mScanCallback.onScanResult(
|
|
ScanSettings.CALLBACK_TYPE_FIRST_MATCH, scanResult);
|
|
} else {
|
|
mScanCallback.onScanResult(
|
|
ScanSettings.CALLBACK_TYPE_MATCH_LOST, scanResult);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onScanManagerErrorCallback(final int errorCode) {
|
|
if (VDBG) {
|
|
Log.d(TAG, "onScanManagerErrorCallback() - errorCode = " + errorCode);
|
|
}
|
|
synchronized (this) {
|
|
if (mScannerId <= 0) {
|
|
return;
|
|
}
|
|
}
|
|
postCallbackError(mScanCallback, errorCode);
|
|
}
|
|
}
|
|
|
|
private int postCallbackErrorOrReturn(final ScanCallback callback, final int errorCode) {
|
|
if (callback == null) {
|
|
return errorCode;
|
|
} else {
|
|
postCallbackError(callback, errorCode);
|
|
return ScanCallback.NO_ERROR;
|
|
}
|
|
}
|
|
|
|
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
|
private void postCallbackError(final ScanCallback callback, final int errorCode) {
|
|
mHandler.post(
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
callback.onScanFailed(errorCode);
|
|
}
|
|
});
|
|
}
|
|
|
|
private boolean isSettingsConfigAllowedForScan(ScanSettings settings) {
|
|
if (mBluetoothAdapter.isOffloadedFilteringSupported()) {
|
|
return true;
|
|
}
|
|
final int callbackType = settings.getCallbackType();
|
|
// Only support regular scan if no offloaded filter support.
|
|
if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
|
|
&& settings.getReportDelayMillis() == 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean isSettingsAndFilterComboAllowed(
|
|
ScanSettings settings, List<ScanFilter> filterList) {
|
|
final int callbackType = settings.getCallbackType();
|
|
// If onlost/onfound is requested, a non-empty filter is expected
|
|
if ((callbackType
|
|
& (ScanSettings.CALLBACK_TYPE_FIRST_MATCH
|
|
| ScanSettings.CALLBACK_TYPE_MATCH_LOST))
|
|
!= 0) {
|
|
if (filterList == null) {
|
|
return false;
|
|
}
|
|
for (ScanFilter filter : filterList) {
|
|
if (filter.isAllFieldsEmpty()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
|
private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) {
|
|
final int callbackType = settings.getCallbackType();
|
|
if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0
|
|
|| (callbackType & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) {
|
|
// For onlost/onfound, we required hw support be available
|
|
return (mBluetoothAdapter.isOffloadedFilteringSupported()
|
|
&& mBluetoothAdapter.isHardwareTrackingFiltersAvailable());
|
|
}
|
|
return true;
|
|
}
|
|
}
|