Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c808a4e4 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Periodic sync and PAST implementation" am: 0a916220 am: e90c9f86

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/1908371

Change-Id: Id5017190e7786319064f26a71b2505b8aada7ffe
parents 8e57af98 e90c9f86
Loading
Loading
Loading
Loading
+56 −6
Original line number Diff line number Diff line
@@ -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
 */
@@ -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) {
@@ -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,
@@ -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) {
@@ -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.
+40 −1
Original line number Diff line number Diff line
@@ -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);
        }

@@ -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
     *************************************************************************/
+209 −40
Original line number Diff line number Diff line
@@ -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;
@@ -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
@@ -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}.
     */
@@ -51,7 +56,7 @@ class PeriodicScanManager {
        if (DBG) {
            Log.d(TAG, "advertise manager created");
        }
        mAdapterService = adapterService;
        mAdapter = BluetoothAdapter.getDefaultAdapter();
    }

    void start() {
@@ -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();
    }
@@ -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) {
@@ -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)
@@ -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,
@@ -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);
@@ -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;
@@ -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 {
@@ -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);
}
+9 −0
Original line number Diff line number Diff line
@@ -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) {
    }
}
+83 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.bluetooth.le;

import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.bluetooth.Attributable;
@@ -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) {
@@ -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