Loading android/app/jni/com_android_bluetooth_gatt.cpp +56 −6 Original line number Diff line number Diff line Loading @@ -184,7 +184,7 @@ static jmethodID method_onPeriodicAdvertisingEnabled; static jmethodID method_onSyncLost; static jmethodID method_onSyncReport; static jmethodID method_onSyncStarted; static jmethodID method_onSyncTransferredCallback; /** * Static variables */ Loading Loading @@ -2230,6 +2230,8 @@ static void periodicScanClassInitNative(JNIEnv* env, jclass clazz) { 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"); method_onSyncTransferredCallback = env->GetMethodID( clazz, "onSyncTransferredCallback", "(IILjava/lang/String;)V"); } static void periodicScanInitializeNative(JNIEnv* env, jobject object) { Loading @@ -2254,10 +2256,16 @@ static void onSyncStarted(int reg_id, uint8_t status, uint16_t sync_handle, uint8_t phy, uint16_t interval) { CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid()) return; if (!mPeriodicScanCallbacksObj) { ALOGE("mPeriodicScanCallbacksObj is NULL. Return."); return; } ScopedLocalRef<jstring> addr(sCallbackEnv.get(), bdaddr2newjstr(sCallbackEnv.get(), &address)); sCallbackEnv->CallVoidMethod(mPeriodicScanCallbacksObj, method_onSyncStarted, reg_id, sync_handle, sid, address_type, address, phy, interval, status); reg_id, sync_handle, sid, address_type, addr.get(), phy, interval, status); } static void onSyncReport(uint16_t sync_handle, int8_t tx_power, int8_t rssi, Loading Loading @@ -2287,19 +2295,56 @@ static void startSyncNative(JNIEnv* env, jobject object, jint sid, jstring address, jint skip, jint timeout, jint reg_id) { if (!sGattIf) return; sGattIf->scanner->StartSync(sid, str2addr(env, address), skip, timeout, base::Bind(&onSyncStarted, reg_id), base::Bind(&onSyncReport), base::Bind(&onSyncLost)); } static void stopSyncNative(int sync_handle) { static void stopSyncNative(JNIEnv* env, jobject object, jint sync_handle) { if (!sGattIf) return; sGattIf->scanner->StopSync(sync_handle); } static void cancelSyncNative(JNIEnv* env, jobject object, jint sid, jstring address) { if (!sGattIf) return; sGattIf->scanner->CancelCreateSync(sid, str2addr(env, address)); } static void onSyncTransferredCb(int pa_source, uint8_t status, RawAddress address) { CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid()) return; if (!mPeriodicScanCallbacksObj) { ALOGE("mPeriodicScanCallbacksObj is NULL. Return."); return; } ScopedLocalRef<jstring> addr(sCallbackEnv.get(), bdaddr2newjstr(sCallbackEnv.get(), &address)); sCallbackEnv->CallVoidMethod(mPeriodicScanCallbacksObj, method_onSyncTransferredCallback, pa_source, status, addr.get()); } static void syncTransferNative(JNIEnv* env, jobject object, jint pa_source, jstring addr, jint service_data, jint sync_handle) { if (!sGattIf) return; sGattIf->scanner->TransferSync(str2addr(env, addr), service_data, sync_handle, base::Bind(&onSyncTransferredCb, pa_source)); } static void transferSetInfoNative(JNIEnv* env, jobject object, jint pa_source, jstring addr, jint service_data, jint adv_handle) { if (!sGattIf) return; sGattIf->scanner->TransferSetInfo( str2addr(env, addr), service_data, adv_handle, base::Bind(&onSyncTransferredCb, pa_source)); } 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) { Loading Loading @@ -2358,6 +2403,11 @@ static JNINativeMethod sPeriodicScanMethods[] = { {"cleanupNative", "()V", (void*)periodicScanCleanupNative}, {"startSyncNative", "(ILjava/lang/String;III)V", (void*)startSyncNative}, {"stopSyncNative", "(I)V", (void*)stopSyncNative}, {"cancelSyncNative", "(ILjava/lang/String;)V", (void*)cancelSyncNative}, {"syncTransferNative", "(ILjava/lang/String;II)V", (void*)syncTransferNative}, {"transferSetInfoNative", "(ILjava/lang/String;II)V", (void*)transferSetInfoNative}, }; // JNI functions defined in ScanManager class. Loading android/app/src/com/android/bluetooth/gatt/GattService.java +40 −1 Original line number Diff line number Diff line Loading @@ -1088,12 +1088,32 @@ public class GattService extends ProfileService { } @Override public void unregisterSync( public void transferSync(BluetoothDevice bda, int serviceData , int syncHandle, AttributionSource attributionSource) { GattService service = getService(); if (service == null) { return; } service.transferSync(bda, serviceData , syncHandle, attributionSource); } @Override public void transferSetInfo(BluetoothDevice bda, int serviceData , int advHandle, IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) { GattService service = getService(); if (service == null) { return; } service.transferSetInfo(bda, serviceData , advHandle, callback, attributionSource); } @Override public void unregisterSync(IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) { GattService service = getService(); if (service == null) { return; } service.unregisterSync(callback, attributionSource); } Loading Loading @@ -2621,6 +2641,25 @@ public class GattService extends ProfileService { mPeriodicScanManager.stopSync(callback); } void transferSync(BluetoothDevice bda, int serviceData, int syncHandle, AttributionSource attributionSource) { if (!Utils.checkScanPermissionForDataDelivery( this, attributionSource, "GattService transferSync")) { return; } mPeriodicScanManager.transferSync(bda, serviceData, syncHandle); } void transferSetInfo(BluetoothDevice bda, int serviceData, int advHandle, IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) { if (!Utils.checkScanPermissionForDataDelivery( this, attributionSource, "GattService transferSetInfo")) { return; } mPeriodicScanManager.transferSetInfo(bda, serviceData, advHandle, callback); } /************************************************************************** * ADVERTISING SET *************************************************************************/ Loading android/app/src/com/android/bluetooth/gatt/PeriodicScanManager.java +209 −40 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.bluetooth.gatt; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.le.IPeriodicAdvertisingCallback; import android.bluetooth.le.PeriodicAdvertisingReport; import android.bluetooth.le.ScanRecord; Loading @@ -30,6 +32,7 @@ import com.android.bluetooth.btservice.AdapterService; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Manages Bluetooth LE Periodic scans Loading @@ -40,10 +43,12 @@ 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<>()); private final BluetoothAdapter mAdapter; Map<IBinder, SyncInfo> mSyncs = new ConcurrentHashMap<>(); Map<IBinder, SyncTransferInfo> mSyncTransfers = Collections.synchronizedMap(new HashMap<>()); static int sTempRegistrationId = -1; private static final int PA_SOURCE_LOCAL = 1; private static final int PA_SOURCE_REMOTE = 2; /** * Constructor of {@link SyncManager}. */ Loading @@ -51,7 +56,7 @@ class PeriodicScanManager { if (DBG) { Log.d(TAG, "advertise manager created"); } mAdapterService = adapterService; mAdapter = BluetoothAdapter.getDefaultAdapter(); } void start() { Loading @@ -67,21 +72,52 @@ class PeriodicScanManager { sTempRegistrationId = -1; } class SyncTransferInfo { public String address; public SyncDeathRecipient deathRecipient; public IPeriodicAdvertisingCallback callback; SyncTransferInfo(String address, IPeriodicAdvertisingCallback callback) { this.address = address; this.callback = callback; } } class SyncInfo { /* When id is negative, the registration is ongoing. When the registration finishes, id * becomes equal to sync_handle */ public Integer id; public Integer advSid; public String address; public Integer skip; public Integer timeout; public SyncDeathRecipient deathRecipient; public IPeriodicAdvertisingCallback callback; SyncInfo(Integer id, SyncDeathRecipient deathRecipient, SyncInfo(Integer id, Integer advSid, String address, Integer skip, Integer timeout, SyncDeathRecipient deathRecipient, IPeriodicAdvertisingCallback callback) { this.id = id; this.advSid = advSid; this.address = address; this.skip = skip; this.timeout = timeout; this.deathRecipient = deathRecipient; this.callback = callback; } } Map.Entry<IBinder, SyncTransferInfo> findSyncTransfer(String address) { Map.Entry<IBinder, SyncTransferInfo> entry = null; for (Map.Entry<IBinder, SyncTransferInfo> e : mSyncTransfers.entrySet()) { if (e.getValue().address.equals(address)) { entry = e; break; } } return entry; } IBinder toBinder(IPeriodicAdvertisingCallback e) { return ((IInterface) e).asBinder(); } Loading Loading @@ -113,6 +149,33 @@ class PeriodicScanManager { return entry; } Map.Entry<IBinder, SyncInfo> findMatchingSync(int advSid, String address) { Map.Entry<IBinder, SyncInfo> entry = null; for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) { if (e.getValue().advSid == advSid && e.getValue().address.equals(address)) { return entry = e; } } return entry; } Map<IBinder, SyncInfo> findAllSync(int syncHandle) { Map<IBinder, SyncInfo> syncMap = new HashMap<IBinder, SyncInfo>(); for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) { if (e.getValue().id != syncHandle) { continue; } syncMap.put(e.getKey(), new SyncInfo(e.getValue().id, e.getValue().advSid, e.getValue().address, e.getValue().skip, e.getValue().timeout, e.getValue().deathRecipient, e.getValue().callback)); } return syncMap; } void onSyncStarted(int regId, int syncHandle, int sid, int addressType, String address, int phy, int interval, int status) throws Exception { if (DBG) { Loading @@ -120,26 +183,37 @@ class PeriodicScanManager { "onSyncStarted() - regId=" + regId + ", syncHandle=" + syncHandle + ", status=" + status); } Map.Entry<IBinder, SyncInfo> entry = findSync(regId); if (entry == null) { Log.i(TAG, "onSyncStarted() - no callback found for regId " + regId); // Sync was stopped before it was properly registered. Map<IBinder, SyncInfo> syncMap = findAllSync(regId); if (syncMap.size() == 0) { Log.d(TAG, "onSyncStarted() - no callback found for regId " + regId); stopSyncNative(syncHandle); return; } IPeriodicAdvertisingCallback callback = entry.getValue().callback; synchronized (mSyncs) { for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) { if (e.getValue().id != regId) { continue; } IPeriodicAdvertisingCallback callback = e.getValue().callback; if (status == 0) { entry.setValue(new SyncInfo(syncHandle, entry.getValue().deathRecipient, callback)); Log.d(TAG, "onSyncStarted: updating id with syncHandle " + syncHandle); e.setValue(new SyncInfo(syncHandle, sid, address, e.getValue().skip, e.getValue().timeout, e.getValue().deathRecipient, callback)); callback.onSyncEstablished(syncHandle, mAdapter.getRemoteDevice(address), sid, e.getValue().skip, e.getValue().timeout, status); } else { IBinder binder = entry.getKey(); binder.unlinkToDeath(entry.getValue().deathRecipient, 0); callback.onSyncEstablished(syncHandle, mAdapter.getRemoteDevice(address), sid, e.getValue().skip, e.getValue().timeout, status); IBinder binder = e.getKey(); binder.unlinkToDeath(e.getValue().deathRecipient, 0); mSyncs.remove(binder); } // TODO: fix callback arguments // callback.onSyncStarted(syncHandle, tx_power, status); } } } void onSyncReport(int syncHandle, int txPower, int rssi, int dataStatus, byte[] data) Loading @@ -148,33 +222,38 @@ class PeriodicScanManager { Log.d(TAG, "onSyncReport() - syncHandle=" + syncHandle); } Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle); if (entry == null) { Map<IBinder, SyncInfo> syncMap = findAllSync(syncHandle); if (syncMap.isEmpty()) { Log.i(TAG, "onSyncReport() - no callback found for syncHandle " + syncHandle); return; } IPeriodicAdvertisingCallback callback = entry.getValue().callback; for (Map.Entry<IBinder, SyncInfo> e :syncMap.entrySet()) { IPeriodicAdvertisingCallback callback = e.getValue().callback; PeriodicAdvertisingReport report = new PeriodicAdvertisingReport(syncHandle, txPower, rssi, dataStatus, ScanRecord.parseFromBytes(data)); callback.onPeriodicAdvertisingReport(report); } } void onSyncLost(int syncHandle) throws Exception { if (DBG) { Log.d(TAG, "onSyncLost() - syncHandle=" + syncHandle); } Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle); if (entry == null) { Map<IBinder, SyncInfo> syncMap = findAllSync(syncHandle); if (syncMap.isEmpty()) { Log.i(TAG, "onSyncLost() - no callback found for syncHandle " + syncHandle); return; } IPeriodicAdvertisingCallback callback = entry.getValue().callback; mSyncs.remove(entry); for (Map.Entry<IBinder, SyncInfo> e :syncMap.entrySet()) { IPeriodicAdvertisingCallback callback = e.getValue().callback; IBinder binder = toBinder(callback); synchronized (mSyncs) { mSyncs.remove(binder); } callback.onSyncLost(syncHandle); } } void startSync(ScanResult scanResult, int skip, int timeout, Loading @@ -189,9 +268,38 @@ class PeriodicScanManager { String address = scanResult.getDevice().getAddress(); int sid = scanResult.getAdvertisingSid(); if (DBG) { Log.d(TAG, "startSync for Device: " + address + " sid: " + sid); } synchronized (mSyncs) { Map.Entry<IBinder, SyncInfo> entry = findMatchingSync(sid, address); if (entry != null) { //Found matching sync. Copy sync handle if (DBG) { Log.d(TAG, "startSync: Matching entry found"); } mSyncs.put(binder, new SyncInfo(entry.getValue().id, sid, address, entry.getValue().skip, entry.getValue().timeout, deathRecipient, callback)); if (entry.getValue().id >= 0) { try { callback.onSyncEstablished(entry.getValue().id, mAdapter.getRemoteDevice(address), sid, entry.getValue().skip, entry.getValue().timeout, 0 /*success*/); } catch (RemoteException e) { throw new IllegalArgumentException("Can't invoke callback"); } } else { Log.d(TAG, "startSync(): sync pending for same remote"); } return; } } int cbId = --sTempRegistrationId; mSyncs.put(binder, new SyncInfo(cbId, deathRecipient, callback)); mSyncs.put(binder, new SyncInfo(cbId, sid, address, skip, timeout, deathRecipient, callback)); if (DBG) { Log.d(TAG, "startSync() - reg_id=" + cbId + ", callback: " + binder); Loading @@ -204,8 +312,10 @@ class PeriodicScanManager { if (DBG) { Log.d(TAG, "stopSync() " + binder); } SyncInfo sync = mSyncs.remove(binder); SyncInfo sync = null; synchronized (mSyncs) { sync = mSyncs.remove(binder); } if (sync == null) { Log.e(TAG, "stopSync() - no client found for callback"); return; Loading @@ -213,14 +323,65 @@ class PeriodicScanManager { Integer syncHandle = sync.id; binder.unlinkToDeath(sync.deathRecipient, 0); Log.d(TAG, "stopSync: " + syncHandle); synchronized (mSyncs) { Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle); if (entry != null) { Log.d(TAG, "stopSync() - another app synced to same PA, not stopping sync"); return; } } Log.d(TAG, "calling stopSyncNative: " + syncHandle.intValue()); if (syncHandle < 0) { Log.i(TAG, "stopSync() - not finished registration yet"); // Sync will be freed once initiated in onSyncStarted() Log.i(TAG, "cancelSync() - sync not established yet"); cancelSyncNative(sync.advSid, sync.address); } else { stopSyncNative(syncHandle.intValue()); } } void onSyncTransferredCallback(int paSource, int status, String bda) { Log.d(TAG, "onSyncTransferredCallback()"); Map.Entry<IBinder, SyncTransferInfo> entry = findSyncTransfer(bda); if (entry != null) { mSyncTransfers.remove(entry); IPeriodicAdvertisingCallback callback = entry.getValue().callback; try { callback.onSyncTransferred(mAdapter.getRemoteDevice(bda), status); } catch (RemoteException e) { throw new IllegalArgumentException("Can't find callback for sync transfer"); } } } void transferSync(BluetoothDevice bda, int serviceData, int syncHandle) { Log.d(TAG, "transferSync()"); Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle); if (entry == null) { Log.d(TAG, "transferSync: callback not registered"); return; } //check for duplicate transfers mSyncTransfers.put(entry.getKey(), new SyncTransferInfo(bda.getAddress(), entry.getValue().callback)); syncTransferNative(PA_SOURCE_REMOTE, bda.getAddress(), serviceData, syncHandle); } stopSyncNative(syncHandle); void transferSetInfo(BluetoothDevice bda, int serviceData, int advHandle, IPeriodicAdvertisingCallback callback) { SyncDeathRecipient deathRecipient = new SyncDeathRecipient(callback); IBinder binder = toBinder(callback); if (DBG) { Log.d(TAG, "transferSetInfo() " + binder); } try { binder.linkToDeath(deathRecipient, 0); } catch (RemoteException e) { throw new IllegalArgumentException("Can't link to periodic scanner death"); } mSyncTransfers.put(binder, new SyncTransferInfo(bda.getAddress(), callback)); transferSetInfoNative(PA_SOURCE_LOCAL, bda.getAddress(), serviceData, advHandle); } static { Loading @@ -236,4 +397,12 @@ class PeriodicScanManager { private native void startSyncNative(int sid, String address, int skip, int timeout, int regId); private native void stopSyncNative(int syncHandle); private native void cancelSyncNative(int sid, String address); private native void syncTransferNative(int paSource, String address, int serviceData, int syncHandle); private native void transferSetInfoNative(int paSource, String address, int serviceData, int advHandle); } framework/java/android/bluetooth/le/PeriodicAdvertisingCallback.java +9 −0 Original line number Diff line number Diff line Loading @@ -78,4 +78,13 @@ public abstract class PeriodicAdvertisingCallback { */ public void onSyncLost(int syncHandle) { } /** * Callback when periodic sync transferred. * * @param device * @param status */ public void onSyncTransferred(BluetoothDevice device, int status) { } } framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java +83 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.bluetooth.le; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.bluetooth.Attributable; Loading Loading @@ -215,6 +216,79 @@ public final class PeriodicAdvertisingManager { } } /** * Transfer periodic sync * * @hide */ public void transferSync(BluetoothDevice bda, int serviceData, int syncHandle) { IBluetoothGatt gatt; try { gatt = mBluetoothManager.getBluetoothGatt(); } catch (RemoteException e) { Log.e(TAG, "Failed to get Bluetooth gatt - ", e); PeriodicAdvertisingCallback callback = null; for (PeriodicAdvertisingCallback cb : mCallbackWrappers.keySet()) { callback = cb; } if (callback != null) { callback.onSyncTransferred(bda, PeriodicAdvertisingCallback.SYNC_NO_RESOURCES); } return; } try { gatt.transferSync(bda, serviceData , syncHandle, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to register sync - ", e); return; } } /** * Transfer set info * * @hide */ public void transferSetInfo(BluetoothDevice bda, int serviceData, int advHandle, PeriodicAdvertisingCallback callback) { transferSetInfo(bda, serviceData, advHandle, callback, null); } /** * Transfer set info * * @hide */ public void transferSetInfo(BluetoothDevice bda, int serviceData, int advHandle, PeriodicAdvertisingCallback callback, @Nullable Handler handler) { 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; } if (handler == null) { handler = new Handler(Looper.getMainLooper()); } IPeriodicAdvertisingCallback wrapper = wrap(callback, handler); if (wrapper == null) { throw new IllegalArgumentException("callback was not properly registered"); } try { gatt.transferSetInfo(bda, serviceData , advHandle, wrapper, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to register sync - ", e); return; } } @SuppressLint("AndroidFrameworkBluetoothPermission") private IPeriodicAdvertisingCallback wrap(PeriodicAdvertisingCallback callback, Handler handler) { Loading Loading @@ -259,6 +333,15 @@ public final class PeriodicAdvertisingManager { } }); } public void onSyncTransferred(BluetoothDevice device, int status) { handler.post(new Runnable() { @Override public void run() { callback.onSyncTransferred(device, status); } }); } }; } } Loading
android/app/jni/com_android_bluetooth_gatt.cpp +56 −6 Original line number Diff line number Diff line Loading @@ -184,7 +184,7 @@ static jmethodID method_onPeriodicAdvertisingEnabled; static jmethodID method_onSyncLost; static jmethodID method_onSyncReport; static jmethodID method_onSyncStarted; static jmethodID method_onSyncTransferredCallback; /** * Static variables */ Loading Loading @@ -2230,6 +2230,8 @@ static void periodicScanClassInitNative(JNIEnv* env, jclass clazz) { 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"); method_onSyncTransferredCallback = env->GetMethodID( clazz, "onSyncTransferredCallback", "(IILjava/lang/String;)V"); } static void periodicScanInitializeNative(JNIEnv* env, jobject object) { Loading @@ -2254,10 +2256,16 @@ static void onSyncStarted(int reg_id, uint8_t status, uint16_t sync_handle, uint8_t phy, uint16_t interval) { CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid()) return; if (!mPeriodicScanCallbacksObj) { ALOGE("mPeriodicScanCallbacksObj is NULL. Return."); return; } ScopedLocalRef<jstring> addr(sCallbackEnv.get(), bdaddr2newjstr(sCallbackEnv.get(), &address)); sCallbackEnv->CallVoidMethod(mPeriodicScanCallbacksObj, method_onSyncStarted, reg_id, sync_handle, sid, address_type, address, phy, interval, status); reg_id, sync_handle, sid, address_type, addr.get(), phy, interval, status); } static void onSyncReport(uint16_t sync_handle, int8_t tx_power, int8_t rssi, Loading Loading @@ -2287,19 +2295,56 @@ static void startSyncNative(JNIEnv* env, jobject object, jint sid, jstring address, jint skip, jint timeout, jint reg_id) { if (!sGattIf) return; sGattIf->scanner->StartSync(sid, str2addr(env, address), skip, timeout, base::Bind(&onSyncStarted, reg_id), base::Bind(&onSyncReport), base::Bind(&onSyncLost)); } static void stopSyncNative(int sync_handle) { static void stopSyncNative(JNIEnv* env, jobject object, jint sync_handle) { if (!sGattIf) return; sGattIf->scanner->StopSync(sync_handle); } static void cancelSyncNative(JNIEnv* env, jobject object, jint sid, jstring address) { if (!sGattIf) return; sGattIf->scanner->CancelCreateSync(sid, str2addr(env, address)); } static void onSyncTransferredCb(int pa_source, uint8_t status, RawAddress address) { CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid()) return; if (!mPeriodicScanCallbacksObj) { ALOGE("mPeriodicScanCallbacksObj is NULL. Return."); return; } ScopedLocalRef<jstring> addr(sCallbackEnv.get(), bdaddr2newjstr(sCallbackEnv.get(), &address)); sCallbackEnv->CallVoidMethod(mPeriodicScanCallbacksObj, method_onSyncTransferredCallback, pa_source, status, addr.get()); } static void syncTransferNative(JNIEnv* env, jobject object, jint pa_source, jstring addr, jint service_data, jint sync_handle) { if (!sGattIf) return; sGattIf->scanner->TransferSync(str2addr(env, addr), service_data, sync_handle, base::Bind(&onSyncTransferredCb, pa_source)); } static void transferSetInfoNative(JNIEnv* env, jobject object, jint pa_source, jstring addr, jint service_data, jint adv_handle) { if (!sGattIf) return; sGattIf->scanner->TransferSetInfo( str2addr(env, addr), service_data, adv_handle, base::Bind(&onSyncTransferredCb, pa_source)); } 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) { Loading Loading @@ -2358,6 +2403,11 @@ static JNINativeMethod sPeriodicScanMethods[] = { {"cleanupNative", "()V", (void*)periodicScanCleanupNative}, {"startSyncNative", "(ILjava/lang/String;III)V", (void*)startSyncNative}, {"stopSyncNative", "(I)V", (void*)stopSyncNative}, {"cancelSyncNative", "(ILjava/lang/String;)V", (void*)cancelSyncNative}, {"syncTransferNative", "(ILjava/lang/String;II)V", (void*)syncTransferNative}, {"transferSetInfoNative", "(ILjava/lang/String;II)V", (void*)transferSetInfoNative}, }; // JNI functions defined in ScanManager class. Loading
android/app/src/com/android/bluetooth/gatt/GattService.java +40 −1 Original line number Diff line number Diff line Loading @@ -1088,12 +1088,32 @@ public class GattService extends ProfileService { } @Override public void unregisterSync( public void transferSync(BluetoothDevice bda, int serviceData , int syncHandle, AttributionSource attributionSource) { GattService service = getService(); if (service == null) { return; } service.transferSync(bda, serviceData , syncHandle, attributionSource); } @Override public void transferSetInfo(BluetoothDevice bda, int serviceData , int advHandle, IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) { GattService service = getService(); if (service == null) { return; } service.transferSetInfo(bda, serviceData , advHandle, callback, attributionSource); } @Override public void unregisterSync(IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) { GattService service = getService(); if (service == null) { return; } service.unregisterSync(callback, attributionSource); } Loading Loading @@ -2621,6 +2641,25 @@ public class GattService extends ProfileService { mPeriodicScanManager.stopSync(callback); } void transferSync(BluetoothDevice bda, int serviceData, int syncHandle, AttributionSource attributionSource) { if (!Utils.checkScanPermissionForDataDelivery( this, attributionSource, "GattService transferSync")) { return; } mPeriodicScanManager.transferSync(bda, serviceData, syncHandle); } void transferSetInfo(BluetoothDevice bda, int serviceData, int advHandle, IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) { if (!Utils.checkScanPermissionForDataDelivery( this, attributionSource, "GattService transferSetInfo")) { return; } mPeriodicScanManager.transferSetInfo(bda, serviceData, advHandle, callback); } /************************************************************************** * ADVERTISING SET *************************************************************************/ Loading
android/app/src/com/android/bluetooth/gatt/PeriodicScanManager.java +209 −40 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.bluetooth.gatt; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.le.IPeriodicAdvertisingCallback; import android.bluetooth.le.PeriodicAdvertisingReport; import android.bluetooth.le.ScanRecord; Loading @@ -30,6 +32,7 @@ import com.android.bluetooth.btservice.AdapterService; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Manages Bluetooth LE Periodic scans Loading @@ -40,10 +43,12 @@ 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<>()); private final BluetoothAdapter mAdapter; Map<IBinder, SyncInfo> mSyncs = new ConcurrentHashMap<>(); Map<IBinder, SyncTransferInfo> mSyncTransfers = Collections.synchronizedMap(new HashMap<>()); static int sTempRegistrationId = -1; private static final int PA_SOURCE_LOCAL = 1; private static final int PA_SOURCE_REMOTE = 2; /** * Constructor of {@link SyncManager}. */ Loading @@ -51,7 +56,7 @@ class PeriodicScanManager { if (DBG) { Log.d(TAG, "advertise manager created"); } mAdapterService = adapterService; mAdapter = BluetoothAdapter.getDefaultAdapter(); } void start() { Loading @@ -67,21 +72,52 @@ class PeriodicScanManager { sTempRegistrationId = -1; } class SyncTransferInfo { public String address; public SyncDeathRecipient deathRecipient; public IPeriodicAdvertisingCallback callback; SyncTransferInfo(String address, IPeriodicAdvertisingCallback callback) { this.address = address; this.callback = callback; } } class SyncInfo { /* When id is negative, the registration is ongoing. When the registration finishes, id * becomes equal to sync_handle */ public Integer id; public Integer advSid; public String address; public Integer skip; public Integer timeout; public SyncDeathRecipient deathRecipient; public IPeriodicAdvertisingCallback callback; SyncInfo(Integer id, SyncDeathRecipient deathRecipient, SyncInfo(Integer id, Integer advSid, String address, Integer skip, Integer timeout, SyncDeathRecipient deathRecipient, IPeriodicAdvertisingCallback callback) { this.id = id; this.advSid = advSid; this.address = address; this.skip = skip; this.timeout = timeout; this.deathRecipient = deathRecipient; this.callback = callback; } } Map.Entry<IBinder, SyncTransferInfo> findSyncTransfer(String address) { Map.Entry<IBinder, SyncTransferInfo> entry = null; for (Map.Entry<IBinder, SyncTransferInfo> e : mSyncTransfers.entrySet()) { if (e.getValue().address.equals(address)) { entry = e; break; } } return entry; } IBinder toBinder(IPeriodicAdvertisingCallback e) { return ((IInterface) e).asBinder(); } Loading Loading @@ -113,6 +149,33 @@ class PeriodicScanManager { return entry; } Map.Entry<IBinder, SyncInfo> findMatchingSync(int advSid, String address) { Map.Entry<IBinder, SyncInfo> entry = null; for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) { if (e.getValue().advSid == advSid && e.getValue().address.equals(address)) { return entry = e; } } return entry; } Map<IBinder, SyncInfo> findAllSync(int syncHandle) { Map<IBinder, SyncInfo> syncMap = new HashMap<IBinder, SyncInfo>(); for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) { if (e.getValue().id != syncHandle) { continue; } syncMap.put(e.getKey(), new SyncInfo(e.getValue().id, e.getValue().advSid, e.getValue().address, e.getValue().skip, e.getValue().timeout, e.getValue().deathRecipient, e.getValue().callback)); } return syncMap; } void onSyncStarted(int regId, int syncHandle, int sid, int addressType, String address, int phy, int interval, int status) throws Exception { if (DBG) { Loading @@ -120,26 +183,37 @@ class PeriodicScanManager { "onSyncStarted() - regId=" + regId + ", syncHandle=" + syncHandle + ", status=" + status); } Map.Entry<IBinder, SyncInfo> entry = findSync(regId); if (entry == null) { Log.i(TAG, "onSyncStarted() - no callback found for regId " + regId); // Sync was stopped before it was properly registered. Map<IBinder, SyncInfo> syncMap = findAllSync(regId); if (syncMap.size() == 0) { Log.d(TAG, "onSyncStarted() - no callback found for regId " + regId); stopSyncNative(syncHandle); return; } IPeriodicAdvertisingCallback callback = entry.getValue().callback; synchronized (mSyncs) { for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) { if (e.getValue().id != regId) { continue; } IPeriodicAdvertisingCallback callback = e.getValue().callback; if (status == 0) { entry.setValue(new SyncInfo(syncHandle, entry.getValue().deathRecipient, callback)); Log.d(TAG, "onSyncStarted: updating id with syncHandle " + syncHandle); e.setValue(new SyncInfo(syncHandle, sid, address, e.getValue().skip, e.getValue().timeout, e.getValue().deathRecipient, callback)); callback.onSyncEstablished(syncHandle, mAdapter.getRemoteDevice(address), sid, e.getValue().skip, e.getValue().timeout, status); } else { IBinder binder = entry.getKey(); binder.unlinkToDeath(entry.getValue().deathRecipient, 0); callback.onSyncEstablished(syncHandle, mAdapter.getRemoteDevice(address), sid, e.getValue().skip, e.getValue().timeout, status); IBinder binder = e.getKey(); binder.unlinkToDeath(e.getValue().deathRecipient, 0); mSyncs.remove(binder); } // TODO: fix callback arguments // callback.onSyncStarted(syncHandle, tx_power, status); } } } void onSyncReport(int syncHandle, int txPower, int rssi, int dataStatus, byte[] data) Loading @@ -148,33 +222,38 @@ class PeriodicScanManager { Log.d(TAG, "onSyncReport() - syncHandle=" + syncHandle); } Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle); if (entry == null) { Map<IBinder, SyncInfo> syncMap = findAllSync(syncHandle); if (syncMap.isEmpty()) { Log.i(TAG, "onSyncReport() - no callback found for syncHandle " + syncHandle); return; } IPeriodicAdvertisingCallback callback = entry.getValue().callback; for (Map.Entry<IBinder, SyncInfo> e :syncMap.entrySet()) { IPeriodicAdvertisingCallback callback = e.getValue().callback; PeriodicAdvertisingReport report = new PeriodicAdvertisingReport(syncHandle, txPower, rssi, dataStatus, ScanRecord.parseFromBytes(data)); callback.onPeriodicAdvertisingReport(report); } } void onSyncLost(int syncHandle) throws Exception { if (DBG) { Log.d(TAG, "onSyncLost() - syncHandle=" + syncHandle); } Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle); if (entry == null) { Map<IBinder, SyncInfo> syncMap = findAllSync(syncHandle); if (syncMap.isEmpty()) { Log.i(TAG, "onSyncLost() - no callback found for syncHandle " + syncHandle); return; } IPeriodicAdvertisingCallback callback = entry.getValue().callback; mSyncs.remove(entry); for (Map.Entry<IBinder, SyncInfo> e :syncMap.entrySet()) { IPeriodicAdvertisingCallback callback = e.getValue().callback; IBinder binder = toBinder(callback); synchronized (mSyncs) { mSyncs.remove(binder); } callback.onSyncLost(syncHandle); } } void startSync(ScanResult scanResult, int skip, int timeout, Loading @@ -189,9 +268,38 @@ class PeriodicScanManager { String address = scanResult.getDevice().getAddress(); int sid = scanResult.getAdvertisingSid(); if (DBG) { Log.d(TAG, "startSync for Device: " + address + " sid: " + sid); } synchronized (mSyncs) { Map.Entry<IBinder, SyncInfo> entry = findMatchingSync(sid, address); if (entry != null) { //Found matching sync. Copy sync handle if (DBG) { Log.d(TAG, "startSync: Matching entry found"); } mSyncs.put(binder, new SyncInfo(entry.getValue().id, sid, address, entry.getValue().skip, entry.getValue().timeout, deathRecipient, callback)); if (entry.getValue().id >= 0) { try { callback.onSyncEstablished(entry.getValue().id, mAdapter.getRemoteDevice(address), sid, entry.getValue().skip, entry.getValue().timeout, 0 /*success*/); } catch (RemoteException e) { throw new IllegalArgumentException("Can't invoke callback"); } } else { Log.d(TAG, "startSync(): sync pending for same remote"); } return; } } int cbId = --sTempRegistrationId; mSyncs.put(binder, new SyncInfo(cbId, deathRecipient, callback)); mSyncs.put(binder, new SyncInfo(cbId, sid, address, skip, timeout, deathRecipient, callback)); if (DBG) { Log.d(TAG, "startSync() - reg_id=" + cbId + ", callback: " + binder); Loading @@ -204,8 +312,10 @@ class PeriodicScanManager { if (DBG) { Log.d(TAG, "stopSync() " + binder); } SyncInfo sync = mSyncs.remove(binder); SyncInfo sync = null; synchronized (mSyncs) { sync = mSyncs.remove(binder); } if (sync == null) { Log.e(TAG, "stopSync() - no client found for callback"); return; Loading @@ -213,14 +323,65 @@ class PeriodicScanManager { Integer syncHandle = sync.id; binder.unlinkToDeath(sync.deathRecipient, 0); Log.d(TAG, "stopSync: " + syncHandle); synchronized (mSyncs) { Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle); if (entry != null) { Log.d(TAG, "stopSync() - another app synced to same PA, not stopping sync"); return; } } Log.d(TAG, "calling stopSyncNative: " + syncHandle.intValue()); if (syncHandle < 0) { Log.i(TAG, "stopSync() - not finished registration yet"); // Sync will be freed once initiated in onSyncStarted() Log.i(TAG, "cancelSync() - sync not established yet"); cancelSyncNative(sync.advSid, sync.address); } else { stopSyncNative(syncHandle.intValue()); } } void onSyncTransferredCallback(int paSource, int status, String bda) { Log.d(TAG, "onSyncTransferredCallback()"); Map.Entry<IBinder, SyncTransferInfo> entry = findSyncTransfer(bda); if (entry != null) { mSyncTransfers.remove(entry); IPeriodicAdvertisingCallback callback = entry.getValue().callback; try { callback.onSyncTransferred(mAdapter.getRemoteDevice(bda), status); } catch (RemoteException e) { throw new IllegalArgumentException("Can't find callback for sync transfer"); } } } void transferSync(BluetoothDevice bda, int serviceData, int syncHandle) { Log.d(TAG, "transferSync()"); Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle); if (entry == null) { Log.d(TAG, "transferSync: callback not registered"); return; } //check for duplicate transfers mSyncTransfers.put(entry.getKey(), new SyncTransferInfo(bda.getAddress(), entry.getValue().callback)); syncTransferNative(PA_SOURCE_REMOTE, bda.getAddress(), serviceData, syncHandle); } stopSyncNative(syncHandle); void transferSetInfo(BluetoothDevice bda, int serviceData, int advHandle, IPeriodicAdvertisingCallback callback) { SyncDeathRecipient deathRecipient = new SyncDeathRecipient(callback); IBinder binder = toBinder(callback); if (DBG) { Log.d(TAG, "transferSetInfo() " + binder); } try { binder.linkToDeath(deathRecipient, 0); } catch (RemoteException e) { throw new IllegalArgumentException("Can't link to periodic scanner death"); } mSyncTransfers.put(binder, new SyncTransferInfo(bda.getAddress(), callback)); transferSetInfoNative(PA_SOURCE_LOCAL, bda.getAddress(), serviceData, advHandle); } static { Loading @@ -236,4 +397,12 @@ class PeriodicScanManager { private native void startSyncNative(int sid, String address, int skip, int timeout, int regId); private native void stopSyncNative(int syncHandle); private native void cancelSyncNative(int sid, String address); private native void syncTransferNative(int paSource, String address, int serviceData, int syncHandle); private native void transferSetInfoNative(int paSource, String address, int serviceData, int advHandle); }
framework/java/android/bluetooth/le/PeriodicAdvertisingCallback.java +9 −0 Original line number Diff line number Diff line Loading @@ -78,4 +78,13 @@ public abstract class PeriodicAdvertisingCallback { */ public void onSyncLost(int syncHandle) { } /** * Callback when periodic sync transferred. * * @param device * @param status */ public void onSyncTransferred(BluetoothDevice device, int status) { } }
framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java +83 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.bluetooth.le; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.bluetooth.Attributable; Loading Loading @@ -215,6 +216,79 @@ public final class PeriodicAdvertisingManager { } } /** * Transfer periodic sync * * @hide */ public void transferSync(BluetoothDevice bda, int serviceData, int syncHandle) { IBluetoothGatt gatt; try { gatt = mBluetoothManager.getBluetoothGatt(); } catch (RemoteException e) { Log.e(TAG, "Failed to get Bluetooth gatt - ", e); PeriodicAdvertisingCallback callback = null; for (PeriodicAdvertisingCallback cb : mCallbackWrappers.keySet()) { callback = cb; } if (callback != null) { callback.onSyncTransferred(bda, PeriodicAdvertisingCallback.SYNC_NO_RESOURCES); } return; } try { gatt.transferSync(bda, serviceData , syncHandle, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to register sync - ", e); return; } } /** * Transfer set info * * @hide */ public void transferSetInfo(BluetoothDevice bda, int serviceData, int advHandle, PeriodicAdvertisingCallback callback) { transferSetInfo(bda, serviceData, advHandle, callback, null); } /** * Transfer set info * * @hide */ public void transferSetInfo(BluetoothDevice bda, int serviceData, int advHandle, PeriodicAdvertisingCallback callback, @Nullable Handler handler) { 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; } if (handler == null) { handler = new Handler(Looper.getMainLooper()); } IPeriodicAdvertisingCallback wrapper = wrap(callback, handler); if (wrapper == null) { throw new IllegalArgumentException("callback was not properly registered"); } try { gatt.transferSetInfo(bda, serviceData , advHandle, wrapper, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to register sync - ", e); return; } } @SuppressLint("AndroidFrameworkBluetoothPermission") private IPeriodicAdvertisingCallback wrap(PeriodicAdvertisingCallback callback, Handler handler) { Loading Loading @@ -259,6 +333,15 @@ public final class PeriodicAdvertisingManager { } }); } public void onSyncTransferred(BluetoothDevice device, int status) { handler.post(new Runnable() { @Override public void run() { callback.onSyncTransferred(device, status); } }); } }; } }