diff options
author | 2024-08-21 18:07:29 +0000 | |
---|---|---|
committer | 2024-11-05 22:08:58 +0000 | |
commit | 0dc741f8e299eba896cbf9f5ced8139e182d820b (patch) | |
tree | e485088173aac942826171bd158536cb35c04914 | |
parent | 4521cb172878ee51b82cc5115d90975ad3298b1b (diff) |
Framework implementation for HCI Vendor Specific Handling
Bug: 360924438
Test: mma -j32
Flag: com.android.bluetooth.flags.hci_vendor_specific_extension
Change-Id: I02baab09782a0e8fc88d36c7d9bc4c4fa518fb8c
17 files changed, 981 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp index 476d018736..fde5137bba 100644 --- a/Android.bp +++ b/Android.bp @@ -154,6 +154,7 @@ java_defaults { "-Xep:OperatorPrecedence:ERROR", "-Xep:ReferenceEquality:ERROR", "-Xep:ReturnAtTheEndOfVoidFunction:ERROR", + "-Xep:ReturnFromVoid:ERROR", "-Xep:StaticAssignmentInConstructor:ERROR", "-Xep:StaticGuardedByInstance:ERROR", "-Xep:StringCaseLocaleUsage:ERROR", diff --git a/android/app/aidl/Android.bp b/android/app/aidl/Android.bp index 37678c2917..03f2d109ce 100644 --- a/android/app/aidl/Android.bp +++ b/android/app/aidl/Android.bp @@ -27,6 +27,7 @@ filegroup { "android/bluetooth/IBluetoothGattServerCallback.aidl", "android/bluetooth/IBluetoothHapClient.aidl", "android/bluetooth/IBluetoothHapClientCallback.aidl", + "android/bluetooth/IBluetoothHciVendorSpecificCallback.aidl", "android/bluetooth/IBluetoothHeadset.aidl", "android/bluetooth/IBluetoothHeadsetClient.aidl", "android/bluetooth/IBluetoothHearingAid.aidl", diff --git a/android/app/aidl/android/bluetooth/IBluetooth.aidl b/android/app/aidl/android/bluetooth/IBluetooth.aidl index 28cb07db6e..83cbe469ae 100644 --- a/android/app/aidl/android/bluetooth/IBluetooth.aidl +++ b/android/app/aidl/android/bluetooth/IBluetooth.aidl @@ -19,6 +19,7 @@ package android.bluetooth; import android.app.PendingIntent; import android.bluetooth.IBluetoothActivityEnergyInfoListener; import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothHciVendorSpecificCallback; import android.bluetooth.IBluetoothPreferredAudioProfilesCallback; import android.bluetooth.IBluetoothQualityReportReadyCallback; import android.bluetooth.IBluetoothCallback; @@ -293,6 +294,13 @@ interface IBluetooth @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") int unregisterBluetoothQualityReportReadyCallback(in IBluetoothQualityReportReadyCallback callback, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)") + void registerHciVendorSpecificCallback(in IBluetoothHciVendorSpecificCallback callback, in int[] eventCodes); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)") + void unregisterHciVendorSpecificCallback(in IBluetoothHciVendorSpecificCallback callback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)") + void sendHciVendorSpecificCommand(in int ocf, in byte[] parameters, in IBluetoothHciVendorSpecificCallback callback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") int getOffloadedTransportDiscoveryDataScanSupported(in AttributionSource attributionSource); diff --git a/android/app/aidl/android/bluetooth/IBluetoothHciVendorSpecificCallback.aidl b/android/app/aidl/android/bluetooth/IBluetoothHciVendorSpecificCallback.aidl new file mode 100644 index 0000000000..2aac21b158 --- /dev/null +++ b/android/app/aidl/android/bluetooth/IBluetoothHciVendorSpecificCallback.aidl @@ -0,0 +1,26 @@ +/* + * Copyright 2024 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 android.bluetooth; + +/** + * Callback definitions for HCI Vendor Specific Callback. + * @hide + */ +oneway interface IBluetoothHciVendorSpecificCallback { + void onCommandStatus(int ocf, in int status); + void onCommandComplete(int ocf, in byte[] returnParameters); + void onEvent(int code, in byte[] data); +} diff --git a/android/app/jni/com_android_bluetooth.h b/android/app/jni/com_android_bluetooth.h index c473b50ac6..d83e6a9f74 100644 --- a/android/app/jni/com_android_bluetooth.h +++ b/android/app/jni/com_android_bluetooth.h @@ -162,6 +162,8 @@ int register_com_android_bluetooth_csip_set_coordinator(JNIEnv* env); int register_com_android_bluetooth_btservice_BluetoothQualityReport(JNIEnv* env); +int register_com_android_bluetooth_btservice_BluetoothHciVendorSpecific(JNIEnv* env); + struct JNIJavaMethod { const char* name; const char* signature; diff --git a/android/app/jni/com_android_bluetooth_BluetoothHciVendorSpecific.cpp b/android/app/jni/com_android_bluetooth_BluetoothHciVendorSpecific.cpp new file mode 100644 index 0000000000..96c15300af --- /dev/null +++ b/android/app/jni/com_android_bluetooth_BluetoothHciVendorSpecific.cpp @@ -0,0 +1,219 @@ +/* + * Copyright 2024 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. + */ + +#define LOG_TAG "BluetoothHciVendorSpecificJni" + +#include <shared_mutex> + +#include "btif/include/btif_hci_vs.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_hci_vs.h" + +using bluetooth::hci_vs::BluetoothHciVendorSpecificInterface; +using bluetooth::hci_vs::Cookie; + +namespace android { + +static std::shared_timed_mutex interface_mutex; +static std::shared_timed_mutex callbacks_mutex; + +static jmethodID method_onCommandStatus; +static jmethodID method_onCommandComplete; +static jmethodID method_onEvent; +static jobject mCallbacksObj = nullptr; + +class BluetoothHciVendorSpecificCallbacksImpl + : public bluetooth::hci_vs::BluetoothHciVendorSpecificCallbacks { +public: + ~BluetoothHciVendorSpecificCallbacksImpl() = default; + + void onCommandStatus(uint16_t ocf, uint8_t status, Cookie cookie) override { + log::info(""); + std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); + + CallbackEnv callbackEnv(__func__); + if (!callbackEnv.valid() || mCallbacksObj == nullptr) { + return; + } + + auto j_cookie = toJByteArray(callbackEnv.get(), cookie); + if (!j_cookie.get()) { + log::error("Error while allocating byte array for cookie"); + return; + } + + callbackEnv->CallVoidMethod(mCallbacksObj, method_onCommandStatus, (jint)ocf, (jint)status, + j_cookie.get()); + } + + void onCommandComplete(uint16_t ocf, std::vector<uint8_t> return_parameters, + Cookie cookie) override { + log::info(""); + std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); + + CallbackEnv callbackEnv(__func__); + if (!callbackEnv.valid() || mCallbacksObj == nullptr) { + return; + } + + auto j_return_parameters = toJByteArray(callbackEnv.get(), return_parameters); + auto j_cookie = toJByteArray(callbackEnv.get(), cookie); + if (!j_return_parameters.get() || !j_cookie.get()) { + log::error("Error while allocating byte array for return parameters or cookie"); + return; + } + + callbackEnv->CallVoidMethod(mCallbacksObj, method_onCommandComplete, (jint)ocf, + j_return_parameters.get(), j_cookie.get()); + } + + void onEvent(uint8_t code, std::vector<uint8_t> data) override { + log::info(""); + std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); + + CallbackEnv callbackEnv(__func__); + if (!callbackEnv.valid() || mCallbacksObj == nullptr) { + return; + } + + auto j_data = toJByteArray(callbackEnv.get(), data); + if (!j_data.get()) { + log::error("Error while allocating byte array for event data"); + return; + } + + callbackEnv->CallVoidMethod(mCallbacksObj, method_onEvent, (jint)code, j_data.get()); + } + +private: + template <typename T> + static ScopedLocalRef<jbyteArray> toJByteArray(JNIEnv* env, const T& src) { + ScopedLocalRef<jbyteArray> dst(env, env->NewByteArray(src.size())); + if (dst.get()) { + env->SetByteArrayRegion(dst.get(), 0, src.size(), reinterpret_cast<const jbyte*>(src.data())); + } + return dst; + } +}; + +static BluetoothHciVendorSpecificInterface* sBluetoothHciVendorSpecificInterface = nullptr; +static BluetoothHciVendorSpecificCallbacksImpl sBluetoothHciVendorSpecificCallbacks; + +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); + + if (sBluetoothHciVendorSpecificInterface != nullptr) { + log::info("Cleaning up BluetoothHciVendorSpecific Interface before initializing..."); + sBluetoothHciVendorSpecificInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + log::info("Cleaning up BluetoothHciVendorSpecific callback object"); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + log::error("Failed to allocate Global Ref for BluetoothHciVendorSpecific Callbacks"); + return; + } + + sBluetoothHciVendorSpecificInterface = + bluetooth::hci_vs::getBluetoothHciVendorSpecificInterface(); + if (sBluetoothHciVendorSpecificInterface == nullptr) { + log::error("Failed to get BluetoothHciVendorSpecific Interface"); + return; + } + + sBluetoothHciVendorSpecificInterface->init(&sBluetoothHciVendorSpecificCallbacks); +} + +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); + + if (sBluetoothHciVendorSpecificInterface != nullptr) { + sBluetoothHciVendorSpecificInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + +static void sendCommandNative(JNIEnv* env, jobject /* obj */, jint ocf, jbyteArray parametersArray, + jbyteArray uuidArray) { + std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex); + log::verbose(""); + if (!sBluetoothHciVendorSpecificInterface) { + return; + } + + jbyte* pParameters = env->GetByteArrayElements(parametersArray, NULL); + jbyte* pUuid = env->GetByteArrayElements(uuidArray, NULL); + if (pParameters && pUuid && env->GetArrayLength(uuidArray) == 16) { + std::vector<uint8_t> parameters( + reinterpret_cast<uint8_t*>(pParameters), + reinterpret_cast<uint8_t*>(pParameters + env->GetArrayLength(parametersArray))); + + Cookie cookie; + std::memcpy(cookie.data(), reinterpret_cast<const uint8_t*>(pUuid), 16); + + if (!sBluetoothHciVendorSpecificInterface) { + return; + } + sBluetoothHciVendorSpecificInterface->sendCommand(ocf, parameters, cookie); + + } else { + jniThrowIOException(env, EINVAL); + } + + if (pParameters) { + env->ReleaseByteArrayElements(parametersArray, pParameters, 0); + } + + if (pUuid) { + env->ReleaseByteArrayElements(uuidArray, pUuid, 0); + } +} + +int register_com_android_bluetooth_btservice_BluetoothHciVendorSpecific(JNIEnv* env) { + const JNINativeMethod methods[] = { + {"initNative", "()V", reinterpret_cast<void*>(initNative)}, + {"cleanupNative", "()V", reinterpret_cast<void*>(cleanupNative)}, + {"sendCommandNative", "(I[B[B)V", reinterpret_cast<void*>(sendCommandNative)}, + }; + const int result = REGISTER_NATIVE_METHODS( + env, "com/android/bluetooth/btservice/BluetoothHciVendorSpecificNativeInterface", + methods); + if (result != 0) { + return result; + } + + const JNIJavaMethod javaMethods[] = { + {"onCommandStatus", "(II[B)V", &method_onCommandStatus}, + {"onCommandComplete", "(I[B[B)V", &method_onCommandComplete}, + {"onEvent", "(I[B)V", &method_onEvent}, + }; + GET_JAVA_METHODS(env, "com/android/bluetooth/btservice/BluetoothHciVendorSpecificNativeInterface", + javaMethods); + + return 0; +} + +} // namespace android diff --git a/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp b/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp index 8e2f4e201d..da4b61e146 100644 --- a/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp +++ b/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp @@ -2509,6 +2509,12 @@ jint JNI_OnLoad(JavaVM* jvm, void* /* reserved */) { return JNI_ERR; } + status = android::register_com_android_bluetooth_btservice_BluetoothHciVendorSpecific(e); + if (status < 0) { + log::error("jni bluetooth hci vendor-specific registration failure: {}", status); + return JNI_ERR; + } + return JNI_VERSION_1_6; } diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java index d1feef50d5..695ad6e138 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java @@ -73,6 +73,7 @@ import android.bluetooth.IBluetooth; import android.bluetooth.IBluetoothActivityEnergyInfoListener; import android.bluetooth.IBluetoothCallback; import android.bluetooth.IBluetoothConnectionCallback; +import android.bluetooth.IBluetoothHciVendorSpecificCallback; import android.bluetooth.IBluetoothMetadataListener; import android.bluetooth.IBluetoothOobDataCallback; import android.bluetooth.IBluetoothPreferredAudioProfilesCallback; @@ -188,6 +189,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -273,6 +275,13 @@ public class AdapterService extends Service { private final DeviceConfigListener mDeviceConfigListener = new DeviceConfigListener(); + private final BluetoothHciVendorSpecificDispatcher mBluetoothHciVendorSpecificDispatcher = + new BluetoothHciVendorSpecificDispatcher(); + private final BluetoothHciVendorSpecificNativeInterface + mBluetoothHciVendorSpecificNativeInterface = + new BluetoothHciVendorSpecificNativeInterface( + mBluetoothHciVendorSpecificDispatcher); + private final Looper mLooper; private final AdapterServiceHandler mHandler; @@ -685,6 +694,10 @@ public class AdapterService extends Service { "BluetoothQualityReportNativeInterface cannot be null when BQR starts"); mBluetoothQualityReportNativeInterface.init(); + if (Flags.hciVendorSpecificExtension()) { + mBluetoothHciVendorSpecificNativeInterface.init(); + } + mSdpManager = new SdpManager(this, mLooper); mDatabaseManager.start(MetadataDatabase.createDatabase(this)); @@ -4135,6 +4148,84 @@ public class AdapterService extends Service { } @Override + public void registerHciVendorSpecificCallback( + IBluetoothHciVendorSpecificCallback callback, int[] eventCodes) { + AdapterService service = getService(); + if (service == null) { + return; + } + if (!callerIsSystemOrActiveOrManagedUser( + service, TAG, "registerHciVendorSpecificCallback")) { + throw new SecurityException("not allowed"); + } + service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + requireNonNull(callback); + requireNonNull(eventCodes); + + Set<Integer> eventCodesSet = + Arrays.stream(eventCodes).boxed().collect(Collectors.toSet()); + if (eventCodesSet.stream() + .anyMatch((n) -> (n < 0) || (n >= 0x50 && n < 0x60) || (n > 0xff))) { + throw new IllegalArgumentException("invalid vendor-specific event code"); + } + + service.mBluetoothHciVendorSpecificDispatcher.register(callback, eventCodesSet); + } + + @Override + public void unregisterHciVendorSpecificCallback( + IBluetoothHciVendorSpecificCallback callback) { + AdapterService service = getService(); + if (service == null) { + return; + } + if (!callerIsSystemOrActiveOrManagedUser( + service, TAG, "unregisterHciVendorSpecificCallback")) { + throw new SecurityException("not allowed"); + } + service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + requireNonNull(callback); + + service.mBluetoothHciVendorSpecificDispatcher.unregister(callback); + } + + @Override + public void sendHciVendorSpecificCommand( + int ocf, byte[] parameters, IBluetoothHciVendorSpecificCallback callback) { + AdapterService service = getService(); + if (service == null) { + return; + } + if (!callerIsSystemOrActiveOrManagedUser( + service, TAG, "sendHciVendorSpecificCommand")) { + throw new SecurityException("not allowed"); + } + service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + + // Open this no-op android command for test purpose + int getVendorCapabilitiesOcf = 0x153; + if (ocf < 0 + || (ocf >= 0x150 && ocf < 0x160 && ocf != getVendorCapabilitiesOcf) + || (ocf > 0x3ff)) { + throw new IllegalArgumentException("invalid vendor-specific event code"); + } + requireNonNull(parameters); + if (parameters.length > 255) { + throw new IllegalArgumentException("Parameters size is too big"); + } + + Optional<byte[]> cookie = + service.mBluetoothHciVendorSpecificDispatcher.getRegisteredCookie(callback); + if (!cookie.isPresent()) { + Log.e(TAG, "send command without registered callback"); + throw new IllegalStateException("callback not registered"); + } + + service.mBluetoothHciVendorSpecificNativeInterface.sendCommand( + ocf, parameters, cookie.get()); + } + + @Override public int getOffloadedTransportDiscoveryDataScanSupported(AttributionSource source) { AdapterService service = getService(); if (service == null diff --git a/android/app/src/com/android/bluetooth/btservice/BluetoothHciVendorSpecificDispatcher.java b/android/app/src/com/android/bluetooth/btservice/BluetoothHciVendorSpecificDispatcher.java new file mode 100644 index 0000000000..fe40294d20 --- /dev/null +++ b/android/app/src/com/android/bluetooth/btservice/BluetoothHciVendorSpecificDispatcher.java @@ -0,0 +1,129 @@ +/* + * Copyright 2024 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.btservice; + +import android.bluetooth.IBluetoothHciVendorSpecificCallback; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; + +import java.nio.ByteBuffer; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +class BluetoothHciVendorSpecificDispatcher { + private static final String TAG = "BluetoothHciVendorSpecificDispatcher"; + + private final class Registration implements IBinder.DeathRecipient { + final IBluetoothHciVendorSpecificCallback mCallback; + final Set<Integer> mEventCodes; + final UUID mUuid; + + Registration(IBluetoothHciVendorSpecificCallback callback, Set<Integer> eventCodes) { + mCallback = callback; + mEventCodes = eventCodes; + mUuid = UUID.randomUUID(); + } + + public void binderDied() { + synchronized (mRegistrations) { + mRegistrations.remove(mCallback.asBinder()); + } + } + } + + @GuardedBy("mRegistrations") + private final ArrayMap<IBinder, Registration> mRegistrations = new ArrayMap<>(); + + void unregister(IBluetoothHciVendorSpecificCallback callback) { + IBinder binder = callback.asBinder(); + synchronized (mRegistrations) { + Registration registration = mRegistrations.remove(binder); + if (registration == null) { + throw new IllegalStateException("callback was never registered"); + } + + binder.unlinkToDeath(registration, 0); + } + } + + void register(IBluetoothHciVendorSpecificCallback callback, Set<Integer> eventCodes) { + IBinder binder = callback.asBinder(); + synchronized (mRegistrations) { + if (mRegistrations.containsKey(binder)) { + throw new IllegalStateException("callback already registered"); + } + + try { + Registration registration = new Registration(callback, eventCodes); + binder.linkToDeath(registration, 0); + mRegistrations.put(binder, registration); + } catch (RemoteException e) { + Log.e(TAG, "link to death", e); + } + } + } + + Optional<byte[]> getRegisteredCookie(IBluetoothHciVendorSpecificCallback callback) { + IBinder binder = callback.asBinder(); + synchronized (mRegistrations) { + if (!mRegistrations.containsKey(binder)) return Optional.empty(); + + Registration registration = mRegistrations.get(binder); + ByteBuffer cookieBb = ByteBuffer.allocate(16); + cookieBb.putLong(registration.mUuid.getMostSignificantBits()); + cookieBb.putLong(registration.mUuid.getLeastSignificantBits()); + return Optional.of(cookieBb.array()); + } + } + + void dispatchCommandStatusOrComplete( + byte[] cookie, + Utils.RemoteExceptionIgnoringConsumer<IBluetoothHciVendorSpecificCallback> action) { + ByteBuffer cookieBb = ByteBuffer.wrap(cookie); + UUID uuid = new UUID(cookieBb.getLong(), cookieBb.getLong()); + synchronized (mRegistrations) { + try { + Registration registration = + mRegistrations.values().stream() + .filter((r) -> r.mUuid.equals(uuid)) + .findAny() + .get(); + action.accept(registration.mCallback); + } catch (NoSuchElementException e) { + Log.e(TAG, "Command status or complete owner not registered"); + return; + } + } + } + + void broadcastEvent( + int eventCode, + Utils.RemoteExceptionIgnoringConsumer<IBluetoothHciVendorSpecificCallback> action) { + synchronized (mRegistrations) { + mRegistrations.values().stream() + .filter((r) -> r.mEventCodes.contains(eventCode)) + .forEach((r) -> action.accept(r.mCallback)); + } + } +} diff --git a/android/app/src/com/android/bluetooth/btservice/BluetoothHciVendorSpecificNativeInterface.java b/android/app/src/com/android/bluetooth/btservice/BluetoothHciVendorSpecificNativeInterface.java new file mode 100644 index 0000000000..7acf26f360 --- /dev/null +++ b/android/app/src/com/android/bluetooth/btservice/BluetoothHciVendorSpecificNativeInterface.java @@ -0,0 +1,63 @@ +/* + * Copyright 2024 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.btservice; + +class BluetoothHciVendorSpecificNativeInterface { + private static final String TAG = "BluetoothHciVendorSpecificNativeInterface"; + private final BluetoothHciVendorSpecificDispatcher mDispatcher; + + public BluetoothHciVendorSpecificNativeInterface( + BluetoothHciVendorSpecificDispatcher dispatcher) { + mDispatcher = dispatcher; + } + + void init() { + initNative(); + } + + void cleanup() { + cleanupNative(); + } + + void sendCommand(int ocf, byte[] parameters, byte[] cookie) { + sendCommandNative(ocf, parameters, cookie); + } + + // Callbacks from the native stack + + private void onCommandStatus(int ocf, int status, byte[] cookie) { + mDispatcher.dispatchCommandStatusOrComplete( + cookie, (cb) -> cb.onCommandStatus(ocf, status)); + } + + private void onCommandComplete(int ocf, byte[] returnParameters, byte[] cookie) { + mDispatcher.dispatchCommandStatusOrComplete( + cookie, (cb) -> cb.onCommandComplete(ocf, returnParameters)); + } + + private void onEvent(int code, byte[] data) { + mDispatcher.broadcastEvent(code, (cb) -> cb.onEvent(code, data)); + } + + // Native methods that call into the JNI interface + + private native void initNative(); + + private native void cleanupNative(); + + private native void sendCommandNative(int ocf, byte[] parameters, byte[] cookie); +} diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index cd02d1805e..4934454873 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -83,12 +83,14 @@ package android.bluetooth { method @NonNull public static String nameForState(int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int notifyActiveDeviceChangeApplied(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean registerBluetoothConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.BluetoothConnectionCallback); + method @FlaggedApi("com.android.bluetooth.flags.hci_vendor_specific_extension") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void registerBluetoothHciVendorSpecificCallback(@NonNull java.util.Set<java.lang.Integer>, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.BluetoothHciVendorSpecificCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int registerBluetoothQualityReportReadyCallback(@NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.BluetoothQualityReportReadyCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int registerPreferredAudioProfilesChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.PreferredAudioProfilesChangedCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean removeActiveDevice(int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean removeOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void requestControllerActivityEnergyInfo(@NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback); method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BluetoothSocket retrieveConnectedRfcommSocket(@NonNull java.util.UUID); + method @FlaggedApi("com.android.bluetooth.flags.hci_vendor_specific_extension") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void sendBluetoothHciVendorSpecificCommand(@IntRange(from=0, to=1023) int, @NonNull byte[]); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setActiveDevice(@NonNull android.bluetooth.BluetoothDevice, int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setAutoOnEnabled(boolean); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int setBluetoothHciSnoopLoggingMode(int); @@ -98,6 +100,7 @@ package android.bluetooth { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int startRfcommServer(@NonNull String, @NonNull java.util.UUID, @NonNull android.app.PendingIntent); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int stopRfcommServer(@NonNull java.util.UUID); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean unregisterBluetoothConnectionCallback(@NonNull android.bluetooth.BluetoothAdapter.BluetoothConnectionCallback); + method @FlaggedApi("com.android.bluetooth.flags.hci_vendor_specific_extension") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void unregisterBluetoothHciVendorSpecificCallback(@NonNull android.bluetooth.BluetoothAdapter.BluetoothHciVendorSpecificCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int unregisterBluetoothQualityReportReadyCallback(@NonNull android.bluetooth.BluetoothAdapter.BluetoothQualityReportReadyCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int unregisterPreferredAudioProfilesChangedCallback(@NonNull android.bluetooth.BluetoothAdapter.PreferredAudioProfilesChangedCallback); field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_AUTO_ON_STATE_CHANGED = "android.bluetooth.action.AUTO_ON_STATE_CHANGED"; @@ -126,6 +129,12 @@ package android.bluetooth { method public void onDeviceDisconnected(@NonNull android.bluetooth.BluetoothDevice, int); } + @FlaggedApi("com.android.bluetooth.flags.hci_vendor_specific_extension") public static interface BluetoothAdapter.BluetoothHciVendorSpecificCallback { + method public void onCommandComplete(@IntRange(from=0, to=1023) int, @NonNull byte[]); + method public void onCommandStatus(@IntRange(from=0, to=1023) int, @IntRange(from=0, to=255) int); + method public void onEvent(@IntRange(from=0, to=254) int, @NonNull byte[]); + } + public static interface BluetoothAdapter.BluetoothQualityReportReadyCallback { method public void onBluetoothQualityReportReady(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothQualityReport, int); } diff --git a/framework/java/android/bluetooth/BluetoothAdapter.java b/framework/java/android/bluetooth/BluetoothAdapter.java index ebbe97566d..2be58683c5 100644 --- a/framework/java/android/bluetooth/BluetoothAdapter.java +++ b/framework/java/android/bluetooth/BluetoothAdapter.java @@ -31,7 +31,9 @@ import static java.util.Objects.requireNonNull; import android.annotation.BroadcastBehavior; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresNoPermission; @@ -3901,6 +3903,14 @@ public final class BluetoothAdapter { mAudioProfilesCallbackWrapper.registerToNewService(mService); mQualityCallbackWrapper.registerToNewService(mService); mBluetoothConnectionCallbackWrapper.registerToNewService(mService); + synchronized (mHciVendorSpecificCallbackRegistration) { + try { + mHciVendorSpecificCallbackRegistration.registerToService( + mService, mHciVendorSpecificCallbackStub); + } catch (Exception e) { + Log.e(TAG, "Failed to register HCI vendor-specific callback", e); + } + } } finally { mServiceLock.readLock().unlock(); } @@ -5575,4 +5585,277 @@ public final class BluetoothAdapter { } return BluetoothStatusCodes.ERROR_UNKNOWN; } + + /** + * Callbacks for receiving response of HCI Vendor-Specific Commands and Vendor-Specific Events + * that arise from the controller. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_HCI_VENDOR_SPECIFIC_EXTENSION) + public interface BluetoothHciVendorSpecificCallback { + /** + * Invoked when an `HCI_Command_Status`, in response to a Vendor Command is received. + * + * @param ocf "Opcode command field" of the HCI Opcode, as defined in the Bluetooth Core + * Specification Vol 4, Part E, Section 5.4.1. + * @param status as defined by the Bluetooth Core Specification. + */ + void onCommandStatus( + @IntRange(from = 0x000, to = 0x3ff) int ocf, + @IntRange(from = 0x00, to = 0xff) int status); + + /** + * Invoked when an `HCI_Command_Complete`, in response to a Vendor Command is received. + * + * @param ocf "Opcode command field" of the HCI Opcode, as defined in the Bluetooth Core + * Specification Vol 4, Part E, Section 5.4.1. + * @param returnParameters Data returned by the command (from 0 to 252 Bytes). + */ + void onCommandComplete( + @IntRange(from = 0x000, to = 0x3ff) int ocf, @NonNull byte[] returnParameters); + + /** + * Invoked when an event is received. + * + * @param code The vendor-specific event Code. The first octet of the event parameters + * of a vendor-specific event (Bluetooth Core Specification Vol 4, Part E, 5.4.3). + * @param data from 0 to 254 Bytes. + */ + void onEvent(@IntRange(from = 0x00, to = 0xfe) int code, @NonNull byte[] data); + } + + private static final class HciVendorSpecificCallbackRegistration { + private BluetoothHciVendorSpecificCallback mCallback; + private Executor mExecutor; + private Set<Integer> mEventCodeSet; + + void set( + BluetoothHciVendorSpecificCallback callback, + Set<Integer> eventCodeSet, + Executor executor) { + mCallback = callback; + mEventCodeSet = eventCodeSet; + mExecutor = executor; + } + + void reset() { + mCallback = null; + mEventCodeSet = null; + mExecutor = null; + } + + boolean isSet() { + return mCallback != null; + } + + boolean isSet(BluetoothHciVendorSpecificCallback callback) { + return isSet() && (callback == mCallback); + } + + @RequiresPermission(BLUETOOTH_PRIVILEGED) + void registerToService(IBluetooth service, IBluetoothHciVendorSpecificCallback stub) { + if (service == null || !isSet()) { + return; + } + + int[] eventCodes = mEventCodeSet.stream().mapToInt(i -> i).toArray(); + try { + service.registerHciVendorSpecificCallback(stub, eventCodes); + } catch (RemoteException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } + } + + @RequiresPermission(BLUETOOTH_PRIVILEGED) + void unregisterFromService(IBluetooth service, IBluetoothHciVendorSpecificCallback stub) { + if (service == null) { + return; + } + try { + service.unregisterHciVendorSpecificCallback(stub); + } catch (RemoteException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } + } + + void execute(Consumer<BluetoothHciVendorSpecificCallback> consumer) { + BluetoothHciVendorSpecificCallback callback = mCallback; + if (callback == null) { + return; + } + executeFromBinder(mExecutor, () -> consumer.accept(callback)); + } + } + + private final HciVendorSpecificCallbackRegistration mHciVendorSpecificCallbackRegistration = + new HciVendorSpecificCallbackRegistration(); + + private final IBluetoothHciVendorSpecificCallback mHciVendorSpecificCallbackStub = + new IBluetoothHciVendorSpecificCallback.Stub() { + + @Override + @RequiresNoPermission + public void onCommandStatus(int ocf, int status) { + synchronized (mHciVendorSpecificCallbackRegistration) { + if (Flags.hciVendorSpecificExtension()) { + mHciVendorSpecificCallbackRegistration.execute( + (cb) -> cb.onCommandStatus(ocf, status)); + } + } + } + + @Override + @RequiresNoPermission + public void onCommandComplete(int ocf, byte[] returnParameters) { + synchronized (mHciVendorSpecificCallbackRegistration) { + if (Flags.hciVendorSpecificExtension()) { + mHciVendorSpecificCallbackRegistration.execute( + (cb) -> cb.onCommandComplete(ocf, returnParameters)); + } + } + } + + @Override + @RequiresNoPermission + public void onEvent(int code, byte[] data) { + synchronized (mHciVendorSpecificCallbackRegistration) { + if (Flags.hciVendorSpecificExtension()) { + mHciVendorSpecificCallbackRegistration.execute( + (cb) -> cb.onEvent(code, data)); + } + } + } + }; + + /** + * Register an {@link BluetoothHciVendorCallback} to listen for HCI vendor responses and events + * + * @param eventCodeSet Set of vendor-specific event codes to listen for updates. Each + * vendor-specific event code must be in the range 0x00 to 0x4f or 0x60 to 0xff. + * The inclusive range 0x50-0x5f is reserved by the system. + * @param executor an {@link Executor} to execute given callback + * @param callback user implementation of the {@link BluetoothHciVendorCallback} + * @throws IllegalArgumentException if the callback is already registered, or event codes not in + * a valid range + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_HCI_VENDOR_SPECIFIC_EXTENSION) + @RequiresPermission(BLUETOOTH_PRIVILEGED) + @SuppressLint("AndroidFrameworkRequiresPermission") // Consumer wrongly report permission + public void registerBluetoothHciVendorSpecificCallback( + @NonNull Set<Integer> eventCodeSet, + @NonNull @CallbackExecutor Executor executor, + @NonNull BluetoothHciVendorSpecificCallback callback) { + if (DBG) Log.d(TAG, "registerBluetoothHciVendorSpecificCallback()"); + + requireNonNull(eventCodeSet); + requireNonNull(executor); + requireNonNull(callback); + if (eventCodeSet.stream() + .anyMatch((n) -> (n < 0) || (n >= 0x50 && n < 0x60) || (n > 0xff))) { + throw new IllegalArgumentException("Event code not in valid range"); + } + + mServiceLock.readLock().lock(); + try { + synchronized (mHciVendorSpecificCallbackRegistration) { + if (mHciVendorSpecificCallbackRegistration.isSet()) { + throw new IllegalArgumentException("Only one registration allowed"); + } + mHciVendorSpecificCallbackRegistration.set(callback, eventCodeSet, executor); + try { + mHciVendorSpecificCallbackRegistration.registerToService( + mService, mHciVendorSpecificCallbackStub); + } catch (Exception e) { + mHciVendorSpecificCallbackRegistration.reset(); + throw e; + } + } + } finally { + mServiceLock.readLock().unlock(); + } + } + + /** + * Unregister the specified {@link BluetoothHciVendorCallback} + * + * @param callback user implementation of the {@link BluetoothHciVendorCallback} + * @throws IllegalArgumentException if the callback has not been registered + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_HCI_VENDOR_SPECIFIC_EXTENSION) + @RequiresPermission(BLUETOOTH_PRIVILEGED) + @SuppressLint("AndroidFrameworkRequiresPermission") // Consumer wrongly report permission + public void unregisterBluetoothHciVendorSpecificCallback( + @NonNull BluetoothHciVendorSpecificCallback callback) { + if (DBG) Log.d(TAG, "unregisterBluetoothHciVendorSpecificCallback()"); + + requireNonNull(callback); + + mServiceLock.readLock().lock(); + try { + synchronized (mHciVendorSpecificCallbackRegistration) { + if (!mHciVendorSpecificCallbackRegistration.isSet(callback)) { + throw new IllegalArgumentException("Callback not registered"); + } + mHciVendorSpecificCallbackRegistration.unregisterFromService( + mService, mHciVendorSpecificCallbackStub); + mHciVendorSpecificCallbackRegistration.reset(); + } + } finally { + mServiceLock.readLock().unlock(); + } + } + + /** + * Send an HCI Vendor-Specific Command + * + * @param ocf "Opcode command field" of the HCI Opcode, as defined in the Bluetooth Core + * Specification Vol 4, Part E, Section 5.4.1. Each vendor-specific ocf must be in the range + * 0x000-0x14f or 0x160-0x3ff. The inclusive range 0x150-0x15f is reserved by the system. + * @param parameters shall be less or equal to 255 bytes. + * @throws IllegalArgumentException if the ocf is not in a valid range + * @throws IllegalStateException when a callback has not been registered + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_HCI_VENDOR_SPECIFIC_EXTENSION) + @RequiresPermission(BLUETOOTH_PRIVILEGED) + public void sendBluetoothHciVendorSpecificCommand( + @IntRange(from = 0x000, to = 0x3ff) int ocf, @NonNull byte[] parameters) { + if (DBG) Log.d(TAG, "sendBluetoothHciVendorSpecificCommand()"); + + // Open this no-op android command for test purpose + int getVendorCapabilitiesOcf = 0x153; + if (ocf < 0 + || (ocf >= 0x150 && ocf < 0x160 && ocf != getVendorCapabilitiesOcf) + || ocf > 0x3ff) { + throw new IllegalArgumentException("Opcode command value not in valid range"); + } + + requireNonNull(parameters); + if (parameters.length > 255) { + throw new IllegalArgumentException("Parameters size is too big"); + } + + mServiceLock.readLock().lock(); + try { + if (!mHciVendorSpecificCallbackRegistration.isSet()) { + throw new IllegalStateException("No Callback registered"); + } + + if (mService != null) { + mService.sendHciVendorSpecificCommand( + ocf, parameters, mHciVendorSpecificCallbackStub); + } + } catch (RemoteException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } finally { + mServiceLock.readLock().unlock(); + } + } } diff --git a/system/btif/Android.bp b/system/btif/Android.bp index e6bdf90de5..fd27f850ba 100644 --- a/system/btif/Android.bp +++ b/system/btif/Android.bp @@ -198,6 +198,7 @@ cc_library_static { "src/btif_gatt_client.cc", "src/btif_gatt_server.cc", "src/btif_gatt_util.cc", + "src/btif_hci_vs.cc", "src/btif_iot_config.cc", "src/btif_keystore.cc", "src/btif_metrics_logging.cc", diff --git a/system/btif/BUILD.gn b/system/btif/BUILD.gn index 68ca336a3d..167ac3bcfb 100644 --- a/system/btif/BUILD.gn +++ b/system/btif/BUILD.gn @@ -59,6 +59,7 @@ static_library("btif") { "src/btif_le_audio_broadcaster.cc", "src/btif_has_client.cc", "src/btif_hearing_aid.cc", + "src/btif_hci_vs.cc", "src/btif_hf.cc", "src/btif_hf_client.cc", "src/btif_hh.cc", diff --git a/system/btif/include/btif_hci_vs.h b/system/btif/include/btif_hci_vs.h new file mode 100644 index 0000000000..0e227ed8e7 --- /dev/null +++ b/system/btif/include/btif_hci_vs.h @@ -0,0 +1,32 @@ +/* + * Copyright 2024 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. + */ + +#ifndef BTIF_HCI_VS_H_ +#define BTIF_HCI_VS_H_ + +#include <bluetooth/log.h> + +#include "include/hardware/bt_hci_vs.h" + +namespace bluetooth { +namespace hci_vs { + +BluetoothHciVendorSpecificInterface* getBluetoothHciVendorSpecificInterface(); + +} // namespace hci_vs +} // namespace bluetooth + +#endif // BTIF_HCI_VS_H_ diff --git a/system/btif/src/btif_hci_vs.cc b/system/btif/src/btif_hci_vs.cc new file mode 100644 index 0000000000..5d591eaf5b --- /dev/null +++ b/system/btif/src/btif_hci_vs.cc @@ -0,0 +1,61 @@ +/* + * Copyright 2024 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. + */ + +#include "btif_hci_vs.h" + +#include <bluetooth/log.h> + +#include "btif_common.h" +#include "hci/hci_interface.h" +#include "main/shim/entry.h" +#include "packet/raw_builder.h" +#include "stack/include/main_thread.h" + +namespace bluetooth { +namespace hci_vs { + +std::unique_ptr<BluetoothHciVendorSpecificInterface> hciVendorSpecificInterface; + +class BluetoothHciVendorSpecificInterfaceImpl + : public bluetooth::hci_vs::BluetoothHciVendorSpecificInterface { + ~BluetoothHciVendorSpecificInterfaceImpl() override = default; + + void init(BluetoothHciVendorSpecificCallbacks* callbacks) override { + log::info("BluetoothHciVendorSpecificInterfaceImpl"); + this->callbacks = callbacks; + } + + void sendCommand(uint16_t ocf, std::vector<uint8_t> parameters, Cookie cookie) override { + // TODO: Send HCI Command + (void)ocf; + (void)parameters; + (void)cookie; + } + +private: + BluetoothHciVendorSpecificCallbacks* callbacks = nullptr; +}; + +BluetoothHciVendorSpecificInterface* getBluetoothHciVendorSpecificInterface() { + if (!hciVendorSpecificInterface) { + hciVendorSpecificInterface.reset(new BluetoothHciVendorSpecificInterfaceImpl()); + } + + return hciVendorSpecificInterface.get(); +} + +} // namespace hci_vs +} // namespace bluetooth diff --git a/system/include/hardware/bt_hci_vs.h b/system/include/hardware/bt_hci_vs.h new file mode 100644 index 0000000000..729f5a5334 --- /dev/null +++ b/system/include/hardware/bt_hci_vs.h @@ -0,0 +1,48 @@ +/* + * Copyright 2024 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. + */ + +#pragma once + +#include <stdint.h> + +#include <vector> + +namespace bluetooth { +namespace hci_vs { + +typedef std::array<uint8_t, 16> Cookie; + +class BluetoothHciVendorSpecificCallbacks { +public: + virtual ~BluetoothHciVendorSpecificCallbacks() = default; + + virtual void onCommandStatus(uint16_t ocf, uint8_t status, Cookie cookie) = 0; + virtual void onCommandComplete(uint16_t ocf, std::vector<uint8_t> return_parameters, + Cookie cookie) = 0; + virtual void onEvent(uint8_t code, std::vector<uint8_t> data) = 0; +}; + +class BluetoothHciVendorSpecificInterface { +public: + virtual ~BluetoothHciVendorSpecificInterface() = default; + + virtual void init(BluetoothHciVendorSpecificCallbacks* callbacks) = 0; + + virtual void sendCommand(uint16_t ocf, std::vector<uint8_t> parameters, Cookie cookie) = 0; +}; + +} // namespace hci_vs +} // namespace bluetooth |