• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.Nullable;
20 import android.annotation.RequiresPermission;
21 import android.annotation.SuppressLint;
22 import android.bluetooth.Attributable;
23 import android.bluetooth.BluetoothAdapter;
24 import android.bluetooth.BluetoothDevice;
25 import android.bluetooth.IBluetoothGatt;
26 import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
27 import android.bluetooth.annotations.RequiresBluetoothScanPermission;
28 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
29 import android.content.AttributionSource;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.os.RemoteException;
33 import android.util.Log;
34 
35 import java.util.IdentityHashMap;
36 import java.util.Objects;
37 
38 /**
39  * This class provides methods to perform periodic advertising related operations. An application
40  * can register for periodic advertisements using {@link PeriodicAdvertisingManager#registerSync}.
41  *
42  * <p>Use {@link BluetoothAdapter#getPeriodicAdvertisingManager()} to get an instance of {@link
43  * PeriodicAdvertisingManager}.
44  *
45  * @hide
46  */
47 public final class PeriodicAdvertisingManager {
48 
49     private static final String TAG = "PeriodicAdvertisingManager";
50 
51     private static final int SKIP_MIN = 0;
52     private static final int SKIP_MAX = 499;
53     private static final int TIMEOUT_MIN = 10;
54     private static final int TIMEOUT_MAX = 16384;
55 
56     private final BluetoothAdapter mBluetoothAdapter;
57     private final AttributionSource mAttributionSource;
58 
59     /* maps callback, to callback wrapper and sync handle */
60     IdentityHashMap<PeriodicAdvertisingCallback, IPeriodicAdvertisingCallback /* callbackWrapper */>
61             mCallbackWrappers;
62 
63     /**
64      * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
65      *
66      * @hide
67      */
PeriodicAdvertisingManager(BluetoothAdapter bluetoothAdapter)68     public PeriodicAdvertisingManager(BluetoothAdapter bluetoothAdapter) {
69         mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
70         mAttributionSource = mBluetoothAdapter.getAttributionSource();
71         mCallbackWrappers = new IdentityHashMap<>();
72     }
73 
74     /**
75      * Synchronize with periodic advertising pointed to by the {@code scanResult}. The {@code
76      * scanResult} used must contain a valid advertisingSid. First call to registerSync will use the
77      * {@code skip} and {@code timeout} provided. Subsequent calls from other apps, trying to sync
78      * with same set will reuse existing sync, thus {@code skip} and {@code timeout} values will not
79      * take effect. The values in effect will be returned in {@link
80      * PeriodicAdvertisingCallback#onSyncEstablished}.
81      *
82      * @param scanResult Scan result containing advertisingSid.
83      * @param skip The number of periodic advertising packets that can be skipped after a successful
84      *     receive. Must be between 0 and 499.
85      * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must
86      *     be between 10 (100ms) and 16384 (163.84s).
87      * @param callback Callback used to deliver all operations status.
88      * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or
89      *     {@code timeout} is invalid or {@code callback} is null.
90      */
91     @RequiresLegacyBluetoothAdminPermission
92     @RequiresBluetoothScanPermission
93     @RequiresBluetoothLocationPermission
94     @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
registerSync( ScanResult scanResult, int skip, int timeout, PeriodicAdvertisingCallback callback)95     public void registerSync(
96             ScanResult scanResult, int skip, int timeout, PeriodicAdvertisingCallback callback) {
97         registerSync(scanResult, skip, timeout, callback, null);
98     }
99 
100     /**
101      * Synchronize with periodic advertising pointed to by the {@code scanResult}. The {@code
102      * scanResult} used must contain a valid advertisingSid. First call to registerSync will use the
103      * {@code skip} and {@code timeout} provided. Subsequent calls from other apps, trying to sync
104      * with same set will reuse existing sync, thus {@code skip} and {@code timeout} values will not
105      * take effect. The values in effect will be returned in {@link
106      * PeriodicAdvertisingCallback#onSyncEstablished}.
107      *
108      * @param scanResult Scan result containing advertisingSid.
109      * @param skip The number of periodic advertising packets that can be skipped after a successful
110      *     receive. Must be between 0 and 499.
111      * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must
112      *     be between 10 (100ms) and 16384 (163.84s).
113      * @param callback Callback used to deliver all operations status.
114      * @param handler thread upon which the callbacks will be invoked.
115      * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or
116      *     {@code timeout} is invalid or {@code callback} is null.
117      */
118     @RequiresLegacyBluetoothAdminPermission
119     @RequiresBluetoothScanPermission
120     @RequiresBluetoothLocationPermission
121     @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
registerSync( ScanResult scanResult, int skip, int timeout, PeriodicAdvertisingCallback callback, Handler handler)122     public void registerSync(
123             ScanResult scanResult,
124             int skip,
125             int timeout,
126             PeriodicAdvertisingCallback callback,
127             Handler handler) {
128         if (callback == null) {
129             throw new IllegalArgumentException("callback can't be null");
130         }
131 
132         if (scanResult == null) {
133             throw new IllegalArgumentException("scanResult can't be null");
134         }
135 
136         if (scanResult.getAdvertisingSid() == ScanResult.SID_NOT_PRESENT) {
137             throw new IllegalArgumentException("scanResult must contain a valid sid");
138         }
139 
140         if (skip < SKIP_MIN || skip > SKIP_MAX) {
141             throw new IllegalArgumentException(
142                     "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX);
143         }
144 
145         if (timeout < TIMEOUT_MIN || timeout > TIMEOUT_MAX) {
146             throw new IllegalArgumentException(
147                     "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX);
148         }
149 
150         IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt();
151 
152         if (handler == null) {
153             handler = new Handler(Looper.getMainLooper());
154         }
155 
156         IPeriodicAdvertisingCallback wrapped = wrap(callback, handler);
157         mCallbackWrappers.put(callback, wrapped);
158 
159         try {
160             gatt.registerSync(scanResult, skip, timeout, wrapped, mAttributionSource);
161         } catch (RemoteException e) {
162             Log.e(TAG, "Failed to register sync - ", e);
163             return;
164         }
165     }
166 
167     /**
168      * Cancel pending attempt to create sync, or terminate existing sync.
169      *
170      * @param callback Callback used to deliver all operations status.
171      * @throws IllegalArgumentException if {@code callback} is null, or not a properly registered
172      *     callback.
173      */
174     @RequiresLegacyBluetoothAdminPermission
175     @RequiresBluetoothScanPermission
176     @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
unregisterSync(PeriodicAdvertisingCallback callback)177     public void unregisterSync(PeriodicAdvertisingCallback callback) {
178         if (callback == null) {
179             throw new IllegalArgumentException("callback can't be null");
180         }
181 
182         IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt();
183 
184         IPeriodicAdvertisingCallback wrapper = mCallbackWrappers.remove(callback);
185         if (wrapper == null) {
186             throw new IllegalArgumentException("callback was not properly registered");
187         }
188 
189         try {
190             gatt.unregisterSync(wrapper, mAttributionSource);
191         } catch (RemoteException e) {
192             Log.e(TAG, "Failed to cancel sync creation - ", e);
193             return;
194         }
195     }
196 
197     /**
198      * Transfer periodic sync
199      *
200      * @hide
201      */
transferSync(BluetoothDevice bda, int serviceData, int syncHandle)202     public void transferSync(BluetoothDevice bda, int serviceData, int syncHandle) {
203         IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt();
204 
205         try {
206             gatt.transferSync(bda, serviceData, syncHandle, mAttributionSource);
207         } catch (RemoteException e) {
208             Log.e(TAG, "Failed to register sync - ", e);
209             return;
210         }
211     }
212 
213     /**
214      * Transfer set info
215      *
216      * @hide
217      */
transferSetInfo( BluetoothDevice bda, int serviceData, int advHandle, PeriodicAdvertisingCallback callback)218     public void transferSetInfo(
219             BluetoothDevice bda,
220             int serviceData,
221             int advHandle,
222             PeriodicAdvertisingCallback callback) {
223         transferSetInfo(bda, serviceData, advHandle, callback, null);
224     }
225 
226     /**
227      * Transfer set info
228      *
229      * @hide
230      */
transferSetInfo( BluetoothDevice bda, int serviceData, int advHandle, PeriodicAdvertisingCallback callback, @Nullable Handler handler)231     public void transferSetInfo(
232             BluetoothDevice bda,
233             int serviceData,
234             int advHandle,
235             PeriodicAdvertisingCallback callback,
236             @Nullable Handler handler) {
237         if (callback == null) {
238             throw new IllegalArgumentException("callback can't be null");
239         }
240         IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt();
241         if (handler == null) {
242             handler = new Handler(Looper.getMainLooper());
243         }
244         IPeriodicAdvertisingCallback wrapper = wrap(callback, handler);
245         if (wrapper == null) {
246             throw new IllegalArgumentException("callback was not properly registered");
247         }
248         try {
249             gatt.transferSetInfo(bda, serviceData, advHandle, wrapper, mAttributionSource);
250         } catch (RemoteException e) {
251             Log.e(TAG, "Failed to register sync - ", e);
252             return;
253         }
254     }
255 
256     @SuppressLint("AndroidFrameworkBluetoothPermission")
wrap( PeriodicAdvertisingCallback callback, Handler handler)257     private IPeriodicAdvertisingCallback wrap(
258             PeriodicAdvertisingCallback callback, Handler handler) {
259         return new IPeriodicAdvertisingCallback.Stub() {
260             public void onSyncEstablished(
261                     int syncHandle,
262                     BluetoothDevice device,
263                     int advertisingSid,
264                     int skip,
265                     int timeout,
266                     int status) {
267                 Attributable.setAttributionSource(device, mAttributionSource);
268                 handler.post(
269                         new Runnable() {
270                             @Override
271                             public void run() {
272                                 callback.onSyncEstablished(
273                                         syncHandle, device, advertisingSid, skip, timeout, status);
274 
275                                 if (status != PeriodicAdvertisingCallback.SYNC_SUCCESS) {
276                                     // App can still unregister the sync until notified it failed.
277                                     // Remove
278                                     // callback
279                                     // after app was notified.
280                                     mCallbackWrappers.remove(callback);
281                                 }
282                             }
283                         });
284             }
285 
286             public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) {
287                 handler.post(
288                         new Runnable() {
289                             @Override
290                             public void run() {
291                                 callback.onPeriodicAdvertisingReport(report);
292                             }
293                         });
294             }
295 
296             public void onSyncLost(int syncHandle) {
297                 handler.post(
298                         new Runnable() {
299                             @Override
300                             public void run() {
301                                 callback.onSyncLost(syncHandle);
302                                 // App can still unregister the sync until notified it's lost.
303                                 // Remove callback after app was notified.
304                                 mCallbackWrappers.remove(callback);
305                             }
306                         });
307             }
308 
309             public void onSyncTransferred(BluetoothDevice device, int status) {
310                 handler.post(
311                         new Runnable() {
312                             @Override
313                             public void run() {
314                                 callback.onSyncTransferred(device, status);
315                             }
316                         });
317             }
318 
319             public void onBigInfoAdvertisingReport(int syncHandle, boolean encrypted) {
320                 handler.post(
321                         new Runnable() {
322                             @Override
323                             public void run() {
324                                 callback.onBigInfoAdvertisingReport(syncHandle, encrypted);
325                             }
326                         });
327             }
328         };
329     }
330 }
331