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

Commit be672e8e authored by Łukasz Rymanowski's avatar Łukasz Rymanowski Committed by Jakub Pawlowski
Browse files

csip: Add Coordinated Set Identification Profile implementation

Tag: #feature
Test: atest CsipSetCoordinatorServiceTest CsipSetCoordinatorStateMachineTest
Bug: 150670922
Sponsor: jpawlowski@
Merged-In: I9d4590b68b20ce23b849e024575224db427bd224
Change-Id: I9d4590b68b20ce23b849e024575224db427bd224
parent 555794fd
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -161,6 +161,8 @@ int register_com_android_bluetooth_btservice_activity_attribution(JNIEnv* env);
int register_com_android_bluetooth_le_audio(JNIEnv* env);
int register_com_android_bluetooth_le_audio(JNIEnv* env);


int register_com_android_bluetooth_vc(JNIEnv* env);
int register_com_android_bluetooth_vc(JNIEnv* env);

int register_com_android_bluetooth_csip_set_coordinator(JNIEnv* env);
}  // namespace android
}  // namespace android


#endif /* COM_ANDROID_BLUETOOTH_H */
#endif /* COM_ANDROID_BLUETOOTH_H */
+6 −0
Original line number Original line Diff line number Diff line
@@ -1841,5 +1841,11 @@ jint JNI_OnLoad(JavaVM* jvm, void* reserved) {
    return JNI_ERR;
    return JNI_ERR;
  }
  }


  status = android::register_com_android_bluetooth_csip_set_coordinator(e);
  if (status < 0) {
    ALOGE("jni csis client registration failure: %d", status);
    return JNI_ERR;
  }

  return JNI_VERSION_1_6;
  return JNI_VERSION_1_6;
}
}
+296 −0
Original line number Original line Diff line number Diff line

/*
 * Copyright 2021 HIMSA II K/S - www.himsa.com.
 * Represented by EHIMA - www.ehima.com
 *
 * 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.
 */
#define LOG_TAG "BluetoothCsipSetCoordinatorJni"
#define LOG_NDEBUG 0
#include <string.h>

#include <shared_mutex>

#include "base/logging.h"
#include "com_android_bluetooth.h"
#include "hardware/bt_csis.h"

using bluetooth::csis::ConnectionState;
using bluetooth::csis::CsisClientCallbacks;
using bluetooth::csis::CsisClientInterface;
using bluetooth::csis::CsisGroupLockStatus;

namespace android {
static jmethodID method_onConnectionStateChanged;
static jmethodID method_onDeviceAvailable;
static jmethodID method_onSetMemberAvailable;
static jmethodID method_onGroupLockChanged;

static CsisClientInterface* sCsisClientInterface = nullptr;
static std::shared_timed_mutex interface_mutex;

static jobject mCallbacksObj = nullptr;
static std::shared_timed_mutex callbacks_mutex;

using bluetooth::Uuid;

#define UUID_PARAMS(uuid) uuid_lsb(uuid), uuid_msb(uuid)

static uint64_t uuid_lsb(const Uuid& uuid) {
  uint64_t lsb = 0;

  auto uu = uuid.To128BitBE();
  for (int i = 8; i <= 15; i++) {
    lsb <<= 8;
    lsb |= uu[i];
  }

  return lsb;
}

static uint64_t uuid_msb(const Uuid& uuid) {
  uint64_t msb = 0;

  auto uu = uuid.To128BitBE();
  for (int i = 0; i <= 7; i++) {
    msb <<= 8;
    msb |= uu[i];
  }

  return msb;
}

class CsisClientCallbacksImpl : public CsisClientCallbacks {
 public:
  ~CsisClientCallbacksImpl() = default;

  void OnConnectionState(const RawAddress& bd_addr,
                         ConnectionState state) override {
    LOG(INFO) << __func__;

    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
    CallbackEnv sCallbackEnv(__func__);
    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;

    ScopedLocalRef<jbyteArray> addr(
        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
    if (!addr.get()) {
      LOG(ERROR) << "Failed to new bd addr jbyteArray for connection state";
      return;
    }

    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
                                     (jbyte*)&bd_addr);
    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,
                                 addr.get(), (jint)state);
  }

  void OnDeviceAvailable(const RawAddress& bd_addr, int group_id,
                         int group_size, const bluetooth::Uuid& uuid) override {
    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
    CallbackEnv sCallbackEnv(__func__);
    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;

    ScopedLocalRef<jbyteArray> addr(
        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
    if (!addr.get()) {
      LOG(ERROR) << "Failed to new bd addr jbyteArray for device available";
      return;
    }
    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
                                     (jbyte*)&bd_addr);

    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onDeviceAvailable,
                                 addr.get(), (jint)group_id, (jint)group_size,
                                 UUID_PARAMS(uuid));
  }

  void OnSetMemberAvailable(const RawAddress& bd_addr, int group_id) override {
    LOG(INFO) << __func__ << ", group id:" << group_id;

    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
    CallbackEnv sCallbackEnv(__func__);
    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;

    ScopedLocalRef<jbyteArray> addr(
        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
    if (!addr.get()) {
      LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
      return;
    }

    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
                                     (jbyte*)&bd_addr);
    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetMemberAvailable,
                                 addr.get(), (jint)group_id);
  }

  void OnGroupLockChanged(int group_id, bool locked,
                          CsisGroupLockStatus status) override {
    LOG(INFO) << __func__ << ", group_id: " << int(group_id)
              << ", locked: " << locked << ", status: " << (int)status;

    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
    CallbackEnv sCallbackEnv(__func__);
    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;

    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGroupLockChanged,
                                 (jint)group_id, (jboolean)locked,
                                 (jint)status);
  }
};

static CsisClientCallbacksImpl sCsisClientCallbacks;

static void classInitNative(JNIEnv* env, jclass clazz) {
  method_onConnectionStateChanged =
      env->GetMethodID(clazz, "onConnectionStateChanged", "([BI)V");

  method_onDeviceAvailable =
      env->GetMethodID(clazz, "onDeviceAvailable", "([BIIJJ)V");

  method_onSetMemberAvailable =
      env->GetMethodID(clazz, "onSetMemberAvailable", "([BI)V");

  method_onGroupLockChanged =
      env->GetMethodID(clazz, "onGroupLockChanged", "(IZI)V");

  LOG(INFO) << __func__ << ": succeeds";
}

static void initNative(JNIEnv* env, jobject object) {
  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
  std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);

  const bt_interface_t* btInf = getBluetoothInterface();
  if (btInf == nullptr) {
    LOG(ERROR) << "Bluetooth module is not loaded";
    return;
  }

  if (sCsisClientInterface != nullptr) {
    LOG(INFO) << "Cleaning up Csis Interface before initializing...";
    sCsisClientInterface->Cleanup();
    sCsisClientInterface = nullptr;
  }

  if (mCallbacksObj != nullptr) {
    LOG(INFO) << "Cleaning up Csis callback object";
    env->DeleteGlobalRef(mCallbacksObj);
    mCallbacksObj = nullptr;
  }

  if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) {
    LOG(ERROR) << "Failed to allocate Global Ref for Csis Client Callbacks";
    return;
  }

  sCsisClientInterface = (CsisClientInterface*)btInf->get_profile_interface(
      BT_PROFILE_CSIS_CLIENT_ID);
  if (sCsisClientInterface == nullptr) {
    LOG(ERROR) << "Failed to get Csis Client Interface";
    return;
  }

  sCsisClientInterface->Init(&sCsisClientCallbacks);
}

static void cleanupNative(JNIEnv* env, jobject object) {
  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
  std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);

  const bt_interface_t* btInf = getBluetoothInterface();
  if (btInf == nullptr) {
    LOG(ERROR) << "Bluetooth module is not loaded";
    return;
  }

  if (sCsisClientInterface != nullptr) {
    sCsisClientInterface->Cleanup();
    sCsisClientInterface = nullptr;
  }

  if (mCallbacksObj != nullptr) {
    env->DeleteGlobalRef(mCallbacksObj);
    mCallbacksObj = nullptr;
  }
}

static jboolean connectNative(JNIEnv* env, jobject object, jbyteArray address) {
  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
  if (!sCsisClientInterface) {
    LOG(ERROR) << __func__
               << ": Failed to get the Csis Client Interface Interface";
    return JNI_FALSE;
  }

  jbyte* addr = env->GetByteArrayElements(address, nullptr);
  if (!addr) {
    jniThrowIOException(env, EINVAL);
    return JNI_FALSE;
  }

  RawAddress* tmpraw = (RawAddress*)addr;
  sCsisClientInterface->Connect(*tmpraw);
  env->ReleaseByteArrayElements(address, addr, 0);
  return JNI_TRUE;
}

static jboolean disconnectNative(JNIEnv* env, jobject object,
                                 jbyteArray address) {
  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
  if (!sCsisClientInterface) {
    LOG(ERROR) << __func__ << ": Failed to get the Csis Client Interface";
    return JNI_FALSE;
  }

  jbyte* addr = env->GetByteArrayElements(address, nullptr);
  if (!addr) {
    jniThrowIOException(env, EINVAL);
    return JNI_FALSE;
  }

  RawAddress* tmpraw = (RawAddress*)addr;
  sCsisClientInterface->Disconnect(*tmpraw);
  env->ReleaseByteArrayElements(address, addr, 0);
  return JNI_TRUE;
}

static void groupLockSetNative(JNIEnv* env, jobject object, jint group_id,
                               jboolean lock) {
  LOG(INFO) << __func__;

  if (!sCsisClientInterface) {
    LOG(ERROR) << __func__
               << ": Failed to get the Bluetooth Csis Client Interface";
    return;
  }

  sCsisClientInterface->LockGroup(group_id, lock);
}

static JNINativeMethod sMethods[] = {
    {"classInitNative", "()V", (void*)classInitNative},
    {"initNative", "()V", (void*)initNative},
    {"cleanupNative", "()V", (void*)cleanupNative},
    {"connectNative", "([B)Z", (void*)connectNative},
    {"disconnectNative", "([B)Z", (void*)disconnectNative},
    {"groupLockSetNative", "(IZ)V", (void*)groupLockSetNative},
};

int register_com_android_bluetooth_csip_set_coordinator(JNIEnv* env) {
  return jniRegisterNativeMethods(
      env, "com/android/bluetooth/csip/CsipSetCoordinatorNativeInterface",
      sMethods, NELEM(sMethods));
}
}  // namespace android
+33 −2
Original line number Original line Diff line number Diff line
@@ -82,7 +82,6 @@ import android.text.TextUtils;
import android.util.Base64;
import android.util.Base64;
import android.util.Log;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseArray;

import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.Utils;
import com.android.bluetooth.Utils;
@@ -93,6 +92,7 @@ import com.android.bluetooth.btservice.activityattribution.ActivityAttributionSe
import com.android.bluetooth.btservice.bluetoothkeystore.BluetoothKeystoreService;
import com.android.bluetooth.btservice.bluetoothkeystore.BluetoothKeystoreService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.btservice.storage.MetadataDatabase;
import com.android.bluetooth.btservice.storage.MetadataDatabase;
import com.android.bluetooth.csip.CsipSetCoordinatorService;
import com.android.bluetooth.gatt.GattService;
import com.android.bluetooth.gatt.GattService;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.hfp.HeadsetService;
@@ -274,6 +274,7 @@ public class AdapterService extends Service {
    private HearingAidService mHearingAidService;
    private HearingAidService mHearingAidService;
    private SapService mSapService;
    private SapService mSapService;
    private VolumeControlService mVolumeControlService;
    private VolumeControlService mVolumeControlService;
    private CsipSetCoordinatorService mCsipSetCoordinatorService;


    /**
    /**
     * Register a {@link ProfileService} with AdapterService.
     * Register a {@link ProfileService} with AdapterService.
@@ -918,6 +919,9 @@ public class AdapterService extends Service {
        if (profile == BluetoothProfile.VOLUME_CONTROL) {
        if (profile == BluetoothProfile.VOLUME_CONTROL) {
            return Utils.arrayContains(remoteDeviceUuids, BluetoothUuid.VOLUME_CONTROL);
            return Utils.arrayContains(remoteDeviceUuids, BluetoothUuid.VOLUME_CONTROL);
        }
        }
        if (profile == BluetoothProfile.CSIP_SET_COORDINATOR) {
            return Utils.arrayContains(remoteDeviceUuids, BluetoothUuid.COORDINATED_SET);
        }


        Log.e(TAG, "isSupported: Unexpected profile passed in to function: " + profile);
        Log.e(TAG, "isSupported: Unexpected profile passed in to function: " + profile);
        return false;
        return false;
@@ -971,7 +975,11 @@ public class AdapterService extends Service {
                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
            return true;
            return true;
        }
        }

        if (mCsipSetCoordinatorService != null
                && mCsipSetCoordinatorService.getConnectionPolicy(device)
                        > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
            return true;
        }
        return false;
        return false;
    }
    }


@@ -1051,6 +1059,14 @@ public class AdapterService extends Service {
            Log.i(TAG, "connectEnabledProfiles: Connecting Volume Control Profile");
            Log.i(TAG, "connectEnabledProfiles: Connecting Volume Control Profile");
            mVolumeControlService.connect(device);
            mVolumeControlService.connect(device);
        }
        }
        if (mCsipSetCoordinatorService != null
                && isSupported(localDeviceUuids, remoteDeviceUuids,
                        BluetoothProfile.CSIP_SET_COORDINATOR, device)
                && mCsipSetCoordinatorService.getConnectionPolicy(device)
                        > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
            Log.i(TAG, "connectEnabledProfiles: Connecting Coordinated Set Profile");
            mCsipSetCoordinatorService.connect(device);
        }


        return true;
        return true;
    }
    }
@@ -1089,6 +1105,7 @@ public class AdapterService extends Service {
        mHearingAidService = HearingAidService.getHearingAidService();
        mHearingAidService = HearingAidService.getHearingAidService();
        mSapService = SapService.getSapService();
        mSapService = SapService.getSapService();
        mVolumeControlService = VolumeControlService.getVolumeControlService();
        mVolumeControlService = VolumeControlService.getVolumeControlService();
        mCsipSetCoordinatorService = CsipSetCoordinatorService.getCsipSetCoordinatorService();
    }
    }


    private boolean isAvailable() {
    private boolean isAvailable() {
@@ -2660,6 +2677,14 @@ public class AdapterService extends Service {
                    BluetoothProfile.CONNECTION_POLICY_ALLOWED);
                    BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            numProfilesConnected++;
            numProfilesConnected++;
        }
        }
        if (mCsipSetCoordinatorService != null
                && isSupported(localDeviceUuids, remoteDeviceUuids,
                        BluetoothProfile.CSIP_SET_COORDINATOR, device)) {
            Log.i(TAG, "connectAllEnabledProfiles: Connecting Coordinated Set Profile");
            mCsipSetCoordinatorService.setConnectionPolicy(
                    device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            numProfilesConnected++;
        }


        Log.i(TAG, "connectAllEnabledProfiles: Number of Profiles Connected: "
        Log.i(TAG, "connectAllEnabledProfiles: Number of Profiles Connected: "
                + numProfilesConnected);
                + numProfilesConnected);
@@ -2750,6 +2775,12 @@ public class AdapterService extends Service {
            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Sap Profile");
            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Sap Profile");
            mSapService.disconnect(device);
            mSapService.disconnect(device);
        }
        }
        if (mCsipSetCoordinatorService != null
                && mCsipSetCoordinatorService.getConnectionState(device)
                        == BluetoothProfile.STATE_CONNECTED) {
            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Coordinater Set Profile");
            mCsipSetCoordinatorService.disconnect(device);
        }


        return true;
        return true;
    }
    }
+2 −2
Original line number Original line Diff line number Diff line
@@ -294,7 +294,7 @@ public class DatabaseManager {
     * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
     * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
     * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
     * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
     * {@link BluetoothProfile#HEARING_AID}, {@link BluetoothProfile#LE_AUDIO},
     * {@link BluetoothProfile#HEARING_AID}, {@link BluetoothProfile#LE_AUDIO},
     * {@link BluetoothProfile#VOLUME_CONTROL}
     * {@link BluetoothProfile#VOLUME_CONTROL}, {@link BluetoothProfile#CSIP_SET_COORDINATOR},
     * @param newConnectionPolicy the connectionPolicy to set; one of
     * @param newConnectionPolicy the connectionPolicy to set; one of
     * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN},
     * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN},
     * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN},
     * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN},
@@ -351,7 +351,7 @@ public class DatabaseManager {
     * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
     * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
     * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
     * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
     * {@link BluetoothProfile#HEARING_AID}, {@link BluetoothProfile#LE_AUDIO},
     * {@link BluetoothProfile#HEARING_AID}, {@link BluetoothProfile#LE_AUDIO},
     * {@link BluetoothProfile#VOLUME_CONTROL}
     * {@link BluetoothProfile#VOLUME_CONTROL}, {@link BluetoothProfile#CSIP_SET_COORDINATOR},
     * @return the profile connection policy of the device; one of
     * @return the profile connection policy of the device; one of
     * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN},
     * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN},
     * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN},
     * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN},
Loading