blob: 0f1a8e913ba83c56d3d5ceebd7ad87bab8f0cc86 [file] [log] [blame]
/*
* 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.
*
* @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 IBluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
/* 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(IBluetoothManager bluetoothManager) {
mBluetoothManager = bluetoothManager;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
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.
*/
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);
mCallbackWrappers.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 = mCallbackWrappers.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.
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);
}
});
}
};
}
}