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

Commit 1c77e46f authored by Jakub Pawlowski's avatar Jakub Pawlowski
Browse files

Bluetooth 5 periodic scan (1/3)

This patch wires calls/callbacks through stack to prepare for actual
implementation.

Test: manual
Bug: 30622771
Change-Id: I9e6d81ccbbe1626de96f766813b048909e9ef452
parent 77e77ed4
Loading
Loading
Loading
Loading
+97 −0
Original line number Diff line number Diff line
@@ -198,6 +198,13 @@ static jmethodID method_onPeriodicAdvertisingParametersUpdated;
static jmethodID method_onPeriodicAdvertisingDataSet;
static jmethodID method_onPeriodicAdvertisingEnabled;

/**
 * Periodic scanner callback methods
 */
static jmethodID method_onSyncLost;
static jmethodID method_onSyncReport;
static jmethodID method_onSyncStarted;

/**
 * Static variables
 */
@@ -205,6 +212,7 @@ static jmethodID method_onPeriodicAdvertisingEnabled;
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 @@ static void setPeriodicAdvertisingEnableNative(JNIEnv* env, jobject object,
      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 @@ static JNINativeMethod sAdvertiseMethods[] = {
     (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 @@ int register_com_android_bluetooth_gatt(JNIEnv* env) {
  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));
+8 −2
Original line number Diff line number Diff line
@@ -142,6 +142,7 @@ public class GattService extends ProfileService {
            new HashMap<Integer, List<BluetoothGattService>>();

    private AdvertiseManager mAdvertiseManager;
    private PeriodicScanManager mPeriodicScanManager;
    private ScanManager mScanManager;
    private AppOpsManager mAppOps;

@@ -172,6 +173,9 @@ public class GattService extends ProfileService {
        mScanManager = new ScanManager(this);
        mScanManager.start();

        mPeriodicScanManager = new PeriodicScanManager(AdapterService.getAdapterService());
        mPeriodicScanManager.start();

        return true;
    }

@@ -184,6 +188,7 @@ public class GattService extends ProfileService {
        mReliableQueue.clear();
        if (mAdvertiseManager != null) mAdvertiseManager.cleanup();
        if (mScanManager != null) mScanManager.cleanup();
        if (mPeriodicScanManager != null) mPeriodicScanManager.cleanup();
        return true;
    }

@@ -192,6 +197,7 @@ public class GattService extends ProfileService {
        cleanupNative();
        if (mAdvertiseManager != null) mAdvertiseManager.cleanup();
        if (mScanManager != null) mScanManager.cleanup();
        if (mPeriodicScanManager != null) mPeriodicScanManager.cleanup();
        return true;
    }

@@ -1487,12 +1493,12 @@ public class GattService extends ProfileService {
    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);
    }

    /**************************************************************************
+230 −0
Original line number Diff line number Diff line
/*
 * 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);
}