Merge "Bluetooth 5 periodic scan (1/3)"
diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp
index bf8d48b..06a11a4 100644
--- a/jni/com_android_bluetooth_gatt.cpp
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -199,12 +199,20 @@
static jmethodID method_onPeriodicAdvertisingEnabled;
/**
+ * Periodic scanner callback methods
+ */
+static jmethodID method_onSyncLost;
+static jmethodID method_onSyncReport;
+static jmethodID method_onSyncStarted;
+
+/**
* Static variables
*/
static const btgatt_interface_t* sGattIf = NULL;
static jobject mCallbacksObj = NULL;
static jobject mAdvertiseCallbacksObj = NULL;
+static jobject mPeriodicScanCallbacksObj = NULL;
/**
* BTA client callbacks
@@ -1938,6 +1946,83 @@
base::Bind(&enablePeriodicSetCb, advertiser_id, enable));
}
+static void periodicScanClassInitNative(JNIEnv* env, jclass clazz) {
+ method_onSyncStarted =
+ env->GetMethodID(clazz, "onSyncStarted", "(IIIILjava/lang/String;III)V");
+ method_onSyncReport = env->GetMethodID(clazz, "onSyncReport", "(IIII[B)V");
+ method_onSyncLost = env->GetMethodID(clazz, "onSyncLost", "(I)V");
+}
+
+static void periodicScanInitializeNative(JNIEnv* env, jobject object) {
+ if (mPeriodicScanCallbacksObj != NULL) {
+ ALOGW("Cleaning up periodic scan callback object");
+ env->DeleteGlobalRef(mPeriodicScanCallbacksObj);
+ mPeriodicScanCallbacksObj = NULL;
+ }
+
+ mPeriodicScanCallbacksObj = env->NewGlobalRef(object);
+}
+
+static void periodicScanCleanupNative(JNIEnv* env, jobject object) {
+ if (mPeriodicScanCallbacksObj != NULL) {
+ env->DeleteGlobalRef(mPeriodicScanCallbacksObj);
+ mPeriodicScanCallbacksObj = NULL;
+ }
+}
+
+static void onSyncStarted(int reg_id, uint8_t status, uint16_t sync_handle,
+ uint8_t sid, uint8_t address_type,
+ bt_bdaddr_t address, uint8_t phy, uint16_t interval) {
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid()) return;
+
+ sCallbackEnv->CallVoidMethod(mPeriodicScanCallbacksObj, method_onSyncStarted,
+ reg_id, sync_handle, sid, address_type, address,
+ phy, interval, status);
+}
+
+static void onSyncReport(uint16_t sync_handle, int8_t tx_power, int8_t rssi,
+ uint8_t data_status, std::vector<uint8_t> data) {
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid()) return;
+
+ ScopedLocalRef<jbyteArray> jb(sCallbackEnv.get(),
+ sCallbackEnv->NewByteArray(data.size()));
+ sCallbackEnv->SetByteArrayRegion(jb.get(), 0, data.size(),
+ (jbyte*)data.data());
+
+ sCallbackEnv->CallVoidMethod(mPeriodicScanCallbacksObj, method_onSyncReport,
+ sync_handle, tx_power, rssi, data_status,
+ jb.get());
+}
+
+static void onSyncLost(uint16_t sync_handle) {
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid()) return;
+
+ sCallbackEnv->CallVoidMethod(mPeriodicScanCallbacksObj, method_onSyncLost,
+ sync_handle);
+}
+
+static void startSyncNative(JNIEnv* env, jobject object, jint sid,
+ jstring address, jint skip, jint timeout,
+ jint reg_id) {
+ if (!sGattIf) return;
+
+ bt_bdaddr_t tmp;
+ jstr2bdaddr(env, &tmp, address);
+
+ sGattIf->scanner->StartSync(
+ sid, tmp, skip, timeout, base::Bind(&onSyncStarted, reg_id),
+ base::Bind(&onSyncReport), base::Bind(&onSyncLost));
+}
+
+static void stopSyncNative(int sync_handle) {
+ if (!sGattIf) return;
+
+ sGattIf->scanner->StopSync(sync_handle);
+}
+
static void gattTestNative(JNIEnv* env, jobject object, jint command,
jlong uuid1_lsb, jlong uuid1_msb, jstring bda1,
jint p1, jint p2, jint p3, jint p4, jint p5) {
@@ -1989,6 +2074,15 @@
(void*)setPeriodicAdvertisingEnableNative},
};
+// JNI functions defined in PeriodicScanManager class.
+static JNINativeMethod sPeriodicScanMethods[] = {
+ {"classInitNative", "()V", (void*)periodicScanClassInitNative},
+ {"initializeNative", "()V", (void*)periodicScanInitializeNative},
+ {"cleanupNative", "()V", (void*)periodicScanCleanupNative},
+ {"startSyncNative", "(ILjava/lang/String;III)V", (void*)startSyncNative},
+ {"stopSyncNative", "(I)V", (void*)stopSyncNative},
+};
+
// JNI functions defined in ScanManager class.
static JNINativeMethod sScanMethods[] = {
{"registerScannerNative", "(JJ)V", (void*)registerScannerNative},
@@ -2100,6 +2194,9 @@
register_success &= jniRegisterNativeMethods(
env, "com/android/bluetooth/gatt/AdvertiseManager", sAdvertiseMethods,
NELEM(sAdvertiseMethods));
+ register_success &= jniRegisterNativeMethods(
+ env, "com/android/bluetooth/gatt/PeriodicScanManager",
+ sPeriodicScanMethods, NELEM(sPeriodicScanMethods));
return register_success &
jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/GattService",
sMethods, NELEM(sMethods));
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index 8a43198..990957c 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -142,6 +142,7 @@
new HashMap<Integer, List<BluetoothGattService>>();
private AdvertiseManager mAdvertiseManager;
+ private PeriodicScanManager mPeriodicScanManager;
private ScanManager mScanManager;
private AppOpsManager mAppOps;
@@ -172,6 +173,9 @@
mScanManager = new ScanManager(this);
mScanManager.start();
+ mPeriodicScanManager = new PeriodicScanManager(AdapterService.getAdapterService());
+ mPeriodicScanManager.start();
+
return true;
}
@@ -184,6 +188,7 @@
mReliableQueue.clear();
if (mAdvertiseManager != null) mAdvertiseManager.cleanup();
if (mScanManager != null) mScanManager.cleanup();
+ if (mPeriodicScanManager != null) mPeriodicScanManager.cleanup();
return true;
}
@@ -192,6 +197,7 @@
cleanupNative();
if (mAdvertiseManager != null) mAdvertiseManager.cleanup();
if (mScanManager != null) mScanManager.cleanup();
+ if (mPeriodicScanManager != null) mPeriodicScanManager.cleanup();
return true;
}
@@ -1487,12 +1493,12 @@
void registerSync(
ScanResult scanResult, int skip, int timeout, IPeriodicAdvertisingCallback callback) {
enforceAdminPermission();
- // TODO(jpawlowski): implement
+ mPeriodicScanManager.startSync(scanResult, skip, timeout, callback);
}
void unregisterSync(IPeriodicAdvertisingCallback callback) {
enforceAdminPermission();
- // TODO(jpawlowski): implement
+ mPeriodicScanManager.stopSync(callback);
}
/**************************************************************************
diff --git a/src/com/android/bluetooth/gatt/PeriodicScanManager.java b/src/com/android/bluetooth/gatt/PeriodicScanManager.java
new file mode 100644
index 0000000..591018b
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/PeriodicScanManager.java
@@ -0,0 +1,230 @@
+/*
+ * 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 com.android.bluetooth.gatt;
+
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertisingSetParameters;
+import android.bluetooth.le.IPeriodicAdvertisingCallback;
+import android.bluetooth.le.PeriodicAdvertisingParameters;
+import android.bluetooth.le.PeriodicAdvertisingReport;
+import android.bluetooth.le.ScanRecord;
+import android.bluetooth.le.ScanResult;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.util.Log;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages Bluetooth LE Periodic scans
+ *
+ * @hide
+ */
+class PeriodicScanManager {
+ private static final boolean DBG = GattServiceConfig.DBG;
+ private static final String TAG = GattServiceConfig.TAG_PREFIX + "SyncManager";
+
+ private final AdapterService mAdapterService;
+ Map<IBinder, SyncInfo> mSyncs = Collections.synchronizedMap(new HashMap<>());
+ static int sTempRegistrationId = -1;
+
+ /**
+ * Constructor of {@link SyncManager}.
+ */
+ PeriodicScanManager(AdapterService adapterService) {
+ logd("advertise manager created");
+ mAdapterService = adapterService;
+ }
+
+ void start() {
+ initializeNative();
+ }
+
+ void cleanup() {
+ logd("cleanup()");
+ cleanupNative();
+ mSyncs.clear();
+ sTempRegistrationId = -1;
+ }
+
+ class SyncInfo {
+ /* When id is negative, the registration is ongoing. When the registration finishes, id
+ * becomes equal to sync_handle */
+ public Integer id;
+ public SyncDeathRecipient deathRecipient;
+ public IPeriodicAdvertisingCallback callback;
+
+ SyncInfo(Integer id, SyncDeathRecipient deathRecipient,
+ IPeriodicAdvertisingCallback callback) {
+ this.id = id;
+ this.deathRecipient = deathRecipient;
+ this.callback = callback;
+ }
+ }
+
+ IBinder toBinder(IPeriodicAdvertisingCallback e) {
+ return ((IInterface) e).asBinder();
+ }
+
+ class SyncDeathRecipient implements IBinder.DeathRecipient {
+ IPeriodicAdvertisingCallback callback;
+
+ public SyncDeathRecipient(IPeriodicAdvertisingCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public void binderDied() {
+ if (DBG) Log.d(TAG, "Binder is dead - unregistering advertising set");
+ stopSync(callback);
+ }
+ }
+
+ Map.Entry<IBinder, SyncInfo> findSync(int sync_handle) {
+ Map.Entry<IBinder, SyncInfo> entry = null;
+ for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) {
+ if (e.getValue().id == sync_handle) {
+ entry = e;
+ break;
+ }
+ }
+ return entry;
+ }
+
+ void onSyncStarted(int reg_id, int sync_handle, int sid, int address_type, String address,
+ int phy, int interval, int status) throws Exception {
+ logd("onSyncStarted() - reg_id=" + reg_id + ", sync_handle=" + sync_handle + ", status="
+ + status);
+
+ Map.Entry<IBinder, SyncInfo> entry = findSync(reg_id);
+ if (entry == null) {
+ Log.i(TAG, "onSyncStarted() - no callback found for reg_id " + reg_id);
+ // Sync was stopped before it was properly registered.
+ stopSyncNative(sync_handle);
+ return;
+ }
+
+ IPeriodicAdvertisingCallback callback = entry.getValue().callback;
+ if (status == 0) {
+ entry.setValue(new SyncInfo(sync_handle, entry.getValue().deathRecipient, callback));
+ } else {
+ IBinder binder = entry.getKey();
+ binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
+ mSyncs.remove(binder);
+ }
+
+ // TODO: fix callback arguments
+ // callback.onSyncStarted(sync_handle, tx_power, status);
+ }
+
+ void onSyncReport(int sync_handle, int tx_power, int rssi, int data_status, byte[] data)
+ throws Exception {
+ logd("onSyncReport() - sync_handle=" + sync_handle);
+
+ Map.Entry<IBinder, SyncInfo> entry = findSync(sync_handle);
+ if (entry == null) {
+ Log.i(TAG, "onSyncReport() - no callback found for sync_handle " + sync_handle);
+ return;
+ }
+
+ IPeriodicAdvertisingCallback callback = entry.getValue().callback;
+ PeriodicAdvertisingReport report = new PeriodicAdvertisingReport(
+ sync_handle, tx_power, rssi, data_status, ScanRecord.parseFromBytes(data));
+ callback.onPeriodicAdvertisingReport(report);
+ }
+
+ void onSyncLost(int sync_handle) throws Exception {
+ logd("onSyncLost() - sync_handle=" + sync_handle);
+
+ Map.Entry<IBinder, SyncInfo> entry = findSync(sync_handle);
+ if (entry == null) {
+ Log.i(TAG, "onSyncLost() - no callback found for sync_handle " + sync_handle);
+ return;
+ }
+
+ IPeriodicAdvertisingCallback callback = entry.getValue().callback;
+ mSyncs.remove(entry);
+ callback.onSyncLost(sync_handle);
+ }
+
+ void startSync(
+ ScanResult scanResult, int skip, int timeout, IPeriodicAdvertisingCallback callback) {
+ SyncDeathRecipient deathRecipient = new SyncDeathRecipient(callback);
+ IBinder binder = toBinder(callback);
+ try {
+ binder.linkToDeath(deathRecipient, 0);
+ } catch (RemoteException e) {
+ throw new IllegalArgumentException("Can't link to periodic scanner death");
+ }
+
+ String address = scanResult.getDevice().getAddress();
+ int sid = scanResult.getAdvertisingSid();
+
+ int cb_id = --sTempRegistrationId;
+ mSyncs.put(binder, new SyncInfo(cb_id, deathRecipient, callback));
+
+ logd("startSync() - reg_id=" + cb_id + ", callback: " + binder);
+ startSyncNative(sid, address, skip, timeout, cb_id);
+ }
+
+ void stopSync(IPeriodicAdvertisingCallback callback) {
+ IBinder binder = toBinder(callback);
+ logd("stopSync() " + binder);
+
+ SyncInfo sync = mSyncs.remove(binder);
+ if (sync == null) {
+ Log.e(TAG, "stopSync() - no client found for callback");
+ return;
+ }
+
+ Integer sync_handle = sync.id;
+ binder.unlinkToDeath(sync.deathRecipient, 0);
+
+ if (sync_handle < 0) {
+ Log.i(TAG, "stopSync() - not finished registration yet");
+ // Sync will be freed once initiated in onSyncStarted()
+ return;
+ }
+
+ stopSyncNative(sync_handle);
+ }
+
+ private void logd(String s) {
+ if (DBG) {
+ Log.d(TAG, s);
+ }
+ }
+
+ static {
+ classInitNative();
+ }
+
+ private native static void classInitNative();
+ private native void initializeNative();
+ private native void cleanupNative();
+ private native void startSyncNative(int sid, String address, int skip, int timeout, int reg_id);
+ private native void stopSyncNative(int sync_handle);
+}