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

Commit 12bfe92f authored by Mingbo's avatar Mingbo Committed by Jakub Rotkiewicz
Browse files

Bluetooth Quality Report features

Snapshot as of:
dbfdf08be44659ad9d3f90085c59b8314361110a
81ac65352e01bfcdbc7db2ffe3ad81122114c7e5
ac187ddfbf173a836ae1665bf857ac132f23020c
4a8bef58021cbd5e6953b11b4a5e9789336fd76f
4a238cdc028199536683aed3a6714770f8b61f10
3917b54b6058ea69d0ad64d89dc418e1780a013d

- added vendor specific fileds to Google's BQR structure
- added support to parse the received BQR events, wrap them to a
  parcelable object(BluetoothQualityReport.java) and send it out
  to apps that registered for this callback
- customers app will receive the above callback and call APIs
  of BluetoothQualityReport.java to fetch useful debug info

Sponsor: rotkiewicz@
Bug: 259448922
Tag: #feature
Test: manual - check BQR event logs
BYPASS_LONG_LINES_REASON: Bluetooth likes 120 lines

Change-Id: If34bb8004984f9564f0a617f277fde260de16a01
parent 24dd365a
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -165,6 +165,9 @@ int register_com_android_bluetooth_le_audio(JNIEnv* env);
int register_com_android_bluetooth_vc(JNIEnv* env);

int register_com_android_bluetooth_csip_set_coordinator(JNIEnv* env);

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

#endif /* COM_ANDROID_BLUETOOTH_H */
+161 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 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 "BluetoothQualityReportJni"

#include <string.h>

#include <shared_mutex>

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

using bluetooth::bqr::BluetoothQualityReportCallbacks;
using bluetooth::bqr::BluetoothQualityReportInterface;

namespace android {
static jmethodID method_bqrDeliver;

static BluetoothQualityReportInterface* sBluetoothQualityReportInterface =
    nullptr;
static std::shared_timed_mutex interface_mutex;

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

class BluetoothQualityReportCallbacksImpl
    : public bluetooth::bqr::BluetoothQualityReportCallbacks {
 public:
  ~BluetoothQualityReportCallbacksImpl() = default;

  void bqr_delivery_callback(const RawAddress bd_addr, uint8_t lmp_ver,
                             uint16_t lmp_subver, uint16_t manufacturer_id,
                             std::vector<uint8_t> bqr_raw_data) override {
    ALOGI("%s", __func__);
    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);

    CallbackEnv sCallbackEnv(__func__);
    if (!sCallbackEnv.valid()) return;
    if (method_bqrDeliver == NULL) return;
    if (mCallbacksObj == nullptr) return;

    ScopedLocalRef<jbyteArray> addr(
        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
    if (!addr.get()) {
      ALOGE("Error while allocation byte array for addr in %s", __func__);
      return;
    }

    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
                                     (jbyte*)bd_addr.address);

    ScopedLocalRef<jbyteArray> raw_data(
        sCallbackEnv.get(), sCallbackEnv->NewByteArray(bqr_raw_data.size()));
    if (!raw_data.get()) {
      ALOGE("Error while allocation byte array for bqr raw data in %s",
            __func__);
      return;
    }
    sCallbackEnv->SetByteArrayRegion(raw_data.get(), 0, bqr_raw_data.size(),
                                     (jbyte*)bqr_raw_data.data());

    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_bqrDeliver, addr.get(),
                                 (jint)lmp_ver, (jint)lmp_subver,
                                 (jint)manufacturer_id, raw_data.get());
  }
};

static BluetoothQualityReportCallbacksImpl sBluetoothQualityReportCallbacks;

static void classInitNative(JNIEnv* env, jclass clazz) {
  method_bqrDeliver = env->GetMethodID(clazz, "bqrDeliver", "([BIII[B)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 (sBluetoothQualityReportInterface != nullptr) {
    LOG(INFO) << "Cleaning up BluetoothQualityReport Interface before "
                 "initializing...";
    sBluetoothQualityReportInterface = nullptr;
  }

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

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

  sBluetoothQualityReportInterface =
      (BluetoothQualityReportInterface*)btInf->get_profile_interface(BT_BQR_ID);
  if (sBluetoothQualityReportInterface == nullptr) {
    LOG(ERROR) << "Failed to get BluetoothQualityReport Interface";
    return;
  }

  sBluetoothQualityReportInterface->init(&sBluetoothQualityReportCallbacks);
}

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 (sBluetoothQualityReportInterface != nullptr) {
    sBluetoothQualityReportInterface = nullptr;
  }

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

static JNINativeMethod sMethods[] = {
    {"classInitNative", "()V", (void*)classInitNative},
    {"initNative", "()V", (void*)initNative},
    {"cleanupNative", "()V", (void*)cleanupNative},
};

int register_com_android_bluetooth_btservice_BluetoothQualityReport(
    JNIEnv* env) {
  return jniRegisterNativeMethods(env,
                                  "com/android/bluetooth/btservice/"
                                  "BluetoothQualityReportNativeInterface",
                                  sMethods, NELEM(sMethods));
}
}  // namespace android
+8 −0
Original line number Diff line number Diff line
@@ -2227,5 +2227,13 @@ jint JNI_OnLoad(JavaVM* jvm, void* reserved) {
    return JNI_ERR;
  }

  status =
      android::register_com_android_bluetooth_btservice_BluetoothQualityReport(
          e);
  if (status < 0) {
    ALOGE("jni bluetooth quality report registration failure: %d", status);
    return JNI_ERR;
  }

  return JNI_VERSION_1_6;
}
+125 −0
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ import android.bluetooth.BluetoothFrameworkInitializer;
import android.bluetooth.BluetoothMap;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProtoEnums;
import android.bluetooth.BluetoothQualityReport;
import android.bluetooth.BluetoothSap;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSinkAudioPolicy;
@@ -65,6 +66,7 @@ import android.bluetooth.IBluetoothConnectionCallback;
import android.bluetooth.IBluetoothMetadataListener;
import android.bluetooth.IBluetoothOobDataCallback;
import android.bluetooth.IBluetoothPreferredAudioProfilesCallback;
import android.bluetooth.IBluetoothQualityReportReadyCallback;
import android.bluetooth.IBluetoothSocketManager;
import android.bluetooth.IncomingRfcommSocketInfo;
import android.bluetooth.OobData;
@@ -235,6 +237,7 @@ public class AdapterService extends Service {
        QUALITY_REPORT_ID_A2DP_AUDIO_CHOPPY(0x03),
        QUALITY_REPORT_ID_SCO_VOICE_CHOPPY(0x04),
        QUALITY_REPORT_ID_ROOT_INFLAMMATION(0x05),
        QUALITY_REPORT_ID_CONNECT_FAIL(0x08),
        QUALITY_REPORT_ID_LMP_LL_MESSAGE_TRACE(0x11),
        QUALITY_REPORT_ID_BT_SCHEDULING_TRACE(0x12),
        QUALITY_REPORT_ID_CONTROLLER_DBG_INFO(0x13);
@@ -293,6 +296,8 @@ public class AdapterService extends Service {
    private Set<IBluetoothConnectionCallback> mBluetoothConnectionCallbacks = new HashSet<>();
    private RemoteCallbackList<IBluetoothPreferredAudioProfilesCallback>
            mPreferredAudioProfilesCallbacks;
    private RemoteCallbackList<IBluetoothQualityReportReadyCallback>
            mBluetoothQualityReportReadyCallbacks;
    private Set<BluetoothDevice> mDevicesPendingAudioProfileChanges = new HashSet<>();
    //Only BluetoothManagerService should be registered
    private RemoteCallbackList<IBluetoothCallback> mCallbacks;
@@ -342,6 +347,7 @@ public class AdapterService extends Service {
    private LeAudioService mLeAudioService;
    private BassClientService mBassClientService;
    private BatteryService mBatteryService;
    private BluetoothQualityReportNativeInterface mBluetoothQualityReportNativeInterface;

    private volatile boolean mTestModeEnabled = false;

@@ -530,6 +536,8 @@ public class AdapterService extends Service {
        mNativeAvailable = true;
        mPreferredAudioProfilesCallbacks =
                new RemoteCallbackList<IBluetoothPreferredAudioProfilesCallback>();
        mBluetoothQualityReportReadyCallbacks =
                new RemoteCallbackList<IBluetoothQualityReportReadyCallback>();
        mCallbacks = new RemoteCallbackList<IBluetoothCallback>();
        mAppOps = getSystemService(AppOpsManager.class);
        //Load the name and address
@@ -543,6 +551,12 @@ public class AdapterService extends Service {

        mBluetoothKeystoreService.initJni();

        mBluetoothQualityReportNativeInterface =
                Objects.requireNonNull(
                        BluetoothQualityReportNativeInterface.getInstance(),
                        "BluetoothQualityReportNativeInterface cannot be null when BQR starts");
        mBluetoothQualityReportNativeInterface.init();

        mSdpManager = SdpManager.init(this);
        registerReceiver(mAlarmBroadcastReceiver, new IntentFilter(ACTION_ALARM_WAKEUP));

@@ -918,6 +932,39 @@ public class AdapterService extends Service {
        }
    }

    /**
     * Callback from Bluetooth Quality Report Native Interface to inform the listeners about
     * Bluetooth Quality.
     *
     * @param device is the BluetoothDevice which connection quality is being reported
     * @param bluetoothQualityReport a Parcel that contains information about Bluetooth Quality
     * @return whether the Bluetooth stack acknowledged the change successfully
     */
    public int bluetoothQualityReportReadyCallback(BluetoothDevice device,
            BluetoothQualityReport bluetoothQualityReport) {
        synchronized (mBluetoothQualityReportReadyCallbacks) {
            if (mBluetoothQualityReportReadyCallbacks != null) {
                int n = mBluetoothQualityReportReadyCallbacks.beginBroadcast();
                debugLog("bluetoothQualityReportReadyCallback() - "
                        + "Broadcasting Bluetooth Quality Report to " + n + " receivers.");
                for (int i = 0; i < n; i++) {
                    try {
                        mBluetoothQualityReportReadyCallbacks.getBroadcastItem(i)
                                .onBluetoothQualityReportReady(device,
                                        bluetoothQualityReport,
                                        BluetoothStatusCodes.SUCCESS);
                    } catch (RemoteException e) {
                        debugLog("bluetoothQualityReportReadyCallback() - Callback #" + i
                                + " failed (" + e + ")");
                    }
                }
                mBluetoothQualityReportReadyCallbacks.finishBroadcast();
            }
        }

        return BluetoothStatusCodes.SUCCESS;
    }

    void switchBufferSizeCallback(boolean isLowLatencyBufferSize) {
        List<BluetoothDevice> activeDevices = getActiveDevices(BluetoothProfile.A2DP);
        if (activeDevices.size() != 1) {
@@ -1061,6 +1108,10 @@ public class AdapterService extends Service {
            mPreferredAudioProfilesCallbacks.kill();
        }

        if (mBluetoothQualityReportReadyCallbacks != null) {
            mBluetoothQualityReportReadyCallbacks.kill();
        }

        if (mCallbacks != null) {
            mCallbacks.kill();
        }
@@ -4462,6 +4513,80 @@ public class AdapterService extends Service {
            }
            return BluetoothStatusCodes.SUCCESS;
        }

        @Override
        public void registerBluetoothQualityReportReadyCallback(
                IBluetoothQualityReportReadyCallback callback,
                AttributionSource attributionSource, SynchronousResultReceiver receiver) {
            try {
                receiver.send(registerBluetoothQualityReportReadyCallback(callback,
                        attributionSource));
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }
        @RequiresPermission(allOf = {
                android.Manifest.permission.BLUETOOTH_CONNECT,
                android.Manifest.permission.BLUETOOTH_PRIVILEGED,
        })
        private int registerBluetoothQualityReportReadyCallback(
                IBluetoothQualityReportReadyCallback callback, AttributionSource source) {
            AdapterService service = getService();
            if (service == null) {
                return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
            }
            if (!callerIsSystemOrActiveOrManagedUser(service, TAG,
                    "registerBluetoothQualityReportReadyCallback")) {
                return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED;
            }
            Objects.requireNonNull(callback);
            if (!Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION;
            }
            enforceBluetoothPrivilegedPermission(service);

            service.mBluetoothQualityReportReadyCallbacks.register(callback);
            return BluetoothStatusCodes.SUCCESS;
        }

        @Override
        public void unregisterBluetoothQualityReportReadyCallback(
                IBluetoothQualityReportReadyCallback callback,
                AttributionSource attributionSource, SynchronousResultReceiver receiver) {
            try {
                receiver.send(unregisterBluetoothQualityReportReadyCallback(callback,
                        attributionSource));
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }
        @RequiresPermission(allOf = {
                android.Manifest.permission.BLUETOOTH_CONNECT,
                android.Manifest.permission.BLUETOOTH_PRIVILEGED,
        })
        private int unregisterBluetoothQualityReportReadyCallback(
                IBluetoothQualityReportReadyCallback callback, AttributionSource source) {
            AdapterService service = getService();
            if (service == null) {
                return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
            }
            if (!callerIsSystemOrActiveOrManagedUser(service, TAG,
                    "unregisterBluetoothQualityReportReadyCallback")) {
                return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED;
            }
            Objects.requireNonNull(callback);
            if (!Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION;
            }
            enforceBluetoothPrivilegedPermission(service);

            if (!service.mBluetoothQualityReportReadyCallbacks.unregister(callback)) {
                Log.e(TAG, "unregisterBluetoothQualityReportReadyCallback: callback was never "
                        + "registered");
                return BluetoothStatusCodes.ERROR_CALLBACK_NOT_REGISTERED;
            }
            return BluetoothStatusCodes.SUCCESS;
        }
    }

    /**
+133 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 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.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothQualityReport;
import android.bluetooth.BluetoothStatusCodes;
import android.util.Log;

import com.android.bluetooth.Utils;
import com.android.internal.annotations.GuardedBy;

final class BluetoothQualityReportNativeInterface {

    private static final String TAG = "BluetoothQualityReportNativeInterface";

    @GuardedBy("INSTANCE_LOCK")
    private static BluetoothQualityReportNativeInterface sInstance;

    private static final Object INSTANCE_LOCK = new Object();

    static {
        classInitNative();
    }

    private BluetoothQualityReportNativeInterface() {}

    /** Get singleton instance. */
    public static BluetoothQualityReportNativeInterface getInstance() {
        synchronized (INSTANCE_LOCK) {
            if (sInstance == null) {
                sInstance = new BluetoothQualityReportNativeInterface();
            }
            return sInstance;
        }
    }

    /**
     * Initializes the native interface.
     *
     * <p>priorities to configure.
     */
    public void init() {
        initNative();
    }

    /** Cleanup the native interface. */
    public void cleanup() {
        cleanupNative();
    }

    /**
     * Callback from the native stack back into the Java framework.
     */
    private void bqrDeliver(
            byte[] remoteAddr, int lmpVer, int lmpSubVer, int manufacturerId, byte[] bqrRawData) {
        BluetoothClass remoteBtClass = null;
        BluetoothDevice device = null;
        String remoteName = null;

        String remoteAddress = Utils.getAddressStringFromByte(remoteAddr);
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();

        if (remoteAddress != null && adapter != null) {
            device = adapter.getRemoteDevice(remoteAddress);
            if (device == null) {
                Log.e(TAG, "bqrDeliver failed: device is null");
                return;
            }
            remoteName = device.getName();
            remoteBtClass = device.getBluetoothClass();
        } else {
            Log.e(TAG, "bqrDeliver failed: "
                    + (remoteAddress == null ? "remoteAddress is null" : "adapter is null"));
            return;
        }

        BluetoothQualityReport bqr;
        try {
            bqr =
                    new BluetoothQualityReport.Builder(bqrRawData)
                            .setRemoteAddress(remoteAddress)
                            .setLmpVersion(lmpVer)
                            .setLmpSubVersion(lmpSubVer)
                            .setManufacturerId(manufacturerId)
                            .setRemoteName(remoteName)
                            .setBluetoothClass(remoteBtClass)
                            .build();
            Log.i(TAG, bqr.toString());
        } catch (Exception e) {
            Log.e(TAG, "bqrDeliver failed: failed to create BluetotQualityReport", e);
            return;
        }

        try {
            AdapterService adapterService = AdapterService.getAdapterService();
            if (adapterService == null) {
                Log.e(TAG, "bqrDeliver failed: adapterService is null");
                return;
            }
            int status = adapterService.bluetoothQualityReportReadyCallback(device, bqr);
            if (status != BluetoothStatusCodes.SUCCESS) {
                Log.e(TAG, "bluetoothQualityReportReadyCallback failed, status: " + status);
            }
        } catch (Exception e) {
            Log.e(TAG, "bqrDeliver failed: bluetoothQualityReportReadyCallback error", e);
            return;
        }
    }

    // Native methods that call into the JNI interface
    private static native void classInitNative();

    private native void initNative();

    private native void cleanupNative();
}
Loading