diff options
author | Jakub Pawlowski <jpawlowski@google.com> | 2017-01-16 07:21:01 -0800 |
---|---|---|
committer | Jakub Pawlowski <jpawlowski@google.com> | 2017-03-08 19:03:12 +0000 |
commit | b74a8cd801d63b94c1770299f02bcb1de5b3b8ea (patch) | |
tree | 8745214eb8b29f5616250fb0414acbaafe6161b0 /framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java | |
parent | dfeded5a05d2634c5b8984669b3bb2608f7eb1d3 (diff) |
Bluetooth 5 periodc scan API (1/2)
Bug: 30622771
Test: manual
Change-Id: I61853bc71f6013e9406d1d151bb51ea4484bb92c
(cherry picked from commit a48e03745becc96181c676dc3d194d0572f11c10)
Diffstat (limited to 'framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java')
-rw-r--r-- | framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java | 237 |
1 files changed, 237 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..12c8a8c8ff --- /dev/null +++ b/framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java @@ -0,0 +1,237 @@ +/* + * 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.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothManager; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * 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}. + * <p> + * <b>Note:</b> Most of the methods here require + * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + */ +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 IBluetoothManager mBluetoothManager; + private BluetoothAdapter mBluetoothAdapter; + + /* maps callback, to callback wrapper and sync handle */ + Map<PeriodicAdvertisingCallback, + IPeriodicAdvertisingCallback /* callbackWrapper */> callbackWrappers; + + /** + * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead. + * + * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management. + * @hide + */ + public PeriodicAdvertisingManager(IBluetoothManager bluetoothManager) { + mBluetoothManager = bluetoothManager; + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + callbackWrappers = 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. + */ + 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. + */ + 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); + callbackWrappers.put(callback, wrapped); + + try { + gatt.registerSync(scanResult, skip, timeout, wrapped); + } 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. + */ + 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 = callbackWrappers.remove(callback); + if (wrapper == null) { + throw new IllegalArgumentException("callback was not properly registered"); + } + + try { + gatt.unregisterSync(wrapper); + } catch (RemoteException e) { + Log.e(TAG, "Failed to cancel sync creation - ", e); + return; + } + } + + 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) { + + 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. + callbackWrappers.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. + callbackWrappers.remove(callback); + } + }); + } + }; + } +} |