diff options
author | TreeHugger Robot <treehugger-gerrit@google.com> | 2022-01-18 23:59:58 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2022-01-18 23:59:58 +0000 |
commit | d15d8266b900c549d2eed52c56f82d7c3c3792c8 (patch) | |
tree | 0b7c4c70453c091e24e37758ff1b1fd49bf20eb3 /framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java | |
parent | 7d69d903a5ee30dac4abc82e056f2b8ec0278708 (diff) | |
parent | aa6d8f71ae61c0e57b35258c00d28fcba5154f09 (diff) |
Merge changes from topic "migrate-sc-bt" into sc-v2-dev-plus-aosp
* changes:
Migrating BT files into packages/modules/Bluetooth
Merge history of Bluetooth API from frameworks/base
Diffstat (limited to 'framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java')
-rw-r--r-- | framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java b/framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java new file mode 100644 index 0000000000..bbd31170bb --- /dev/null +++ b/framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2017 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.RequiresPermission; +import android.annotation.SuppressLint; +import android.bluetooth.Attributable; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothManager; +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.util.Log; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Objects; + +/** + * This class provides methods to perform periodic advertising related + * operations. An application can register for periodic advertisements using + * {@link PeriodicAdvertisingManager#registerSync}. + * <p> + * Use {@link BluetoothAdapter#getPeriodicAdvertisingManager()} to get an + * instance of {@link PeriodicAdvertisingManager}. + * + * @hide + */ +public final class PeriodicAdvertisingManager { + + private static final String TAG = "PeriodicAdvertisingManager"; + + private static final int SKIP_MIN = 0; + private static final int SKIP_MAX = 499; + private static final int TIMEOUT_MIN = 10; + private static final int TIMEOUT_MAX = 16384; + + private static final int SYNC_STARTING = -1; + + private final BluetoothAdapter mBluetoothAdapter; + private final IBluetoothManager mBluetoothManager; + private final AttributionSource mAttributionSource; + + /* maps callback, to callback wrapper and sync handle */ + Map<PeriodicAdvertisingCallback, + IPeriodicAdvertisingCallback /* callbackWrapper */> mCallbackWrappers; + + /** + * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead. + * + * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management. + * @hide + */ + public PeriodicAdvertisingManager(BluetoothAdapter bluetoothAdapter) { + mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); + mBluetoothManager = mBluetoothAdapter.getBluetoothManager(); + mAttributionSource = mBluetoothAdapter.getAttributionSource(); + mCallbackWrappers = new IdentityHashMap<>(); + } + + /** + * Synchronize with periodic advertising pointed to by the {@code scanResult}. + * The {@code scanResult} used must contain a valid advertisingSid. First + * call to registerSync will use the {@code skip} and {@code timeout} provided. + * Subsequent calls from other apps, trying to sync with same set will reuse + * existing sync, thus {@code skip} and {@code timeout} values will not take + * effect. The values in effect will be returned in + * {@link PeriodicAdvertisingCallback#onSyncEstablished}. + * + * @param scanResult Scan result containing advertisingSid. + * @param skip The number of periodic advertising packets that can be skipped after a successful + * receive. Must be between 0 and 499. + * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must + * be between 10 (100ms) and 16384 (163.84s). + * @param callback Callback used to deliver all operations status. + * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or + * {@code timeout} is invalid or {@code callback} is null. + */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) + public void registerSync(ScanResult scanResult, int skip, int timeout, + PeriodicAdvertisingCallback callback) { + registerSync(scanResult, skip, timeout, callback, null); + } + + /** + * Synchronize with periodic advertising pointed to by the {@code scanResult}. + * The {@code scanResult} used must contain a valid advertisingSid. First + * call to registerSync will use the {@code skip} and {@code timeout} provided. + * Subsequent calls from other apps, trying to sync with same set will reuse + * existing sync, thus {@code skip} and {@code timeout} values will not take + * effect. The values in effect will be returned in + * {@link PeriodicAdvertisingCallback#onSyncEstablished}. + * + * @param scanResult Scan result containing advertisingSid. + * @param skip The number of periodic advertising packets that can be skipped after a successful + * receive. Must be between 0 and 499. + * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must + * be between 10 (100ms) and 16384 (163.84s). + * @param callback Callback used to deliver all operations status. + * @param handler thread upon which the callbacks will be invoked. + * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or + * {@code timeout} is invalid or {@code callback} is null. + */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) + public void registerSync(ScanResult scanResult, int skip, int timeout, + PeriodicAdvertisingCallback callback, Handler handler) { + if (callback == null) { + throw new IllegalArgumentException("callback can't be null"); + } + + if (scanResult == null) { + throw new IllegalArgumentException("scanResult can't be null"); + } + + if (scanResult.getAdvertisingSid() == ScanResult.SID_NOT_PRESENT) { + throw new IllegalArgumentException("scanResult must contain a valid sid"); + } + + if (skip < SKIP_MIN || skip > SKIP_MAX) { + throw new IllegalArgumentException( + "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX); + } + + if (timeout < TIMEOUT_MIN || timeout > TIMEOUT_MAX) { + throw new IllegalArgumentException( + "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX); + } + + IBluetoothGatt gatt; + try { + gatt = mBluetoothManager.getBluetoothGatt(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to get Bluetooth gatt - ", e); + callback.onSyncEstablished(0, scanResult.getDevice(), scanResult.getAdvertisingSid(), + skip, timeout, + PeriodicAdvertisingCallback.SYNC_NO_RESOURCES); + return; + } + + if (handler == null) { + handler = new Handler(Looper.getMainLooper()); + } + + IPeriodicAdvertisingCallback wrapped = wrap(callback, handler); + mCallbackWrappers.put(callback, wrapped); + + try { + gatt.registerSync( + scanResult, skip, timeout, wrapped, mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register sync - ", e); + return; + } + } + + /** + * Cancel pending attempt to create sync, or terminate existing sync. + * + * @param callback Callback used to deliver all operations status. + * @throws IllegalArgumentException if {@code callback} is null, or not a properly registered + * callback. + */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) + public void unregisterSync(PeriodicAdvertisingCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback can't be null"); + } + + IBluetoothGatt gatt; + try { + gatt = mBluetoothManager.getBluetoothGatt(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to get Bluetooth gatt - ", e); + return; + } + + IPeriodicAdvertisingCallback wrapper = mCallbackWrappers.remove(callback); + if (wrapper == null) { + throw new IllegalArgumentException("callback was not properly registered"); + } + + try { + gatt.unregisterSync(wrapper, mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, "Failed to cancel sync creation - ", e); + return; + } + } + + @SuppressLint("AndroidFrameworkBluetoothPermission") + private IPeriodicAdvertisingCallback wrap(PeriodicAdvertisingCallback callback, + Handler handler) { + return new IPeriodicAdvertisingCallback.Stub() { + public void onSyncEstablished(int syncHandle, BluetoothDevice device, + int advertisingSid, int skip, int timeout, int status) { + Attributable.setAttributionSource(device, mAttributionSource); + handler.post(new Runnable() { + @Override + public void run() { + callback.onSyncEstablished(syncHandle, device, advertisingSid, skip, + timeout, + status); + + if (status != PeriodicAdvertisingCallback.SYNC_SUCCESS) { + // App can still unregister the sync until notified it failed. Remove + // callback + // after app was notifed. + mCallbackWrappers.remove(callback); + } + } + }); + } + + public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) { + handler.post(new Runnable() { + @Override + public void run() { + callback.onPeriodicAdvertisingReport(report); + } + }); + } + + public void onSyncLost(int syncHandle) { + handler.post(new Runnable() { + @Override + public void run() { + callback.onSyncLost(syncHandle); + // App can still unregister the sync until notified it's lost. + // Remove callback after app was notifed. + mCallbackWrappers.remove(callback); + } + }); + } + }; + } +} |