Home | History | Annotate | Download | only in le
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package android.bluetooth.le;
     18 
     19 import android.bluetooth.BluetoothAdapter;
     20 import android.bluetooth.BluetoothDevice;
     21 import android.bluetooth.IBluetoothGatt;
     22 import android.bluetooth.IBluetoothManager;
     23 import android.os.Handler;
     24 import android.os.Looper;
     25 import android.os.RemoteException;
     26 import android.util.Log;
     27 
     28 import java.util.IdentityHashMap;
     29 import java.util.Map;
     30 
     31 /**
     32  * This class provides methods to perform periodic advertising related
     33  * operations. An application can register for periodic advertisements using
     34  * {@link PeriodicAdvertisingManager#registerSync}.
     35  * <p>
     36  * Use {@link BluetoothAdapter#getPeriodicAdvertisingManager()} to get an
     37  * instance of {@link PeriodicAdvertisingManager}.
     38  * <p>
     39  * <b>Note:</b> Most of the methods here require
     40  * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
     41  *
     42  * @hide
     43  */
     44 public final class PeriodicAdvertisingManager {
     45 
     46     private static final String TAG = "PeriodicAdvertisingManager";
     47 
     48     private static final int SKIP_MIN = 0;
     49     private static final int SKIP_MAX = 499;
     50     private static final int TIMEOUT_MIN = 10;
     51     private static final int TIMEOUT_MAX = 16384;
     52 
     53     private static final int SYNC_STARTING = -1;
     54 
     55     private final IBluetoothManager mBluetoothManager;
     56     private BluetoothAdapter mBluetoothAdapter;
     57 
     58     /* maps callback, to callback wrapper and sync handle */
     59     Map<PeriodicAdvertisingCallback,
     60             IPeriodicAdvertisingCallback /* callbackWrapper */> mCallbackWrappers;
     61 
     62     /**
     63      * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
     64      *
     65      * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management.
     66      * @hide
     67      */
     68     public PeriodicAdvertisingManager(IBluetoothManager bluetoothManager) {
     69         mBluetoothManager = bluetoothManager;
     70         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     71         mCallbackWrappers = new IdentityHashMap<>();
     72     }
     73 
     74     /**
     75      * Synchronize with periodic advertising pointed to by the {@code scanResult}.
     76      * The {@code scanResult} used must contain a valid advertisingSid. First
     77      * call to registerSync will use the {@code skip} and {@code timeout} provided.
     78      * Subsequent calls from other apps, trying to sync with same set will reuse
     79      * existing sync, thus {@code skip} and {@code timeout} values will not take
     80      * effect. The values in effect will be returned in
     81      * {@link PeriodicAdvertisingCallback#onSyncEstablished}.
     82      *
     83      * @param scanResult Scan result containing advertisingSid.
     84      * @param skip The number of periodic advertising packets that can be skipped after a successful
     85      * receive. Must be between 0 and 499.
     86      * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must
     87      * be between 10 (100ms) and 16384 (163.84s).
     88      * @param callback Callback used to deliver all operations status.
     89      * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or
     90      * {@code timeout} is invalid or {@code callback} is null.
     91      */
     92     public void registerSync(ScanResult scanResult, int skip, int timeout,
     93             PeriodicAdvertisingCallback callback) {
     94         registerSync(scanResult, skip, timeout, callback, null);
     95     }
     96 
     97     /**
     98      * Synchronize with periodic advertising pointed to by the {@code scanResult}.
     99      * The {@code scanResult} used must contain a valid advertisingSid. First
    100      * call to registerSync will use the {@code skip} and {@code timeout} provided.
    101      * Subsequent calls from other apps, trying to sync with same set will reuse
    102      * existing sync, thus {@code skip} and {@code timeout} values will not take
    103      * effect. The values in effect will be returned in
    104      * {@link PeriodicAdvertisingCallback#onSyncEstablished}.
    105      *
    106      * @param scanResult Scan result containing advertisingSid.
    107      * @param skip The number of periodic advertising packets that can be skipped after a successful
    108      * receive. Must be between 0 and 499.
    109      * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must
    110      * be between 10 (100ms) and 16384 (163.84s).
    111      * @param callback Callback used to deliver all operations status.
    112      * @param handler thread upon which the callbacks will be invoked.
    113      * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or
    114      * {@code timeout} is invalid or {@code callback} is null.
    115      */
    116     public void registerSync(ScanResult scanResult, int skip, int timeout,
    117             PeriodicAdvertisingCallback callback, Handler handler) {
    118         if (callback == null) {
    119             throw new IllegalArgumentException("callback can't be null");
    120         }
    121 
    122         if (scanResult == null) {
    123             throw new IllegalArgumentException("scanResult can't be null");
    124         }
    125 
    126         if (scanResult.getAdvertisingSid() == ScanResult.SID_NOT_PRESENT) {
    127             throw new IllegalArgumentException("scanResult must contain a valid sid");
    128         }
    129 
    130         if (skip < SKIP_MIN || skip > SKIP_MAX) {
    131             throw new IllegalArgumentException(
    132                     "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX);
    133         }
    134 
    135         if (timeout < TIMEOUT_MIN || timeout > TIMEOUT_MAX) {
    136             throw new IllegalArgumentException(
    137                     "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX);
    138         }
    139 
    140         IBluetoothGatt gatt;
    141         try {
    142             gatt = mBluetoothManager.getBluetoothGatt();
    143         } catch (RemoteException e) {
    144             Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
    145             callback.onSyncEstablished(0, scanResult.getDevice(), scanResult.getAdvertisingSid(),
    146                     skip, timeout,
    147                     PeriodicAdvertisingCallback.SYNC_NO_RESOURCES);
    148             return;
    149         }
    150 
    151         if (handler == null) {
    152             handler = new Handler(Looper.getMainLooper());
    153         }
    154 
    155         IPeriodicAdvertisingCallback wrapped = wrap(callback, handler);
    156         mCallbackWrappers.put(callback, wrapped);
    157 
    158         try {
    159             gatt.registerSync(scanResult, skip, timeout, wrapped);
    160         } catch (RemoteException e) {
    161             Log.e(TAG, "Failed to register sync - ", e);
    162             return;
    163         }
    164     }
    165 
    166     /**
    167      * Cancel pending attempt to create sync, or terminate existing sync.
    168      *
    169      * @param callback Callback used to deliver all operations status.
    170      * @throws IllegalArgumentException if {@code callback} is null, or not a properly registered
    171      * callback.
    172      */
    173     public void unregisterSync(PeriodicAdvertisingCallback callback) {
    174         if (callback == null) {
    175             throw new IllegalArgumentException("callback can't be null");
    176         }
    177 
    178         IBluetoothGatt gatt;
    179         try {
    180             gatt = mBluetoothManager.getBluetoothGatt();
    181         } catch (RemoteException e) {
    182             Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
    183             return;
    184         }
    185 
    186         IPeriodicAdvertisingCallback wrapper = mCallbackWrappers.remove(callback);
    187         if (wrapper == null) {
    188             throw new IllegalArgumentException("callback was not properly registered");
    189         }
    190 
    191         try {
    192             gatt.unregisterSync(wrapper);
    193         } catch (RemoteException e) {
    194             Log.e(TAG, "Failed to cancel sync creation - ", e);
    195             return;
    196         }
    197     }
    198 
    199     private IPeriodicAdvertisingCallback wrap(PeriodicAdvertisingCallback callback,
    200             Handler handler) {
    201         return new IPeriodicAdvertisingCallback.Stub() {
    202             public void onSyncEstablished(int syncHandle, BluetoothDevice device,
    203                     int advertisingSid, int skip, int timeout, int status) {
    204 
    205                 handler.post(new Runnable() {
    206                     @Override
    207                     public void run() {
    208                         callback.onSyncEstablished(syncHandle, device, advertisingSid, skip,
    209                                 timeout,
    210                                 status);
    211 
    212                         if (status != PeriodicAdvertisingCallback.SYNC_SUCCESS) {
    213                             // App can still unregister the sync until notified it failed. Remove
    214                             // callback
    215                             // after app was notifed.
    216                             mCallbackWrappers.remove(callback);
    217                         }
    218                     }
    219                 });
    220             }
    221 
    222             public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) {
    223                 handler.post(new Runnable() {
    224                     @Override
    225                     public void run() {
    226                         callback.onPeriodicAdvertisingReport(report);
    227                     }
    228                 });
    229             }
    230 
    231             public void onSyncLost(int syncHandle) {
    232                 handler.post(new Runnable() {
    233                     @Override
    234                     public void run() {
    235                         callback.onSyncLost(syncHandle);
    236                         // App can still unregister the sync until notified it's lost.
    237                         // Remove callback after app was notifed.
    238                         mCallbackWrappers.remove(callback);
    239                     }
    240                 });
    241             }
    242         };
    243     }
    244 }
    245