diff options
531 files changed, 19967 insertions, 10793 deletions
diff --git a/Android.bp b/Android.bp index 7ec7305d0a..bef6c85049 100644 --- a/Android.bp +++ b/Android.bp @@ -73,6 +73,11 @@ cc_defaults { ], c_std: "c99", cpp_std: "c++20", + arch: { + riscv64: { + ldflags: ["-Wl,--no-relax"], + }, + }, } // List of tidy checks that are enabled for cc targets. diff --git a/AndroidTestTemplate.xml b/AndroidTestTemplate.xml index 8332422b34..4083baac14 100644 --- a/AndroidTestTemplate.xml +++ b/AndroidTestTemplate.xml @@ -39,7 +39,7 @@ <!-- Only run tests in MTS if the Bluetooth Mainline module is installed. --> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> - <option name="mainline-module-package-name" value="com.android.btservices" /> - <option name="mainline-module-package-name" value="com.google.android.btservices" /> + <option name="mainline-module-package-name" value="com.android.bt" /> + <option name="mainline-module-package-name" value="com.google.android.bt" /> </object> </configuration> diff --git a/OWNERS_channel_sounding b/OWNERS_channel_sounding new file mode 100644 index 0000000000..f39c775033 --- /dev/null +++ b/OWNERS_channel_sounding @@ -0,0 +1,3 @@ +aliceypkuo@google.com +ashchen@google.com +chienyuanhuang@google.com diff --git a/TEST_MAPPING b/TEST_MAPPING index 5fc31de514..562f9717c8 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -79,6 +79,9 @@ "name": "bluetooth_le_audio_test" }, { + "name": "bluetooth_ras_test" + }, + { "name": "bluetooth_packet_parser_test" }, { @@ -275,6 +278,9 @@ "name": "bluetooth_le_audio_test" }, { + "name": "bluetooth_ras_test" + }, + { "name": "bluetooth_packet_parser_test" }, { diff --git a/android/ChannelSoundingTestApp/app/src/androidTest/java/com/android/bluetooth/channelsoundingtestapp/ExampleInstrumentedTest.java b/android/ChannelSoundingTestApp/app/src/androidTest/java/com/android/bluetooth/channelsoundingtestapp/ExampleInstrumentedTest.java deleted file mode 100644 index 2a73b92d19..0000000000 --- a/android/ChannelSoundingTestApp/app/src/androidTest/java/com/android/bluetooth/channelsoundingtestapp/ExampleInstrumentedTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.channelsoundingtestapp; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Context; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertThat(appContext.getPackageName()) - .isEqualto("com.android.bluetooth.channelsoundingtestapp"); - } -} diff --git a/android/ChannelSoundingTestApp/app/src/test/java/com/android/bluetooth/channelsoundingtestapp/ExampleUnitTest.java b/android/ChannelSoundingTestApp/app/src/test/java/com/android/bluetooth/channelsoundingtestapp/ExampleUnitTest.java deleted file mode 100644 index a3309495cf..0000000000 --- a/android/ChannelSoundingTestApp/app/src/test/java/com/android/bluetooth/channelsoundingtestapp/ExampleUnitTest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.channelsoundingtestapp; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.Test; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> - */ -public class ExampleUnitTest { - - @Test - public void addition_isCorrect() { - assertThat(2 + 2).isEqualTo(4); - } -} diff --git a/android/apishim/Android.bp b/android/apishim/Android.bp index 9d980b2498..7780c014f3 100644 --- a/android/apishim/Android.bp +++ b/android/apishim/Android.bp @@ -23,9 +23,7 @@ java_defaults { "androidx.annotation_annotation", "modules-utils-build", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", } diff --git a/android/app/Android.bp b/android/app/Android.bp index db85914522..2da5e5ada9 100644 --- a/android/app/Android.bp +++ b/android/app/Android.bp @@ -32,9 +32,7 @@ java_library { name: "bluetooth.mapsapi", srcs: ["lib/mapapi/**/*.java"], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", sdk_version: "module_current", lint: { @@ -47,9 +45,7 @@ java_library { srcs: [":framework-mms-shared-srcs"], libs: ["unsupportedappusage"], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", sdk_version: "module_current", lint: { @@ -80,7 +76,7 @@ cc_library_shared { "packages/modules/Bluetooth/system", "packages/modules/Bluetooth/system/gd", ], - // libbluetooth_jni is the jni lib included in the btservices apex. + // libbluetooth_jni is the jni lib included in the com.android.bt apex. // As this library is inside an APEX the shared_libs that does not // expose stubs are copied inside it. As a result using those as // shared libraries is less interesting as they are not shared, so we link @@ -164,9 +160,7 @@ cc_library_shared { sanitize: { scs: true, }, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", } @@ -200,15 +194,12 @@ cc_library { sanitize: { scs: true, }, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], host_supported: true, min_sdk_version: "Tiramisu", } // Bluetooth APK - android_app { name: "Bluetooth", defaults: ["bluetooth_framework_errorprone_rules"], @@ -219,6 +210,7 @@ android_app { ":system-messages-proto-src", "proto/keystore.proto", "src/**/*.java", + "src/**/*.kt", ], proto: { type: "lite", @@ -234,7 +226,6 @@ android_app { jni_uses_platform_apis: true, libs: [ - "app-compat-annotations", "bluetooth_constants_java", "bluetooth_flags_java_lib", "error_prone_annotations", @@ -246,7 +237,6 @@ android_app { "framework-mediaprovider.stubs.module_lib", "framework-statsd.stubs.module_lib", "framework-tethering.stubs.module_lib", - "unsupportedappusage", // Need to link the class at runtime "framework-bluetooth.stubs.module_lib", @@ -338,39 +328,15 @@ android_app { enabled: true, shrink: true, optimize: false, - // TODO(b/289285719): Revisit after resolving mocking issues in testing. - proguard_compatibility: true, proguard_flags_files: ["proguard.flags"], }, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", sdk_version: "module_current", updatable: true, } java_library { - name: "bluetooth.change-ids", - srcs: [ - "src/com/android/bluetooth/ChangeIds.java", - ], - libs: [ - "app-compat-annotations", - ], - apex_available: [ - "com.android.btservices", - ], - min_sdk_version: "Tiramisu", - sdk_version: "module_current", -} - -platform_compat_config { - name: "bluetoothapk-platform-compat-config", - src: ":bluetooth.change-ids", -} - -java_library { name: "bluetooth-proto-enums-java-gen", installable: false, proto: { @@ -379,9 +345,7 @@ java_library { srcs: [ ":srcs_bluetooth_protos", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", sdk_version: "module_current", } diff --git a/android/app/OWNERS b/android/app/OWNERS index 9edeffc18e..99c73be372 100644 --- a/android/app/OWNERS +++ b/android/app/OWNERS @@ -9,3 +9,6 @@ okamil@google.com poahlo@google.com siyuanh@google.com wescande@google.com + +# Reviewers for Channel Sounding related files +per-file /src/com/android/bluetooth/gatt/DistanceMeasurement*.java=file:/OWNERS_channel_sounding diff --git a/android/app/aidl/Android.bp b/android/app/aidl/Android.bp index 0829e0296c..edc1d050f9 100644 --- a/android/app/aidl/Android.bp +++ b/android/app/aidl/Android.bp @@ -17,6 +17,7 @@ filegroup { "android/bluetooth/IBluetoothA2dp.aidl", "android/bluetooth/IBluetoothA2dpSink.aidl", "android/bluetooth/IBluetoothActivityEnergyInfoListener.aidl", + "android/bluetooth/IBluetoothAdvertise.aidl", "android/bluetooth/IBluetoothAvrcpController.aidl", "android/bluetooth/IBluetoothCallback.aidl", "android/bluetooth/IBluetoothConnectionCallback.aidl", @@ -59,6 +60,7 @@ filegroup { "android/bluetooth/IBluetoothSocketManager.aidl", "android/bluetooth/IBluetoothVolumeControl.aidl", "android/bluetooth/IBluetoothVolumeControlCallback.aidl", + "android/bluetooth/IDistanceMeasurement.aidl", "android/bluetooth/IncomingRfcommSocketInfo.aidl", "android/bluetooth/le/IAdvertisingSetCallback.aidl", "android/bluetooth/le/IDistanceMeasurementCallback.aidl", diff --git a/android/app/aidl/android/bluetooth/IBluetooth.aidl b/android/app/aidl/android/bluetooth/IBluetooth.aidl index 158e37823f..9ab583b042 100644 --- a/android/app/aidl/android/bluetooth/IBluetooth.aidl +++ b/android/app/aidl/android/bluetooth/IBluetooth.aidl @@ -344,4 +344,10 @@ interface IBluetooth @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)") boolean isRfcommSocketOffloadSupported(in AttributionSource source); + + @JavaPassthrough(annotation="@android.annotation.RequiresNoPermission") + IBinder getBluetoothAdvertise(); + + @JavaPassthrough(annotation="@android.annotation.RequiresNoPermission") + IBinder getDistanceMeasurement(); } diff --git a/android/app/aidl/android/bluetooth/IBluetoothA2dp.aidl b/android/app/aidl/android/bluetooth/IBluetoothA2dp.aidl index bcfc95585d..fa5d1362ac 100644 --- a/android/app/aidl/android/bluetooth/IBluetoothA2dp.aidl +++ b/android/app/aidl/android/bluetooth/IBluetoothA2dp.aidl @@ -53,7 +53,7 @@ interface IBluetoothA2dp { boolean isA2dpPlaying(in BluetoothDevice device, in AttributionSource attributionSource); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)") List<BluetoothCodecType> getSupportedCodecTypes(); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true)") BluetoothCodecStatus getCodecStatus(in BluetoothDevice device, in AttributionSource attributionSource); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true)") oneway void setCodecConfigPreference(in BluetoothDevice device, in BluetoothCodecConfig codecConfig, in AttributionSource attributionSource); diff --git a/android/app/aidl/android/bluetooth/IBluetoothAdvertise.aidl b/android/app/aidl/android/bluetooth/IBluetoothAdvertise.aidl new file mode 100644 index 0000000000..bb7a49daf0 --- /dev/null +++ b/android/app/aidl/android/bluetooth/IBluetoothAdvertise.aidl @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2025 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; + +import android.bluetooth.le.AdvertiseSettings; +import android.bluetooth.le.AdvertiseData; +import android.bluetooth.le.AdvertisingSetParameters; +import android.bluetooth.le.IAdvertisingSetCallback; +import android.bluetooth.le.IPeriodicAdvertisingCallback; +import android.bluetooth.le.PeriodicAdvertisingParameters; +import android.content.AttributionSource; + +/** + * API for interacting with BLE advertising + * @hide + */ +interface IBluetoothAdvertise { + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE,android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true)") + void startAdvertisingSet(in AdvertisingSetParameters parameters, in AdvertiseData advertiseData, + in AdvertiseData scanResponse, in PeriodicAdvertisingParameters periodicParameters, + in AdvertiseData periodicData, in int duration, in int maxExtAdvEvents, in int gattServerIf, + in IAdvertisingSetCallback callback, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") + void stopAdvertisingSet(in IAdvertisingSetCallback callback, in AttributionSource attributionSource); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") + void getOwnAddress(in int advertiserId, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") + void enableAdvertisingSet(in int advertiserId, in boolean enable, in int duration, in int maxExtAdvEvents, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") + void setAdvertisingData(in int advertiserId, in AdvertiseData data, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") + void setScanResponseData(in int advertiserId, in AdvertiseData data, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE,android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true)") + void setAdvertisingParameters(in int advertiserId, in AdvertisingSetParameters parameters, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") + void setPeriodicAdvertisingParameters(in int advertiserId, in PeriodicAdvertisingParameters parameters, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") + void setPeriodicAdvertisingData(in int advertiserId, in AdvertiseData data, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") + void setPeriodicAdvertisingEnable(in int advertiserId, in boolean enable, in AttributionSource attributionSource); +} diff --git a/android/app/aidl/android/bluetooth/IBluetoothGatt.aidl b/android/app/aidl/android/bluetooth/IBluetoothGatt.aidl index bfc380f50a..40976dbf00 100644 --- a/android/app/aidl/android/bluetooth/IBluetoothGatt.aidl +++ b/android/app/aidl/android/bluetooth/IBluetoothGatt.aidl @@ -16,30 +16,12 @@ package android.bluetooth; -import android.app.PendingIntent; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGattService; -import android.bluetooth.le.AdvertiseSettings; -import android.bluetooth.le.AdvertiseData; -import android.bluetooth.le.AdvertisingSetParameters; -import android.bluetooth.le.DistanceMeasurementMethod; -import android.bluetooth.le.DistanceMeasurementParams; -import android.bluetooth.le.IDistanceMeasurementCallback; -import android.bluetooth.le.PeriodicAdvertisingParameters; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanResult; -import android.bluetooth.le.ScanSettings; -import android.bluetooth.le.ResultStorageDescriptor; -import android.content.AttributionSource; -import android.os.ParcelUuid; -import android.os.WorkSource; - import android.bluetooth.IBluetoothGattCallback; import android.bluetooth.IBluetoothGattServerCallback; -import android.bluetooth.le.IAdvertisingSetCallback; -import android.bluetooth.le.IPeriodicAdvertisingCallback; -import android.bluetooth.le.IScannerCallback; - +import android.content.AttributionSource; +import android.os.ParcelUuid; /** * API for interacting with BLE / GATT * @hide @@ -48,31 +30,6 @@ interface IBluetoothGatt { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE,android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true)") - void startAdvertisingSet(in AdvertisingSetParameters parameters, in AdvertiseData advertiseData, - in AdvertiseData scanResponse, in PeriodicAdvertisingParameters periodicParameters, - in AdvertiseData periodicData, in int duration, in int maxExtAdvEvents, in int gattServerIf, - in IAdvertisingSetCallback callback, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") - void stopAdvertisingSet(in IAdvertisingSetCallback callback, in AttributionSource attributionSource); - - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") - void getOwnAddress(in int advertiserId, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") - void enableAdvertisingSet(in int advertiserId, in boolean enable, in int duration, in int maxExtAdvEvents, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") - void setAdvertisingData(in int advertiserId, in AdvertiseData data, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") - void setScanResponseData(in int advertiserId, in AdvertiseData data, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE,android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true)") - void setAdvertisingParameters(in int advertiserId, in AdvertisingSetParameters parameters, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") - void setPeriodicAdvertisingParameters(in int advertiserId, in PeriodicAdvertisingParameters parameters, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") - void setPeriodicAdvertisingData(in int advertiserId, in AdvertiseData data, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)") - void setPeriodicAdvertisingEnable(in int advertiserId, in boolean enable, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback, boolean eatt_support, in AttributionSource attributionSource); @@ -150,17 +107,4 @@ interface IBluetoothGatt { void disconnectAll(in AttributionSource attributionSource); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true)") int subrateModeRequest(in int clientIf, in BluetoothDevice device, in int subrateMode, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") - List<DistanceMeasurementMethod> getSupportedDistanceMeasurementMethods(in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") - void startDistanceMeasurement(in ParcelUuid uuid, in DistanceMeasurementParams params, in IDistanceMeasurementCallback callback, - in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") - int stopDistanceMeasurement(in ParcelUuid uuid, in BluetoothDevice device, in int method, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") - int getChannelSoundingMaxSupportedSecurityLevel(in BluetoothDevice remoteDevice, in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") - int getLocalChannelSoundingMaxSupportedSecurityLevel(in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") - int[] getChannelSoundingSupportedSecurityLevels(in AttributionSource attributionSource); } diff --git a/android/app/aidl/android/bluetooth/IDistanceMeasurement.aidl b/android/app/aidl/android/bluetooth/IDistanceMeasurement.aidl new file mode 100644 index 0000000000..78c793a351 --- /dev/null +++ b/android/app/aidl/android/bluetooth/IDistanceMeasurement.aidl @@ -0,0 +1,44 @@ +/* + * Copyright 2025 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; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.le.DistanceMeasurementMethod; +import android.bluetooth.le.DistanceMeasurementParams; +import android.bluetooth.le.IDistanceMeasurementCallback; +import android.content.AttributionSource; +import android.os.ParcelUuid; + +/** + * API for interacting with distance measurement + * @hide + */ +interface IDistanceMeasurement { + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") + List<DistanceMeasurementMethod> getSupportedDistanceMeasurementMethods(in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") + void startDistanceMeasurement(in ParcelUuid uuid, in DistanceMeasurementParams params, in IDistanceMeasurementCallback callback, + in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") + int stopDistanceMeasurement(in ParcelUuid uuid, in BluetoothDevice device, in int method, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") + int getChannelSoundingMaxSupportedSecurityLevel(in BluetoothDevice remoteDevice, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") + int getLocalChannelSoundingMaxSupportedSecurityLevel(in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") + int[] getChannelSoundingSupportedSecurityLevels(in AttributionSource attributionSource); +} diff --git a/android/app/aidl/android/bluetooth/le/IAdvertisingSetCallback.aidl b/android/app/aidl/android/bluetooth/le/IAdvertisingSetCallback.aidl index 35e88a4a68..449b173309 100644 --- a/android/app/aidl/android/bluetooth/le/IAdvertisingSetCallback.aidl +++ b/android/app/aidl/android/bluetooth/le/IAdvertisingSetCallback.aidl @@ -20,7 +20,7 @@ package android.bluetooth.le; * @hide */ oneway interface IAdvertisingSetCallback { - void onAdvertisingSetStarted(in IBinder gattBinder, in int advertiserId, in int tx_power, in int status); + void onAdvertisingSetStarted(in IBinder advertiseBinder, in int advertiserId, in int tx_power, in int status); void onOwnAddressRead(in int advertiserId, in int addressType, in String address); void onAdvertisingSetStopped(in int advertiserId); void onAdvertisingEnabled(in int advertiserId, in boolean enable, in int status); diff --git a/android/app/change-ids/Android.bp b/android/app/change-ids/Android.bp new file mode 100644 index 0000000000..ab4e853c3f --- /dev/null +++ b/android/app/change-ids/Android.bp @@ -0,0 +1,31 @@ +// Copyright 2025 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library { + name: "bluetooth.change-ids", + srcs: ["com/android/bluetooth/ChangeIds.java"], + libs: ["app-compat-annotations"], + apex_available: ["com.android.bt"], + min_sdk_version: "Tiramisu", + sdk_version: "module_current", +} + +platform_compat_config { + name: "bluetoothapk-platform-compat-config", + src: ":bluetooth.change-ids", +} diff --git a/android/app/src/com/android/bluetooth/ChangeIds.java b/android/app/change-ids/com/android/bluetooth/ChangeIds.java index 4c06486dee..4c06486dee 100644 --- a/android/app/src/com/android/bluetooth/ChangeIds.java +++ b/android/app/change-ids/com/android/bluetooth/ChangeIds.java diff --git a/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp b/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp index 805e22b0df..6df96370b1 100644 --- a/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp +++ b/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp @@ -2228,6 +2228,25 @@ static jboolean disconnectAllAclsNative(JNIEnv* /* env */, jobject /* obj */) { return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } +static jboolean disconnectAclNative(JNIEnv* env, jobject /* obj */, jbyteArray address, + jint transport) { + log::verbose(""); + + if (!sBluetoothInterface) { + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (addr == nullptr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + RawAddress addr_obj = {}; + addr_obj.FromOctets(reinterpret_cast<uint8_t*>(addr)); + + return sBluetoothInterface->disconnect_acl(addr_obj, transport); +} + static jboolean allowWakeByHidNative(JNIEnv* /* env */, jobject /* obj */) { log::verbose(""); @@ -2320,6 +2339,7 @@ int register_com_android_bluetooth_btservice_AdapterService(JNIEnv* env) { {"clearFilterAcceptListNative", "()Z", reinterpret_cast<void*>(clearFilterAcceptListNative)}, {"disconnectAllAclsNative", "()Z", reinterpret_cast<void*>(disconnectAllAclsNative)}, + {"disconnectAclNative", "([BI)Z", reinterpret_cast<void*>(disconnectAclNative)}, {"allowWakeByHidNative", "()Z", reinterpret_cast<void*>(allowWakeByHidNative)}, {"restoreFilterAcceptListNative", "()Z", reinterpret_cast<void*>(restoreFilterAcceptListNative)}, diff --git a/android/app/jni/com_android_bluetooth_gatt.cpp b/android/app/jni/com_android_bluetooth_gatt.cpp index 80e334e844..014668bfd0 100644 --- a/android/app/jni/com_android_bluetooth_gatt.cpp +++ b/android/app/jni/com_android_bluetooth_gatt.cpp @@ -122,6 +122,16 @@ static std::vector<uint8_t> toVector(JNIEnv* env, jbyteArray ba) { return data_vec; } +static std::string jstr_to_str(JNIEnv* env, jstring js) { + const char* cstr = env->GetStringUTFChars(js, NULL); + if (cstr == nullptr) { + return ""; + } + std::string ret = std::string(cstr); + env->ReleaseStringUTFChars(js, cstr); + return ret; +} + namespace android { /** @@ -1155,8 +1165,10 @@ public: void OnDistanceMeasurementResult(RawAddress address, uint32_t centimeter, uint32_t error_centimeter, int azimuth_angle, int error_azimuth_angle, int altitude_angle, - int error_altitude_angle, uint64_t elapsedRealtimeNanos, - int8_t confidence_level, uint8_t method) { + int error_altitude_angle, uint64_t elapsed_realtime_nanos, + int8_t confidence_level, double delay_spread_meters, + uint8_t detected_attack_level, double velocity_meters_per_second, + uint8_t method) { std::shared_lock<std::shared_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mDistanceMeasurementCallbacksObj) { @@ -1166,7 +1178,8 @@ public: sCallbackEnv->CallVoidMethod( mDistanceMeasurementCallbacksObj, method_onDistanceMeasurementResult, addr.get(), centimeter, error_centimeter, azimuth_angle, error_azimuth_angle, altitude_angle, - error_altitude_angle, elapsedRealtimeNanos, confidence_level, method); + error_altitude_angle, elapsed_realtime_nanos, confidence_level, delay_spread_meters, + detected_attack_level, velocity_meters_per_second, method); } }; @@ -1259,13 +1272,13 @@ static int gattClientGetDeviceTypeNative(JNIEnv* env, jobject /* object */, jstr return sGattIf->client->get_device_type(str2addr(env, address)); } -static void gattClientRegisterAppNative(JNIEnv* /* env */, jobject /* object */, jlong app_uuid_lsb, - jlong app_uuid_msb, jboolean eatt_support) { +static void gattClientRegisterAppNative(JNIEnv* env, jobject /* object */, jlong app_uuid_lsb, + jlong app_uuid_msb, jstring name, jboolean eatt_support) { if (!sGattIf) { return; } Uuid uuid = from_java_uuid(app_uuid_msb, app_uuid_lsb); - sGattIf->client->register_client(uuid, eatt_support); + sGattIf->client->register_client(uuid, jstr_to_str(env, name).c_str(), eatt_support); } static void gattClientUnregisterAppNative(JNIEnv* /* env */, jobject /* object */, jint clientIf) { @@ -2914,7 +2927,7 @@ static int register_com_android_bluetooth_gatt_distance_measurement(JNIEnv* env) &method_onDistanceMeasurementStarted}, {"onDistanceMeasurementStopped", "(Ljava/lang/String;II)V", &method_onDistanceMeasurementStopped}, - {"onDistanceMeasurementResult", "(Ljava/lang/String;IIIIIIJII)V", + {"onDistanceMeasurementResult", "(Ljava/lang/String;IIIIIIJIDIDI)V", &method_onDistanceMeasurementResult}, }; GET_JAVA_METHODS(env, "com/android/bluetooth/gatt/DistanceMeasurementNativeInterface", @@ -2929,7 +2942,8 @@ static int register_com_android_bluetooth_gatt_(JNIEnv* env) { {"cleanupNative", "()V", (void*)cleanupNative}, {"gattClientGetDeviceTypeNative", "(Ljava/lang/String;)I", (void*)gattClientGetDeviceTypeNative}, - {"gattClientRegisterAppNative", "(JJZ)V", (void*)gattClientRegisterAppNative}, + {"gattClientRegisterAppNative", "(JJLjava/lang/String;Z)V", + (void*)gattClientRegisterAppNative}, {"gattClientUnregisterAppNative", "(I)V", (void*)gattClientUnregisterAppNative}, {"gattClientConnectNative", "(ILjava/lang/String;IZIZII)V", (void*)gattClientConnectNative}, diff --git a/android/app/proguard.flags b/android/app/proguard.flags index 75da0433f5..80b6c4a32a 100644 --- a/android/app/proguard.flags +++ b/android/app/proguard.flags @@ -7,4 +7,10 @@ -keep class android.support.v4.media.MediaDescriptionCompat { *; } # Required for tests that use Mockito's thenThrow with checked exceptions. --keepattributes Exceptions
\ No newline at end of file +-keepattributes Exceptions + +# Minimal set of keep rules for mocked methods with checked exceptions. +# This can be relaxed to specific packages if that simplifies testing. +# See also https://r8.googlesource.com/r8/+/refs/heads/master/compatibility-faq.md#r8-full-mode. +-keepclassmembers,allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification class com.android.bluetooth.BluetoothMethodProxy { public <methods>; } +-keepclassmembers,allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification class com.android.obex.Operation { public <methods>; } diff --git a/android/app/src/com/android/bluetooth/Utils.java b/android/app/src/com/android/bluetooth/Utils.java index ca897af46e..0f15903dfb 100644 --- a/android/app/src/com/android/bluetooth/Utils.java +++ b/android/app/src/com/android/bluetooth/Utils.java @@ -44,6 +44,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.app.BroadcastOptions; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.companion.AssociationInfo; import android.companion.CompanionDeviceManager; @@ -72,6 +73,7 @@ import androidx.annotation.VisibleForTesting; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.flags.Flags; import org.xmlpull.v1.XmlPullParser; @@ -169,6 +171,34 @@ public final class Utils { } /** + * Checks CoD and metadata to determine if the device is a watch + * + * @param service Adapter service + * @param device the remote device + * @return {@code true} if it's a watch, {@code false} otherwise + */ + public static boolean isWatch( + @NonNull AdapterService service, @NonNull BluetoothDevice device) { + // Check CoD + BluetoothClass deviceClass = new BluetoothClass(service.getRemoteClass(device)); + if (deviceClass.getDeviceClass() == BluetoothClass.Device.WEARABLE_WRIST_WATCH) { + return true; + } + + // Check metadata + DatabaseManager mDbManager = service.getDatabase(); + byte[] deviceType = mDbManager.getCustomMeta(device, BluetoothDevice.METADATA_DEVICE_TYPE); + if (deviceType == null) { + return false; + } + String deviceTypeStr = new String(deviceType); + if (deviceTypeStr.equals(BluetoothDevice.DEVICE_TYPE_WATCH)) { + return true; + } + return false; + } + + /** * Only exposed for testing, do not invoke this method outside of tests. * * @param enabled true if the dual mode state is enabled, false otherwise @@ -845,10 +875,7 @@ public final class Utils { return true; } - Log.e( - TAG, - "Permission denial: Need ACCESS_COARSE_LOCATION " - + "permission to get scan results"); + Log.e(TAG, "Need ACCESS_COARSE_LOCATION permission for " + currentAttribution); return false; } @@ -889,8 +916,8 @@ public final class Utils { Log.e( TAG, - "Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION" - + "permission to get scan results"); + "Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission for " + + currentAttribution); return false; } @@ -920,9 +947,7 @@ public final class Utils { return true; } - Log.e( - TAG, - "Permission denial: Need ACCESS_FINE_LOCATION " + "permission to get scan results"); + Log.e(TAG, "Need ACCESS_FINE_LOCATION permission for " + currentAttribution); return false; } diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java index 136a0d4621..100076127b 100644 --- a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java +++ b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java @@ -50,7 +50,6 @@ import android.media.BluetoothProfileConnectionInfo; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerThread; import android.os.Looper; import android.sysprop.BluetoothProperties; import android.util.Log; @@ -1515,12 +1514,14 @@ public class A2dpService extends ProfileService { @Override public BluetoothCodecStatus getCodecStatus( BluetoothDevice device, AttributionSource source) { + requireNonNull(device); A2dpService service = getServiceAndEnforceConnect(source); if (service == null) { return null; } - service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + Utils.enforceCdmAssociationIfNotBluetoothPrivileged( + service, service.mCompanionDeviceManager, source, device); return service.getCodecStatus(device); } @@ -1530,6 +1531,7 @@ public class A2dpService extends ProfileService { BluetoothDevice device, BluetoothCodecConfig codecConfig, AttributionSource source) { + requireNonNull(device); A2dpService service = getServiceAndEnforceConnect(source); if (service == null) { return; diff --git a/android/app/src/com/android/bluetooth/audio_util/MediaBrowserWrapper.java b/android/app/src/com/android/bluetooth/audio_util/MediaBrowserWrapper.java index f45fc0b189..e4de73700c 100644 --- a/android/app/src/com/android/bluetooth/audio_util/MediaBrowserWrapper.java +++ b/android/app/src/com/android/bluetooth/audio_util/MediaBrowserWrapper.java @@ -79,6 +79,7 @@ class MediaBrowserWrapper { private final Looper mLooper; private final String mPackageName; private final Handler mRunHandler; + private final String mClassName; private ConnectionState mBrowserConnectionState = ConnectionState.DISCONNECTED; @@ -94,6 +95,7 @@ class MediaBrowserWrapper { Context context, Looper looper, String packageName, String className) { mContext = context; mPackageName = packageName; + mClassName = className; mLooper = looper; mRunHandler = new Handler(mLooper); mWrappedBrowser = @@ -383,4 +385,9 @@ class MediaBrowserWrapper { return mRunHandler; } } + + @Override + public String toString() { + return "Browsable Package & Class Name: " + mPackageName + " " + mClassName + "\n"; + } } diff --git a/android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java b/android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java index b04f684f34..00213d0ef6 100644 --- a/android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java +++ b/android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java @@ -985,6 +985,19 @@ public class MediaPlayerList { mActivePlayerId = playerId; + if (Utils.isPtsTestMode()) { + sendFolderUpdate(true, true, false); + } else if (Flags.setAddressedPlayer() && Flags.browsingRefactor()) { + // If the browsing refactor flag is not active, addressed player should always be 0. + // If the new active player has been set by Addressed player key event + // We don't send an addressed player update. + if (mActivePlayerId != mAddressedPlayerId) { + mAddressedPlayerId = mActivePlayerId; + Log.d(TAG, "setActivePlayer AddressedPlayer changed to " + mAddressedPlayerId); + sendFolderUpdate(false, true, false); + } + } + MediaPlayerWrapper player = getActivePlayer(); if (player == null) return; @@ -1002,19 +1015,6 @@ public class MediaPlayerList { return; } - if (Utils.isPtsTestMode()) { - sendFolderUpdate(true, true, false); - } else if (Flags.setAddressedPlayer() && Flags.browsingRefactor()) { - // If the browsing refactor flag is not active, addressed player should always be 0. - // If the new active player has been set by Addressed player key event - // We don't send an addressed player update. - if (mActivePlayerId != mAddressedPlayerId) { - mAddressedPlayerId = mActivePlayerId; - Log.d(TAG, "setActivePlayer AddressedPlayer changed to " + mAddressedPlayerId); - sendFolderUpdate(false, true, false); - } - } - MediaData data = player.getCurrentMediaData(); if (mAudioPlaybackIsActive) { data.state = mCurrMediaData.state; diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/bip/BipImageDescriptor.java b/android/app/src/com/android/bluetooth/avrcpcontroller/bip/BipImageDescriptor.java index 76daff6a52..1e3be0184a 100644 --- a/android/app/src/com/android/bluetooth/avrcpcontroller/bip/BipImageDescriptor.java +++ b/android/app/src/com/android/bluetooth/avrcpcontroller/bip/BipImageDescriptor.java @@ -87,7 +87,7 @@ public class BipImageDescriptor { * @param encoding The encoding you would like to set as a BIP spec defined string * @return This object so you can continue building */ - public Builder setPropietaryEncoding(String encoding) { + public Builder setProprietaryEncoding(String encoding) { mImageDescriptor.mEncoding = new BipEncoding(BipEncoding.USR_XXX, encoding); return this; } diff --git a/android/app/src/com/android/bluetooth/bas/BatteryStateMachine.java b/android/app/src/com/android/bluetooth/bas/BatteryStateMachine.java index 67b2b4a005..7b4d7fe3b4 100644 --- a/android/app/src/com/android/bluetooth/bas/BatteryStateMachine.java +++ b/android/app/src/com/android/bluetooth/bas/BatteryStateMachine.java @@ -32,6 +32,7 @@ import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; +import android.content.AttributionSource; import android.os.Looper; import android.os.Message; import android.util.Log; @@ -172,6 +173,10 @@ public class BatteryStateMachine extends StateMachine { @VisibleForTesting @SuppressLint("AndroidFrameworkRequiresPermission") // We should call internal gatt interface boolean connectGatt() { + mDevice.setAttributionSource( + (new AttributionSource.Builder(AttributionSource.myAttributionSource())) + .setAttributionTag("BatteryService") + .build()); mBluetoothGatt = mDevice.connectGatt( mService, diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java index 2fff02ea1e..0daef0175a 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java @@ -2499,6 +2499,28 @@ public class BassClientService extends ProfileService { BluetoothDevice srcDevice = getDeviceForSyncHandle(syncHandle); mSyncHandleToDeviceMap.remove(syncHandle); int broadcastId = getBroadcastIdForSyncHandle(syncHandle); + if (leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator()) { + synchronized (mPendingSourcesToAdd) { + Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); + while (iterator.hasNext()) { + AddSourceData pendingSourcesToAdd = iterator.next(); + if (pendingSourcesToAdd.mSourceMetadata.getBroadcastId() == broadcastId) { + iterator.remove(); + } + } + } + synchronized (mSinksWaitingForPast) { + Iterator<Map.Entry<BluetoothDevice, Pair<Integer, Integer>>> iterator = + mSinksWaitingForPast.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry<BluetoothDevice, Pair<Integer, Integer>> entry = iterator.next(); + int broadcastIdForPast = entry.getValue().first; + if (broadcastId == broadcastIdForPast) { + iterator.remove(); + } + } + } + } mSyncHandleToBroadcastIdMap.remove(syncHandle); if (srcDevice != null) { mPeriodicAdvertisementResultMap.get(srcDevice).remove(broadcastId); @@ -3091,6 +3113,13 @@ public class BassClientService extends ProfileService { mCallbacks.notifySourceAddFailed(device, sourceMetadata, statusCode); continue; } + if (!stateMachine.isBassStateReady()) { + Log.d(TAG, "addSource: BASS state not ready, retry later with device: " + device); + synchronized (mPendingSourcesToAdd) { + mPendingSourcesToAdd.add(new AddSourceData(device, sourceMetadata, isGroupOp)); + } + continue; + } if (stateMachine.hasPendingSourceOperation()) { Log.w( TAG, @@ -3730,38 +3759,76 @@ public class BassClientService extends ProfileService { } } - /** Handle device newly connected and its peer device still has active source */ - private void checkAndResumeBroadcast(BluetoothDevice sink) { + /* Handle device Bass state ready and check if assistant should resume broadcast */ + private void handleBassStateReady(BluetoothDevice sink) { + // Check its peer device still has active source Map<Integer, BluetoothLeBroadcastMetadata> entry = mBroadcastMetadataMap.get(sink); - if (entry == null) { - Log.d(TAG, "checkAndResumeBroadcast: no entry for device: " + sink + ", available"); - return; + if (entry != null) { + for (Map.Entry<Integer, BluetoothLeBroadcastMetadata> idMetadataIdPair : + entry.entrySet()) { + BluetoothLeBroadcastMetadata metadata = idMetadataIdPair.getValue(); + if (metadata == null) { + Log.d(TAG, "handleBassStateReady: no metadata available"); + continue; + } + for (BluetoothDevice groupDevice : getTargetDeviceList(sink, true)) { + if (groupDevice.equals(sink)) { + continue; + } + // Check peer device + Optional<BluetoothLeBroadcastReceiveState> receiver = + getOrCreateStateMachine(groupDevice).getAllSources().stream() + .filter(e -> e.getBroadcastId() == metadata.getBroadcastId()) + .findAny(); + if (receiver.isPresent() + && !getAllSources(sink).stream() + .anyMatch( + rs -> + (rs.getBroadcastId() + == receiver.get().getBroadcastId()))) { + Log.d(TAG, "handleBassStateReady: restore the source for device, " + sink); + addSource(sink, metadata, false); + return; + } + } + } + } else { + Log.d(TAG, "handleBassStateReady: no entry for device: " + sink + ", available"); } - for (Map.Entry<Integer, BluetoothLeBroadcastMetadata> idMetadataIdPair : entry.entrySet()) { - BluetoothLeBroadcastMetadata metadata = idMetadataIdPair.getValue(); - if (metadata == null) { - Log.d(TAG, "checkAndResumeBroadcast: no metadata available"); - continue; - } - for (BluetoothDevice groupDevice : getTargetDeviceList(sink, true)) { - if (groupDevice.equals(sink)) { - continue; + // Continue to check if there is pending source to add due to BASS not ready + synchronized (mPendingSourcesToAdd) { + Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); + while (iterator.hasNext()) { + AddSourceData pendingSourcesToAdd = iterator.next(); + if (pendingSourcesToAdd.mSink.equals(sink)) { + Log.d(TAG, "handleBassStateReady: retry adding source with device, " + sink); + addSource( + pendingSourcesToAdd.mSink, + pendingSourcesToAdd.mSourceMetadata, + pendingSourcesToAdd.mIsGroupOp); + iterator.remove(); + return; } - // Check peer device - Optional<BluetoothLeBroadcastReceiveState> receiver = - getOrCreateStateMachine(groupDevice).getAllSources().stream() - .filter(e -> e.getBroadcastId() == metadata.getBroadcastId()) - .findAny(); - if (receiver.isPresent() - && !getAllSources(sink).stream() - .anyMatch( - rs -> - (rs.getBroadcastId() - == receiver.get().getBroadcastId()))) { - Log.d(TAG, "checkAndResumeBroadcast: restore the source for device: " + sink); - addSource(sink, metadata, false); + } + } + } + + /* Handle device Bass state setup failed */ + private void handleBassStateSetupFailed(BluetoothDevice sink) { + // Check if there is pending source to add due to BASS not ready + synchronized (mPendingSourcesToAdd) { + Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); + while (iterator.hasNext()) { + AddSourceData pendingSourcesToAdd = iterator.next(); + if (pendingSourcesToAdd.mSink.equals(sink)) { + mCallbacks.notifySourceAddFailed( + pendingSourcesToAdd.mSink, + pendingSourcesToAdd.mSourceMetadata, + BluetoothStatusCodes.ERROR_REMOTE_NOT_ENOUGH_RESOURCES); + iterator.remove(); + return; } } } @@ -4009,7 +4076,10 @@ public class BassClientService extends ProfileService { mUnicastSourceStreamStatus = Optional.of(status); if (status == STATUS_LOCAL_STREAM_REQUESTED) { - if (areReceiversReceivingOnlyExternalBroadcast(getConnectedDevices())) { + if ((leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator() + && hasPrimaryDeviceManagedExternalBroadcast()) + || (!leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator() + && areReceiversReceivingOnlyExternalBroadcast(getConnectedDevices()))) { if (leaudioBroadcastAssistantPeripheralEntrustment()) { cacheSuspendingSources(BassConstants.INVALID_BROADCAST_ID); List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> sourcesToStop = @@ -4022,11 +4092,13 @@ public class BassClientService extends ProfileService { suspendAllReceiversSourceSynchronization(); } } - for (Map.Entry<Integer, PauseType> entry : mPausedBroadcastIds.entrySet()) { - Integer broadcastId = entry.getKey(); - PauseType pauseType = entry.getValue(); - if (pauseType != PauseType.HOST_INTENTIONAL) { - suspendReceiversSourceSynchronization(broadcastId); + if (!leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator()) { + for (Map.Entry<Integer, PauseType> entry : mPausedBroadcastIds.entrySet()) { + Integer broadcastId = entry.getKey(); + PauseType pauseType = entry.getValue(); + if (pauseType != PauseType.HOST_INTENTIONAL) { + suspendReceiversSourceSynchronization(broadcastId); + } } } } else if (status == STATUS_LOCAL_STREAM_SUSPENDED) { @@ -4190,9 +4262,19 @@ public class BassClientService extends ProfileService { return activeSinks; } + /** Get sink devices synced to the broadcasts by broadcast id */ + public List<BluetoothDevice> getSyncedBroadcastSinks(int broadcastId) { + return getConnectedDevices().stream() + .filter( + device -> + getAllSources(device).stream() + .anyMatch(rs -> rs.getBroadcastId() == broadcastId)) + .toList(); + } + private boolean isSyncedToBroadcastStream(Long syncState) { - return syncState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS - && syncState != BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG; + return syncState != BassConstants.BCAST_RCVR_STATE_BIS_SYNC_NOT_SYNC_TO_BIS + && syncState != BassConstants.BCAST_RCVR_STATE_BIS_SYNC_FAILED_SYNC_TO_BIG; } /** Handle broadcast state changed */ @@ -4235,6 +4317,7 @@ public class BassClientService extends ProfileService { private static final int MSG_RECEIVESTATE_CHANGED = 12; private static final int MSG_SOURCE_LOST = 13; private static final int MSG_BASS_STATE_READY = 14; + private static final int MSG_BASS_STATE_SETUP_FAILED = 15; @GuardedBy("mCallbacksList") private final RemoteCallbackList<IBluetoothLeBroadcastAssistantCallback> mCallbacksList = @@ -4298,7 +4381,12 @@ public class BassClientService extends ProfileService { switch (msg.what) { case MSG_BASS_STATE_READY: sink = (BluetoothDevice) msg.obj; - sService.checkAndResumeBroadcast(sink); + sService.handleBassStateReady(sink); + isMsgHandled = true; + break; + case MSG_BASS_STATE_SETUP_FAILED: + sink = (BluetoothDevice) msg.obj; + sService.handleBassStateSetupFailed(sink); isMsgHandled = true; break; default: @@ -4577,6 +4665,11 @@ public class BassClientService extends ProfileService { sEventLogger.logd(TAG, "notifyBassStateReady: sink: " + sink); obtainMessage(MSG_BASS_STATE_READY, sink).sendToTarget(); } + + void notifyBassStateSetupFailed(BluetoothDevice sink) { + sEventLogger.logd(TAG, "notifyBassStateSetupFailed: sink: " + sink); + obtainMessage(MSG_BASS_STATE_SETUP_FAILED, sink).sendToTarget(); + } } @Override diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java index efdc0dcae7..db1ebce250 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java @@ -47,6 +47,7 @@ import android.bluetooth.le.PeriodicAdvertisingCallback; import android.bluetooth.le.PeriodicAdvertisingReport; import android.bluetooth.le.ScanRecord; import android.bluetooth.le.ScanResult; +import android.content.AttributionSource; import android.content.Intent; import android.os.Binder; import android.os.Looper; @@ -155,6 +156,7 @@ class BassClientStateMachine extends StateMachine { @VisibleForTesting BluetoothGattCharacteristic mBroadcastScanControlPoint; private final Map<Integer, Boolean> mFirstTimeBisDiscoveryMap; private int mPASyncRetryCounter = 0; + private boolean mBassStateReady = false; @VisibleForTesting int mNumOfBroadcastReceiverStates = 0; int mNumOfReadyBroadcastReceiverStates = 0; @VisibleForTesting int mPendingOperation = -1; @@ -939,15 +941,15 @@ class BassClientStateMachine extends StateMachine { // Check Bis state for (int i = 0; i < recvState.getNumSubgroups(); i++) { Long bisState = recvState.getBisSyncState().get(i); - if (bisState != BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG - && bisState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS) { + if (bisState != BassConstants.BCAST_RCVR_STATE_BIS_SYNC_FAILED_SYNC_TO_BIG + && bisState != BassConstants.BCAST_RCVR_STATE_BIS_SYNC_NOT_SYNC_TO_BIS) { // Any bis synced, update status and break syncStats.updateBisSyncedTime(SystemClock.elapsedRealtime()); syncStats.updateSyncStatus( BluetoothStatsLog .BROADCAST_AUDIO_SYNC_REPORTED__SYNC_STATUS__SYNC_STATUS_AUDIO_SYNC_SUCCESS); break; - } else if (bisState == BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG) { + } else if (bisState == BassConstants.BCAST_RCVR_STATE_BIS_SYNC_FAILED_SYNC_TO_BIG) { logBroadcastSyncStatsWithStatus( broadcastId, BluetoothStatsLog @@ -1177,6 +1179,7 @@ class BassClientStateMachine extends StateMachine { if (leaudioBroadcastResyncHelper()) { // Notify service BASS state ready for operations mService.getCallbacks().notifyBassStateReady(mDevice); + mBassStateReady = true; } } else { log("Updated receiver state: " + recvState); @@ -1533,6 +1536,7 @@ class BassClientStateMachine extends StateMachine { + status + "mBluetoothGatt" + mBluetoothGatt); + mService.getCallbacks().notifyBassStateSetupFailed(mDevice); } } else { log("remote initiated callback"); @@ -1560,6 +1564,7 @@ class BassClientStateMachine extends StateMachine { if (mNumOfReadyBroadcastReceiverStates == mNumOfBroadcastReceiverStates) { // Notify service BASS state ready for operations mService.getCallbacks().notifyBassStateReady(mDevice); + mBassStateReady = true; } } else { processBroadcastReceiverStateObsolete( @@ -1604,6 +1609,9 @@ class BassClientStateMachine extends StateMachine { if (status == BluetoothGatt.GATT_SUCCESS) { Log.d(TAG, "mtu: " + mtu); mMaxSingleAttributeWriteValueLen = mtu - ATT_WRITE_CMD_HDR_LEN; + } else { + Log.w(TAG, "onMtuChanged failed: " + status); + mService.getCallbacks().notifyBassStateSetupFailed(mDevice); } } @@ -1797,6 +1805,10 @@ class BassClientStateMachine extends StateMachine { mGattCallback = new GattCallback(); } + mDevice.setAttributionSource( + (new AttributionSource.Builder(AttributionSource.myAttributionSource())) + .setAttributionTag("BassClient") + .build()); BluetoothGatt gatt = mDevice.connectGatt( mService, @@ -1875,6 +1887,7 @@ class BassClientStateMachine extends StateMachine { } mPendingOperation = -1; mPendingMetadata = null; + mBassStateReady = false; mCurrentMetadata.clear(); mPendingRemove.clear(); } @@ -2049,15 +2062,16 @@ class BassClientStateMachine extends StateMachine { } } - private static int getBisSyncFromChannelPreference(List<BluetoothLeBroadcastChannel> channels) { - int bisSync = 0; + private static long getBisSyncFromChannelPreference( + List<BluetoothLeBroadcastChannel> channels) { + long bisSync = 0L; for (BluetoothLeBroadcastChannel channel : channels) { if (channel.isSelected()) { if (channel.getChannelIndex() == 0) { Log.e(TAG, "getBisSyncFromChannelPreference: invalid channel index=0"); continue; } - bisSync |= 1 << (channel.getChannelIndex() - 1); + bisSync |= 1L << (channel.getChannelIndex() - 1); } } @@ -2106,14 +2120,14 @@ class BassClientStateMachine extends StateMachine { for (BluetoothLeBroadcastSubgroup subGroup : subGroups) { // BIS_Sync - int bisSync = getBisSyncFromChannelPreference(subGroup.getChannels()); - if (bisSync == 0) { - bisSync = 0xFFFFFFFF; + long bisSync = getBisSyncFromChannelPreference(subGroup.getChannels()); + if (bisSync == BassConstants.BIS_SYNC_DO_NOT_SYNC_TO_BIS) { + bisSync = BassConstants.BIS_SYNC_NO_PREFERENCE; } - stream.write(bisSync & 0x00000000000000FF); - stream.write((bisSync & 0x000000000000FF00) >>> 8); - stream.write((bisSync & 0x0000000000FF0000) >>> 16); - stream.write((bisSync & 0x00000000FF000000) >>> 24); + stream.write((byte) (bisSync & 0x00000000000000FFL)); + stream.write((byte) ((bisSync & 0x000000000000FF00L) >>> 8)); + stream.write((byte) ((bisSync & 0x0000000000FF0000L) >>> 16)); + stream.write((byte) ((bisSync & 0x00000000FF000000L) >>> 24)); // Metadata_Length BluetoothLeAudioContentMetadata metadata = subGroup.getContentMetadata(); @@ -2162,28 +2176,41 @@ class BassClientStateMachine extends StateMachine { res[offset++] = (byte) numSubGroups; for (int i = 0; i < numSubGroups; i++) { - int bisIndexValue = 0xFFFFFFFF; + long bisIndexValue = BassConstants.BIS_SYNC_NO_PREFERENCE; + long currentBisIndexValue = BassConstants.BIS_SYNC_NO_PREFERENCE; + if (i < existingState.getBisSyncState().size()) { + currentBisIndexValue = existingState.getBisSyncState().get(i); + } + if (paSync == BassConstants.PA_SYNC_DO_NOT_SYNC) { - bisIndexValue = 0; - } else if (metaData != null - && (paSync == BassConstants.PA_SYNC_PAST_AVAILABLE - || paSync == BassConstants.PA_SYNC_PAST_NOT_AVAILABLE)) { + bisIndexValue = BassConstants.BIS_SYNC_DO_NOT_SYNC_TO_BIS; + } else if (metaData != null) { bisIndexValue = getBisSyncFromChannelPreference( metaData.getSubgroups().get(i).getChannels()); - // Let sink decide to which BIS sync if there is no channel preference - if (bisIndexValue == 0) { - bisIndexValue = 0xFFFFFFFF; + // If updating metadata with paSync INVALID_PA_SYNC_VALUE + // Use bisIndexValue parsed from metadata channels + if (paSync == BassConstants.PA_SYNC_PAST_AVAILABLE + || paSync == BassConstants.PA_SYNC_PAST_NOT_AVAILABLE) { + // Let sink decide to which BIS sync if there is no channel preference + if (bisIndexValue == BassConstants.BIS_SYNC_DO_NOT_SYNC_TO_BIS) { + bisIndexValue = BassConstants.BIS_SYNC_NO_PREFERENCE; + } } - } else if (i < existingState.getBisSyncState().size()) { - bisIndexValue = existingState.getBisSyncState().get(i).intValue(); + } else { + // Keep using BIS index from remote receive state + bisIndexValue = currentBisIndexValue; } - log("UPDATE_BCAST_SOURCE: bisIndexValue : " + bisIndexValue); + log( + "UPDATE_BCAST_SOURCE: bisIndexValue from: " + + currentBisIndexValue + + " to: " + + bisIndexValue); // BIS_Sync - res[offset++] = (byte) (bisIndexValue & 0x00000000000000FF); - res[offset++] = (byte) ((bisIndexValue & 0x000000000000FF00) >>> 8); - res[offset++] = (byte) ((bisIndexValue & 0x0000000000FF0000) >>> 16); - res[offset++] = (byte) ((bisIndexValue & 0x00000000FF000000) >>> 24); + res[offset++] = (byte) (bisIndexValue & 0x00000000000000FFL); + res[offset++] = (byte) ((bisIndexValue & 0x000000000000FF00L) >>> 8); + res[offset++] = (byte) ((bisIndexValue & 0x0000000000FF0000L) >>> 16); + res[offset++] = (byte) ((bisIndexValue & 0x00000000FF000000L) >>> 24); // Metadata_Length; On Modify source, don't update any Metadata res[offset++] = 0; } @@ -2863,6 +2890,10 @@ class BassClientStateMachine extends StateMachine { return mNumOfBroadcastReceiverStates; } + boolean isBassStateReady() { + return mBassStateReady; + } + BluetoothLeBroadcastMetadata getCurrentBroadcastMetadata(Integer sourceId) { return mCurrentMetadata.getOrDefault(sourceId, null); } diff --git a/android/app/src/com/android/bluetooth/bass_client/BassConstants.java b/android/app/src/com/android/bluetooth/bass_client/BassConstants.java index 981f4b6287..5efd2d4185 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassConstants.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassConstants.java @@ -62,6 +62,9 @@ public class BassConstants { public static final int BCAST_RCVR_STATE_BADCODE_START_IDX = 14; public static final int BCAST_RCVR_STATE_BADCODE_SIZE = 16; public static final int BCAST_RCVR_STATE_BIS_SYNC_SIZE = 4; + // BIS_Sync State value + public static final long BCAST_RCVR_STATE_BIS_SYNC_NOT_SYNC_TO_BIS = 0x00000000L; + public static final long BCAST_RCVR_STATE_BIS_SYNC_FAILED_SYNC_TO_BIG = 0xFFFFFFFFL; // 30 secs time out for all gatt writes public static final int GATT_TXN_TIMEOUT_MS = 30000; public static final int SOURCE_OPERATION_TIMEOUT_MS = 3000; @@ -75,11 +78,11 @@ public class BassConstants { public static final int BCAST_NAME_AD_TYPE = 0x30; public static final int BCAST_NAME_LEN_MIN = 4; public static final int BCAST_NAME_LEN_MAX = 32; - // PA_Sync parameter value + // PA_Sync parameter value in BASS operations public static final int PA_SYNC_DO_NOT_SYNC = 0x00; public static final int PA_SYNC_PAST_AVAILABLE = 0x01; public static final int PA_SYNC_PAST_NOT_AVAILABLE = 0x02; - // BIS_Sync parameter value - public static final long BIS_SYNC_NOT_SYNC_TO_BIS = 0x00000000L; - public static final long BIS_SYNC_FAILED_SYNC_TO_BIG = 0xFFFFFFFFL; + // BIS_Sync parameter value in BASS operations + public static final long BIS_SYNC_DO_NOT_SYNC_TO_BIS = 0x00000000L; + public static final long BIS_SYNC_NO_PREFERENCE = 0xFFFFFFFFL; } diff --git a/android/app/src/com/android/bluetooth/btservice/AbstractionLayer.java b/android/app/src/com/android/bluetooth/btservice/AbstractionLayer.java index 981b2db0c7..9ebe10d181 100644 --- a/android/app/src/com/android/bluetooth/btservice/AbstractionLayer.java +++ b/android/app/src/com/android/bluetooth/btservice/AbstractionLayer.java @@ -50,6 +50,7 @@ public final class AbstractionLayer { static final int BT_PROPERTY_REMOTE_ASHA_TRUNCATED_HISYNCID = 0X16; static final int BT_PROPERTY_REMOTE_MODEL_NUM = 0x17; static final int BT_PROPERTY_LPP_OFFLOAD_FEATURES = 0x1B; + static final int BT_PROPERTY_UUIDS_LE = 0x1C; public static final int BT_DEVICE_TYPE_BREDR = 0x01; public static final int BT_DEVICE_TYPE_BLE = 0x02; diff --git a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java index 6fb1169a9e..6a7c4fd27c 100644 --- a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +++ b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java @@ -19,7 +19,6 @@ package com.android.bluetooth.btservice; import android.annotation.NonNull; import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothLeAudio; @@ -361,7 +360,7 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac if (mDbManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP) != BluetoothProfile.CONNECTION_POLICY_ALLOWED || mAudioManager.getMode() != AudioManager.MODE_NORMAL) { - if (isWatch(device)) { + if (Utils.isWatch(mAdapterService, device)) { Log.i(TAG, "Do not set hfp active for watch device " + device); return; } @@ -867,9 +866,6 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { Log.d(TAG, "onAudioDevicesRemoved"); - if (!Flags.admFallbackWhenWiredAudioDisconnected()) { - return; - } if (!Arrays.stream(removedDevices) .anyMatch(AudioManagerAudioDeviceCallback::isWiredAudioHeadset)) { return; @@ -1236,15 +1232,7 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac if (Objects.equals(a2dpFallbackDevice, device)) { Log.d(TAG, "Found an A2DP fallback device: " + device); setA2dpActiveDevice(device); - if (Flags.admAlwaysFallbackToAvailableDevice()) { - setHfpActiveDevice(headsetFallbackDevice); - } else { - if (Objects.equals(headsetFallbackDevice, device)) { - setHfpActiveDevice(device); - } else { - setHfpActiveDevice(null); - } - } + setHfpActiveDevice(headsetFallbackDevice); /* If dual mode is enabled, LEA will be made active once all supported classic audio profiles are made active for the device. */ if (!Utils.isDualModeAudioEnabled()) { @@ -1275,15 +1263,7 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac if (Objects.equals(headsetFallbackDevice, device)) { Log.d(TAG, "Found a HFP fallback device: " + device); setHfpActiveDevice(device); - if (Flags.admAlwaysFallbackToAvailableDevice()) { - setA2dpActiveDevice(a2dpFallbackDevice); - } else { - if (Objects.equals(a2dpFallbackDevice, device)) { - setA2dpActiveDevice(a2dpFallbackDevice); - } else { - setA2dpActiveDevice(null, true); - } - } + setA2dpActiveDevice(a2dpFallbackDevice); if (!Utils.isDualModeAudioEnabled()) { setLeAudioActiveDevice(null, true); } @@ -1367,32 +1347,6 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac } /** - * Checks CoD and metadata to determine if the device is a watch - * - * @param device the remote device - * @return {@code true} if it's a watch, {@code false} otherwise - */ - private boolean isWatch(BluetoothDevice device) { - // Check CoD - BluetoothClass deviceClass = new BluetoothClass(mAdapterService.getRemoteClass(device)); - if (deviceClass.getDeviceClass() == BluetoothClass.Device.WEARABLE_WRIST_WATCH) { - return true; - } - - // Check metadata - byte[] deviceType = mDbManager.getCustomMeta(device, BluetoothDevice.METADATA_DEVICE_TYPE); - if (deviceType == null) { - return false; - } - String deviceTypeStr = new String(deviceType); - if (deviceTypeStr.equals(BluetoothDevice.DEVICE_TYPE_WATCH)) { - return true; - } - - return false; - } - - /** * Checks if le audio broadcasting is ON * * @return {@code true} if is broadcasting audio, {@code false} otherwise diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterNativeInterface.java b/android/app/src/com/android/bluetooth/btservice/AdapterNativeInterface.java index 64b5a0dfae..244e132a66 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterNativeInterface.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterNativeInterface.java @@ -316,6 +316,14 @@ public class AdapterNativeInterface { return disconnectAllAclsNative(); } + boolean disconnectAllAcls(BluetoothDevice device) { + return disconnectAcl(device, BluetoothDevice.TRANSPORT_AUTO); + } + + boolean disconnectAcl(BluetoothDevice device, int transport) { + return disconnectAclNative(Utils.getBytesFromAddress(device.getAddress()), transport); + } + boolean allowWakeByHid() { return allowWakeByHidNative(); } @@ -463,6 +471,8 @@ public class AdapterNativeInterface { private native boolean disconnectAllAclsNative(); + private native boolean disconnectAclNative(byte[] address, int transport); + private native boolean allowWakeByHidNative(); private native boolean restoreFilterAcceptListNative(); diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java index 081c0b57c9..c7ce8669bc 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java @@ -59,6 +59,7 @@ import android.bluetooth.BluetoothAdapter.ActiveDeviceUse; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice.BluetoothAddress; import android.bluetooth.BluetoothFrameworkInitializer; +import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothMap; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProtoEnums; @@ -116,6 +117,7 @@ import android.sysprop.BluetoothProperties; import android.text.TextUtils; import android.util.Base64; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import com.android.bluetooth.BluetoothMetricsProto; @@ -290,6 +292,11 @@ public class AdapterService extends Service { private long mEnergyUsedTotalVoltAmpSecMicro; private HashSet<String> mLeAudioAllowDevices = new HashSet<>(); + /* List of pairs of gatt clients which controls AutoActiveMode on the device.*/ + @VisibleForTesting + final List<Pair<Integer, BluetoothDevice>> mLeGattClientsControllingAutoActiveMode = + new ArrayList<>(); + private BluetoothAdapter mAdapter; @VisibleForTesting AdapterProperties mAdapterProperties; private AdapterState mAdapterStateMachine; @@ -4179,7 +4186,7 @@ public class AdapterService extends Service { Set<Integer> eventCodesSet = Arrays.stream(eventCodes).boxed().collect(Collectors.toSet()); if (eventCodesSet.stream() - .anyMatch((n) -> (n < 0) || (n >= 0x50 && n < 0x60) || (n > 0xff))) { + .anyMatch((n) -> (n < 0) || (n >= 0x52 && n < 0x60) || (n > 0xff))) { throw new IllegalArgumentException("invalid vendor-specific event code"); } @@ -4410,6 +4417,18 @@ public class AdapterService extends Service { service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); return service.isRfcommSocketOffloadSupported(); } + + @Override + public IBinder getBluetoothAdvertise() { + AdapterService service = getService(); + return service == null ? null : service.getBluetoothAdvertise(); + } + + @Override + public IBinder getDistanceMeasurement() { + AdapterService service = getService(); + return service == null ? null : service.getDistanceMeasurement(); + } } /** @@ -5141,6 +5160,192 @@ public class AdapterService extends Service { return getConnectionState(device) != BluetoothDevice.CONNECTION_STATE_DISCONNECTED; } + private void addGattClientToControlAutoActiveMode(int clientIf, BluetoothDevice device) { + if (!Flags.allowGattConnectFromTheAppsWithoutMakingLeaudioDeviceActive()) { + Log.i( + TAG, + "flag: allowGattConnectFromTheAppsWithoutMakingLeaudioDeviceActive is not" + + " enabled"); + return; + } + + /* When GATT client is connecting to LeAudio device, stack should not assume that + * LeAudio device should be automatically connected to Audio Framework. + * e.g. given LeAudio device might be busy with audio streaming from another device. + * LeAudio shall be automatically connected to Audio Framework when + * 1. Remote device expects that - Targeted Announcements are used + * 2. User is connecting device from Settings application. + * + * Above conditions are tracked by LeAudioService. In here, there is need to notify + * LeAudioService that connection is made for GATT purposes, so LeAudioService can + * disable AutoActiveMode and make sure to not make device Active just after connection + * is created. + * + * Note: AutoActiveMode is by default set to true and it means that LeAudio device is ready + * to streaming just after connection is created. That implies that device will be connected + * to Audio Framework (is made Active) when connection is created. + */ + + int groupId = mLeAudioService.getGroupId(device); + if (groupId == BluetoothLeAudio.GROUP_ID_INVALID) { + /* If this is not a LeAudio device, there is nothing to do here. */ + return; + } + + if (mLeAudioService.getConnectionPolicy(device) + != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + Log.d( + TAG, + "addGattClientToControlAutoActiveMode: " + + device + + " LeAudio connection policy is not allowed"); + return; + } + + Log.i( + TAG, + "addGattClientToControlAutoActiveMode: clientIf: " + + clientIf + + ", " + + device + + ", groupId: " + + groupId); + + synchronized (mLeGattClientsControllingAutoActiveMode) { + Pair newPair = new Pair<>(clientIf, device); + if (mLeGattClientsControllingAutoActiveMode.contains(newPair)) { + return; + } + + for (Pair<Integer, BluetoothDevice> pair : mLeGattClientsControllingAutoActiveMode) { + if (pair.second.equals(device) + || groupId == mLeAudioService.getGroupId(pair.second)) { + Log.i(TAG, "addGattClientToControlAutoActiveMode: adding new client"); + mLeGattClientsControllingAutoActiveMode.add(newPair); + return; + } + } + + if (mLeAudioService.setAutoActiveModeState(mLeAudioService.getGroupId(device), false)) { + Log.i( + TAG, + "addGattClientToControlAutoActiveMode: adding new client and notifying" + + " leAudioService"); + mLeGattClientsControllingAutoActiveMode.add(newPair); + } + } + } + + /** + * When this is called, AdapterService is aware of user doing GATT connection over LE. Adapter + * service will use this information to manage internal GATT services if needed. For now, + * AdapterService is using this information to control Auto Active Mode for LeAudio devices. + * + * @param clientIf clientIf ClientIf which was doing GATT connection attempt + * @param device device Remote device to connect + */ + public void notifyDirectLeGattClientConnect(int clientIf, BluetoothDevice device) { + if (mLeAudioService != null) { + addGattClientToControlAutoActiveMode(clientIf, device); + } + } + + private void removeGattClientFromControlAutoActiveMode(int clientIf, BluetoothDevice device) { + if (mLeGattClientsControllingAutoActiveMode.isEmpty()) { + return; + } + + int groupId = mLeAudioService.getGroupId(device); + if (groupId == BluetoothLeAudio.GROUP_ID_INVALID) { + /* If this is not a LeAudio device, there is nothing to do here. */ + return; + } + + /* Remember if auto active mode is still disabled. + * If it is disabled, it means, that either User or remote device did not make an + * action to make LeAudio device Active. + * That means, AdapterService should disconnect ACL when all the clients are disconnected + * from the group to which the device belongs. + */ + boolean isAutoActiveModeDisabled = !mLeAudioService.isAutoActiveModeEnabled(groupId); + + synchronized (mLeGattClientsControllingAutoActiveMode) { + Log.d( + TAG, + "removeGattClientFromControlAutoActiveMode: removing clientIf:" + + clientIf + + ", " + + device + + ", groupId: " + + groupId); + + mLeGattClientsControllingAutoActiveMode.remove(new Pair<>(clientIf, device)); + + if (!mLeGattClientsControllingAutoActiveMode.isEmpty()) { + for (Pair<Integer, BluetoothDevice> pair : + mLeGattClientsControllingAutoActiveMode) { + if (pair.second.equals(device) + || groupId == mLeAudioService.getGroupId(pair.second)) { + Log.d( + TAG, + "removeGattClientFromControlAutoActiveMode:" + + device + + " or groupId: " + + groupId + + " is still in use by clientif: " + + pair.first); + return; + } + } + } + + /* Back auto active mode to default. */ + mLeAudioService.setAutoActiveModeState(groupId, true); + } + + int leConnectedState = + BluetoothDevice.CONNECTION_STATE_ENCRYPTED_LE + | BluetoothDevice.CONNECTION_STATE_CONNECTED; + + /* If auto active mode was disabled for the given group and is still connected + * make sure to disconnected all the devices from the group + */ + if (isAutoActiveModeDisabled && ((getConnectionState(device) & leConnectedState) != 0)) { + for (BluetoothDevice dev : mLeAudioService.getGroupDevices(groupId)) { + /* Need to disconnect all the devices from the group as those might be connected + * as well especially those which migh keep the connection + */ + if ((getConnectionState(dev) & leConnectedState) != 0) { + mNativeInterface.disconnectAcl(dev, BluetoothDevice.TRANSPORT_LE); + } + } + } + } + + /** + * Notify AdapterService about failed GATT connection attempt. + * + * @param clientIf ClientIf which was doing GATT connection attempt + * @param device Remote device to which connection attpemt failed + */ + public void notifyGattClientConnectFailed(int clientIf, BluetoothDevice device) { + if (mLeAudioService != null) { + removeGattClientFromControlAutoActiveMode(clientIf, device); + } + } + + /** + * Notify AdapterService about GATT connection being disconnecting or disconnected. + * + * @param clientIf ClientIf which is disconnecting or is already disconnected + * @param device Remote device which is disconnecting or is disconnected + */ + public void notifyGattClientDisconnect(int clientIf, BluetoothDevice device) { + if (mLeAudioService != null) { + removeGattClientFromControlAutoActiveMode(clientIf, device); + } + } + public int getConnectionState(BluetoothDevice device) { final String address = device.getAddress(); if (Flags.apiGetConnectionStateUsingIdentityAddress()) { @@ -6147,6 +6352,16 @@ public class AdapterService extends Service { } } + @Nullable + IBinder getBluetoothAdvertise() { + return mGattService == null ? null : mGattService.getBluetoothAdvertise(); + } + + @Nullable + IBinder getDistanceMeasurement() { + return mGattService == null ? null : mGattService.getDistanceMeasurement(); + } + @RequiresPermission(BLUETOOTH_CONNECT) void unregAllGattClient(AttributionSource source) { if (mGattService != null) { @@ -6226,10 +6441,9 @@ public class AdapterService extends Service { Log.w(TAG, "GATT Service is not running!"); return; } - if (Flags.scanManagerRefactor()) { - mScanController.notifyProfileConnectionStateChange(profile, fromState, toState); - } else { - mGattService.notifyProfileConnectionStateChange(profile, fromState, toState); + ScanController controller = getBluetoothScanController(); + if (controller != null) { + controller.notifyProfileConnectionStateChange(profile, fromState, toState); } } @@ -6556,6 +6770,12 @@ public class AdapterService extends Service { } writer.println(); + writer.println("LE Gatt clients controlling AutoActiveMode:"); + for (Pair<Integer, BluetoothDevice> pair : mLeGattClientsControllingAutoActiveMode) { + writer.println(" clientIf:" + pair.first + " " + pair.second); + } + writer.println(); + mAdapterStateMachine.dump(fd, writer, args); StringBuilder sb = new StringBuilder(); diff --git a/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java b/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java index 475da0dba1..47a5d9991f 100644 --- a/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java +++ b/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java @@ -274,6 +274,27 @@ public class PhonePolicy implements AdapterService.BluetoothStateCallback { && hap.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN; } + private boolean shouldBlockBroadcastForHapDevice(BluetoothDevice device, ParcelUuid[] uuids) { + if (!Flags.leaudioDisableBroadcastForHapDevice()) { + Log.i(TAG, "disableBroadcastForHapDevice: Flag is disabled"); + return false; + } + + HapClientService hap = mFactory.getHapClientService(); + if (hap == null) { + Log.e(TAG, "shouldBlockBroadcastForHapDevice: No HapClientService"); + return false; + } + + if (!SystemProperties.getBoolean(SYSPROP_HAP_ENABLED, true)) { + Log.i(TAG, "shouldBlockBroadcastForHapDevice: SystemProperty is overridden to false"); + return false; + } + + return Utils.arrayContains(uuids, BluetoothUuid.HAS) + && hap.getConnectionPolicy(device) == CONNECTION_POLICY_ALLOWED; + } + // Policy implementation, all functions MUST be private private void processInitProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids) { String log = "processInitProfilePriorities(" + device + "): "; @@ -518,7 +539,7 @@ public class PhonePolicy implements AdapterService.BluetoothStateCallback { if ((bcService != null) && Utils.arrayContains(uuids, BluetoothUuid.BASS) && (bcService.getConnectionPolicy(device) == CONNECTION_POLICY_UNKNOWN)) { - if (isLeAudioProfileAllowed) { + if (isLeAudioProfileAllowed && !shouldBlockBroadcastForHapDevice(device, uuids)) { Log.d(TAG, log + "Setting BASS priority"); if (mAutoConnectProfilesSupported) { bcService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); @@ -531,7 +552,7 @@ public class PhonePolicy implements AdapterService.BluetoothStateCallback { CONNECTION_POLICY_ALLOWED); } } else { - Log.d(TAG, log + "LE_AUDIO is not allowed: Clear BASS priority"); + Log.d(TAG, log + "LE_AUDIO Broadcast is not allowed: Clear BASS priority"); mAdapterService .getDatabase() .setProfileConnectionPolicy( diff --git a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java index f0bad17e64..f297d905ac 100644 --- a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java +++ b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java @@ -345,7 +345,8 @@ public class RemoteDevices { private String mModelName; @VisibleForTesting int mBondState; @VisibleForTesting int mDeviceType; - @VisibleForTesting ParcelUuid[] mUuids; + @VisibleForTesting ParcelUuid[] mUuidsBrEdr; + @VisibleForTesting ParcelUuid[] mUuidsLe; private BluetoothSinkAudioPolicy mAudioPolicy; DeviceProperties() { @@ -506,20 +507,71 @@ public class RemoteDevices { } /** - * @return the mUuids + * @return the UUIDs on LE and Classic transport */ ParcelUuid[] getUuids() { synchronized (mObject) { - return mUuids; + /* When we bond dual mode device, and discover LE and Classic services, stack would + * return LE and Classic UUIDs separately, but Java apps expect them merged. + */ + int combinedUuidsLength = + (mUuidsBrEdr != null ? mUuidsBrEdr.length : 0) + + (mUuidsLe != null ? mUuidsLe.length : 0); + if (!Flags.separateServiceStorage() || combinedUuidsLength == 0) { + return mUuidsBrEdr; + } + + java.util.LinkedHashSet<ParcelUuid> result = + new java.util.LinkedHashSet<ParcelUuid>(); + if (mUuidsBrEdr != null) { + for (ParcelUuid uuid : mUuidsBrEdr) { + result.add(uuid); + } + } + + if (mUuidsLe != null) { + for (ParcelUuid uuid : mUuidsLe) { + result.add(uuid); + } + } + + return result.toArray(new ParcelUuid[combinedUuidsLength]); + } + } + + /** + * @return just classic transport UUIDS + */ + ParcelUuid[] getUuidsBrEdr() { + synchronized (mObject) { + return mUuidsBrEdr; + } + } + + /** + * @param uuids the mUuidsBrEdr to set + */ + void setUuidsBrEdr(ParcelUuid[] uuids) { + synchronized (mObject) { + this.mUuidsBrEdr = uuids; + } + } + + /** + * @return the mUuidsLe + */ + ParcelUuid[] getUuidsLe() { + synchronized (mObject) { + return mUuidsLe; } } /** - * @param uuids the mUuids to set + * @param uuids the mUuidsLe to set */ - void setUuids(ParcelUuid[] uuids) { + void setUuidsLe(ParcelUuid[] uuids) { synchronized (mObject) { - this.mUuids = uuids; + this.mUuidsLe = uuids; } } @@ -636,7 +688,8 @@ public class RemoteDevices { cachedBluetoothDevice issued a connect using the local cached copy of uuids, without waiting for the ACTION_UUID intent. This was resulting in multiple calls to connect().*/ - mUuids = null; + mUuidsBrEdr = null; + mUuidsLe = null; mAlias = null; } } @@ -988,147 +1041,168 @@ public class RemoteDevices { return; } + boolean uuids_updated = false; + for (int j = 0; j < types.length; j++) { type = types[j]; val = values[j]; - if (val.length > 0) { - synchronized (mObject) { - debugLog("Update property, device=" + bdDevice + ", type: " + type); - switch (type) { - case AbstractionLayer.BT_PROPERTY_BDNAME: - final String newName = new String(val); - if (newName.equals(deviceProperties.getName())) { - debugLog("Skip name update for " + bdDevice); - break; - } - deviceProperties.setName(newName); - List<String> wordBreakdownList = - MetricsLogger.getInstance().getWordBreakdownList(newName); - if (SdkLevel.isAtLeastU()) { - MetricsLogger.getInstance() - .uploadRestrictedBluetothDeviceName(wordBreakdownList); - } - intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); - intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProperties.getName()); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mAdapterService.sendBroadcast( - intent, - BLUETOOTH_CONNECT, - Utils.getTempBroadcastOptions().toBundle()); - debugLog("Remote device name is: " + deviceProperties.getName()); - break; - case AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME: - deviceProperties.setAlias(bdDevice, new String(val)); - debugLog("Remote device alias is: " + deviceProperties.getAlias()); - break; - case AbstractionLayer.BT_PROPERTY_BDADDR: - deviceProperties.setAddress(val); - debugLog( - "Remote Address is:" - + Utils.getRedactedAddressStringFromByte(val)); + if (val.length == 0) { + continue; + } + + synchronized (mObject) { + debugLog("Update property, device=" + bdDevice + ", type: " + type); + switch (type) { + case AbstractionLayer.BT_PROPERTY_BDNAME: + final String newName = new String(val); + if (newName.equals(deviceProperties.getName())) { + debugLog("Skip name update for " + bdDevice); break; - case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE: - final int newBluetoothClass = Utils.byteArrayToInt(val); - if (newBluetoothClass == deviceProperties.getBluetoothClass()) { - debugLog( - "Skip class update, device=" - + bdDevice - + ", cod=0x" - + Integer.toHexString(newBluetoothClass)); - break; - } - deviceProperties.setBluetoothClass(newBluetoothClass); - intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); - intent.putExtra( - BluetoothDevice.EXTRA_CLASS, - new BluetoothClass(deviceProperties.getBluetoothClass())); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mAdapterService.sendBroadcast( - intent, - BLUETOOTH_CONNECT, - Utils.getTempBroadcastOptions().toBundle()); + } + deviceProperties.setName(newName); + List<String> wordBreakdownList = + MetricsLogger.getInstance().getWordBreakdownList(newName); + if (SdkLevel.isAtLeastU()) { + MetricsLogger.getInstance() + .uploadRestrictedBluetothDeviceName(wordBreakdownList); + } + intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); + intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProperties.getName()); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mAdapterService.sendBroadcast( + intent, + BLUETOOTH_CONNECT, + Utils.getTempBroadcastOptions().toBundle()); + debugLog("Remote device name is: " + deviceProperties.getName()); + break; + case AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME: + deviceProperties.setAlias(bdDevice, new String(val)); + debugLog("Remote device alias is: " + deviceProperties.getAlias()); + break; + case AbstractionLayer.BT_PROPERTY_BDADDR: + deviceProperties.setAddress(val); + debugLog( + "Remote Address is:" + Utils.getRedactedAddressStringFromByte(val)); + break; + case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE: + final int newBluetoothClass = Utils.byteArrayToInt(val); + if (newBluetoothClass == deviceProperties.getBluetoothClass()) { debugLog( - "Remote class update, device=" + "Skip class update, device=" + bdDevice + ", cod=0x" + Integer.toHexString(newBluetoothClass)); break; - case AbstractionLayer.BT_PROPERTY_UUIDS: + } + deviceProperties.setBluetoothClass(newBluetoothClass); + intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); + intent.putExtra( + BluetoothDevice.EXTRA_CLASS, + new BluetoothClass(deviceProperties.getBluetoothClass())); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mAdapterService.sendBroadcast( + intent, + BLUETOOTH_CONNECT, + Utils.getTempBroadcastOptions().toBundle()); + debugLog( + "Remote class update, device=" + + bdDevice + + ", cod=0x" + + Integer.toHexString(newBluetoothClass)); + break; + case AbstractionLayer.BT_PROPERTY_UUIDS: + case AbstractionLayer.BT_PROPERTY_UUIDS_LE: + if (type == AbstractionLayer.BT_PROPERTY_UUIDS) { final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val); - if (areUuidsEqual(newUuids, deviceProperties.getUuids())) { + if (areUuidsEqual(newUuids, deviceProperties.getUuidsBrEdr())) { // SDP Skip adding UUIDs to property cache if equal debugLog("Skip uuids update for " + bdDevice.getAddress()); MetricsLogger.getInstance() .cacheCount(BluetoothProtoEnums.SDP_UUIDS_EQUAL_SKIP, 1); break; } - deviceProperties.setUuids(newUuids); - if (mAdapterService.getState() == BluetoothAdapter.STATE_ON) { - // SDP Adding UUIDs to property cache and sending intent - MetricsLogger.getInstance() - .cacheCount( - BluetoothProtoEnums.SDP_ADD_UUID_WITH_INTENT, 1); - mAdapterService.deviceUuidUpdated(bdDevice); - sendUuidIntent(bdDevice, deviceProperties, true); - } else if (mAdapterService.getState() - == BluetoothAdapter.STATE_BLE_ON) { - // SDP Adding UUIDs to property cache but with no intent - MetricsLogger.getInstance() - .cacheCount( - BluetoothProtoEnums.SDP_ADD_UUID_WITH_NO_INTENT, 1); - mAdapterService.deviceUuidUpdated(bdDevice); - } else { - // SDP Silently dropping UUIDs and with no intent + deviceProperties.setUuidsBrEdr(newUuids); + } else if (type == AbstractionLayer.BT_PROPERTY_UUIDS_LE) { + final ParcelUuid[] newUuidsLe = Utils.byteArrayToUuid(val); + if (areUuidsEqual(newUuidsLe, deviceProperties.getUuidsLe())) { + // SDP Skip adding UUIDs to property cache if equal + debugLog("Skip LE uuids update for " + bdDevice.getAddress()); MetricsLogger.getInstance() - .cacheCount(BluetoothProtoEnums.SDP_DROP_UUID, 1); - } - break; - case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE: - if (deviceProperties.isConsolidated()) { + .cacheCount(BluetoothProtoEnums.SDP_UUIDS_EQUAL_SKIP, 1); break; } - // The device type from hal layer, defined in bluetooth.h, - // matches the type defined in BluetoothDevice.java - deviceProperties.setDeviceType(Utils.byteArrayToInt(val)); - break; - case AbstractionLayer.BT_PROPERTY_REMOTE_RSSI: - // RSSI from hal is in one byte - deviceProperties.setRssi(val[0]); - break; - case AbstractionLayer.BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER: - deviceProperties.setIsCoordinatedSetMember(val[0] != 0); - break; - case AbstractionLayer.BT_PROPERTY_REMOTE_ASHA_CAPABILITY: - deviceProperties.setAshaCapability(val[0]); - break; - case AbstractionLayer.BT_PROPERTY_REMOTE_ASHA_TRUNCATED_HISYNCID: - deviceProperties.setAshaTruncatedHiSyncId(val[0]); - break; - case AbstractionLayer.BT_PROPERTY_REMOTE_MODEL_NUM: - final String modelName = new String(val); - debugLog("Remote device model name: " + modelName); - deviceProperties.setModelName(modelName); - BluetoothStatsLog.write( - BluetoothStatsLog.BLUETOOTH_DEVICE_INFO_REPORTED, - mAdapterService.obfuscateAddress(bdDevice), - BluetoothProtoEnums.DEVICE_INFO_INTERNAL, - LOG_SOURCE_DIS, - null, - modelName, - null, - null, - mAdapterService.getMetricId(bdDevice), - bdDevice.getAddressType(), - 0, - 0, - 0); + deviceProperties.setUuidsLe(newUuidsLe); + } + uuids_updated = true; + break; + case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE: + if (deviceProperties.isConsolidated()) { break; - } + } + // The device type from hal layer, defined in bluetooth.h, + // matches the type defined in BluetoothDevice.java + deviceProperties.setDeviceType(Utils.byteArrayToInt(val)); + break; + case AbstractionLayer.BT_PROPERTY_REMOTE_RSSI: + // RSSI from hal is in one byte + deviceProperties.setRssi(val[0]); + break; + case AbstractionLayer.BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER: + deviceProperties.setIsCoordinatedSetMember(val[0] != 0); + break; + case AbstractionLayer.BT_PROPERTY_REMOTE_ASHA_CAPABILITY: + deviceProperties.setAshaCapability(val[0]); + break; + case AbstractionLayer.BT_PROPERTY_REMOTE_ASHA_TRUNCATED_HISYNCID: + deviceProperties.setAshaTruncatedHiSyncId(val[0]); + break; + case AbstractionLayer.BT_PROPERTY_REMOTE_MODEL_NUM: + final String modelName = new String(val); + debugLog("Remote device model name: " + modelName); + deviceProperties.setModelName(modelName); + BluetoothStatsLog.write( + BluetoothStatsLog.BLUETOOTH_DEVICE_INFO_REPORTED, + mAdapterService.obfuscateAddress(bdDevice), + BluetoothProtoEnums.DEVICE_INFO_INTERNAL, + LOG_SOURCE_DIS, + null, + modelName, + null, + null, + mAdapterService.getMetricId(bdDevice), + bdDevice.getAddressType(), + 0, + 0, + 0); + break; } } } + + if (!uuids_updated) { + return; + } + + /* uuids_updated == true + * We might have received LE and BREDR UUIDS separately, ensure that UUID intent is sent + * just once */ + + if (mAdapterService.getState() == BluetoothAdapter.STATE_ON) { + // SDP Adding UUIDs to property cache and sending intent + MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.SDP_ADD_UUID_WITH_INTENT, 1); + mAdapterService.deviceUuidUpdated(bdDevice); + sendUuidIntent(bdDevice, deviceProperties, true); + } else if (mAdapterService.getState() == BluetoothAdapter.STATE_BLE_ON) { + // SDP Adding UUIDs to property cache but with no intent + MetricsLogger.getInstance() + .cacheCount(BluetoothProtoEnums.SDP_ADD_UUID_WITH_NO_INTENT, 1); + mAdapterService.deviceUuidUpdated(bdDevice); + } else { + // SDP Silently dropping UUIDs and with no intent + MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.SDP_DROP_UUID, 1); + } } void deviceFoundCallback(byte[] address) { diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseBinder.kt b/android/app/src/com/android/bluetooth/gatt/AdvertiseBinder.kt new file mode 100644 index 0000000000..66415cd82f --- /dev/null +++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseBinder.kt @@ -0,0 +1,175 @@ +/* + * Copyright 2025 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.Manifest.permission.BLUETOOTH_ADVERTISE +import android.Manifest.permission.BLUETOOTH_PRIVILEGED +import android.annotation.RequiresPermission +import android.bluetooth.IBluetoothAdvertise +import android.bluetooth.le.AdvertiseData +import android.bluetooth.le.AdvertisingSetParameters +import android.bluetooth.le.IAdvertisingSetCallback +import android.bluetooth.le.PeriodicAdvertisingParameters +import android.content.AttributionSource +import android.content.Context +import com.android.bluetooth.Utils + +class AdvertiseBinder( + private val mContext: Context, + private val mAdvertiseManager: AdvertiseManager, +) : IBluetoothAdvertise.Stub() { + @Volatile private var mIsAvailable = true + + fun cleanup() { + mIsAvailable = false + } + + @RequiresPermission(BLUETOOTH_ADVERTISE) + private fun getManager(source: AttributionSource): AdvertiseManager? { + if (!Utils.checkAdvertisePermissionForDataDelivery(mContext, source, "AdvertiseManager")) { + return null + } + return if (mIsAvailable) mAdvertiseManager else null + } + + override fun startAdvertisingSet( + parameters: AdvertisingSetParameters, + advertiseData: AdvertiseData?, + scanResponse: AdvertiseData?, + periodicParameters: PeriodicAdvertisingParameters?, + periodicData: AdvertiseData?, + duration: Int, + maxExtAdvEvents: Int, + serverIf: Int, + callback: IAdvertisingSetCallback, + source: AttributionSource, + ) { + if ( + parameters.ownAddressType != AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT || + serverIf != 0 || + parameters.isDirected + ) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null) + } + + getManager(source)?.let { + it.doOnAdvertiseThread { + it.startAdvertisingSet( + parameters, + advertiseData, + scanResponse, + periodicParameters, + periodicData, + duration, + maxExtAdvEvents, + serverIf, + callback, + source, + ) + } + } + } + + override fun stopAdvertisingSet(callback: IAdvertisingSetCallback, source: AttributionSource) { + getManager(source)?.let { it.doOnAdvertiseThread { it.stopAdvertisingSet(callback) } } + } + + override fun getOwnAddress(advertiserId: Int, source: AttributionSource) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null) + getManager(source)?.let { it.doOnAdvertiseThread { it.getOwnAddress(advertiserId) } } + } + + override fun enableAdvertisingSet( + advertiserId: Int, + enable: Boolean, + duration: Int, + maxExtAdvEvents: Int, + source: AttributionSource, + ) { + getManager(source)?.let { + it.doOnAdvertiseThread { + it.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents) + } + } + } + + override fun setAdvertisingData( + advertiserId: Int, + data: AdvertiseData?, + source: AttributionSource, + ) { + getManager(source)?.let { + it.doOnAdvertiseThread { it.setAdvertisingData(advertiserId, data) } + } + } + + override fun setScanResponseData( + advertiserId: Int, + data: AdvertiseData?, + source: AttributionSource, + ) { + getManager(source)?.let { + it.doOnAdvertiseThread { it.setScanResponseData(advertiserId, data) } + } + } + + override fun setAdvertisingParameters( + advertiserId: Int, + parameters: AdvertisingSetParameters, + source: AttributionSource, + ) { + if ( + parameters.ownAddressType != AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT || + parameters.isDirected + ) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null) + } + getManager(source)?.let { + it.doOnAdvertiseThread { it.setAdvertisingParameters(advertiserId, parameters) } + } + } + + override fun setPeriodicAdvertisingParameters( + advertiserId: Int, + parameters: PeriodicAdvertisingParameters?, + source: AttributionSource, + ) { + getManager(source)?.let { + it.doOnAdvertiseThread { it.setPeriodicAdvertisingParameters(advertiserId, parameters) } + } + } + + override fun setPeriodicAdvertisingData( + advertiserId: Int, + data: AdvertiseData?, + source: AttributionSource, + ) { + getManager(source)?.let { + it.doOnAdvertiseThread { it.setPeriodicAdvertisingData(advertiserId, data) } + } + } + + override fun setPeriodicAdvertisingEnable( + advertiserId: Int, + enable: Boolean, + source: AttributionSource, + ) { + getManager(source)?.let { + it.doOnAdvertiseThread { it.setPeriodicAdvertisingEnable(advertiserId, enable) } + } + } +} diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java index f3ccb8a9c1..6ed719cedc 100644 --- a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java +++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java @@ -24,44 +24,52 @@ import android.bluetooth.le.PeriodicAdvertisingParameters; import android.content.AttributionSource; import android.os.Binder; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; -import android.os.IInterface; import android.os.Looper; import android.os.RemoteException; import android.util.Log; +import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; -import com.android.internal.annotations.GuardedBy; +import com.android.bluetooth.flags.Flags; import com.android.internal.annotations.VisibleForTesting; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; -/** - * Manages Bluetooth LE advertising operations and interacts with bluedroid stack. TODO: add tests. - */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +/** Manages Bluetooth LE advertising operations. */ public class AdvertiseManager { private static final String TAG = GattServiceConfig.TAG_PREFIX + "AdvertiseManager"; - private final GattService mService; + private static final long RUN_SYNC_WAIT_TIME_MS = 2000L; + + private final AdapterService mService; private final AdvertiseManagerNativeInterface mNativeInterface; + private final AdvertiseBinder mAdvertiseBinder; private final AdvertiserMap mAdvertiserMap; - @GuardedBy("itself") private final Map<IBinder, AdvertiserInfo> mAdvertisers = new HashMap<>(); - private Handler mHandler; - static int sTempRegistrationId = -1; + private final Handler mHandler; + private volatile boolean mIsAvailable = true; + @VisibleForTesting int mTempRegistrationId = -1; - AdvertiseManager(GattService service) { - this(service, AdvertiseManagerNativeInterface.getInstance(), new AdvertiserMap()); + AdvertiseManager(AdapterService service, Looper advertiseLooper) { + this( + service, + advertiseLooper, + AdvertiseManagerNativeInterface.getInstance(), + new AdvertiserMap()); } @VisibleForTesting AdvertiseManager( - GattService service, + AdapterService service, + Looper advertiseLooper, AdvertiseManagerNativeInterface nativeInterface, AdvertiserMap advertiserMap) { Log.d(TAG, "advertise manager created"); @@ -69,40 +77,30 @@ public class AdvertiseManager { mNativeInterface = nativeInterface; mAdvertiserMap = advertiserMap; - // Start a HandlerThread that handles advertising operations mNativeInterface.init(this); - HandlerThread thread = new HandlerThread("BluetoothAdvertiseManager"); - thread.start(); - mHandler = new Handler(thread.getLooper()); - } - - // TODO(b/327849650): We shouldn't need this, it should be safe to do in the cleanup method. But - // it would be a logic change. - void clear() { - mAdvertiserMap.clear(); + mHandler = new Handler(advertiseLooper); + mAdvertiseBinder = new AdvertiseBinder(service, this); } void cleanup() { Log.d(TAG, "cleanup()"); - mNativeInterface.cleanup(); - synchronized (mAdvertisers) { - mAdvertisers.clear(); - } - sTempRegistrationId = -1; - - if (mHandler != null) { - // Shut down the thread - mHandler.removeCallbacksAndMessages(null); - Looper looper = mHandler.getLooper(); - if (looper != null) { - looper.quit(); - } - mHandler = null; - } + mIsAvailable = false; + mHandler.removeCallbacksAndMessages(null); + forceRunSyncOnAdvertiseThread( + () -> { + mAdvertiserMap.clear(); + mAdvertiseBinder.cleanup(); + mNativeInterface.cleanup(); + mAdvertisers.clear(); + }); } void dump(StringBuilder sb) { - mAdvertiserMap.dump(sb); + forceRunSyncOnAdvertiseThread(() -> mAdvertiserMap.dump(sb)); + } + + AdvertiseBinder getBinder() { + return mAdvertiseBinder; } static class AdvertiserInfo { @@ -122,8 +120,12 @@ public class AdvertiseManager { } } + private interface CallbackWrapper { + void call() throws RemoteException; + } + IBinder toBinder(IAdvertisingSetCallback e) { - return ((IInterface) e).asBinder(); + return e.asBinder(); } class AdvertisingSetDeathRecipient implements IBinder.DeathRecipient { @@ -138,25 +140,22 @@ public class AdvertiseManager { @Override public void binderDied() { Log.d(TAG, "Binder is dead - unregistering advertising set (" + mPackageName + ")!"); - stopAdvertisingSet(callback); + doOnAdvertiseThread(() -> stopAdvertisingSet(callback)); } } - Map.Entry<IBinder, AdvertiserInfo> findAdvertiser(int advertiserId) { + private Map.Entry<IBinder, AdvertiserInfo> findAdvertiser(int advertiserId) { Map.Entry<IBinder, AdvertiserInfo> entry = null; - synchronized (mAdvertisers) { - for (Map.Entry<IBinder, AdvertiserInfo> e : mAdvertisers.entrySet()) { - if (e.getValue().id == advertiserId) { - entry = e; - break; - } + for (Map.Entry<IBinder, AdvertiserInfo> e : mAdvertisers.entrySet()) { + if (e.getValue().id == advertiserId) { + entry = e; + break; } } return entry; } - void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status) - throws Exception { + void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status) { Log.d( TAG, "onAdvertisingSetStarted() - regId=" @@ -165,6 +164,7 @@ public class AdvertiseManager { + advertiserId + ", status=" + status); + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(regId); @@ -184,27 +184,24 @@ public class AdvertiseManager { } else { IBinder binder = entry.getKey(); binder.unlinkToDeath(entry.getValue().deathRecipient, 0); - synchronized (mAdvertisers) { - mAdvertisers.remove(binder); - } + mAdvertisers.remove(binder); AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(regId); if (stats != null) { - int instanceCount; - synchronized (mAdvertisers) { - instanceCount = mAdvertisers.size(); - } - stats.recordAdvertiseStop(instanceCount); + stats.recordAdvertiseStop(mAdvertisers.size()); stats.recordAdvertiseErrorCount(status); } mAdvertiserMap.removeAppAdvertiseStats(regId); } - IBinder gattBinder = mService.getBinder(); - callback.onAdvertisingSetStarted(gattBinder, advertiserId, txPower, status); + sendToCallback( + advertiserId, + () -> + callback.onAdvertisingSetStarted( + mAdvertiseBinder, advertiserId, txPower, status)); } - void onAdvertisingEnabled(int advertiserId, boolean enable, int status) throws Exception { + void onAdvertisingEnabled(int advertiserId, boolean enable, int status) { Log.d( TAG, "onAdvertisingSetEnabled() - advertiserId=" @@ -213,6 +210,7 @@ public class AdvertiseManager { + enable + ", status=" + status); + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { @@ -224,16 +222,13 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onAdvertisingEnabled(advertiserId, enable, status); + sendToCallback( + advertiserId, () -> callback.onAdvertisingEnabled(advertiserId, enable, status)); if (!enable && status != 0) { AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(advertiserId); if (stats != null) { - int instanceCount; - synchronized (mAdvertisers) { - instanceCount = mAdvertisers.size(); - } - stats.recordAdvertiseStop(instanceCount); + stats.recordAdvertiseStop(mAdvertisers.size()); } } } @@ -249,15 +244,18 @@ public class AdvertiseManager { int serverIf, IAdvertisingSetCallback callback, AttributionSource attrSource) { + checkThread(); // If we are using an isolated server, force usage of an NRPA if (serverIf != 0 && parameters.getOwnAddressType() != AdvertisingSetParameters.ADDRESS_TYPE_RANDOM_NON_RESOLVABLE) { Log.w(TAG, "Cannot advertise an isolated GATT server using a resolvable address"); try { - IBinder gattBinder = mService.getBinder(); callback.onAdvertisingSetStarted( - gattBinder, 0x00, 0x00, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); + mAdvertiseBinder, + 0x00, + 0x00, + AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); } catch (RemoteException exception) { Log.e(TAG, "Failed to callback:" + Log.getStackTraceString(exception)); } @@ -281,7 +279,7 @@ public class AdvertiseManager { throw new IllegalArgumentException("Can't link to advertiser's death"); } - String deviceName = AdapterService.getAdapterService().getName(); + String deviceName = mService.getName(); try { byte[] advDataBytes = AdvertiseHelper.advertiseDataToBytes(advertiseData, deviceName); byte[] scanResponseBytes = @@ -289,10 +287,8 @@ public class AdvertiseManager { byte[] periodicDataBytes = AdvertiseHelper.advertiseDataToBytes(periodicData, deviceName); - int cbId = --sTempRegistrationId; - synchronized (mAdvertisers) { - mAdvertisers.put(binder, new AdvertiserInfo(cbId, deathRecipient, callback)); - } + int cbId = --mTempRegistrationId; + mAdvertisers.put(binder, new AdvertiserInfo(cbId, deathRecipient, callback)); Log.d(TAG, "startAdvertisingSet() - reg_id=" + cbId + ", callback: " + binder); @@ -321,18 +317,20 @@ public class AdvertiseManager { } catch (IllegalArgumentException e) { try { binder.unlinkToDeath(deathRecipient, 0); - IBinder gattBinder = mService.getBinder(); callback.onAdvertisingSetStarted( - gattBinder, 0x00, 0x00, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); + mAdvertiseBinder, + 0x00, + 0x00, + AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); } catch (RemoteException exception) { Log.e(TAG, "Failed to callback:" + Log.getStackTraceString(exception)); } } } - void onOwnAddressRead(int advertiserId, int addressType, String address) - throws RemoteException { + void onOwnAddressRead(int advertiserId, int addressType, String address) { Log.d(TAG, "onOwnAddressRead() advertiserId=" + advertiserId); + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { @@ -341,10 +339,12 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onOwnAddressRead(advertiserId, addressType, address); + sendToCallback( + advertiserId, () -> callback.onOwnAddressRead(advertiserId, addressType, address)); } void getOwnAddress(int advertiserId) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "getOwnAddress() - bad advertiserId " + advertiserId); @@ -354,13 +354,11 @@ public class AdvertiseManager { } void stopAdvertisingSet(IAdvertisingSetCallback callback) { + checkThread(); IBinder binder = toBinder(callback); Log.d(TAG, "stopAdvertisingSet() " + binder); - AdvertiserInfo adv; - synchronized (mAdvertisers) { - adv = mAdvertisers.remove(binder); - } + AdvertiserInfo adv = mAdvertisers.remove(binder); if (adv == null) { Log.e(TAG, "stopAdvertisingSet() - no client found for callback"); return; @@ -387,6 +385,7 @@ public class AdvertiseManager { } void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "enableAdvertisingSet() - bad advertiserId " + advertiserId); @@ -398,12 +397,13 @@ public class AdvertiseManager { } void setAdvertisingData(int advertiserId, AdvertiseData data) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "setAdvertisingData() - bad advertiserId " + advertiserId); return; } - String deviceName = AdapterService.getAdapterService().getName(); + String deviceName = mService.getName(); try { mNativeInterface.setAdvertisingData( advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName)); @@ -420,12 +420,13 @@ public class AdvertiseManager { } void setScanResponseData(int advertiserId, AdvertiseData data) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "setScanResponseData() - bad advertiserId " + advertiserId); return; } - String deviceName = AdapterService.getAdapterService().getName(); + String deviceName = mService.getName(); try { mNativeInterface.setScanResponseData( advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName)); @@ -442,6 +443,7 @@ public class AdvertiseManager { } void setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "setAdvertisingParameters() - bad advertiserId " + advertiserId); @@ -454,6 +456,7 @@ public class AdvertiseManager { void setPeriodicAdvertisingParameters( int advertiserId, PeriodicAdvertisingParameters parameters) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "setPeriodicAdvertisingParameters() - bad advertiserId " + advertiserId); @@ -465,12 +468,13 @@ public class AdvertiseManager { } void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "setPeriodicAdvertisingData() - bad advertiserId " + advertiserId); return; } - String deviceName = AdapterService.getAdapterService().getName(); + String deviceName = mService.getName(); try { mNativeInterface.setPeriodicAdvertisingData( advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName)); @@ -487,6 +491,7 @@ public class AdvertiseManager { } void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "setPeriodicAdvertisingEnable() - bad advertiserId " + advertiserId); @@ -495,7 +500,8 @@ public class AdvertiseManager { mNativeInterface.setPeriodicAdvertisingEnable(advertiserId, enable); } - void onAdvertisingDataSet(int advertiserId, int status) throws Exception { + void onAdvertisingDataSet(int advertiserId, int status) { + checkThread(); Log.d(TAG, "onAdvertisingDataSet() advertiserId=" + advertiserId + ", status=" + status); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); @@ -505,10 +511,11 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onAdvertisingDataSet(advertiserId, status); + sendToCallback(advertiserId, () -> callback.onAdvertisingDataSet(advertiserId, status)); } - void onScanResponseDataSet(int advertiserId, int status) throws Exception { + void onScanResponseDataSet(int advertiserId, int status) { + checkThread(); Log.d(TAG, "onScanResponseDataSet() advertiserId=" + advertiserId + ", status=" + status); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); @@ -518,11 +525,10 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onScanResponseDataSet(advertiserId, status); + sendToCallback(advertiserId, () -> callback.onScanResponseDataSet(advertiserId, status)); } - void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) - throws Exception { + void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) { Log.d( TAG, "onAdvertisingParametersUpdated() advertiserId=" @@ -531,6 +537,7 @@ public class AdvertiseManager { + txPower + ", status=" + status); + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { @@ -539,16 +546,19 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onAdvertisingParametersUpdated(advertiserId, txPower, status); + sendToCallback( + advertiserId, + () -> callback.onAdvertisingParametersUpdated(advertiserId, txPower, status)); } - void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) throws Exception { + void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) { Log.d( TAG, "onPeriodicAdvertisingParametersUpdated() advertiserId=" + advertiserId + ", status=" + status); + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { @@ -559,16 +569,19 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onPeriodicAdvertisingParametersUpdated(advertiserId, status); + sendToCallback( + advertiserId, + () -> callback.onPeriodicAdvertisingParametersUpdated(advertiserId, status)); } - void onPeriodicAdvertisingDataSet(int advertiserId, int status) throws Exception { + void onPeriodicAdvertisingDataSet(int advertiserId, int status) { Log.d( TAG, "onPeriodicAdvertisingDataSet() advertiserId=" + advertiserId + ", status=" + status); + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { @@ -577,11 +590,11 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onPeriodicAdvertisingDataSet(advertiserId, status); + sendToCallback( + advertiserId, () -> callback.onPeriodicAdvertisingDataSet(advertiserId, status)); } - void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) - throws Exception { + void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) { Log.d( TAG, "onPeriodicAdvertisingEnabled() advertiserId=" @@ -594,13 +607,65 @@ public class AdvertiseManager { Log.i(TAG, "onAdvertisingSetEnable() - bad advertiserId " + advertiserId); return; } + checkThread(); IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status); + sendToCallback( + advertiserId, + () -> callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status)); AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(advertiserId); if (stats != null) { stats.onPeriodicAdvertiseEnabled(enable); } } + + void doOnAdvertiseThread(Runnable r) { + if (mIsAvailable) { + if (Flags.advertiseThread()) { + mHandler.post( + () -> { + if (mIsAvailable) { + r.run(); + } + }); + } else { + r.run(); + } + } + } + + private void forceRunSyncOnAdvertiseThread(Runnable r) { + if (!Flags.advertiseThread()) { + r.run(); + return; + } + final CompletableFuture<Void> future = new CompletableFuture<>(); + mHandler.postAtFrontOfQueue( + () -> { + r.run(); + future.complete(null); + }); + try { + future.get(RUN_SYNC_WAIT_TIME_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + Log.w(TAG, "Unable to complete sync task: " + e); + } + } + + private void checkThread() { + if (Flags.advertiseThread() + && !mHandler.getLooper().isCurrentThread() + && !Utils.isInstrumentationTestMode()) { + throw new IllegalStateException("Not on advertise thread"); + } + } + + private void sendToCallback(int advertiserId, CallbackWrapper wrapper) { + try { + wrapper.call(); + } catch (RemoteException e) { + Log.i(TAG, "RemoteException in callback for advertiserId: " + advertiserId); + } + } } diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseManagerNativeInterface.java b/android/app/src/com/android/bluetooth/gatt/AdvertiseManagerNativeInterface.java index 215c709970..6cdd47f97e 100644 --- a/android/app/src/com/android/bluetooth/gatt/AdvertiseManagerNativeInterface.java +++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseManagerNativeInterface.java @@ -19,11 +19,12 @@ package com.android.bluetooth.gatt; import android.bluetooth.le.AdvertisingSetParameters; import android.bluetooth.le.PeriodicAdvertisingParameters; +import androidx.annotation.VisibleForTesting; + import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; /** Native interface for AdvertiseManager */ -@VisibleForTesting +@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public class AdvertiseManagerNativeInterface { private static final String TAG = AdvertiseManagerNativeInterface.class.getSimpleName(); @@ -121,43 +122,47 @@ public class AdvertiseManagerNativeInterface { setPeriodicAdvertisingEnableNative(advertiserId, enable); } - void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status) - throws Exception { - mManager.onAdvertisingSetStarted(regId, advertiserId, txPower, status); + void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status) { + mManager.doOnAdvertiseThread( + () -> mManager.onAdvertisingSetStarted(regId, advertiserId, txPower, status)); } - void onOwnAddressRead(int advertiserId, int addressType, String address) throws Exception { - mManager.onOwnAddressRead(advertiserId, addressType, address); + void onOwnAddressRead(int advertiserId, int addressType, String address) { + mManager.doOnAdvertiseThread( + () -> mManager.onOwnAddressRead(advertiserId, addressType, address)); } - void onAdvertisingEnabled(int advertiserId, boolean enable, int status) throws Exception { - mManager.onAdvertisingEnabled(advertiserId, enable, status); + void onAdvertisingEnabled(int advertiserId, boolean enable, int status) { + mManager.doOnAdvertiseThread( + () -> mManager.onAdvertisingEnabled(advertiserId, enable, status)); } - void onAdvertisingDataSet(int advertiserId, int status) throws Exception { - mManager.onAdvertisingDataSet(advertiserId, status); + void onAdvertisingDataSet(int advertiserId, int status) { + mManager.doOnAdvertiseThread(() -> mManager.onAdvertisingDataSet(advertiserId, status)); } - void onScanResponseDataSet(int advertiserId, int status) throws Exception { - mManager.onScanResponseDataSet(advertiserId, status); + void onScanResponseDataSet(int advertiserId, int status) { + mManager.doOnAdvertiseThread(() -> mManager.onScanResponseDataSet(advertiserId, status)); } - void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) - throws Exception { - mManager.onAdvertisingParametersUpdated(advertiserId, txPower, status); + void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) { + mManager.doOnAdvertiseThread( + () -> mManager.onAdvertisingParametersUpdated(advertiserId, txPower, status)); } - void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) throws Exception { - mManager.onPeriodicAdvertisingParametersUpdated(advertiserId, status); + void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) { + mManager.doOnAdvertiseThread( + () -> mManager.onPeriodicAdvertisingParametersUpdated(advertiserId, status)); } - void onPeriodicAdvertisingDataSet(int advertiserId, int status) throws Exception { - mManager.onPeriodicAdvertisingDataSet(advertiserId, status); + void onPeriodicAdvertisingDataSet(int advertiserId, int status) { + mManager.doOnAdvertiseThread( + () -> mManager.onPeriodicAdvertisingDataSet(advertiserId, status)); } - void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) - throws Exception { - mManager.onPeriodicAdvertisingEnabled(advertiserId, enable, status); + void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) { + mManager.doOnAdvertiseThread( + () -> mManager.onPeriodicAdvertisingEnabled(advertiserId, enable, status)); } private native void initializeNative(); diff --git a/android/app/src/com/android/bluetooth/gatt/ContextMap.java b/android/app/src/com/android/bluetooth/gatt/ContextMap.java index e43927a4b0..8ba3e3b671 100644 --- a/android/app/src/com/android/bluetooth/gatt/ContextMap.java +++ b/android/app/src/com/android/bluetooth/gatt/ContextMap.java @@ -27,9 +27,11 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; -import com.android.bluetooth.flags.Flags; import com.android.internal.annotations.GuardedBy; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -49,6 +51,9 @@ import java.util.function.Predicate; */ public class ContextMap<C> { private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap"; + private static final DateTimeFormatter sDateFormat = + DateTimeFormatter.ofPattern("MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()); + private static final int MAX_LAST_RECORDS = 5; /** Connection class helps map connection IDs to device addresses. */ public static class Connection { @@ -142,12 +147,47 @@ public class ContextMap<C> { } } + private class AppRecord { + public final UUID uuid; + public final String appName; + @Nullable public final String attributionTag; + public final Instant registerTime; + + public int clientIf; + public RemoveReason reason; + @Nullable public Instant unregisterTime; + + AppRecord(App app) { + uuid = app.uuid; + appName = app.name; + attributionTag = app.attributionTag; + + registerTime = Instant.now(); + } + } + + public enum RemoveReason { + REASON_UNREGISTER_ALL, + REASON_UNREGISTER_CLIENT, + REASON_UNREGISTER_SERVER, + REASON_BINDER_DIED, + REASON_REGISTER_FAILED, + + REASON_UNKNOWN + } + /** Our internal application list */ private final Object mAppsLock = new Object(); @GuardedBy("mAppsLock") private List<App> mApps = new ArrayList<>(); + @GuardedBy("mAppsLock") + private final List<AppRecord> mOngoingRecords = new ArrayList<>(); + + @GuardedBy("mAppsLock") + private final List<AppRecord> mLastRecords = new ArrayList<>(); + /** Internal list of connected devices */ private List<Connection> mConnections = new ArrayList<>(); @@ -164,12 +204,14 @@ public class ContextMap<C> { synchronized (mAppsLock) { App app = new App(uuid, callback, appUid, appName, attrSource); mApps.add(app); + recordRegisterApp(app); + return app; } } /** Remove the context for a given UUID */ - public void remove(UUID uuid) { + public void remove(UUID uuid, RemoveReason reason) { synchronized (mAppsLock) { Iterator<App> i = mApps.iterator(); while (i.hasNext()) { @@ -177,6 +219,7 @@ public class ContextMap<C> { if (entry.uuid.equals(uuid)) { entry.unlinkToDeath(); i.remove(); + recordUnregisterApp(entry, reason); break; } } @@ -184,7 +227,7 @@ public class ContextMap<C> { } /** Remove the context for a given application ID. */ - public void remove(int id) { + public void remove(int id, RemoveReason reason) { boolean find = false; synchronized (mAppsLock) { Iterator<App> i = mApps.iterator(); @@ -194,6 +237,7 @@ public class ContextMap<C> { find = true; entry.unlinkToDeath(); i.remove(); + recordUnregisterApp(entry, reason); break; } } @@ -226,18 +270,7 @@ public class ContextMap<C> { /** Remove a connection with the given ID. */ void removeConnection(int id, int connId) { synchronized (mConnectionsLock) { - if (Flags.bleContextMapRemoveFix()) { - mConnections.removeIf(conn -> conn.appId == id && conn.connId == connId); - } else { - Iterator<Connection> i = mConnections.iterator(); - while (i.hasNext()) { - Connection connection = i.next(); - if (connection.connId == connId) { - i.remove(); - break; - } - } - } + mConnections.removeIf(conn -> conn.appId == id && conn.connId == connId); } } @@ -360,6 +393,7 @@ public class ContextMap<C> { entry.unlinkToDeath(); } mApps.clear(); + mOngoingRecords.clear(); } synchronized (mConnectionsLock) { @@ -381,7 +415,46 @@ public class ContextMap<C> { /** Logs debug information. */ protected void dump(StringBuilder sb) { synchronized (mAppsLock) { - sb.append(" Entries: ").append(mApps.size()).append("\n\n"); + sb.append(" Entries: ").append(mApps.size()).append("\n"); + sb.append(" Last apps: ").append("\n"); + for (AppRecord record : mLastRecords) { + sb.append(" ") + .append(sDateFormat.format(record.registerTime)) + .append(" ~ ") + .append(sDateFormat.format(record.unregisterTime)) + .append(" app_if: ") + .append(record.clientIf) + .append(", appName: ") + .append(record.appName); + if (record.attributionTag != null) { + sb.append(", tag: ").append(record.attributionTag); + } + sb.append(", reason: ").append(record.reason).append("\n"); + } + sb.append("\n"); + } + } + + @GuardedBy("mAppsLock") + private void recordRegisterApp(App app) { + mOngoingRecords.add(new AppRecord(app)); + } + + @GuardedBy("mAppsLock") + private void recordUnregisterApp(App app, RemoveReason reason) { + for (int i = 0; i < mOngoingRecords.size(); i++) { + if (app.uuid.equals(mOngoingRecords.get(i).uuid)) { + AppRecord record = mOngoingRecords.remove(i); + record.clientIf = app.id; + record.reason = reason; + record.unregisterTime = Instant.now(); + + if (mLastRecords.size() >= MAX_LAST_RECORDS) { + mLastRecords.remove(0); + } + mLastRecords.add(record); + break; + } } } } diff --git a/android/app/src/com/android/bluetooth/gatt/DistanceMeasurementBinder.java b/android/app/src/com/android/bluetooth/gatt/DistanceMeasurementBinder.java new file mode 100644 index 0000000000..3a23b6899d --- /dev/null +++ b/android/app/src/com/android/bluetooth/gatt/DistanceMeasurementBinder.java @@ -0,0 +1,145 @@ +/* + * Copyright 2025 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 static android.Manifest.permission.BLUETOOTH_CONNECT; +import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; + +import static com.android.bluetooth.Utils.callerIsSystemOrActiveOrManagedUser; + +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothStatusCodes; +import android.bluetooth.IDistanceMeasurement; +import android.bluetooth.le.ChannelSoundingParams; +import android.bluetooth.le.DistanceMeasurementMethod; +import android.bluetooth.le.DistanceMeasurementParams; +import android.bluetooth.le.IDistanceMeasurementCallback; +import android.content.AttributionSource; +import android.content.Context; +import android.os.ParcelUuid; + +import com.android.bluetooth.Utils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class DistanceMeasurementBinder extends IDistanceMeasurement.Stub { + private static final String TAG = DistanceMeasurementBinder.class.getSimpleName(); + private final Context mContext; + private final DistanceMeasurementManager mDistanceMeasurementManager; + private volatile boolean mIsAvailable = true; + + DistanceMeasurementBinder(Context context, DistanceMeasurementManager manager) { + mContext = context; + mDistanceMeasurementManager = manager; + } + + void cleanup() { + mIsAvailable = false; + } + + @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) + @Nullable + private DistanceMeasurementManager getManager(AttributionSource source, String method) { + if (!mIsAvailable + || !callerIsSystemOrActiveOrManagedUser( + mContext, TAG, "DistanceMeasurement " + method) + || !Utils.checkConnectPermissionForDataDelivery( + mContext, source, "DistanceMeasurement " + method)) { + return null; + } + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + return mDistanceMeasurementManager; + } + + @Override + public List<DistanceMeasurementMethod> getSupportedDistanceMeasurementMethods( + AttributionSource source) { + DistanceMeasurementManager manager = + getManager(source, "getSupportedDistanceMeasurementMethods"); + if (manager == null) { + return Collections.emptyList(); + } + return Arrays.asList(manager.getSupportedDistanceMeasurementMethods()); + } + + @Override + public void startDistanceMeasurement( + ParcelUuid uuid, + DistanceMeasurementParams distanceMeasurementParams, + IDistanceMeasurementCallback callback, + AttributionSource source) { + DistanceMeasurementManager manager = getManager(source, "startDistanceMeasurement"); + if (manager == null) { + return; + } + manager.startDistanceMeasurement(uuid.getUuid(), distanceMeasurementParams, callback); + } + + @Override + public int stopDistanceMeasurement( + ParcelUuid uuid, BluetoothDevice device, int method, AttributionSource source) { + if (!mIsAvailable) { + return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; + } else if (!callerIsSystemOrActiveOrManagedUser(mContext, TAG, "stopDistanceMeasurement")) { + return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED; + } else if (!Utils.checkConnectPermissionForDataDelivery( + mContext, source, "DistanceMeasurement stopDistanceMeasurement")) { + return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION; + } + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + return mDistanceMeasurementManager.stopDistanceMeasurement( + uuid.getUuid(), device, method, false); + } + + @Override + public int getChannelSoundingMaxSupportedSecurityLevel( + BluetoothDevice remoteDevice, AttributionSource source) { + DistanceMeasurementManager manager = + getManager(source, "getChannelSoundingMaxSupportedSecurityLevel"); + if (manager == null) { + return ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN; + } + return manager.getChannelSoundingMaxSupportedSecurityLevel(remoteDevice); + } + + @Override + public int getLocalChannelSoundingMaxSupportedSecurityLevel(AttributionSource source) { + DistanceMeasurementManager manager = + getManager(source, "getLocalChannelSoundingMaxSupportedSecurityLevel"); + if (manager == null) { + return ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN; + } + return manager.getLocalChannelSoundingMaxSupportedSecurityLevel(); + } + + @Override + public int[] getChannelSoundingSupportedSecurityLevels(AttributionSource source) { + DistanceMeasurementManager manager = + getManager(source, "getChannelSoundingSupportedSecurityLevels"); + + if (manager == null) { + return new int[0]; + } + return manager.getChannelSoundingSupportedSecurityLevels().stream() + .mapToInt(i -> i) + .toArray(); + } +} diff --git a/android/app/src/com/android/bluetooth/gatt/DistanceMeasurementManager.java b/android/app/src/com/android/bluetooth/gatt/DistanceMeasurementManager.java index 65e401bd97..8817c2bf66 100644 --- a/android/app/src/com/android/bluetooth/gatt/DistanceMeasurementManager.java +++ b/android/app/src/com/android/bluetooth/gatt/DistanceMeasurementManager.java @@ -52,15 +52,22 @@ public class DistanceMeasurementManager { private static final int CS_MEDIUM_FREQUENCY_INTERVAL_MS = 3000; private static final int CS_HIGH_FREQUENCY_INTERVAL_MS = 200; + // sync with system/gd/hic/DistanceMeasurementManager + private static final int INVALID_AZIMUTH_ANGLE_DEGREE = -1; + private static final int INVALID_ALTITUDE_ANGLE_DEGREE = -91; + private final AdapterService mAdapterService; private final HandlerThread mHandlerThread; - DistanceMeasurementNativeInterface mDistanceMeasurementNativeInterface; + private final DistanceMeasurementNativeInterface mDistanceMeasurementNativeInterface; + private final DistanceMeasurementBinder mDistanceMeasurementBinder; private final ConcurrentHashMap<String, CopyOnWriteArraySet<DistanceMeasurementTracker>> mRssiTrackers = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, CopyOnWriteArraySet<DistanceMeasurementTracker>> mCsTrackers = new ConcurrentHashMap<>(); private final boolean mHasChannelSoundingFeature; + private volatile boolean mIsTurnedOff = false; + /** Constructor of {@link DistanceMeasurementManager}. */ DistanceMeasurementManager(AdapterService adapterService) { mAdapterService = adapterService; @@ -70,6 +77,7 @@ public class DistanceMeasurementManager { mHandlerThread.start(); mDistanceMeasurementNativeInterface = DistanceMeasurementNativeInterface.getInstance(); mDistanceMeasurementNativeInterface.init(this); + mDistanceMeasurementBinder = new DistanceMeasurementBinder(adapterService, this); if (Flags.channelSounding25q2Apis()) { mHasChannelSoundingFeature = adapterService @@ -81,7 +89,26 @@ public class DistanceMeasurementManager { } void cleanup() { + mIsTurnedOff = true; + mDistanceMeasurementBinder.cleanup(); mDistanceMeasurementNativeInterface.cleanup(); + Log.d(TAG, "stop all sessions as BT is off"); + for (String addressForCs : mCsTrackers.keySet()) { + onDistanceMeasurementStopped( + addressForCs, + BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, + DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_CHANNEL_SOUNDING); + } + for (String addressForRssi : mRssiTrackers.keySet()) { + onDistanceMeasurementStopped( + addressForRssi, + BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, + DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_RSSI); + } + } + + DistanceMeasurementBinder getBinder() { + return mDistanceMeasurementBinder; } DistanceMeasurementMethod[] getSupportedDistanceMeasurementMethods() { @@ -102,6 +129,12 @@ public class DistanceMeasurementManager { void startDistanceMeasurement( UUID uuid, DistanceMeasurementParams params, IDistanceMeasurementCallback callback) { + if (mIsTurnedOff) { + Log.d(TAG, "BT is turned off, no new request is allowed."); + invokeStartFail( + callback, params.getDevice(), BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED); + return; + } Log.i( TAG, "startDistanceMeasurement:" @@ -471,6 +504,9 @@ public class DistanceMeasurementManager { int errorAltitudeAngle, long elapsedRealtimeNanos, int confidenceLevel, + double delaySpreadMeters, + int detectedAttackLevel, + double velocityMetersPerSecond, int method) { logd( "onDistanceMeasurementResult " @@ -482,16 +518,31 @@ public class DistanceMeasurementManager { DistanceMeasurementResult.Builder builder = new DistanceMeasurementResult.Builder(centimeter / 100.0, errorCentimeter / 100.0) .setMeasurementTimestampNanos(elapsedRealtimeNanos); - if (confidenceLevel != -1) { - builder.setConfidenceLevel(confidenceLevel / 100.0); - } - DistanceMeasurementResult result = builder.build(); + switch (method) { case DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_RSSI: - handleRssiResult(address, result); + handleRssiResult(address, builder.build()); break; case DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_CHANNEL_SOUNDING: - handleCsResult(address, result); + if (azimuthAngle != INVALID_AZIMUTH_ANGLE_DEGREE) { + builder.setAzimuthAngle(azimuthAngle); + builder.setErrorAzimuthAngle(errorAzimuthAngle); + } + if (altitudeAngle != INVALID_ALTITUDE_ANGLE_DEGREE) { + builder.setAltitudeAngle(altitudeAngle); + builder.setErrorAltitudeAngle(errorAltitudeAngle); + } + if (confidenceLevel != -1) { + builder.setConfidenceLevel(confidenceLevel / 100.0); + } + if (delaySpreadMeters >= 0) { + builder.setDelaySpreadMeters(delaySpreadMeters); + } + if (velocityMetersPerSecond >= 0) { + builder.setVelocityMetersPerSecond(velocityMetersPerSecond); + } + builder.setDetectedAttackLevel(detectedAttackLevel); + handleCsResult(address, builder.build()); break; default: Log.w(TAG, "onDistanceMeasurementResult: invalid method " + method); @@ -538,4 +589,5 @@ public class DistanceMeasurementManager { private static void logd(String msg) { Log.d(TAG, msg); } + } diff --git a/android/app/src/com/android/bluetooth/gatt/DistanceMeasurementNativeInterface.java b/android/app/src/com/android/bluetooth/gatt/DistanceMeasurementNativeInterface.java index 7009dde697..ad8a7f9bd4 100644 --- a/android/app/src/com/android/bluetooth/gatt/DistanceMeasurementNativeInterface.java +++ b/android/app/src/com/android/bluetooth/gatt/DistanceMeasurementNativeInterface.java @@ -107,6 +107,9 @@ public class DistanceMeasurementNativeInterface { int errorAltitudeAngle, long elapsedRealtimeNanos, int confidenceLevel, + double delayedSpreadMeters, + int detectedAttackLevel, + double velocityMetersPerSecond, int method) { mDistanceMeasurementManager.onDistanceMeasurementResult( address, @@ -118,6 +121,9 @@ public class DistanceMeasurementNativeInterface { errorAltitudeAngle, elapsedRealtimeNanos, confidenceLevel, + delayedSpreadMeters, + detectedAttackLevel, + velocityMetersPerSecond, method); } diff --git a/android/app/src/com/android/bluetooth/gatt/GattNativeInterface.java b/android/app/src/com/android/bluetooth/gatt/GattNativeInterface.java index af59e0c8f6..ad4d9538e7 100644 --- a/android/app/src/com/android/bluetooth/gatt/GattNativeInterface.java +++ b/android/app/src/com/android/bluetooth/gatt/GattNativeInterface.java @@ -282,7 +282,7 @@ public class GattNativeInterface { private native int gattClientGetDeviceTypeNative(String address); private native void gattClientRegisterAppNative( - long appUuidLsb, long appUuidMsb, boolean eattSupport); + long appUuidLsb, long appUuidMsb, String name, boolean eattSupport); private native void gattClientUnregisterAppNative(int clientIf); @@ -427,8 +427,9 @@ public class GattNativeInterface { /** * Register the given client It will invoke {@link #onClientRegistered(int, int, long, long)}. */ - public void gattClientRegisterApp(long appUuidLsb, long appUuidMsb, boolean eattSupport) { - gattClientRegisterAppNative(appUuidLsb, appUuidMsb, eattSupport); + public void gattClientRegisterApp( + long appUuidLsb, long appUuidMsb, String name, boolean eattSupport) { + gattClientRegisterAppNative(appUuidLsb, appUuidMsb, name, eattSupport); } /** Unregister the client */ diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java index 2927132986..e9563f8908 100644 --- a/android/app/src/com/android/bluetooth/gatt/GattService.java +++ b/android/app/src/com/android/bluetooth/gatt/GattService.java @@ -16,13 +16,13 @@ package com.android.bluetooth.gatt; -import static android.Manifest.permission.BLUETOOTH_ADVERTISE; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; import static com.android.bluetooth.Utils.callerIsSystemOrActiveOrManagedUser; import static com.android.bluetooth.Utils.checkCallerTargetSdk; +import static com.android.bluetooth.util.AttributionSourceUtil.getLastAttributionTag; import static java.util.Objects.requireNonNull; @@ -43,14 +43,6 @@ import android.bluetooth.BluetoothUtils; import android.bluetooth.IBluetoothGatt; import android.bluetooth.IBluetoothGattCallback; import android.bluetooth.IBluetoothGattServerCallback; -import android.bluetooth.le.AdvertiseData; -import android.bluetooth.le.AdvertisingSetParameters; -import android.bluetooth.le.ChannelSoundingParams; -import android.bluetooth.le.DistanceMeasurementMethod; -import android.bluetooth.le.DistanceMeasurementParams; -import android.bluetooth.le.IAdvertisingSetCallback; -import android.bluetooth.le.IDistanceMeasurementCallback; -import android.bluetooth.le.PeriodicAdvertisingParameters; import android.companion.CompanionDeviceManager; import android.content.AttributionSource; import android.content.pm.PackageManager; @@ -59,6 +51,7 @@ import android.content.pm.PackageManager.PackageInfoFlags; import android.content.res.Resources; import android.os.Binder; import android.os.Build; +import android.os.HandlerThread; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; @@ -127,6 +120,9 @@ public class GattService extends ProfileService { private static final Map<String, Integer> EARLY_MTU_EXCHANGE_PACKAGES = Map.of("com.teslamotors", GATT_MTU_MAX); + private static final Map<String, String> GATT_CLIENTS_NOTIFY_TO_ADAPTER_PACKAGES = + Map.of("com.google.android.gms", "com.google.android.gms.findmydevice"); + @VisibleForTesting static final int GATT_CLIENT_LIMIT_PER_APP = 32; @Nullable public final ScanController mScanController; @@ -161,6 +157,7 @@ public class GattService extends ProfileService { private final DistanceMeasurementManager mDistanceMeasurementManager; private final ActivityManager mActivityManager; private final PackageManager mPackageManager; + private final HandlerThread mHandlerThread; public GattService(AdapterService adapterService) { super(requireNonNull(adapterService)); @@ -174,7 +171,12 @@ public class GattService extends ProfileService { mNativeInterface = GattObjectsFactory.getInstance().getNativeInterface(); mNativeInterface.init(this); - mAdvertiseManager = new AdvertiseManager(this); + + // Create a thread to handle LE operations + mHandlerThread = new HandlerThread("Bluetooth LE"); + mHandlerThread.start(); + + mAdvertiseManager = new AdvertiseManager(mAdapterService, mHandlerThread.getLooper()); if (!Flags.scanManagerRefactor()) { mScanController = new ScanController(adapterService); @@ -214,7 +216,6 @@ public class GattService extends ProfileService { if (mScanController != null) { mScanController.stop(); } - mAdvertiseManager.clear(); mClientMap.clear(); mRestrictedHandles.clear(); mServerMap.clear(); @@ -229,6 +230,7 @@ public class GattService extends ProfileService { mNativeInterface.cleanup(); mAdvertiseManager.cleanup(); mDistanceMeasurementManager.cleanup(); + mHandlerThread.quit(); } /** This is only used when Flags.scanManagerRefactor() is true. */ @@ -284,13 +286,6 @@ public class GattService extends ProfileService { return restrictedHandles != null && restrictedHandles.contains(handle); } - /** Notify Scan manager of bluetooth profile connection state changes */ - public void notifyProfileConnectionStateChange(int profile, int fromState, int toState) { - if (mScanController != null) { - mScanController.notifyProfileConnectionStateChange(profile, fromState, toState); - } - } - class ServerDeathRecipient implements IBinder.DeathRecipient { int mAppIf; private String mPackageName; @@ -323,7 +318,8 @@ public class GattService extends ProfileService { Log.d( TAG, "Binder is dead - unregistering client (" + mPackageName + " " + mAppIf + ")!"); - unregisterClient(mAppIf, getAttributionSource()); + unregisterClient( + mAppIf, getAttributionSource(), ContextMap.RemoveReason.REASON_BINDER_DIED); } } @@ -382,7 +378,8 @@ public class GattService extends ProfileService { if (service == null) { return; } - service.unregisterClient(clientIf, attributionSource); + service.unregisterClient( + clientIf, attributionSource, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); } @Override @@ -833,133 +830,6 @@ public class GattService extends ProfileService { } @Override - public void startAdvertisingSet( - AdvertisingSetParameters parameters, - AdvertiseData advertiseData, - AdvertiseData scanResponse, - PeriodicAdvertisingParameters periodicParameters, - AdvertiseData periodicData, - int duration, - int maxExtAdvEvents, - int serverIf, - IAdvertisingSetCallback callback, - AttributionSource attributionSource) { - GattService service = getService(); - if (service == null) { - return; - } - service.startAdvertisingSet( - parameters, - advertiseData, - scanResponse, - periodicParameters, - periodicData, - duration, - maxExtAdvEvents, - serverIf, - callback, - attributionSource); - } - - @Override - public void stopAdvertisingSet( - IAdvertisingSetCallback callback, AttributionSource attributionSource) { - GattService service = getService(); - if (service == null) { - return; - } - service.stopAdvertisingSet(callback, attributionSource); - } - - @Override - public void getOwnAddress(int advertiserId, AttributionSource attributionSource) { - GattService service = getService(); - if (service == null) { - return; - } - service.getOwnAddress(advertiserId, attributionSource); - } - - @Override - public void enableAdvertisingSet( - int advertiserId, - boolean enable, - int duration, - int maxExtAdvEvents, - AttributionSource attributionSource) { - GattService service = getService(); - if (service == null) { - return; - } - service.enableAdvertisingSet( - advertiserId, enable, duration, maxExtAdvEvents, attributionSource); - } - - @Override - public void setAdvertisingData( - int advertiserId, AdvertiseData data, AttributionSource attributionSource) { - GattService service = getService(); - if (service == null) { - return; - } - service.setAdvertisingData(advertiserId, data, attributionSource); - } - - @Override - public void setScanResponseData( - int advertiserId, AdvertiseData data, AttributionSource attributionSource) { - GattService service = getService(); - if (service == null) { - return; - } - service.setScanResponseData(advertiserId, data, attributionSource); - } - - @Override - public void setAdvertisingParameters( - int advertiserId, - AdvertisingSetParameters parameters, - AttributionSource attributionSource) { - GattService service = getService(); - if (service == null) { - return; - } - service.setAdvertisingParameters(advertiserId, parameters, attributionSource); - } - - @Override - public void setPeriodicAdvertisingParameters( - int advertiserId, - PeriodicAdvertisingParameters parameters, - AttributionSource attributionSource) { - GattService service = getService(); - if (service == null) { - return; - } - service.setPeriodicAdvertisingParameters(advertiserId, parameters, attributionSource); - } - - @Override - public void setPeriodicAdvertisingData( - int advertiserId, AdvertiseData data, AttributionSource attributionSource) { - GattService service = getService(); - if (service == null) { - return; - } - service.setPeriodicAdvertisingData(advertiserId, data, attributionSource); - } - - @Override - public void setPeriodicAdvertisingEnable( - int advertiserId, boolean enable, AttributionSource attributionSource) { - GattService service = getService(); - if (service == null) { - return; - } - service.setPeriodicAdvertisingEnable(advertiserId, enable, attributionSource); - } - - @Override public void disconnectAll(AttributionSource attributionSource) { GattService service = getService(); if (service == null) { @@ -967,119 +837,7 @@ public class GattService extends ProfileService { } service.disconnectAll(attributionSource); } - - @Override - public List<DistanceMeasurementMethod> getSupportedDistanceMeasurementMethods( - AttributionSource attributionSource) { - GattService service = getService(); - if (service == null - || !callerIsSystemOrActiveOrManagedUser( - service, TAG, "GattService getSupportedDistanceMeasurementMethods") - || !Utils.checkConnectPermissionForDataDelivery( - service, - attributionSource, - "GattService getSupportedDistanceMeasurementMethods")) { - return Collections.emptyList(); - } - service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); - return Arrays.asList(service.getSupportedDistanceMeasurementMethods()); - } - - @Override - public void startDistanceMeasurement( - ParcelUuid uuid, - DistanceMeasurementParams distanceMeasurementParams, - IDistanceMeasurementCallback callback, - AttributionSource attributionSource) { - GattService service = getService(); - if (service == null - || !callerIsSystemOrActiveOrManagedUser( - service, TAG, "startDistanceMeasurement") - || !Utils.checkConnectPermissionForDataDelivery( - service, attributionSource, "GattService startDistanceMeasurement")) { - return; - } - service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); - service.startDistanceMeasurement(uuid.getUuid(), distanceMeasurementParams, callback); - } - - @Override - public int stopDistanceMeasurement( - ParcelUuid uuid, - BluetoothDevice device, - int method, - AttributionSource attributionSource) { - GattService service = getService(); - if (service == null) { - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } else if (!callerIsSystemOrActiveOrManagedUser( - service, TAG, "stopDistanceMeasurement")) { - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED; - } else if (!Utils.checkConnectPermissionForDataDelivery( - service, attributionSource, "GattService stopDistanceMeasurement")) { - return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION; - } - service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); - return service.stopDistanceMeasurement(uuid.getUuid(), device, method); - } - - @Override - public int getChannelSoundingMaxSupportedSecurityLevel( - BluetoothDevice remoteDevice, AttributionSource attributionSource) { - GattService service = getService(); - if (service == null - || !callerIsSystemOrActiveOrManagedUser( - service, TAG, "GattService getChannelSoundingMaxSupportedSecurityLevel") - || !Utils.checkConnectPermissionForDataDelivery( - service, - attributionSource, - "GattService getChannelSoundingMaxSupportedSecurityLevel")) { - return ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN; - } - service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); - return service.getChannelSoundingMaxSupportedSecurityLevel(remoteDevice); - } - - @Override - public int getLocalChannelSoundingMaxSupportedSecurityLevel( - AttributionSource attributionSource) { - GattService service = getService(); - if (service == null - || !callerIsSystemOrActiveOrManagedUser( - service, - TAG, - "GattService getLocalChannelSoundingMaxSupportedSecurityLevel") - || !Utils.checkConnectPermissionForDataDelivery( - service, - attributionSource, - "GattService getLocalChannelSoundingMaxSupportedSecurityLevel")) { - return ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN; - } - service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); - return service.getLocalChannelSoundingMaxSupportedSecurityLevel(); - } - - @Override - public int[] getChannelSoundingSupportedSecurityLevels( - AttributionSource attributionSource) { - GattService service = getService(); - - if (service == null - || !callerIsSystemOrActiveOrManagedUser( - service, TAG, "GattService getChannelSoundingSupportedSecurityLevels") - || !Utils.checkConnectPermissionForDataDelivery( - service, - attributionSource, - "GattService getChannelSoundingSupportedSecurityLevels")) { - return new int[0]; - } - service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); - return service.getChannelSoundingSupportedSecurityLevels().stream() - .mapToInt(i -> i) - .toArray(); - } } - ; /************************************************************************** * Callback functions - CLIENT @@ -1095,7 +853,7 @@ public class GattService extends ProfileService { app.id = clientIf; app.linkToDeath(new ClientDeathRecipient(clientIf, app.name)); } else { - mClientMap.remove(uuid); + mClientMap.remove(uuid, ContextMap.RemoveReason.REASON_REGISTER_FAILED); } app.callback.onClientRegistered(status, clientIf); } @@ -1109,7 +867,9 @@ public class GattService extends ProfileService { + ", connId=" + connId + ", address=" - + BluetoothUtils.toAnonymizedAddress(address)); + + BluetoothUtils.toAnonymizedAddress(address) + + ", status=" + + status); int connectionState = BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED; if (status == 0) { mClientMap.addConnection(clientIf, connId, address); @@ -1123,7 +883,10 @@ public class GattService extends ProfileService { mPermits.putIfAbsent(address, -1); } connectionState = BluetoothProtoEnums.CONNECTION_STATE_CONNECTED; + } else { + mAdapterService.notifyGattClientConnectFailed(clientIf, getDevice(address)); } + ContextMap<IBluetoothGattCallback>.App app = mClientMap.getById(clientIf); if (app != null) { app.callback.onClientConnectionState( @@ -1152,6 +915,7 @@ public class GattService extends ProfileService { + BluetoothUtils.toAnonymizedAddress(address)); mClientMap.removeConnection(clientIf, connId); + mAdapterService.notifyGattClientDisconnect(clientIf, getDevice(address)); ContextMap<IBluetoothGattCallback>.App app = mClientMap.getById(clientIf); mRestrictedHandles.remove(connId); @@ -1745,193 +1509,13 @@ public class GattService extends ProfileService { public void unregAll(AttributionSource attributionSource) { for (Integer appId : mClientMap.getAllAppsIds()) { Log.d(TAG, "unreg:" + appId); - unregisterClient(appId, attributionSource); - } - } - - /************************************************************************** - * ADVERTISING SET - *************************************************************************/ - @RequiresPermission( - allOf = { - BLUETOOTH_ADVERTISE, - BLUETOOTH_PRIVILEGED, - }, - conditional = true) - void startAdvertisingSet( - AdvertisingSetParameters parameters, - AdvertiseData advertiseData, - AdvertiseData scanResponse, - PeriodicAdvertisingParameters periodicParameters, - AdvertiseData periodicData, - int duration, - int maxExtAdvEvents, - int serverIf, - IAdvertisingSetCallback callback, - AttributionSource attributionSource) { - if (!Utils.checkAdvertisePermissionForDataDelivery( - this, attributionSource, "GattService startAdvertisingSet")) { - return; - } - if (parameters.getOwnAddressType() != AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT - || serverIf != 0 - || parameters.isDirected()) { - this.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); - } - mAdvertiseManager.startAdvertisingSet( - parameters, - advertiseData, - scanResponse, - periodicParameters, - periodicData, - duration, - maxExtAdvEvents, - serverIf, - callback, - attributionSource); - } - - @RequiresPermission(BLUETOOTH_ADVERTISE) - void stopAdvertisingSet(IAdvertisingSetCallback callback, AttributionSource attributionSource) { - if (!Utils.checkAdvertisePermissionForDataDelivery( - this, attributionSource, "GattService stopAdvertisingSet")) { - return; - } - mAdvertiseManager.stopAdvertisingSet(callback); - } - - @RequiresPermission( - allOf = { - BLUETOOTH_ADVERTISE, - BLUETOOTH_PRIVILEGED, - }) - void getOwnAddress(int advertiserId, AttributionSource attributionSource) { - if (!Utils.checkAdvertisePermissionForDataDelivery( - this, attributionSource, "GattService getOwnAddress")) { - return; - } - this.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); - mAdvertiseManager.getOwnAddress(advertiserId); - } - - @RequiresPermission(BLUETOOTH_ADVERTISE) - void enableAdvertisingSet( - int advertiserId, - boolean enable, - int duration, - int maxExtAdvEvents, - AttributionSource attributionSource) { - if (!Utils.checkAdvertisePermissionForDataDelivery( - this, attributionSource, "GattService enableAdvertisingSet")) { - return; - } - mAdvertiseManager.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents); - } - - @RequiresPermission(BLUETOOTH_ADVERTISE) - void setAdvertisingData( - int advertiserId, AdvertiseData data, AttributionSource attributionSource) { - if (!Utils.checkAdvertisePermissionForDataDelivery( - this, attributionSource, "GattService setAdvertisingData")) { - return; - } - mAdvertiseManager.setAdvertisingData(advertiserId, data); - } - - @RequiresPermission(BLUETOOTH_ADVERTISE) - void setScanResponseData( - int advertiserId, AdvertiseData data, AttributionSource attributionSource) { - if (!Utils.checkAdvertisePermissionForDataDelivery( - this, attributionSource, "GattService setScanResponseData")) { - return; - } - mAdvertiseManager.setScanResponseData(advertiserId, data); - } - - @RequiresPermission( - allOf = { - BLUETOOTH_ADVERTISE, - BLUETOOTH_PRIVILEGED, - }, - conditional = true) - void setAdvertisingParameters( - int advertiserId, - AdvertisingSetParameters parameters, - AttributionSource attributionSource) { - if (!Utils.checkAdvertisePermissionForDataDelivery( - this, attributionSource, "GattService setAdvertisingParameters")) { - return; - } - if (parameters.getOwnAddressType() != AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT - || parameters.isDirected()) { - this.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); - } - mAdvertiseManager.setAdvertisingParameters(advertiserId, parameters); - } - - @RequiresPermission(BLUETOOTH_ADVERTISE) - void setPeriodicAdvertisingParameters( - int advertiserId, - PeriodicAdvertisingParameters parameters, - AttributionSource attributionSource) { - if (!Utils.checkAdvertisePermissionForDataDelivery( - this, attributionSource, "GattService setPeriodicAdvertisingParameters")) { - return; - } - mAdvertiseManager.setPeriodicAdvertisingParameters(advertiserId, parameters); - } - - @RequiresPermission(BLUETOOTH_ADVERTISE) - void setPeriodicAdvertisingData( - int advertiserId, AdvertiseData data, AttributionSource attributionSource) { - if (!Utils.checkAdvertisePermissionForDataDelivery( - this, attributionSource, "GattService setPeriodicAdvertisingData")) { - return; + unregisterClient( + appId, attributionSource, ContextMap.RemoveReason.REASON_UNREGISTER_ALL); } - mAdvertiseManager.setPeriodicAdvertisingData(advertiserId, data); - } - - @RequiresPermission(BLUETOOTH_ADVERTISE) - void setPeriodicAdvertisingEnable( - int advertiserId, boolean enable, AttributionSource attributionSource) { - if (!Utils.checkAdvertisePermissionForDataDelivery( - this, attributionSource, "GattService setPeriodicAdvertisingEnable")) { - return; + for (Integer appId : mServerMap.getAllAppsIds()) { + Log.d(TAG, "unreg:" + appId); + unregisterServer(appId, attributionSource); } - mAdvertiseManager.setPeriodicAdvertisingEnable(advertiserId, enable); - } - - /************************************************************************** - * Distance Measurement - *************************************************************************/ - - DistanceMeasurementMethod[] getSupportedDistanceMeasurementMethods() { - return mDistanceMeasurementManager.getSupportedDistanceMeasurementMethods(); - } - - void startDistanceMeasurement( - UUID uuid, - DistanceMeasurementParams distanceMeasurementParams, - IDistanceMeasurementCallback callback) { - mDistanceMeasurementManager.startDistanceMeasurement( - uuid, distanceMeasurementParams, callback); - } - - int stopDistanceMeasurement(UUID uuid, BluetoothDevice device, int method) { - return mDistanceMeasurementManager.stopDistanceMeasurement(uuid, device, method, false); - } - - int getChannelSoundingMaxSupportedSecurityLevel(BluetoothDevice remoteDevice) { - return mDistanceMeasurementManager.getChannelSoundingMaxSupportedSecurityLevel( - remoteDevice); - } - - int getLocalChannelSoundingMaxSupportedSecurityLevel() { - return mDistanceMeasurementManager.getLocalChannelSoundingMaxSupportedSecurityLevel(); - } - - Set<Integer> getChannelSoundingSupportedSecurityLevels() { - return mDistanceMeasurementManager.getChannelSoundingSupportedSecurityLevels(); } /************************************************************************** @@ -1959,14 +1543,26 @@ public class GattService extends ProfileService { return; } - Log.d(TAG, "registerClient() - UUID=" + uuid); + String name = attributionSource.getPackageName(); + String tag = getLastAttributionTag(attributionSource); + String myPackage = AttributionSource.myAttributionSource().getPackageName(); + if (myPackage.equals(name) && tag != null) { + /* For clients created by Bluetooth stack, use just tag as name */ + name = tag; + } else if (tag != null) { + name = name + "[" + tag + "]"; + } + + Log.d(TAG, "registerClient() - UUID=" + uuid + " name=" + name); mClientMap.add(uuid, callback, this, attributionSource); + mNativeInterface.gattClientRegisterApp( - uuid.getLeastSignificantBits(), uuid.getMostSignificantBits(), eatt_support); + uuid.getLeastSignificantBits(), uuid.getMostSignificantBits(), name, eatt_support); } @RequiresPermission(BLUETOOTH_CONNECT) - void unregisterClient(int clientIf, AttributionSource attributionSource) { + void unregisterClient( + int clientIf, AttributionSource attributionSource, ContextMap.RemoveReason reason) { if (!Utils.checkConnectPermissionForDataDelivery( this, attributionSource, "GattService unregisterClient")) { return; @@ -1982,7 +1578,7 @@ public class GattService extends ProfileService { BluetoothStatsLog.BLUETOOTH_CROSS_LAYER_EVENT_REPORTED__STATE__END, attributionSource.getUid()); } - mClientMap.remove(clientIf); + mClientMap.remove(clientIf, reason); mNativeInterface.gattClientUnregisterApp(clientIf); } @@ -2055,6 +1651,21 @@ public class GattService extends ProfileService { } } + if (transport != BluetoothDevice.TRANSPORT_BREDR && isDirect && !opportunistic) { + String attributionTag = getLastAttributionTag(attributionSource); + if (packageName != null && attributionTag != null) { + for (Map.Entry<String, String> entry : + GATT_CLIENTS_NOTIFY_TO_ADAPTER_PACKAGES.entrySet()) { + if (packageName.contains(entry.getKey()) + && attributionTag.contains(entry.getValue())) { + mAdapterService.notifyDirectLeGattClientConnect( + clientIf, getDevice(address)); + break; + } + } + } + } + mNativeInterface.gattClientConnect( clientIf, address, @@ -2093,6 +1704,9 @@ public class GattService extends ProfileService { .BLUETOOTH_CROSS_LAYER_EVENT_REPORTED__EVENT_TYPE__GATT_DISCONNECT_JAVA, BluetoothStatsLog.BLUETOOTH_CROSS_LAYER_EVENT_REPORTED__STATE__START, attributionSource.getUid()); + + mAdapterService.notifyGattClientDisconnect(clientIf, getDevice(address)); + mNativeInterface.gattClientDisconnect(clientIf, address, connId != null ? connId : 0); } @@ -3096,7 +2710,7 @@ public class GattService extends ProfileService { deleteServices(serverIf); - mServerMap.remove(serverIf); + mServerMap.remove(serverIf, ContextMap.RemoveReason.REASON_UNREGISTER_SERVER); mNativeInterface.gattServerUnregisterApp(serverIf); } @@ -3350,6 +2964,18 @@ public class GattService extends ProfileService { } /************************************************************************** + * Binder functions + *************************************************************************/ + + public IBinder getBluetoothAdvertise() { + return mAdvertiseManager.getBinder(); + } + + public IBinder getDistanceMeasurement() { + return mDistanceMeasurementManager.getBinder(); + } + + /************************************************************************** * Private functions *************************************************************************/ diff --git a/android/app/src/com/android/bluetooth/gatt/GattServiceConfig.java b/android/app/src/com/android/bluetooth/gatt/GattServiceConfig.kt index cbf574639c..c31521d6a4 100644 --- a/android/app/src/com/android/bluetooth/gatt/GattServiceConfig.java +++ b/android/app/src/com/android/bluetooth/gatt/GattServiceConfig.kt @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.android.bluetooth.gatt; +package com.android.bluetooth.gatt -/** GattService configuration. */ -public class GattServiceConfig { - public static final String TAG_PREFIX = "BtGatt."; - public static final boolean DEBUG_ADMIN = false; +object GattServiceConfig { + @JvmField val TAG_PREFIX = "BtGatt." + @JvmField val DEBUG_ADMIN = false } diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java index 69feb368ef..949db48483 100644 --- a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java +++ b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java @@ -28,7 +28,6 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothProfile; @@ -2644,16 +2643,7 @@ public class HeadsetService extends ProfileService { List<BluetoothDevice> fallbackCandidates = getConnectedDevices(); List<BluetoothDevice> uninterestedCandidates = new ArrayList<>(); for (BluetoothDevice device : fallbackCandidates) { - byte[] deviceType = - dbManager.getCustomMeta(device, BluetoothDevice.METADATA_DEVICE_TYPE); - BluetoothClass deviceClass = - new BluetoothClass( - mAdapterService.getRemoteDevices().getBluetoothClass(device)); - if ((deviceClass != null - && deviceClass.getMajorDeviceClass() - == BluetoothClass.Device.WEARABLE_WRIST_WATCH) - || (deviceType != null - && BluetoothDevice.DEVICE_TYPE_WATCH.equals(new String(deviceType)))) { + if (Utils.isWatch(mAdapterService, device)) { uninterestedCandidates.add(device); } } diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java index fd56faa613..fb0ed9d2f0 100644 --- a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java +++ b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java @@ -55,7 +55,6 @@ import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.MetricsLogger; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.storage.DatabaseManager; -import com.android.bluetooth.flags.Flags; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -1122,12 +1121,6 @@ class HeadsetStateMachine extends StateMachine { } } break; - case INTENT_SCO_VOLUME_CHANGED: - if (Flags.hfpAllowVolumeChangeWithoutSco()) { - // when flag is removed, remove INTENT_SCO_VOLUME_CHANGED case in AudioOn - processIntentScoVolume((Intent) message.obj, mDevice); - } - break; case INTENT_CONNECTION_ACCESS_REPLY: handleAccessPermissionResult((Intent) message.obj); break; @@ -1630,8 +1623,6 @@ class HeadsetStateMachine extends StateMachine { break; } case INTENT_SCO_VOLUME_CHANGED: - // TODO: b/362313390 Remove this case once the fix is in place because this - // message will be handled by the ConnectedBase state. processIntentScoVolume((Intent) message.obj, mDevice); break; case STACK_EVENT: diff --git a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java index a48abbb9d3..0f654b8ccd 100644 --- a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java +++ b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java @@ -48,7 +48,6 @@ import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.storage.DatabaseManager; -import com.android.bluetooth.flags.Flags; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -180,18 +179,12 @@ public class HeadsetClientService extends ProfileService { int hfToAmVol(int hfVol) { int amRange = mMaxAmVcVol - mMinAmVcVol; int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME; - int amVol = 0; - if (Flags.headsetClientAmHfVolumeSymmetric()) { - amVol = - (int) - Math.round( - (hfVol - MIN_HFP_SCO_VOICE_CALL_VOLUME) - * ((double) amRange / hfRange)) - + mMinAmVcVol; - } else { - int amOffset = (amRange * (hfVol - MIN_HFP_SCO_VOICE_CALL_VOLUME)) / hfRange; - amVol = mMinAmVcVol + amOffset; - } + int amVol = + (int) + Math.round( + (hfVol - MIN_HFP_SCO_VOICE_CALL_VOLUME) + * ((double) amRange / hfRange)) + + mMinAmVcVol; Log.d(TAG, "HF -> AM " + hfVol + " " + amVol); return amVol; } @@ -199,15 +192,9 @@ public class HeadsetClientService extends ProfileService { int amToHfVol(int amVol) { int amRange = (mMaxAmVcVol > mMinAmVcVol) ? (mMaxAmVcVol - mMinAmVcVol) : 1; int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME; - int hfVol = 0; - if (Flags.headsetClientAmHfVolumeSymmetric()) { - hfVol = - (int) Math.round((amVol - mMinAmVcVol) * ((double) hfRange / amRange)) - + MIN_HFP_SCO_VOICE_CALL_VOLUME; - } else { - int hfOffset = (hfRange * (amVol - mMinAmVcVol)) / amRange; - hfVol = MIN_HFP_SCO_VOICE_CALL_VOLUME + hfOffset; - } + int hfVol = + (int) Math.round((amVol - mMinAmVcVol) * ((double) hfRange / amRange)) + + MIN_HFP_SCO_VOICE_CALL_VOLUME; Log.d(TAG, "AM -> HF " + amVol + " " + hfVol); return hfVol; } diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java index d05b6777f2..bb5346fe6e 100644 --- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java @@ -102,6 +102,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -142,6 +143,9 @@ public class LeAudioService extends ProfileService { /** Indicates group is active */ private static final int ACTIVE_STATE_ACTIVE = 0x02; + /** Filter for Targeted Announcements */ + static final byte[] CAP_TARGETED_ANNOUNCEMENT_PAYLOAD = new byte[] {0x01}; + /** This is used by application read-only for checking the fallback active group id. */ public static final String BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_GROUP_ID = "bluetooth_le_broadcast_fallback_active_group_id"; @@ -255,6 +259,7 @@ public class LeAudioService extends ProfileService { mInputSelectableConfig = new ArrayList<>(); mOutputSelectableConfig = new ArrayList<>(); mInactivatedDueToContextType = false; + mAutoActiveModeEnabled = true; } Integer mGroupId; @@ -270,6 +275,7 @@ public class LeAudioService extends ProfileService { List<BluetoothLeAudioCodecConfig> mInputSelectableConfig; List<BluetoothLeAudioCodecConfig> mOutputSelectableConfig; Boolean mInactivatedDueToContextType; + Boolean mAutoActiveModeEnabled; private Integer mActiveState; private Integer mAllowedSinkContexts; @@ -816,7 +822,7 @@ public class LeAudioService extends ProfileService { } @VisibleForTesting - static synchronized void setLeAudioService(LeAudioService instance) { + public static synchronized void setLeAudioService(LeAudioService instance) { Log.d(TAG, "setLeAudioService(): set to: " + instance); sLeAudioService = instance; } @@ -1650,6 +1656,31 @@ public class LeAudioService extends ProfileService { && groupId == mUnicastGroupIdDeactivatedForBroadcastTransition; } + /** Get local broadcast receiving devices */ + public Set<BluetoothDevice> getLocalBroadcastReceivers() { + if (mBroadcastDescriptors == null) { + Log.e(TAG, "getLocalBroadcastReceivers: Invalid Broadcast Descriptors"); + return Collections.emptySet(); + } + + BassClientService bassClientService = getBassClientService(); + if (bassClientService == null) { + Log.e(TAG, "getLocalBroadcastReceivers: Bass service not available"); + return Collections.emptySet(); + } + + Set<BluetoothDevice> deviceList = new HashSet<>(); + for (Map.Entry<Integer, LeAudioBroadcastDescriptor> entry : + mBroadcastDescriptors.entrySet()) { + if (!entry.getValue().mState.equals(LeAudioStackEvent.BROADCAST_STATE_STOPPED)) { + List<BluetoothDevice> devices = + bassClientService.getSyncedBroadcastSinks(entry.getKey()); + deviceList.addAll(devices); + } + } + return deviceList; + } + private boolean areBroadcastsAllStopped() { if (mBroadcastDescriptors == null) { Log.e(TAG, "areBroadcastsAllStopped: Invalid Broadcast Descriptors"); @@ -1987,12 +2018,37 @@ public class LeAudioService extends ProfileService { mExposedActiveDevice = device; } + boolean isAnyGroupDisabledFromAutoActiveMode() { + mGroupReadLock.lock(); + try { + for (Map.Entry<Integer, LeAudioGroupDescriptor> groupEntry : + mGroupDescriptorsView.entrySet()) { + LeAudioGroupDescriptor groupDescriptor = groupEntry.getValue(); + if (!groupDescriptor.mAutoActiveModeEnabled) { + Log.d( + TAG, + "isAnyGroupDisabledFromAutoActiveMode: disabled groupId " + + groupEntry.getKey()); + return true; + } + } + } finally { + mGroupReadLock.unlock(); + } + return false; + } + boolean isScannerNeeded() { if (mDeviceDescriptors.isEmpty() || !mBluetoothEnabled) { Log.d(TAG, "isScannerNeeded: false, mBluetoothEnabled: " + mBluetoothEnabled); return false; } + if (isAnyGroupDisabledFromAutoActiveMode()) { + Log.d(TAG, "isScannerNeeded true, some group has disabled Auto Active Mode"); + return true; + } + if (allLeAudioDevicesConnected()) { Log.d(TAG, "isScannerNeeded: all devices connected, scanner not needed"); return false; @@ -2027,66 +2083,92 @@ public class LeAudioService extends ProfileService { private class AudioServerScanCallback extends IScannerCallback.Stub { // See BluetoothLeScanner.BleScanCallbackWrapper.mScannerId - int mScannerId = 0; + static final int SCANNER_NOT_INITIALIZED = -2; + static final int SCANNER_INITIALIZING = -1; + int mScannerId = SCANNER_NOT_INITIALIZED; synchronized void startBackgroundScan() { - if (mScannerId != 0) { - Log.d(TAG, "Scanner is already registered with id " + mScannerId); + if (mScannerId >= 0) { + Log.i( + TAG, + "startBackgroundScan: Scanner is already registered with id " + mScannerId); return; } + + if (mScannerId == SCANNER_INITIALIZING) { + Log.i(TAG, "startBackgroundScan: Scanner is already initializing"); + return; + } + + mScannerId = SCANNER_INITIALIZING; + mAdapterService .getBluetoothScanController() - .getTransitionalScanHelper() .registerScannerInternal(this, getAttributionSource(), null); } synchronized void stopBackgroundScan() { - if (mScannerId == 0) { - Log.d(TAG, "Scanner is already unregistered"); + if (mScannerId < 0) { + Log.d(TAG, "Scanner is not running (mScannerId=" + mScannerId + ")"); return; } mAdapterService .getBluetoothScanController() - .getTransitionalScanHelper() .stopScanInternal(mScannerId); - mAdapterService - .getBluetoothScanController() - .getTransitionalScanHelper() - .unregisterScannerInternal(mScannerId); - mScannerId = 0; + mAdapterService.getBluetoothScanController().unregisterScannerInternal(mScannerId); + mScannerId = SCANNER_NOT_INITIALIZED; } @Override public synchronized void onScannerRegistered(int status, int scannerId) { + Log.d(TAG, "onScannerRegistered: status: " + status + ", id:" + scannerId); + if (status != 0) { + mScannerId = SCANNER_NOT_INITIALIZED; + return; + } mScannerId = scannerId; - /* Filter we are building here will not match to anything. - * Eventually we should be able to start scan from native when - * b/276350722 is done - */ ScanFilter filter = new ScanFilter.Builder() - .setServiceData(BluetoothUuid.LE_AUDIO, new byte[] {0x11}) + .setServiceData(BluetoothUuid.CAP, CAP_TARGETED_ANNOUNCEMENT_PAYLOAD) .build(); ScanSettings settings = new ScanSettings.Builder() .setLegacy(false) + .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .setScanMode(ScanSettings.SCAN_MODE_BALANCED) .setPhy(BluetoothDevice.PHY_LE_1M) .build(); mAdapterService .getBluetoothScanController() - .getTransitionalScanHelper() .startScanInternal(scannerId, settings, List.of(filter)); } - // Eventually we should be able to start scan from native when b/276350722 is done - // All the result returned here are ignored @Override - public void onScanResult(ScanResult scanResult) {} + public void onScanResult(ScanResult scanResult) { + Log.d(TAG, "onScanResult: " + scanResult.getDevice()); + BluetoothDevice device = scanResult.getDevice(); + if (device == null) { + return; + } + + int groupId = getGroupId(device); + LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId); + if (descriptor == null) { + return; + } + + if (!descriptor.mAutoActiveModeEnabled) { + Log.i(TAG, "onScanResult: GroupId: " + groupId + " is getting Active"); + descriptor.mAutoActiveModeEnabled = true; + if (!getConnectedPeerDevices(groupId).isEmpty()) { + setActiveDevice(device); + } + } + } @Override public void onBatchScanResults(List<ScanResult> batchResults) {} @@ -2385,6 +2467,25 @@ public class LeAudioService extends ProfileService { } } + private void clearAutoActiveModeToDefault() { + mGroupReadLock.lock(); + try { + for (Map.Entry<Integer, LeAudioGroupDescriptor> groupEntry : + mGroupDescriptorsView.entrySet()) { + LeAudioGroupDescriptor groupDescriptor = groupEntry.getValue(); + if (!groupDescriptor.mAutoActiveModeEnabled) { + Log.d( + TAG, + "mAutoActiveModeEnabled back to default for groupId: " + + groupEntry.getKey()); + groupDescriptor.mAutoActiveModeEnabled = true; + } + } + } finally { + mGroupReadLock.unlock(); + } + } + /** * Set the active device group. * @@ -2403,6 +2504,9 @@ public class LeAudioService extends ProfileService { groupId = descriptor.mGroupId; + /* User force device being active, clear the flag */ + clearAutoActiveModeToDefault(); + if (!isGroupAvailableForStream(groupId)) { Log.e( TAG, @@ -2429,11 +2533,10 @@ public class LeAudioService extends ProfileService { + ", mExposedActiveDevice: " + mExposedActiveDevice); - if (!Flags.leaudioBroadcastPrimaryGroupSelection() - && isBroadcastActive() - && currentlyActiveGroupId == LE_AUDIO_GROUP_ID_INVALID - && mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID) { - + /* Replace fallback unicast and monitoring input device if device is active local + * broadcaster. + */ + if (isAnyBroadcastInStreamingState()) { LeAudioGroupDescriptor fallbackGroupDescriptor = getGroupDescriptor(groupId); // If broadcast is ongoing and need to update unicast fallback active group @@ -3112,12 +3215,16 @@ public class LeAudioService extends ProfileService { } } + private boolean isAnyBroadcastInStreamingState() { + return mBroadcastDescriptors.values().stream() + .anyMatch(d -> d.mState.equals(LeAudioStackEvent.BROADCAST_STATE_STREAMING)); + } + void transitionFromBroadcastToUnicast() { if (mUnicastGroupIdDeactivatedForBroadcastTransition == LE_AUDIO_GROUP_ID_INVALID) { Log.d(TAG, "No deactivated group due for broadcast transmission"); // Notify audio manager - if (mBroadcastDescriptors.values().stream() - .noneMatch(d -> d.mState.equals(LeAudioStackEvent.BROADCAST_STATE_STREAMING))) { + if (!isAnyBroadcastInStreamingState()) { updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false); } return; @@ -3883,11 +3990,7 @@ public class LeAudioService extends ProfileService { } // Notify audio manager - if (mBroadcastDescriptors.values().stream() - .anyMatch( - d -> - d.mState.equals( - LeAudioStackEvent.BROADCAST_STATE_STREAMING))) { + if (isAnyBroadcastInStreamingState()) { if (!Objects.equals(device, mActiveBroadcastAudioDevice)) { updateBroadcastActiveDevice(device, mActiveBroadcastAudioDevice, true); } @@ -4026,11 +4129,6 @@ public class LeAudioService extends ProfileService { return; } - if (descriptor.mGroupId != LE_AUDIO_GROUP_ID_INVALID) { - /* In case device is still in the group, let's remove it */ - mNativeInterface.groupRemoveNode(descriptor.mGroupId, device); - } - descriptor.mGroupId = LE_AUDIO_GROUP_ID_INVALID; descriptor.mSinkAudioLocation = BluetoothLeAudio.AUDIO_LOCATION_INVALID; descriptor.mDirection = AUDIO_DIRECTION_NONE; @@ -4181,6 +4279,7 @@ public class LeAudioService extends ProfileService { if (getConnectedPeerDevices(groupId).isEmpty()) { descriptor.mIsConnected = false; + descriptor.mAutoActiveModeEnabled = true; descriptor.mAvailableContexts = Flags.leaudioUnicastNoAvailableContexts() ? null : 0; if (descriptor.isActive()) { /* Notify Native layer */ @@ -4523,6 +4622,91 @@ public class LeAudioService extends ProfileService { } /** + * Set auto active mode state. + * + * <p>Auto Active Mode by default is set to true and it means, that after ACL connection is + * created to LeAudio device which is part of the group, can be connected to Audio Framework + * (set as Active). + * + * <p>If Auto Active Mode is set to false, it means that after LeAudio device is connected for + * given group, the function isGroupAvailableForStream(groupId) will return false and + * ActiveDeviceManager will not make this group active. + * + * <p>This mode can change internally when two things happen: 1. LeAudioService detects Targeted + * Announcements from the device which belongs to the group. + * 2. @BluetoothLeAudio.setActiveDevice() is called with a device which belongs to the group. + * + * <p>Note: Auto Active Mode can be disabled only when all devices from the group are + * disconnected. + * + * @param groupId LeAudio group id which Auto Active Mode should be changed. + * @param enabled true when Auto Active Mode should be enabled (default value), false otherwise. + * @return true when Auto Active Mode is set, false otherwise + */ + public boolean setAutoActiveModeState(int groupId, boolean enabled) { + Log.d(TAG, "setAutoActiveModeState: groupId: " + groupId + " enabled: " + enabled); + + mGroupReadLock.lock(); + try { + LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId); + if (descriptor == null) { + Log.i( + TAG, + "setAutoActiveModeState: groupId: " + + groupId + + " is not known LeAudio group"); + return false; + } + + /* Disabling Auto Active Mode is allowed only when all the devices from the group + * are disconnected */ + if (!enabled && descriptor.mIsConnected) { + Log.i( + TAG, + "setAutoActiveModeState: GroupId: " + groupId + " is already connected "); + return false; + } + + Log.i(TAG, "setAutoActiveModeState: groupId: " + groupId + ", enabled: " + enabled); + descriptor.mAutoActiveModeEnabled = enabled; + return true; + } finally { + mGroupReadLock.unlock(); + } + } + + /** + * Is Auto Active Mode enabled + * + * @param groupId LeAudio group id which Auto Active Mode should be taken. + * @return true when Auto Active Mode is enabled, false otherwise + */ + public boolean isAutoActiveModeEnabled(int groupId) { + mGroupReadLock.lock(); + try { + LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId); + if (descriptor == null) { + Log.i( + TAG, + "isAutoActiveModeEnabled: groupId: " + + groupId + + " is not known LeAudio group"); + return false; + } + Log.i( + TAG, + "isAutoActiveModeEnabled: groupId: " + + groupId + + ", mAutoActiveModeEnabled: " + + descriptor.mAutoActiveModeEnabled); + return descriptor.mAutoActiveModeEnabled; + + } finally { + mGroupReadLock.unlock(); + } + } + + /** * Check if group is available for streaming. If there is no available context types then group * is not available for streaming. * @@ -4534,9 +4718,21 @@ public class LeAudioService extends ProfileService { try { LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId); if (descriptor == null) { - Log.e(TAG, "getGroupId: No valid descriptor for groupId: " + groupId); + Log.e( + TAG, + "isGroupAvailableForStream: No valid descriptor for groupId: " + groupId); return false; } + + if (!descriptor.mAutoActiveModeEnabled) { + Log.e( + TAG, + "isGroupAvailableForStream: Auto Active Mode is disabled for groupId: " + + groupId); + return false; + } + + Log.i(TAG, " descriptor.mAvailableContexts: " + descriptor.mAvailableContexts); return descriptor.mAvailableContexts != null && descriptor.mAvailableContexts != 0; } finally { mGroupReadLock.unlock(); @@ -6048,6 +6244,8 @@ public class LeAudioService extends ProfileService { sb, "mInactivatedDueToContextType: " + groupDescriptor.mInactivatedDueToContextType); + ProfileService.println( + sb, "mAutoActiveModeEnabled: " + groupDescriptor.mAutoActiveModeEnabled); for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> deviceEntry : mDeviceDescriptors.entrySet()) { diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java index ee743f3265..8d1fec5c72 100644 --- a/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java +++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java @@ -50,6 +50,7 @@ import android.os.Message; import android.util.Log; import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.flags.Flags; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -276,7 +277,12 @@ final class LeAudioStateMachine extends StateMachine { switch (message.what) { case CONNECT: - deferMessage(message); + if (Flags.leaudioSmIgnoreConnectEventsInConnectingState() + && !hasDeferredMessages(DISCONNECT)) { + Log.w(TAG, "Connecting: CONNECT ignored: " + mDevice); + } else { + deferMessage(message); + } break; case CONNECT_TIMEOUT: Log.w(TAG, "Connecting connection timeout: " + mDevice); diff --git a/android/app/src/com/android/bluetooth/le_scan/AppScanStats.java b/android/app/src/com/android/bluetooth/le_scan/AppScanStats.java index 0780d54189..dc7284b94b 100644 --- a/android/app/src/com/android/bluetooth/le_scan/AppScanStats.java +++ b/android/app/src/com/android/bluetooth/le_scan/AppScanStats.java @@ -65,8 +65,8 @@ public class AppScanStats { // ScannerMap here is needed to grab Apps ScannerMap mScannerMap; - // TransitionalScanHelper is needed to add scan event protos to be dumped later - final TransitionalScanHelper mScanHelper; + // ScanController is needed to add scan event protos to be dumped later + final ScanController mScanController; // Battery stats is used to keep track of scans and result stats BatteryStatsManager mBatteryStatsManager; @@ -173,13 +173,13 @@ public class AppScanStats { WorkSource source, ScannerMap map, AdapterService adapterService, - TransitionalScanHelper scanHelper, + ScanController scanController, TimeProvider timeProvider) { mAdapterService = requireNonNull(adapterService); mTimeProvider = requireNonNull(timeProvider); mAppName = name; mScannerMap = map; - mScanHelper = scanHelper; + mScanController = scanController; mBatteryStatsManager = adapterService.getSystemService(BatteryStatsManager.class); if (source == null) { @@ -310,7 +310,7 @@ public class AppScanStats { .setEventTimeMillis(System.currentTimeMillis()) .setInitiator(truncateAppName(mAppName)) .build(); - mScanHelper.addScanEvent(scanEvent); + mScanController.addScanEvent(scanEvent); if (!isScanning()) { mScanStartTime = startTime; @@ -362,7 +362,7 @@ public class AppScanStats { .setInitiator(truncateAppName(mAppName)) .setNumberResults(scan.results) .build(); - mScanHelper.addScanEvent(scanEvent); + mScanController.addScanEvent(scanEvent); mTotalScanTime += scanDuration; long activeDuration = scanDuration - scan.suspendDuration; diff --git a/android/app/src/com/android/bluetooth/le_scan/ScanController.java b/android/app/src/com/android/bluetooth/le_scan/ScanController.java index e992a8da16..7f39fdaaf1 100644 --- a/android/app/src/com/android/bluetooth/le_scan/ScanController.java +++ b/android/app/src/com/android/bluetooth/le_scan/ScanController.java @@ -16,48 +16,90 @@ package com.android.bluetooth.le_scan; +import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; +import static android.Manifest.permission.BLUETOOTH_SCAN; +import static android.Manifest.permission.UPDATE_DEVICE_STATS; + +import static com.android.bluetooth.Utils.checkCallerTargetSdk; + import static java.util.Objects.requireNonNull; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; +import android.app.AppOpsManager; import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothUtils; import android.bluetooth.IBluetoothScan; +import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.IPeriodicAdvertisingCallback; import android.bluetooth.le.IScannerCallback; +import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanRecord; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; +import android.companion.AssociationInfo; +import android.companion.CompanionDeviceManager; import android.content.AttributionSource; +import android.content.Intent; +import android.net.MacAddress; +import android.os.Binder; +import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; import android.os.WorkSource; +import android.provider.DeviceConfig; import android.text.format.DateUtils; import android.util.Log; import com.android.bluetooth.BluetoothMetricsProto; +import com.android.bluetooth.R; +import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.BluetoothAdapterProxy; import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.util.NumberUtils; +import com.android.internal.annotations.VisibleForTesting; import libcore.util.HexEncoding; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.stream.Collectors; public class ScanController { private static final String TAG = ScanController.class.getSimpleName(); - public final TransitionalScanHelper mTransitionalScanHelper; - public final HandlerThread mScanThread; + // Batch scan related constants. + private static final int TRUNCATED_RESULT_SIZE = 11; - private final BluetoothScanBinder mBinder; + /** The default floor value for LE batch scan report delays greater than 0 */ + static final long DEFAULT_REPORT_DELAY_FLOOR = 5000L; - private boolean mIsAvailable; + private static final int NUM_SCAN_EVENTS_KEPT = 20; - private volatile boolean mTestModeEnabled = false; - private final Looper mMainLooper; - private Handler mTestModeHandler; - private final Object mTestModeLock = new Object(); + // onFoundLost related constants + @VisibleForTesting static final int ADVT_STATE_ONFOUND = 0; + private static final int ADVT_STATE_ONLOST = 1; + + private static final int ET_LEGACY_MASK = 0x10; /** Example raw beacons captured from a Blue Charm BC011 */ private static final String[] TEST_MODE_BEACONS = @@ -69,15 +111,103 @@ public class ScanController { "0201061AFF4C000215426C7565436861726D426561636F6E730EFE1355C509168020691E0EFE13551109426C7565436861726D5F31363936383500000000", }; + static class PendingIntentInfo { + public PendingIntent intent; + public ScanSettings settings; + public List<ScanFilter> filters; + public String callingPackage; + public int callingUid; + + @Override + public boolean equals(Object other) { + if (!(other instanceof PendingIntentInfo)) { + return false; + } + return intent.equals(((PendingIntentInfo) other).intent); + } + + @Override + public int hashCode() { + return intent == null ? 0 : intent.hashCode(); + } + } + + private final PendingIntent.CancelListener mScanIntentCancelListener = + new PendingIntent.CancelListener() { + public void onCanceled(PendingIntent intent) { + Log.d(TAG, "scanning PendingIntent canceled"); + stopScanInternal(intent); + } + }; + + private final AdapterService mAdapterService; + + private final HashMap<Integer, Integer> mFilterIndexToMsftAdvMonitorMap = new HashMap<>(); + private final String mExposureNotificationPackage; + + private final AppOpsManager mAppOps; + private final CompanionDeviceManager mCompanionManager; + private final PeriodicScanManager mPeriodicScanManager; + private final ScanManager mScanManager; + + public final HandlerThread mScanThread; + + private final BluetoothScanBinder mBinder; + + /** Internal list of scan events to use with the proto */ + private final ArrayDeque<BluetoothMetricsProto.ScanEvent> mScanEvents = + new ArrayDeque<>(NUM_SCAN_EVENTS_KEPT); + + private final Predicate<ScanResult> mLocationDenylistPredicate; + + private ScannerMap mScannerMap = new ScannerMap(); + + private boolean mIsAvailable; + + private volatile boolean mTestModeEnabled = false; + private final Looper mMainLooper; + private Handler mTestModeHandler; + private final Object mTestModeLock = new Object(); + public ScanController(AdapterService adapterService) { - mTransitionalScanHelper = - new TransitionalScanHelper(requireNonNull(adapterService), () -> mTestModeEnabled); + mAdapterService = requireNonNull(adapterService); + mExposureNotificationPackage = + mAdapterService.getString(R.string.exposure_notification_package); + mLocationDenylistPredicate = + (scanResult) -> { + final MacAddress parsedAddress = + MacAddress.fromString(scanResult.getDevice().getAddress()); + if (mAdapterService + .getLocationDenylistMac() + .test(parsedAddress.toByteArray())) { + Log.v(TAG, "Skipping device matching denylist: " + scanResult.getDevice()); + return true; + } + final ScanRecord scanRecord = scanResult.getScanRecord(); + if (scanRecord.matchesAnyField( + mAdapterService.getLocationDenylistAdvertisingData())) { + Log.v(TAG, "Skipping data matching denylist: " + scanRecord); + return true; + } + return false; + }; mMainLooper = adapterService.getMainLooper(); mBinder = new BluetoothScanBinder(this); mIsAvailable = true; mScanThread = new HandlerThread("BluetoothScanManager"); mScanThread.start(); - mTransitionalScanHelper.start(mScanThread.getLooper()); + mAppOps = mAdapterService.getSystemService(AppOpsManager.class); + mCompanionManager = mAdapterService.getSystemService(CompanionDeviceManager.class); + mScanManager = + ScanObjectsFactory.getInstance() + .createScanManager( + mAdapterService, + this, + BluetoothAdapterProxy.getInstance(), + mScanThread.getLooper()); + + mPeriodicScanManager = + ScanObjectsFactory.getInstance().createPeriodicScanManager(mAdapterService); } public void stop() { @@ -85,17 +215,23 @@ public class ScanController { mIsAvailable = false; mBinder.clearScanController(); mScanThread.quitSafely(); - mTransitionalScanHelper.stop(); - mTransitionalScanHelper.cleanup(); + mScannerMap.clear(); + mScanManager.cleanup(); + mPeriodicScanManager.cleanup(); } - /** Notify Scan manager of bluetooth profile connection state changes */ - public void notifyProfileConnectionStateChange(int profile, int fromState, int toState) { - mTransitionalScanHelper.notifyProfileConnectionStateChange(profile, fromState, toState); + ScannerMap getScannerMap() { + return mScannerMap; } - public TransitionalScanHelper getTransitionalScanHelper() { - return mTransitionalScanHelper; + @VisibleForTesting + void setScannerMap(ScannerMap scannerMap) { + mScannerMap = scannerMap; + } + + /** Notify Scan manager of bluetooth profile connection state changes */ + public void notifyProfileConnectionStateChange(int profile, int fromState, int toState) { + mScanManager.handleBluetoothProfileConnectionStateChanged(profile, fromState, toState); } public IBinder getBinder() { @@ -113,7 +249,7 @@ public class ScanController { return; } for (String test : TEST_MODE_BEACONS) { - mTransitionalScanHelper.onScanResultInternal( + onScanResultInternal( 0x1b, 0x1, "DD:34:02:05:5C:4D", @@ -141,18 +277,1456 @@ public class ScanController { } } + /************************************************************************** + * Callback functions - CLIENT + *************************************************************************/ + + // EN format defined here: + // https://blog.google/documents/70/Exposure_Notification_-_Bluetooth_Specification_v1.2.2.pdf + private static final byte[] EXPOSURE_NOTIFICATION_FLAGS_PREAMBLE = + new byte[] { + // size 2, flag field, flags byte (value is not important) + (byte) 0x02, (byte) 0x01 + }; + + private static final int EXPOSURE_NOTIFICATION_FLAGS_LENGTH = 0x2 + 1; + private static final byte[] EXPOSURE_NOTIFICATION_PAYLOAD_PREAMBLE = + new byte[] { + // size 3, complete 16 bit UUID, EN UUID + (byte) 0x03, (byte) 0x03, (byte) 0x6F, (byte) 0xFD, + // size 23, data for 16 bit UUID, EN UUID + (byte) 0x17, (byte) 0x16, (byte) 0x6F, (byte) 0xFD, + // ...payload + }; + private static final int EXPOSURE_NOTIFICATION_PAYLOAD_LENGTH = 0x03 + 0x17 + 2; + + private static boolean arrayStartsWith(byte[] array, byte[] prefix) { + if (array.length < prefix.length) { + return false; + } + for (int i = 0; i < prefix.length; i++) { + if (prefix[i] != array[i]) { + return false; + } + } + return true; + } + + private ScanResult getSanitizedExposureNotification(ScanResult result) { + ScanRecord record = result.getScanRecord(); + // Remove the flags part of the payload, if present + if (record.getBytes().length > EXPOSURE_NOTIFICATION_FLAGS_LENGTH + && arrayStartsWith(record.getBytes(), EXPOSURE_NOTIFICATION_FLAGS_PREAMBLE)) { + record = + ScanRecord.parseFromBytes( + Arrays.copyOfRange( + record.getBytes(), + EXPOSURE_NOTIFICATION_FLAGS_LENGTH, + record.getBytes().length)); + } + + if (record.getBytes().length != EXPOSURE_NOTIFICATION_PAYLOAD_LENGTH) { + return null; + } + if (!arrayStartsWith(record.getBytes(), EXPOSURE_NOTIFICATION_PAYLOAD_PREAMBLE)) { + return null; + } + + return new ScanResult(null, 0, 0, 0, 0, 0, result.getRssi(), 0, record, 0); + } + + /** Callback method for a scan result. */ + void onScanResult( + int eventType, + int addressType, + String address, + int primaryPhy, + int secondaryPhy, + int advertisingSid, + int txPower, + int rssi, + int periodicAdvInt, + byte[] advData, + String originalAddress) { + // When in testing mode, ignore all real-world events + if (mTestModeEnabled) return; + + AppScanStats.recordScanRadioResultCount(); + onScanResultInternal( + eventType, + addressType, + address, + primaryPhy, + secondaryPhy, + advertisingSid, + txPower, + rssi, + periodicAdvInt, + advData, + originalAddress); + } + + private void onScanResultInternal( + int eventType, + int addressType, + String address, + int primaryPhy, + int secondaryPhy, + int advertisingSid, + int txPower, + int rssi, + int periodicAdvInt, + byte[] advData, + String originalAddress) { + Log.v( + TAG, + "onScanResult() - eventType=0x" + + Integer.toHexString(eventType) + + ", addressType=" + + addressType + + ", address=" + + BluetoothUtils.toAnonymizedAddress(address) + + ", primaryPhy=" + + primaryPhy + + ", secondaryPhy=" + + secondaryPhy + + ", advertisingSid=0x" + + Integer.toHexString(advertisingSid) + + ", txPower=" + + txPower + + ", rssi=" + + rssi + + ", periodicAdvInt=0x" + + Integer.toHexString(periodicAdvInt) + + ", originalAddress=" + + originalAddress); + + String identityAddress = mAdapterService.getIdentityAddress(address); + if (!address.equals(identityAddress)) { + Log.v( + TAG, + "found identityAddress of " + + address + + ", replace originalAddress as " + + identityAddress); + originalAddress = identityAddress; + } + + byte[] legacyAdvData = Arrays.copyOfRange(advData, 0, 62); + + for (ScanClient client : mScanManager.getRegularScanQueue()) { + ScannerMap.ScannerApp app = mScannerMap.getById(client.scannerId); + if (app == null) { + Log.v(TAG, "App is null; skip."); + continue; + } + + BluetoothDevice device = + BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(address, addressType); + + ScanSettings settings = client.settings; + byte[] scanRecordData; + // This is for compatibility with applications that assume fixed size scan data. + if (settings.getLegacy()) { + if ((eventType & ET_LEGACY_MASK) == 0) { + // If this is legacy scan, but nonlegacy result - skip. + Log.v(TAG, "Legacy scan, non legacy result; skip."); + continue; + } else { + // Some apps are used to fixed-size advertise data. + scanRecordData = legacyAdvData; + } + } else { + scanRecordData = advData; + } + + ScanRecord scanRecord = ScanRecord.parseFromBytes(scanRecordData); + ScanResult result = + new ScanResult( + device, + eventType, + primaryPhy, + secondaryPhy, + advertisingSid, + txPower, + rssi, + periodicAdvInt, + scanRecord, + SystemClock.elapsedRealtimeNanos()); + + if (client.hasDisavowedLocation) { + if (mLocationDenylistPredicate.test(result)) { + Log.i(TAG, "Skipping client for location deny list"); + continue; + } + } + + boolean hasPermission = hasScanResultPermission(client); + if (!hasPermission) { + for (String associatedDevice : client.associatedDevices) { + if (associatedDevice.equalsIgnoreCase(address)) { + hasPermission = true; + break; + } + } + } + if (!hasPermission && client.eligibleForSanitizedExposureNotification) { + ScanResult sanitized = getSanitizedExposureNotification(result); + if (sanitized != null) { + hasPermission = true; + result = sanitized; + } + } + boolean matchResult = matchesFilters(client, result, originalAddress); + if (!hasPermission || !matchResult) { + Log.v( + TAG, + "Skipping client: permission=" + hasPermission + " matches=" + matchResult); + continue; + } + + int callbackType = settings.getCallbackType(); + if (!(callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES + || callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH)) { + Log.v(TAG, "Skipping client: CALLBACK_TYPE_ALL_MATCHES"); + continue; + } + + try { + app.mAppScanStats.addResult(client.scannerId); + if (app.mCallback != null) { + app.mCallback.onScanResult(result); + } else { + Log.v(TAG, "Callback is null, sending scan results by pendingIntent"); + // Send the PendingIntent + ArrayList<ScanResult> results = new ArrayList<>(); + results.add(result); + sendResultsByPendingIntent( + app.mInfo, results, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); + } + } catch (RemoteException | PendingIntent.CanceledException e) { + Log.e(TAG, "Exception: " + e); + handleDeadScanClient(client); + } + } + } + + private void sendResultByPendingIntent( + PendingIntentInfo pii, ScanResult result, int callbackType, ScanClient client) { + ArrayList<ScanResult> results = new ArrayList<>(); + results.add(result); + try { + sendResultsByPendingIntent(pii, results, callbackType); + } catch (PendingIntent.CanceledException e) { + final long token = Binder.clearCallingIdentity(); + try { + stopScanInternal(client.scannerId); + unregisterScannerInternal(client.scannerId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + @SuppressWarnings("NonApiType") + private void sendResultsByPendingIntent( + PendingIntentInfo pii, ArrayList<ScanResult> results, int callbackType) + throws PendingIntent.CanceledException { + Intent extrasIntent = new Intent(); + extrasIntent.putParcelableArrayListExtra( + BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT, results); + extrasIntent.putExtra(BluetoothLeScanner.EXTRA_CALLBACK_TYPE, callbackType); + pii.intent.send(mAdapterService, 0, extrasIntent); + } + + private void sendErrorByPendingIntent(PendingIntentInfo pii, int errorCode) + throws PendingIntent.CanceledException { + Intent extrasIntent = new Intent(); + extrasIntent.putExtra(BluetoothLeScanner.EXTRA_ERROR_CODE, errorCode); + pii.intent.send(mAdapterService, 0, extrasIntent); + } + + /** Callback method for scanner registration. */ + void onScannerRegistered(int status, int scannerId, long uuidLsb, long uuidMsb) + throws RemoteException { + UUID uuid = new UUID(uuidMsb, uuidLsb); + Log.d( + TAG, + "onScannerRegistered() - UUID=" + + uuid + + ", scannerId=" + + scannerId + + ", status=" + + status); + + // First check the callback map + ScannerMap.ScannerApp cbApp = mScannerMap.getByUuid(uuid); + if (cbApp != null) { + if (status == 0) { + cbApp.mId = scannerId; + // If app is callback based, setup a death recipient. App will initiate the start. + // Otherwise, if PendingIntent based, start the scan directly. + if (cbApp.mCallback != null) { + cbApp.linkToDeath(new ScannerDeathRecipient(scannerId, cbApp.mName)); + } else { + continuePiStartScan(scannerId, cbApp); + } + } else { + mScannerMap.remove(scannerId); + } + if (cbApp.mCallback != null) { + cbApp.mCallback.onScannerRegistered(status, scannerId); + } + } + } + + /** Determines if the given scan client has the appropriate permissions to receive callbacks. */ + private boolean hasScanResultPermission(final ScanClient client) { + if (client.hasNetworkSettingsPermission + || client.hasNetworkSetupWizardPermission + || client.hasScanWithoutLocationPermission) { + return true; + } + if (client.hasDisavowedLocation) { + return true; + } + return client.hasLocationPermission + && !Utils.blockedByLocationOff(mAdapterService, client.userHandle); + } + + // Check if a scan record matches a specific filters. + private boolean matchesFilters(ScanClient client, ScanResult scanResult) { + return matchesFilters(client, scanResult, null); + } + + // Check if a scan record matches a specific filters or original address + private boolean matchesFilters( + ScanClient client, ScanResult scanResult, String originalAddress) { + if (client.filters == null || client.filters.isEmpty()) { + // TODO: Do we really wanna return true here? + return true; + } + for (ScanFilter filter : client.filters) { + // Need to check the filter matches, and the original address without changing the API + if (filter.matches(scanResult)) { + return true; + } + if (originalAddress != null + && originalAddress.equalsIgnoreCase(filter.getDeviceAddress())) { + return true; + } + } + return false; + } + + private void handleDeadScanClient(ScanClient client) { + if (client.appDied) { + Log.w(TAG, "Already dead client " + client.scannerId); + return; + } + client.appDied = true; + if (client.stats != null) { + client.stats.isAppDead = true; + } + stopScanInternal(client.scannerId); + } + + /** Callback method for scan filter enablement/disablement. */ + void onScanFilterEnableDisabled(int action, int status, int clientIf) { + Log.d( + TAG, + "onScanFilterEnableDisabled() - clientIf=" + + clientIf + + ", status=" + + status + + ", action=" + + action); + mScanManager.callbackDone(clientIf, status); + } + + /** Callback method for configuration of scan filter params. */ + void onScanFilterParamsConfigured(int action, int status, int clientIf, int availableSpace) { + Log.d( + TAG, + "onScanFilterParamsConfigured() - clientIf=" + + clientIf + + ", status=" + + status + + ", action=" + + action + + ", availableSpace=" + + availableSpace); + mScanManager.callbackDone(clientIf, status); + } + + /** Callback method for configuration of scan filter. */ + void onScanFilterConfig( + int action, int status, int clientIf, int filterType, int availableSpace) { + Log.d( + TAG, + "onScanFilterConfig() - clientIf=" + + clientIf + + ", action = " + + action + + " status = " + + status + + ", filterType=" + + filterType + + ", availableSpace=" + + availableSpace); + + mScanManager.callbackDone(clientIf, status); + } + + /** Callback method for configuration of batch scan storage. */ + void onBatchScanStorageConfigured(int status, int clientIf) { + Log.d(TAG, "onBatchScanStorageConfigured() - clientIf=" + clientIf + ", status=" + status); + mScanManager.callbackDone(clientIf, status); + } + + /** Callback method for start/stop of batch scan. */ + // TODO: split into two different callbacks : onBatchScanStarted and onBatchScanStopped. + void onBatchScanStartStopped(int startStopAction, int status, int clientIf) { + Log.d( + TAG, + "onBatchScanStartStopped() - clientIf=" + + clientIf + + ", status=" + + status + + ", startStopAction=" + + startStopAction); + mScanManager.callbackDone(clientIf, status); + } + + ScanClient findBatchScanClientById(int scannerId) { + for (ScanClient client : mScanManager.getBatchScanQueue()) { + if (client.scannerId == scannerId) { + return client; + } + } + return null; + } + + /** Callback method for batch scan reports */ + void onBatchScanReports( + int status, int scannerId, int reportType, int numRecords, byte[] recordData) + throws RemoteException { + // When in testing mode, ignore all real-world events + if (mTestModeEnabled) return; + + AppScanStats.recordBatchScanRadioResultCount(numRecords); + onBatchScanReportsInternal(status, scannerId, reportType, numRecords, recordData); + } + + @VisibleForTesting + void onBatchScanReportsInternal( + int status, int scannerId, int reportType, int numRecords, byte[] recordData) + throws RemoteException { + Log.d( + TAG, + "onBatchScanReports() - scannerId=" + + scannerId + + ", status=" + + status + + ", reportType=" + + reportType + + ", numRecords=" + + numRecords); + + Set<ScanResult> results = parseBatchScanResults(numRecords, reportType, recordData); + if (reportType == ScanManager.SCAN_RESULT_TYPE_TRUNCATED) { + // We only support single client for truncated mode. + ScannerMap.ScannerApp app = mScannerMap.getById(scannerId); + if (app == null) { + return; + } + + ScanClient client = findBatchScanClientById(scannerId); + if (client == null) { + return; + } + + ArrayList<ScanResult> permittedResults; + if (hasScanResultPermission(client)) { + permittedResults = new ArrayList<ScanResult>(results); + } else { + permittedResults = new ArrayList<ScanResult>(); + for (ScanResult scanResult : results) { + for (String associatedDevice : client.associatedDevices) { + if (associatedDevice.equalsIgnoreCase( + scanResult.getDevice().getAddress())) { + permittedResults.add(scanResult); + } + } + } + } + + if (client.hasDisavowedLocation) { + permittedResults.removeIf(mLocationDenylistPredicate); + } + if (permittedResults.isEmpty()) { + mScanManager.callbackDone(scannerId, status); + return; + } + + if (app.mCallback != null) { + app.mCallback.onBatchScanResults(permittedResults); + mScanManager.batchScanResultDelivered(); + } else { + // PendingIntent based + try { + sendResultsByPendingIntent( + app.mInfo, permittedResults, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); + } catch (PendingIntent.CanceledException e) { + Log.d(TAG, "Exception while sending result", e); + } + } + } else { + for (ScanClient client : mScanManager.getFullBatchScanQueue()) { + // Deliver results for each client. + deliverBatchScan(client, results); + } + } + mScanManager.callbackDone(scannerId, status); + } + + @SuppressWarnings("NonApiType") + private void sendBatchScanResults( + ScannerMap.ScannerApp app, ScanClient client, ArrayList<ScanResult> results) { + if (results.isEmpty()) { + return; + } + try { + if (app.mCallback != null) { + if (mScanManager.isAutoBatchScanClientEnabled(client)) { + Log.d(TAG, "sendBatchScanResults() to onScanResult()" + client); + for (ScanResult result : results) { + app.mAppScanStats.addResult(client.scannerId); + app.mCallback.onScanResult(result); + } + } else { + Log.d(TAG, "sendBatchScanResults() to onBatchScanResults()" + client); + app.mCallback.onBatchScanResults(results); + } + } else { + sendResultsByPendingIntent( + app.mInfo, results, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); + } + } catch (RemoteException | PendingIntent.CanceledException e) { + Log.e(TAG, "Exception: " + e); + handleDeadScanClient(client); + } + mScanManager.batchScanResultDelivered(); + } + + // Check and deliver scan results for different scan clients. + private void deliverBatchScan(ScanClient client, Set<ScanResult> allResults) + throws RemoteException { + ScannerMap.ScannerApp app = mScannerMap.getById(client.scannerId); + if (app == null) { + return; + } + + ArrayList<ScanResult> permittedResults; + if (hasScanResultPermission(client)) { + permittedResults = new ArrayList<ScanResult>(allResults); + } else { + permittedResults = new ArrayList<ScanResult>(); + for (ScanResult scanResult : allResults) { + for (String associatedDevice : client.associatedDevices) { + if (associatedDevice.equalsIgnoreCase(scanResult.getDevice().getAddress())) { + permittedResults.add(scanResult); + } + } + } + } + + if (client.filters == null || client.filters.isEmpty()) { + sendBatchScanResults(app, client, permittedResults); + return; + } + // Reconstruct the scan results. + ArrayList<ScanResult> results = new ArrayList<ScanResult>(); + for (ScanResult scanResult : permittedResults) { + if (matchesFilters(client, scanResult)) { + results.add(scanResult); + } + } + sendBatchScanResults(app, client, results); + } + + private Set<ScanResult> parseBatchScanResults( + int numRecords, int reportType, byte[] batchRecord) { + if (numRecords == 0) { + return Collections.emptySet(); + } + Log.d(TAG, "current time is " + SystemClock.elapsedRealtimeNanos()); + if (reportType == ScanManager.SCAN_RESULT_TYPE_TRUNCATED) { + return parseTruncatedResults(numRecords, batchRecord); + } else { + return parseFullResults(numRecords, batchRecord); + } + } + + private Set<ScanResult> parseTruncatedResults(int numRecords, byte[] batchRecord) { + Log.d(TAG, "batch record " + Arrays.toString(batchRecord)); + Set<ScanResult> results = new HashSet<ScanResult>(numRecords); + long now = SystemClock.elapsedRealtimeNanos(); + for (int i = 0; i < numRecords; ++i) { + byte[] record = + extractBytes(batchRecord, i * TRUNCATED_RESULT_SIZE, TRUNCATED_RESULT_SIZE); + byte[] address = extractBytes(record, 0, 6); + reverse(address); + BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); + int rssi = record[8]; + long timestampNanos = now - parseTimestampNanos(extractBytes(record, 9, 2)); + results.add( + new ScanResult( + device, ScanRecord.parseFromBytes(new byte[0]), rssi, timestampNanos)); + } + return results; + } + + @VisibleForTesting + long parseTimestampNanos(byte[] data) { + long timestampUnit = NumberUtils.littleEndianByteArrayToInt(data); + // Timestamp is in every 50 ms. + return TimeUnit.MILLISECONDS.toNanos(timestampUnit * 50); + } + + private Set<ScanResult> parseFullResults(int numRecords, byte[] batchRecord) { + Log.d(TAG, "Batch record : " + Arrays.toString(batchRecord)); + Set<ScanResult> results = new HashSet<ScanResult>(numRecords); + int position = 0; + long now = SystemClock.elapsedRealtimeNanos(); + while (position < batchRecord.length) { + byte[] address = extractBytes(batchRecord, position, 6); + // TODO: remove temp hack. + reverse(address); + BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); + position += 6; + // Skip address type. + position++; + // Skip tx power level. + position++; + int rssi = batchRecord[position++]; + long timestampNanos = now - parseTimestampNanos(extractBytes(batchRecord, position, 2)); + position += 2; + + // Combine advertise packet and scan response packet. + int advertisePacketLen = batchRecord[position++]; + byte[] advertiseBytes = extractBytes(batchRecord, position, advertisePacketLen); + position += advertisePacketLen; + int scanResponsePacketLen = batchRecord[position++]; + byte[] scanResponseBytes = extractBytes(batchRecord, position, scanResponsePacketLen); + position += scanResponsePacketLen; + byte[] scanRecord = new byte[advertisePacketLen + scanResponsePacketLen]; + System.arraycopy(advertiseBytes, 0, scanRecord, 0, advertisePacketLen); + System.arraycopy( + scanResponseBytes, 0, scanRecord, advertisePacketLen, scanResponsePacketLen); + Log.d(TAG, "ScanRecord : " + Arrays.toString(scanRecord)); + results.add( + new ScanResult( + device, ScanRecord.parseFromBytes(scanRecord), rssi, timestampNanos)); + } + return results; + } + + // Reverse byte array. + private void reverse(byte[] address) { + int len = address.length; + for (int i = 0; i < len / 2; ++i) { + byte b = address[i]; + address[i] = address[len - 1 - i]; + address[len - 1 - i] = b; + } + } + + // Helper method to extract bytes from byte array. + private static byte[] extractBytes(byte[] scanRecord, int start, int length) { + byte[] bytes = new byte[length]; + System.arraycopy(scanRecord, start, bytes, 0, length); + return bytes; + } + + void onBatchScanThresholdCrossed(int clientIf) { + Log.d(TAG, "onBatchScanThresholdCrossed() - clientIf=" + clientIf); + flushPendingBatchResultsInternal(clientIf); + } + + AdvtFilterOnFoundOnLostInfo createOnTrackAdvFoundLostObject( + int clientIf, + int advPktLen, + byte[] advPkt, + int scanRspLen, + byte[] scanRsp, + int filtIndex, + int advState, + int advInfoPresent, + String address, + int addrType, + int txPower, + int rssiValue, + int timeStamp) { + + return new AdvtFilterOnFoundOnLostInfo( + clientIf, + advPktLen, + advPkt, + scanRspLen, + scanRsp, + filtIndex, + advState, + advInfoPresent, + address, + addrType, + txPower, + rssiValue, + timeStamp); + } + + void onTrackAdvFoundLost(AdvtFilterOnFoundOnLostInfo trackingInfo) throws RemoteException { + Log.d( + TAG, + "onTrackAdvFoundLost() - scannerId= " + + trackingInfo.getClientIf() + + " address = " + + trackingInfo.getAddress() + + " addressType = " + + trackingInfo.getAddressType() + + " adv_state = " + + trackingInfo.getAdvState()); + + ScannerMap.ScannerApp app = mScannerMap.getById(trackingInfo.getClientIf()); + if (app == null) { + Log.e(TAG, "app is null"); + return; + } + + BluetoothDevice device = + BluetoothAdapter.getDefaultAdapter() + .getRemoteLeDevice( + trackingInfo.getAddress(), trackingInfo.getAddressType()); + int advertiserState = trackingInfo.getAdvState(); + ScanResult result = + new ScanResult( + device, + ScanRecord.parseFromBytes(trackingInfo.getResult()), + trackingInfo.getRSSIValue(), + SystemClock.elapsedRealtimeNanos()); + + for (ScanClient client : mScanManager.getRegularScanQueue()) { + if (client.scannerId == trackingInfo.getClientIf()) { + ScanSettings settings = client.settings; + if ((advertiserState == ADVT_STATE_ONFOUND) + && ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) + != 0)) { + if (app.mCallback != null) { + app.mCallback.onFoundOrLost(true, result); + } else { + sendResultByPendingIntent( + app.mInfo, result, ScanSettings.CALLBACK_TYPE_FIRST_MATCH, client); + } + } else if ((advertiserState == ADVT_STATE_ONLOST) + && ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) + != 0)) { + if (app.mCallback != null) { + app.mCallback.onFoundOrLost(false, result); + } else { + sendResultByPendingIntent( + app.mInfo, result, ScanSettings.CALLBACK_TYPE_MATCH_LOST, client); + } + } else { + Log.d( + TAG, + "Not reporting onlost/onfound : " + + advertiserState + + " scannerId = " + + client.scannerId + + " callbackType " + + settings.getCallbackType()); + } + } + } + } + + /** Callback method for configuration of scan parameters. */ + void onScanParamSetupCompleted(int status, int scannerId) { + Log.d(TAG, "onScanParamSetupCompleted() - scannerId=" + scannerId + ", status=" + status); + ScannerMap.ScannerApp app = mScannerMap.getById(scannerId); + if (app == null || app.mCallback == null) { + Log.e(TAG, "Advertise app or callback is null"); + return; + } + } + + // callback from ScanManager for dispatch of errors apps. + void onScanManagerErrorCallback(int scannerId, int errorCode) throws RemoteException { + ScannerMap.ScannerApp app = mScannerMap.getById(scannerId); + if (app == null) { + Log.e(TAG, "App null"); + return; + } + if (app.mCallback != null) { + app.mCallback.onScanManagerErrorCallback(errorCode); + } else { + try { + sendErrorByPendingIntent(app.mInfo, errorCode); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Error sending error code via PendingIntent:" + e); + } + } + } + + int msftMonitorHandleFromFilterIndex(int filterIndex) { + if (!mFilterIndexToMsftAdvMonitorMap.containsKey(filterIndex)) { + Log.e(TAG, "Monitor with filterIndex'" + filterIndex + "' does not exist"); + return -1; + } + return mFilterIndexToMsftAdvMonitorMap.get(filterIndex); + } + + void onMsftAdvMonitorAdd(int filterIndex, int monitorHandle, int status) { + if (status != 0) { + Log.e( + TAG, + "Error adding advertisement monitor with filter index '" + filterIndex + "'"); + return; + } + if (mFilterIndexToMsftAdvMonitorMap.containsKey(filterIndex)) { + Log.e(TAG, "Monitor with filterIndex'" + filterIndex + "' already added"); + return; + } + mFilterIndexToMsftAdvMonitorMap.put(filterIndex, monitorHandle); + } + + void onMsftAdvMonitorRemove(int filterIndex, int status) { + if (status != 0) { + Log.e( + TAG, + "Error removing advertisement monitor with filter index '" + filterIndex + "'"); + } + if (!mFilterIndexToMsftAdvMonitorMap.containsKey(filterIndex)) { + Log.e(TAG, "Monitor with filterIndex'" + filterIndex + "' does not exist"); + return; + } + mFilterIndexToMsftAdvMonitorMap.remove(filterIndex); + } + + void onMsftAdvMonitorEnable(int status) { + if (status != 0) { + Log.e(TAG, "Error enabling advertisement monitor"); + } + } + + /************************************************************************** + * GATT Service functions - Shared CLIENT/SERVER + *************************************************************************/ + + @RequiresPermission(BLUETOOTH_SCAN) + @VisibleForTesting + void registerScanner( + IScannerCallback callback, WorkSource workSource, AttributionSource attributionSource) { + if (!Utils.checkScanPermissionForDataDelivery( + mAdapterService, attributionSource, "ScanHelper registerScanner")) { + return; + } + + enforceImpersonatationPermissionIfNeeded(workSource); + + AppScanStats app = mScannerMap.getAppScanStatsByUid(Binder.getCallingUid()); + if (app != null + && app.isScanningTooFrequently() + && !Utils.checkCallerHasPrivilegedPermission(mAdapterService)) { + Log.e(TAG, "App '" + app.mAppName + "' is scanning too frequently"); + try { + callback.onScannerRegistered(ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY, -1); + } catch (RemoteException e) { + Log.e(TAG, "Exception: " + e); + } + return; + } + registerScannerInternal(callback, attributionSource, workSource); + } + + /** Intended for internal use within the Bluetooth app. Bypass permission check */ + public void registerScannerInternal( + IScannerCallback callback, AttributionSource attrSource, WorkSource workSource) { + UUID uuid = UUID.randomUUID(); + Log.d(TAG, "registerScanner() - UUID=" + uuid); + + mScannerMap.add(uuid, attrSource, workSource, callback, mAdapterService, this); + mScanManager.registerScanner(uuid); + } + + @RequiresPermission(BLUETOOTH_SCAN) + private void unregisterScanner(int scannerId, AttributionSource attributionSource) { + if (!Utils.checkScanPermissionForDataDelivery( + mAdapterService, attributionSource, "ScanHelper unregisterScanner")) { + return; + } + + unregisterScannerInternal(scannerId); + } + + /** Intended for internal use within the Bluetooth app. Bypass permission check */ + public void unregisterScannerInternal(int scannerId) { + Log.d(TAG, "unregisterScanner() - scannerId=" + scannerId); + mScannerMap.remove(scannerId); + mScanManager.unregisterScanner(scannerId); + } + + private List<String> getAssociatedDevices(String callingPackage) { + if (mCompanionManager == null) { + return Collections.emptyList(); + } + + final long identity = Binder.clearCallingIdentity(); + try { + return mCompanionManager.getAllAssociations().stream() + .filter( + info -> + info.getPackageName().equals(callingPackage) + && !info.isSelfManaged() + && info.getDeviceMacAddress() != null) + .map(AssociationInfo::getDeviceMacAddress) + .map(MacAddress::toString) + .collect(Collectors.toList()); + } catch (SecurityException se) { + // Not an app with associated devices + } catch (Exception e) { + Log.e(TAG, "Cannot check device associations for " + callingPackage, e); + } finally { + Binder.restoreCallingIdentity(identity); + } + return Collections.emptyList(); + } + + @RequiresPermission(BLUETOOTH_SCAN) + private void startScan( + int scannerId, + ScanSettings settings, + List<ScanFilter> filters, + AttributionSource attributionSource) { + Log.d(TAG, "start scan with filters"); + + if (!Utils.checkScanPermissionForDataDelivery( + mAdapterService, attributionSource, "Starting GATT scan.")) { + return; + } + + enforcePrivilegedPermissionIfNeeded(settings); + String callingPackage = attributionSource.getPackageName(); + settings = enforceReportDelayFloor(settings); + enforcePrivilegedPermissionIfNeeded(filters); + final ScanClient scanClient = new ScanClient(scannerId, settings, filters); + scanClient.userHandle = Binder.getCallingUserHandle(); + mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); + scanClient.eligibleForSanitizedExposureNotification = + callingPackage.equals(mExposureNotificationPackage); + + scanClient.hasDisavowedLocation = + Utils.hasDisavowedLocationForScan( + mAdapterService, attributionSource, mTestModeEnabled); + + scanClient.isQApp = + checkCallerTargetSdk(mAdapterService, callingPackage, Build.VERSION_CODES.Q); + if (!scanClient.hasDisavowedLocation) { + if (scanClient.isQApp) { + scanClient.hasLocationPermission = + Utils.checkCallerHasFineLocation( + mAdapterService, attributionSource, scanClient.userHandle); + } else { + scanClient.hasLocationPermission = + Utils.checkCallerHasCoarseOrFineLocation( + mAdapterService, attributionSource, scanClient.userHandle); + } + } + scanClient.hasNetworkSettingsPermission = + Utils.checkCallerHasNetworkSettingsPermission(mAdapterService); + scanClient.hasNetworkSetupWizardPermission = + Utils.checkCallerHasNetworkSetupWizardPermission(mAdapterService); + scanClient.hasScanWithoutLocationPermission = + Utils.checkCallerHasScanWithoutLocationPermission(mAdapterService); + scanClient.associatedDevices = getAssociatedDevices(callingPackage); + + startScan(scannerId, settings, filters, scanClient); + } + + /** Intended for internal use within the Bluetooth app. Bypass permission check */ + public void startScanInternal(int scannerId, ScanSettings settings, List<ScanFilter> filters) { + final ScanClient scanClient = new ScanClient(scannerId, settings, filters); + scanClient.userHandle = Binder.getCallingUserHandle(); + scanClient.eligibleForSanitizedExposureNotification = false; + scanClient.hasDisavowedLocation = false; + scanClient.isQApp = true; + scanClient.hasNetworkSettingsPermission = + Utils.checkCallerHasNetworkSettingsPermission(mAdapterService); + scanClient.hasNetworkSetupWizardPermission = + Utils.checkCallerHasNetworkSetupWizardPermission(mAdapterService); + scanClient.hasScanWithoutLocationPermission = + Utils.checkCallerHasScanWithoutLocationPermission(mAdapterService); + scanClient.associatedDevices = Collections.emptyList(); + + startScan(scannerId, settings, filters, scanClient); + } + + private void startScan( + int scannerId, ScanSettings settings, List<ScanFilter> filters, ScanClient scanClient) { + AppScanStats app = mScannerMap.getAppScanStatsById(scannerId); + if (app != null) { + scanClient.stats = app; + boolean isFilteredScan = (filters != null) && !filters.isEmpty(); + boolean isCallbackScan = false; + + ScannerMap.ScannerApp cbApp = mScannerMap.getById(scannerId); + if (cbApp != null) { + isCallbackScan = cbApp.mCallback != null; + } + app.recordScanStart( + settings, + filters, + isFilteredScan, + isCallbackScan, + scannerId, + cbApp == null ? null : cbApp.mAttributionTag); + } + + mScanManager.startScan(scanClient); + } + + @RequiresPermission(BLUETOOTH_SCAN) + private void registerPiAndStartScan( + PendingIntent pendingIntent, + ScanSettings settings, + List<ScanFilter> filters, + AttributionSource attributionSource) { + Log.d(TAG, "start scan with filters, for PendingIntent"); + + if (!Utils.checkScanPermissionForDataDelivery( + mAdapterService, attributionSource, "Starting GATT scan.")) { + return; + } + enforcePrivilegedPermissionIfNeeded(settings); + settings = enforceReportDelayFloor(settings); + enforcePrivilegedPermissionIfNeeded(filters); + UUID uuid = UUID.randomUUID(); + String callingPackage = attributionSource.getPackageName(); + int callingUid = attributionSource.getUid(); + PendingIntentInfo piInfo = new PendingIntentInfo(); + piInfo.intent = pendingIntent; + piInfo.settings = settings; + piInfo.filters = filters; + piInfo.callingPackage = callingPackage; + piInfo.callingUid = callingUid; + Log.d( + TAG, + "startScan(PI) -" + + (" UUID=" + uuid) + + (" Package=" + callingPackage) + + (" UID=" + callingUid)); + + // Don't start scan if the Pi scan already in mScannerMap. + if (mScannerMap.getByPendingIntentInfo(piInfo) != null) { + Log.d(TAG, "Don't startScan(PI) since the same Pi scan already in mScannerMap."); + return; + } + + ScannerMap.ScannerApp app = + mScannerMap.add(uuid, attributionSource, piInfo, mAdapterService, this); + + app.mUserHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid()); + mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); + app.mEligibleForSanitizedExposureNotification = + callingPackage.equals(mExposureNotificationPackage); + + app.mHasDisavowedLocation = + Utils.hasDisavowedLocationForScan( + mAdapterService, attributionSource, mTestModeEnabled); + + if (!app.mHasDisavowedLocation) { + try { + if (checkCallerTargetSdk(mAdapterService, callingPackage, Build.VERSION_CODES.Q)) { + app.mHasLocationPermission = + Utils.checkCallerHasFineLocation( + mAdapterService, attributionSource, app.mUserHandle); + } else { + app.mHasLocationPermission = + Utils.checkCallerHasCoarseOrFineLocation( + mAdapterService, attributionSource, app.mUserHandle); + } + } catch (SecurityException se) { + // No need to throw here. Just mark as not granted. + app.mHasLocationPermission = false; + } + } + app.mHasNetworkSettingsPermission = + Utils.checkCallerHasNetworkSettingsPermission(mAdapterService); + app.mHasNetworkSetupWizardPermission = + Utils.checkCallerHasNetworkSetupWizardPermission(mAdapterService); + app.mHasScanWithoutLocationPermission = + Utils.checkCallerHasScanWithoutLocationPermission(mAdapterService); + app.mAssociatedDevices = getAssociatedDevices(callingPackage); + mScanManager.registerScanner(uuid); + + // If this fails, we should stop the scan immediately. + if (!pendingIntent.addCancelListener(Runnable::run, mScanIntentCancelListener)) { + Log.d(TAG, "scanning PendingIntent is already cancelled, stopping scan."); + stopScan(pendingIntent, attributionSource); + } + } + + /** Start a scan with pending intent. */ + @VisibleForTesting + void continuePiStartScan(int scannerId, ScannerMap.ScannerApp app) { + final PendingIntentInfo piInfo = app.mInfo; + final ScanClient scanClient = + new ScanClient(scannerId, piInfo.settings, piInfo.filters, piInfo.callingUid); + scanClient.hasLocationPermission = app.mHasLocationPermission; + scanClient.userHandle = app.mUserHandle; + scanClient.isQApp = checkCallerTargetSdk(mAdapterService, app.mName, Build.VERSION_CODES.Q); + scanClient.eligibleForSanitizedExposureNotification = + app.mEligibleForSanitizedExposureNotification; + scanClient.hasNetworkSettingsPermission = app.mHasNetworkSettingsPermission; + scanClient.hasNetworkSetupWizardPermission = app.mHasNetworkSetupWizardPermission; + scanClient.hasScanWithoutLocationPermission = app.mHasScanWithoutLocationPermission; + scanClient.associatedDevices = app.mAssociatedDevices; + scanClient.hasDisavowedLocation = app.mHasDisavowedLocation; + + AppScanStats scanStats = mScannerMap.getAppScanStatsById(scannerId); + if (scanStats != null) { + scanClient.stats = scanStats; + boolean isFilteredScan = (piInfo.filters != null) && !piInfo.filters.isEmpty(); + scanStats.recordScanStart( + piInfo.settings, + piInfo.filters, + isFilteredScan, + false, + scannerId, + app.mAttributionTag); + } + + mScanManager.startScan(scanClient); + } + + @RequiresPermission(BLUETOOTH_SCAN) + @VisibleForTesting + void flushPendingBatchResults(int scannerId, AttributionSource attributionSource) { + if (!Utils.checkScanPermissionForDataDelivery( + mAdapterService, attributionSource, "ScanHelper flushPendingBatchResults")) { + return; + } + flushPendingBatchResultsInternal(scannerId); + } + + private void flushPendingBatchResultsInternal(int scannerId) { + Log.d(TAG, "flushPendingBatchResultsInternal - scannerId=" + scannerId); + mScanManager.flushBatchScanResults(new ScanClient(scannerId)); + } + + @RequiresPermission(BLUETOOTH_SCAN) + private void stopScan(int scannerId, AttributionSource attributionSource) { + if (!Utils.checkScanPermissionForDataDelivery( + mAdapterService, attributionSource, "ScanHelper stopScan")) { + return; + } + stopScanInternal(scannerId); + } + + /** Intended for internal use within the Bluetooth app. Bypass permission check */ + public void stopScanInternal(int scannerId) { + int scanQueueSize = + mScanManager.getBatchScanQueue().size() + mScanManager.getRegularScanQueue().size(); + Log.d(TAG, "stopScan() - queue size =" + scanQueueSize); + + AppScanStats app = mScannerMap.getAppScanStatsById(scannerId); + if (app != null) { + app.recordScanStop(scannerId); + } + + mScanManager.stopScan(scannerId); + } + + @RequiresPermission(BLUETOOTH_SCAN) + private void stopScan(PendingIntent intent, AttributionSource attributionSource) { + if (!Utils.checkScanPermissionForDataDelivery( + mAdapterService, attributionSource, "ScanHelper stopScan")) { + return; + } + stopScanInternal(intent); + } + + /** Intended for internal use within the Bluetooth app. Bypass permission check */ + private void stopScanInternal(PendingIntent intent) { + PendingIntentInfo pii = new PendingIntentInfo(); + pii.intent = intent; + ScannerMap.ScannerApp app = mScannerMap.getByPendingIntentInfo(pii); + Log.v(TAG, "stopScan(PendingIntent): app found = " + app); + if (app != null) { + intent.removeCancelListener(mScanIntentCancelListener); + final int scannerId = app.mId; + stopScanInternal(scannerId); + // Also unregister the scanner + unregisterScannerInternal(scannerId); + } + } + + /************************************************************************** + * PERIODIC SCANNING + *************************************************************************/ + @RequiresPermission(BLUETOOTH_SCAN) + @VisibleForTesting + void registerSync( + ScanResult scanResult, + int skip, + int timeout, + IPeriodicAdvertisingCallback callback, + AttributionSource attributionSource) { + if (!Utils.checkScanPermissionForDataDelivery( + mAdapterService, attributionSource, "ScanHelper registerSync")) { + return; + } + mPeriodicScanManager.startSync(scanResult, skip, timeout, callback); + } + + @RequiresPermission(BLUETOOTH_SCAN) + @VisibleForTesting + void unregisterSync( + IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) { + if (!Utils.checkScanPermissionForDataDelivery( + mAdapterService, attributionSource, "ScanHelper unregisterSync")) { + return; + } + mPeriodicScanManager.stopSync(callback); + } + + @RequiresPermission(BLUETOOTH_SCAN) + @VisibleForTesting + void transferSync( + BluetoothDevice bda, + int serviceData, + int syncHandle, + AttributionSource attributionSource) { + if (!Utils.checkScanPermissionForDataDelivery( + mAdapterService, attributionSource, "ScanHelper transferSync")) { + return; + } + mPeriodicScanManager.transferSync(bda, serviceData, syncHandle); + } + + @RequiresPermission(BLUETOOTH_SCAN) + @VisibleForTesting + void transferSetInfo( + BluetoothDevice bda, + int serviceData, + int advHandle, + IPeriodicAdvertisingCallback callback, + AttributionSource attributionSource) { + if (!Utils.checkScanPermissionForDataDelivery( + mAdapterService, attributionSource, "ScanHelper transferSetInfo")) { + return; + } + mPeriodicScanManager.transferSetInfo(bda, serviceData, advHandle, callback); + } + + @RequiresPermission(BLUETOOTH_SCAN) + private int numHwTrackFiltersAvailable(AttributionSource attributionSource) { + if (!Utils.checkScanPermissionForDataDelivery( + mAdapterService, attributionSource, "ScanHelper numHwTrackFiltersAvailable")) { + return 0; + } + return (mAdapterService.getTotalNumOfTrackableAdvertisements() + - mScanManager.getCurrentUsedTrackingAdvertisement()); + } + + /** + * DeathRecipient handler used to unregister applications that disconnect ungracefully (ie. + * crash or forced close). + */ + class ScannerDeathRecipient implements IBinder.DeathRecipient { + int mScannerId; + private String mPackageName; + + ScannerDeathRecipient(int scannerId, String packageName) { + mScannerId = scannerId; + mPackageName = packageName; + } + + @Override + public void binderDied() { + Log.d( + TAG, + "Binder is dead - unregistering scanner (" + + mPackageName + + " " + + mScannerId + + ")!"); + + ScanClient client = getScanClient(mScannerId); + if (client != null) { + handleDeadScanClient(client); + } + } + + private ScanClient getScanClient(int clientIf) { + for (ScanClient client : mScanManager.getRegularScanQueue()) { + if (client.scannerId == clientIf) { + return client; + } + } + for (ScanClient client : mScanManager.getBatchScanQueue()) { + if (client.scannerId == clientIf) { + return client; + } + } + return null; + } + } + + private boolean needsPrivilegedPermissionForScan(ScanSettings settings) { + // BLE scan only mode needs special permission. + if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) { + return true; + } + + // Regular scan, no special permission. + if (settings == null) { + return false; + } + + // Ambient discovery mode, needs privileged permission. + if (settings.getScanMode() == ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY) { + return true; + } + + // Regular scan, no special permission. + if (settings.getReportDelayMillis() == 0) { + return false; + } + + // Batch scan, truncated mode needs permission. + return settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_ABBREVIATED; + } + + /* + * The ScanFilter#setDeviceAddress API overloads are @SystemApi access methods. This + * requires that the permissions be BLUETOOTH_PRIVILEGED. + */ + @SuppressLint("AndroidFrameworkRequiresPermission") + private void enforcePrivilegedPermissionIfNeeded(List<ScanFilter> filters) { + Log.d(TAG, "enforcePrivilegedPermissionIfNeeded(" + filters + ")"); + // Some 3p API cases may have null filters, need to allow + if (filters != null) { + for (ScanFilter filter : filters) { + // The only case to enforce here is if there is an address + // If there is an address, enforce if the correct combination criteria is met. + if (filter.getDeviceAddress() != null) { + // At this point we have an address, that means a caller used the + // setDeviceAddress(address) public API for the ScanFilter + // We don't want to enforce if the type is PUBLIC and the IRK is null + // However, if we have a different type that means the caller used a new + // @SystemApi such as setDeviceAddress(address, type) or + // setDeviceAddress(address, type, irk) which are both @SystemApi and require + // permissions to be enforced + if (filter.getAddressType() == BluetoothDevice.ADDRESS_TYPE_PUBLIC + && filter.getIrk() == null) { + // Do not enforce + } else { + mAdapterService.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + } + } + } + } + } + + @SuppressLint("AndroidFrameworkRequiresPermission") + private void enforcePrivilegedPermissionIfNeeded(ScanSettings settings) { + if (needsPrivilegedPermissionForScan(settings)) { + mAdapterService.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + } + } + + // Enforce caller has UPDATE_DEVICE_STATS permission, which allows the caller to blame other + // apps for Bluetooth usage. A {@link SecurityException} will be thrown if the caller app does + // not have UPDATE_DEVICE_STATS permission. + @RequiresPermission(UPDATE_DEVICE_STATS) + private void enforceImpersonatationPermission() { + mAdapterService.enforceCallingOrSelfPermission( + UPDATE_DEVICE_STATS, "Need UPDATE_DEVICE_STATS permission"); + } + + @SuppressLint("AndroidFrameworkRequiresPermission") + private void enforceImpersonatationPermissionIfNeeded(WorkSource workSource) { + if (workSource != null) { + enforceImpersonatationPermission(); + } + } + + /** + * Ensures the report delay is either 0 or at least the floor value (5000ms) + * + * @param settings are the scan settings passed into a request to start le scanning + * @return the passed in ScanSettings object if the report delay is 0 or above the floor value; + * a new ScanSettings object with the report delay being the floor value if the original + * report delay was between 0 and the floor value (exclusive of both) + */ + @VisibleForTesting + ScanSettings enforceReportDelayFloor(ScanSettings settings) { + if (settings.getReportDelayMillis() == 0) { + return settings; + } + + // Need to clear identity to pass device config permission check + final long callerToken = Binder.clearCallingIdentity(); + try { + long floor = + DeviceConfig.getLong( + DeviceConfig.NAMESPACE_BLUETOOTH, + "report_delay", + DEFAULT_REPORT_DELAY_FLOOR); + + if (settings.getReportDelayMillis() > floor) { + return settings; + } else { + return new ScanSettings.Builder() + .setCallbackType(settings.getCallbackType()) + .setLegacy(settings.getLegacy()) + .setMatchMode(settings.getMatchMode()) + .setNumOfMatches(settings.getNumOfMatches()) + .setPhy(settings.getPhy()) + .setReportDelay(floor) + .setScanMode(settings.getScanMode()) + .setScanResultType(settings.getScanResultType()) + .build(); + } + } finally { + Binder.restoreCallingIdentity(callerToken); + } + } + + void addScanEvent(BluetoothMetricsProto.ScanEvent event) { + synchronized (mScanEvents) { + if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT) { + mScanEvents.remove(); + } + mScanEvents.add(event); + } + } + public void dumpRegisterId(StringBuilder sb) { sb.append(" Scanner:\n"); - mTransitionalScanHelper.getScannerMap().dumpApps(sb, ProfileService::println); + mScannerMap.dumpApps(sb, ProfileService::println); } public void dump(StringBuilder sb) { sb.append("GATT Scanner Map\n"); - mTransitionalScanHelper.getScannerMap().dump(sb); + mScannerMap.dump(sb); } public void dumpProto(BluetoothMetricsProto.BluetoothLog.Builder builder) { - mTransitionalScanHelper.dumpProto(builder); + synchronized (mScanEvents) { + builder.addAllScanEvent(mScanEvents); + } } static class BluetoothScanBinder extends IBluetoothScan.Stub { @@ -172,7 +1746,6 @@ public class ScanController { return; } mScanController - .getTransitionalScanHelper() .registerScanner(callback, workSource, attributionSource); } @@ -183,7 +1756,6 @@ public class ScanController { return; } mScanController - .getTransitionalScanHelper() .unregisterScanner(scannerId, attributionSource); } @@ -198,7 +1770,6 @@ public class ScanController { return; } mScanController - .getTransitionalScanHelper() .startScan(scannerId, settings, filters, attributionSource); } @@ -213,7 +1784,6 @@ public class ScanController { return; } mScanController - .getTransitionalScanHelper() .registerPiAndStartScan(intent, settings, filters, attributionSource); } @@ -223,7 +1793,7 @@ public class ScanController { if (mScanController == null) { return; } - mScanController.getTransitionalScanHelper().stopScan(scannerId, attributionSource); + mScanController.stopScan(scannerId, attributionSource); } @Override @@ -232,7 +1802,7 @@ public class ScanController { if (mScanController == null) { return; } - mScanController.getTransitionalScanHelper().stopScan(intent, attributionSource); + mScanController.stopScan(intent, attributionSource); } @Override @@ -242,7 +1812,6 @@ public class ScanController { return; } mScanController - .getTransitionalScanHelper() .flushPendingBatchResults(scannerId, attributionSource); } @@ -258,7 +1827,6 @@ public class ScanController { return; } mScanController - .getTransitionalScanHelper() .registerSync(scanResult, skip, timeout, callback, attributionSource); } @@ -269,7 +1837,7 @@ public class ScanController { if (mScanController == null) { return; } - mScanController.getTransitionalScanHelper().unregisterSync(callback, attributionSource); + mScanController.unregisterSync(callback, attributionSource); } @Override @@ -283,7 +1851,6 @@ public class ScanController { return; } mScanController - .getTransitionalScanHelper() .transferSync(bda, serviceData, syncHandle, attributionSource); } @@ -299,7 +1866,6 @@ public class ScanController { return; } mScanController - .getTransitionalScanHelper() .transferSetInfo(bda, serviceData, advHandle, callback, attributionSource); } @@ -310,7 +1876,6 @@ public class ScanController { return 0; } return mScanController - .getTransitionalScanHelper() .numHwTrackFiltersAvailable(attributionSource); } diff --git a/android/app/src/com/android/bluetooth/le_scan/ScanManager.java b/android/app/src/com/android/bluetooth/le_scan/ScanManager.java index 453b5849fd..e946b3afcf 100644 --- a/android/app/src/com/android/bluetooth/le_scan/ScanManager.java +++ b/android/app/src/com/android/bluetooth/le_scan/ScanManager.java @@ -85,7 +85,7 @@ public class ScanManager { public static final int SCAN_MODE_SCREEN_OFF_BALANCED_WINDOW_MS = 183; public static final int SCAN_MODE_SCREEN_OFF_BALANCED_INTERVAL_MS = 730; - // Result type defined in bt stack. Need to be accessed by TransitionalScanHelper. + // Result type defined in bt stack. Need to be accessed by ScanController. static final int SCAN_RESULT_TYPE_TRUNCATED = 1; static final int SCAN_RESULT_TYPE_FULL = 2; static final int SCAN_RESULT_TYPE_BOTH = 3; @@ -119,7 +119,7 @@ public class ScanManager { @GuardedBy("mCurUsedTrackableAdvertisementsLock") private int mCurUsedTrackableAdvertisements = 0; - private final TransitionalScanHelper mScanHelper; + private final ScanController mScanController; private final AdapterService mAdapterService; private final TimeProvider mTimeProvider; private ScanNative mScanNative; @@ -146,6 +146,7 @@ public class ScanManager { @VisibleForTesting boolean mIsConnecting; @VisibleForTesting int mProfilesConnecting; private int mProfilesConnected, mProfilesDisconnecting; + private final BatchScanThrottler mBatchScanThrottler; @VisibleForTesting static class UidImportance { @@ -158,9 +159,9 @@ public class ScanManager { } } - public ScanManager( + ScanManager( AdapterService adapterService, - TransitionalScanHelper scanHelper, + ScanController scanController, BluetoothAdapterProxy bluetoothAdapterProxy, Looper looper, TimeProvider timeProvider) { @@ -169,10 +170,10 @@ public class ScanManager { mBatchClients = Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>()); mSuspendedScanClients = Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>()); - mScanHelper = scanHelper; + mScanController = scanController; mAdapterService = adapterService; mTimeProvider = timeProvider; - mScanNative = new ScanNative(scanHelper); + mScanNative = new ScanNative(scanController); mDisplayManager = mAdapterService.getSystemService(DisplayManager.class); mActivityManager = mAdapterService.getSystemService(ActivityManager.class); mLocationManager = mAdapterService.getSystemService(LocationManager.class); @@ -202,13 +203,13 @@ public class ScanManager { IntentFilter locationIntentFilter = new IntentFilter(LocationManager.MODE_CHANGED_ACTION); locationIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); mAdapterService.registerReceiver(mLocationReceiver, locationIntentFilter); + mBatchScanThrottler = new BatchScanThrottler(timeProvider, mScreenOn); } - public void cleanup() { + void cleanup() { mRegularScanClients.clear(); mBatchClients.clear(); mSuspendedScanClients.clear(); - mScanNative.cleanup(); if (mActivityManager != null) { try { @@ -225,6 +226,8 @@ public class ScanManager { // Shut down the thread mHandler.removeCallbacksAndMessages(null); + mScanNative.cleanup(); + try { mAdapterService.unregisterReceiver(mLocationReceiver); } catch (IllegalArgumentException e) { @@ -232,16 +235,16 @@ public class ScanManager { } } - public void registerScanner(UUID uuid) { + void registerScanner(UUID uuid) { mScanNative.registerScanner(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits()); } - public void unregisterScanner(int scannerId) { + void unregisterScanner(int scannerId) { mScanNative.unregisterScanner(scannerId); } /** Returns the regular scan queue. */ - public Set<ScanClient> getRegularScanQueue() { + Set<ScanClient> getRegularScanQueue() { return mRegularScanClients; } @@ -251,12 +254,12 @@ public class ScanManager { } /** Returns batch scan queue. */ - public Set<ScanClient> getBatchScanQueue() { + Set<ScanClient> getBatchScanQueue() { return mBatchClients; } /** Returns a set of full batch scan clients. */ - public Set<ScanClient> getFullBatchScanQueue() { + Set<ScanClient> getFullBatchScanQueue() { // TODO: split full batch scan clients and truncated batch clients so we don't need to // construct this every time. Set<ScanClient> fullBatchClients = new HashSet<ScanClient>(); @@ -268,12 +271,12 @@ public class ScanManager { return fullBatchClients; } - public void startScan(ScanClient client) { + void startScan(ScanClient client) { Log.d(TAG, "startScan() " + client); sendMessage(MSG_START_BLE_SCAN, client); } - public void stopScan(int scannerId) { + void stopScan(int scannerId) { ScanClient client = mScanNative.getBatchScanClient(scannerId); if (client == null) { client = mScanNative.getRegularScanClient(scannerId); @@ -284,14 +287,18 @@ public class ScanManager { sendMessage(MSG_STOP_BLE_SCAN, client); } - public void flushBatchScanResults(ScanClient client) { + void flushBatchScanResults(ScanClient client) { sendMessage(MSG_FLUSH_BATCH_RESULTS, client); } - public void callbackDone(int scannerId, int status) { + void callbackDone(int scannerId, int status) { mScanNative.callbackDone(scannerId, status); } + void batchScanResultDelivered() { + mBatchScanThrottler.resetBackoff(); + } + private void sendMessage(int what, ScanClient client) { mHandler.obtainMessage(what, client).sendToTarget(); } @@ -304,10 +311,16 @@ public class ScanManager { return mBluetoothAdapterProxy.isOffloadedScanFilteringSupported(); } - public boolean isAutoBatchScanClientEnabled(ScanClient client) { + boolean isAutoBatchScanClientEnabled(ScanClient client) { return mScanNative.isAutoBatchScanClientEnabled(client); } + int getCurrentUsedTrackingAdvertisement() { + synchronized (mCurUsedTrackableAdvertisementsLock) { + return mCurUsedTrackableAdvertisements; + } + } + // Handler class that handles BLE scan operations. @VisibleForTesting class ClientHandler extends Handler { @@ -492,7 +505,7 @@ public class ScanManager { } if (client.appDied) { Log.d(TAG, "app died, unregister scanner - " + client.scannerId); - mScanHelper.unregisterScannerInternal(client.scannerId); + mScanController.unregisterScannerInternal(client.scannerId); } } @@ -533,6 +546,7 @@ public class ScanManager { } mScreenOn = false; Log.d(TAG, "handleScreenOff()"); + mBatchScanThrottler.onScreenOn(false); handleSuspendScans(); updateRegularScanClientsScreenOff(); updateRegularScanToBatchScanClients(); @@ -863,6 +877,7 @@ public class ScanManager { } mScreenOn = true; Log.d(TAG, "handleScreenOn()"); + mBatchScanThrottler.onScreenOn(true); updateBatchScanToRegularScanClients(); handleResumeScans(); updateRegularScanClientsScreenOn(); @@ -920,14 +935,14 @@ public class ScanManager { /** Parameters for batch scans. */ static class BatchScanParams { - public int scanMode; - public int fullScanscannerId; - public int truncatedScanscannerId; + @VisibleForTesting int mScanMode; + private int mFullScanscannerId; + private int mTruncatedScanscannerId; BatchScanParams() { - scanMode = -1; - fullScanscannerId = -1; - truncatedScanscannerId = -1; + mScanMode = -1; + mFullScanscannerId = -1; + mTruncatedScanscannerId = -1; } @Override @@ -938,20 +953,14 @@ public class ScanManager { if (!(obj instanceof BatchScanParams other)) { return false; } - return scanMode == other.scanMode - && fullScanscannerId == other.fullScanscannerId - && truncatedScanscannerId == other.truncatedScanscannerId; + return mScanMode == other.mScanMode + && mFullScanscannerId == other.mFullScanscannerId + && mTruncatedScanscannerId == other.mTruncatedScanscannerId; } @Override public int hashCode() { - return Objects.hash(scanMode, fullScanscannerId, truncatedScanscannerId); - } - } - - public int getCurrentUsedTrackingAdvertisement() { - synchronized (mCurUsedTrackableAdvertisementsLock) { - return mCurUsedTrackableAdvertisements; + return Objects.hash(mScanMode, mFullScanscannerId, mTruncatedScanscannerId); } } @@ -1010,9 +1019,9 @@ public class ScanManager { private final MsftAdvMonitorMergedPatternList mMsftAdvMonitorMergedPatternList = new MsftAdvMonitorMergedPatternList(); - ScanNative(TransitionalScanHelper scanHelper) { + ScanNative(ScanController scanController) { mNativeInterface = ScanObjectsFactory.getInstance().getScanNativeInterface(); - mNativeInterface.init(scanHelper); + mNativeInterface.init(scanController); mFilterIndexStack = new ArrayDeque<Integer>(); mClientFilterIndexMap = new HashMap<Integer, Deque<Integer>>(); @@ -1261,9 +1270,9 @@ public class ScanManager { waitForCallback(); resetCountDownLatch(); int scanInterval = - Utils.millsToUnit(getBatchScanIntervalMillis(batchScanParams.scanMode)); + Utils.millsToUnit(getBatchScanIntervalMillis(batchScanParams.mScanMode)); int scanWindow = - Utils.millsToUnit(getBatchScanWindowMillis(batchScanParams.scanMode)); + Utils.millsToUnit(getBatchScanWindowMillis(batchScanParams.mScanMode)); mNativeInterface.gattClientStartBatchScan( scannerId, resultType, @@ -1297,15 +1306,15 @@ public class ScanManager { BatchScanParams params = new BatchScanParams(); ScanClient winner = getAggressiveClient(mBatchClients); if (winner != null) { - params.scanMode = winner.settings.getScanMode(); + params.mScanMode = winner.settings.getScanMode(); } // TODO: split full batch scan results and truncated batch scan results to different // collections. for (ScanClient client : mBatchClients) { if (client.settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_FULL) { - params.fullScanscannerId = client.scannerId; + params.mFullScanscannerId = client.scannerId; } else { - params.truncatedScanscannerId = client.scannerId; + params.mTruncatedScanscannerId = client.scannerId; } } return params; @@ -1358,7 +1367,10 @@ public class ScanManager { if (mBatchClients.isEmpty()) { return; } - long batchTriggerIntervalMillis = getBatchTriggerIntervalMillis(); + long batchTriggerIntervalMillis = + Flags.batchScanOptimization() + ? mBatchScanThrottler.getBatchTriggerIntervalMillis(mBatchClients) + : getBatchTriggerIntervalMillis(); // Allows the alarm to be triggered within // [batchTriggerIntervalMillis, 1.1 * batchTriggerIntervalMillis] long windowLengthMillis = batchTriggerIntervalMillis / 10; @@ -1386,7 +1398,7 @@ public class ScanManager { "Error freeing for onfound/onlost filter resources " + entriesToFreePerFilter); try { - mScanHelper.onScanManagerErrorCallback( + mScanController.onScanManagerErrorCallback( client.scannerId, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); } catch (RemoteException e) { Log.e(TAG, "failed on onScanManagerCallback at freeing", e); @@ -1493,16 +1505,16 @@ public class ScanManager { void flushBatchResults(int scannerId) { Log.d(TAG, "flushPendingBatchResults - scannerId = " + scannerId); - if (mBatchScanParams.fullScanscannerId != -1) { + if (mBatchScanParams.mFullScanscannerId != -1) { resetCountDownLatch(); mNativeInterface.gattClientReadScanReports( - mBatchScanParams.fullScanscannerId, SCAN_RESULT_TYPE_FULL); + mBatchScanParams.mFullScanscannerId, SCAN_RESULT_TYPE_FULL); waitForCallback(); } - if (mBatchScanParams.truncatedScanscannerId != -1) { + if (mBatchScanParams.mTruncatedScanscannerId != -1) { resetCountDownLatch(); mNativeInterface.gattClientReadScanReports( - mBatchScanParams.truncatedScanscannerId, SCAN_RESULT_TYPE_TRUNCATED); + mBatchScanParams.mTruncatedScanscannerId, SCAN_RESULT_TYPE_TRUNCATED); waitForCallback(); } setBatchAlarm(); @@ -1587,7 +1599,7 @@ public class ScanManager { mAdapterService.getTotalNumOfTrackableAdvertisements()); } try { - mScanHelper.onScanManagerErrorCallback( + mScanController.onScanManagerErrorCallback( scannerId, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); } catch (RemoteException e) { Log.e(TAG, "failed on onScanManagerCallback", e); @@ -1661,13 +1673,13 @@ public class ScanManager { /** Return batch scan result type value defined in bt stack. */ private int getResultType(BatchScanParams params) { - if (params.fullScanscannerId != -1 && params.truncatedScanscannerId != -1) { + if (params.mFullScanscannerId != -1 && params.mTruncatedScanscannerId != -1) { return SCAN_RESULT_TYPE_BOTH; } - if (params.truncatedScanscannerId != -1) { + if (params.mTruncatedScanscannerId != -1) { return SCAN_RESULT_TYPE_TRUNCATED; } - if (params.fullScanscannerId != -1) { + if (params.mFullScanscannerId != -1) { return SCAN_RESULT_TYPE_FULL; } return -1; @@ -2113,7 +2125,7 @@ public class ScanManager { new ActivityManager.OnUidImportanceListener() { @Override public void onUidImportance(final int uid, final int importance) { - if (mScanHelper.getScannerMap().getAppScanStatsByUid(uid) != null) { + if (mScanController.getScannerMap().getAppScanStatsByUid(uid) != null) { Message message = new Message(); message.what = MSG_IMPORTANCE_CHANGE; message.obj = new UidImportance(uid, importance); diff --git a/android/app/src/com/android/bluetooth/le_scan/ScanNativeInterface.java b/android/app/src/com/android/bluetooth/le_scan/ScanNativeInterface.java index 9f0ebcc979..1b14b7e500 100644 --- a/android/app/src/com/android/bluetooth/le_scan/ScanNativeInterface.java +++ b/android/app/src/com/android/bluetooth/le_scan/ScanNativeInterface.java @@ -33,7 +33,7 @@ public class ScanNativeInterface { private static ScanNativeInterface sInterface; private CountDownLatch mLatch = new CountDownLatch(1); - @Nullable private TransitionalScanHelper mScanHelper; + @Nullable private ScanController mScanController; private ScanNativeInterface() {} @@ -59,8 +59,8 @@ public class ScanNativeInterface { } } - void init(TransitionalScanHelper scanHelper) { - mScanHelper = scanHelper; + void init(ScanController scanController) { + mScanController = scanController; initializeNative(); } @@ -202,7 +202,7 @@ public class ScanNativeInterface { /** Remove a MSFT Advertisement Monitor */ public void gattClientMsftAdvMonitorRemove(int filter_index) { - int monitor_handle = mScanHelper.msftMonitorHandleFromFilterIndex(filter_index); + int monitor_handle = mScanController.msftMonitorHandleFromFilterIndex(filter_index); if (monitor_handle < 0) return; gattClientMsftAdvMonitorRemoveNative(filter_index, monitor_handle); } @@ -278,11 +278,11 @@ public class ScanNativeInterface { int periodicAdvInt, byte[] advData, String originalAddress) { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onScanResult( + mScanController.onScanResult( eventType, addressType, address, @@ -298,70 +298,70 @@ public class ScanNativeInterface { void onScannerRegistered(int status, int scannerId, long uuidLsb, long uuidMsb) throws RemoteException { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onScannerRegistered(status, scannerId, uuidLsb, uuidMsb); + mScanController.onScannerRegistered(status, scannerId, uuidLsb, uuidMsb); } void onScanFilterEnableDisabled(int action, int status, int clientIf) { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onScanFilterEnableDisabled(action, status, clientIf); + mScanController.onScanFilterEnableDisabled(action, status, clientIf); } void onScanFilterParamsConfigured(int action, int status, int clientIf, int availableSpace) { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onScanFilterParamsConfigured(action, status, clientIf, availableSpace); + mScanController.onScanFilterParamsConfigured(action, status, clientIf, availableSpace); } void onScanFilterConfig( int action, int status, int clientIf, int filterType, int availableSpace) { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onScanFilterConfig(action, status, clientIf, filterType, availableSpace); + mScanController.onScanFilterConfig(action, status, clientIf, filterType, availableSpace); } void onBatchScanStorageConfigured(int status, int clientIf) { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onBatchScanStorageConfigured(status, clientIf); + mScanController.onBatchScanStorageConfigured(status, clientIf); } void onBatchScanStartStopped(int startStopAction, int status, int clientIf) { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onBatchScanStartStopped(startStopAction, status, clientIf); + mScanController.onBatchScanStartStopped(startStopAction, status, clientIf); } void onBatchScanReports( int status, int scannerId, int reportType, int numRecords, byte[] recordData) throws RemoteException { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onBatchScanReports(status, scannerId, reportType, numRecords, recordData); + mScanController.onBatchScanReports(status, scannerId, reportType, numRecords, recordData); } void onBatchScanThresholdCrossed(int clientIf) { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onBatchScanThresholdCrossed(clientIf); + mScanController.onBatchScanThresholdCrossed(clientIf); } @Nullable @@ -379,11 +379,11 @@ public class ScanNativeInterface { int txPower, int rssiValue, int timeStamp) { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return null; } - return mScanHelper.createOnTrackAdvFoundLostObject( + return mScanController.createOnTrackAdvFoundLostObject( clientIf, advPktLen, advPkt, @@ -400,42 +400,42 @@ public class ScanNativeInterface { } void onTrackAdvFoundLost(AdvtFilterOnFoundOnLostInfo trackingInfo) throws RemoteException { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onTrackAdvFoundLost(trackingInfo); + mScanController.onTrackAdvFoundLost(trackingInfo); } void onScanParamSetupCompleted(int status, int scannerId) throws RemoteException { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onScanParamSetupCompleted(status, scannerId); + mScanController.onScanParamSetupCompleted(status, scannerId); } void onMsftAdvMonitorAdd(int filter_index, int monitor_handle, int status) { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onMsftAdvMonitorAdd(filter_index, monitor_handle, status); + mScanController.onMsftAdvMonitorAdd(filter_index, monitor_handle, status); } void onMsftAdvMonitorRemove(int filter_index, int status) { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onMsftAdvMonitorRemove(filter_index, status); + mScanController.onMsftAdvMonitorRemove(filter_index, status); } void onMsftAdvMonitorEnable(int status) { - if (mScanHelper == null) { + if (mScanController == null) { Log.e(TAG, "Scan helper is null!"); return; } - mScanHelper.onMsftAdvMonitorEnable(status); + mScanController.onMsftAdvMonitorEnable(status); } } diff --git a/android/app/src/com/android/bluetooth/le_scan/ScanObjectsFactory.java b/android/app/src/com/android/bluetooth/le_scan/ScanObjectsFactory.java index f3fba49c79..3d5521786c 100644 --- a/android/app/src/com/android/bluetooth/le_scan/ScanObjectsFactory.java +++ b/android/app/src/com/android/bluetooth/le_scan/ScanObjectsFactory.java @@ -68,18 +68,18 @@ public class ScanObjectsFactory { * Create an instance of ScanManager * * @param adapterService an AdapterService instance - * @param scanHelper a TransitionalScanHelper instance + * @param scanController a ScanController instance * @param bluetoothAdapterProxy a bluetoothAdapterProxy instance * @param looper the looper to be used for processing messages * @return the created ScanManager instance */ public ScanManager createScanManager( AdapterService adapterService, - TransitionalScanHelper scanHelper, + ScanController scanController, BluetoothAdapterProxy bluetoothAdapterProxy, Looper looper) { return new ScanManager( - adapterService, scanHelper, bluetoothAdapterProxy, looper, getSystemClock()); + adapterService, scanController, bluetoothAdapterProxy, looper, getSystemClock()); } public PeriodicScanManager createPeriodicScanManager(AdapterService adapterService) { diff --git a/android/app/src/com/android/bluetooth/le_scan/ScannerMap.java b/android/app/src/com/android/bluetooth/le_scan/ScannerMap.java index 00d9641032..a31f6b1c75 100644 --- a/android/app/src/com/android/bluetooth/le_scan/ScannerMap.java +++ b/android/app/src/com/android/bluetooth/le_scan/ScannerMap.java @@ -56,18 +56,25 @@ public class ScannerMap { WorkSource workSource, IScannerCallback callback, AdapterService adapterService, - TransitionalScanHelper scanHelper) { - return add(uuid, attributionSource, workSource, callback, null, adapterService, scanHelper); + ScanController scanController) { + return add( + uuid, + attributionSource, + workSource, + callback, + null, + adapterService, + scanController); } /** Add an entry to the application context list with a pending intent. */ ScannerApp add( UUID uuid, AttributionSource attributionSource, - TransitionalScanHelper.PendingIntentInfo piInfo, + ScanController.PendingIntentInfo piInfo, AdapterService adapterService, - TransitionalScanHelper scanHelper) { - return add(uuid, attributionSource, null, null, piInfo, adapterService, scanHelper); + ScanController scanController) { + return add(uuid, attributionSource, null, null, piInfo, adapterService, scanController); } private ScannerApp add( @@ -75,9 +82,9 @@ public class ScannerMap { AttributionSource attributionSource, @Nullable WorkSource workSource, @Nullable IScannerCallback callback, - @Nullable TransitionalScanHelper.PendingIntentInfo piInfo, + @Nullable ScanController.PendingIntentInfo piInfo, AdapterService adapterService, - TransitionalScanHelper scanHelper) { + ScanController scanController) { int appUid; String appName = null; if (piInfo != null) { @@ -99,7 +106,7 @@ public class ScannerMap { workSource, this, adapterService, - scanHelper, + scanController, getSystemClock()); mAppScanStatsMap.put(appUid, appScanStats); } @@ -175,7 +182,7 @@ public class ScannerMap { } /** Get an application context by the pending intent info object. */ - ScannerApp getByPendingIntentInfo(TransitionalScanHelper.PendingIntentInfo info) { + ScannerApp getByPendingIntentInfo(ScanController.PendingIntentInfo info) { ScannerApp app = getAppByPredicate(entry -> entry.mInfo != null && entry.mInfo.equals(info)); if (app == null) { @@ -219,7 +226,7 @@ public class ScannerMap { public static class ScannerApp { /** Context information */ - @Nullable TransitionalScanHelper.PendingIntentInfo mInfo; + @Nullable ScanController.PendingIntentInfo mInfo; /** Statistics for this app */ AppScanStats mAppScanStats; @@ -269,7 +276,7 @@ public class ScannerMap { UUID uuid, @Nullable String attributionTag, @Nullable IScannerCallback callback, - @Nullable TransitionalScanHelper.PendingIntentInfo info, + @Nullable ScanController.PendingIntentInfo info, String name, AppScanStats appScanStats) { this.mUuid = uuid; diff --git a/android/app/src/com/android/bluetooth/le_scan/TransitionalScanHelper.java b/android/app/src/com/android/bluetooth/le_scan/TransitionalScanHelper.java deleted file mode 100644 index 102d0d0649..0000000000 --- a/android/app/src/com/android/bluetooth/le_scan/TransitionalScanHelper.java +++ /dev/null @@ -1,1674 +0,0 @@ -/* - * Copyright (C) 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.le_scan; - -import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; -import static android.Manifest.permission.BLUETOOTH_SCAN; -import static android.Manifest.permission.UPDATE_DEVICE_STATS; - -import static com.android.bluetooth.Utils.checkCallerTargetSdk; - -import static java.util.Objects.requireNonNull; - -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.app.AppOpsManager; -import android.app.PendingIntent; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothUtils; -import android.bluetooth.le.BluetoothLeScanner; -import android.bluetooth.le.IPeriodicAdvertisingCallback; -import android.bluetooth.le.IScannerCallback; -import android.bluetooth.le.ScanCallback; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanRecord; -import android.bluetooth.le.ScanResult; -import android.bluetooth.le.ScanSettings; -import android.companion.AssociationInfo; -import android.companion.CompanionDeviceManager; -import android.content.AttributionSource; -import android.content.Intent; -import android.net.MacAddress; -import android.os.Binder; -import android.os.Build; -import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; -import android.os.SystemClock; -import android.os.UserHandle; -import android.os.WorkSource; -import android.provider.DeviceConfig; -import android.util.Log; - -import com.android.bluetooth.BluetoothMetricsProto; -import com.android.bluetooth.R; -import com.android.bluetooth.Utils; -import com.android.bluetooth.btservice.AdapterService; -import com.android.bluetooth.btservice.BluetoothAdapterProxy; -import com.android.bluetooth.gatt.GattServiceConfig; -import com.android.bluetooth.util.NumberUtils; -import com.android.internal.annotations.VisibleForTesting; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** - * A helper class which contains all scan related functions extracted from {@link - * com.android.bluetooth.gatt.GattService}. The purpose of this class is to preserve scan - * functionality within GattService and provide the same functionality in {@link ScanController}. - */ -public class TransitionalScanHelper { - private static final String TAG = GattServiceConfig.TAG_PREFIX + "ScanHelper"; - - // Batch scan related constants. - private static final int TRUNCATED_RESULT_SIZE = 11; - - /** The default floor value for LE batch scan report delays greater than 0 */ - @VisibleForTesting static final long DEFAULT_REPORT_DELAY_FLOOR = 5000; - - private static final int NUM_SCAN_EVENTS_KEPT = 20; - - // onFoundLost related constants - @VisibleForTesting static final int ADVT_STATE_ONFOUND = 0; - private static final int ADVT_STATE_ONLOST = 1; - - private static final int ET_LEGACY_MASK = 0x10; - - /** Keep the arguments passed in for the PendingIntent. */ - public static class PendingIntentInfo { - public PendingIntent intent; - public ScanSettings settings; - public List<ScanFilter> filters; - public String callingPackage; - public int callingUid; - - @Override - public boolean equals(Object other) { - if (!(other instanceof PendingIntentInfo)) { - return false; - } - return intent.equals(((PendingIntentInfo) other).intent); - } - - @Override - public int hashCode() { - return intent == null ? 0 : intent.hashCode(); - } - } - - public interface TestModeAccessor { - /** Indicates if bluetooth test mode is enabled. */ - boolean isTestModeEnabled(); - } - - private final PendingIntent.CancelListener mScanIntentCancelListener = - new PendingIntent.CancelListener() { - public void onCanceled(PendingIntent intent) { - Log.d(TAG, "scanning PendingIntent canceled"); - stopScanInternal(intent); - } - }; - - private final AdapterService mAdapterService; - private final TestModeAccessor mTestModeAccessor; - private final HashMap<Integer, Integer> mFilterIndexToMsftAdvMonitorMap = new HashMap<>(); - private final String mExposureNotificationPackage; - - private AppOpsManager mAppOps; - private CompanionDeviceManager mCompanionManager; - private PeriodicScanManager mPeriodicScanManager; - private ScanManager mScanManager; - - private ScannerMap mScannerMap = new ScannerMap(); - - public ScannerMap getScannerMap() { - return mScannerMap; - } - - @VisibleForTesting - public void setScannerMap(ScannerMap scannerMap) { - mScannerMap = scannerMap; - } - - /** Internal list of scan events to use with the proto */ - private final ArrayDeque<BluetoothMetricsProto.ScanEvent> mScanEvents = - new ArrayDeque<>(NUM_SCAN_EVENTS_KEPT); - - private final Predicate<ScanResult> mLocationDenylistPredicate; - - public TransitionalScanHelper( - AdapterService adapterService, TestModeAccessor testModeAccessor) { - mAdapterService = requireNonNull(adapterService); - mExposureNotificationPackage = - mAdapterService.getString(R.string.exposure_notification_package); - mTestModeAccessor = testModeAccessor; - mLocationDenylistPredicate = - (scanResult) -> { - final MacAddress parsedAddress = - MacAddress.fromString(scanResult.getDevice().getAddress()); - if (mAdapterService - .getLocationDenylistMac() - .test(parsedAddress.toByteArray())) { - Log.v(TAG, "Skipping device matching denylist: " + scanResult.getDevice()); - return true; - } - final ScanRecord scanRecord = scanResult.getScanRecord(); - if (scanRecord.matchesAnyField( - mAdapterService.getLocationDenylistAdvertisingData())) { - Log.v(TAG, "Skipping data matching denylist: " + scanRecord); - return true; - } - return false; - }; - } - - /** - * Starts the LE scanning component. - * - * @param looper for scan operations - */ - public void start(Looper looper) { - mAppOps = mAdapterService.getSystemService(AppOpsManager.class); - mCompanionManager = mAdapterService.getSystemService(CompanionDeviceManager.class); - mScanManager = - ScanObjectsFactory.getInstance() - .createScanManager( - mAdapterService, - this, - BluetoothAdapterProxy.getInstance(), - looper); - - mPeriodicScanManager = - ScanObjectsFactory.getInstance().createPeriodicScanManager(mAdapterService); - } - - /** Stops the scanning component. */ - public void stop() { - mScannerMap.clear(); - } - - /** Cleans up the scanning component. */ - public void cleanup() { - if (mScanManager != null) { - mScanManager.cleanup(); - } - if (mPeriodicScanManager != null) { - mPeriodicScanManager.cleanup(); - } - } - - /** Notifies scan manager of bluetooth profile connection state changes */ - public void notifyProfileConnectionStateChange(int profile, int fromState, int toState) { - if (mScanManager == null) { - Log.w(TAG, "scan manager is null"); - return; - } - mScanManager.handleBluetoothProfileConnectionStateChanged(profile, fromState, toState); - } - - public int getCurrentUsedTrackingAdvertisement() { - return mScanManager.getCurrentUsedTrackingAdvertisement(); - } - - /************************************************************************** - * Callback functions - CLIENT - *************************************************************************/ - - // EN format defined here: - // https://blog.google/documents/70/Exposure_Notification_-_Bluetooth_Specification_v1.2.2.pdf - private static final byte[] EXPOSURE_NOTIFICATION_FLAGS_PREAMBLE = - new byte[] { - // size 2, flag field, flags byte (value is not important) - (byte) 0x02, (byte) 0x01 - }; - - private static final int EXPOSURE_NOTIFICATION_FLAGS_LENGTH = 0x2 + 1; - private static final byte[] EXPOSURE_NOTIFICATION_PAYLOAD_PREAMBLE = - new byte[] { - // size 3, complete 16 bit UUID, EN UUID - (byte) 0x03, (byte) 0x03, (byte) 0x6F, (byte) 0xFD, - // size 23, data for 16 bit UUID, EN UUID - (byte) 0x17, (byte) 0x16, (byte) 0x6F, (byte) 0xFD, - // ...payload - }; - private static final int EXPOSURE_NOTIFICATION_PAYLOAD_LENGTH = 0x03 + 0x17 + 2; - - private static boolean arrayStartsWith(byte[] array, byte[] prefix) { - if (array.length < prefix.length) { - return false; - } - for (int i = 0; i < prefix.length; i++) { - if (prefix[i] != array[i]) { - return false; - } - } - return true; - } - - private ScanResult getSanitizedExposureNotification(ScanResult result) { - ScanRecord record = result.getScanRecord(); - // Remove the flags part of the payload, if present - if (record.getBytes().length > EXPOSURE_NOTIFICATION_FLAGS_LENGTH - && arrayStartsWith(record.getBytes(), EXPOSURE_NOTIFICATION_FLAGS_PREAMBLE)) { - record = - ScanRecord.parseFromBytes( - Arrays.copyOfRange( - record.getBytes(), - EXPOSURE_NOTIFICATION_FLAGS_LENGTH, - record.getBytes().length)); - } - - if (record.getBytes().length != EXPOSURE_NOTIFICATION_PAYLOAD_LENGTH) { - return null; - } - if (!arrayStartsWith(record.getBytes(), EXPOSURE_NOTIFICATION_PAYLOAD_PREAMBLE)) { - return null; - } - - return new ScanResult(null, 0, 0, 0, 0, 0, result.getRssi(), 0, record, 0); - } - - /** Callback method for a scan result. */ - public void onScanResult( - int eventType, - int addressType, - String address, - int primaryPhy, - int secondaryPhy, - int advertisingSid, - int txPower, - int rssi, - int periodicAdvInt, - byte[] advData, - String originalAddress) { - // When in testing mode, ignore all real-world events - if (mTestModeAccessor.isTestModeEnabled()) return; - - AppScanStats.recordScanRadioResultCount(); - onScanResultInternal( - eventType, - addressType, - address, - primaryPhy, - secondaryPhy, - advertisingSid, - txPower, - rssi, - periodicAdvInt, - advData, - originalAddress); - } - - // TODO(b/327849650): Refactor to reduce the visibility of this method. - public void onScanResultInternal( - int eventType, - int addressType, - String address, - int primaryPhy, - int secondaryPhy, - int advertisingSid, - int txPower, - int rssi, - int periodicAdvInt, - byte[] advData, - String originalAddress) { - Log.v( - TAG, - "onScanResult() - eventType=0x" - + Integer.toHexString(eventType) - + ", addressType=" - + addressType - + ", address=" - + BluetoothUtils.toAnonymizedAddress(address) - + ", primaryPhy=" - + primaryPhy - + ", secondaryPhy=" - + secondaryPhy - + ", advertisingSid=0x" - + Integer.toHexString(advertisingSid) - + ", txPower=" - + txPower - + ", rssi=" - + rssi - + ", periodicAdvInt=0x" - + Integer.toHexString(periodicAdvInt) - + ", originalAddress=" - + originalAddress); - - String identityAddress = mAdapterService.getIdentityAddress(address); - if (!address.equals(identityAddress)) { - Log.v( - TAG, - "found identityAddress of " - + address - + ", replace originalAddress as " - + identityAddress); - originalAddress = identityAddress; - } - - byte[] legacyAdvData = Arrays.copyOfRange(advData, 0, 62); - - for (ScanClient client : mScanManager.getRegularScanQueue()) { - ScannerMap.ScannerApp app = mScannerMap.getById(client.scannerId); - if (app == null) { - Log.v(TAG, "App is null; skip."); - continue; - } - - BluetoothDevice device = - BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(address, addressType); - - ScanSettings settings = client.settings; - byte[] scanRecordData; - // This is for compatibility with applications that assume fixed size scan data. - if (settings.getLegacy()) { - if ((eventType & ET_LEGACY_MASK) == 0) { - // If this is legacy scan, but nonlegacy result - skip. - Log.v(TAG, "Legacy scan, non legacy result; skip."); - continue; - } else { - // Some apps are used to fixed-size advertise data. - scanRecordData = legacyAdvData; - } - } else { - scanRecordData = advData; - } - - ScanRecord scanRecord = ScanRecord.parseFromBytes(scanRecordData); - ScanResult result = - new ScanResult( - device, - eventType, - primaryPhy, - secondaryPhy, - advertisingSid, - txPower, - rssi, - periodicAdvInt, - scanRecord, - SystemClock.elapsedRealtimeNanos()); - - if (client.hasDisavowedLocation) { - if (mLocationDenylistPredicate.test(result)) { - Log.i(TAG, "Skipping client for location deny list"); - continue; - } - } - - boolean hasPermission = hasScanResultPermission(client); - if (!hasPermission) { - for (String associatedDevice : client.associatedDevices) { - if (associatedDevice.equalsIgnoreCase(address)) { - hasPermission = true; - break; - } - } - } - if (!hasPermission && client.eligibleForSanitizedExposureNotification) { - ScanResult sanitized = getSanitizedExposureNotification(result); - if (sanitized != null) { - hasPermission = true; - result = sanitized; - } - } - boolean matchResult = matchesFilters(client, result, originalAddress); - if (!hasPermission || !matchResult) { - Log.v( - TAG, - "Skipping client: permission=" + hasPermission + " matches=" + matchResult); - continue; - } - - int callbackType = settings.getCallbackType(); - if (!(callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES - || callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH)) { - Log.v(TAG, "Skipping client: CALLBACK_TYPE_ALL_MATCHES"); - continue; - } - - try { - app.mAppScanStats.addResult(client.scannerId); - if (app.mCallback != null) { - app.mCallback.onScanResult(result); - } else { - Log.v(TAG, "Callback is null, sending scan results by pendingIntent"); - // Send the PendingIntent - ArrayList<ScanResult> results = new ArrayList<>(); - results.add(result); - sendResultsByPendingIntent( - app.mInfo, results, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); - } - } catch (RemoteException | PendingIntent.CanceledException e) { - Log.e(TAG, "Exception: " + e); - handleDeadScanClient(client); - } - } - } - - private void sendResultByPendingIntent( - PendingIntentInfo pii, ScanResult result, int callbackType, ScanClient client) { - ArrayList<ScanResult> results = new ArrayList<>(); - results.add(result); - try { - sendResultsByPendingIntent(pii, results, callbackType); - } catch (PendingIntent.CanceledException e) { - final long token = Binder.clearCallingIdentity(); - try { - stopScanInternal(client.scannerId); - unregisterScannerInternal(client.scannerId); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - - @SuppressWarnings("NonApiType") - private void sendResultsByPendingIntent( - PendingIntentInfo pii, ArrayList<ScanResult> results, int callbackType) - throws PendingIntent.CanceledException { - Intent extrasIntent = new Intent(); - extrasIntent.putParcelableArrayListExtra( - BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT, results); - extrasIntent.putExtra(BluetoothLeScanner.EXTRA_CALLBACK_TYPE, callbackType); - pii.intent.send(mAdapterService, 0, extrasIntent); - } - - private void sendErrorByPendingIntent(PendingIntentInfo pii, int errorCode) - throws PendingIntent.CanceledException { - Intent extrasIntent = new Intent(); - extrasIntent.putExtra(BluetoothLeScanner.EXTRA_ERROR_CODE, errorCode); - pii.intent.send(mAdapterService, 0, extrasIntent); - } - - /** Callback method for scanner registration. */ - public void onScannerRegistered(int status, int scannerId, long uuidLsb, long uuidMsb) - throws RemoteException { - UUID uuid = new UUID(uuidMsb, uuidLsb); - Log.d( - TAG, - "onScannerRegistered() - UUID=" - + uuid - + ", scannerId=" - + scannerId - + ", status=" - + status); - - // First check the callback map - ScannerMap.ScannerApp cbApp = mScannerMap.getByUuid(uuid); - if (cbApp != null) { - if (status == 0) { - cbApp.mId = scannerId; - // If app is callback based, setup a death recipient. App will initiate the start. - // Otherwise, if PendingIntent based, start the scan directly. - if (cbApp.mCallback != null) { - cbApp.linkToDeath(new ScannerDeathRecipient(scannerId, cbApp.mName)); - } else { - continuePiStartScan(scannerId, cbApp); - } - } else { - mScannerMap.remove(scannerId); - } - if (cbApp.mCallback != null) { - cbApp.mCallback.onScannerRegistered(status, scannerId); - } - } - } - - /** Determines if the given scan client has the appropriate permissions to receive callbacks. */ - private boolean hasScanResultPermission(final ScanClient client) { - if (client.hasNetworkSettingsPermission - || client.hasNetworkSetupWizardPermission - || client.hasScanWithoutLocationPermission) { - return true; - } - if (client.hasDisavowedLocation) { - return true; - } - return client.hasLocationPermission - && !Utils.blockedByLocationOff(mAdapterService, client.userHandle); - } - - // Check if a scan record matches a specific filters. - private boolean matchesFilters(ScanClient client, ScanResult scanResult) { - return matchesFilters(client, scanResult, null); - } - - // Check if a scan record matches a specific filters or original address - private boolean matchesFilters( - ScanClient client, ScanResult scanResult, String originalAddress) { - if (client.filters == null || client.filters.isEmpty()) { - // TODO: Do we really wanna return true here? - return true; - } - for (ScanFilter filter : client.filters) { - // Need to check the filter matches, and the original address without changing the API - if (filter.matches(scanResult)) { - return true; - } - if (originalAddress != null - && originalAddress.equalsIgnoreCase(filter.getDeviceAddress())) { - return true; - } - } - return false; - } - - private void handleDeadScanClient(ScanClient client) { - if (client.appDied) { - Log.w(TAG, "Already dead client " + client.scannerId); - return; - } - client.appDied = true; - if (client.stats != null) { - client.stats.isAppDead = true; - } - stopScanInternal(client.scannerId); - } - - /** Callback method for scan filter enablement/disablement. */ - public void onScanFilterEnableDisabled(int action, int status, int clientIf) { - Log.d( - TAG, - "onScanFilterEnableDisabled() - clientIf=" - + clientIf - + ", status=" - + status - + ", action=" - + action); - mScanManager.callbackDone(clientIf, status); - } - - /** Callback method for configuration of scan filter params. */ - public void onScanFilterParamsConfigured( - int action, int status, int clientIf, int availableSpace) { - Log.d( - TAG, - "onScanFilterParamsConfigured() - clientIf=" - + clientIf - + ", status=" - + status - + ", action=" - + action - + ", availableSpace=" - + availableSpace); - mScanManager.callbackDone(clientIf, status); - } - - /** Callback method for configuration of scan filter. */ - public void onScanFilterConfig( - int action, int status, int clientIf, int filterType, int availableSpace) { - Log.d( - TAG, - "onScanFilterConfig() - clientIf=" - + clientIf - + ", action = " - + action - + " status = " - + status - + ", filterType=" - + filterType - + ", availableSpace=" - + availableSpace); - - mScanManager.callbackDone(clientIf, status); - } - - /** Callback method for configuration of batch scan storage. */ - public void onBatchScanStorageConfigured(int status, int clientIf) { - Log.d(TAG, "onBatchScanStorageConfigured() - clientIf=" + clientIf + ", status=" + status); - mScanManager.callbackDone(clientIf, status); - } - - /** Callback method for start/stop of batch scan. */ - // TODO: split into two different callbacks : onBatchScanStarted and onBatchScanStopped. - public void onBatchScanStartStopped(int startStopAction, int status, int clientIf) { - Log.d( - TAG, - "onBatchScanStartStopped() - clientIf=" - + clientIf - + ", status=" - + status - + ", startStopAction=" - + startStopAction); - mScanManager.callbackDone(clientIf, status); - } - - ScanClient findBatchScanClientById(int scannerId) { - for (ScanClient client : mScanManager.getBatchScanQueue()) { - if (client.scannerId == scannerId) { - return client; - } - } - return null; - } - - /** Callback method for batch scan reports */ - public void onBatchScanReports( - int status, int scannerId, int reportType, int numRecords, byte[] recordData) - throws RemoteException { - // When in testing mode, ignore all real-world events - if (mTestModeAccessor.isTestModeEnabled()) return; - - AppScanStats.recordBatchScanRadioResultCount(numRecords); - onBatchScanReportsInternal(status, scannerId, reportType, numRecords, recordData); - } - - @VisibleForTesting - void onBatchScanReportsInternal( - int status, int scannerId, int reportType, int numRecords, byte[] recordData) - throws RemoteException { - Log.d( - TAG, - "onBatchScanReports() - scannerId=" - + scannerId - + ", status=" - + status - + ", reportType=" - + reportType - + ", numRecords=" - + numRecords); - - Set<ScanResult> results = parseBatchScanResults(numRecords, reportType, recordData); - if (reportType == ScanManager.SCAN_RESULT_TYPE_TRUNCATED) { - // We only support single client for truncated mode. - ScannerMap.ScannerApp app = mScannerMap.getById(scannerId); - if (app == null) { - return; - } - - ScanClient client = findBatchScanClientById(scannerId); - if (client == null) { - return; - } - - ArrayList<ScanResult> permittedResults; - if (hasScanResultPermission(client)) { - permittedResults = new ArrayList<ScanResult>(results); - } else { - permittedResults = new ArrayList<ScanResult>(); - for (ScanResult scanResult : results) { - for (String associatedDevice : client.associatedDevices) { - if (associatedDevice.equalsIgnoreCase( - scanResult.getDevice().getAddress())) { - permittedResults.add(scanResult); - } - } - } - if (permittedResults.isEmpty()) { - return; - } - } - - if (client.hasDisavowedLocation) { - permittedResults.removeIf(mLocationDenylistPredicate); - } - - if (app.mCallback != null) { - app.mCallback.onBatchScanResults(permittedResults); - } else { - // PendingIntent based - try { - sendResultsByPendingIntent( - app.mInfo, permittedResults, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); - } catch (PendingIntent.CanceledException e) { - Log.d(TAG, "Exception while sending result", e); - } - } - } else { - for (ScanClient client : mScanManager.getFullBatchScanQueue()) { - // Deliver results for each client. - deliverBatchScan(client, results); - } - } - mScanManager.callbackDone(scannerId, status); - } - - @SuppressWarnings("NonApiType") - private void sendBatchScanResults( - ScannerMap.ScannerApp app, ScanClient client, ArrayList<ScanResult> results) { - try { - if (app.mCallback != null) { - if (mScanManager.isAutoBatchScanClientEnabled(client)) { - Log.d(TAG, "sendBatchScanResults() to onScanResult()" + client); - for (ScanResult result : results) { - app.mAppScanStats.addResult(client.scannerId); - app.mCallback.onScanResult(result); - } - } else { - Log.d(TAG, "sendBatchScanResults() to onBatchScanResults()" + client); - app.mCallback.onBatchScanResults(results); - } - } else { - sendResultsByPendingIntent( - app.mInfo, results, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); - } - } catch (RemoteException | PendingIntent.CanceledException e) { - Log.e(TAG, "Exception: " + e); - handleDeadScanClient(client); - } - } - - // Check and deliver scan results for different scan clients. - private void deliverBatchScan(ScanClient client, Set<ScanResult> allResults) - throws RemoteException { - ScannerMap.ScannerApp app = mScannerMap.getById(client.scannerId); - if (app == null) { - return; - } - - ArrayList<ScanResult> permittedResults; - if (hasScanResultPermission(client)) { - permittedResults = new ArrayList<ScanResult>(allResults); - } else { - permittedResults = new ArrayList<ScanResult>(); - for (ScanResult scanResult : allResults) { - for (String associatedDevice : client.associatedDevices) { - if (associatedDevice.equalsIgnoreCase(scanResult.getDevice().getAddress())) { - permittedResults.add(scanResult); - } - } - } - if (permittedResults.isEmpty()) { - return; - } - } - - if (client.filters == null || client.filters.isEmpty()) { - sendBatchScanResults(app, client, permittedResults); - // TODO: Question to reviewer: Shouldn't there be a return here? - } - // Reconstruct the scan results. - ArrayList<ScanResult> results = new ArrayList<ScanResult>(); - for (ScanResult scanResult : permittedResults) { - if (matchesFilters(client, scanResult)) { - results.add(scanResult); - } - } - sendBatchScanResults(app, client, results); - } - - private Set<ScanResult> parseBatchScanResults( - int numRecords, int reportType, byte[] batchRecord) { - if (numRecords == 0) { - return Collections.emptySet(); - } - Log.d(TAG, "current time is " + SystemClock.elapsedRealtimeNanos()); - if (reportType == ScanManager.SCAN_RESULT_TYPE_TRUNCATED) { - return parseTruncatedResults(numRecords, batchRecord); - } else { - return parseFullResults(numRecords, batchRecord); - } - } - - private Set<ScanResult> parseTruncatedResults(int numRecords, byte[] batchRecord) { - Log.d(TAG, "batch record " + Arrays.toString(batchRecord)); - Set<ScanResult> results = new HashSet<ScanResult>(numRecords); - long now = SystemClock.elapsedRealtimeNanos(); - for (int i = 0; i < numRecords; ++i) { - byte[] record = - extractBytes(batchRecord, i * TRUNCATED_RESULT_SIZE, TRUNCATED_RESULT_SIZE); - byte[] address = extractBytes(record, 0, 6); - reverse(address); - BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); - int rssi = record[8]; - long timestampNanos = now - parseTimestampNanos(extractBytes(record, 9, 2)); - results.add( - new ScanResult( - device, ScanRecord.parseFromBytes(new byte[0]), rssi, timestampNanos)); - } - return results; - } - - @VisibleForTesting - long parseTimestampNanos(byte[] data) { - long timestampUnit = NumberUtils.littleEndianByteArrayToInt(data); - // Timestamp is in every 50 ms. - return TimeUnit.MILLISECONDS.toNanos(timestampUnit * 50); - } - - private Set<ScanResult> parseFullResults(int numRecords, byte[] batchRecord) { - Log.d(TAG, "Batch record : " + Arrays.toString(batchRecord)); - Set<ScanResult> results = new HashSet<ScanResult>(numRecords); - int position = 0; - long now = SystemClock.elapsedRealtimeNanos(); - while (position < batchRecord.length) { - byte[] address = extractBytes(batchRecord, position, 6); - // TODO: remove temp hack. - reverse(address); - BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); - position += 6; - // Skip address type. - position++; - // Skip tx power level. - position++; - int rssi = batchRecord[position++]; - long timestampNanos = now - parseTimestampNanos(extractBytes(batchRecord, position, 2)); - position += 2; - - // Combine advertise packet and scan response packet. - int advertisePacketLen = batchRecord[position++]; - byte[] advertiseBytes = extractBytes(batchRecord, position, advertisePacketLen); - position += advertisePacketLen; - int scanResponsePacketLen = batchRecord[position++]; - byte[] scanResponseBytes = extractBytes(batchRecord, position, scanResponsePacketLen); - position += scanResponsePacketLen; - byte[] scanRecord = new byte[advertisePacketLen + scanResponsePacketLen]; - System.arraycopy(advertiseBytes, 0, scanRecord, 0, advertisePacketLen); - System.arraycopy( - scanResponseBytes, 0, scanRecord, advertisePacketLen, scanResponsePacketLen); - Log.d(TAG, "ScanRecord : " + Arrays.toString(scanRecord)); - results.add( - new ScanResult( - device, ScanRecord.parseFromBytes(scanRecord), rssi, timestampNanos)); - } - return results; - } - - // Reverse byte array. - private void reverse(byte[] address) { - int len = address.length; - for (int i = 0; i < len / 2; ++i) { - byte b = address[i]; - address[i] = address[len - 1 - i]; - address[len - 1 - i] = b; - } - } - - // Helper method to extract bytes from byte array. - private static byte[] extractBytes(byte[] scanRecord, int start, int length) { - byte[] bytes = new byte[length]; - System.arraycopy(scanRecord, start, bytes, 0, length); - return bytes; - } - - public void onBatchScanThresholdCrossed(int clientIf) { - Log.d(TAG, "onBatchScanThresholdCrossed() - clientIf=" + clientIf); - flushPendingBatchResultsInternal(clientIf); - } - - public AdvtFilterOnFoundOnLostInfo createOnTrackAdvFoundLostObject( - int clientIf, - int advPktLen, - byte[] advPkt, - int scanRspLen, - byte[] scanRsp, - int filtIndex, - int advState, - int advInfoPresent, - String address, - int addrType, - int txPower, - int rssiValue, - int timeStamp) { - - return new AdvtFilterOnFoundOnLostInfo( - clientIf, - advPktLen, - advPkt, - scanRspLen, - scanRsp, - filtIndex, - advState, - advInfoPresent, - address, - addrType, - txPower, - rssiValue, - timeStamp); - } - - public void onTrackAdvFoundLost(AdvtFilterOnFoundOnLostInfo trackingInfo) - throws RemoteException { - Log.d( - TAG, - "onTrackAdvFoundLost() - scannerId= " - + trackingInfo.getClientIf() - + " address = " - + trackingInfo.getAddress() - + " addressType = " - + trackingInfo.getAddressType() - + " adv_state = " - + trackingInfo.getAdvState()); - - ScannerMap.ScannerApp app = mScannerMap.getById(trackingInfo.getClientIf()); - if (app == null) { - Log.e(TAG, "app is null"); - return; - } - - BluetoothDevice device = - BluetoothAdapter.getDefaultAdapter() - .getRemoteLeDevice( - trackingInfo.getAddress(), trackingInfo.getAddressType()); - int advertiserState = trackingInfo.getAdvState(); - ScanResult result = - new ScanResult( - device, - ScanRecord.parseFromBytes(trackingInfo.getResult()), - trackingInfo.getRSSIValue(), - SystemClock.elapsedRealtimeNanos()); - - for (ScanClient client : mScanManager.getRegularScanQueue()) { - if (client.scannerId == trackingInfo.getClientIf()) { - ScanSettings settings = client.settings; - if ((advertiserState == ADVT_STATE_ONFOUND) - && ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) - != 0)) { - if (app.mCallback != null) { - app.mCallback.onFoundOrLost(true, result); - } else { - sendResultByPendingIntent( - app.mInfo, result, ScanSettings.CALLBACK_TYPE_FIRST_MATCH, client); - } - } else if ((advertiserState == ADVT_STATE_ONLOST) - && ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) - != 0)) { - if (app.mCallback != null) { - app.mCallback.onFoundOrLost(false, result); - } else { - sendResultByPendingIntent( - app.mInfo, result, ScanSettings.CALLBACK_TYPE_MATCH_LOST, client); - } - } else { - Log.d( - TAG, - "Not reporting onlost/onfound : " - + advertiserState - + " scannerId = " - + client.scannerId - + " callbackType " - + settings.getCallbackType()); - } - } - } - } - - /** Callback method for configuration of scan parameters. */ - public void onScanParamSetupCompleted(int status, int scannerId) { - Log.d(TAG, "onScanParamSetupCompleted() - scannerId=" + scannerId + ", status=" + status); - ScannerMap.ScannerApp app = mScannerMap.getById(scannerId); - if (app == null || app.mCallback == null) { - Log.e(TAG, "Advertise app or callback is null"); - return; - } - } - - // callback from ScanManager for dispatch of errors apps. - public void onScanManagerErrorCallback(int scannerId, int errorCode) throws RemoteException { - ScannerMap.ScannerApp app = mScannerMap.getById(scannerId); - if (app == null) { - Log.e(TAG, "App null"); - return; - } - if (app.mCallback != null) { - app.mCallback.onScanManagerErrorCallback(errorCode); - } else { - try { - sendErrorByPendingIntent(app.mInfo, errorCode); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "Error sending error code via PendingIntent:" + e); - } - } - } - - public int msftMonitorHandleFromFilterIndex(int filter_index) { - if (!mFilterIndexToMsftAdvMonitorMap.containsKey(filter_index)) { - Log.e(TAG, "Monitor with filter_index'" + filter_index + "' does not exist"); - return -1; - } - return mFilterIndexToMsftAdvMonitorMap.get(filter_index); - } - - public void onMsftAdvMonitorAdd(int filter_index, int monitor_handle, int status) { - if (status != 0) { - Log.e( - TAG, - "Error adding advertisement monitor with filter index '" + filter_index + "'"); - return; - } - if (mFilterIndexToMsftAdvMonitorMap.containsKey(filter_index)) { - Log.e(TAG, "Monitor with filter_index'" + filter_index + "' already added"); - return; - } - mFilterIndexToMsftAdvMonitorMap.put(filter_index, monitor_handle); - } - - public void onMsftAdvMonitorRemove(int filter_index, int status) { - if (status != 0) { - Log.e( - TAG, - "Error removing advertisement monitor with filter index '" - + filter_index - + "'"); - } - if (!mFilterIndexToMsftAdvMonitorMap.containsKey(filter_index)) { - Log.e(TAG, "Monitor with filter_index'" + filter_index + "' does not exist"); - return; - } - mFilterIndexToMsftAdvMonitorMap.remove(filter_index); - } - - public void onMsftAdvMonitorEnable(int status) { - if (status != 0) { - Log.e(TAG, "Error enabling advertisement monitor"); - } - } - - /************************************************************************** - * GATT Service functions - Shared CLIENT/SERVER - *************************************************************************/ - - @RequiresPermission(BLUETOOTH_SCAN) - public void registerScanner( - IScannerCallback callback, WorkSource workSource, AttributionSource attributionSource) { - if (!Utils.checkScanPermissionForDataDelivery( - mAdapterService, attributionSource, "ScanHelper registerScanner")) { - return; - } - - enforceImpersonatationPermissionIfNeeded(workSource); - - AppScanStats app = mScannerMap.getAppScanStatsByUid(Binder.getCallingUid()); - if (app != null - && app.isScanningTooFrequently() - && !Utils.checkCallerHasPrivilegedPermission(mAdapterService)) { - Log.e(TAG, "App '" + app.mAppName + "' is scanning too frequently"); - try { - callback.onScannerRegistered(ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY, -1); - } catch (RemoteException e) { - Log.e(TAG, "Exception: " + e); - } - return; - } - registerScannerInternal(callback, attributionSource, workSource); - } - - /** Intended for internal use within the Bluetooth app. Bypass permission check */ - public void registerScannerInternal( - IScannerCallback callback, AttributionSource attrSource, WorkSource workSource) { - UUID uuid = UUID.randomUUID(); - Log.d(TAG, "registerScanner() - UUID=" + uuid); - - mScannerMap.add(uuid, attrSource, workSource, callback, mAdapterService, this); - mScanManager.registerScanner(uuid); - } - - @RequiresPermission(BLUETOOTH_SCAN) - public void unregisterScanner(int scannerId, AttributionSource attributionSource) { - if (!Utils.checkScanPermissionForDataDelivery( - mAdapterService, attributionSource, "ScanHelper unregisterScanner")) { - return; - } - - unregisterScannerInternal(scannerId); - } - - /** Intended for internal use within the Bluetooth app. Bypass permission check */ - public void unregisterScannerInternal(int scannerId) { - Log.d(TAG, "unregisterScanner() - scannerId=" + scannerId); - mScannerMap.remove(scannerId); - mScanManager.unregisterScanner(scannerId); - } - - private List<String> getAssociatedDevices(String callingPackage) { - if (mCompanionManager == null) { - return Collections.emptyList(); - } - - final long identity = Binder.clearCallingIdentity(); - try { - return mCompanionManager.getAllAssociations().stream() - .filter( - info -> - info.getPackageName().equals(callingPackage) - && !info.isSelfManaged() - && info.getDeviceMacAddress() != null) - .map(AssociationInfo::getDeviceMacAddress) - .map(MacAddress::toString) - .collect(Collectors.toList()); - } catch (SecurityException se) { - // Not an app with associated devices - } catch (Exception e) { - Log.e(TAG, "Cannot check device associations for " + callingPackage, e); - } finally { - Binder.restoreCallingIdentity(identity); - } - return Collections.emptyList(); - } - - @RequiresPermission(BLUETOOTH_SCAN) - public void startScan( - int scannerId, - ScanSettings settings, - List<ScanFilter> filters, - AttributionSource attributionSource) { - Log.d(TAG, "start scan with filters"); - - if (!Utils.checkScanPermissionForDataDelivery( - mAdapterService, attributionSource, "Starting GATT scan.")) { - return; - } - - enforcePrivilegedPermissionIfNeeded(settings); - String callingPackage = attributionSource.getPackageName(); - settings = enforceReportDelayFloor(settings); - enforcePrivilegedPermissionIfNeeded(filters); - final ScanClient scanClient = new ScanClient(scannerId, settings, filters); - scanClient.userHandle = Binder.getCallingUserHandle(); - mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); - scanClient.eligibleForSanitizedExposureNotification = - callingPackage.equals(mExposureNotificationPackage); - - scanClient.hasDisavowedLocation = - Utils.hasDisavowedLocationForScan( - mAdapterService, attributionSource, mTestModeAccessor.isTestModeEnabled()); - - scanClient.isQApp = - checkCallerTargetSdk(mAdapterService, callingPackage, Build.VERSION_CODES.Q); - if (!scanClient.hasDisavowedLocation) { - if (scanClient.isQApp) { - scanClient.hasLocationPermission = - Utils.checkCallerHasFineLocation( - mAdapterService, attributionSource, scanClient.userHandle); - } else { - scanClient.hasLocationPermission = - Utils.checkCallerHasCoarseOrFineLocation( - mAdapterService, attributionSource, scanClient.userHandle); - } - } - scanClient.hasNetworkSettingsPermission = - Utils.checkCallerHasNetworkSettingsPermission(mAdapterService); - scanClient.hasNetworkSetupWizardPermission = - Utils.checkCallerHasNetworkSetupWizardPermission(mAdapterService); - scanClient.hasScanWithoutLocationPermission = - Utils.checkCallerHasScanWithoutLocationPermission(mAdapterService); - scanClient.associatedDevices = getAssociatedDevices(callingPackage); - - startScan(scannerId, settings, filters, scanClient); - } - - /** Intended for internal use within the Bluetooth app. Bypass permission check */ - public void startScanInternal(int scannerId, ScanSettings settings, List<ScanFilter> filters) { - final ScanClient scanClient = new ScanClient(scannerId, settings, filters); - scanClient.userHandle = Binder.getCallingUserHandle(); - scanClient.eligibleForSanitizedExposureNotification = false; - scanClient.hasDisavowedLocation = false; - scanClient.isQApp = true; - scanClient.hasNetworkSettingsPermission = - Utils.checkCallerHasNetworkSettingsPermission(mAdapterService); - scanClient.hasNetworkSetupWizardPermission = - Utils.checkCallerHasNetworkSetupWizardPermission(mAdapterService); - scanClient.hasScanWithoutLocationPermission = - Utils.checkCallerHasScanWithoutLocationPermission(mAdapterService); - scanClient.associatedDevices = Collections.emptyList(); - - startScan(scannerId, settings, filters, scanClient); - } - - private void startScan( - int scannerId, ScanSettings settings, List<ScanFilter> filters, ScanClient scanClient) { - AppScanStats app = mScannerMap.getAppScanStatsById(scannerId); - if (app != null) { - scanClient.stats = app; - boolean isFilteredScan = (filters != null) && !filters.isEmpty(); - boolean isCallbackScan = false; - - ScannerMap.ScannerApp cbApp = mScannerMap.getById(scannerId); - if (cbApp != null) { - isCallbackScan = cbApp.mCallback != null; - } - app.recordScanStart( - settings, - filters, - isFilteredScan, - isCallbackScan, - scannerId, - cbApp == null ? null : cbApp.mAttributionTag); - } - - mScanManager.startScan(scanClient); - } - - @RequiresPermission(BLUETOOTH_SCAN) - public void registerPiAndStartScan( - PendingIntent pendingIntent, - ScanSettings settings, - List<ScanFilter> filters, - AttributionSource attributionSource) { - Log.d(TAG, "start scan with filters, for PendingIntent"); - - if (!Utils.checkScanPermissionForDataDelivery( - mAdapterService, attributionSource, "Starting GATT scan.")) { - return; - } - enforcePrivilegedPermissionIfNeeded(settings); - settings = enforceReportDelayFloor(settings); - enforcePrivilegedPermissionIfNeeded(filters); - UUID uuid = UUID.randomUUID(); - String callingPackage = attributionSource.getPackageName(); - int callingUid = attributionSource.getUid(); - PendingIntentInfo piInfo = new PendingIntentInfo(); - piInfo.intent = pendingIntent; - piInfo.settings = settings; - piInfo.filters = filters; - piInfo.callingPackage = callingPackage; - piInfo.callingUid = callingUid; - Log.d( - TAG, - "startScan(PI) -" - + (" UUID=" + uuid) - + (" Package=" + callingPackage) - + (" UID=" + callingUid)); - - // Don't start scan if the Pi scan already in mScannerMap. - if (mScannerMap.getByPendingIntentInfo(piInfo) != null) { - Log.d(TAG, "Don't startScan(PI) since the same Pi scan already in mScannerMap."); - return; - } - - ScannerMap.ScannerApp app = - mScannerMap.add(uuid, attributionSource, piInfo, mAdapterService, this); - - app.mUserHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid()); - mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); - app.mEligibleForSanitizedExposureNotification = - callingPackage.equals(mExposureNotificationPackage); - - app.mHasDisavowedLocation = - Utils.hasDisavowedLocationForScan( - mAdapterService, attributionSource, mTestModeAccessor.isTestModeEnabled()); - - if (!app.mHasDisavowedLocation) { - try { - if (checkCallerTargetSdk(mAdapterService, callingPackage, Build.VERSION_CODES.Q)) { - app.mHasLocationPermission = - Utils.checkCallerHasFineLocation( - mAdapterService, attributionSource, app.mUserHandle); - } else { - app.mHasLocationPermission = - Utils.checkCallerHasCoarseOrFineLocation( - mAdapterService, attributionSource, app.mUserHandle); - } - } catch (SecurityException se) { - // No need to throw here. Just mark as not granted. - app.mHasLocationPermission = false; - } - } - app.mHasNetworkSettingsPermission = - Utils.checkCallerHasNetworkSettingsPermission(mAdapterService); - app.mHasNetworkSetupWizardPermission = - Utils.checkCallerHasNetworkSetupWizardPermission(mAdapterService); - app.mHasScanWithoutLocationPermission = - Utils.checkCallerHasScanWithoutLocationPermission(mAdapterService); - app.mAssociatedDevices = getAssociatedDevices(callingPackage); - mScanManager.registerScanner(uuid); - - // If this fails, we should stop the scan immediately. - if (!pendingIntent.addCancelListener(Runnable::run, mScanIntentCancelListener)) { - Log.d(TAG, "scanning PendingIntent is already cancelled, stopping scan."); - stopScan(pendingIntent, attributionSource); - } - } - - /** Start a scan with pending intent. */ - public void continuePiStartScan(int scannerId, ScannerMap.ScannerApp app) { - final PendingIntentInfo piInfo = app.mInfo; - final ScanClient scanClient = - new ScanClient(scannerId, piInfo.settings, piInfo.filters, piInfo.callingUid); - scanClient.hasLocationPermission = app.mHasLocationPermission; - scanClient.userHandle = app.mUserHandle; - scanClient.isQApp = checkCallerTargetSdk(mAdapterService, app.mName, Build.VERSION_CODES.Q); - scanClient.eligibleForSanitizedExposureNotification = - app.mEligibleForSanitizedExposureNotification; - scanClient.hasNetworkSettingsPermission = app.mHasNetworkSettingsPermission; - scanClient.hasNetworkSetupWizardPermission = app.mHasNetworkSetupWizardPermission; - scanClient.hasScanWithoutLocationPermission = app.mHasScanWithoutLocationPermission; - scanClient.associatedDevices = app.mAssociatedDevices; - scanClient.hasDisavowedLocation = app.mHasDisavowedLocation; - - AppScanStats scanStats = mScannerMap.getAppScanStatsById(scannerId); - if (scanStats != null) { - scanClient.stats = scanStats; - boolean isFilteredScan = (piInfo.filters != null) && !piInfo.filters.isEmpty(); - scanStats.recordScanStart( - piInfo.settings, - piInfo.filters, - isFilteredScan, - false, - scannerId, - app.mAttributionTag); - } - - mScanManager.startScan(scanClient); - } - - @RequiresPermission(BLUETOOTH_SCAN) - public void flushPendingBatchResults(int scannerId, AttributionSource attributionSource) { - if (!Utils.checkScanPermissionForDataDelivery( - mAdapterService, attributionSource, "ScanHelper flushPendingBatchResults")) { - return; - } - flushPendingBatchResultsInternal(scannerId); - } - - private void flushPendingBatchResultsInternal(int scannerId) { - Log.d(TAG, "flushPendingBatchResultsInternal - scannerId=" + scannerId); - mScanManager.flushBatchScanResults(new ScanClient(scannerId)); - } - - @RequiresPermission(BLUETOOTH_SCAN) - public void stopScan(int scannerId, AttributionSource attributionSource) { - if (!Utils.checkScanPermissionForDataDelivery( - mAdapterService, attributionSource, "ScanHelper stopScan")) { - return; - } - stopScanInternal(scannerId); - } - - /** Intended for internal use within the Bluetooth app. Bypass permission check */ - public void stopScanInternal(int scannerId) { - int scanQueueSize = - mScanManager.getBatchScanQueue().size() + mScanManager.getRegularScanQueue().size(); - Log.d(TAG, "stopScan() - queue size =" + scanQueueSize); - - AppScanStats app = mScannerMap.getAppScanStatsById(scannerId); - if (app != null) { - app.recordScanStop(scannerId); - } - - mScanManager.stopScan(scannerId); - } - - @RequiresPermission(BLUETOOTH_SCAN) - public void stopScan(PendingIntent intent, AttributionSource attributionSource) { - if (!Utils.checkScanPermissionForDataDelivery( - mAdapterService, attributionSource, "ScanHelper stopScan")) { - return; - } - stopScanInternal(intent); - } - - /** Intended for internal use within the Bluetooth app. Bypass permission check */ - private void stopScanInternal(PendingIntent intent) { - PendingIntentInfo pii = new PendingIntentInfo(); - pii.intent = intent; - ScannerMap.ScannerApp app = mScannerMap.getByPendingIntentInfo(pii); - Log.v(TAG, "stopScan(PendingIntent): app found = " + app); - if (app != null) { - intent.removeCancelListener(mScanIntentCancelListener); - final int scannerId = app.mId; - stopScanInternal(scannerId); - // Also unregister the scanner - unregisterScannerInternal(scannerId); - } - } - - /************************************************************************** - * PERIODIC SCANNING - *************************************************************************/ - @RequiresPermission(BLUETOOTH_SCAN) - public void registerSync( - ScanResult scanResult, - int skip, - int timeout, - IPeriodicAdvertisingCallback callback, - AttributionSource attributionSource) { - if (!Utils.checkScanPermissionForDataDelivery( - mAdapterService, attributionSource, "ScanHelper registerSync")) { - return; - } - mPeriodicScanManager.startSync(scanResult, skip, timeout, callback); - } - - @RequiresPermission(BLUETOOTH_SCAN) - public void unregisterSync( - IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) { - if (!Utils.checkScanPermissionForDataDelivery( - mAdapterService, attributionSource, "ScanHelper unregisterSync")) { - return; - } - mPeriodicScanManager.stopSync(callback); - } - - @RequiresPermission(BLUETOOTH_SCAN) - public void transferSync( - BluetoothDevice bda, - int serviceData, - int syncHandle, - AttributionSource attributionSource) { - if (!Utils.checkScanPermissionForDataDelivery( - mAdapterService, attributionSource, "ScanHelper transferSync")) { - return; - } - mPeriodicScanManager.transferSync(bda, serviceData, syncHandle); - } - - @RequiresPermission(BLUETOOTH_SCAN) - public void transferSetInfo( - BluetoothDevice bda, - int serviceData, - int advHandle, - IPeriodicAdvertisingCallback callback, - AttributionSource attributionSource) { - if (!Utils.checkScanPermissionForDataDelivery( - mAdapterService, attributionSource, "ScanHelper transferSetInfo")) { - return; - } - mPeriodicScanManager.transferSetInfo(bda, serviceData, advHandle, callback); - } - - @RequiresPermission(BLUETOOTH_SCAN) - public int numHwTrackFiltersAvailable(AttributionSource attributionSource) { - if (!Utils.checkScanPermissionForDataDelivery( - mAdapterService, attributionSource, "ScanHelper numHwTrackFiltersAvailable")) { - return 0; - } - return (mAdapterService.getTotalNumOfTrackableAdvertisements() - - getCurrentUsedTrackingAdvertisement()); - } - - /** - * DeathRecipient handler used to unregister applications that disconnect ungracefully (ie. - * crash or forced close). - */ - class ScannerDeathRecipient implements IBinder.DeathRecipient { - int mScannerId; - private String mPackageName; - - ScannerDeathRecipient(int scannerId, String packageName) { - mScannerId = scannerId; - mPackageName = packageName; - } - - @Override - public void binderDied() { - Log.d( - TAG, - "Binder is dead - unregistering scanner (" - + mPackageName - + " " - + mScannerId - + ")!"); - - ScanClient client = getScanClient(mScannerId); - if (client != null) { - handleDeadScanClient(client); - } - } - - private ScanClient getScanClient(int clientIf) { - for (ScanClient client : mScanManager.getRegularScanQueue()) { - if (client.scannerId == clientIf) { - return client; - } - } - for (ScanClient client : mScanManager.getBatchScanQueue()) { - if (client.scannerId == clientIf) { - return client; - } - } - return null; - } - } - - private boolean needsPrivilegedPermissionForScan(ScanSettings settings) { - // BLE scan only mode needs special permission. - if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) { - return true; - } - - // Regular scan, no special permission. - if (settings == null) { - return false; - } - - // Ambient discovery mode, needs privileged permission. - if (settings.getScanMode() == ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY) { - return true; - } - - // Regular scan, no special permission. - if (settings.getReportDelayMillis() == 0) { - return false; - } - - // Batch scan, truncated mode needs permission. - return settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_ABBREVIATED; - } - - /* - * The {@link ScanFilter#setDeviceAddress} API overloads are @SystemApi access methods. This - * requires that the permissions be BLUETOOTH_PRIVILEGED. - */ - @SuppressLint("AndroidFrameworkRequiresPermission") - private void enforcePrivilegedPermissionIfNeeded(List<ScanFilter> filters) { - Log.d(TAG, "enforcePrivilegedPermissionIfNeeded(" + filters + ")"); - // Some 3p API cases may have null filters, need to allow - if (filters != null) { - for (ScanFilter filter : filters) { - // The only case to enforce here is if there is an address - // If there is an address, enforce if the correct combination criteria is met. - if (filter.getDeviceAddress() != null) { - // At this point we have an address, that means a caller used the - // setDeviceAddress(address) public API for the ScanFilter - // We don't want to enforce if the type is PUBLIC and the IRK is null - // However, if we have a different type that means the caller used a new - // @SystemApi such as setDeviceAddress(address, type) or - // setDeviceAddress(address, type, irk) which are both @SystemApi and require - // permissions to be enforced - if (filter.getAddressType() == BluetoothDevice.ADDRESS_TYPE_PUBLIC - && filter.getIrk() == null) { - // Do not enforce - } else { - mAdapterService.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); - } - } - } - } - } - - @SuppressLint("AndroidFrameworkRequiresPermission") - private void enforcePrivilegedPermissionIfNeeded(ScanSettings settings) { - if (needsPrivilegedPermissionForScan(settings)) { - mAdapterService.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); - } - } - - // Enforce caller has UPDATE_DEVICE_STATS permission, which allows the caller to blame other - // apps for Bluetooth usage. A {@link SecurityException} will be thrown if the caller app does - // not have UPDATE_DEVICE_STATS permission. - @RequiresPermission(UPDATE_DEVICE_STATS) - private void enforceImpersonatationPermission() { - mAdapterService.enforceCallingOrSelfPermission( - UPDATE_DEVICE_STATS, "Need UPDATE_DEVICE_STATS permission"); - } - - @SuppressLint("AndroidFrameworkRequiresPermission") - private void enforceImpersonatationPermissionIfNeeded(WorkSource workSource) { - if (workSource != null) { - enforceImpersonatationPermission(); - } - } - - /** - * Ensures the report delay is either 0 or at least the floor value (5000ms) - * - * @param settings are the scan settings passed into a request to start le scanning - * @return the passed in ScanSettings object if the report delay is 0 or above the floor value; - * a new ScanSettings object with the report delay being the floor value if the original - * report delay was between 0 and the floor value (exclusive of both) - */ - @VisibleForTesting - ScanSettings enforceReportDelayFloor(ScanSettings settings) { - if (settings.getReportDelayMillis() == 0) { - return settings; - } - - // Need to clear identity to pass device config permission check - final long callerToken = Binder.clearCallingIdentity(); - try { - long floor = - DeviceConfig.getLong( - DeviceConfig.NAMESPACE_BLUETOOTH, - "report_delay", - DEFAULT_REPORT_DELAY_FLOOR); - - if (settings.getReportDelayMillis() > floor) { - return settings; - } else { - return new ScanSettings.Builder() - .setCallbackType(settings.getCallbackType()) - .setLegacy(settings.getLegacy()) - .setMatchMode(settings.getMatchMode()) - .setNumOfMatches(settings.getNumOfMatches()) - .setPhy(settings.getPhy()) - .setReportDelay(floor) - .setScanMode(settings.getScanMode()) - .setScanResultType(settings.getScanResultType()) - .build(); - } - } finally { - Binder.restoreCallingIdentity(callerToken); - } - } - - public void addScanEvent(BluetoothMetricsProto.ScanEvent event) { - synchronized (mScanEvents) { - if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT) { - mScanEvents.remove(); - } - mScanEvents.add(event); - } - } - - public void dumpProto(BluetoothMetricsProto.BluetoothLog.Builder builder) { - synchronized (mScanEvents) { - builder.addAllScanEvent(mScanEvents); - } - } -} diff --git a/android/app/src/com/android/bluetooth/mapclient/MapClientService.java b/android/app/src/com/android/bluetooth/mapclient/MapClientService.java index ecc0ac4d0f..851ead30be 100644 --- a/android/app/src/com/android/bluetooth/mapclient/MapClientService.java +++ b/android/app/src/com/android/bluetooth/mapclient/MapClientService.java @@ -19,6 +19,9 @@ package com.android.bluetooth.mapclient; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; +import static java.util.Objects.requireNonNull; +import static java.util.Objects.requireNonNullElseGet; + import android.Manifest; import android.annotation.RequiresPermission; import android.app.PendingIntent; @@ -29,7 +32,6 @@ import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothMapClient; import android.bluetooth.SdpMasRecord; import android.content.AttributionSource; -import android.content.Context; import android.net.Uri; import android.os.Handler; import android.os.Looper; @@ -50,7 +52,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; public class MapClientService extends ProfileService { @@ -60,24 +61,40 @@ public class MapClientService extends ProfileService { private final Map<BluetoothDevice, MceStateMachine> mMapInstanceMap = new ConcurrentHashMap<>(1); - private MnsService mMnsServer; - private AdapterService mAdapterService; - private DatabaseManager mDatabaseManager; - private static MapClientService sMapClientService; - @VisibleForTesting private Handler mHandler; + private final AdapterService mAdapterService; + private final DatabaseManager mDatabaseManager; + private final MnsService mMnsServer; + private final Looper mStateMachinesLooper; + private final Handler mHandler; - private Looper mSmLooper; + private static MapClientService sMapClientService; - public MapClientService(Context ctx) { - super(ctx); + public MapClientService(AdapterService adapterService) { + this(adapterService, null, null); } @VisibleForTesting - MapClientService(Context ctx, Looper looper, MnsService mnsServer) { - this(ctx); - mSmLooper = looper; - mMnsServer = mnsServer; + MapClientService(AdapterService adapterService, Looper looper, MnsService mnsServer) { + super(requireNonNull(adapterService)); + mAdapterService = adapterService; + mDatabaseManager = requireNonNull(adapterService.getDatabase()); + mMnsServer = requireNonNullElseGet(mnsServer, () -> new MnsService(this)); + + if (looper == null) { + mHandler = new Handler(requireNonNull(Looper.getMainLooper())); + mStateMachinesLooper = null; + } else { + mHandler = new Handler(looper); + + // MapClient is only using a common state machine looper for test. + // In real device, it use a thread per device connected to avoid congestion. + mStateMachinesLooper = looper; + } + + removeUncleanAccounts(); + MapClientContent.clearAllContent(this); + setMapClientService(this); } public static boolean isEnabled() { @@ -172,10 +189,11 @@ public class MapClientService extends ProfileService { // When creating a new statemachine, its state is set to CONNECTING - which will trigger // connect. MceStateMachine mapStateMachine; - if (mSmLooper != null) { - mapStateMachine = new MceStateMachine(this, device, mSmLooper); + if (mStateMachinesLooper != null) { + mapStateMachine = + new MceStateMachine(this, device, mAdapterService, mStateMachinesLooper); } else { - mapStateMachine = new MceStateMachine(this, device); + mapStateMachine = new MceStateMachine(this, device, mAdapterService); } mMapInstanceMap.put(device, mapStateMachine); } @@ -291,33 +309,10 @@ public class MapClientService extends ProfileService { } @Override - public synchronized void start() { - Log.d(TAG, "start()"); - - mAdapterService = AdapterService.getAdapterService(); - mDatabaseManager = - Objects.requireNonNull( - AdapterService.getAdapterService().getDatabase(), - "DatabaseManager cannot be null when MapClientService starts"); - - mHandler = new Handler(Looper.getMainLooper()); - - if (mMnsServer == null) { - mMnsServer = new MnsService(this); - } - - removeUncleanAccounts(); - MapClientContent.clearAllContent(this); - setMapClientService(this); - } - - @Override public synchronized void stop() { Log.d(TAG, "stop()"); - if (mMnsServer != null) { - mMnsServer.stop(); - } + mMnsServer.stop(); for (MceStateMachine stateMachine : mMapInstanceMap.values()) { if (stateMachine.getState() == BluetoothAdapter.STATE_CONNECTED) { stateMachine.disconnect(); @@ -327,10 +322,7 @@ public class MapClientService extends ProfileService { mMapInstanceMap.clear(); // Unregister Handler and stop all queued messages. - if (mHandler != null) { - mHandler.removeCallbacksAndMessages(null); - mHandler = null; - } + mHandler.removeCallbacksAndMessages(null); } @Override diff --git a/android/app/src/com/android/bluetooth/mapclient/MceStateMachine.java b/android/app/src/com/android/bluetooth/mapclient/MceStateMachine.java index b24ddade19..2c3420af84 100644 --- a/android/app/src/com/android/bluetooth/mapclient/MceStateMachine.java +++ b/android/app/src/com/android/bluetooth/mapclient/MceStateMachine.java @@ -42,6 +42,8 @@ import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; import static android.Manifest.permission.RECEIVE_SMS; +import static java.util.Objects.requireNonNull; + import android.app.Activity; import android.app.PendingIntent; import android.bluetooth.BluetoothDevice; @@ -112,11 +114,11 @@ class MceStateMachine extends StateMachine { // messaging app takes that responsibility. private static final Boolean SAVE_OUTBOUND_MESSAGES = true; @VisibleForTesting static final Duration DISCONNECT_TIMEOUT = Duration.ofSeconds(3); - private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(10); + @VisibleForTesting static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(10); private static final int MAX_MESSAGES = 20; private static final int MSG_CONNECT = 1; private static final int MSG_DISCONNECT = 2; - static final int MSG_CONNECTING_TIMEOUT = 3; + private static final int MSG_CONNECTING_TIMEOUT = 3; private static final int MSG_DISCONNECTING_TIMEOUT = 4; // Constants for SDP. Note that these values come from the native stack, but no centralized @@ -147,22 +149,26 @@ class MceStateMachine extends StateMachine { private static final String SEND_MESSAGE_TYPE = "persist.bluetooth.pts.mapclient.sendmessagetype"; + private final State mDisconnected = new Disconnected(); + private final State mConnecting = new Connecting(); + private final State mConnected = new Connected(); + private final State mDisconnecting = new Disconnecting(); + + private final HashMap<String, Bmessage> mSentMessageLog = new HashMap<>(MAX_MESSAGES); + private final HashMap<Bmessage, PendingIntent> mSentReceiptRequested = + new HashMap<>(MAX_MESSAGES); + private final HashMap<Bmessage, PendingIntent> mDeliveryReceiptRequested = + new HashMap<>(MAX_MESSAGES); + + private final BluetoothDevice mDevice; + private final MapClientService mService; + private final AdapterService mAdapterService; + // Connectivity States private int mPreviousState = BluetoothProfile.STATE_DISCONNECTED; private int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED; - private State mDisconnected; - private State mConnecting; - private State mConnected; - private State mDisconnecting; - - private final BluetoothDevice mDevice; - private MapClientService mService; private MasClient mMasClient; private MapClientContent mDatabase; - private HashMap<String, Bmessage> mSentMessageLog = new HashMap<>(MAX_MESSAGES); - private HashMap<Bmessage, PendingIntent> mSentReceiptRequested = new HashMap<>(MAX_MESSAGES); - private HashMap<Bmessage, PendingIntent> mDeliveryReceiptRequested = - new HashMap<>(MAX_MESSAGES); private final Object mLock = new Object(); @@ -225,25 +231,33 @@ class MceStateMachine extends StateMachine { ConcurrentHashMap<String, MessageMetadata> mMessages = new ConcurrentHashMap<String, MessageMetadata>(); - MceStateMachine(MapClientService service, BluetoothDevice device) { + MceStateMachine( + MapClientService service, BluetoothDevice device, AdapterService adapterService) { super(TAG); // Create a state machine with its own separate thread + mAdapterService = requireNonNull(adapterService); mService = service; mDevice = device; initStateMachine(); } - MceStateMachine(MapClientService service, BluetoothDevice device, Looper looper) { - this(service, device, null, null, looper); + MceStateMachine( + MapClientService service, + BluetoothDevice device, + AdapterService adapterService, + Looper looper) { + this(service, device, adapterService, looper, null, null); } @VisibleForTesting MceStateMachine( MapClientService service, BluetoothDevice device, + AdapterService adapterService, + Looper looper, MasClient masClient, - MapClientContent database, - Looper looper) { - super(TAG, looper); + MapClientContent database) { + super(TAG, requireNonNull(looper)); + mAdapterService = requireNonNull(adapterService); mService = service; mMasClient = masClient; mDevice = device; @@ -254,10 +268,6 @@ class MceStateMachine extends StateMachine { private void initStateMachine() { mPreviousState = BluetoothProfile.STATE_DISCONNECTED; - mDisconnected = new Disconnected(); - mConnecting = new Connecting(); - mDisconnecting = new Disconnecting(); - mConnected = new Connected(); addState(mDisconnected); addState(mConnecting); @@ -302,11 +312,8 @@ class MceStateMachine extends StateMachine { } setState(state); - AdapterService adapterService = AdapterService.getAdapterService(); - if (adapterService != null) { - adapterService.updateProfileConnectionAdapterProperties( - mDevice, BluetoothProfile.MAP_CLIENT, state, prevState); - } + mAdapterService.updateProfileConnectionAdapterProperties( + mDevice, BluetoothProfile.MAP_CLIENT, state, prevState); Intent intent = new Intent(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); diff --git a/android/app/src/com/android/bluetooth/mapclient/MnsService.java b/android/app/src/com/android/bluetooth/mapclient/MnsService.java index 9b86757da5..99cee09481 100644 --- a/android/app/src/com/android/bluetooth/mapclient/MnsService.java +++ b/android/app/src/com/android/bluetooth/mapclient/MnsService.java @@ -34,22 +34,20 @@ import java.io.IOException; public class MnsService { private static final String TAG = MnsService.class.getSimpleName(); - static final int MSG_EVENT = 1; - /* for Client */ - static final int EVENT_REPORT = 1001; - /* MAP version 1.4 */ - private static final int MNS_VERSION = 0x0104; + static final int EVENT_REPORT = 1001; // for Client + private static final int MNS_VERSION = 0x0104; // MAP version 1.4 private final SocketAcceptor mAcceptThread = new SocketAcceptor(); + private final MapClientService mMapClientService; + private ObexServerSockets mServerSockets; - private MapClientService mContext; private volatile boolean mShutdown = false; // Used to interrupt socket accept thread private int mSdpHandle = -1; - MnsService(MapClientService context) { + MnsService(MapClientService service) { Log.v(TAG, "MnsService()"); - mContext = context; + mMapClientService = service; mServerSockets = ObexServerSockets.create(mAcceptThread); SdpManagerNativeInterface nativeInterface = SdpManagerNativeInterface.getInstance(); if (!nativeInterface.isAvailable()) { @@ -116,7 +114,7 @@ public class MnsService { public synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket socket) { Log.d(TAG, "onConnect" + device + " SOCKET: " + socket); /* Signal to the service that we have received an incoming connection.*/ - MceStateMachine stateMachine = mContext.getMceStateMachineForDevice(device); + MceStateMachine stateMachine = mMapClientService.getMceStateMachineForDevice(device); if (stateMachine == null) { Log.e( TAG, diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java index 6efdb17d66..7bf72bcee1 100644 --- a/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java +++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java @@ -51,7 +51,6 @@ import android.util.Log; import com.android.bluetooth.BluetoothMethodProxy; import com.android.bluetooth.R; import com.android.bluetooth.Utils; -import com.android.bluetooth.flags.Flags; import com.google.common.annotations.VisibleForTesting; @@ -234,7 +233,7 @@ class BluetoothOppNotification { case NOTIFY: synchronized (BluetoothOppNotification.this) { if (mPendingUpdate > 0 && mUpdateNotificationThread == null) { - Log.v(TAG, "new notify threadi!"); + Log.v(TAG, "new notify thread!"); mUpdateNotificationThread = new NotificationUpdateThread(); mUpdateNotificationThread.start(); Log.v(TAG, "send delay message"); diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java index a8c653db5c..b912e8e966 100644 --- a/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java +++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java @@ -51,7 +51,6 @@ import android.database.Cursor; import android.media.MediaScannerConnection; import android.media.MediaScannerConnection.MediaScannerConnectionClient; import android.net.Uri; -import android.os.Binder; import android.os.Handler; import android.os.Message; import android.os.Process; @@ -135,7 +134,7 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti private BluetoothShareContentObserver mObserver; /** Class to handle Notification Manager updates */ - @VisibleForTesting BluetoothOppNotification mNotifier; + @VisibleForTesting final BluetoothOppNotification mNotifier; private boolean mPendingUpdate; @@ -236,28 +235,6 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_WARN, 0); } - } - - public static boolean isEnabled() { - return BluetoothProperties.isProfileOppEnabled().orElse(false); - } - - @Override - protected IProfileServiceBinder initBinder() { - return new OppBinder(); - } - - private static class OppBinder extends Binder implements IProfileServiceBinder { - - OppBinder() {} - - @Override - public void cleanup() {} - } - - @Override - public void start() { - Log.v(TAG, "start()"); setComponentAvailable(OPP_PROVIDER, true); setComponentAvailable(INCOMING_FILE_CONFIRM_ACTIVITY, true); @@ -282,6 +259,15 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti setBluetoothOppService(this); } + public static boolean isEnabled() { + return BluetoothProperties.isProfileOppEnabled().orElse(false); + } + + @Override + protected IProfileServiceBinder initBinder() { + return null; + } + @Override public void stop() { if (sBluetoothOppService == null) { @@ -457,7 +443,7 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti BluetoothStatsLog .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 7); - Log.e(TAG, "close tranport error"); + Log.e(TAG, "close transport error"); } } else { Log.i(TAG, "OPP busy! Retry after 1 second"); @@ -623,9 +609,7 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti } } - if (mNotifier != null) { - mNotifier.cancelOppNotifications(); - } + mNotifier.cancelOppNotifications(); } /* suppose we auto accept an incoming OPUSH connection */ @@ -740,8 +724,9 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti mPendingUpdate = false; } Cursor cursor = - getContentResolver() - .query( + BluetoothMethodProxy.getInstance() + .contentResolverQuery( + getContentResolver(), BluetoothShare.CONTENT_URI, null, null, diff --git a/android/app/src/com/android/bluetooth/pan/PanNativeInterface.java b/android/app/src/com/android/bluetooth/pan/PanNativeInterface.java index 0580c58560..cb4f561b73 100644 --- a/android/app/src/com/android/bluetooth/pan/PanNativeInterface.java +++ b/android/app/src/com/android/bluetooth/pan/PanNativeInterface.java @@ -22,41 +22,19 @@ import android.bluetooth.BluetoothPan; import android.bluetooth.BluetoothProfile; import android.util.Log; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; /** Provides Bluetooth Pan native interface for the Pan service */ public class PanNativeInterface { private static final String TAG = PanNativeInterface.class.getSimpleName(); - private PanService mPanService; - @GuardedBy("INSTANCE_LOCK") - private static PanNativeInterface sInstance; + private final PanService mPanService; - private static final Object INSTANCE_LOCK = new Object(); - - private PanNativeInterface() {} - - /** Get singleton instance. */ - public static PanNativeInterface getInstance() { - synchronized (INSTANCE_LOCK) { - if (sInstance == null) { - sInstance = new PanNativeInterface(); - } - return sInstance; - } - } - - /** Set singleton instance. */ - @VisibleForTesting - public static void setInstance(PanNativeInterface instance) { - synchronized (INSTANCE_LOCK) { - sInstance = instance; - } + PanNativeInterface(PanService panService) { + mPanService = panService; } - void init(PanService panService) { - mPanService = panService; + void init() { initializeNative(); } diff --git a/android/app/src/com/android/bluetooth/pan/PanService.java b/android/app/src/com/android/bluetooth/pan/PanService.java index ee57196ac4..d59d74e0c5 100644 --- a/android/app/src/com/android/bluetooth/pan/PanService.java +++ b/android/app/src/com/android/bluetooth/pan/PanService.java @@ -19,6 +19,10 @@ package com.android.bluetooth.pan; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; import static android.Manifest.permission.TETHER_PRIVILEGED; +import static android.bluetooth.BluetoothUtils.logRemoteException; + +import static java.util.Objects.requireNonNull; +import static java.util.Objects.requireNonNullElseGet; import android.annotation.RequiresPermission; import android.bluetooth.BluetoothDevice; @@ -29,7 +33,6 @@ import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothPan; import android.bluetooth.IBluetoothPanCallback; import android.content.AttributionSource; -import android.content.Context; import android.content.Intent; import android.content.res.Resources.NotFoundException; import android.net.TetheringInterface; @@ -57,53 +60,49 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; /** Provides Bluetooth Pan Device profile, as a service in the Bluetooth application. */ public class PanService extends ProfileService { private static final String TAG = PanService.class.getSimpleName(); + private static PanService sPanService; private static final int BLUETOOTH_MAX_PAN_CONNECTIONS = 5; - @VisibleForTesting ConcurrentHashMap<BluetoothDevice, BluetoothPanDevice> mPanDevices; - - private int mMaxPanDevices; - private String mPanIfName; - @VisibleForTesting boolean mIsTethering = false; - private HashMap<String, IBluetoothPanCallback> mBluetoothTetheringCallbacks; - - private TetheringManager mTetheringManager; - private DatabaseManager mDatabaseManager; - @VisibleForTesting UserManager mUserManager; - private static final int MESSAGE_CONNECT = 1; private static final int MESSAGE_DISCONNECT = 2; private static final int MESSAGE_CONNECT_STATE_CHANGED = 11; - private boolean mTetherOn = false; - private BluetoothTetheringNetworkFactory mNetworkFactory; - private boolean mStarted = false; + @VisibleForTesting + final ConcurrentHashMap<BluetoothDevice, BluetoothPanDevice> mPanDevices = + new ConcurrentHashMap<>(); - private AdapterService mAdapterService; + private final Map<String, IBluetoothPanCallback> mBluetoothTetheringCallbacks = new HashMap<>(); + private final AdapterService mAdapterService; + private final PanNativeInterface mNativeInterface; + private final DatabaseManager mDatabaseManager; + private final TetheringManager mTetheringManager; + private final UserManager mUserManager; + private final int mMaxPanDevices; - @VisibleForTesting PanNativeInterface mNativeInterface; + private String mPanIfName; + @VisibleForTesting boolean mIsTethering = false; + private boolean mTetherOn = false; + private BluetoothTetheringNetworkFactory mNetworkFactory; - TetheringManager.TetheringEventCallback mTetheringCallback = + final TetheringManager.TetheringEventCallback mTetheringCallback = new TetheringManager.TetheringEventCallback() { @Override public void onError(TetheringInterface iface, int error) { if (mIsTethering && iface.getType() == TetheringManager.TETHERING_BLUETOOTH) { - // tethering is fail because of @TetheringIfaceError error. + // Tethering fail because of @TetheringIfaceError error. Log.e(TAG, "Error setting up tether interface: " + error); - for (Map.Entry device : mPanDevices.entrySet()) { + for (BluetoothDevice device : mPanDevices.keySet()) { mNativeInterface.disconnect( Flags.panUseIdentityAddress() - ? Utils.getByteBrEdrAddress( - (BluetoothDevice) device.getKey()) - : Utils.getByteAddress( - (BluetoothDevice) device.getKey())); + ? Utils.getByteBrEdrAddress(mAdapterService, device) + : Utils.getByteAddress(device)); } mPanDevices.clear(); mIsTethering = false; @@ -111,8 +110,35 @@ public class PanService extends ProfileService { } }; - public PanService(Context ctx) { - super(ctx); + public PanService(AdapterService adapterService) { + this(adapterService, null); + } + + @VisibleForTesting + PanService(AdapterService adapterService, PanNativeInterface nativeInterface) { + super(requireNonNull(adapterService)); + mAdapterService = adapterService; + mDatabaseManager = requireNonNull(mAdapterService.getDatabase()); + mNativeInterface = + requireNonNullElseGet(nativeInterface, () -> new PanNativeInterface(this)); + mUserManager = requireNonNull(getSystemService(UserManager.class)); + mTetheringManager = requireNonNull(getSystemService(TetheringManager.class)); + + int maxPanDevice; + try { + maxPanDevice = + getResources() + .getInteger(com.android.bluetooth.R.integer.config_max_pan_devices); + } catch (NotFoundException e) { + maxPanDevice = BLUETOOTH_MAX_PAN_CONNECTIONS; + } + mMaxPanDevices = maxPanDevice; + + mNativeInterface.init(); + + mTetheringManager.registerTetheringEventCallback( + new HandlerExecutor(new Handler(Looper.getMainLooper())), mTetheringCallback); + setPanService(this); } public static boolean isEnabled() { @@ -143,50 +169,8 @@ public class PanService extends ProfileService { } @Override - public void start() { - mAdapterService = - Objects.requireNonNull( - AdapterService.getAdapterService(), - "AdapterService cannot be null when PanService starts"); - mDatabaseManager = - Objects.requireNonNull( - AdapterService.getAdapterService().getDatabase(), - "DatabaseManager cannot be null when PanService starts"); - mNativeInterface = - Objects.requireNonNull( - PanNativeInterface.getInstance(), - "PanNativeInterface cannot be null when PanService starts"); - - mBluetoothTetheringCallbacks = new HashMap<>(); - mPanDevices = new ConcurrentHashMap<BluetoothDevice, BluetoothPanDevice>(); - try { - mMaxPanDevices = - getResources() - .getInteger(com.android.bluetooth.R.integer.config_max_pan_devices); - } catch (NotFoundException e) { - mMaxPanDevices = BLUETOOTH_MAX_PAN_CONNECTIONS; - } - mNativeInterface.init(this); - - mUserManager = getSystemService(UserManager.class); - - mTetheringManager = getSystemService(TetheringManager.class); - mTetheringManager.registerTetheringEventCallback( - new HandlerExecutor(new Handler(Looper.getMainLooper())), mTetheringCallback); - setPanService(this); - mStarted = true; - } - - @Override public void stop() { - if (!mStarted) { - Log.w(TAG, "stop() called before start()"); - return; - } - if (mTetheringManager != null) { - mTetheringManager.unregisterTetheringEventCallback(mTetheringCallback); - mTetheringManager = null; - } + mTetheringManager.unregisterTetheringEventCallback(mTetheringCallback); mNativeInterface.cleanup(); mHandler.removeCallbacksAndMessages(null); } @@ -196,29 +180,25 @@ public class PanService extends ProfileService { // TODO(b/72948646): this should be moved to stop() setPanService(null); - mUserManager = null; - - if (mPanDevices != null) { - int[] desiredStates = { - BluetoothProfile.STATE_CONNECTING, - BluetoothProfile.STATE_CONNECTED, - BluetoothProfile.STATE_DISCONNECTING - }; - List<BluetoothDevice> devList = getDevicesMatchingConnectionStates(desiredStates); - for (BluetoothDevice device : devList) { - BluetoothPanDevice panDevice = mPanDevices.get(device); - Log.d(TAG, "panDevice: " + panDevice + " device address: " + device); - if (panDevice != null) { - handlePanDeviceStateChange( - device, - mPanIfName, - BluetoothProfile.STATE_DISCONNECTED, - panDevice.mLocalRole, - panDevice.mRemoteRole); - } + int[] desiredStates = { + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_DISCONNECTING + }; + List<BluetoothDevice> devList = getDevicesMatchingConnectionStates(desiredStates); + for (BluetoothDevice device : devList) { + BluetoothPanDevice panDevice = mPanDevices.get(device); + Log.d(TAG, "panDevice: " + panDevice + " device address: " + device); + if (panDevice != null) { + handlePanDeviceStateChange( + device, + mPanIfName, + BluetoothProfile.STATE_DISCONNECTED, + panDevice.mLocalRole, + panDevice.mRemoteRole); } - mPanDevices.clear(); } + mPanDevices.clear(); } private final Handler mHandler = @@ -230,7 +210,8 @@ public class PanService extends ProfileService { BluetoothDevice connectDevice = (BluetoothDevice) msg.obj; if (!mNativeInterface.connect( Flags.identityAddressNullIfNotKnown() - ? Utils.getByteBrEdrAddress(connectDevice) + ? Utils.getByteBrEdrAddress( + mAdapterService, connectDevice) : mAdapterService.getByteIdentityAddress( connectDevice))) { handlePanDeviceStateChange( @@ -251,7 +232,8 @@ public class PanService extends ProfileService { BluetoothDevice disconnectDevice = (BluetoothDevice) msg.obj; if (!mNativeInterface.disconnect( Flags.identityAddressNullIfNotKnown() - ? Utils.getByteBrEdrAddress(disconnectDevice) + ? Utils.getByteBrEdrAddress( + mAdapterService, disconnectDevice) : mAdapterService.getByteIdentityAddress( disconnectDevice))) { handlePanDeviceStateChange( @@ -663,7 +645,7 @@ public class PanService extends ProfileService { mPanDevices.remove(device); mNativeInterface.disconnect( Flags.panUseIdentityAddress() - ? Utils.getByteBrEdrAddress(device) + ? Utils.getByteBrEdrAddress(mAdapterService, device) : Utils.getByteAddress(device)); return; } @@ -675,7 +657,7 @@ public class PanService extends ProfileService { cb.onAvailable(iface); } } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + logRemoteException(TAG, e); } } } else if (state == BluetoothProfile.STATE_DISCONNECTED) { @@ -690,12 +672,12 @@ public class PanService extends ProfileService { cb.onUnavailable(); } } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + logRemoteException(TAG, e); } mIsTethering = false; } } - } else if (mStarted) { + } else { // PANU Role = reverse Tether Log.d( TAG, diff --git a/android/app/src/com/android/bluetooth/sap/SapService.java b/android/app/src/com/android/bluetooth/sap/SapService.java index 6401b27f82..861553477b 100644 --- a/android/app/src/com/android/bluetooth/sap/SapService.java +++ b/android/app/src/com/android/bluetooth/sap/SapService.java @@ -19,6 +19,8 @@ package com.android.bluetooth.sap; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; +import static java.util.Objects.requireNonNull; + import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.app.AlarmManager; @@ -60,15 +62,10 @@ import java.util.Collections; import java.util.List; public class SapService extends ProfileService implements AdapterService.BluetoothStateCallback { + private static final String TAG = "SapService"; private static final String SDP_SAP_SERVICE_NAME = "SIM Access"; private static final int SDP_SAP_VERSION = 0x0102; - private static final String TAG = "SapService"; - - /** - * To log debug/verbose in SAP, use the command "setprop log.tag.SapService DEBUG" or "setprop - * log.tag.SapService VERBOSE" and then "adb root" + "adb shell "stop; start"" - */ /* Message ID's */ private static final int START_LISTENER = 1; @@ -100,8 +97,9 @@ public class SapService extends ProfileService implements AdapterService.Bluetoo "com.android.bluetooth.sap.USER_CONFIRM_TIMEOUT"; private static final int USER_CONFIRM_TIMEOUT_VALUE = 25000; + private final AdapterService mAdapterService; + private PowerManager.WakeLock mWakeLock = null; - private AdapterService mAdapterService; private SocketAcceptThread mAcceptThread = null; private BluetoothServerSocket mServerSocket = null; private int mSdpHandle = -1; @@ -113,9 +111,7 @@ public class SapService extends ProfileService implements AdapterService.Bluetoo private SapServer mSapServer = null; private AlarmManager mAlarmManager = null; private boolean mRemoveTimeoutMsg = false; - private boolean mIsWaitingAuthorization = false; - private boolean mIsRegistered = false; private static SapService sSapService; @@ -123,9 +119,22 @@ public class SapService extends ProfileService implements AdapterService.Bluetoo BluetoothUuid.SAP, }; - public SapService(Context ctx) { - super(ctx); + public SapService(AdapterService adapterService) { + super(requireNonNull(adapterService)); + mAdapterService = adapterService; BluetoothSap.invalidateBluetoothGetConnectionStateCache(); + + IntentFilter filter = new IntentFilter(); + filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); + filter.addAction(USER_CONFIRM_TIMEOUT_ACTION); + + registerReceiver(mSapReceiver, filter); + + mAdapterService.registerBluetoothStateCallback(getMainExecutor(), this); + // start RFCOMM listener + mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER)); + setSapService(this); } public static boolean isEnabled() { @@ -148,7 +157,7 @@ public class SapService extends ProfileService implements AdapterService.Bluetoo private void removeSdpRecord() { SdpManagerNativeInterface nativeInterface = SdpManagerNativeInterface.getInstance(); - if (mAdapterService != null && mSdpHandle >= 0 && nativeInterface.isAvailable()) { + if (mSdpHandle >= 0 && nativeInterface.isAvailable()) { Log.v(TAG, "Removing SDP record handle: " + mSdpHandle); nativeInterface.removeSdpRecord(mSdpHandle); mSdpHandle = -1; @@ -203,9 +212,6 @@ public class SapService extends ProfileService implements AdapterService.Bluetoo if (!initSocketOK) { // Need to break out of this loop if BT is being turned off. - if (mAdapterService == null) { - break; - } int state = mAdapterService.getState(); if ((state != BluetoothAdapter.STATE_TURNING_ON) && (state != BluetoothAdapter.STATE_ON)) { @@ -636,7 +642,7 @@ public class SapService extends ProfileService implements AdapterService.Bluetoo Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); enforceCallingOrSelfPermission( BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission"); - AdapterService.getAdapterService() + mAdapterService .getDatabase() .setProfileConnectionPolicy(device, BluetoothProfile.SAP, connectionPolicy); if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { @@ -659,7 +665,7 @@ public class SapService extends ProfileService implements AdapterService.Bluetoo public int getConnectionPolicy(BluetoothDevice device) { enforceCallingOrSelfPermission( BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission"); - return AdapterService.getAdapterService() + return mAdapterService .getDatabase() .getProfileConnectionPolicy(device, BluetoothProfile.SAP); } @@ -670,41 +676,10 @@ public class SapService extends ProfileService implements AdapterService.Bluetoo } @Override - public void start() { - Log.v(TAG, "start()"); - IntentFilter filter = new IntentFilter(); - filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); - filter.addAction(USER_CONFIRM_TIMEOUT_ACTION); - - try { - registerReceiver(mSapReceiver, filter); - mIsRegistered = true; - } catch (Exception e) { - Log.w(TAG, "Unable to register sap receiver", e); - } - mInterrupted = false; - mAdapterService = AdapterService.getAdapterService(); - mAdapterService.registerBluetoothStateCallback(getMainExecutor(), this); - // start RFCOMM listener - mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER)); - setSapService(this); - } - - @Override public void stop() { Log.v(TAG, "stop()"); - if (!mIsRegistered) { - Log.i(TAG, "Avoid unregister when receiver it is not registered"); - return; - } setSapService(null); - try { - mIsRegistered = false; - unregisterReceiver(mSapReceiver); - } catch (Exception e) { - Log.w(TAG, "Unable to unregister sap receiver", e); - } + unregisterReceiver(mSapReceiver); mAdapterService.unregisterBluetoothStateCallback(this); setState(BluetoothSap.STATE_DISCONNECTED, BluetoothSap.RESULT_CANCELED); sendShutdownMessage(); diff --git a/android/app/src/com/android/bluetooth/tbs/TbsGatt.java b/android/app/src/com/android/bluetooth/tbs/TbsGatt.java index 803b249a3d..9403b5fc93 100644 --- a/android/app/src/com/android/bluetooth/tbs/TbsGatt.java +++ b/android/app/src/com/android/bluetooth/tbs/TbsGatt.java @@ -19,6 +19,8 @@ package com.android.bluetooth.tbs; import static android.bluetooth.BluetoothDevice.METADATA_GTBS_CCCD; +import static java.util.Objects.requireNonNull; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; @@ -27,7 +29,6 @@ import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattServerCallback; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; -import android.content.Context; import android.net.Uri; import android.os.Handler; import android.os.Looper; @@ -48,12 +49,10 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.UUID; public class TbsGatt { - - private static final String TAG = "TbsGatt"; + private static final String TAG = TbsGatt.class.getSimpleName(); private static final String UUID_PREFIX = "0000"; private static final String UUID_SUFFIX = "-0000-1000-8000-00805f9b34fb"; @@ -66,51 +65,50 @@ public class TbsGatt { @VisibleForTesting static final UUID UUID_BEARER_TECHNOLOGY = makeUuid("2BB5"); @VisibleForTesting static final UUID UUID_BEARER_URI_SCHEMES_SUPPORTED_LIST = makeUuid("2BB6"); @VisibleForTesting static final UUID UUID_BEARER_LIST_CURRENT_CALLS = makeUuid("2BB9"); - @VisibleForTesting static final UUID UUID_CONTENT_CONTROL_ID = makeUuid("2BBA"); + private static final UUID UUID_CONTENT_CONTROL_ID = makeUuid("2BBA"); @VisibleForTesting static final UUID UUID_STATUS_FLAGS = makeUuid("2BBB"); @VisibleForTesting static final UUID UUID_CALL_STATE = makeUuid("2BBD"); @VisibleForTesting static final UUID UUID_CALL_CONTROL_POINT = makeUuid("2BBE"); - - @VisibleForTesting - static final UUID UUID_CALL_CONTROL_POINT_OPTIONAL_OPCODES = makeUuid("2BBF"); - + private static final UUID UUID_CALL_CONTROL_POINT_OPTIONAL_OPCODES = makeUuid("2BBF"); @VisibleForTesting static final UUID UUID_TERMINATION_REASON = makeUuid("2BC0"); @VisibleForTesting static final UUID UUID_INCOMING_CALL = makeUuid("2BC1"); @VisibleForTesting static final UUID UUID_CALL_FRIENDLY_NAME = makeUuid("2BC2"); - @VisibleForTesting static final UUID UUID_CLIENT_CHARACTERISTIC_CONFIGURATION = makeUuid("2902"); @VisibleForTesting static final int STATUS_FLAG_INBAND_RINGTONE_ENABLED = 0x0001; @VisibleForTesting static final int STATUS_FLAG_SILENT_MODE_ENABLED = 0x0002; - @VisibleForTesting static final int CALL_CONTROL_POINT_OPTIONAL_OPCODE_LOCAL_HOLD = 0x0001; - @VisibleForTesting static final int CALL_CONTROL_POINT_OPTIONAL_OPCODE_JOIN = 0x0002; - - @VisibleForTesting public static final int CALL_CONTROL_POINT_OPCODE_ACCEPT = 0x00; - @VisibleForTesting public static final int CALL_CONTROL_POINT_OPCODE_TERMINATE = 0x01; - @VisibleForTesting public static final int CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD = 0x02; - @VisibleForTesting public static final int CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE = 0x03; - @VisibleForTesting public static final int CALL_CONTROL_POINT_OPCODE_ORIGINATE = 0x04; - @VisibleForTesting public static final int CALL_CONTROL_POINT_OPCODE_JOIN = 0x05; + private static final int CALL_CONTROL_POINT_OPTIONAL_OPCODE_LOCAL_HOLD = 0x0001; + private static final int CALL_CONTROL_POINT_OPTIONAL_OPCODE_JOIN = 0x0002; - @VisibleForTesting public static final int CALL_CONTROL_POINT_RESULT_SUCCESS = 0x00; + static final int CALL_CONTROL_POINT_OPCODE_ACCEPT = 0x00; + static final int CALL_CONTROL_POINT_OPCODE_TERMINATE = 0x01; + static final int CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD = 0x02; + static final int CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE = 0x03; + static final int CALL_CONTROL_POINT_OPCODE_ORIGINATE = 0x04; + static final int CALL_CONTROL_POINT_OPCODE_JOIN = 0x05; - @VisibleForTesting - public static final int CALL_CONTROL_POINT_RESULT_OPCODE_NOT_SUPPORTED = 0x01; + static final int CALL_CONTROL_POINT_RESULT_SUCCESS = 0x00; + static final int CALL_CONTROL_POINT_RESULT_OPCODE_NOT_SUPPORTED = 0x01; + static final int CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE = 0x02; + static final int CALL_CONTROL_POINT_RESULT_INVALID_CALL_INDEX = 0x03; + static final int CALL_CONTROL_POINT_RESULT_STATE_MISMATCH = 0x04; + static final int CALL_CONTROL_POINT_RESULT_INVALID_OUTGOING_URI = 0x06; - @VisibleForTesting - public static final int CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE = 0x02; + private final Object mPendingGattOperationsLock = new Object(); + private final Map<BluetoothDevice, Integer> mStatusFlagValue = new HashMap<>(); - @VisibleForTesting public static final int CALL_CONTROL_POINT_RESULT_INVALID_CALL_INDEX = 0x03; - @VisibleForTesting public static final int CALL_CONTROL_POINT_RESULT_STATE_MISMATCH = 0x04; - @VisibleForTesting public static final int CALL_CONTROL_POINT_RESULT_LACK_OF_RESOURCES = 0x05; + @GuardedBy("mPendingGattOperationsLock") + private final Map<BluetoothDevice, List<GattOpContext>> mPendingGattOperations = + new HashMap<>(); - @VisibleForTesting - public static final int CALL_CONTROL_POINT_RESULT_INVALID_OUTGOING_URI = 0x06; + private final Map<BluetoothDevice, HashMap<UUID, Short>> mCccDescriptorValues = new HashMap<>(); - private final Object mPendingGattOperationsLock = new Object(); - private final Context mContext; + private final AdapterService mAdapterService; + private final TbsService mTbsService; + private final Handler mHandler; + private final BluetoothGattServerProxy mBluetoothGattServer; private final GattCharacteristic mBearerProviderNameCharacteristic; private final GattCharacteristic mBearerUciCharacteristic; private final GattCharacteristic mBearerTechnologyCharacteristic; @@ -124,18 +122,10 @@ public class TbsGatt { private final GattCharacteristic mTerminationReasonCharacteristic; private final GattCharacteristic mIncomingCallCharacteristic; private final GattCharacteristic mCallFriendlyNameCharacteristic; - private boolean mSilentMode = false; - private Map<BluetoothDevice, Integer> mStatusFlagValue = new HashMap<>(); - - @GuardedBy("mPendingGattOperationsLock") - private Map<BluetoothDevice, List<GattOpContext>> mPendingGattOperations = new HashMap<>(); - private BluetoothGattServerProxy mBluetoothGattServer; - private Handler mHandler; private Callback mCallback; - private AdapterService mAdapterService; - private HashMap<BluetoothDevice, HashMap<UUID, Short>> mCccDescriptorValues; - private TbsService mTbsService; + + private boolean mSilentMode = false; private static final int LOG_NB_EVENTS = 200; private BluetoothEventLogger mEventLogger = null; @@ -242,12 +232,19 @@ public class TbsGatt { public byte[] mValue; } - TbsGatt(TbsService tbsService) { - mContext = tbsService; - mAdapterService = - Objects.requireNonNull( - AdapterService.getAdapterService(), - "AdapterService shouldn't be null when creating TbsGatt"); + TbsGatt(AdapterService adapterService, TbsService tbsService) { + this(adapterService, tbsService, new BluetoothGattServerProxy(adapterService)); + } + + @VisibleForTesting + TbsGatt( + AdapterService adapterService, + TbsService tbsService, + BluetoothGattServerProxy gattServerProxy) { + mTbsService = requireNonNull(tbsService); + mAdapterService = requireNonNull(adapterService); + mBluetoothGattServer = requireNonNull(gattServerProxy); + mHandler = new Handler(Looper.getMainLooper()); mBearerProviderNameCharacteristic = new GattCharacteristic( @@ -317,13 +314,6 @@ public class TbsGatt { | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED); - mTbsService = tbsService; - mBluetoothGattServer = null; - } - - @VisibleForTesting - void setBluetoothGattServerForTesting(BluetoothGattServerProxy proxy) { - mBluetoothGattServer = proxy; } public boolean init( @@ -335,7 +325,6 @@ public class TbsGatt { String providerName, int technology, Callback callback) { - mCccDescriptorValues = new HashMap<>(); mBearerProviderNameCharacteristic.setValue(providerName); mBearerTechnologyCharacteristic.setValue(new byte[] {(byte) (technology & 0xFF)}); mBearerUciCharacteristic.setValue(uci); @@ -344,11 +333,6 @@ public class TbsGatt { setCallControlPointOptionalOpcodes(isLocalHoldOpcodeSupported, isJoinOpcodeSupported); mStatusFlagsCharacteristic.setValue(0, BluetoothGattCharacteristic.FORMAT_UINT16, 0); mCallback = callback; - mHandler = new Handler(Looper.getMainLooper()); - - if (mBluetoothGattServer == null) { - mBluetoothGattServer = new BluetoothGattServerProxy(mContext); - } if (!mBluetoothGattServer.open(mGattServerCallback)) { Log.e(TAG, " Could not open Gatt server"); @@ -381,22 +365,14 @@ public class TbsGatt { mEventLogger.add("Initialized"); mAdapterService.registerBluetoothStateCallback( - mContext.getMainExecutor(), mBluetoothStateChangeCallback); + mAdapterService.getMainExecutor(), mBluetoothStateChangeCallback); return true; } public void cleanup() { mAdapterService.unregisterBluetoothStateCallback(mBluetoothStateChangeCallback); - if (mBluetoothGattServer == null) { - return; - } mBluetoothGattServer.close(); - mBluetoothGattServer = null; - } - - public Context getContext() { - return mContext; } private void removeUuidFromMetadata(ParcelUuid charUuid, BluetoothDevice device) { @@ -506,19 +482,14 @@ public class TbsGatt { BluetoothDevice device, BluetoothGattCharacteristic characteristic, byte[] value) { if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) return; if (value == null) return; - if (mBluetoothGattServer != null) { - mBluetoothGattServer.notifyCharacteristicChanged( - device, characteristic, false, value); - } + mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false, value); } private void notifyCharacteristicChanged( BluetoothDevice device, BluetoothGattCharacteristic characteristic) { if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) return; - if (mBluetoothGattServer != null) { - mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false); - } + mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false); } public void notifyWithValue( diff --git a/android/app/src/com/android/bluetooth/tbs/TbsGeneric.java b/android/app/src/com/android/bluetooth/tbs/TbsGeneric.java index 957e1e2b69..b49eee0a03 100644 --- a/android/app/src/com/android/bluetooth/tbs/TbsGeneric.java +++ b/android/app/src/com/android/bluetooth/tbs/TbsGeneric.java @@ -17,6 +17,8 @@ package com.android.bluetooth.tbs; +import static java.util.Objects.requireNonNull; + import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothLeCall; @@ -50,8 +52,7 @@ import java.util.UUID; /** Container class to store TBS instances */ public class TbsGeneric { - - private static final String TAG = "TbsGeneric"; + private static final String TAG = TbsGeneric.class.getSimpleName(); private static final String UCI = "GTBS"; private static final String DEFAULT_PROVIDER_NAME = "none"; @@ -121,17 +122,20 @@ public class TbsGeneric { } } - private boolean mIsInitialized = false; - private TbsGatt mTbsGatt = null; - private List<Bearer> mBearerList = new ArrayList<>(); + private final List<Bearer> mBearerList = new ArrayList<>(); + private final Map<Integer, TbsCall> mCurrentCallsList = new TreeMap<>(); + private final Receiver mReceiver = new Receiver(); + private final ServiceFactory mFactory = new ServiceFactory(); + + private final TbsGatt mTbsGatt; + private final Context mContext; + + private boolean mIsInitialized; private int mLastIndexAssigned = TbsCall.INDEX_UNASSIGNED; - private Map<Integer, TbsCall> mCurrentCallsList = new TreeMap<>(); private Bearer mForegroundBearer = null; private int mLastRequestIdAssigned = 0; private List<String> mUriSchemes = new ArrayList<>(Arrays.asList("tel")); - private Receiver mReceiver = null; private int mStoredRingerMode = -1; - private final ServiceFactory mFactory = new ServiceFactory(); private LeAudioService mLeAudioService; private final class Receiver extends BroadcastReceiver { @@ -162,9 +166,9 @@ public class TbsGeneric { } ; - public synchronized boolean init(TbsGatt tbsGatt) { - Log.d(TAG, "init"); - mTbsGatt = tbsGatt; + TbsGeneric(Context ctx, TbsGatt tbsGatt) { + mTbsGatt = requireNonNull(tbsGatt); + mContext = requireNonNull(ctx); int ccid = ContentControlIdKeeper.acquireCcid( @@ -173,7 +177,7 @@ public class TbsGeneric { if (!isCcidValid(ccid)) { Log.e(TAG, " CCID is not valid"); cleanup(); - return false; + return; } if (!mTbsGatt.init( @@ -187,15 +191,10 @@ public class TbsGeneric { mTbsGattCallback)) { Log.e(TAG, " TbsGatt init failed"); cleanup(); - return false; + return; } - AudioManager audioManager = mTbsGatt.getContext().getSystemService(AudioManager.class); - if (audioManager == null) { - Log.w(TAG, " AudioManager is not available"); - cleanup(); - return false; - } + AudioManager audioManager = requireNonNull(mContext.getSystemService(AudioManager.class)); // read initial value of ringer mode mStoredRingerMode = audioManager.getRingerMode(); @@ -206,25 +205,20 @@ public class TbsGeneric { mTbsGatt.clearSilentModeFlag(); } - mReceiver = new Receiver(); IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - mTbsGatt.getContext().registerReceiver(mReceiver, filter); + mContext.registerReceiver(mReceiver, filter); mIsInitialized = true; - return true; } public synchronized void cleanup() { Log.d(TAG, "cleanup"); - if (mTbsGatt != null) { - if (mReceiver != null) { - mTbsGatt.getContext().unregisterReceiver(mReceiver); - } - mTbsGatt.cleanup(); - mTbsGatt = null; + if (mIsInitialized) { + mContext.unregisterReceiver(mReceiver); } + mTbsGatt.cleanup(); mIsInitialized = false; } @@ -236,9 +230,7 @@ public class TbsGeneric { */ public synchronized void onDeviceAuthorizationSet(BluetoothDevice device) { // Notify TBS GATT service instance in case of pending operations - if (mTbsGatt != null) { - mTbsGatt.onDeviceAuthorizationSet(device); - } + mTbsGatt.onDeviceAuthorizationSet(device); } /** @@ -247,10 +239,6 @@ public class TbsGeneric { * @param device device for which inband ringtone has been set */ public synchronized void setInbandRingtoneSupport(BluetoothDevice device) { - if (mTbsGatt == null) { - Log.w(TAG, "setInbandRingtoneSupport, mTbsGatt is null"); - return; - } mTbsGatt.setInbandRingtoneFlag(device); } @@ -260,10 +248,6 @@ public class TbsGeneric { * @param device device for which inband ringtone has been cleared */ public synchronized void clearInbandRingtoneSupport(BluetoothDevice device) { - if (mTbsGatt == null) { - Log.w(TAG, "setInbandRingtoneSupport, mTbsGatt is null"); - return; - } mTbsGatt.clearInbandRingtoneFlag(device); } @@ -767,7 +751,7 @@ public class TbsGeneric { Log.i(TAG, "originate uri=" + uri); Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, Uri.parse(uri)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mTbsGatt.getContext().startActivity(intent); + mContext.startActivity(intent); mTbsGatt.setCallControlPointResult( device, TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE, @@ -844,6 +828,20 @@ public class TbsGeneric { return; } + if (shouldBlockTbsForBroadcastReceiver(device)) { + Log.w( + TAG, + "Blocking TBS operation for non-primary device in broadcast," + + " opcode = " + + callControlRequestOpcodeStr(opcode)); + mTbsGatt.setCallControlPointResult( + device, + opcode, + 0, + TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE); + return; + } + int result; switch (opcode) { @@ -1245,6 +1243,20 @@ public class TbsGeneric { return false; } + private boolean shouldBlockTbsForBroadcastReceiver(BluetoothDevice device) { + if (device == null) { + Log.w(TAG, "shouldBlockTbsForBroadcastReceiver: Ignore null device"); + return false; + } + if (!isLeAudioServiceAvailable()) { + Log.w(TAG, "shouldBlockTbsForBroadcastReceiver: LeAudioService is not available"); + return false; + } + + return mLeAudioService.getLocalBroadcastReceivers().contains(device) + && !mLeAudioService.isPrimaryDevice(device); + } + /** * Dump status of TBS service along with related objects * diff --git a/android/app/src/com/android/bluetooth/tbs/TbsService.java b/android/app/src/com/android/bluetooth/tbs/TbsService.java index dc3819eec1..263f765e53 100644 --- a/android/app/src/com/android/bluetooth/tbs/TbsService.java +++ b/android/app/src/com/android/bluetooth/tbs/TbsService.java @@ -20,6 +20,8 @@ package com.android.bluetooth.tbs; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; +import static java.util.Objects.requireNonNull; + import android.annotation.RequiresPermission; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeCall; @@ -27,13 +29,13 @@ import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothLeCallControl; import android.bluetooth.IBluetoothLeCallControlCallback; import android.content.AttributionSource; -import android.content.Context; import android.os.ParcelUuid; import android.os.RemoteException; import android.sysprop.BluetoothProperties; import android.util.Log; import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.le_audio.LeAudioService; import com.android.internal.annotations.VisibleForTesting; @@ -44,16 +46,20 @@ import java.util.Map; import java.util.UUID; public class TbsService extends ProfileService { - - private static final String TAG = "TbsService"; + private static final String TAG = TbsService.class.getSimpleName(); private static TbsService sTbsService; + private final Map<BluetoothDevice, Integer> mDeviceAuthorizations = new HashMap<>(); + private final TbsGeneric mTbsGeneric; - private final TbsGeneric mTbsGeneric = new TbsGeneric(); + public TbsService(AdapterService adapterService) { + super(requireNonNull(adapterService)); - public TbsService(Context ctx) { - super(ctx); + // Mark service as started + setTbsService(this); + + mTbsGeneric = new TbsGeneric(adapterService, new TbsGatt(adapterService, this)); } public static boolean isEnabled() { @@ -66,19 +72,6 @@ public class TbsService extends ProfileService { } @Override - public void start() { - Log.d(TAG, "start()"); - if (sTbsService != null) { - throw new IllegalStateException("start() called twice"); - } - - // Mark service as started - setTbsService(this); - - mTbsGeneric.init(new TbsGatt(this)); - } - - @Override public void stop() { Log.d(TAG, "stop()"); if (sTbsService == null) { diff --git a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java index 2fa94bd27e..eacdefbb06 100644 --- a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java +++ b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java @@ -148,8 +148,6 @@ public class BluetoothInCallService extends InCallService { private final CallInfo mCallInfo; - protected boolean mOnCreateCalled = false; - private int mMaxNumberOfCalls = 0; private BluetoothAdapter mAdapter = null; @@ -367,7 +365,6 @@ public class BluetoothInCallService extends InCallService { private BluetoothInCallService(CallInfo callInfo) { Log.i(TAG, "BluetoothInCallService is created"); mCallInfo = Objects.requireNonNullElseGet(callInfo, () -> new CallInfo()); - sInstance = this; mExecutor = Executors.newSingleThreadExecutor(); } @@ -546,10 +543,6 @@ public class BluetoothInCallService extends InCallService { @RequiresPermission(allOf = {BLUETOOTH_CONNECT, MODIFY_PHONE_STATE}) public boolean listCurrentCalls() { synchronized (LOCK) { - if (!mOnCreateCalled) { - Log.w(TAG, "listcurrentCalls() is called before onCreate()"); - return false; - } enforceModifyPermission(); // only log if it is after we recently updated the headset state or else it can // clog the android log since this can be queried every second. @@ -780,25 +773,28 @@ public class BluetoothInCallService extends InCallService { @Override public void onCreate() { - Log.d(TAG, "onCreate"); super.onCreate(); - mAdapter = requireNonNull(getSystemService(BluetoothManager.class)).getAdapter(); - mTelephonyManager = requireNonNull(getSystemService(TelephonyManager.class)); - mTelecomManager = requireNonNull(getSystemService(TelecomManager.class)); - mAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET); - mAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.LE_CALL_CONTROL); - mBluetoothAdapterReceiver = new BluetoothAdapterReceiver(); - IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); - intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - registerReceiver(mBluetoothAdapterReceiver, intentFilter); - mOnCreateCalled = true; + synchronized (LOCK) { + Log.d(TAG, "onCreate"); + mAdapter = requireNonNull(getSystemService(BluetoothManager.class)).getAdapter(); + mTelephonyManager = requireNonNull(getSystemService(TelephonyManager.class)); + mTelecomManager = requireNonNull(getSystemService(TelecomManager.class)); + mAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET); + mAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.LE_CALL_CONTROL); + mBluetoothAdapterReceiver = new BluetoothAdapterReceiver(); + IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); + intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + registerReceiver(mBluetoothAdapterReceiver, intentFilter); + sInstance = this; + } } @Override public void onDestroy() { - Log.d(TAG, "onDestroy"); - clear(); - mOnCreateCalled = false; + synchronized (LOCK) { + Log.d(TAG, "onDestroy"); + clear(); + } super.onDestroy(); } diff --git a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java index e0868e416a..d4309af577 100644 --- a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java +++ b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java @@ -31,6 +31,9 @@ import static android.bluetooth.IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVAL import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; import static android.bluetooth.IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME; +import static com.android.bluetooth.flags.Flags.leaudioBroadcastVolumeControlPrimaryGroupOnly; +import static com.android.bluetooth.flags.Flags.vcpDeviceVolumeApiImprovements; + import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElseGet; @@ -64,7 +67,6 @@ import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.ServiceFactory; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.csip.CsipSetCoordinatorService; -import com.android.bluetooth.flags.Flags; import com.android.bluetooth.le_audio.LeAudioService; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -117,9 +119,12 @@ public class VolumeControlService extends ProfileService { new HashMap<>(); private final Map<BluetoothDevice, VolumeControlInputDescriptor> mAudioInputs = new ConcurrentHashMap<>(); - private final Map<Integer, Integer> mGroupVolumeCache = new HashMap<>(); - private final Map<Integer, Boolean> mGroupMuteCache = new HashMap<>(); - private final Map<BluetoothDevice, Integer> mDeviceVolumeCache = new HashMap<>(); + private final Map<Integer, Integer> mGroupVolumeCache = new ConcurrentHashMap<>(); + private final Map<Integer, Boolean> mGroupMuteCache = new ConcurrentHashMap<>(); + private final Map<BluetoothDevice, Integer> mDeviceVolumeCache = new ConcurrentHashMap<>(); + private final Map<BluetoothDevice, Boolean> mDeviceMuteCache = new ConcurrentHashMap<>(); + + private Boolean mIgnoreSetVolumeFromAF = false; @VisibleForTesting ServiceFactory mFactory = new ServiceFactory(); @@ -198,6 +203,7 @@ public class VolumeControlService extends ProfileService { mGroupVolumeCache.clear(); mGroupMuteCache.clear(); mDeviceVolumeCache.clear(); + mDeviceMuteCache.clear(); synchronized (mCallbacks) { mCallbacks.kill(); @@ -529,7 +535,8 @@ public class VolumeControlService extends ProfileService { mNativeInterface.setExtAudioOutVolumeOffset(device, instanceId, volumeOffset); } - void setDeviceVolume(BluetoothDevice device, int volume, boolean isGroupOp) { + @VisibleForTesting + synchronized void setDeviceVolume(BluetoothDevice device, int volume, boolean isGroupOp) { Log.d( TAG, "setDeviceVolume: " + device + ", volume: " + volume + ", isGroupOp: " + isGroupOp); @@ -551,18 +558,60 @@ public class VolumeControlService extends ProfileService { Log.i(TAG, "Setting individual device volume"); mDeviceVolumeCache.put(device, volume); mNativeInterface.setVolume(device, volume); + + if (vcpDeviceVolumeApiImprovements()) { + // We only receive the volume change and mute state needs to be acquired manually + Boolean isStreamMute = + mAudioManager.isStreamMute(getBluetoothContextualVolumeStream()); + adjustDeviceMute(device, volume, isStreamMute); + } + } + } + + private void adjustDeviceMute(BluetoothDevice device, int volume, Boolean isStreamMute) { + Boolean isMute = getMute(device); + if (!isMute.equals(isStreamMute)) { + Log.d( + TAG, + "Mute state mismatch, stream mute: " + + isStreamMute + + ", device mute: " + + isMute + + ", new volume: " + + volume); + if (isStreamMute) { + Log.i(TAG, "Mute the device " + device); + mute(device); + } + if (!isStreamMute && (volume > 0)) { + Log.i(TAG, "Unmute the device " + device); + unmute(device); + } } } - public void setGroupVolume(int groupId, int volume) { + public synchronized void setGroupVolume(int groupId, int volume) { Log.d(TAG, "setGroupVolume: " + groupId + ", volume: " + volume); + if (mIgnoreSetVolumeFromAF) { + Log.d(TAG, "setGroupVolume ignored (from AF) because persisted/cached volume was used"); + mIgnoreSetVolumeFromAF = false; + return; + } + if (volume < 0) { Log.w(TAG, "Tried to set invalid volume " + volume + ". Ignored."); return; } - mGroupVolumeCache.put(groupId, volume); + synchronized (mDeviceVolumeCache) { + mGroupVolumeCache.put(groupId, volume); + if (vcpDeviceVolumeApiImprovements()) { + for (BluetoothDevice dev : getGroupDevices(groupId)) { + mDeviceVolumeCache.put(dev, volume); + } + } + } mNativeInterface.setGroupVolume(groupId, volume); // We only receive the volume change and mute state needs to be acquired manually @@ -578,7 +627,7 @@ public class VolumeControlService extends ProfileService { * have to explicitly unmute the remote device. */ if (!isGroupMute.equals(isStreamMute)) { - Log.w( + Log.d( TAG, "Mute state mismatch, stream mute: " + isStreamMute @@ -594,21 +643,60 @@ public class VolumeControlService extends ProfileService { Log.i(TAG, "Unmute the group " + groupId); unmuteGroup(groupId); } + } else if (vcpDeviceVolumeApiImprovements()) { + for (BluetoothDevice device : getGroupDevices(groupId)) { + adjustDeviceMute(device, volume, isStreamMute); + } } } + /** + * Get group cached volume. If not cached, then try to read from any device from this group. + * + * @param groupId the group identifier + * @return the cached volume + */ public int getGroupVolume(int groupId) { - return mGroupVolumeCache.getOrDefault(groupId, VOLUME_CONTROL_UNKNOWN_VOLUME); + if (vcpDeviceVolumeApiImprovements()) { + synchronized (mDeviceVolumeCache) { + Integer volume = mGroupVolumeCache.get(groupId); + if (volume != null) { + return volume; + } + Log.d(TAG, "No group volume available"); + for (BluetoothDevice device : getGroupDevices(groupId)) { + volume = mDeviceVolumeCache.get(device); + if (volume != null) { + Log.w(TAG, "Volume taken from device: " + device); + return volume; + } + } + return VOLUME_CONTROL_UNKNOWN_VOLUME; + } + } else { + return mGroupVolumeCache.getOrDefault(groupId, VOLUME_CONTROL_UNKNOWN_VOLUME); + } } /** - * Get device cached volume. + * Get device cached volume. If not cached, then try to read from its group. * * @param device the device * @return the cached volume */ public int getDeviceVolume(BluetoothDevice device) { - return mDeviceVolumeCache.getOrDefault(device, VOLUME_CONTROL_UNKNOWN_VOLUME); + if (vcpDeviceVolumeApiImprovements()) { + synchronized (mDeviceVolumeCache) { + Integer volume = mDeviceVolumeCache.get(device); + if (volume != null) { + return volume; + } + return mGroupVolumeCache.getOrDefault( + getGroupId(device), VOLUME_CONTROL_UNKNOWN_VOLUME); + } + } else { + return mDeviceVolumeCache.getOrDefault(device, VOLUME_CONTROL_UNKNOWN_VOLUME); + } } /** @@ -617,7 +705,7 @@ public class VolumeControlService extends ProfileService { * @param groupId the group identifier * @param active indicator if group is active or not */ - public void setGroupActive(int groupId, boolean active) { + public synchronized void setGroupActive(int groupId, boolean active) { Log.d(TAG, "setGroupActive: " + groupId + ", active: " + active); if (!active) { /* For now we don't need to handle group inactivation */ @@ -634,27 +722,75 @@ public class VolumeControlService extends ProfileService { } /** + * Get device cached mute status. If not cached, then try to read from its group. + * + * @param device the device + * @return mute status + */ + public Boolean getMute(BluetoothDevice device) { + synchronized (mDeviceMuteCache) { + Boolean isMute = mDeviceMuteCache.get(device); + if (isMute != null) { + return isMute; + } + return mGroupMuteCache.getOrDefault(getGroupId(device), false); + } + } + + /** + * Get group cached mute status. If not cached, then try to read from any device from this + * group. + * * @param groupId the group identifier + * @return mute status */ public Boolean getGroupMute(int groupId) { - return mGroupMuteCache.getOrDefault(groupId, false); + if (vcpDeviceVolumeApiImprovements()) { + synchronized (mDeviceMuteCache) { + Boolean isMute = mGroupMuteCache.get(groupId); + if (isMute != null) { + return isMute; + } + for (BluetoothDevice device : getGroupDevices(groupId)) { + isMute = mDeviceMuteCache.get(device); + if (isMute != null) { + return isMute; + } + } + return false; + } + } else { + return mGroupMuteCache.getOrDefault(groupId, false); + } } public void mute(BluetoothDevice device) { + mDeviceMuteCache.put(device, true); mNativeInterface.mute(device); } public void muteGroup(int groupId) { - mGroupMuteCache.put(groupId, true); + synchronized (mDeviceMuteCache) { + mGroupMuteCache.put(groupId, true); + for (BluetoothDevice dev : getGroupDevices(groupId)) { + mDeviceMuteCache.put(dev, true); + } + } mNativeInterface.muteGroup(groupId); } public void unmute(BluetoothDevice device) { + mDeviceMuteCache.put(device, false); mNativeInterface.unmute(device); } public void unmuteGroup(int groupId) { - mGroupMuteCache.put(groupId, false); + synchronized (mDeviceMuteCache) { + mGroupMuteCache.put(groupId, false); + for (BluetoothDevice dev : getGroupDevices(groupId)) { + mDeviceMuteCache.put(dev, false); + } + } mNativeInterface.unmuteGroup(groupId); } @@ -724,7 +860,7 @@ public class VolumeControlService extends ProfileService { notifyNewCallbackOfKnownVolumeInfo(callback); } - public void handleGroupNodeAdded(int groupId, BluetoothDevice device) { + public synchronized void handleGroupNodeAdded(int groupId, BluetoothDevice device) { // Ignore disconnected device, its volume will be set once it connects synchronized (mStateMachines) { VolumeControlStateMachine sm = mStateMachines.get(device); @@ -737,18 +873,33 @@ public class VolumeControlService extends ProfileService { } // If group volume has already changed, the new group member should set it - Integer groupVolume = getGroupVolume(groupId); - if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) { - Log.i(TAG, "Setting value:" + groupVolume + " to " + device); - mNativeInterface.setVolume(device, groupVolume); - } - - Boolean isGroupMuted = getGroupMute(groupId); - Log.i(TAG, "Setting mute:" + isGroupMuted + " to " + device); - if (isGroupMuted) { - mNativeInterface.mute(device); + if (vcpDeviceVolumeApiImprovements()) { + int volume = getDeviceVolume(device); + if (volume != VOLUME_CONTROL_UNKNOWN_VOLUME) { + Log.i(TAG, "Setting device/group volume:" + volume + " to the device:" + device); + setDeviceVolume(device, volume, false); + Boolean isDeviceMuted = getMute(device); + Log.i(TAG, "Setting mute:" + isDeviceMuted + " to " + device); + if (isDeviceMuted) { + mute(device); + } else { + unmute(device); + } + } } else { - mNativeInterface.unmute(device); + Integer groupVolume = getGroupVolume(groupId); + if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) { + Log.i(TAG, "Setting value:" + groupVolume + " to " + device); + mNativeInterface.setVolume(device, groupVolume); + } + + Boolean isGroupMuted = getGroupMute(groupId); + Log.i(TAG, "Setting mute:" + isGroupMuted + " to " + device); + if (isGroupMuted) { + mNativeInterface.mute(device); + } else { + mNativeInterface.unmute(device); + } } } @@ -769,14 +920,22 @@ public class VolumeControlService extends ProfileService { return; } - mGroupVolumeCache.put(groupId, volume); - mGroupMuteCache.put(groupId, mute); + synchronized (mDeviceVolumeCache) { + mGroupVolumeCache.put(groupId, volume); + mGroupMuteCache.put(groupId, mute); + if (vcpDeviceVolumeApiImprovements()) { + for (BluetoothDevice dev : getGroupDevices(groupId)) { + mDeviceVolumeCache.put(dev, volume); + mDeviceMuteCache.put(dev, mute); + } + } + } LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService != null) { int currentlyActiveGroupId = leAudioService.getActiveGroupId(); if (currentlyActiveGroupId == GROUP_ID_INVALID || groupId != currentlyActiveGroupId) { - if (!Flags.leaudioBroadcastVolumeControlPrimaryGroupOnly()) { + if (!leaudioBroadcastVolumeControlPrimaryGroupOnly()) { Log.i( TAG, "Skip updating to audio system if not updating volume for current" @@ -823,7 +982,7 @@ public class VolumeControlService extends ProfileService { return (int) Math.round((double) streamVolume * LE_AUDIO_MAX_VOL / streamMaxVolume); } - void handleVolumeControlChanged( + synchronized void handleVolumeControlChanged( BluetoothDevice device, int groupId, int volume, @@ -838,9 +997,6 @@ public class VolumeControlService extends ProfileService { return; } - int groupVolume = getGroupVolume(groupId); - Boolean groupMute = getGroupMute(groupId); - if (isAutonomous && device != null) { Log.i( TAG, @@ -851,25 +1007,59 @@ public class VolumeControlService extends ProfileService { /* We are here, because system has just started and LeAudio device is connected. If * remote device has User Persistent flag set, Android sets the volume to local cache * and to the audio system if not already streaming to other devices. - * If Reset Flag is set, then Android sets to remote devices either cached volume volume - * taken from audio manager. + * If Reset Flag is set, then Android sets to remote devices either cached volume or + * volume taken from audio manager (AF always call setVolume via LeAudioService at first + * connected remote from group). * Note, to match BR/EDR behavior, don't show volume change in UI here */ if (((flags & VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK) == 0x01) && (getConnectedDevices(groupId).size() == 1)) { Log.i(TAG, "Setting device: " + device + " volume: " + volume + " to the system"); + if (vcpDeviceVolumeApiImprovements()) { + // Ignore volume from AF because persisted volume was used + mIgnoreSetVolumeFromAF = true; + } updateGroupCacheAndAudioSystem(groupId, volume, mute, false); return; } // Reset flag is used - if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) { - Log.i(TAG, "Setting group volume: " + groupVolume + " to the device: " + device); - setGroupVolume(groupId, groupVolume); + if (vcpDeviceVolumeApiImprovements()) { + int deviceVolume = getDeviceVolume(device); + if (deviceVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) { + Log.i( + TAG, + "Setting device/group volume: " + + deviceVolume + + " to the device: " + + device); + setDeviceVolume(device, deviceVolume, false); + Boolean isDeviceMuted = getMute(device); + Log.i(TAG, "Setting mute:" + isDeviceMuted + " to " + device); + if (isDeviceMuted) { + mute(device); + } else { + unmute(device); + } + if (getConnectedDevices(groupId).size() == 1) { + // Ignore volume from AF because cached volume was used + mIgnoreSetVolumeFromAF = true; + } + } } else { - int systemVolume = getBleVolumeFromCurrentStream(); - Log.i(TAG, "Setting system volume: " + systemVolume + " to the group: " + groupId); - setGroupVolume(groupId, systemVolume); + int groupVolume = getGroupVolume(groupId); + if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) { + Log.i( + TAG, + "Setting group volume: " + groupVolume + " to the device: " + device); + setGroupVolume(groupId, groupVolume); + } else { + int systemVolume = getBleVolumeFromCurrentStream(); + Log.i( + TAG, + "Setting system volume: " + systemVolume + " to the group: " + groupId); + setGroupVolume(groupId, systemVolume); + } } return; @@ -894,13 +1084,16 @@ public class VolumeControlService extends ProfileService { } } - if (!isAutonomous) { + if (!vcpDeviceVolumeApiImprovements() && !isAutonomous) { /* If the change is triggered by Android device, the stream is already changed. * However it might be called with isAutonomous, one the first read of after * reconnection. Make sure device has group volume. Also it might happen that * remote side send us wrong value - lets check it. */ + int groupVolume = getGroupVolume(groupId); + Boolean groupMute = getGroupMute(groupId); + if ((groupVolume == volume) && (groupMute == mute)) { Log.i(TAG, " Volume:" + volume + ", mute:" + mute + " confirmed by remote side."); return; @@ -936,7 +1129,9 @@ public class VolumeControlService extends ProfileService { + " expected volume: " + groupVolume); } - } else { + } + + if (isAutonomous && device == null) { /* Received group notification for autonomous change. Update cache and audio system. */ updateGroupCacheAndAudioSystem(groupId, volume, mute, true); } @@ -1249,7 +1444,7 @@ public class VolumeControlService extends ProfileService { input.onGainSettingsPropertiesChanged(id, unit, min, max); } - void handleStackEvent(VolumeControlStackEvent stackEvent) { + synchronized void handleStackEvent(VolumeControlStackEvent stackEvent) { if (!isAvailable()) { Log.e(TAG, "Event ignored, service not available: " + stackEvent); return; @@ -1375,10 +1570,13 @@ public class VolumeControlService extends ProfileService { int broadcastVolume = VOLUME_CONTROL_UNKNOWN_VOLUME; if (volume.isPresent()) { broadcastVolume = volume.get(); - mDeviceVolumeCache.put(dev, broadcastVolume); + if (!vcpDeviceVolumeApiImprovements()) { + mDeviceVolumeCache.put(dev, broadcastVolume); + } } else { broadcastVolume = getDeviceVolume(dev); - if (broadcastVolume == VOLUME_CONTROL_UNKNOWN_VOLUME) { + if (!vcpDeviceVolumeApiImprovements() + && broadcastVolume == VOLUME_CONTROL_UNKNOWN_VOLUME) { broadcastVolume = getGroupVolume(getGroupId(dev)); } } @@ -1474,7 +1672,7 @@ public class VolumeControlService extends ProfileService { Log.d(TAG, device + " is unbond. Remove state machine"); removeStateMachine(device); } - } else if (toState == STATE_CONNECTED) { + } else if (!vcpDeviceVolumeApiImprovements() && toState == STATE_CONNECTED) { // Restore the group volume if it was changed while the device was not yet connected. Integer groupId = getGroupId(device); if (groupId != GROUP_ID_INVALID) { @@ -2025,5 +2223,18 @@ public class VolumeControlService extends ProfileService { + ", mute: " + getGroupMute(entry.getKey())); } + + if (vcpDeviceVolumeApiImprovements()) { + for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceVolumeCache.entrySet()) { + ProfileService.println( + sb, + " Device: " + + entry.getKey() + + " volume: " + + entry.getValue() + + ", mute: " + + getMute(entry.getKey())); + } + } } } diff --git a/android/app/src/com/com/android/bluetooth/le_scan/BatchScanThrottler.java b/android/app/src/com/com/android/bluetooth/le_scan/BatchScanThrottler.java new file mode 100644 index 0000000000..4fd324dcf9 --- /dev/null +++ b/android/app/src/com/com/android/bluetooth/le_scan/BatchScanThrottler.java @@ -0,0 +1,112 @@ +/* + * Copyright 2025 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.le_scan; + +import static com.android.bluetooth.le_scan.ScanController.DEFAULT_REPORT_DELAY_FLOOR; + +import android.provider.DeviceConfig; + +import com.android.bluetooth.Utils.TimeProvider; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Set; + +/** + * Throttler to reduce the number of times the Bluetooth process wakes up to check for pending batch + * scan results. The wake-up intervals are increased when no matching results are found and are + * longer when the screen is off. + */ +class BatchScanThrottler { + // Minimum batch trigger interval to check for batched results when the screen is off + @VisibleForTesting static final long SCREEN_OFF_MINIMUM_DELAY_FLOOR_MS = 20000L; + // Adjusted minimum report delay for unfiltered batch scan clients + @VisibleForTesting static final long UNFILTERED_DELAY_FLOOR_MS = 20000L; + // Adjusted minimum report delay for unfiltered batch scan clients when the screen is off + @VisibleForTesting static final long UNFILTERED_SCREEN_OFF_DELAY_FLOOR_MS = 60000L; + // Backoff stages used as multipliers for the minimum delay floor (standard or screen-off) + @VisibleForTesting static final int[] BACKOFF_MULTIPLIERS = {1, 1, 2, 2, 4}; + // Start screen-off trigger interval throttling after the screen has been off for this period + // of time. This allows the screen-on intervals to be used for a short period of time after the + // screen has gone off, and avoids too much flipping between screen-off and screen-on backoffs + // when the screen is off for a short period of time + @VisibleForTesting static final long SCREEN_OFF_DELAY_MS = 60000L; + private final TimeProvider mTimeProvider; + private final long mDelayFloor; + private final long mScreenOffDelayFloor; + private int mBackoffStage = 0; + private long mScreenOffTriggerTime = 0L; + private boolean mScreenOffThrottling = false; + + BatchScanThrottler(TimeProvider timeProvider, boolean screenOn) { + mTimeProvider = timeProvider; + mDelayFloor = + DeviceConfig.getLong( + DeviceConfig.NAMESPACE_BLUETOOTH, + "report_delay", + DEFAULT_REPORT_DELAY_FLOOR); + mScreenOffDelayFloor = Math.max(mDelayFloor, SCREEN_OFF_MINIMUM_DELAY_FLOOR_MS); + onScreenOn(screenOn); + } + + void resetBackoff() { + mBackoffStage = 0; + } + + void onScreenOn(boolean screenOn) { + if (screenOn) { + mScreenOffTriggerTime = 0L; + mScreenOffThrottling = false; + resetBackoff(); + } else { + // Screen-off intervals to be used after the trigger time + mScreenOffTriggerTime = mTimeProvider.elapsedRealtime() + SCREEN_OFF_DELAY_MS; + } + } + + long getBatchTriggerIntervalMillis(Set<ScanClient> batchClients) { + // Check if we're past the screen-off time and should be using screen-off backoff values + if (!mScreenOffThrottling + && mScreenOffTriggerTime != 0 + && mTimeProvider.elapsedRealtime() >= mScreenOffTriggerTime) { + mScreenOffThrottling = true; + resetBackoff(); + } + long unfilteredFloor = + mScreenOffThrottling + ? UNFILTERED_SCREEN_OFF_DELAY_FLOOR_MS + : UNFILTERED_DELAY_FLOOR_MS; + long intervalMillis = Long.MAX_VALUE; + for (ScanClient client : batchClients) { + if (client.settings.getReportDelayMillis() > 0) { + long clientIntervalMillis = client.settings.getReportDelayMillis(); + if ((client.filters == null || client.filters.isEmpty()) + && clientIntervalMillis < unfilteredFloor) { + clientIntervalMillis = unfilteredFloor; + } + intervalMillis = Math.min(intervalMillis, clientIntervalMillis); + } + } + int backoffIndex = + mBackoffStage >= BACKOFF_MULTIPLIERS.length + ? BACKOFF_MULTIPLIERS.length - 1 + : mBackoffStage++; + return Math.max( + intervalMillis, + (mScreenOffThrottling ? mScreenOffDelayFloor : mDelayFloor) + * BACKOFF_MULTIPLIERS[backoffIndex]); + } +} diff --git a/android/app/tests/unit/Android.bp b/android/app/tests/unit/Android.bp index 346c57fffa..eae8e4a89f 100644 --- a/android/app/tests/unit/Android.bp +++ b/android/app/tests/unit/Android.bp @@ -22,6 +22,7 @@ java_defaults { static_libs: [ "PlatformProperties", + "TestParameterInjector", "android.media.audio-aconfig-exported-java", "androidx.media_media", "androidx.room_room-migration", diff --git a/android/app/tests/unit/AndroidTest.xml b/android/app/tests/unit/AndroidTest.xml index 212b245d6e..5ac9bd5608 100644 --- a/android/app/tests/unit/AndroidTest.xml +++ b/android/app/tests/unit/AndroidTest.xml @@ -55,6 +55,6 @@ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> <option name="enable" value="true" /> - <option name="mainline-module-package-name" value="com.android.btservices" /> + <option name="mainline-module-package-name" value="com.android.bt" /> </object> </configuration> diff --git a/android/app/tests/unit/GoogleAndroidTest.xml b/android/app/tests/unit/GoogleAndroidTest.xml index 61a5011c8f..bcda5eacce 100644 --- a/android/app/tests/unit/GoogleAndroidTest.xml +++ b/android/app/tests/unit/GoogleAndroidTest.xml @@ -51,6 +51,6 @@ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> <option name="enable" value="true" /> - <option name="mainline-module-package-name" value="com.google.android.btservices" /> + <option name="mainline-module-package-name" value="com.google.android.bt" /> </object> </configuration> diff --git a/android/app/tests/unit/src/com/android/bluetooth/TestUtils.java b/android/app/tests/unit/src/com/android/bluetooth/TestUtils.java index 877df75ef2..78d1e66db1 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/TestUtils.java +++ b/android/app/tests/unit/src/com/android/bluetooth/TestUtils.java @@ -41,7 +41,6 @@ import androidx.test.uiautomator.UiDevice; import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService; import com.android.bluetooth.btservice.AdapterService; -import org.junit.Assert; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; @@ -112,11 +111,11 @@ public class TestUtils { * TestUtils#setAdapterService(AdapterService)} */ public static void clearAdapterService(AdapterService adapterService) { - Assert.assertSame( - "AdapterService.getAdapterService() must return the same object as the" - + " supplied adapterService in this method", - adapterService, - AdapterService.getAdapterService()); + assertWithMessage( + "AdapterService.getAdapterService() must return the same object as the" + + " supplied adapterService in this method") + .that(adapterService) + .isSameInstanceAs(AdapterService.getAdapterService()); assertThat(adapterService).isNotNull(); AdapterService.clearAdapterService(adapterService); } @@ -157,10 +156,7 @@ public class TestUtils { return context.getPackageManager() .getResourcesForApplication("com.android.bluetooth.tests"); } catch (PackageManager.NameNotFoundException e) { - assertWithMessage( - "Setup Failure: Unable to get test application resources" - + e.toString()) - .fail(); + assertWithMessage("Unable to get test application resources: " + e.toString()).fail(); return null; } } @@ -178,7 +174,7 @@ public class TestUtils { assertThat(intent).isNotNull(); return intent; } catch (InterruptedException e) { - Assert.fail("Cannot obtain an Intent from the queue: " + e.getMessage()); + assertWithMessage("Cannot obtain an Intent from the queue: " + e.toString()).fail(); } return null; } @@ -194,7 +190,7 @@ public class TestUtils { Intent intent = queue.poll(timeoutMs, TimeUnit.MILLISECONDS); assertThat(intent).isNull(); } catch (InterruptedException e) { - Assert.fail("Cannot obtain an Intent from the queue: " + e.getMessage()); + assertWithMessage("Cannot obtain an Intent from the queue: " + e.toString()).fail(); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java index 04527233f0..a481cef2e0 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java @@ -16,6 +16,9 @@ package com.android.bluetooth.a2dp; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + import static org.mockito.Mockito.*; import android.bluetooth.BluetoothAdapter; @@ -31,7 +34,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.R; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -243,22 +245,22 @@ public class A2dpCodecConfigTest { for (BluetoothCodecConfig config : codecConfigs) { switch (config.getCodecType()) { case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC: - Assert.assertEquals(config.getCodecPriority(), SBC_PRIORITY_DEFAULT); + assertThat(config.getCodecPriority()).isEqualTo(SBC_PRIORITY_DEFAULT); break; case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC: - Assert.assertEquals(config.getCodecPriority(), AAC_PRIORITY_DEFAULT); + assertThat(config.getCodecPriority()).isEqualTo(AAC_PRIORITY_DEFAULT); break; case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX: - Assert.assertEquals(config.getCodecPriority(), APTX_PRIORITY_DEFAULT); + assertThat(config.getCodecPriority()).isEqualTo(APTX_PRIORITY_DEFAULT); break; case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD: - Assert.assertEquals(config.getCodecPriority(), APTX_HD_PRIORITY_DEFAULT); + assertThat(config.getCodecPriority()).isEqualTo(APTX_HD_PRIORITY_DEFAULT); break; case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: - Assert.assertEquals(config.getCodecPriority(), LDAC_PRIORITY_DEFAULT); + assertThat(config.getCodecPriority()).isEqualTo(LDAC_PRIORITY_DEFAULT); break; case BluetoothCodecConfig.SOURCE_CODEC_TYPE_OPUS: - Assert.assertEquals(config.getCodecPriority(), OPUS_PRIORITY_DEFAULT); + assertThat(config.getCodecPriority()).isEqualTo(OPUS_PRIORITY_DEFAULT); break; } } @@ -785,10 +787,12 @@ public class A2dpCodecConfigTest { codecConfig.getCodecSpecific3(), codecConfig.getCodecSpecific4()); } - Assert.fail( - "getDefaultCodecConfigByType: No such codecType=" - + codecType - + " in sDefaultCodecConfigs"); + assertWithMessage( + "Default codec (" + + Arrays.toString(sDefaultCodecConfigs) + + ") does not contains " + + codecType) + .fail(); return null; } @@ -808,10 +812,12 @@ public class A2dpCodecConfigTest { codecCapabilities.getCodecSpecific3(), codecCapabilities.getCodecSpecific4()); } - Assert.fail( - "getCodecCapabilitiesByType: No such codecType=" - + codecType - + " in sCodecCapabilities"); + assertWithMessage( + "Codec capabilities (" + + Arrays.toString(sCodecCapabilities) + + ") does not contains " + + codecType) + .fail(); return null; } diff --git a/android/app/tests/unit/src/com/android/bluetooth/audio_util/BrowserPlayerWrapperTest.java b/android/app/tests/unit/src/com/android/bluetooth/audio_util/BrowserPlayerWrapperTest.java index 4b67658962..cd9ad4dd9a 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/audio_util/BrowserPlayerWrapperTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/audio_util/BrowserPlayerWrapperTest.java @@ -44,7 +44,6 @@ import com.android.bluetooth.R; import com.android.bluetooth.TestUtils; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -274,7 +273,7 @@ public class BrowserPlayerWrapperTest { MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue(); browserConnCb.onConnected(); - Assert.assertEquals("root_folder", wrapper.getRootId()); + assertThat(wrapper.getRootId()).isEqualTo("root_folder"); verify(mMockBrowser).disconnect(); } @@ -392,22 +391,22 @@ public class BrowserPlayerWrapperTest { for (int i = 0; i < item_list.size(); i++) { MediaItem expected = items.get(i); ListItem item = item_list.get(i); - Assert.assertEquals(expected.isBrowsable(), item.isFolder); + assertThat(item.isFolder).isEqualTo(expected.isBrowsable()); if (item.isFolder) { Folder folder = item.folder; assertThat(folder).isNotNull(); assertThat(folder.isPlayable).isFalse(); - Assert.assertEquals(expected.getDescription().getMediaId(), folder.mediaId); - Assert.assertEquals(expected.getDescription().getTitle().toString(), folder.title); + assertThat(folder.mediaId).isEqualTo(expected.getDescription().getMediaId()); + assertThat(folder.title).isEqualTo(expected.getDescription().getTitle().toString()); } else { Metadata song = item.song; assertThat(song).isNotNull(); - Assert.assertEquals(expected.getDescription().getMediaId(), song.mediaId); - Assert.assertEquals(expected.getDescription().getTitle().toString(), song.title); - Assert.assertEquals( - expected.getDescription().getSubtitle().toString(), song.artist); - Assert.assertEquals( - expected.getDescription().getDescription().toString(), song.album); + assertThat(song.mediaId).isEqualTo(expected.getDescription().getMediaId()); + assertThat(song.title).isEqualTo(expected.getDescription().getTitle().toString()); + assertThat(song.artist) + .isEqualTo(expected.getDescription().getSubtitle().toString()); + assertThat(song.album) + .isEqualTo(expected.getDescription().getDescription().toString()); if (expected.getDescription().getIconBitmap() != null) { assertThat(song.image).isNotNull(); Bitmap expectedBitmap = expected.getDescription().getIconBitmap(); @@ -415,7 +414,7 @@ public class BrowserPlayerWrapperTest { } else if (expected.getDescription().getIconUri() != null) { assertThat(mTestBitmap.sameAs(song.image.getImage())).isTrue(); } else { - Assert.assertEquals(null, song.image); + assertThat(song.image).isNull(); } } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerListTest.java b/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerListTest.java index ee066309b7..c8b03d632f 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerListTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerListTest.java @@ -16,6 +16,8 @@ package com.android.bluetooth.audio_util; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.*; import android.content.Context; @@ -31,7 +33,6 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -138,7 +139,7 @@ public class MediaPlayerListTest { } @Test - public void testUpdateMeidaDataForAudioPlaybackWhenAcitvePlayNotPlaying() { + public void testUpdateMediaDataForAudioPlaybackWhenActivePlayNotPlaying() { // Verify update media data with playing state doReturn(prepareMediaData(PlaybackState.STATE_PAUSED)) .when(mMockPlayerWrapper) @@ -146,7 +147,7 @@ public class MediaPlayerListTest { mMediaPlayerList.injectAudioPlaybacActive(true); verify(mMediaUpdateCallback).run(mMediaUpdateData.capture()); MediaData data = mMediaUpdateData.getValue(); - Assert.assertEquals(data.state.getState(), PlaybackState.STATE_PLAYING); + assertThat(data.state.getState()).isEqualTo(PlaybackState.STATE_PLAYING); // verify update media data with current media player media data MediaData currentMediaData = prepareMediaData(PlaybackState.STATE_PAUSED); @@ -154,9 +155,9 @@ public class MediaPlayerListTest { mMediaPlayerList.injectAudioPlaybacActive(false); verify(mMediaUpdateCallback, times(2)).run(mMediaUpdateData.capture()); data = mMediaUpdateData.getValue(); - Assert.assertEquals(data.metadata, currentMediaData.metadata); - Assert.assertEquals(data.state.toString(), currentMediaData.state.toString()); - Assert.assertEquals(data.queue, currentMediaData.queue); + assertThat(data.metadata).isEqualTo(currentMediaData.metadata); + assertThat(data.state.toString()).isEqualTo(currentMediaData.state.toString()); + assertThat(data.queue).isEqualTo(currentMediaData.queue); } @Test @@ -171,7 +172,7 @@ public class MediaPlayerListTest { } @Test - public void testNotUdpateMediaDataForAudioPlaybackWhenActivePlayerIsPlaying() { + public void testNotUpdateMediaDataForAudioPlaybackWhenActivePlayerIsPlaying() { // Verify not update media data for Audio Playback when active player is playing doReturn(prepareMediaData(PlaybackState.STATE_PLAYING)) .when(mMockPlayerWrapper) @@ -182,7 +183,7 @@ public class MediaPlayerListTest { } @Test - public void testNotUdpateMediaDataForActivePlayerWhenAudioPlaybackIsActive() { + public void testNotUpdateMediaDataForActivePlayerWhenAudioPlaybackIsActive() { doReturn(prepareMediaData(PlaybackState.STATE_PLAYING)) .when(mMockPlayerWrapper) .getCurrentMediaData(); @@ -225,7 +226,7 @@ public class MediaPlayerListTest { MediaPlayerWrapper newActiveMediaPlayer = mMediaPlayerList.getActivePlayer(); // Should be the same as before. - Assert.assertEquals(activeMediaPlayer, newActiveMediaPlayer); + assertThat(activeMediaPlayer).isEqualTo(newActiveMediaPlayer); session.release(); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java b/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java index 2ffe3589d5..2b323d9810 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java @@ -39,7 +39,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.R; import com.android.bluetooth.TestUtils; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -257,7 +256,7 @@ public class MediaPlayerWrapperTest { verify(mMockController).registerCallback(mControllerCbs.capture(), any()); MediaController.Callback controllerCallbacks = mControllerCbs.getValue(); - // Update Metdata returned by controller + // Update Metadata returned by controller mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "New Title"); doReturn(mTestMetadata.build()).when(mMockController).getMetadata(); controllerCallbacks.onMetadataChanged(mTestMetadata.build()); @@ -265,15 +264,9 @@ public class MediaPlayerWrapperTest { // Assert that the metadata was updated and playback state wasn't verify(mTestCbs).mediaUpdatedCallback(mMediaUpdateData.capture()); MediaData data = mMediaUpdateData.getValue(); - Assert.assertEquals( - "Returned Metadata isn't equal to given Metadata", - data.metadata, - Util.toMetadata(mMockContext, mTestMetadata.build())); - Assert.assertEquals( - "Returned PlaybackState isn't equal to original PlaybackState", - data.state.toString(), - mTestState.build().toString()); - Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0); + assertThat(data.metadata).isEqualTo(Util.toMetadata(mMockContext, mTestMetadata.build())); + assertThat(data.state.toString()).isEqualTo(mTestState.build().toString()); + assertThat(data.queue).isEmpty(); // Update PlaybackState returned by controller mTestState.setActiveQueueItemId(103); @@ -283,15 +276,9 @@ public class MediaPlayerWrapperTest { // Assert that the PlaybackState was changed but metadata stayed the same verify(mTestCbs, times(2)).mediaUpdatedCallback(mMediaUpdateData.capture()); data = mMediaUpdateData.getValue(); - Assert.assertEquals( - "Returned PlaybackState isn't equal to given PlaybackState", - data.state.toString(), - mTestState.build().toString()); - Assert.assertEquals( - "Returned Metadata isn't equal to given Metadata", - data.metadata, - Util.toMetadata(mMockContext, mTestMetadata.build())); - Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0); + assertThat(data.state.toString()).isEqualTo(mTestState.build().toString()); + assertThat(data.metadata).isEqualTo(Util.toMetadata(mMockContext, mTestMetadata.build())); + assertThat(data.queue).isEmpty(); // Verify that there are no timeout messages pending and there were no timeouts assertThat(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT)).isFalse(); @@ -332,15 +319,9 @@ public class MediaPlayerWrapperTest { // Assert that both metadata and playback state are there. verify(mTestCbs).mediaUpdatedCallback(mMediaUpdateData.capture()); MediaData data = mMediaUpdateData.getValue(); - Assert.assertEquals( - "Returned PlaybackState isn't equal to given PlaybackState", - data.state.toString(), - mTestState.build().toString()); - Assert.assertEquals( - "Returned Metadata isn't equal to given Metadata", - data.metadata, - Util.toMetadata(mMockContext, mTestMetadata.build())); - Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0); + assertThat(data.state.toString()).isEqualTo(mTestState.build().toString()); + assertThat(data.metadata).isEqualTo(Util.toMetadata(mMockContext, mTestMetadata.build())); + assertThat(data.queue).isEmpty(); // Verify that there are no timeout messages pending and there were no timeouts assertThat(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT)).isFalse(); @@ -371,10 +352,7 @@ public class MediaPlayerWrapperTest { // Assert that the metadata returned by getMetadata() is used instead of null verify(mTestCbs).mediaUpdatedCallback(mMediaUpdateData.capture()); MediaData data = mMediaUpdateData.getValue(); - Assert.assertEquals( - "Returned metadata is incorrect", - data.metadata, - Util.toMetadata(mMockContext, mTestMetadata.build())); + assertThat(data.metadata).isEqualTo(Util.toMetadata(mMockContext, mTestMetadata.build())); } @Test @@ -402,10 +380,7 @@ public class MediaPlayerWrapperTest { verify(mTestCbs).mediaUpdatedCallback(mMediaUpdateData.capture()); MediaData data = mMediaUpdateData.getValue(); - Assert.assertEquals( - "Returned PlaybackState is incorrect", - data.state.toString(), - mTestState.build().toString()); + assertThat(data.state.toString()).isEqualTo(mTestState.build().toString()); } @Test @@ -428,7 +403,7 @@ public class MediaPlayerWrapperTest { // Assert that both metadata and playback state are there. verify(mTestCbs).mediaUpdatedCallback(mMediaUpdateData.capture()); MediaData data = mMediaUpdateData.getValue(); - Assert.assertEquals("Returned Queue isn't null", data.queue.size(), 0); + assertThat(data.queue).isEmpty(); } /* @@ -446,9 +421,10 @@ public class MediaPlayerWrapperTest { // Call getCurrentQueue() multiple times. for (int i = 0; i < 3; i++) { - Assert.assertEquals( - Util.toMetadataList(mMockContext, getQueueFromDescriptions(mTestQueue)), - wrapper.getCurrentQueue()); + assertThat(wrapper.getCurrentQueue()) + .isEqualTo( + Util.toMetadataList( + mMockContext, getQueueFromDescriptions(mTestQueue))); } doReturn(mTestMetadata.build()).when(mMockController).getMetadata(); @@ -471,9 +447,8 @@ public class MediaPlayerWrapperTest { assertThat(Util.toMetadata(mMockContext, mTestMetadata.build()).duration) .isNotEqualTo(wrapper.getCurrentQueue().get(0).duration); doReturn(mTestMetadata.build()).when(mMockController).getMetadata(); - Assert.assertEquals( - Util.toMetadata(mMockContext, mTestMetadata.build()).duration, - wrapper.getCurrentQueue().get(0).duration); + assertThat(wrapper.getCurrentQueue().get(0).duration) + .isEqualTo(Util.toMetadata(mMockContext, mTestMetadata.build()).duration); // The MediaController Metadata should still not be equal to the queue // as the track count is different and should not be overridden. assertThat(Util.toMetadata(mMockContext, mTestMetadata.build())) @@ -506,15 +481,9 @@ public class MediaPlayerWrapperTest { // Assert that both metadata and only the first playback state is there. verify(mTestCbs).mediaUpdatedCallback(mMediaUpdateData.capture()); MediaData data = mMediaUpdateData.getValue(); - Assert.assertEquals( - "Returned PlaybackState isn't equal to given PlaybackState", - data.state.toString(), - mTestState.build().toString()); - Assert.assertEquals( - "Returned Metadata isn't equal to given Metadata", - data.metadata, - Util.toMetadata(mMockContext, mTestMetadata.build())); - Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0); + assertThat(data.state.toString()).isEqualTo(mTestState.build().toString()); + assertThat(data.metadata).isEqualTo(Util.toMetadata(mMockContext, mTestMetadata.build())); + assertThat(data.queue).isEmpty(); // Update PlaybackState returned by controller (Shouldn't trigger update) mTestState.setState(PlaybackState.STATE_PLAYING, 1020, 1.0f); @@ -623,18 +592,10 @@ public class MediaPlayerWrapperTest { verify(mTestCbs).mediaUpdatedCallback(mMediaUpdateData.capture()); verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean()); MediaData data = mMediaUpdateData.getValue(); - Assert.assertEquals( - "Returned Metadata isn't equal to given Metadata", - data.metadata, - Util.toMetadata(mMockContext, mTestMetadata.build())); - Assert.assertEquals( - "Returned PlaybackState isn't equal to given PlaybackState", - data.state.toString(), - mTestState.build().toString()); - Assert.assertEquals( - "Returned Queue isn't equal to given Queue", - data.queue, - Util.toMetadataList(mMockContext, getQueueFromDescriptions(mTestQueue))); + assertThat(data.metadata).isEqualTo(Util.toMetadata(mMockContext, mTestMetadata.build())); + assertThat(data.state.toString()).isEqualTo(mTestState.build().toString()); + assertThat(data.queue) + .isEqualTo(Util.toMetadataList(mMockContext, getQueueFromDescriptions(mTestQueue))); // Verify that there are no timeout messages pending and there were no timeouts assertThat(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT)).isFalse(); @@ -673,18 +634,10 @@ public class MediaPlayerWrapperTest { // Assert that the callback was called with the mismatch data verify(mTestCbs).mediaUpdatedCallback(mMediaUpdateData.capture()); MediaData data = mMediaUpdateData.getValue(); - Assert.assertEquals( - "Returned Metadata isn't equal to given Metadata", - data.metadata, - Util.toMetadata(mMockContext, mTestMetadata.build())); - Assert.assertEquals( - "Returned PlaybackState isn't equal to given PlaybackState", - data.state.toString(), - mTestState.build().toString()); - Assert.assertEquals( - "Returned Queue isn't equal to given Queue", - data.queue, - Util.toMetadataList(mMockContext, getQueueFromDescriptions(mTestQueue))); + assertThat(data.metadata).isEqualTo(Util.toMetadata(mMockContext, mTestMetadata.build())); + assertThat(data.state.toString()).isEqualTo(mTestState.build().toString()); + assertThat(data.queue) + .isEqualTo(Util.toMetadataList(mMockContext, getQueueFromDescriptions(mTestQueue))); } /* @@ -711,7 +664,7 @@ public class MediaPlayerWrapperTest { s.setState(PlaybackState.STATE_PAUSED, 0, 1.0f); MediaDescription.Builder d = new MediaDescription.Builder(); for (int i = 1; i <= numTestLoops; i++) { - // Setup Media Info for current itteration + // Setup Media Info for current iteration m.putString(MediaMetadata.METADATA_KEY_TITLE, "BT Fuzz Song " + i); m.putString(MediaMetadata.METADATA_KEY_ARTIST, "BT Fuzz Artist " + i); m.putString(MediaMetadata.METADATA_KEY_ALBUM, "BT Fuzz Album " + i); @@ -751,18 +704,9 @@ public class MediaPlayerWrapperTest { // that all the Media info matches what was given verify(mTestCbs, times(i)).mediaUpdatedCallback(mMediaUpdateData.capture()); MediaData data = mMediaUpdateData.getValue(); - Assert.assertEquals( - "Returned Metadata isn't equal to given Metadata", - data.metadata, - Util.toMetadata(mMockContext, m.build())); - Assert.assertEquals( - "Returned PlaybackState isn't equal to given PlaybackState", - data.state.toString(), - s.build().toString()); - Assert.assertEquals( - "Returned Queue isn't equal to given Queue", - data.queue, - Util.toMetadataList(mMockContext, q)); + assertThat(data.metadata).isEqualTo(Util.toMetadata(mMockContext, m.build())); + assertThat(data.state.toString()).isEqualTo(s.build().toString()); + assertThat(data.queue).isEqualTo(Util.toMetadataList(mMockContext, q)); } // Verify that there are no timeout messages pending and there were no timeouts diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java index 03a05875b2..24006678fc 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java @@ -50,9 +50,7 @@ import com.android.bluetooth.a2dpsink.A2dpSinkService; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.flags.Flags; -import org.hamcrest.core.IsInstanceOf; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -173,7 +171,7 @@ public class AvrcpControllerStateMachineTest { TestUtils.waitForLooperToBeIdle(sm.getHandler().getLooper()); // is disconnected - Assert.assertEquals(sm.getState(), BluetoothProfile.STATE_DISCONNECTED); + assertThat(sm.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED); // told mAvrcpControllerService to remove it verify(mAvrcpControllerService).removeStateMachine(eq(sm)); @@ -203,19 +201,17 @@ public class AvrcpControllerStateMachineTest { */ private int setUpConnectedState(boolean control, boolean browsing) { - Assert.assertThat( - mAvrcpStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class)); + assertThat(mAvrcpStateMachine.getCurrentState()) + .isInstanceOf(AvrcpControllerStateMachine.Disconnected.class); mAvrcpStateMachine.connect(StackEvent.connectionStateChanged(control, browsing)); TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)) .sendBroadcast(mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), any(Bundle.class)); - Assert.assertThat( - mAvrcpStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Connected.class)); - Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_CONNECTED); + assertThat(mAvrcpStateMachine.getCurrentState()) + .isInstanceOf(AvrcpControllerStateMachine.Connected.class); + assertThat(mAvrcpStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); return BluetoothProfile.STATE_CONNECTED; } @@ -273,7 +269,7 @@ public class AvrcpControllerStateMachineTest { mAvrcpStateMachine.sendMessage( AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED, track); TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); - Assert.assertEquals(mAvrcpStateMachine.getCurrentTrack(), track); + assertThat(mAvrcpStateMachine.getCurrentTrack()).isEqualTo(track); } /** Set the current play status (Play, Pause, etc.) of the device */ @@ -328,35 +324,7 @@ public class AvrcpControllerStateMachineTest { // Make sure its set by re grabbing the node and checking its contents are cached nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING"); assertThat(nowPlaying.isCached()).isTrue(); - assertNowPlayingList(nowPlayingList); - } - - private String avrcpItemListToString(List<AvrcpItem> items) { - StringBuilder s = new StringBuilder(); - s.append("["); - if (items != null) { - for (int i = 0; i < items.size(); i++) { - AvrcpItem item = items.get(i); - s.append((item != null ? Long.toString(item.getUid()) : "null")); - if (i != items.size() - 1) s.append(", "); - } - } - s.append("]"); - return s.toString(); - } - - /** Assert that the Now Playing list is a particular value */ - private void assertNowPlayingList(List<AvrcpItem> expected) { - List<AvrcpItem> current = getNowPlayingList(); - String err = - "Now playing list incorrect, expected=" - + avrcpItemListToString(expected) - + ", actual=" - + avrcpItemListToString(current); - Assert.assertEquals(err, expected.size(), current.size()); - for (int i = 0; i < expected.size(); i++) { - Assert.assertEquals(err, expected.get(i), current.get(i)); - } + assertThat(getNowPlayingList()).containsExactlyElementsIn(nowPlayingList).inOrder(); } /** @@ -387,19 +355,19 @@ public class AvrcpControllerStateMachineTest { numBroadcastsSent += 2; verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)) .sendBroadcast(mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), any(Bundle.class)); - Assert.assertEquals( - mTestDevice, - mIntentArgument.getValue().getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); - Assert.assertEquals( - BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED, - mIntentArgument.getValue().getAction()); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, - mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); - Assert.assertThat( - mAvrcpStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class)); - Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED); + assertThat( + mIntentArgument + .getValue() + .getParcelableExtra( + BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class)) + .isEqualTo(mTestDevice); + assertThat(mIntentArgument.getValue().getAction()) + .isEqualTo(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED); + assertThat(mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + assertThat(mAvrcpStateMachine.getCurrentState()) + .isInstanceOf(AvrcpControllerStateMachine.Disconnected.class); + assertThat(mAvrcpStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED); verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine)); } @@ -410,26 +378,25 @@ public class AvrcpControllerStateMachineTest { MediaControllerCompat.TransportControls transportControls = BluetoothMediaBrowserService.getTransportControls(); assertThat(transportControls).isNotNull(); - Assert.assertEquals( - PlaybackStateCompat.STATE_NONE, - BluetoothMediaBrowserService.getPlaybackState().getState()); + assertThat(BluetoothMediaBrowserService.getPlaybackState().getState()) + .isEqualTo(PlaybackStateCompat.STATE_NONE); mAvrcpStateMachine.disconnect(); numBroadcastsSent += 2; verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)) .sendBroadcast(mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), any(Bundle.class)); - Assert.assertEquals( - mTestDevice, - mIntentArgument.getValue().getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); - Assert.assertEquals( - BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED, - mIntentArgument.getValue().getAction()); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, - mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); - Assert.assertThat( - mAvrcpStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class)); - Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED); + assertThat( + mIntentArgument + .getValue() + .getParcelableExtra( + BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class)) + .isEqualTo(mTestDevice); + assertThat(mIntentArgument.getValue().getAction()) + .isEqualTo(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED); + assertThat(mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + assertThat(mAvrcpStateMachine.getCurrentState()) + .isInstanceOf(AvrcpControllerStateMachine.Disconnected.class); + assertThat(mAvrcpStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED); verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine)); } @@ -437,29 +404,28 @@ public class AvrcpControllerStateMachineTest { @Test @FlakyTest public void testBrowsingOnly() { - Assert.assertEquals(0, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount()); + assertThat(mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount()).isEqualTo(0); int numBroadcastsSent = setUpConnectedState(false, true); - Assert.assertEquals(1, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount()); - Assert.assertEquals( - PlaybackStateCompat.STATE_NONE, - BluetoothMediaBrowserService.getPlaybackState().getState()); + assertThat(mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount()).isEqualTo(1); + assertThat(BluetoothMediaBrowserService.getPlaybackState().getState()) + .isEqualTo(PlaybackStateCompat.STATE_NONE); mAvrcpStateMachine.disconnect(); numBroadcastsSent += 2; verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)) .sendBroadcast(mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), any(Bundle.class)); - Assert.assertEquals( - mTestDevice, - mIntentArgument.getValue().getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); - Assert.assertEquals( - BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED, - mIntentArgument.getValue().getAction()); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, - mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); - Assert.assertThat( - mAvrcpStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class)); - Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED); + assertThat( + mIntentArgument + .getValue() + .getParcelableExtra( + BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class)) + .isEqualTo(mTestDevice); + assertThat(mIntentArgument.getValue().getAction()) + .isEqualTo(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED); + assertThat(mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + assertThat(mAvrcpStateMachine.getCurrentState()) + .isInstanceOf(AvrcpControllerStateMachine.Disconnected.class); + assertThat(mAvrcpStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED); verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine)); } @@ -478,7 +444,7 @@ public class AvrcpControllerStateMachineTest { /** Test to make sure the state machine is tracking the correct device */ @Test public void testGetDevice() { - Assert.assertEquals(mAvrcpStateMachine.getDevice(), mTestDevice); + assertThat(mAvrcpStateMachine.getDevice()).isEqualTo(mTestDevice); } /** Test that dumpsys will generate information about connected devices */ @@ -724,10 +690,9 @@ public class AvrcpControllerStateMachineTest { TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); // Verify that the player object is available. - Assert.assertEquals(true, results.isCached()); - Assert.assertEquals( - "MediaItem{mFlags=1, mDescription=" + playerName + ", null, null}", - results.getChildren().get(0).getMediaItem().toString()); + assertThat(results.isCached()).isTrue(); + assertThat(results.getChildren().get(0).getMediaItem().toString()) + .isEqualTo("MediaItem{mFlags=1, mDescription=" + playerName + ", null, null}"); // Fetch contents of that player object BrowseTree.BrowseNode playerOneNode = @@ -806,13 +771,13 @@ public class AvrcpControllerStateMachineTest { assertThat(mAvrcpStateMachine.mBrowseTree.mRootNode.isCached()).isTrue(); SparseArray<AvrcpPlayer> players = mAvrcpStateMachine.getAvailablePlayers(); assertThat(players.contains(mAvrcpStateMachine.getAddressedPlayerId())).isTrue(); - Assert.assertEquals(testPlayers.size(), players.size()); + assertThat(players.size()).isEqualTo(testPlayers.size()); for (AvrcpPlayer player : testPlayers) { assertThat(players.contains(player.getId())).isTrue(); } // Verify we request metadata, playback state and now playing list - assertNowPlayingList(new ArrayList<AvrcpItem>()); + assertThat(getNowPlayingList()).isEmpty(); verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) .getNowPlayingList(eq(mTestAddress), eq(0), eq(19)); verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) @@ -858,7 +823,7 @@ public class AvrcpControllerStateMachineTest { assertThat(mAvrcpStateMachine.mBrowseTree.mRootNode.isCached()).isTrue(); SparseArray<AvrcpPlayer> players = mAvrcpStateMachine.getAvailablePlayers(); assertThat(players.contains(mAvrcpStateMachine.getAddressedPlayerId())).isTrue(); - Assert.assertEquals(testPlayers.size() + 1, players.size()); + assertThat(players.size()).isEqualTo(testPlayers.size() + 1); for (AvrcpPlayer player : testPlayers) { assertThat(players.contains(player.getId())).isTrue(); } @@ -913,12 +878,12 @@ public class AvrcpControllerStateMachineTest { TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); // The addressed player should always be in the available player set - Assert.assertEquals(2, mAvrcpStateMachine.getAddressedPlayerId()); + assertThat(mAvrcpStateMachine.getAddressedPlayerId()).isEqualTo(2); SparseArray<AvrcpPlayer> players = mAvrcpStateMachine.getAvailablePlayers(); assertThat(players.contains(mAvrcpStateMachine.getAddressedPlayerId())).isTrue(); // Make sure the Now Playing list is now cleared - assertNowPlayingList(new ArrayList<AvrcpItem>()); + assertThat(getNowPlayingList()).isEmpty(); // Verify that a player change to a player with Now Playing support causes a refresh. verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) @@ -967,7 +932,7 @@ public class AvrcpControllerStateMachineTest { TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); // Make sure the Now Playing list is now cleared and we requested metadata - assertNowPlayingList(new ArrayList<AvrcpItem>()); + assertThat(getNowPlayingList()).isEmpty(); verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) .getCurrentMetadata(eq(mTestAddress)); verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) @@ -1256,24 +1221,24 @@ public class AvrcpControllerStateMachineTest { MediaMetadataCompat metadata = controller.getMetadata(); assertThat(metadata).isNotNull(); - Assert.assertEquals("title", metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)); - Assert.assertEquals("artist", metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)); - Assert.assertEquals("album", metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM)); - Assert.assertEquals(1, metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER)); - Assert.assertEquals(10, metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS)); - Assert.assertEquals("none", metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE)); - Assert.assertEquals(10, metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)).isEqualTo("title"); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)).isEqualTo("artist"); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM)).isEqualTo("album"); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER)).isEqualTo(1); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS)).isEqualTo(10); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE)).isEqualTo("none"); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)).isEqualTo(10); PlaybackStateCompat playbackState = controller.getPlaybackState(); assertThat(playbackState).isNotNull(); - Assert.assertEquals(PlaybackStateCompat.STATE_PAUSED, playbackState.getState()); - Assert.assertEquals(7, playbackState.getPosition()); + assertThat(playbackState.getState()).isEqualTo(PlaybackStateCompat.STATE_PAUSED); + assertThat(playbackState.getPosition()).isEqualTo(7); List<MediaSessionCompat.QueueItem> queue = controller.getQueue(); assertThat(queue).isNotNull(); - Assert.assertEquals(2, queue.size()); - Assert.assertEquals("title", queue.get(0).getDescription().getTitle().toString()); - Assert.assertEquals("title 2", queue.get(1).getDescription().getTitle().toString()); + assertThat(queue).hasSize(2); + assertThat(queue.get(0).getDescription().getTitle().toString()).isEqualTo("title"); + assertThat(queue.get(1).getDescription().getTitle().toString()).isEqualTo("title 2"); } /** Test becoming inactive from the active state */ @@ -1323,18 +1288,18 @@ public class AvrcpControllerStateMachineTest { MediaMetadataCompat metadata = controller.getMetadata(); assertThat(metadata).isNotNull(); - Assert.assertEquals("Song 1", metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)); - Assert.assertEquals("artist", metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)); - Assert.assertEquals("album", metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM)); - Assert.assertEquals(1, metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER)); - Assert.assertEquals(2, metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS)); - Assert.assertEquals("none", metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE)); - Assert.assertEquals(10, metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)).isEqualTo("Song 1"); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)).isEqualTo("artist"); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM)).isEqualTo("album"); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER)).isEqualTo(1); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS)).isEqualTo(2); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE)).isEqualTo("none"); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)).isEqualTo(10); PlaybackStateCompat playbackState = controller.getPlaybackState(); assertThat(playbackState).isNotNull(); - Assert.assertEquals(PlaybackStateCompat.STATE_PLAYING, playbackState.getState()); - Assert.assertEquals(0, playbackState.getActiveQueueItemId()); + assertThat(playbackState.getState()).isEqualTo(PlaybackStateCompat.STATE_PLAYING); + assertThat(playbackState.getActiveQueueItemId()).isEqualTo(0); // Track changes, with new metadata and new track number track = makeTrack("Song 2", "artist", "album", 2, 2, "none", 10, null); @@ -1344,18 +1309,18 @@ public class AvrcpControllerStateMachineTest { // Assert new track metadata and active queue item metadata = controller.getMetadata(); assertThat(metadata).isNotNull(); - Assert.assertEquals("Song 2", metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)); - Assert.assertEquals("artist", metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)); - Assert.assertEquals("album", metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM)); - Assert.assertEquals(2, metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER)); - Assert.assertEquals(2, metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS)); - Assert.assertEquals("none", metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE)); - Assert.assertEquals(10, metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)).isEqualTo("Song 2"); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)).isEqualTo("artist"); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM)).isEqualTo("album"); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER)).isEqualTo(2); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS)).isEqualTo(2); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE)).isEqualTo("none"); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)).isEqualTo(10); playbackState = controller.getPlaybackState(); assertThat(playbackState).isNotNull(); - Assert.assertEquals(PlaybackStateCompat.STATE_PLAYING, playbackState.getState()); - Assert.assertEquals(1, playbackState.getActiveQueueItemId()); + assertThat(playbackState.getState()).isEqualTo(PlaybackStateCompat.STATE_PLAYING); + assertThat(playbackState.getActiveQueueItemId()).isEqualTo(1); } /** Test receiving a track change update when we're not the active device */ @@ -1405,9 +1370,8 @@ public class AvrcpControllerStateMachineTest { eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN)); verify(mA2dpSinkService, never()).requestAudioFocus(mTestDevice, true); - Assert.assertEquals( - PlaybackStateCompat.STATE_ERROR, - BluetoothMediaBrowserService.getPlaybackState().getState()); + assertThat(BluetoothMediaBrowserService.getPlaybackState().getState()) + .isEqualTo(PlaybackStateCompat.STATE_ERROR); } /** Test receiving a play position update when we're not the active device */ @@ -1433,7 +1397,7 @@ public class AvrcpControllerStateMachineTest { PlaybackStateCompat playbackState = controller.getPlaybackState(); assertThat(playbackState).isNotNull(); - Assert.assertEquals(0, playbackState.getPosition()); + assertThat(playbackState.getPosition()).isEqualTo(0); } /** Test receiving a now playing list update when we're not the active device */ @@ -1771,7 +1735,7 @@ public class AvrcpControllerStateMachineTest { // Make sure its set by re grabbing the node and checking its contents are cached nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING"); assertThat(nowPlaying.isCached()).isTrue(); - assertNowPlayingList(updatedNowPlayingList); + assertThat(getNowPlayingList()).containsExactlyElementsIn(updatedNowPlayingList).inOrder(); } /** @@ -1831,7 +1795,7 @@ public class AvrcpControllerStateMachineTest { // Make sure its set by re grabbing the node and checking its contents are cached nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING"); assertThat(nowPlaying.isCached()).isTrue(); - assertNowPlayingList(updatedNowPlayingList); + assertThat(getNowPlayingList()).containsExactlyElementsIn(updatedNowPlayingList).inOrder(); } /** @@ -1899,7 +1863,7 @@ public class AvrcpControllerStateMachineTest { // Make sure its set by re grabbing the node and checking its contents are cached nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING"); assertThat(nowPlaying.isCached()).isTrue(); - assertNowPlayingList(updatedNowPlayingList); + assertThat(getNowPlayingList()).containsExactlyElementsIn(updatedNowPlayingList).inOrder(); } /** @@ -1964,7 +1928,7 @@ public class AvrcpControllerStateMachineTest { // Make sure its set by re grabbing the node and checking its contents are cached nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING"); assertThat(nowPlaying.isCached()).isTrue(); - assertNowPlayingList(updatedNowPlayingList); + assertThat(getNowPlayingList()).containsExactlyElementsIn(updatedNowPlayingList).inOrder(); } /** @@ -1997,7 +1961,8 @@ public class AvrcpControllerStateMachineTest { TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); // Node should be set to cached and notified on - assertNowPlayingList(new ArrayList<AvrcpItem>()); + assertThat(getNowPlayingList()).isEmpty(); + assertThat(nowPlaying.isCached()).isTrue(); // See that state from BluetoothMediaBrowserService is updated to null (i.e. empty) @@ -2089,7 +2054,7 @@ public class AvrcpControllerStateMachineTest { playerNodes = mAvrcpStateMachine.findNode(results.getID()); assertThat(playerNodes.isCached()).isTrue(); assertThat(playerNodes.getChildren()).isNotNull(); - assertThat(playerNodes.getChildren().size()).isEqualTo(2); + assertThat(playerNodes.getChildren()).hasSize(2); assertThat(playerNodes.getChildren().get(0).getMediaItem().toString()) .isEqualTo("MediaItem{mFlags=1, mDescription=player 1, null, null}"); assertThat(playerNodes.getChildren().get(1).getMediaItem().toString()) diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpCoverArtStorageTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpCoverArtStorageTest.java index 6cf6bb466b..37db2a87bb 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpCoverArtStorageTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpCoverArtStorageTest.java @@ -32,7 +32,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -95,7 +94,7 @@ public final class AvrcpCoverArtStorageTest { Uri uri = mAvrcpCoverArtStorage.addImage(mDevice1, mHandle1, mImage1); - Assert.assertEquals(expectedUri, uri); + assertThat(uri).isEqualTo(expectedUri); assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isTrue(); } @@ -106,12 +105,12 @@ public final class AvrcpCoverArtStorageTest { Uri uri = mAvrcpCoverArtStorage.addImage(mDevice1, mHandle1, mImage1); assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isTrue(); - Assert.assertEquals(expectedUri, uri); + assertThat(uri).isEqualTo(expectedUri); assertImageSame(mImage1, mDevice1, mHandle1); uri = mAvrcpCoverArtStorage.addImage(mDevice1, mHandle1, mImage2); assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isTrue(); - Assert.assertEquals(expectedUri, uri); + assertThat(uri).isEqualTo(expectedUri); assertImageSame(mImage2, mDevice1, mHandle1); } @@ -126,10 +125,10 @@ public final class AvrcpCoverArtStorageTest { Uri uri2 = mAvrcpCoverArtStorage.addImage(mDevice1, mHandle2, mImage2); assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isTrue(); - Assert.assertEquals(expectedUri1, uri1); + assertThat(uri1).isEqualTo(expectedUri1); assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle2)).isTrue(); - Assert.assertEquals(expectedUri2, uri2); + assertThat(uri2).isEqualTo(expectedUri2); } @Test @@ -143,38 +142,38 @@ public final class AvrcpCoverArtStorageTest { Uri uri2 = mAvrcpCoverArtStorage.addImage(mDevice2, mHandle1, mImage1); assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isTrue(); - Assert.assertEquals(expectedUri1, uri1); + assertThat(uri1).isEqualTo(expectedUri1); assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isTrue(); - Assert.assertEquals(expectedUri2, uri2); + assertThat(uri2).isEqualTo(expectedUri2); } @Test public void addNullImage_imageNotAdded() { Uri uri = mAvrcpCoverArtStorage.addImage(mDevice1, mHandle1, null); assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isFalse(); - Assert.assertEquals(null, uri); + assertThat(uri).isNull(); } @Test public void addImageNullDevice_imageNotAdded() { Uri uri = mAvrcpCoverArtStorage.addImage(null, mHandle1, mImage1); assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isFalse(); - Assert.assertEquals(null, uri); + assertThat(uri).isNull(); } @Test public void addImageNullHandle_imageNotAdded() { Uri uri = mAvrcpCoverArtStorage.addImage(mDevice1, null, mImage1); assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isFalse(); - Assert.assertEquals(null, uri); + assertThat(uri).isNull(); } @Test public void addImageEmptyHandle_imageNotAdded() { Uri uri = mAvrcpCoverArtStorage.addImage(mDevice1, "", mImage1); assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isFalse(); - Assert.assertEquals(null, uri); + assertThat(uri).isNull(); } @Test @@ -198,28 +197,28 @@ public final class AvrcpCoverArtStorageTest { public void getImageThatDoesntExist_returnsNull() { assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isFalse(); Bitmap image = mAvrcpCoverArtStorage.getImage(mDevice1, mHandle1); - Assert.assertEquals(null, image); + assertThat(image).isNull(); } @Test public void getImageNullDevice_returnsNull() { assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isFalse(); Bitmap image = mAvrcpCoverArtStorage.getImage(null, mHandle1); - Assert.assertEquals(null, image); + assertThat(image).isNull(); } @Test public void getImageNullHandle_returnsNull() { assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isFalse(); Bitmap image = mAvrcpCoverArtStorage.getImage(mDevice1, null); - Assert.assertEquals(null, image); + assertThat(image).isNull(); } @Test public void getImageEmptyHandle_returnsNull() { assertThat(mAvrcpCoverArtStorage.doesImageExist(mDevice1, mHandle1)).isFalse(); Bitmap image = mAvrcpCoverArtStorage.getImage(mDevice1, ""); - Assert.assertEquals(null, image); + assertThat(image).isNull(); } @Test @@ -344,6 +343,6 @@ public final class AvrcpCoverArtStorageTest { mAvrcpCoverArtStorage.addImage(mDevice1, mHandle1, mImage1); - Assert.assertEquals(expectedString, mAvrcpCoverArtStorage.toString()); + assertThat(mAvrcpCoverArtStorage.toString()).isEqualTo(expectedString); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpItemTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpItemTest.java index 7a296cd075..9e77c640d2 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpItemTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpItemTest.java @@ -29,7 +29,6 @@ import android.support.v4.media.MediaMetadataCompat; import androidx.test.runner.AndroidJUnit4; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -92,19 +91,19 @@ public final class AvrcpItemTest { AvrcpItem item = builder.build(); - Assert.assertEquals(mDevice, item.getDevice()); - Assert.assertEquals(true, item.isPlayable()); - Assert.assertEquals(false, item.isBrowsable()); - Assert.assertEquals(0, item.getUid()); - Assert.assertEquals(UUID, item.getUuid()); - Assert.assertEquals(null, item.getDisplayableName()); - Assert.assertEquals(title, item.getTitle()); - Assert.assertEquals(artist, item.getArtistName()); - Assert.assertEquals(album, item.getAlbumName()); - Assert.assertEquals(trackNumber, item.getTrackNumber()); - Assert.assertEquals(totalTracks, item.getTotalNumberOfTracks()); - Assert.assertEquals(artHandle, item.getCoverArtHandle()); - Assert.assertEquals(uri, item.getCoverArtLocation()); + assertThat(item.getDevice()).isEqualTo(mDevice); + assertThat(item.isPlayable()).isTrue(); + assertThat(item.isBrowsable()).isFalse(); + assertThat(item.getUid()).isEqualTo(0); + assertThat(item.getUuid()).isEqualTo(UUID); + assertThat(item.getDisplayableName()).isNull(); + assertThat(item.getTitle()).isEqualTo(title); + assertThat(item.getArtistName()).isEqualTo(artist); + assertThat(item.getAlbumName()).isEqualTo(album); + assertThat(item.getTrackNumber()).isEqualTo(trackNumber); + assertThat(item.getTotalNumberOfTracks()).isEqualTo(totalTracks); + assertThat(item.getCoverArtHandle()).isEqualTo(artHandle); + assertThat(item.getCoverArtLocation()).isEqualTo(uri); } @Test @@ -139,19 +138,19 @@ public final class AvrcpItemTest { builder.fromAvrcpAttributeArray(attrIds, attrMap); AvrcpItem item = builder.build(); - Assert.assertEquals(null, item.getDevice()); - Assert.assertEquals(false, item.isPlayable()); - Assert.assertEquals(false, item.isBrowsable()); - Assert.assertEquals(0, item.getUid()); - Assert.assertEquals(null, item.getUuid()); - Assert.assertEquals(null, item.getDisplayableName()); - Assert.assertEquals(title, item.getTitle()); - Assert.assertEquals(artist, item.getArtistName()); - Assert.assertEquals(album, item.getAlbumName()); - Assert.assertEquals(1, item.getTrackNumber()); - Assert.assertEquals(12, item.getTotalNumberOfTracks()); - Assert.assertEquals(artHandle, item.getCoverArtHandle()); - Assert.assertEquals(null, item.getCoverArtLocation()); + assertThat(item.getDevice()).isNull(); + assertThat(item.isPlayable()).isFalse(); + assertThat(item.isBrowsable()).isFalse(); + assertThat(item.getUid()).isEqualTo(0); + assertThat(item.getUuid()).isNull(); + assertThat(item.getDisplayableName()).isNull(); + assertThat(item.getTitle()).isEqualTo(title); + assertThat(item.getArtistName()).isEqualTo(artist); + assertThat(item.getAlbumName()).isEqualTo(album); + assertThat(item.getTrackNumber()).isEqualTo(1); + assertThat(item.getTotalNumberOfTracks()).isEqualTo(12); + assertThat(item.getCoverArtHandle()).isEqualTo(artHandle); + assertThat(item.getCoverArtLocation()).isNull(); } @Test @@ -201,19 +200,19 @@ public final class AvrcpItemTest { builder.fromAvrcpAttributeArray(attrIds, attrMap); AvrcpItem item = builder.build(); - Assert.assertEquals(null, item.getDevice()); - Assert.assertEquals(false, item.isPlayable()); - Assert.assertEquals(false, item.isBrowsable()); - Assert.assertEquals(0, item.getUid()); - Assert.assertEquals(null, item.getUuid()); - Assert.assertEquals(null, item.getDisplayableName()); - Assert.assertEquals(title, item.getTitle()); - Assert.assertEquals(artist, item.getArtistName()); - Assert.assertEquals(album, item.getAlbumName()); - Assert.assertEquals(1, item.getTrackNumber()); - Assert.assertEquals(12, item.getTotalNumberOfTracks()); - Assert.assertEquals(artHandle, item.getCoverArtHandle()); - Assert.assertEquals(null, item.getCoverArtLocation()); + assertThat(item.getDevice()).isNull(); + assertThat(item.isPlayable()).isFalse(); + assertThat(item.isBrowsable()).isFalse(); + assertThat(item.getUid()).isEqualTo(0); + assertThat(item.getUuid()).isNull(); + assertThat(item.getDisplayableName()).isNull(); + assertThat(item.getTitle()).isEqualTo(title); + assertThat(item.getArtistName()).isEqualTo(artist); + assertThat(item.getAlbumName()).isEqualTo(album); + assertThat(item.getTrackNumber()).isEqualTo(1); + assertThat(item.getTotalNumberOfTracks()).isEqualTo(12); + assertThat(item.getCoverArtHandle()).isEqualTo(artHandle); + assertThat(item.getCoverArtLocation()).isNull(); } @Test @@ -248,19 +247,19 @@ public final class AvrcpItemTest { builder.fromAvrcpAttributeArray(attrIds, attrMap); AvrcpItem item = builder.build(); - Assert.assertEquals(null, item.getDevice()); - Assert.assertEquals(false, item.isPlayable()); - Assert.assertEquals(false, item.isBrowsable()); - Assert.assertEquals(0, item.getUid()); - Assert.assertEquals(null, item.getUuid()); - Assert.assertEquals(null, item.getDisplayableName()); - Assert.assertEquals(title, item.getTitle()); - Assert.assertEquals(artist, item.getArtistName()); - Assert.assertEquals(album, item.getAlbumName()); - Assert.assertEquals(1, item.getTrackNumber()); - Assert.assertEquals(12, item.getTotalNumberOfTracks()); - Assert.assertEquals(null, item.getCoverArtHandle()); - Assert.assertEquals(null, item.getCoverArtLocation()); + assertThat(item.getDevice()).isNull(); + assertThat(item.isPlayable()).isFalse(); + assertThat(item.isBrowsable()).isFalse(); + assertThat(item.getUid()).isEqualTo(0); + assertThat(item.getUuid()).isNull(); + assertThat(item.getDisplayableName()).isNull(); + assertThat(item.getTitle()).isEqualTo(title); + assertThat(item.getArtistName()).isEqualTo(artist); + assertThat(item.getAlbumName()).isEqualTo(album); + assertThat(item.getTrackNumber()).isEqualTo(1); + assertThat(item.getTotalNumberOfTracks()).isEqualTo(12); + assertThat(item.getCoverArtHandle()).isNull(); + assertThat(item.getCoverArtLocation()).isNull(); } @Test @@ -295,19 +294,19 @@ public final class AvrcpItemTest { builder.fromAvrcpAttributeArray(attrIds, attrMap); AvrcpItem item = builder.build(); - Assert.assertEquals(null, item.getDevice()); - Assert.assertEquals(false, item.isPlayable()); - Assert.assertEquals(false, item.isBrowsable()); - Assert.assertEquals(0, item.getUid()); - Assert.assertEquals(null, item.getUuid()); - Assert.assertEquals(null, item.getDisplayableName()); - Assert.assertEquals(title, item.getTitle()); - Assert.assertEquals(artist, item.getArtistName()); - Assert.assertEquals(album, item.getAlbumName()); - Assert.assertEquals(1, item.getTrackNumber()); - Assert.assertEquals(12, item.getTotalNumberOfTracks()); - Assert.assertEquals(null, item.getCoverArtHandle()); - Assert.assertEquals(null, item.getCoverArtLocation()); + assertThat(item.getDevice()).isNull(); + assertThat(item.isPlayable()).isFalse(); + assertThat(item.isBrowsable()).isFalse(); + assertThat(item.getUid()).isEqualTo(0); + assertThat(item.getUuid()).isNull(); + assertThat(item.getDisplayableName()).isNull(); + assertThat(item.getTitle()).isEqualTo(title); + assertThat(item.getArtistName()).isEqualTo(artist); + assertThat(item.getAlbumName()).isEqualTo(album); + assertThat(item.getTrackNumber()).isEqualTo(1); + assertThat(item.getTotalNumberOfTracks()).isEqualTo(12); + assertThat(item.getCoverArtHandle()).isNull(); + assertThat(item.getCoverArtLocation()).isNull(); } @Test @@ -342,19 +341,19 @@ public final class AvrcpItemTest { builder.fromAvrcpAttributeArray(attrIds, attrMap); AvrcpItem item = builder.build(); - Assert.assertEquals(null, item.getDevice()); - Assert.assertEquals(false, item.isPlayable()); - Assert.assertEquals(false, item.isBrowsable()); - Assert.assertEquals(0, item.getUid()); - Assert.assertEquals(null, item.getUuid()); - Assert.assertEquals(null, item.getDisplayableName()); - Assert.assertEquals(title, item.getTitle()); - Assert.assertEquals(artist, item.getArtistName()); - Assert.assertEquals(album, item.getAlbumName()); - Assert.assertEquals(1, item.getTrackNumber()); - Assert.assertEquals(12, item.getTotalNumberOfTracks()); - Assert.assertEquals(null, item.getCoverArtHandle()); - Assert.assertEquals(null, item.getCoverArtLocation()); + assertThat(item.getDevice()).isNull(); + assertThat(item.isPlayable()).isFalse(); + assertThat(item.isBrowsable()).isFalse(); + assertThat(item.getUid()).isEqualTo(0); + assertThat(item.getUuid()).isNull(); + assertThat(item.getDisplayableName()).isNull(); + assertThat(item.getTitle()).isEqualTo(title); + assertThat(item.getArtistName()).isEqualTo(artist); + assertThat(item.getAlbumName()).isEqualTo(album); + assertThat(item.getTrackNumber()).isEqualTo(1); + assertThat(item.getTotalNumberOfTracks()).isEqualTo(12); + assertThat(item.getCoverArtHandle()).isNull(); + assertThat(item.getCoverArtLocation()).isNull(); } @Test @@ -389,19 +388,19 @@ public final class AvrcpItemTest { builder.fromAvrcpAttributeArray(attrIds, attrMap); AvrcpItem item = builder.build(); - Assert.assertEquals(null, item.getDevice()); - Assert.assertEquals(false, item.isPlayable()); - Assert.assertEquals(false, item.isBrowsable()); - Assert.assertEquals(0, item.getUid()); - Assert.assertEquals(null, item.getUuid()); - Assert.assertEquals(null, item.getDisplayableName()); - Assert.assertEquals(title, item.getTitle()); - Assert.assertEquals(artist, item.getArtistName()); - Assert.assertEquals(album, item.getAlbumName()); - Assert.assertEquals(1, item.getTrackNumber()); - Assert.assertEquals(12, item.getTotalNumberOfTracks()); - Assert.assertEquals(null, item.getCoverArtHandle()); - Assert.assertEquals(null, item.getCoverArtLocation()); + assertThat(item.getDevice()).isNull(); + assertThat(item.isPlayable()).isFalse(); + assertThat(item.isBrowsable()).isFalse(); + assertThat(item.getUid()).isEqualTo(0); + assertThat(item.getUuid()).isNull(); + assertThat(item.getDisplayableName()).isNull(); + assertThat(item.getTitle()).isEqualTo(title); + assertThat(item.getArtistName()).isEqualTo(artist); + assertThat(item.getAlbumName()).isEqualTo(album); + assertThat(item.getTrackNumber()).isEqualTo(1); + assertThat(item.getTotalNumberOfTracks()).isEqualTo(12); + assertThat(item.getCoverArtHandle()).isNull(); + assertThat(item.getCoverArtLocation()).isNull(); } @Test @@ -413,10 +412,10 @@ public final class AvrcpItemTest { builder.setCoverArtLocation(uri); AvrcpItem item = builder.build(); - Assert.assertEquals(uri, item.getCoverArtLocation()); + assertThat(item.getCoverArtLocation()).isEqualTo(uri); item.setCoverArtLocation(uri2); - Assert.assertEquals(uri2, item.getCoverArtLocation()); + assertThat(item.getCoverArtLocation()).isEqualTo(uri2); } @Test @@ -452,30 +451,28 @@ public final class AvrcpItemTest { AvrcpItem item = builder.build(); MediaMetadataCompat metadata = item.toMediaMetadata(); - Assert.assertEquals(UUID, metadata.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID)); - Assert.assertEquals( - title, metadata.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE)); - Assert.assertEquals(title, metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)); - Assert.assertEquals(artist, metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)); - Assert.assertEquals(album, metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM)); - Assert.assertEquals( - trackNumber, metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER)); - Assert.assertEquals( - totalTracks, metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS)); - Assert.assertEquals(genre, metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE)); - Assert.assertEquals( - playingTime, metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)); - Assert.assertEquals( - uri, - Uri.parse(metadata.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI))); - Assert.assertEquals( - uri, Uri.parse(metadata.getString(MediaMetadataCompat.METADATA_KEY_ART_URI))); - Assert.assertEquals( - uri, Uri.parse(metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI))); - Assert.assertEquals( - null, metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON)); - Assert.assertEquals(null, metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ART)); - Assert.assertEquals(null, metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART)); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID)).isEqualTo(UUID); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE)) + .isEqualTo(title); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)).isEqualTo(title); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)).isEqualTo(artist); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM)).isEqualTo(album); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER)) + .isEqualTo(trackNumber); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS)) + .isEqualTo(totalTracks); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE)).isEqualTo(genre); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)) + .isEqualTo(playingTime); + assertThat(Uri.parse(metadata.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI))) + .isEqualTo(uri); + assertThat(Uri.parse(metadata.getString(MediaMetadataCompat.METADATA_KEY_ART_URI))) + .isEqualTo(uri); + assertThat(Uri.parse(metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI))) + .isEqualTo(uri); + assertThat(metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON)).isNull(); + assertThat(metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ART)).isNull(); + assertThat(metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART)).isNull(); assertThat(metadata.containsKey(MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE)).isFalse(); } @@ -507,30 +504,28 @@ public final class AvrcpItemTest { AvrcpItem item = builder.build(); MediaMetadataCompat metadata = item.toMediaMetadata(); - Assert.assertEquals(UUID, metadata.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID)); - Assert.assertEquals( - title, metadata.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE)); - Assert.assertEquals(title, metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)); - Assert.assertEquals(artist, metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)); - Assert.assertEquals(null, metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM)); - Assert.assertEquals( - totalTracks, metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS)); - Assert.assertEquals(genre, metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE)); - Assert.assertEquals( - playingTime, metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)); - Assert.assertEquals( - uri, - Uri.parse(metadata.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI))); - Assert.assertEquals( - uri, Uri.parse(metadata.getString(MediaMetadataCompat.METADATA_KEY_ART_URI))); - Assert.assertEquals( - uri, Uri.parse(metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI))); - Assert.assertEquals( - null, metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON)); - Assert.assertEquals(null, metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ART)); - Assert.assertEquals(null, metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART)); - Assert.assertEquals( - type, metadata.getLong(MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE)); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID)).isEqualTo(UUID); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE)) + .isEqualTo(title); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)).isEqualTo(title); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)).isEqualTo(artist); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM)).isNull(); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS)) + .isEqualTo(totalTracks); + assertThat(metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE)).isEqualTo(genre); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)) + .isEqualTo(playingTime); + assertThat(Uri.parse(metadata.getString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI))) + .isEqualTo(uri); + assertThat(Uri.parse(metadata.getString(MediaMetadataCompat.METADATA_KEY_ART_URI))) + .isEqualTo(uri); + assertThat(Uri.parse(metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI))) + .isEqualTo(uri); + assertThat(metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON)).isNull(); + assertThat(metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ART)).isNull(); + assertThat(metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART)).isNull(); + assertThat(metadata.getLong(MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE)) + .isEqualTo(type); } @Test @@ -550,14 +545,14 @@ public final class AvrcpItemTest { assertThat(mediaItem.isPlayable()).isTrue(); assertThat(mediaItem.isBrowsable()).isFalse(); - Assert.assertEquals(UUID, mediaItem.getMediaId()); + assertThat(mediaItem.getMediaId()).isEqualTo(UUID); - Assert.assertEquals(UUID, desc.getMediaId()); - Assert.assertEquals(null, desc.getMediaUri()); - Assert.assertEquals(title, desc.getTitle().toString()); + assertThat(desc.getMediaId()).isEqualTo(UUID); + assertThat(desc.getMediaUri()).isNull(); + assertThat(desc.getTitle().toString()).isEqualTo(title); assertThat(desc.getSubtitle()).isNull(); - Assert.assertEquals(uri, desc.getIconUri()); - Assert.assertEquals(null, desc.getIconBitmap()); + assertThat(desc.getIconUri()).isEqualTo(uri); + assertThat(desc.getIconBitmap()).isNull(); } @Test @@ -579,14 +574,14 @@ public final class AvrcpItemTest { assertThat(mediaItem.isPlayable()).isTrue(); assertThat(mediaItem.isBrowsable()).isFalse(); - Assert.assertEquals(UUID, mediaItem.getMediaId()); + assertThat(mediaItem.getMediaId()).isEqualTo(UUID); - Assert.assertEquals(UUID, desc.getMediaId()); - Assert.assertEquals(null, desc.getMediaUri()); - Assert.assertEquals(displayName, desc.getTitle().toString()); + assertThat(desc.getMediaId()).isEqualTo(UUID); + assertThat(desc.getMediaUri()).isNull(); + assertThat(desc.getTitle().toString()).isEqualTo(displayName); assertThat(desc.getSubtitle()).isNull(); - Assert.assertEquals(uri, desc.getIconUri()); - Assert.assertEquals(null, desc.getIconBitmap()); + assertThat(desc.getIconUri()).isEqualTo(uri); + assertThat(desc.getIconBitmap()).isNull(); } @Test @@ -606,14 +601,14 @@ public final class AvrcpItemTest { assertThat(mediaItem.isPlayable()).isFalse(); assertThat(mediaItem.isBrowsable()).isTrue(); - Assert.assertEquals(UUID, mediaItem.getMediaId()); + assertThat(mediaItem.getMediaId()).isEqualTo(UUID); - Assert.assertEquals(UUID, desc.getMediaId()); - Assert.assertEquals(null, desc.getMediaUri()); - Assert.assertEquals(title, desc.getTitle().toString()); + assertThat(desc.getMediaId()).isEqualTo(UUID); + assertThat(desc.getMediaUri()).isNull(); + assertThat(desc.getTitle().toString()).isEqualTo(title); assertThat(desc.getSubtitle()).isNull(); - Assert.assertEquals(uri, desc.getIconUri()); - Assert.assertEquals(null, desc.getIconBitmap()); + assertThat(desc.getIconUri()).isEqualTo(uri); + assertThat(desc.getIconBitmap()).isNull(); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/BrowseNodeTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/BrowseNodeTest.java index 6241e65d0a..4649eb4cf3 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/BrowseNodeTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/BrowseNodeTest.java @@ -155,7 +155,7 @@ public class BrowseNodeTest { mRootNode.addChild(browseNode); - assertThat(mRootNode.getContents().size()).isEqualTo(1); + assertThat(mRootNode.getContents()).hasSize(1); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/BrowseTreeTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/BrowseTreeTest.java index 275b39452d..16c43583e6 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/BrowseTreeTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/BrowseTreeTest.java @@ -47,7 +47,7 @@ public class BrowseTreeTest { public void constructor_withoutDevice() { BrowseTree browseTree = new BrowseTree(null); - assertThat(browseTree.mRootNode.mItem.getDevice()).isEqualTo(null); + assertThat(browseTree.mRootNode.mItem.getDevice()).isNull(); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipAttachmentFormatTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipAttachmentFormatTest.java index c4ae402379..3500d0e0c7 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipAttachmentFormatTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipAttachmentFormatTest.java @@ -22,7 +22,6 @@ import android.annotation.SuppressLint; import androidx.test.runner.AndroidJUnit4; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -63,23 +62,23 @@ public class BipAttachmentFormatTest { int expectedSize = (size != null ? Integer.parseInt(size) : -1); BipAttachmentFormat attachment = new BipAttachmentFormat(contentType, charset, name, size, created, modified); - Assert.assertEquals(contentType, attachment.getContentType()); - Assert.assertEquals(charset, attachment.getCharset()); - Assert.assertEquals(name, attachment.getName()); - Assert.assertEquals(expectedSize, attachment.getSize()); + assertThat(attachment.getContentType()).isEqualTo(contentType); + assertThat(attachment.getCharset()).isEqualTo(charset); + assertThat(attachment.getName()).isEqualTo(name); + assertThat(attachment.getSize()).isEqualTo(expectedSize); if (expectedCreated != null) { - Assert.assertEquals(expectedCreated, attachment.getCreatedDate().getTime()); - Assert.assertEquals(isCreatedUtc, attachment.getCreatedDate().isUtc()); + assertThat(attachment.getCreatedDate().getTime()).isEqualTo(expectedCreated); + assertThat(attachment.getCreatedDate().isUtc()).isEqualTo(isCreatedUtc); } else { - Assert.assertEquals(null, attachment.getCreatedDate()); + assertThat(attachment.getCreatedDate()).isNull(); } if (expectedModified != null) { - Assert.assertEquals(expectedModified, attachment.getModifiedDate().getTime()); - Assert.assertEquals(isModifiedUtc, attachment.getModifiedDate().isUtc()); + assertThat(attachment.getModifiedDate().getTime()).isEqualTo(expectedModified); + assertThat(attachment.getModifiedDate().isUtc()).isEqualTo(isModifiedUtc); } else { - Assert.assertEquals(null, attachment.getModifiedDate()); + assertThat(attachment.getModifiedDate()).isNull(); } } @@ -93,23 +92,23 @@ public class BipAttachmentFormatTest { Date modified) { BipAttachmentFormat attachment = new BipAttachmentFormat(contentType, charset, name, size, created, modified); - Assert.assertEquals(contentType, attachment.getContentType()); - Assert.assertEquals(charset, attachment.getCharset()); - Assert.assertEquals(name, attachment.getName()); - Assert.assertEquals(size, attachment.getSize()); + assertThat(attachment.getContentType()).isEqualTo(contentType); + assertThat(attachment.getCharset()).isEqualTo(charset); + assertThat(attachment.getName()).isEqualTo(name); + assertThat(attachment.getSize()).isEqualTo(size); if (created != null) { - Assert.assertEquals(created, attachment.getCreatedDate().getTime()); + assertThat(attachment.getCreatedDate().getTime()).isEqualTo(created); assertThat(attachment.getCreatedDate().isUtc()).isTrue(); } else { - Assert.assertEquals(null, attachment.getCreatedDate()); + assertThat(attachment.getCreatedDate()).isNull(); } if (modified != null) { - Assert.assertEquals(modified, attachment.getModifiedDate().getTime()); + assertThat(attachment.getModifiedDate().getTime()).isEqualTo(modified); assertThat(attachment.getModifiedDate().isUtc()).isTrue(); } else { - Assert.assertEquals(null, attachment.getModifiedDate()); + assertThat(attachment.getModifiedDate()).isNull(); } } @@ -326,7 +325,7 @@ public class BipAttachmentFormatTest { "2048", "19900101T123456", "19900101T123456"); - Assert.assertEquals(expected, attachment.toString()); + assertThat(attachment.toString()).isEqualTo(expected); // Create by parsing, all fields with utc dates attachment = @@ -337,31 +336,31 @@ public class BipAttachmentFormatTest { "2048", "19900101T123456Z", "19900101T123456Z"); - Assert.assertEquals(expectedUtc, attachment.toString()); + assertThat(attachment.toString()).isEqualTo(expectedUtc); // Create by parsing, no timestamps attachment = new BipAttachmentFormat( "text/plain", "ISO-8859-1", "thisisatextfile.txt", "2048", null, null); - Assert.assertEquals(expectedNoDates, attachment.toString()); + assertThat(attachment.toString()).isEqualTo(expectedNoDates); // Create by parsing, no size, no dates attachment = new BipAttachmentFormat( "text/plain", "ISO-8859-1", "thisisatextfile.txt", null, null, null); - Assert.assertEquals(expectedNoSizeNoDates, attachment.toString()); + assertThat(attachment.toString()).isEqualTo(expectedNoSizeNoDates); // Create by parsing, no charset, no dates attachment = new BipAttachmentFormat( "text/plain", null, "thisisatextfile.txt", "2048", null, null); - Assert.assertEquals(expectedNoCharsetNoDates, attachment.toString()); + assertThat(attachment.toString()).isEqualTo(expectedNoCharsetNoDates); // Create by parsing, content type only attachment = new BipAttachmentFormat( "text/plain", null, "thisisatextfile.txt", null, null, null); - Assert.assertEquals(expectedRequiredOnly, attachment.toString()); + assertThat(attachment.toString()).isEqualTo(expectedRequiredOnly); } @Test @@ -395,30 +394,30 @@ public class BipAttachmentFormatTest { BipAttachmentFormat attachment = new BipAttachmentFormat( "text/plain", "ISO-8859-1", "thisisatextfile.txt", 2048, date, date); - Assert.assertEquals(expected, attachment.toString()); + assertThat(attachment.toString()).isEqualTo(expected); // Create with objects, no dates attachment = new BipAttachmentFormat( "text/plain", "ISO-8859-1", "thisisatextfile.txt", 2048, null, null); - Assert.assertEquals(expectedNoDates, attachment.toString()); + assertThat(attachment.toString()).isEqualTo(expectedNoDates); // Create with objects, no size and no dates attachment = new BipAttachmentFormat( "text/plain", "ISO-8859-1", "thisisatextfile.txt", -1, null, null); - Assert.assertEquals(expectedNoSizeNoDates, attachment.toString()); + assertThat(attachment.toString()).isEqualTo(expectedNoSizeNoDates); // Create with objects, no charset, no dates attachment = new BipAttachmentFormat( "text/plain", null, "thisisatextfile.txt", 2048, null, null); - Assert.assertEquals(expectedNoCharsetNoDates, attachment.toString()); + assertThat(attachment.toString()).isEqualTo(expectedNoCharsetNoDates); // Create with objects, content type only attachment = new BipAttachmentFormat("text/plain", null, "thisisatextfile.txt", -1, null, null); - Assert.assertEquals(expectedRequiredOnly, attachment.toString()); + assertThat(attachment.toString()).isEqualTo(expectedRequiredOnly); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipDatetimeTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipDatetimeTest.java index 33937d8741..9b3032c657 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipDatetimeTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipDatetimeTest.java @@ -22,7 +22,6 @@ import android.annotation.SuppressLint; import androidx.test.runner.AndroidJUnit4; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -67,17 +66,17 @@ public class BipDatetimeTest { @SuppressLint("UndefinedEquals") private void testParse(String date, Date expectedDate, boolean isUtc, String expectedStr) { BipDateTime bipDateTime = new BipDateTime(date); - Assert.assertEquals(expectedDate, bipDateTime.getTime()); - Assert.assertEquals(isUtc, bipDateTime.isUtc()); - Assert.assertEquals(expectedStr, bipDateTime.toString()); + assertThat(bipDateTime.getTime()).isEqualTo(expectedDate); + assertThat(bipDateTime.isUtc()).isEqualTo(isUtc); + assertThat(bipDateTime.toString()).isEqualTo(expectedStr); } @SuppressLint("UndefinedEquals") private void testCreate(Date date, String dateStr) { BipDateTime bipDate = new BipDateTime(date); - Assert.assertEquals(date, bipDate.getTime()); + assertThat(bipDate.getTime()).isEqualTo(date); assertThat(bipDate.isUtc()).isTrue(); - Assert.assertEquals(dateStr, bipDate.toString()); + assertThat(bipDate.toString()).isEqualTo(dateStr); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipEncodingTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipEncodingTest.java index 2ffb5a9870..99f4a5281a 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipEncodingTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipEncodingTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.runner.AndroidJUnit4; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,10 +34,10 @@ public class BipEncodingTest { String propId, boolean isAndroidSupported) { BipEncoding encoding = new BipEncoding(input); - Assert.assertEquals(encodingType, encoding.getType()); - Assert.assertEquals(encodingStr, encoding.toString()); - Assert.assertEquals(propId, encoding.getProprietaryEncodingId()); - Assert.assertEquals(isAndroidSupported, encoding.isAndroidSupported()); + assertThat(encoding.getType()).isEqualTo(encodingType); + assertThat(encoding.toString()).isEqualTo(encodingStr); + assertThat(encoding.getProprietaryEncodingId()).isEqualTo(propId); + assertThat(encoding.isAndroidSupported()).isEqualTo(isAndroidSupported); } private void testParseMany( @@ -111,26 +110,26 @@ public class BipEncodingTest { }; for (int encodingType : inputs) { BipEncoding encoding = new BipEncoding(encodingType, null); - Assert.assertEquals(encodingType, encoding.getType()); - Assert.assertEquals(null, encoding.getProprietaryEncodingId()); + assertThat(encoding.getType()).isEqualTo(encodingType); + assertThat(encoding.getProprietaryEncodingId()).isNull(); } } @Test public void testCreateProprietaryEncoding() { BipEncoding encoding = new BipEncoding(BipEncoding.USR_XXX, "test-encoding"); - Assert.assertEquals(BipEncoding.USR_XXX, encoding.getType()); - Assert.assertEquals("TEST-ENCODING", encoding.getProprietaryEncodingId()); - Assert.assertEquals("USR-TEST-ENCODING", encoding.toString()); + assertThat(encoding.getType()).isEqualTo(BipEncoding.USR_XXX); + assertThat(encoding.getProprietaryEncodingId()).isEqualTo("TEST-ENCODING"); + assertThat(encoding.toString()).isEqualTo("USR-TEST-ENCODING"); assertThat(encoding.isAndroidSupported()).isFalse(); } @Test public void testCreateProprietaryEncoding_emptyId() { BipEncoding encoding = new BipEncoding(BipEncoding.USR_XXX, ""); - Assert.assertEquals(BipEncoding.USR_XXX, encoding.getType()); - Assert.assertEquals("", encoding.getProprietaryEncodingId()); - Assert.assertEquals("USR-", encoding.toString()); + assertThat(encoding.getType()).isEqualTo(BipEncoding.USR_XXX); + assertThat(encoding.getProprietaryEncodingId()).isEqualTo(""); + assertThat(encoding.toString()).isEqualTo("USR-"); assertThat(encoding.isAndroidSupported()).isFalse(); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImageDescriptorTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImageDescriptorTest.java index 971ec50064..58f89753cd 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImageDescriptorTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImageDescriptorTest.java @@ -22,7 +22,6 @@ import android.annotation.SuppressLint; import androidx.test.runner.AndroidJUnit4; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,7 +46,7 @@ public class BipImageDescriptorTest { builder.setFileSize(500000); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(expected, descriptor.toString()); + assertThat(descriptor.toString()).isEqualTo(expected); } @Test @@ -65,7 +64,7 @@ public class BipImageDescriptorTest { builder.setFileSize(500000); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(expected, descriptor.toString()); + assertThat(descriptor.toString()).isEqualTo(expected); } @Test @@ -78,12 +77,12 @@ public class BipImageDescriptorTest { + "</image-descriptor>"; BipImageDescriptor.Builder builder = new BipImageDescriptor.Builder(); - builder.setPropietaryEncoding("NOKIA-1"); + builder.setProprietaryEncoding("NOKIA-1"); builder.setFixedDimensions(1280, 960); builder.setFileSize(500000); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(expected, descriptor.toString()); + assertThat(descriptor.toString()).isEqualTo(expected); } @Test @@ -96,12 +95,12 @@ public class BipImageDescriptorTest { + "</image-descriptor>"; BipImageDescriptor.Builder builder = new BipImageDescriptor.Builder(); - builder.setPropietaryEncoding("NOKIA-1"); + builder.setProprietaryEncoding("NOKIA-1"); builder.setFixedDimensions(1280, 960); builder.setTransformation(BipTransformation.STRETCH); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(expected, descriptor.toString()); + assertThat(descriptor.toString()).isEqualTo(expected); } @Test @@ -114,12 +113,12 @@ public class BipImageDescriptorTest { + "</image-descriptor>"; BipImageDescriptor.Builder builder = new BipImageDescriptor.Builder(); - builder.setPropietaryEncoding("NOKIA-1"); + builder.setProprietaryEncoding("NOKIA-1"); builder.setFixedDimensions(1280, 960); builder.setTransformation(BipTransformation.CROP); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(expected, descriptor.toString()); + assertThat(descriptor.toString()).isEqualTo(expected); } @Test @@ -132,12 +131,12 @@ public class BipImageDescriptorTest { + "</image-descriptor>"; BipImageDescriptor.Builder builder = new BipImageDescriptor.Builder(); - builder.setPropietaryEncoding("NOKIA-1"); + builder.setProprietaryEncoding("NOKIA-1"); builder.setFixedDimensions(1280, 960); builder.setTransformation(BipTransformation.FILL); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(expected, descriptor.toString()); + assertThat(descriptor.toString()).isEqualTo(expected); } @Test @@ -150,13 +149,13 @@ public class BipImageDescriptorTest { + "</image-descriptor>"; BipImageDescriptor.Builder builder = new BipImageDescriptor.Builder(); - builder.setPropietaryEncoding("NOKIA-1"); + builder.setProprietaryEncoding("NOKIA-1"); builder.setFixedDimensions(1280, 960); builder.setTransformation(BipTransformation.CROP); builder.setTransformation(BipTransformation.FILL); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(expected, descriptor.toString()); + assertThat(descriptor.toString()).isEqualTo(expected); } @Test @@ -172,7 +171,7 @@ public class BipImageDescriptorTest { builder.setFixedDimensions(1280, 960); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(expected, descriptor.toString()); + assertThat(descriptor.toString()).isEqualTo(expected); } @Test @@ -189,7 +188,7 @@ public class BipImageDescriptorTest { builder.setMaxFileSize(500000); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(expected, descriptor.toString()); + assertThat(descriptor.toString()).isEqualTo(expected); } @Test @@ -208,7 +207,7 @@ public class BipImageDescriptorTest { builder.setTransformation(BipTransformation.FILL); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(expected, descriptor.toString()); + assertThat(descriptor.toString()).isEqualTo(expected); } @Test @@ -218,7 +217,7 @@ public class BipImageDescriptorTest { builder.setFileSize(500000); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(null, descriptor.toString()); + assertThat(descriptor.toString()).isNull(); } @Test @@ -228,7 +227,7 @@ public class BipImageDescriptorTest { builder.setFileSize(500000); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(null, descriptor.toString()); + assertThat(descriptor.toString()).isNull(); } @Test @@ -237,7 +236,7 @@ public class BipImageDescriptorTest { builder.setFileSize(500000); BipImageDescriptor descriptor = builder.build(); - Assert.assertEquals(null, descriptor.toString()); + assertThat(descriptor.toString()).isNull(); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImageFormatTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImageFormatTest.java index 9c8aec1b85..2f1e60cf34 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImageFormatTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImageFormatTest.java @@ -22,7 +22,6 @@ import android.annotation.SuppressLint; import androidx.test.runner.AndroidJUnit4; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,52 +32,52 @@ public class BipImageFormatTest { public void testParseNative_requiredOnly() { String expected = "<native encoding=\"JPEG\" pixel=\"1280*1024\" />"; BipImageFormat format = BipImageFormat.parseNative("JPEG", "1280*1024", null); - Assert.assertEquals(BipImageFormat.FORMAT_NATIVE, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(1280, 1024), format.getPixel()); - Assert.assertEquals(new BipTransformation(), format.getTransformation()); - Assert.assertEquals(-1, format.getSize()); - Assert.assertEquals(-1, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_NATIVE); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(1280, 1024)); + assertThat(format.getTransformation()).isEqualTo(new BipTransformation()); + assertThat(format.getSize()).isEqualTo(-1); + assertThat(format.getMaxSize()).isEqualTo(-1); + assertThat(format.toString()).isEqualTo(expected); } @Test public void testParseNative_withSize() { String expected = "<native encoding=\"JPEG\" pixel=\"1280*1024\" size=\"1048576\" />"; BipImageFormat format = BipImageFormat.parseNative("JPEG", "1280*1024", "1048576"); - Assert.assertEquals(BipImageFormat.FORMAT_NATIVE, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(1280, 1024), format.getPixel()); - Assert.assertEquals(new BipTransformation(), format.getTransformation()); - Assert.assertEquals(1048576, format.getSize()); - Assert.assertEquals(-1, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_NATIVE); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(1280, 1024)); + assertThat(format.getTransformation()).isEqualTo(new BipTransformation()); + assertThat(format.getSize()).isEqualTo(1048576); + assertThat(format.getMaxSize()).isEqualTo(-1); + assertThat(format.toString()).isEqualTo(expected); } @Test public void testParseVariant_requiredOnly() { String expected = "<variant encoding=\"JPEG\" pixel=\"1280*1024\" />"; BipImageFormat format = BipImageFormat.parseVariant("JPEG", "1280*1024", null, null); - Assert.assertEquals(BipImageFormat.FORMAT_VARIANT, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(1280, 1024), format.getPixel()); - Assert.assertEquals(new BipTransformation(), format.getTransformation()); - Assert.assertEquals(-1, format.getSize()); - Assert.assertEquals(-1, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_VARIANT); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(1280, 1024)); + assertThat(format.getTransformation()).isEqualTo(new BipTransformation()); + assertThat(format.getSize()).isEqualTo(-1); + assertThat(format.getMaxSize()).isEqualTo(-1); + assertThat(format.toString()).isEqualTo(expected); } @Test public void testParseVariant_withMaxSize() { String expected = "<variant encoding=\"JPEG\" pixel=\"1280*1024\" maxsize=\"1048576\" />"; BipImageFormat format = BipImageFormat.parseVariant("JPEG", "1280*1024", "1048576", null); - Assert.assertEquals(BipImageFormat.FORMAT_VARIANT, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(1280, 1024), format.getPixel()); - Assert.assertEquals(new BipTransformation(), format.getTransformation()); - Assert.assertEquals(-1, format.getSize()); - Assert.assertEquals(1048576, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_VARIANT); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(1280, 1024)); + assertThat(format.getTransformation()).isEqualTo(new BipTransformation()); + assertThat(format.getSize()).isEqualTo(-1); + assertThat(format.getMaxSize()).isEqualTo(1048576); + assertThat(format.toString()).isEqualTo(expected); } @Test @@ -93,16 +92,16 @@ public class BipImageFormatTest { BipImageFormat format = BipImageFormat.parseVariant("JPEG", "1280*1024", null, "stretch fill crop"); - Assert.assertEquals(trans, format.getTransformation()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getTransformation()).isEqualTo(trans); + assertThat(format.toString()).isEqualTo(expected); format = BipImageFormat.parseVariant("JPEG", "1280*1024", null, "stretch crop fill"); - Assert.assertEquals(trans, format.getTransformation()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getTransformation()).isEqualTo(trans); + assertThat(format.toString()).isEqualTo(expected); format = BipImageFormat.parseVariant("JPEG", "1280*1024", null, "crop stretch fill"); - Assert.assertEquals(trans, format.getTransformation()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getTransformation()).isEqualTo(trans); + assertThat(format.toString()).isEqualTo(expected); } @Test @@ -117,31 +116,31 @@ public class BipImageFormatTest { BipImageFormat format = BipImageFormat.parseVariant("JPEG", "1280*1024", "1048576", "stretch fill crop"); - Assert.assertEquals(BipImageFormat.FORMAT_VARIANT, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(1280, 1024), format.getPixel()); - Assert.assertEquals(trans, format.getTransformation()); - Assert.assertEquals(-1, format.getSize()); - Assert.assertEquals(1048576, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_VARIANT); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(1280, 1024)); + assertThat(format.getTransformation()).isEqualTo(trans); + assertThat(format.getSize()).isEqualTo(-1); + assertThat(format.getMaxSize()).isEqualTo(1048576); + assertThat(format.toString()).isEqualTo(expected); format = BipImageFormat.parseVariant("JPEG", "1280*1024", "1048576", "stretch crop fill"); - Assert.assertEquals(BipImageFormat.FORMAT_VARIANT, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(1280, 1024), format.getPixel()); - Assert.assertEquals(trans, format.getTransformation()); - Assert.assertEquals(-1, format.getSize()); - Assert.assertEquals(1048576, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_VARIANT); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(1280, 1024)); + assertThat(format.getTransformation()).isEqualTo(trans); + assertThat(format.getSize()).isEqualTo(-1); + assertThat(format.getMaxSize()).isEqualTo(1048576); + assertThat(format.toString()).isEqualTo(expected); format = BipImageFormat.parseVariant("JPEG", "1280*1024", "1048576", "crop stretch fill"); - Assert.assertEquals(BipImageFormat.FORMAT_VARIANT, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(1280, 1024), format.getPixel()); - Assert.assertEquals(trans, format.getTransformation()); - Assert.assertEquals(-1, format.getSize()); - Assert.assertEquals(1048576, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_VARIANT); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(1280, 1024)); + assertThat(format.getTransformation()).isEqualTo(trans); + assertThat(format.getSize()).isEqualTo(-1); + assertThat(format.getMaxSize()).isEqualTo(1048576); + assertThat(format.toString()).isEqualTo(expected); } @Test @@ -152,13 +151,13 @@ public class BipImageFormatTest { new BipEncoding(BipEncoding.JPEG, null), BipPixel.createFixed(1280, 1024), -1); - Assert.assertEquals(BipImageFormat.FORMAT_NATIVE, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(1280, 1024), format.getPixel()); - Assert.assertEquals(new BipTransformation(), format.getTransformation()); - Assert.assertEquals(-1, format.getSize()); - Assert.assertEquals(-1, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_NATIVE); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(1280, 1024)); + assertThat(format.getTransformation()).isNull(); + assertThat(format.getSize()).isEqualTo(-1); + assertThat(format.getMaxSize()).isEqualTo(-1); + assertThat(format.toString()).isEqualTo(expected); } @Test @@ -169,13 +168,13 @@ public class BipImageFormatTest { new BipEncoding(BipEncoding.JPEG, null), BipPixel.createFixed(1280, 1024), 1048576); - Assert.assertEquals(BipImageFormat.FORMAT_NATIVE, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(1280, 1024), format.getPixel()); - Assert.assertEquals(new BipTransformation(), format.getTransformation()); - Assert.assertEquals(1048576, format.getSize()); - Assert.assertEquals(-1, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_NATIVE); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(1280, 1024)); + assertThat(format.getTransformation()).isNull(); + assertThat(format.getSize()).isEqualTo(1048576); + assertThat(format.getMaxSize()).isEqualTo(-1); + assertThat(format.toString()).isEqualTo(expected); } @Test @@ -187,13 +186,13 @@ public class BipImageFormatTest { BipPixel.createFixed(32, 32), -1, null); - Assert.assertEquals(BipImageFormat.FORMAT_VARIANT, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(32, 32), format.getPixel()); - Assert.assertEquals(null, format.getTransformation()); - Assert.assertEquals(-1, format.getSize()); - Assert.assertEquals(-1, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_VARIANT); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(32, 32)); + assertThat(format.getTransformation()).isNull(); + assertThat(format.getSize()).isEqualTo(-1); + assertThat(format.getMaxSize()).isEqualTo(-1); + assertThat(format.toString()).isEqualTo(expected); } @Test @@ -214,13 +213,13 @@ public class BipImageFormatTest { BipPixel.createFixed(32, 32), -1, trans); - Assert.assertEquals(BipImageFormat.FORMAT_VARIANT, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(32, 32), format.getPixel()); - Assert.assertEquals(trans, format.getTransformation()); - Assert.assertEquals(-1, format.getSize()); - Assert.assertEquals(-1, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_VARIANT); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(32, 32)); + assertThat(format.getTransformation()).isEqualTo(trans); + assertThat(format.getSize()).isEqualTo(-1); + assertThat(format.getMaxSize()).isEqualTo(-1); + assertThat(format.toString()).isEqualTo(expected); } @Test @@ -232,13 +231,13 @@ public class BipImageFormatTest { BipPixel.createFixed(32, 32), 123, null); - Assert.assertEquals(BipImageFormat.FORMAT_VARIANT, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(32, 32), format.getPixel()); - Assert.assertEquals(null, format.getTransformation()); - Assert.assertEquals(-1, format.getSize()); - Assert.assertEquals(123, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_VARIANT); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(32, 32)); + assertThat(format.getTransformation()).isNull(); + assertThat(format.getSize()).isEqualTo(-1); + assertThat(format.getMaxSize()).isEqualTo(123); + assertThat(format.toString()).isEqualTo(expected); } @Test @@ -259,13 +258,13 @@ public class BipImageFormatTest { BipPixel.createFixed(32, 32), 123, trans); - Assert.assertEquals(BipImageFormat.FORMAT_VARIANT, format.getType()); - Assert.assertEquals(new BipEncoding(BipEncoding.JPEG, null), format.getEncoding()); - Assert.assertEquals(BipPixel.createFixed(32, 32), format.getPixel()); - Assert.assertEquals(trans, format.getTransformation()); - Assert.assertEquals(-1, format.getSize()); - Assert.assertEquals(123, format.getMaxSize()); - Assert.assertEquals(expected, format.toString()); + assertThat(format.getType()).isEqualTo(BipImageFormat.FORMAT_VARIANT); + assertThat(format.getEncoding()).isEqualTo(new BipEncoding(BipEncoding.JPEG, null)); + assertThat(format.getPixel()).isEqualTo(BipPixel.createFixed(32, 32)); + assertThat(format.getTransformation()).isEqualTo(trans); + assertThat(format.getSize()).isEqualTo(-1); + assertThat(format.getMaxSize()).isEqualTo(123); + assertThat(format.toString()).isEqualTo(expected); } @Test(expected = ParseException.class) diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImagePropertiesTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImagePropertiesTest.java index c238f914d9..6e767249e9 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImagePropertiesTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImagePropertiesTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.runner.AndroidJUnit4; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -131,11 +130,11 @@ public class BipImagePropertiesTest { + IMAGE_PROPERTIES_END; InputStream stream = toUtf8Stream(xmlString); BipImageProperties properties = new BipImageProperties(stream); - Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle()); - Assert.assertEquals(VERSION, properties.getVersion()); - Assert.assertEquals(null, properties.getFriendlyName()); + assertThat(properties.getImageHandle()).isEqualTo(IMAGE_HANDLE); + assertThat(properties.getVersion()).isEqualTo(VERSION); + assertThat(properties.getFriendlyName()).isNull(); assertThat(properties.isValid()).isTrue(); - Assert.assertEquals(xmlString, properties.toString()); + assertThat(properties.toString()).isEqualTo(xmlString); } /** @@ -166,11 +165,11 @@ public class BipImagePropertiesTest { + IMAGE_PROPERTIES_END; InputStream stream = toUtf8Stream(xmlString); BipImageProperties properties = new BipImageProperties(stream); - Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle()); - Assert.assertEquals(VERSION, properties.getVersion()); - Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName()); + assertThat(properties.getImageHandle()).isEqualTo(IMAGE_HANDLE); + assertThat(properties.getVersion()).isEqualTo(VERSION); + assertThat(properties.getFriendlyName()).isEqualTo(FRIENDLY_NAME); assertThat(properties.isValid()).isTrue(); - Assert.assertEquals(xmlString, properties.toString()); + assertThat(properties.toString()).isEqualTo(xmlString); } /** @@ -192,12 +191,12 @@ public class BipImagePropertiesTest { + IMAGE_PROPERTIES_END; InputStream stream = toUtf8Stream(xmlString); BipImageProperties properties = new BipImageProperties(stream); - Assert.assertEquals(null, properties.getImageHandle()); - Assert.assertEquals(VERSION, properties.getVersion()); - Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName()); + assertThat(properties.getImageHandle()).isNull(); + assertThat(properties.getVersion()).isEqualTo(VERSION); + assertThat(properties.getFriendlyName()).isEqualTo(FRIENDLY_NAME); assertThat(properties.isValid()).isFalse(); - Assert.assertEquals(xmlString, properties.toString()); - Assert.assertEquals(null, properties.serialize()); + assertThat(properties.toString()).isEqualTo(xmlString); + assertThat(properties.serialize()).isNull(); } /** @@ -219,12 +218,12 @@ public class BipImagePropertiesTest { + IMAGE_PROPERTIES_END; InputStream stream = toUtf8Stream(xmlString); BipImageProperties properties = new BipImageProperties(stream); - Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle()); - Assert.assertEquals(null, properties.getVersion()); - Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName()); + assertThat(properties.getImageHandle()).isEqualTo(IMAGE_HANDLE); + assertThat(properties.getVersion()).isNull(); + assertThat(properties.getFriendlyName()).isEqualTo(FRIENDLY_NAME); assertThat(properties.isValid()).isFalse(); - Assert.assertEquals(xmlString, properties.toString()); - Assert.assertEquals(null, properties.serialize()); + assertThat(properties.toString()).isEqualTo(xmlString); + assertThat(properties.serialize()).isNull(); } /** @@ -245,11 +244,11 @@ public class BipImagePropertiesTest { + IMAGE_PROPERTIES_END; InputStream stream = toUtf8Stream(xmlString); BipImageProperties properties = new BipImageProperties(stream); - Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle()); - Assert.assertEquals(VERSION, properties.getVersion()); - Assert.assertEquals(null, properties.getFriendlyName()); + assertThat(properties.getImageHandle()).isEqualTo(IMAGE_HANDLE); + assertThat(properties.getVersion()).isEqualTo(VERSION); + assertThat(properties.getFriendlyName()).isNull(); assertThat(properties.isValid()).isTrue(); - Assert.assertEquals(xmlString, properties.toString()); + assertThat(properties.toString()).isEqualTo(xmlString); } /** @@ -268,11 +267,11 @@ public class BipImagePropertiesTest { + IMAGE_PROPERTIES_END; InputStream stream = toUtf8Stream(xmlString); BipImageProperties properties = new BipImageProperties(stream); - Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle()); - Assert.assertEquals(VERSION, properties.getVersion()); - Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName()); + assertThat(properties.getImageHandle()).isEqualTo(IMAGE_HANDLE); + assertThat(properties.getVersion()).isEqualTo(VERSION); + assertThat(properties.getFriendlyName()).isEqualTo(FRIENDLY_NAME); assertThat(properties.isValid()).isTrue(); - Assert.assertEquals(xmlString, properties.toString()); + assertThat(properties.toString()).isEqualTo(xmlString); } /** @@ -291,11 +290,11 @@ public class BipImagePropertiesTest { + IMAGE_PROPERTIES_END; InputStream stream = toUtf8Stream(xmlString); BipImageProperties properties = new BipImageProperties(stream); - Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle()); - Assert.assertEquals(VERSION, properties.getVersion()); - Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName()); + assertThat(properties.getImageHandle()).isEqualTo(IMAGE_HANDLE); + assertThat(properties.getVersion()).isEqualTo(VERSION); + assertThat(properties.getFriendlyName()).isEqualTo(FRIENDLY_NAME); assertThat(properties.isValid()).isTrue(); - Assert.assertEquals(xmlString, properties.toString()); + assertThat(properties.toString()).isEqualTo(xmlString); } /** @@ -314,11 +313,11 @@ public class BipImagePropertiesTest { + IMAGE_PROPERTIES_END; InputStream stream = toUtf8Stream(xmlString); BipImageProperties properties = new BipImageProperties(stream); - Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle()); - Assert.assertEquals(VERSION, properties.getVersion()); - Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName()); + assertThat(properties.getImageHandle()).isEqualTo(IMAGE_HANDLE); + assertThat(properties.getVersion()).isEqualTo(VERSION); + assertThat(properties.getFriendlyName()).isEqualTo(FRIENDLY_NAME); assertThat(properties.isValid()).isTrue(); - Assert.assertEquals(xmlString, properties.toString()); + assertThat(properties.toString()).isEqualTo(xmlString); } /** @@ -342,12 +341,12 @@ public class BipImagePropertiesTest { + IMAGE_PROPERTIES_END; InputStream stream = toUtf8Stream(xmlString); BipImageProperties properties = new BipImageProperties(stream); - Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle()); - Assert.assertEquals(VERSION, properties.getVersion()); - Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName()); + assertThat(properties.getImageHandle()).isEqualTo(IMAGE_HANDLE); + assertThat(properties.getVersion()).isEqualTo(VERSION); + assertThat(properties.getFriendlyName()).isEqualTo(FRIENDLY_NAME); assertThat(properties.isValid()).isFalse(); - Assert.assertEquals(xmlString, properties.toString()); - Assert.assertEquals(null, properties.serialize()); + assertThat(properties.toString()).isEqualTo(xmlString); + assertThat(properties.serialize()).isNull(); } /** @@ -361,11 +360,11 @@ public class BipImagePropertiesTest { String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES + IMAGE_PROPERTIES_END; InputStream stream = toUtf8Stream(xmlString); BipImageProperties properties = new BipImageProperties(stream); - Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle()); - Assert.assertEquals(VERSION, properties.getVersion()); - Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName()); + assertThat(properties.getImageHandle()).isEqualTo(IMAGE_HANDLE); + assertThat(properties.getVersion()).isEqualTo(VERSION); + assertThat(properties.getFriendlyName()).isEqualTo(FRIENDLY_NAME); assertThat(properties.isValid()).isFalse(); - Assert.assertEquals(null, properties.serialize()); + assertThat(properties.serialize()).isNull(); } /** Test parsing an image-properties with no open tag */ @@ -447,10 +446,10 @@ public class BipImagePropertiesTest { new BipAttachmentFormat("audio/basic", null, "ABCD1234.wav", 102400, null, null)); BipImageProperties properties = builder.build(); - Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle()); - Assert.assertEquals(VERSION, properties.getVersion()); - Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName()); + assertThat(properties.getImageHandle()).isEqualTo(IMAGE_HANDLE); + assertThat(properties.getVersion()).isEqualTo(VERSION); + assertThat(properties.getFriendlyName()).isEqualTo(FRIENDLY_NAME); assertThat(properties.isValid()).isTrue(); - Assert.assertEquals(xmlString, properties.toString()); + assertThat(properties.toString()).isEqualTo(xmlString); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImageTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImageTest.java index 87ffdc5387..e3ee39dee5 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImageTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImageTest.java @@ -27,7 +27,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -56,7 +55,7 @@ public class BipImageTest { mTestResources.openRawResource(com.android.bluetooth.tests.R.raw.image_200_200); Bitmap bitmap = BitmapFactory.decodeStream(expectedInputStream); - Assert.assertEquals(sImageHandle, image.getImageHandle()); + assertThat(image.getImageHandle()).isEqualTo(sImageHandle); assertThat(bitmap.sameAs(image.getImage())).isTrue(); } @@ -70,7 +69,7 @@ public class BipImageTest { mTestResources.openRawResource(com.android.bluetooth.tests.R.raw.image_600_600); Bitmap bitmap = BitmapFactory.decodeStream(expectedInputStream); - Assert.assertEquals(sImageHandle, image.getImageHandle()); + assertThat(image.getImageHandle()).isEqualTo(sImageHandle); assertThat(bitmap.sameAs(image.getImage())).isTrue(); } @@ -80,7 +79,7 @@ public class BipImageTest { mTestResources.openRawResource(com.android.bluetooth.tests.R.raw.image_200_200); Bitmap bitmap = BitmapFactory.decodeStream(imageInputStream); BipImage image = new BipImage(sImageHandle, bitmap); - Assert.assertEquals(sImageHandle, image.getImageHandle()); + assertThat(image.getImageHandle()).isEqualTo(sImageHandle); assertThat(bitmap.sameAs(image.getImage())).isTrue(); } @@ -90,7 +89,7 @@ public class BipImageTest { mTestResources.openRawResource(com.android.bluetooth.tests.R.raw.image_600_600); Bitmap bitmap = BitmapFactory.decodeStream(imageInputStream); BipImage image = new BipImage(sImageHandle, bitmap); - Assert.assertEquals(sImageHandle, image.getImageHandle()); + assertThat(image.getImageHandle()).isEqualTo(sImageHandle); assertThat(bitmap.sameAs(image.getImage())).isTrue(); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipPixelTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipPixelTest.java index e40ef9cefc..88f093966a 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipPixelTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipPixelTest.java @@ -16,9 +16,10 @@ package com.android.bluetooth.avrcpcontroller; +import static com.google.common.truth.Truth.assertThat; + import androidx.test.runner.AndroidJUnit4; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,44 +36,44 @@ public class BipPixelTest { int maxHeight, String pixelStr) { BipPixel pixel = new BipPixel(input); - Assert.assertEquals(pixelType, pixel.getType()); - Assert.assertEquals(minWidth, pixel.getMinWidth()); - Assert.assertEquals(minHeight, pixel.getMinHeight()); - Assert.assertEquals(maxWidth, pixel.getMaxWidth()); - Assert.assertEquals(maxHeight, pixel.getMaxHeight()); - Assert.assertEquals(pixelStr, pixel.toString()); + assertThat(pixel.getType()).isEqualTo(pixelType); + assertThat(pixel.getMinWidth()).isEqualTo(minWidth); + assertThat(pixel.getMinHeight()).isEqualTo(minHeight); + assertThat(pixel.getMaxWidth()).isEqualTo(maxWidth); + assertThat(pixel.getMaxHeight()).isEqualTo(maxHeight); + assertThat(pixel.toString()).isEqualTo(pixelStr); } private void testFixed(int width, int height, String pixelStr) { BipPixel pixel = BipPixel.createFixed(width, height); - Assert.assertEquals(BipPixel.TYPE_FIXED, pixel.getType()); - Assert.assertEquals(width, pixel.getMinWidth()); - Assert.assertEquals(height, pixel.getMinHeight()); - Assert.assertEquals(width, pixel.getMaxWidth()); - Assert.assertEquals(height, pixel.getMaxHeight()); - Assert.assertEquals(pixelStr, pixel.toString()); + assertThat(pixel.getType()).isEqualTo(BipPixel.TYPE_FIXED); + assertThat(pixel.getMinWidth()).isEqualTo(width); + assertThat(pixel.getMinHeight()).isEqualTo(height); + assertThat(pixel.getMaxWidth()).isEqualTo(width); + assertThat(pixel.getMaxHeight()).isEqualTo(height); + assertThat(pixel.toString()).isEqualTo(pixelStr); } private void testResizableModified( int minWidth, int minHeight, int maxWidth, int maxHeight, String pixelStr) { BipPixel pixel = BipPixel.createResizableModified(minWidth, minHeight, maxWidth, maxHeight); - Assert.assertEquals(BipPixel.TYPE_RESIZE_MODIFIED_ASPECT_RATIO, pixel.getType()); - Assert.assertEquals(minWidth, pixel.getMinWidth()); - Assert.assertEquals(minHeight, pixel.getMinHeight()); - Assert.assertEquals(maxWidth, pixel.getMaxWidth()); - Assert.assertEquals(maxHeight, pixel.getMaxHeight()); - Assert.assertEquals(pixelStr, pixel.toString()); + assertThat(pixel.getType()).isEqualTo(BipPixel.TYPE_RESIZE_MODIFIED_ASPECT_RATIO); + assertThat(pixel.getMinWidth()).isEqualTo(minWidth); + assertThat(pixel.getMinHeight()).isEqualTo(minHeight); + assertThat(pixel.getMaxWidth()).isEqualTo(maxWidth); + assertThat(pixel.getMaxHeight()).isEqualTo(maxHeight); + assertThat(pixel.toString()).isEqualTo(pixelStr); } private void testResizableFixed(int minWidth, int maxWidth, int maxHeight, String pixelStr) { int minHeight = (minWidth * maxHeight) / maxWidth; // spec defined BipPixel pixel = BipPixel.createResizableFixed(minWidth, maxWidth, maxHeight); - Assert.assertEquals(BipPixel.TYPE_RESIZE_FIXED_ASPECT_RATIO, pixel.getType()); - Assert.assertEquals(minWidth, pixel.getMinWidth()); - Assert.assertEquals(minHeight, pixel.getMinHeight()); - Assert.assertEquals(maxWidth, pixel.getMaxWidth()); - Assert.assertEquals(maxHeight, pixel.getMaxHeight()); - Assert.assertEquals(pixelStr, pixel.toString()); + assertThat(pixel.getType()).isEqualTo(BipPixel.TYPE_RESIZE_FIXED_ASPECT_RATIO); + assertThat(pixel.getMinWidth()).isEqualTo(minWidth); + assertThat(pixel.getMinHeight()).isEqualTo(minHeight); + assertThat(pixel.getMaxWidth()).isEqualTo(maxWidth); + assertThat(pixel.getMaxHeight()).isEqualTo(maxHeight); + assertThat(pixel.toString()).isEqualTo(pixelStr); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipTransformationTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipTransformationTest.java index 3b5893c1f1..031a71c81e 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipTransformationTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipTransformationTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.runner.AndroidJUnit4; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,7 +31,7 @@ public class BipTransformationTest { public void testCreateEmpty() { BipTransformation trans = new BipTransformation(); assertThat(trans.supportsAny()).isFalse(); - Assert.assertEquals(null, trans.toString()); + assertThat(trans.toString()).isNull(); } @Test @@ -42,13 +41,13 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); - Assert.assertEquals("crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("crop"); trans.addTransformation(BipTransformation.STRETCH); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); - Assert.assertEquals("stretch crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch crop"); } @Test @@ -58,13 +57,13 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); - Assert.assertEquals("crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("crop"); trans.addTransformation(BipTransformation.CROP); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); - Assert.assertEquals("crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("crop"); } @Test(expected = IllegalArgumentException.class) @@ -87,14 +86,14 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); - Assert.assertEquals("crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("crop"); trans.removeTransformation(BipTransformation.CROP); assertThat(trans.isSupported(BipTransformation.CROP)).isFalse(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); assertThat(trans.supportsAny()).isFalse(); - Assert.assertEquals(null, trans.toString()); + assertThat(trans.toString()).isNull(); } @Test @@ -105,13 +104,13 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); - Assert.assertEquals("stretch crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch crop"); trans.removeTransformation(BipTransformation.CROP); assertThat(trans.isSupported(BipTransformation.CROP)).isFalse(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); - Assert.assertEquals("stretch", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch"); } @Test(expected = IllegalArgumentException.class) @@ -123,7 +122,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); - Assert.assertEquals("stretch crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch crop"); } @Test @@ -135,7 +134,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); - Assert.assertEquals("stretch crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch crop"); } @Test @@ -144,7 +143,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); assertThat(trans.isSupported(BipTransformation.CROP)).isFalse(); - Assert.assertEquals("stretch", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch"); } @Test @@ -153,7 +152,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); - Assert.assertEquals("crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("crop"); } @Test @@ -162,7 +161,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); assertThat(trans.isSupported(BipTransformation.CROP)).isFalse(); - Assert.assertEquals("fill", trans.toString()); + assertThat(trans.toString()).isEqualTo("fill"); } @Test @@ -171,7 +170,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.CROP)).isFalse(); - Assert.assertEquals("stretch fill", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch fill"); } @Test @@ -180,7 +179,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); - Assert.assertEquals("stretch crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch crop"); } @Test @@ -189,7 +188,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); - Assert.assertEquals("fill crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("fill crop"); } @Test @@ -198,7 +197,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); - Assert.assertEquals("stretch fill crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch fill crop"); } @Test @@ -207,7 +206,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); - Assert.assertEquals("fill crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("fill crop"); } @Test @@ -216,7 +215,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); - Assert.assertEquals("stretch fill crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch fill crop"); } @Test @@ -225,7 +224,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); - Assert.assertEquals("stretch fill crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch fill crop"); } @Test @@ -234,7 +233,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); assertThat(trans.isSupported(BipTransformation.CROP)).isFalse(); - Assert.assertEquals("stretch", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch"); } @Test @@ -243,7 +242,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); assertThat(trans.isSupported(BipTransformation.CROP)).isFalse(); - Assert.assertEquals("fill", trans.toString()); + assertThat(trans.toString()).isEqualTo("fill"); } @Test @@ -252,7 +251,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); - Assert.assertEquals("crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("crop"); } @Test @@ -261,7 +260,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); - Assert.assertEquals("crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("crop"); } @Test @@ -272,7 +271,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.CROP)).isFalse(); - Assert.assertEquals("stretch fill", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch fill"); } @Test @@ -287,7 +286,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); - Assert.assertEquals("stretch fill crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch fill crop"); } @Test @@ -302,7 +301,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); - Assert.assertEquals("stretch fill crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch fill crop"); } @Test(expected = IllegalArgumentException.class) @@ -321,7 +320,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); assertThat(trans.isSupported(BipTransformation.CROP)).isFalse(); - Assert.assertEquals(null, trans.toString()); + assertThat(trans.toString()).isNull(); } @Test @@ -330,7 +329,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); - Assert.assertEquals("stretch fill crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch fill crop"); } @Test @@ -339,7 +338,7 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isFalse(); assertThat(trans.isSupported(BipTransformation.FILL)).isTrue(); assertThat(trans.isSupported(BipTransformation.CROP)).isTrue(); - Assert.assertEquals("fill crop", trans.toString()); + assertThat(trans.toString()).isEqualTo("fill crop"); } @Test @@ -348,6 +347,6 @@ public class BipTransformationTest { assertThat(trans.isSupported(BipTransformation.STRETCH)).isTrue(); assertThat(trans.isSupported(BipTransformation.FILL)).isFalse(); assertThat(trans.isSupported(BipTransformation.CROP)).isFalse(); - Assert.assertEquals("stretch", trans.toString()); + assertThat(trans.toString()).isEqualTo("stretch"); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BaseDataTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BaseDataTest.java index 93082e863a..bf0d75a60a 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BaseDataTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BaseDataTest.java @@ -105,7 +105,7 @@ public class BaseDataTest { assertThat(level.presentationDelay).isEqualTo(new byte[] {0x01, 0x02, 0x03}); assertThat(level.numSubGroups).isEqualTo(1); - assertThat(data.getLevelTwo().size()).isEqualTo(1); + assertThat(data.getLevelTwo()).hasSize(1); level = data.getLevelTwo().get(0); assertThat(level.numSubGroups).isEqualTo(1); @@ -113,7 +113,7 @@ public class BaseDataTest { assertThat(level.codecConfigLength).isEqualTo(2); assertThat(level.metaDataLength).isEqualTo(3); - assertThat(data.getLevelThree().size()).isEqualTo(1); + assertThat(data.getLevelThree()).hasSize(1); level = data.getLevelThree().get(0); assertThat(level.index).isEqualTo(4); assertThat(level.codecConfigLength).isEqualTo(3); @@ -142,8 +142,7 @@ public class BaseDataTest { (byte) 'A', // codecConfigInfo }; - BaseData data = BaseData.parseBaseData(serviceData); - assertThat(data).isEqualTo(null); + assertThat(BaseData.parseBaseData(serviceData)).isNull(); } @Test @@ -173,8 +172,7 @@ public class BaseDataTest { (byte) 0x08, // metaData }; - BaseData data = BaseData.parseBaseData(serviceData); - assertThat(data).isEqualTo(null); + assertThat(BaseData.parseBaseData(serviceData)).isNull(); } @Test @@ -210,8 +208,7 @@ public class BaseDataTest { (byte) 'C' // codecConfigInfo }; - BaseData data = BaseData.parseBaseData(serviceData); - assertThat(data).isEqualTo(null); + assertThat(BaseData.parseBaseData(serviceData)).isNull(); } @Test @@ -252,7 +249,7 @@ public class BaseDataTest { assertThat(level.presentationDelay).isEqualTo(new byte[] {0x01, 0x02, 0x03}); assertThat(level.numSubGroups).isEqualTo(1); - assertThat(data.getLevelTwo().size()).isEqualTo(1); + assertThat(data.getLevelTwo()).hasSize(1); level = data.getLevelTwo().get(0); assertThat(level.numSubGroups).isEqualTo(1); @@ -260,7 +257,7 @@ public class BaseDataTest { assertThat(level.codecConfigLength).isEqualTo(2); assertThat(level.metaDataLength).isEqualTo(3); - assertThat(data.getLevelThree().size()).isEqualTo(1); + assertThat(data.getLevelThree()).hasSize(1); level = data.getLevelThree().get(0); assertThat(level.index).isEqualTo(4); @@ -308,7 +305,7 @@ public class BaseDataTest { assertThat(level.presentationDelay).isEqualTo(new byte[] {0x01, 0x02, 0x03}); assertThat(level.numSubGroups).isEqualTo(1); - assertThat(data.getLevelTwo().size()).isEqualTo(1); + assertThat(data.getLevelTwo()).hasSize(1); level = data.getLevelTwo().get(0); assertThat(level.numSubGroups).isEqualTo(1); @@ -320,7 +317,7 @@ public class BaseDataTest { assertThat(level.codecConfigLength).isEqualTo(4); assertThat(level.metaDataLength).isEqualTo(3); - assertThat(data.getLevelThree().size()).isEqualTo(1); + assertThat(data.getLevelThree()).hasSize(1); level = data.getLevelThree().get(0); assertThat(level.index).isEqualTo(4); assertThat(level.codecConfigLength).isEqualTo(3); @@ -356,7 +353,7 @@ public class BaseDataTest { assertThat(level.presentationDelay).isEqualTo(new byte[] {0x01, 0x02, 0x03}); assertThat(level.numSubGroups).isEqualTo(1); - assertThat(data.getLevelTwo().size()).isEqualTo(1); + assertThat(data.getLevelTwo()).hasSize(1); level = data.getLevelTwo().get(0); assertThat(level.numSubGroups).isEqualTo(1); @@ -368,7 +365,7 @@ public class BaseDataTest { assertThat(level.codecConfigLength).isEqualTo(0); assertThat(level.metaDataLength).isEqualTo(0); - assertThat(data.getLevelThree().size()).isEqualTo(1); + assertThat(data.getLevelThree()).hasSize(1); level = data.getLevelThree().get(0); assertThat(level.index).isEqualTo(4); assertThat(level.codecConfigLength).isEqualTo(0); @@ -392,8 +389,7 @@ public class BaseDataTest { (byte) 0x00, // metaDataLength }; - BaseData data = BaseData.parseBaseData(serviceData); - assertThat(data).isEqualTo(null); + assertThat(BaseData.parseBaseData(serviceData)).isNull(); } @Test @@ -455,7 +451,7 @@ public class BaseDataTest { assertThat(level.presentationDelay).isEqualTo(new byte[] {0x01, 0x02, 0x03}); assertThat(level.numSubGroups).isEqualTo(1); - assertThat(data.getLevelTwo().size()).isEqualTo(1); + assertThat(data.getLevelTwo()).hasSize(1); level = data.getLevelTwo().get(0); assertThat(level.numSubGroups).isEqualTo(1); @@ -464,7 +460,7 @@ public class BaseDataTest { assertThat(level.metaDataLength).isEqualTo(metaDataLength); assertThat(level.metaData).isEqualTo(Bytes.concat(metadataHeader, metadataPayload)); - assertThat(data.getLevelThree().size()).isEqualTo(1); + assertThat(data.getLevelThree()).hasSize(1); level = data.getLevelThree().get(0); assertThat(level.index).isEqualTo(4); assertThat(level.codecConfigLength).isEqualTo(3); diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java index 8f3dd8aa7c..c1c7734f2e 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java @@ -86,7 +86,6 @@ import com.android.bluetooth.le_audio.LeAudioService; import com.google.common.truth.Expect; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -105,6 +104,7 @@ import java.util.HashMap; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.stream.Collectors; @@ -143,7 +143,7 @@ public class BassClientServiceTest { private static final int TEST_NUM_SOURCES = 1; private final HashMap<BluetoothDevice, BassClientStateMachine> mStateMachines = new HashMap<>(); - private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mIntentQueue; + private final BlockingQueue<Intent> mIntentQueue = new LinkedBlockingQueue<>(); private Context mTargetContext; private BassClientService mBassClientService; @@ -291,6 +291,7 @@ public class BassClientServiceTest { doReturn((BluetoothDevice) invocation.getArgument(0)) .when(stateMachine) .getDevice(); + doReturn(true).when(stateMachine).isBassStateReady(); mStateMachines.put( (BluetoothDevice) invocation.getArgument(0), stateMachine); return stateMachine; @@ -312,10 +313,6 @@ public class BassClientServiceTest { when(mCallback.asBinder()).thenReturn(mBinder); mBassClientService.registerCallback(mCallback); - mIntentQueue = new HashMap<>(); - mIntentQueue.put(mCurrentDevice, new LinkedBlockingQueue<>()); - mIntentQueue.put(mCurrentDevice1, new LinkedBlockingQueue<>()); - // Set up the Connection State Changed receiver IntentFilter filter = new IntentFilter(); filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); @@ -339,6 +336,7 @@ public class BassClientServiceTest { if (mBassClientService == null) { return; } + mTargetContext.unregisterReceiver(mBassIntentReceiver); mBassClientService.unregisterCallback(mCallback); mBassClientService.stop(); @@ -349,7 +347,6 @@ public class BassClientServiceTest { mCurrentDevice1 = null; mSourceDevice = null; mSourceDevice2 = null; - mTargetContext.unregisterReceiver(mBassIntentReceiver); mIntentQueue.clear(); BassObjectsFactory.setInstanceForTesting(null); TestUtils.clearAdapterService(mAdapterService); @@ -368,11 +365,7 @@ public class BassClientServiceTest { } try { - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - assertThat(device).isNotNull(); - LinkedBlockingQueue<Intent> queue = mIntentQueue.get(device); - assertThat(queue).isNotNull(); - queue.put(intent); + mIntentQueue.put(intent); } catch (InterruptedException e) { throw new AssertionError("Cannot add Intent to the queue: " + e.getMessage()); } @@ -396,10 +389,8 @@ public class BassClientServiceTest { when(mDatabaseManager.getProfileConnectionPolicy( mCurrentDevice, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)) .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); - Assert.assertEquals( - "Initial device policy", - BluetoothProfile.CONNECTION_POLICY_UNKNOWN, - mBassClientService.getConnectionPolicy(mCurrentDevice)); + assertThat(mBassClientService.getConnectionPolicy(mCurrentDevice)) + .isEqualTo(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); } /** @@ -459,7 +450,7 @@ public class BassClientServiceTest { prepareConnectedDeviceGroup(); List<ScanFilter> scanFilters = new ArrayList<>(); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { Mockito.clearInvocations(sm); } @@ -494,10 +485,6 @@ public class BassClientServiceTest { mCurrentDevice = TestUtils.getTestDevice(mBluetoothAdapter, 0); mCurrentDevice1 = TestUtils.getTestDevice(mBluetoothAdapter, 1); - // Prepare intent queues - mIntentQueue.put(mCurrentDevice, new LinkedBlockingQueue<>()); - mIntentQueue.put(mCurrentDevice1, new LinkedBlockingQueue<>()); - // Mock the CSIP group List<BluetoothDevice> groupDevices = new ArrayList<>(); groupDevices.add(mCurrentDevice); @@ -513,7 +500,7 @@ public class BassClientServiceTest { assertThat(mBassClientService.connect(mCurrentDevice)).isTrue(); assertThat(mBassClientService.connect(mCurrentDevice1)).isTrue(); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { // Verify the call verify(sm).sendMessage(eq(BassClientStateMachine.CONNECT)); @@ -567,7 +554,7 @@ public class BassClientServiceTest { private void startSearchingForSources() { List<ScanFilter> scanFilters = new ArrayList<>(); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { Mockito.clearInvocations(sm); } @@ -598,7 +585,7 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) @@ -617,7 +604,7 @@ public class BassClientServiceTest { .verify(mMethodProxy) .periodicAdvertisingManagerUnregisterSync(any(), any()); expect.that(mBassClientService.getActiveSyncedSources()).isEmpty(); - expect.that(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null); + expect.that(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isNull(); expect.that(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); } @@ -635,7 +622,7 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) @@ -651,7 +638,7 @@ public class BassClientServiceTest { .verify(mMethodProxy) .periodicAdvertisingManagerUnregisterSync(any(), any()); expect.that(mBassClientService.getActiveSyncedSources()).isEmpty(); - expect.that(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null); + expect.that(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isNull(); expect.that(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); } @@ -755,7 +742,7 @@ public class BassClientServiceTest { any(), any(), anyInt(), anyInt(), any(), any()); // Verify not getting ADD_BCAST_SOURCE message before source sync - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { verify(sm, never()).sendMessage(any()); } @@ -987,7 +974,7 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); - // Error in syncEstablished causes soureLost, sourceAddFailed notification + // Error in syncEstablished causes sourceLost, sourceAddFailed notification // and removing cache because scanning is active onSyncEstablishedFailed(device1, handle1); TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); @@ -1042,7 +1029,7 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); - // Error in syncEstablished causes soureLost, sourceAddFailed notification + // Error in syncEstablished causes sourceLost, sourceAddFailed notification // and not removing cache because scanning is inactice onSyncEstablishedFailed(device1, handle1); TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); @@ -1159,7 +1146,7 @@ public class BassClientServiceTest { mBassClientService.addSource(mCurrentDevice1, meta, false); handleHandoverSupport(); - // Error in syncEstablished causes soureLost, sourceAddFailed notification for both sinks + // Error in syncEstablished causes sourceLost, sourceAddFailed notification for both sinks onSyncEstablishedFailed(mSourceDevice, TEST_SYNC_HANDLE); TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); InOrder inOrderCallback = inOrder(mCallback); @@ -1239,7 +1226,7 @@ public class BassClientServiceTest { .isFalse(); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) @@ -1288,7 +1275,7 @@ public class BassClientServiceTest { assertThat(mBassClientService.mHandler.hasMessages(BassClientService.MESSAGE_SYNC_TIMEOUT)) .isTrue(); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) @@ -1477,7 +1464,7 @@ public class BassClientServiceTest { private void verifyConnectionStateIntent( int timeoutMs, BluetoothDevice device, int newState, int prevState) { - Intent intent = TestUtils.waitForIntent(timeoutMs, mIntentQueue.get(device)); + Intent intent = TestUtils.waitForIntent(timeoutMs, mIntentQueue); assertThat(intent.getAction()) .isEqualTo(BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED); assertThat(device).isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); @@ -1500,7 +1487,7 @@ public class BassClientServiceTest { handleHandoverSupport(); // Verify all group members getting ADD_BCAST_SOURCE message - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); @@ -1924,7 +1911,7 @@ public class BassClientServiceTest { // Verify all group members getting UPDATE_BCAST_SOURCE message // because PA state is synced - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); @@ -1958,7 +1945,7 @@ public class BassClientServiceTest { // Verify all group members getting UPDATE_BCAST_SOURCE message if // bis sync state is non-zero and pa sync state is not synced - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); @@ -2044,7 +2031,7 @@ public class BassClientServiceTest { assertThat(msg.get().arg1).isEqualTo(TEST_SOURCE_ID); assertThat(msg.get().arg2).isEqualTo(BassConstants.PA_SYNC_DO_NOT_SYNC); // Verify metadata is null - assertThat(msg.get().obj).isEqualTo(null); + assertThat(msg.get().obj).isNull(); } for (BassClientStateMachine sm : mStateMachines.values()) { @@ -2073,7 +2060,7 @@ public class BassClientServiceTest { null); doReturn(null).when(sm).getCurrentBroadcastMetadata(eq(TEST_SOURCE_ID)); assertThat(mBassClientService.getSourceMetadata(sm.getDevice(), TEST_SOURCE_ID)) - .isEqualTo(null); + .isNull(); doReturn(meta).when(sm).getCurrentBroadcastMetadata(eq(TEST_SOURCE_ID)); doReturn(true).when(sm).isSyncedToTheSource(eq(TEST_SOURCE_ID)); @@ -2248,7 +2235,7 @@ public class BassClientServiceTest { onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); verifyAddSourceForGroup(meta); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); prepareRemoteSourceState(meta, true, true); // Add another broadcast source @@ -2259,7 +2246,7 @@ public class BassClientServiceTest { onScanResult(mSourceDevice2, TEST_BROADCAST_ID + 1); onSyncEstablished(mSourceDevice2, TEST_SYNC_HANDLE + 1); verifyAddSourceForGroup(meta1); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( @@ -2290,13 +2277,13 @@ public class BassClientServiceTest { // Remove the first broadcast source mBassClientService.removeSource(mCurrentDevice, TEST_SOURCE_ID); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); verifyRemoveMessageAndInjectSourceRemoval(); // Modify the second one and verify all group members getting UPDATE_BCAST_SOURCE BluetoothLeBroadcastMetadata metaUpdate = createBroadcastMetadata(TEST_BROADCAST_ID + 3); mBassClientService.modifySource(mCurrentDevice1, TEST_SOURCE_ID + 3, metaUpdate); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); @@ -2321,7 +2308,7 @@ public class BassClientServiceTest { // Remove the second broadcast source and verify all group members getting // REMOVE_BCAST_SOURCE message for the second source mBassClientService.removeSource(mCurrentDevice, TEST_SOURCE_ID + 2); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); @@ -2390,7 +2377,7 @@ public class BassClientServiceTest { .UPDATE_BCAST_SOURCE) && (m.arg1 == TEST_SOURCE_ID + 20)) .collect(Collectors.toList()); - assertThat(msgs.size()).isEqualTo(1); + assertThat(msgs).hasSize(1); } else { throw new AssertionError("Unexpected device"); } @@ -2404,7 +2391,7 @@ public class BassClientServiceTest { // Verify errors are reported for the entire group mBassClientService.addSource(mCurrentDevice1, null, true); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { verify(sm, never()).sendMessage(any()); } @@ -2419,7 +2406,7 @@ public class BassClientServiceTest { // Verify errors are reported for the entire group mBassClientService.modifySource(mCurrentDevice, TEST_SOURCE_ID, null); TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { BluetoothDevice dev = sm.getDevice(); try { @@ -2433,7 +2420,7 @@ public class BassClientServiceTest { } } - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { doReturn(BluetoothProfile.STATE_DISCONNECTED).when(sm).getConnectionState(); } @@ -2441,7 +2428,7 @@ public class BassClientServiceTest { // Verify errors are reported for the entire group mBassClientService.removeSource(mCurrentDevice, TEST_SOURCE_ID); TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { BluetoothDevice dev = sm.getDevice(); try { @@ -2491,30 +2478,30 @@ public class BassClientServiceTest { // Check adding first handle mBassClientService.addActiveSyncedSource(handle1); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(handle1); // Check if cannot add duplicate element mBassClientService.addActiveSyncedSource(handle1); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(handle1); // Check adding second element mBassClientService.addActiveSyncedSource(handle2); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(2); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(2); assertThat(mBassClientService.getActiveSyncedSources()) .containsExactly(handle1, handle2) .inOrder(); // Check removing non existing element mBassClientService.removeActiveSyncedSource(handle3); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(2); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(2); assertThat(mBassClientService.getActiveSyncedSources()) .containsExactly(handle1, handle2) .inOrder(); // Check removing second element mBassClientService.removeActiveSyncedSource(handle1); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(handle2); // Check removing first element @@ -2524,7 +2511,7 @@ public class BassClientServiceTest { // Add 2 elements mBassClientService.addActiveSyncedSource(handle1); mBassClientService.addActiveSyncedSource(handle2); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(2); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(2); // Check removing all at once mBassClientService.removeActiveSyncedSource(null); @@ -2532,10 +2519,10 @@ public class BassClientServiceTest { } else { final int testSyncHandle = 1; prepareConnectedDeviceGroup(); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); - assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isEqualTo(null); - assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isEqualTo(null); + assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isNull(); + assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isNull(); // Verify add active synced source mBassClientService.addActiveSyncedSource(mCurrentDevice, testSyncHandle); @@ -2543,10 +2530,8 @@ public class BassClientServiceTest { // Verify duplicated source won't be added mBassClientService.addActiveSyncedSource(mCurrentDevice, testSyncHandle); mBassClientService.addActiveSyncedSource(mCurrentDevice1, testSyncHandle); - assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)) - .isNotEqualTo(null); - assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)) - .isNotEqualTo(null); + assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isNotNull(); + assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isNotNull(); assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice).size()) .isEqualTo(1); assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1).size()) @@ -2555,8 +2540,8 @@ public class BassClientServiceTest { // Verify remove active synced source mBassClientService.removeActiveSyncedSource(mCurrentDevice, testSyncHandle); mBassClientService.removeActiveSyncedSource(mCurrentDevice1, testSyncHandle); - expect.that(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isEqualTo(null); - expect.that(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isEqualTo(null); + expect.that(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isNull(); + expect.that(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isNull(); } } @@ -2833,7 +2818,7 @@ public class BassClientServiceTest { 0x56, 0x18, 0x07, - 0x04, // WRONG PUBLIC_BROADCAST data (metada size) + 0x04, // WRONG PUBLIC_BROADCAST data (metadata size) 0x06, 0x07, 0x08, @@ -2913,11 +2898,11 @@ public class BassClientServiceTest { // Two SyncRequest queued but not synced yet assertThat(mBassClientService.getActiveSyncedSources()).isEmpty(); - assertThat(mBassClientService.getDeviceForSyncHandle(handle1)).isEqualTo(null); - assertThat(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(null); - assertThat(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(null); - assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(null); - assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isEqualTo(null); + assertThat(mBassClientService.getDeviceForSyncHandle(handle1)).isNull(); + assertThat(mBassClientService.getDeviceForSyncHandle(handle2)).isNull(); + assertThat(mBassClientService.getDeviceForSyncHandle(handle3)).isNull(); + assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isNull(); + assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isNull(); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle1)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle2)) @@ -2935,13 +2920,13 @@ public class BassClientServiceTest { .verify(mMethodProxy) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(handle1); assertThat(mBassClientService.getDeviceForSyncHandle(handle1)).isEqualTo(device1); - assertThat(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(null); - assertThat(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(null); - assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(null); - assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isEqualTo(null); + assertThat(mBassClientService.getDeviceForSyncHandle(handle2)).isNull(); + assertThat(mBassClientService.getDeviceForSyncHandle(handle3)).isNull(); + assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isNull(); + assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isNull(); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle1)).isEqualTo(broadcastId1); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle2)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); @@ -2954,15 +2939,15 @@ public class BassClientServiceTest { // Sync 2 onSyncEstablished(device2, handle2); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(2); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(2); assertThat(mBassClientService.getActiveSyncedSources()) .containsExactly(handle1, handle2) .inOrder(); assertThat(mBassClientService.getDeviceForSyncHandle(handle1)).isEqualTo(device1); assertThat(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(device2); - assertThat(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(null); - assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(null); - assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isEqualTo(null); + assertThat(mBassClientService.getDeviceForSyncHandle(handle3)).isNull(); + assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isNull(); + assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isNull(); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle1)).isEqualTo(broadcastId1); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle2)).isEqualTo(broadcastId2); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle3)) @@ -2979,14 +2964,14 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(device3, handle3); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(3); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(3); assertThat(mBassClientService.getActiveSyncedSources()) .containsExactly(handle1, handle2, handle3) .inOrder(); assertThat(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(device2); assertThat(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(device3); - assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(null); - assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isEqualTo(null); + assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isNull(); + assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isNull(); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle1)).isEqualTo(broadcastId1); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle2)).isEqualTo(broadcastId2); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle3)).isEqualTo(broadcastId3); @@ -3002,7 +2987,7 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(device4, handle4); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(4); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(4); assertThat(mBassClientService.getActiveSyncedSources()) .containsExactly(handle1, handle2, handle3, handle4) .inOrder(); @@ -3010,7 +2995,7 @@ public class BassClientServiceTest { assertThat(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(device2); assertThat(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(device3); assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(device4); - assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isEqualTo(null); + assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isNull(); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle1)).isEqualTo(broadcastId1); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle2)).isEqualTo(broadcastId2); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle3)).isEqualTo(broadcastId3); @@ -3027,15 +3012,15 @@ public class BassClientServiceTest { .verify(mMethodProxy) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(3); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(3); assertThat(mBassClientService.getActiveSyncedSources()) .containsExactly(handle2, handle3, handle4) .inOrder(); - assertThat(mBassClientService.getDeviceForSyncHandle(handle1)).isEqualTo(null); + assertThat(mBassClientService.getDeviceForSyncHandle(handle1)).isNull(); assertThat(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(device2); assertThat(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(device3); assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(device4); - assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isEqualTo(null); + assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isNull(); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle1)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle2)).isEqualTo(broadcastId2); @@ -3050,7 +3035,7 @@ public class BassClientServiceTest { expect.that(mBassClientService.getActiveSyncedSources()) .containsExactly(handle2, handle3, handle4, handle5) .inOrder(); - expect.that(mBassClientService.getDeviceForSyncHandle(handle1)).isEqualTo(null); + expect.that(mBassClientService.getDeviceForSyncHandle(handle1)).isNull(); expect.that(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(device2); expect.that(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(device3); expect.that(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(device4); @@ -3111,7 +3096,7 @@ public class BassClientServiceTest { onSyncEstablished(device3, handle3); onScanResult(device4, broadcastId4); onSyncEstablished(device4, handle4); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(4); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(4); assertThat(mBassClientService.getActiveSyncedSources()) .containsExactly(handle1, handle2, handle3, handle4) .inOrder(); @@ -3119,7 +3104,7 @@ public class BassClientServiceTest { assertThat(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(device2); assertThat(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(device3); assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(device4); - assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isEqualTo(null); + assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isNull(); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle1)).isEqualTo(broadcastId1); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle2)).isEqualTo(broadcastId2); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle3)).isEqualTo(broadcastId3); @@ -3146,10 +3131,10 @@ public class BassClientServiceTest { .containsExactly(handle1, handle3, handle4) .inOrder(); expect.that(mBassClientService.getDeviceForSyncHandle(handle1)).isEqualTo(device1); - expect.that(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(null); + expect.that(mBassClientService.getDeviceForSyncHandle(handle2)).isNull(); expect.that(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(device3); expect.that(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(device4); - expect.that(mBassClientService.getDeviceForSyncHandle(handle5)).isEqualTo(null); + expect.that(mBassClientService.getDeviceForSyncHandle(handle5)).isNull(); expect.that(mBassClientService.getBroadcastIdForSyncHandle(handle1)) .isEqualTo(broadcastId1); expect.that(mBassClientService.getBroadcastIdForSyncHandle(handle2)) @@ -3206,7 +3191,7 @@ public class BassClientServiceTest { onSyncEstablished(device3, handle3); onScanResult(device4, broadcastId4); onSyncEstablished(device4, handle4); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(4); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(4); assertThat(mBassClientService.getActiveSyncedSources()) .containsExactly(handle1, handle2, handle3, handle4) .inOrder(); @@ -3214,7 +3199,7 @@ public class BassClientServiceTest { assertThat(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(device2); assertThat(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(device3); assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(device4); - assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isEqualTo(null); + assertThat(mBassClientService.getDeviceForSyncHandle(handle5)).isNull(); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle1)).isEqualTo(broadcastId1); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle2)).isEqualTo(broadcastId2); assertThat(mBassClientService.getBroadcastIdForSyncHandle(handle3)).isEqualTo(broadcastId3); @@ -3279,11 +3264,11 @@ public class BassClientServiceTest { expect.that(mBassClientService.getActiveSyncedSources()) .containsExactly(handle2, handle3, handle4) .inOrder(); - expect.that(mBassClientService.getDeviceForSyncHandle(handle1)).isEqualTo(null); + expect.that(mBassClientService.getDeviceForSyncHandle(handle1)).isNull(); expect.that(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(device2); expect.that(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(device3); expect.that(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(device4); - expect.that(mBassClientService.getDeviceForSyncHandle(handle5)).isEqualTo(null); + expect.that(mBassClientService.getDeviceForSyncHandle(handle5)).isNull(); expect.that(mBassClientService.getBroadcastIdForSyncHandle(handle1)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); expect.that(mBassClientService.getBroadcastIdForSyncHandle(handle2)) @@ -3350,11 +3335,11 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(device5, handle5); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(4); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(4); assertThat(mBassClientService.getActiveSyncedSources()) .containsExactly(handle2, handle3, handle4, handle5) .inOrder(); - assertThat(mBassClientService.getDeviceForSyncHandle(handle1)).isEqualTo(null); + assertThat(mBassClientService.getDeviceForSyncHandle(handle1)).isNull(); assertThat(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(device2); assertThat(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(device3); assertThat(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(device4); @@ -3397,7 +3382,7 @@ public class BassClientServiceTest { .isEqualTo(broadcastId1); // Verify not getting ADD_BCAST_SOURCE message before source sync - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { verify(sm, never()).sendMessage(any()); } @@ -3410,7 +3395,7 @@ public class BassClientServiceTest { .containsExactly(handle3, handle4, handle5, handle1) .inOrder(); expect.that(mBassClientService.getDeviceForSyncHandle(handle1)).isEqualTo(device1); - expect.that(mBassClientService.getDeviceForSyncHandle(handle2)).isEqualTo(null); + expect.that(mBassClientService.getDeviceForSyncHandle(handle2)).isNull(); expect.that(mBassClientService.getDeviceForSyncHandle(handle3)).isEqualTo(device3); expect.that(mBassClientService.getDeviceForSyncHandle(handle4)).isEqualTo(device4); expect.that(mBassClientService.getDeviceForSyncHandle(handle5)).isEqualTo(device5); @@ -3465,7 +3450,7 @@ public class BassClientServiceTest { .when(mLeAudioService) .getActiveDevices(); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); @@ -3487,7 +3472,7 @@ public class BassClientServiceTest { handleHandoverSupport(); // Verify all group members getting ADD_BCAST_SOURCE message - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); @@ -3642,7 +3627,7 @@ public class BassClientServiceTest { prepareConnectedDeviceGroup(); startSearchingForSources(); - // Added and executed immidiatelly as no other in queue + // Added and executed immediately as no other in queue mCallbackCaptor .getValue() .onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult1); @@ -3840,7 +3825,7 @@ public class BassClientServiceTest { // Test using onSyncEstablishedFailed - // Added and executed immidiatelly as no other in queue, high rssi + // Added and executed immediately as no other in queue, high rssi mCallbackCaptor .getValue() .onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult1); @@ -3923,7 +3908,7 @@ public class BassClientServiceTest { // Test using onSyncLost - // Added and executed immidiatelly as no other in queue, high rssi + // Added and executed immediately as no other in queue, high rssi mCallbackCaptor .getValue() .onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult1); @@ -3995,15 +3980,15 @@ public class BassClientServiceTest { ScanRecord record = ScanRecord.parseFromBytes(scanRecord); prepareConnectedDeviceGroup(); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); // Verify add active synced source mBassClientService.addActiveSyncedSource(mCurrentDevice, testSyncHandle); mBassClientService.addActiveSyncedSource(mCurrentDevice1, testSyncHandle); - assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isNotEqualTo(null); - assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isNotEqualTo(null); - assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice).size()).isEqualTo(1); - assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1).size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isNotNull(); + assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isNotNull(); + assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).hasSize(1); + assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).hasSize(1); // Verify selectSource with max synced device should not proceed mBassClientService.addActiveSyncedSource(mCurrentDevice, testSyncHandle1); @@ -4013,10 +3998,10 @@ public class BassClientServiceTest { mBassClientService.addActiveSyncedSource(mCurrentDevice, testSyncHandle3); mBassClientService.addActiveSyncedSource(mCurrentDevice1, testSyncHandle3); - assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isNotEqualTo(null); - assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isNotEqualTo(null); - assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice).size()).isEqualTo(4); - assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1).size()).isEqualTo(4); + assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isNotNull(); + assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isNotNull(); + assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).hasSize(4); + assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).hasSize(4); BluetoothDevice testDevice4 = mBluetoothAdapter.getRemoteLeDevice( @@ -4038,8 +4023,8 @@ public class BassClientServiceTest { // Verify remove all active synced source mBassClientService.removeActiveSyncedSource(mCurrentDevice, null); mBassClientService.removeActiveSyncedSource(mCurrentDevice1, null); - expect.that(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isEqualTo(null); - expect.that(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isEqualTo(null); + expect.that(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isNull(); + expect.that(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isNull(); } @Test @@ -4078,7 +4063,7 @@ public class BassClientServiceTest { assertThat( mBassClientService.getPeriodicAdvertisementResult( mSourceDevice, testBroadcastIdInvalid)) - .isEqualTo(null); + .isNull(); PeriodicAdvertisementResult paResult = mBassClientService.getPeriodicAdvertisementResult(mSourceDevice, testBroadcastId); assertThat(paResult.getAddressType()).isEqualTo(BluetoothDevice.ADDRESS_TYPE_RANDOM); @@ -4134,7 +4119,7 @@ public class BassClientServiceTest { assertThat( mBassClientService.getPeriodicAdvertisementResult( mSourceDevice, testBroadcastId2)) - .isEqualTo(null); + .isNull(); PeriodicAdvertisementResult paResult = mBassClientService.getPeriodicAdvertisementResult(mSourceDevice, testBroadcastId1); assertThat(paResult.getAddressType()).isEqualTo(BluetoothDevice.ADDRESS_TYPE_RANDOM); @@ -4170,7 +4155,7 @@ public class BassClientServiceTest { expect.that( mBassClientService.getPeriodicAdvertisementResult( mSourceDevice, testBroadcastId1)) - .isEqualTo(null); + .isNull(); paResult = mBassClientService.getPeriodicAdvertisementResult(mSourceDevice, testBroadcastId2); expect.that(paResult.getAddressType()).isEqualTo(BluetoothDevice.ADDRESS_TYPE_RANDOM); @@ -4244,7 +4229,7 @@ public class BassClientServiceTest { } private void verifyAllGroupMembersGettingUpdateOrAddSource(BluetoothLeBroadcastMetadata meta) { - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); @@ -4400,7 +4385,7 @@ public class BassClientServiceTest { /* Imitate broadcast source stop, sink notify about loosing BIS sync */ verifyRemoveMessageAndInjectSourceRemoval(); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { Mockito.clearInvocations(sm); } @@ -4414,7 +4399,7 @@ public class BassClientServiceTest { mBassClientService.handleUnicastSourceStreamStatusChange( 0 /* STATUS_LOCAL_STREAM_REQUESTED */); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { verify(sm, never()).sendMessage(any()); } @@ -4487,6 +4472,8 @@ public class BassClientServiceTest { // Verify getSyncedBroadcastSinks returns empty device list if no broadcst ID assertThat(mBassClientService.getSyncedBroadcastSinks().isEmpty()).isTrue(); + assertThat(mBassClientService.getSyncedBroadcastSinks(TEST_BROADCAST_ID).isEmpty()) + .isTrue(); // Update receiver state with broadcast ID injectRemoteSourceStateChanged(meta, true, false); @@ -4494,7 +4481,7 @@ public class BassClientServiceTest { List<BluetoothDevice> activeSinks = mBassClientService.getSyncedBroadcastSinks(); if (Flags.leaudioBigDependsOnAudioState()) { // Verify getSyncedBroadcastSinks returns correct device list if no BIS synced - assertThat(activeSinks.size()).isEqualTo(2); + assertThat(activeSinks).hasSize(2); assertThat(activeSinks.contains(mCurrentDevice)).isTrue(); assertThat(activeSinks.contains(mCurrentDevice1)).isTrue(); } else { @@ -4502,6 +4489,16 @@ public class BassClientServiceTest { assertThat(mBassClientService.getSyncedBroadcastSinks().isEmpty()).isTrue(); } + activeSinks.clear(); + // Verify getSyncedBroadcastSinks by broadcast id + activeSinks = mBassClientService.getSyncedBroadcastSinks(TEST_BROADCAST_ID); + if (Flags.leaudioBigDependsOnAudioState()) { + // Verify getSyncedBroadcastSinks returns correct device list if no BIS synced + assertThat(activeSinks.size()).isEqualTo(2); + assertThat(activeSinks.contains(mCurrentDevice)).isTrue(); + assertThat(activeSinks.contains(mCurrentDevice1)).isTrue(); + } + // Update receiver state with BIS sync injectRemoteSourceStateChanged(meta, true, true); @@ -4814,13 +4811,13 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); byte[] scanRecord = new byte[] { @@ -4870,13 +4867,13 @@ public class BassClientServiceTest { callback.onPeriodicAdvertisingReport(report); // Not canceled, not updated base - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); mInOrderMethodProxy .verify(mMethodProxy, never()) .periodicAdvertisingManagerUnregisterSync(any(), any()); @@ -4885,10 +4882,10 @@ public class BassClientServiceTest { // Canceled, not updated base expect.that(mBassClientService.getActiveSyncedSources()).isEmpty(); - expect.that(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null); + expect.that(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isNull(); expect.that(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); - expect.that(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + expect.that(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); mInOrderMethodProxy .verify(mMethodProxy) .periodicAdvertisingManagerUnregisterSync(any(), any()); @@ -4905,13 +4902,13 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); byte[] scanRecord = new byte[] { @@ -4970,13 +4967,13 @@ public class BassClientServiceTest { callback.onPeriodicAdvertisingReport(report); // Not canceled, not updated base - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); mInOrderMethodProxy .verify(mMethodProxy, never()) .periodicAdvertisingManagerUnregisterSync(any(), any()); @@ -4985,10 +4982,10 @@ public class BassClientServiceTest { // Canceled, not updated base expect.that(mBassClientService.getActiveSyncedSources()).isEmpty(); - expect.that(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null); + expect.that(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isNull(); expect.that(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); - expect.that(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + expect.that(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); mInOrderMethodProxy .verify(mMethodProxy) .periodicAdvertisingManagerUnregisterSync(any(), any()); @@ -5005,13 +5002,13 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); onPeriodicAdvertisingReport(); @@ -5022,7 +5019,7 @@ public class BassClientServiceTest { .isEqualTo(mSourceDevice); expect.that(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - expect.that(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNotEqualTo(null); + expect.that(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNotNull(); mInOrderMethodProxy .verify(mMethodProxy, never()) .periodicAdvertisingManagerUnregisterSync(any(), any()); @@ -5039,13 +5036,13 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); byte[] scanRecordNoBaseData = new byte[] { @@ -5146,13 +5143,13 @@ public class BassClientServiceTest { callback.onPeriodicAdvertisingReport(report); // Not canceled, not updated base - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); mInOrderMethodProxy .verify(mMethodProxy, never()) .periodicAdvertisingManagerUnregisterSync(any(), any()); @@ -5166,7 +5163,7 @@ public class BassClientServiceTest { .isEqualTo(mSourceDevice); expect.that(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - expect.that(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNotEqualTo(null); + expect.that(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNotNull(); mInOrderMethodProxy .verify(mMethodProxy, never()) .periodicAdvertisingManagerUnregisterSync(any(), any()); @@ -5179,24 +5176,24 @@ public class BassClientServiceTest { startSearchingForSources(); onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); onPeriodicAdvertisingReport(); // Not canceled, updated base - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNotEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNotNull(); if (!Flags.leaudioBigDependsOnAudioState()) { onBigInfoAdvertisingReport(); @@ -5212,7 +5209,7 @@ public class BassClientServiceTest { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - Assert.assertEquals(TEST_RSSI, metaData.getValue().getRssi()); + assertThat(metaData.getValue().getRssi()).isEqualTo(TEST_RSSI); // Any of them should not notified second time onPeriodicAdvertisingReport(); @@ -5294,25 +5291,25 @@ public class BassClientServiceTest { mCallbackCaptor.getValue().onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); // No public announcement so it will not notify onPeriodicAdvertisingReport(); // Not canceled, updated base - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNotEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNotNull(); // Not notified TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); @@ -5342,13 +5339,13 @@ public class BassClientServiceTest { startSearchingForSources(); onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); // Big report before periodic so before base update onBigInfoAdvertisingReport(); @@ -5365,13 +5362,13 @@ public class BassClientServiceTest { onPeriodicAdvertisingReport(); // Not canceled, updated base - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNotEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNotNull(); if (!Flags.leaudioBigDependsOnAudioState()) { // onBigInfoAdvertisingReport causes notification @@ -5397,13 +5394,13 @@ public class BassClientServiceTest { startSearchingForSources(); onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNull(); byte[] scanRecordNoBaseData = new byte[] { @@ -5469,7 +5466,7 @@ public class BassClientServiceTest { .isEqualTo(mSourceDevice); expect.that(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); - expect.that(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNotEqualTo(null); + expect.that(mBassClientService.getBase(TEST_SYNC_HANDLE)).isNotNull(); // Notified TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); @@ -5560,7 +5557,7 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); @@ -5578,7 +5575,7 @@ public class BassClientServiceTest { // Cleaned all assertThat(mBassClientService.getActiveSyncedSources()).isEmpty(); - assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isNull(); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); @@ -5604,7 +5601,7 @@ public class BassClientServiceTest { .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); - assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); + assertThat(mBassClientService.getActiveSyncedSources()).hasSize(1); assertThat(mBassClientService.getActiveSyncedSources()).containsExactly(TEST_SYNC_HANDLE); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); @@ -5623,7 +5620,7 @@ public class BassClientServiceTest { // Cleaned all assertThat(mBassClientService.getActiveSyncedSources()).isEmpty(); - assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null); + assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isNull(); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); @@ -6363,7 +6360,8 @@ public class BassClientServiceTest { @Test @EnableFlags({ Flags.FLAG_LEAUDIO_BROADCAST_RESYNC_HELPER, - Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE + Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE, + Flags.FLAG_LEAUDIO_MONITOR_UNICAST_SOURCE_WHEN_MANAGED_BY_BROADCAST_DELEGATOR }) public void sinkUnintentional_handleUnicastSourceStreamStatusChange_withoutScanning() { sinkUnintentionalWithoutScanning(); @@ -6372,7 +6370,6 @@ public class BassClientServiceTest { mBassClientService.handleUnicastSourceStreamStatusChange( 0 /* STATUS_LOCAL_STREAM_REQUESTED */); verifyStopBigMonitoringWithUnsync(); - verifyRemoveMessageAndInjectSourceRemoval(); checkNoResumeSynchronizationByBig(); /* Unicast finished streaming */ @@ -6385,7 +6382,8 @@ public class BassClientServiceTest { @Test @EnableFlags({ Flags.FLAG_LEAUDIO_BROADCAST_RESYNC_HELPER, - Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE + Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE, + Flags.FLAG_LEAUDIO_MONITOR_UNICAST_SOURCE_WHEN_MANAGED_BY_BROADCAST_DELEGATOR }) public void sinkUnintentional_handleUnicastSourceStreamStatusChange_duringScanning() { sinkUnintentionalDuringScanning(); @@ -6394,7 +6392,6 @@ public class BassClientServiceTest { mBassClientService.handleUnicastSourceStreamStatusChange( 0 /* STATUS_LOCAL_STREAM_REQUESTED */); verifyStopBigMonitoringWithoutUnsync(); - verifyRemoveMessageAndInjectSourceRemoval(); checkNoResumeSynchronizationByBig(); /* Unicast finished streaming */ @@ -6657,6 +6654,44 @@ public class BassClientServiceTest { @Test @EnableFlags({ Flags.FLAG_LEAUDIO_BROADCAST_RESYNC_HELPER, + Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE, + Flags.FLAG_LEAUDIO_BROADCAST_ASSISTANT_PERIPHERAL_ENTRUSTMENT, + Flags.FLAG_LEAUDIO_MONITOR_UNICAST_SOURCE_WHEN_MANAGED_BY_BROADCAST_DELEGATOR + }) + public void hostIntentional_handleUnicastSourceStreamStatusChange_beforeResumeCompleted() { + prepareSynchronizedPairAndStopSearching(); + + /* Unicast would like to stream */ + mBassClientService.handleUnicastSourceStreamStatusChange( + 0 /* STATUS_LOCAL_STREAM_REQUESTED */); + checkNoSinkPause(); + + /* Unicast finished streaming */ + mBassClientService.handleUnicastSourceStreamStatusChange( + 2 /* STATUS_LOCAL_STREAM_SUSPENDED */); + mInOrderMethodProxy + .verify(mMethodProxy) + .periodicAdvertisingManagerRegisterSync( + any(), any(), anyInt(), anyInt(), any(), any()); + + /* Unicast would like to stream again before previous resume was complete*/ + mBassClientService.handleUnicastSourceStreamStatusChange( + 0 /* STATUS_LOCAL_STREAM_REQUESTED */); + + /* Unicast finished streaming */ + mBassClientService.handleUnicastSourceStreamStatusChange( + 2 /* STATUS_LOCAL_STREAM_SUSPENDED */); + mInOrderMethodProxy + .verify(mMethodProxy) + .periodicAdvertisingManagerRegisterSync( + any(), any(), anyInt(), anyInt(), any(), any()); + onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); // In case of add source to inactive + verifyAllGroupMembersGettingUpdateOrAddSource(createBroadcastMetadata(TEST_BROADCAST_ID)); + } + + @Test + @EnableFlags({ + Flags.FLAG_LEAUDIO_BROADCAST_RESYNC_HELPER, Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE }) public void hostIntentional_handleUnicastSourceStreamStatusChangeNoContext_withoutScanning() { @@ -6839,7 +6874,7 @@ public class BassClientServiceTest { mBassClientService.getCallbacks().notifyBassStateReady(mCurrentDevice); TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); - assertThat(mStateMachines.size()).isEqualTo(2); + assertThat(mStateMachines).hasSize(2); for (BassClientStateMachine sm : mStateMachines.values()) { // No adding source if device remain synced verify(sm, never()).sendMessage(any()); @@ -6876,6 +6911,83 @@ public class BassClientServiceTest { } } + /** Test add pending source when BASS state get ready */ + @Test + @EnableFlags({ + Flags.FLAG_LEAUDIO_BROADCAST_RESYNC_HELPER, + Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE + }) + public void sinkBassStateReady_addPendingSource() { + prepareConnectedDeviceGroup(); + BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); + // Verify adding source when Bass state not ready + for (BassClientStateMachine sm : mStateMachines.values()) { + doReturn(false).when(sm).isBassStateReady(); + } + doReturn(true).when(mLeAudioService).isPlaying(TEST_BROADCAST_ID); + doReturn(new ArrayList<BluetoothLeBroadcastMetadata>(Arrays.asList(meta))) + .when(mLeAudioService) + .getAllBroadcastMetadata(); + // Add broadcast source and got queued due to BASS not ready + mBassClientService.addSource(mCurrentDevice, meta, false); + + mBassClientService.getCallbacks().notifyBassStateSetupFailed(mCurrentDevice); + TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); + + // Verify adding source callback is triggered if BASS state initiate failed + try { + verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()) + .onSourceAddFailed( + eq(mCurrentDevice), + eq(meta), + eq(BluetoothStatusCodes.ERROR_REMOTE_NOT_ENOUGH_RESOURCES)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + // Verify not getting ADD_BCAST_SOURCE message if no pending source to add + for (BassClientStateMachine sm : mStateMachines.values()) { + doReturn(true).when(sm).isBassStateReady(); + } + mBassClientService.getCallbacks().notifyBassStateReady(mCurrentDevice); + TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); + + for (BassClientStateMachine sm : mStateMachines.values()) { + if (sm.getDevice().equals(mCurrentDevice)) { + verify(sm, never()).sendMessage(any()); + clearInvocations(sm); + } + } + + for (BassClientStateMachine sm : mStateMachines.values()) { + doReturn(false).when(sm).isBassStateReady(); + } + // Add broadcast source and got queued due to BASS not ready + mBassClientService.addSource(mCurrentDevice, meta, false); + + for (BassClientStateMachine sm : mStateMachines.values()) { + doReturn(true).when(sm).isBassStateReady(); + } + mBassClientService.getCallbacks().notifyBassStateReady(mCurrentDevice); + TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); + + // Verify adding source is resumed once BASS state ready + for (BassClientStateMachine sm : mStateMachines.values()) { + if (sm.getDevice().equals(mCurrentDevice)) { + ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); + verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); + + Message msg = + messageCaptor.getAllValues().stream() + .filter(m -> (m.what == BassClientStateMachine.ADD_BCAST_SOURCE)) + .findFirst() + .orElse(null); + assertThat(msg).isNotNull(); + clearInvocations(sm); + } + } + } + @Test public void testIsLocalBroadacst() { int broadcastId = 12345; @@ -7020,7 +7132,7 @@ public class BassClientServiceTest { mBassClientService.syncRequestForPast( mCurrentDevice1, TEST_BROADCAST_ID, TEST_SOURCE_ID + 1); - // Sync will send INITIATE_PA_SYNC_TRANSFER and remove pending soure to add + // Sync will send INITIATE_PA_SYNC_TRANSFER and remove pending source to add onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); verifyInitiatePaSyncTransferAndNoOthers(); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java index 919326767f..e6b2d5f1d6 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java @@ -48,6 +48,7 @@ import static com.android.bluetooth.bass_client.BassClientStateMachine.UPDATE_BC import static com.android.bluetooth.bass_client.BassConstants.CLIENT_CHARACTERISTIC_CONFIG; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -110,9 +111,7 @@ import com.google.common.primitives.Bytes; import org.hamcrest.Matcher; import org.hamcrest.core.AllOf; -import org.hamcrest.core.IsInstanceOf; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -145,8 +144,11 @@ public class BassClientStateMachineTest { private static final int WAIT_MS = 1_200; private static final int TEST_BROADCAST_ID = 42; private static final int TEST_SOURCE_ID = 1; + private static final int TEST_CHANNEL_INDEX = 1; private static final String TEST_BROADCAST_NAME = "Test"; private static final String EMPTY_BLUETOOTH_DEVICE_ADDRESS = "00:00:00:00:00:00"; + private static final byte OPCODE_UPDATE_SOURCE = 0x03; + private static final int UPDATE_SOURCE_FIXED_LENGTH = 6; private Context mTargetContext; private BluetoothAdapter mAdapter; private HandlerThread mHandlerThread; @@ -208,7 +210,7 @@ public class BassClientStateMachineTest { || type == BassClientStateMachine.ConnectedProcessing.class) { return BluetoothProfile.STATE_CONNECTED; } else { - Assert.fail("Invalid class type given: " + type); + assertWithMessage("Invalid class type given: " + type).fail(); return 0; } } @@ -228,8 +230,8 @@ public class BassClientStateMachineTest { /** Test that default state is disconnected */ @Test public void testDefaultDisconnectedState() { - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, mBassClientStateMachine.getConnectionState()); + assertThat(mBassClientStateMachine.getConnectionState()) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); } /** @@ -259,9 +261,8 @@ public class BassClientStateMachineTest { .sendBroadcast(any(Intent.class), anyString()); // Check that we are in Disconnected state - Assert.assertThat( - mBassClientStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(BassClientStateMachine.Disconnected.class)); + assertThat(mBassClientStateMachine.getCurrentState()) + .isInstanceOf(BassClientStateMachine.Disconnected.class); } @Test @@ -277,9 +278,8 @@ public class BassClientStateMachineTest { .sendBroadcast(any(Intent.class), anyString()); // Check that we are in Disconnected state - Assert.assertThat( - mBassClientStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(BassClientStateMachine.Disconnected.class)); + assertThat(mBassClientStateMachine.getCurrentState()) + .isInstanceOf(BassClientStateMachine.Disconnected.class); assertThat(mBassClientStateMachine.mBluetoothGatt).isNull(); } @@ -298,13 +298,11 @@ public class BassClientStateMachineTest { intentArgument1.capture(), any(String[].class), any(BroadcastOptions.class)); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTING, - intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); + assertThat(intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)) + .isEqualTo(BluetoothProfile.STATE_CONNECTING); - Assert.assertThat( - mBassClientStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(BassClientStateMachine.Connecting.class)); + assertThat(mBassClientStateMachine.getCurrentState()) + .isInstanceOf(BassClientStateMachine.Connecting.class); assertThat(mBassClientStateMachine.mGattCallback).isNotNull(); mBassClientStateMachine.notifyConnectionStateChanged( @@ -319,9 +317,8 @@ public class BassClientStateMachineTest { any(String[].class), any(BroadcastOptions.class)); - Assert.assertThat( - mBassClientStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(BassClientStateMachine.Connected.class)); + assertThat(mBassClientStateMachine.getCurrentState()) + .isInstanceOf(BassClientStateMachine.Connected.class); } @Test @@ -339,13 +336,11 @@ public class BassClientStateMachineTest { intentArgument1.capture(), any(String[].class), any(BroadcastOptions.class)); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTING, - intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); + assertThat(intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)) + .isEqualTo(BluetoothProfile.STATE_CONNECTING); - Assert.assertThat( - mBassClientStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(BassClientStateMachine.Connecting.class)); + assertThat(mBassClientStateMachine.getCurrentState()) + .isInstanceOf(BassClientStateMachine.Connecting.class); // Verify that one connection state broadcast is executed ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class); @@ -354,13 +349,11 @@ public class BassClientStateMachineTest { intentArgument2.capture(), any(String[].class), any(BroadcastOptions.class)); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, - intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); + assertThat(intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); - Assert.assertThat( - mBassClientStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(BassClientStateMachine.Disconnected.class)); + assertThat(mBassClientStateMachine.getCurrentState()) + .isInstanceOf(BassClientStateMachine.Disconnected.class); } @Test @@ -734,6 +727,8 @@ public class BassClientStateMachineTest { BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class); mBassClientStateMachine.mBluetoothGatt = btGatt; + BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class); + when(mBassClientService.getCallbacks()).thenReturn(callbacks); // Do nothing if mDiscoveryInitiated is false. mBassClientStateMachine.mDiscoveryInitiated = false; @@ -748,6 +743,8 @@ public class BassClientStateMachineTest { cb.onServicesDiscovered(null, status); verify(btGatt, never()).requestMtu(anyInt()); + verify(callbacks).notifyBassStateSetupFailed(eq(mBassClientStateMachine.getDevice())); + assertThat(mBassClientStateMachine.isBassStateReady()).isEqualTo(false); // call requestMtu() if status is GATT_SUCCESS. mBassClientStateMachine.mDiscoveryInitiated = true; @@ -812,7 +809,7 @@ public class BassClientStateMachineTest { ArgumentCaptor.forClass(BluetoothLeBroadcastReceiveState.class); verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mEmptyTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mEmptyTestDevice); mBassClientStateMachine.mPendingOperation = 0; mBassClientStateMachine.mPendingSourceId = 0; @@ -824,7 +821,7 @@ public class BassClientStateMachineTest { TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mEmptyTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mEmptyTestDevice); mBassClientStateMachine.mPendingMetadata = createBroadcastMetadata(); sourceId = 1; @@ -881,7 +878,7 @@ public class BassClientStateMachineTest { verify(callbacks).notifySourceAdded(any(), any(), anyInt()); verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mSourceTestDevice); // set some values for covering more lines of processPASyncState() mBassClientStateMachine.mPendingMetadata = null; @@ -915,7 +912,7 @@ public class BassClientStateMachineTest { verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mSourceTestDevice); assertThat(mBassClientStateMachine.mMsgWhats).contains(REMOVE_BCAST_SOURCE); mBassClientStateMachine.mIsPendingRemove = null; @@ -939,7 +936,7 @@ public class BassClientStateMachineTest { any(), anyInt(), eq(BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST)); verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mEmptyTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mEmptyTestDevice); } @Test @@ -972,7 +969,7 @@ public class BassClientStateMachineTest { ArgumentCaptor<BluetoothLeBroadcastReceiveState> receiveStateCaptor = ArgumentCaptor.forClass(BluetoothLeBroadcastReceiveState.class); verify(callbacks).notifyReceiveStateChanged(any(), anyInt(), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mEmptyTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mEmptyTestDevice); } @Test @@ -1104,7 +1101,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mSourceTestDevice); // Read first time second (last) characteristic int sourceId2 = 2; @@ -1121,7 +1118,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId2), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mSourceTestDevice); } /** This also tests BassClientStateMachine#processBroadcastReceiverState. */ @@ -1219,7 +1216,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mSourceTestDevice); // Empty value to indicates removing source from device by remote when(characteristic.getValue()).thenReturn(new byte[] {}); @@ -1234,7 +1231,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mEmptyTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mEmptyTestDevice); // Sync value again mBassClientStateMachine.mPendingOperation = ADD_BCAST_SOURCE; @@ -1249,7 +1246,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mSourceTestDevice); // Empty value to indicates removing source from device by local app mBassClientStateMachine.mPendingOperation = REMOVE_BCAST_SOURCE; @@ -1265,7 +1262,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mEmptyTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mEmptyTestDevice); // Sync value again mBassClientStateMachine.mPendingOperation = ADD_BCAST_SOURCE; @@ -1280,7 +1277,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mSourceTestDevice); // Empty value to indicates removing source from device by stack (source switch) BluetoothLeBroadcastMetadata metadata = createBroadcastMetadata(); @@ -1297,7 +1294,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mEmptyTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mEmptyTestDevice); assertThat(mBassClientStateMachine.mMsgWhats).contains(ADD_BCAST_SOURCE); assertThat(mBassClientStateMachine.mMsgObj).isEqualTo(metadata); @@ -1314,7 +1311,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mSourceTestDevice); // Update value - PA SyncInfo Request value[BassConstants.BCAST_RCVR_STATE_PA_SYNC_IDX] = @@ -1342,7 +1339,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mSourceTestDevice); // Update value - PA SyncInfo Request, local broadcast mBassClientStateMachine.mPendingMetadata = createBroadcastMetadata(); @@ -1364,7 +1361,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mSourceTestDevice); // Update value - Broadcast Code value[BassConstants.BCAST_RCVR_STATE_PA_SYNC_IDX] = @@ -1384,7 +1381,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mSourceTestDevice); // Update value - Pending Remove value[BassConstants.BCAST_RCVR_STATE_PA_SYNC_IDX] = @@ -1401,7 +1398,7 @@ public class BassClientStateMachineTest { inOrderCallbacks .verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); - Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); + assertThat(receiveStateCaptor.getValue().getSourceDevice()).isEqualTo(mSourceTestDevice); } @Test @@ -1436,6 +1433,15 @@ public class BassClientStateMachineTest { BluetoothGattCallback cb = mBassClientStateMachine.mGattCallback; mBassClientStateMachine.mMTUChangeRequested = true; + BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class); + when(mBassClientService.getCallbacks()).thenReturn(callbacks); + + // Verify notifyBassStateSetupFailed is called + cb.onMtuChanged(null, 10, GATT_FAILURE); + verify(callbacks).notifyBassStateSetupFailed(eq(mBassClientStateMachine.getDevice())); + assertThat(mBassClientStateMachine.mMTUChangeRequested).isTrue(); + assertThat(mBassClientStateMachine.isBassStateReady()).isEqualTo(false); + cb.onMtuChanged(null, 10, GATT_SUCCESS); assertThat(mBassClientStateMachine.mMTUChangeRequested).isTrue(); @@ -1853,7 +1859,7 @@ public class BassClientStateMachineTest { BassClientStateMachine.ConnectedProcessing.class); verify(scanControlPoint).setValue(any(byte[].class)); verify(btGatt).writeCharacteristic(any()); - assertThat(mBassClientStateMachine.mPendingSourceToSwitch).isEqualTo(null); + assertThat(mBassClientStateMachine.mPendingSourceToSwitch).isNull(); } @Test @@ -2651,7 +2657,7 @@ public class BassClientStateMachineTest { verify(mMethodProxy, timeout(TIMEOUT_MS)) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); - Assert.assertEquals(mBassClientStateMachine.mPendingSourceToAdd, metadata); + assertThat(mBassClientStateMachine.mPendingSourceToAdd).isEqualTo(metadata); verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any()); } @@ -2778,7 +2784,7 @@ public class BassClientStateMachineTest { ArgumentCaptor.forClass(BluetoothLeBroadcastMetadata.class); verify(callbacks).notifySourceFound(metaData.capture()); - Assert.assertEquals(testRssi, metaData.getValue().getRssi()); + assertThat(metaData.getValue().getRssi()).isEqualTo(testRssi); } @Test @@ -2978,7 +2984,7 @@ public class BassClientStateMachineTest { TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED, BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING, - BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG); + BassConstants.BCAST_RCVR_STATE_BIS_SYNC_FAILED_SYNC_TO_BIG); // Verify broadcast audio session is logged when bis sync failed verify(mMetricsLogger) .logLeAudioBroadcastAudioSync( @@ -3088,6 +3094,7 @@ public class BassClientStateMachineTest { 0x0L); // Verify notifyBassStateReady is called verify(callbacks).notifyBassStateReady(eq(mTestDevice)); + assertThat(mBassClientStateMachine.isBassStateReady()).isEqualTo(true); } @Test @@ -3179,6 +3186,69 @@ public class BassClientStateMachineTest { assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(TEST_SOURCE_ID); } + @Test + public void updateBroadcastSource_withMetadataChanged() { + prepareInitialReceiveStateForGatt(); + + generateBroadcastReceiveStatesAndVerify( + mSourceTestDevice, + TEST_SOURCE_ID, + BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED, + BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING, + 0x1L); + + BassClientStateMachine.BluetoothGattTestableWrapper btGatt = + Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class); + mBassClientStateMachine.mBluetoothGatt = btGatt; + BluetoothGattCharacteristic scanControlPoint = + Mockito.mock(BluetoothGattCharacteristic.class); + mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint; + + BluetoothLeBroadcastMetadata metadata = createBroadcastMetadata(); + mBassClientStateMachine.mPendingMetadata = metadata; + + // Verify pausing broadcast stream with updated metadata + BluetoothLeBroadcastMetadata updatedMetadataPaused = getMetadataToPauseStream(metadata); + byte[] valueBisPaused = convertMetadataToUpdateSourceByteArray(updatedMetadataPaused); + + sendMessageAndVerifyTransition( + mBassClientStateMachine.obtainMessage( + UPDATE_BCAST_SOURCE, + TEST_SOURCE_ID, + BassConstants.INVALID_PA_SYNC_VALUE, + updatedMetadataPaused), + BassClientStateMachine.ConnectedProcessing.class); + assertThat(mBassClientStateMachine.mPendingOperation).isEqualTo(UPDATE_BCAST_SOURCE); + assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(TEST_SOURCE_ID); + verify(scanControlPoint).setValue(eq(valueBisPaused)); + + sendMessageAndVerifyTransition( + mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED), + BassClientStateMachine.Connected.class); + TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); + Mockito.clearInvocations(scanControlPoint); + + // Verify resuming broadcast stream with the original metadata + byte[] valueBisResumed = convertMetadataToUpdateSourceByteArray(metadata); + sendMessageAndVerifyTransition( + mBassClientStateMachine.obtainMessage( + UPDATE_BCAST_SOURCE, + TEST_SOURCE_ID, + BassConstants.INVALID_PA_SYNC_VALUE, + metadata), + BassClientStateMachine.ConnectedProcessing.class); + assertThat(mBassClientStateMachine.mPendingOperation).isEqualTo(UPDATE_BCAST_SOURCE); + assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(TEST_SOURCE_ID); + verify(scanControlPoint).setValue(eq(valueBisResumed)); + + sendMessageAndVerifyTransition( + mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED), + BassClientStateMachine.Connected.class); + + TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); + Mockito.clearInvocations(scanControlPoint); + } + private void initToConnectingState() { allowConnection(true); allowConnectGatt(true); @@ -3258,7 +3328,7 @@ public class BassClientStateMachineTest { Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND)); } - Assert.assertThat(mBassClientStateMachine.getCurrentState(), IsInstanceOf.instanceOf(type)); + assertThat(mBassClientStateMachine.getCurrentState()).isInstanceOf(type); } private BluetoothLeBroadcastMetadata createBroadcastMetadata() { @@ -3284,6 +3354,78 @@ public class BassClientStateMachineTest { return builder.build(); } + private byte[] convertMetadataToUpdateSourceByteArray(BluetoothLeBroadcastMetadata metaData) { + int numSubGroups = metaData.getSubgroups().size(); + + byte[] res = new byte[UPDATE_SOURCE_FIXED_LENGTH + numSubGroups * 5]; + int offset = 0; + // Opcode + res[offset++] = OPCODE_UPDATE_SOURCE; + // Source_ID + res[offset++] = (byte) TEST_SOURCE_ID; + // PA_Sync + res[offset++] = (byte) (0x01); + // PA_Interval + res[offset++] = (byte) 0xFF; + res[offset++] = (byte) 0xFF; + // Num_Subgroups + res[offset++] = (byte) numSubGroups; + + for (int i = 0; i < numSubGroups; i++) { + int bisIndexValue = 0; + for (BluetoothLeBroadcastChannel channel : + metaData.getSubgroups().get(i).getChannels()) { + if (channel.isSelected()) { + if (channel.getChannelIndex() == 0) { + continue; + } + bisIndexValue |= 1 << (channel.getChannelIndex() - 1); + } + } + // BIS_Sync + res[offset++] = (byte) (bisIndexValue & 0x00000000000000FF); + res[offset++] = (byte) ((bisIndexValue & 0x000000000000FF00) >>> 8); + res[offset++] = (byte) ((bisIndexValue & 0x0000000000FF0000) >>> 16); + res[offset++] = (byte) ((bisIndexValue & 0x00000000FF000000) >>> 24); + // Metadata_Length; On Modify source, don't update any Metadata + res[offset++] = 0; + } + return res; + } + + private BluetoothLeBroadcastMetadata getMetadataToPauseStream( + BluetoothLeBroadcastMetadata metadata) { + BluetoothLeBroadcastMetadata.Builder metadataToUpdateBuilder = + new BluetoothLeBroadcastMetadata.Builder(metadata); + + List<BluetoothLeBroadcastSubgroup> updatedSubgroups = new ArrayList<>(); + for (BluetoothLeBroadcastSubgroup subgroup : metadata.getSubgroups()) { + BluetoothLeBroadcastSubgroup.Builder subgroupBuilder = + new BluetoothLeBroadcastSubgroup.Builder(subgroup); + + List<BluetoothLeBroadcastChannel> updatedChannels = new ArrayList<>(); + for (BluetoothLeBroadcastChannel channel : subgroup.getChannels()) { + BluetoothLeBroadcastChannel updatedChannel = + new BluetoothLeBroadcastChannel.Builder(channel).setSelected(false).build(); + updatedChannels.add(updatedChannel); + } + + subgroupBuilder.clearChannel(); + for (BluetoothLeBroadcastChannel channel : updatedChannels) { + subgroupBuilder.addChannel(channel); + } + + updatedSubgroups.add(subgroupBuilder.build()); + } + + metadataToUpdateBuilder.clearSubgroup(); + for (BluetoothLeBroadcastSubgroup subgroup : updatedSubgroups) { + metadataToUpdateBuilder.addSubgroup(subgroup); + } + + return metadataToUpdateBuilder.build(); + } + private void prepareInitialReceiveStateForGatt() { initToConnectedState(); mBassClientStateMachine.connectGatt(true); @@ -3386,7 +3528,7 @@ public class BassClientStateMachineTest { null, characteristic, GATT_SUCCESS); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - assertThat(mBassClientStateMachine.getAllSources().size()).isEqualTo(1); + assertThat(mBassClientStateMachine.getAllSources()).hasSize(1); BluetoothLeBroadcastReceiveState recvState = mBassClientStateMachine.getAllSources().get(0); assertThat(recvState.getSourceId()).isEqualTo(sourceId); @@ -3398,10 +3540,10 @@ public class BassClientStateMachineTest { assertThat(recvState.getBigEncryptionState()).isEqualTo(bigEncryptState); assertThat(recvState.getNumSubgroups()).isEqualTo(numOfSubgroups); - assertThat(recvState.getBisSyncState().size()).isEqualTo(numOfSubgroups); + assertThat(recvState.getBisSyncState()).hasSize(numOfSubgroups); assertThat(recvState.getBisSyncState().get(0)).isEqualTo(bisSyncState); - assertThat(recvState.getSubgroupMetadata().size()).isEqualTo(numOfSubgroups); + assertThat(recvState.getSubgroupMetadata()).hasSize(numOfSubgroups); BluetoothLeAudioContentMetadata metaData = recvState.getSubgroupMetadata().get(0); assertThat(metaData.getRawMetadata().length).isEqualTo(metaDataLength); assertThat(metaData.getRawMetadata()) @@ -3416,7 +3558,6 @@ public class BassClientStateMachineTest { // German language code in ISO 639-3 final String testLanguage = "deu"; final int testCodecId = 42; - final int testChannelIndex = 56; BluetoothLeAudioCodecConfigMetadata codecMetadata = new BluetoothLeAudioCodecConfigMetadata.Builder() @@ -3442,7 +3583,7 @@ public class BassClientStateMachineTest { BluetoothLeBroadcastChannel channel = new BluetoothLeBroadcastChannel.Builder() .setSelected(true) - .setChannelIndex(testChannelIndex) + .setChannelIndex(TEST_CHANNEL_INDEX) .setCodecMetadata(channelCodecMetadata) .build(); builder.addChannel(channel); diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java index 1d7e3a2c2d..c81c611336 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java @@ -63,7 +63,6 @@ import com.android.bluetooth.hfp.HeadsetService; import com.android.bluetooth.le_audio.LeAudioService; import org.junit.After; -import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Rule; @@ -288,7 +287,7 @@ public class ActiveDeviceManagerTest { // Don't call mA2dpService.setActiveDevice() mTestLooper.dispatchAll(); verify(mA2dpService).setActiveDevice(mA2dpDevice); - Assert.assertEquals(mA2dpDevice, mActiveDeviceManager.getA2dpActiveDevice()); + assertThat(mActiveDeviceManager.getA2dpActiveDevice()).isEqualTo(mA2dpDevice); } /** @@ -358,7 +357,7 @@ public class ActiveDeviceManagerTest { // Don't call mHeadsetService.setActiveDevice() mTestLooper.dispatchAll(); verify(mHeadsetService).setActiveDevice(mHeadsetDevice); - Assert.assertEquals(mHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice()); + assertThat(mActiveDeviceManager.getHfpActiveDevice()).isEqualTo(mHeadsetDevice); } /** @@ -589,7 +588,6 @@ public class ActiveDeviceManagerTest { } @Test - @EnableFlags(Flags.FLAG_ADM_ALWAYS_FALLBACK_TO_AVAILABLE_DEVICE) public void a2dpHeadsetActivated_checkFallbackMeachanismOneA2dpOneHeadset() { // Active call when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); @@ -621,7 +619,7 @@ public class ActiveDeviceManagerTest { a2dpActiveDeviceChanged(null); mTestLooper.dispatchAll(); - assertThat(mActiveDeviceManager.getA2dpActiveDevice()).isEqualTo(null); + assertThat(mActiveDeviceManager.getA2dpActiveDevice()).isNull(); assertThat(mActiveDeviceManager.getHfpActiveDevice()).isEqualTo(mA2dpHeadsetDevice); // Connect 2nd device @@ -807,7 +805,7 @@ public class ActiveDeviceManagerTest { verify(mHearingAidService).removeActiveDevice(false); // Don't call mA2dpService.setActiveDevice() verify(mA2dpService, never()).setActiveDevice(mA2dpDevice); - Assert.assertEquals(mA2dpDevice, mActiveDeviceManager.getA2dpActiveDevice()); + assertThat(mActiveDeviceManager.getA2dpActiveDevice()).isEqualTo(mA2dpDevice); assertThat(mActiveDeviceManager.getHearingAidActiveDevices()).isEmpty(); } @@ -826,7 +824,7 @@ public class ActiveDeviceManagerTest { verify(mHearingAidService).removeActiveDevice(false); // Don't call mHeadsetService.setActiveDevice() verify(mHeadsetService, never()).setActiveDevice(mHeadsetDevice); - Assert.assertEquals(mHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice()); + assertThat(mActiveDeviceManager.getHfpActiveDevice()).isEqualTo(mHeadsetDevice); assertThat(mActiveDeviceManager.getHearingAidActiveDevices()).isEmpty(); } @@ -975,7 +973,7 @@ public class ActiveDeviceManagerTest { // Don't call mLeAudioService.setActiveDevice() mTestLooper.dispatchAll(); verify(mLeAudioService, never()).setActiveDevice(any(BluetoothDevice.class)); - Assert.assertEquals(mLeAudioDevice, mActiveDeviceManager.getLeAudioActiveDevice()); + assertThat(mActiveDeviceManager.getLeAudioActiveDevice()).isEqualTo(mLeAudioDevice); } /** @@ -1203,7 +1201,7 @@ public class ActiveDeviceManagerTest { mTestLooper.dispatchAll(); verify(mLeAudioService).removeActiveDevice(true); verify(mA2dpService).setActiveDevice(mA2dpDevice); - Assert.assertEquals(mA2dpDevice, mActiveDeviceManager.getA2dpActiveDevice()); + assertThat(mActiveDeviceManager.getA2dpActiveDevice()).isEqualTo(mA2dpDevice); assertThat(mActiveDeviceManager.getLeAudioActiveDevice()).isNull(); } @@ -1221,7 +1219,7 @@ public class ActiveDeviceManagerTest { mTestLooper.dispatchAll(); verify(mLeAudioService).removeActiveDevice(true); verify(mHeadsetService).setActiveDevice(mHeadsetDevice); - Assert.assertEquals(mHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice()); + assertThat(mActiveDeviceManager.getHfpActiveDevice()).isEqualTo(mHeadsetDevice); assertThat(mActiveDeviceManager.getLeAudioActiveDevice()).isNull(); } @@ -1585,8 +1583,8 @@ public class ActiveDeviceManagerTest { verify(mA2dpService, atLeastOnce()).setActiveDevice(mDualModeAudioDevice); verify(mHeadsetService, atLeastOnce()).setActiveDevice(mDualModeAudioDevice); verify(mLeAudioService, atLeastOnce()).removeActiveDevice(true); - Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getA2dpActiveDevice()); - Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getHfpActiveDevice()); + assertThat(mActiveDeviceManager.getA2dpActiveDevice()).isEqualTo(mDualModeAudioDevice); + assertThat(mActiveDeviceManager.getHfpActiveDevice()).isEqualTo(mDualModeAudioDevice); // Ensure we make classic audio profiles inactive when LEA is made active leAudioConnected(mDualModeAudioDevice); @@ -1594,7 +1592,7 @@ public class ActiveDeviceManagerTest { verify(mA2dpService).removeActiveDevice(false); verify(mHeadsetService).setActiveDevice(isNull()); verify(mLeAudioService).setActiveDevice(mDualModeAudioDevice); - Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getLeAudioActiveDevice()); + assertThat(mActiveDeviceManager.getLeAudioActiveDevice()).isEqualTo(mDualModeAudioDevice); } /** @@ -1638,15 +1636,15 @@ public class ActiveDeviceManagerTest { verify(mLeAudioService, never()).removeActiveDevice(anyBoolean()); verify(mLeAudioService).setActiveDevice(mDualModeAudioDevice); - Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getA2dpActiveDevice()); - Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getHfpActiveDevice()); - Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getLeAudioActiveDevice()); + assertThat(mActiveDeviceManager.getA2dpActiveDevice()).isEqualTo(mDualModeAudioDevice); + assertThat(mActiveDeviceManager.getHfpActiveDevice()).isEqualTo(mDualModeAudioDevice); + assertThat(mActiveDeviceManager.getLeAudioActiveDevice()).isEqualTo(mDualModeAudioDevice); // Verify LEA made inactive when a supported classic audio profile is made inactive a2dpActiveDeviceChanged(null); mTestLooper.dispatchAll(); - Assert.assertEquals(null, mActiveDeviceManager.getA2dpActiveDevice()); - Assert.assertEquals(null, mActiveDeviceManager.getLeAudioActiveDevice()); + assertThat(mActiveDeviceManager.getA2dpActiveDevice()).isNull(); + assertThat(mActiveDeviceManager.getLeAudioActiveDevice()).isNull(); } /** @@ -1721,7 +1719,6 @@ public class ActiveDeviceManagerTest { /** A wired audio device is disconnected. Check if falls back to connected A2DP. */ @Test - @EnableFlags(Flags.FLAG_ADM_FALLBACK_WHEN_WIRED_AUDIO_DISCONNECTED) public void wiredAudioDeviceDisconnected_setFallbackDevice() throws Exception { AudioDeviceInfo[] testDevices = createAudioDeviceInfoTestDevices(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java index 44956086f3..f3dab9d9be 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java @@ -81,6 +81,7 @@ import com.android.bluetooth.flags.Flags; import com.android.bluetooth.gatt.AdvertiseManagerNativeInterface; import com.android.bluetooth.gatt.DistanceMeasurementNativeInterface; import com.android.bluetooth.gatt.GattNativeInterface; +import com.android.bluetooth.le_audio.LeAudioService; import com.android.bluetooth.le_scan.PeriodicScanNativeInterface; import com.android.bluetooth.le_scan.ScanNativeInterface; import com.android.bluetooth.sdp.SdpManagerNativeInterface; @@ -93,6 +94,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -144,6 +146,7 @@ public class AdapterServiceTest { private @Mock Context mMockContext; private @Mock ApplicationInfo mMockApplicationInfo; + private @Mock LeAudioService mMockLeAudioService; private @Mock Resources mMockResources; private @Mock ProfileService mMockGattService; private @Mock ProfileService mMockService; @@ -185,6 +188,10 @@ public class AdapterServiceTest { private int mForegroundUserId; private TestLooper mLooper; + private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); + private final BluetoothDevice mDevice = TestUtils.getTestDevice(mAdapter, 0); + private final BluetoothDevice mDeviceTwo = TestUtils.getTestDevice(mAdapter, 2); + static void configureEnabledProfiles() { Log.e(TAG, "configureEnabledProfiles"); @@ -224,6 +231,12 @@ public class AdapterServiceTest { doReturn(mJniCallbacks).when(mNativeInterface).getCallbacks(); + doReturn(true).when(mMockLeAudioService).isAvailable(); + LeAudioService.setLeAudioService(mMockLeAudioService); + doReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED) + .when(mMockLeAudioService) + .getConnectionPolicy(any()); + AdapterNativeInterface.setInstance(mNativeInterface); BluetoothKeystoreNativeInterface.setInstance(mKeystoreNativeInterface); BluetoothQualityReportNativeInterface.setInstance(mQualityNativeInterface); @@ -351,6 +364,7 @@ public class AdapterServiceTest { // Restores the foregroundUserId to the ID prior to the test setup Utils.setForegroundUserId(mForegroundUserId); + LeAudioService.setLeAudioService(null); mAdapterService.cleanup(); mAdapterService.unregisterRemoteCallback(mIBluetoothCallback); AdapterNativeInterface.setInstance(null); @@ -1121,4 +1135,333 @@ public class AdapterServiceTest { } assertThat(mLooper.nextMessage()).isNull(); } + + InOrder prepareLeAudioWithConnectedDevices( + List<BluetoothDevice> devices, + int groupId, + boolean returnOnSetAutoActiveModeState, + int returnOnGetConnectionStateLeAudio, + int returnOnGetConnectionStateAdapter) { + doEnable(false); + + doReturn(groupId).when(mMockLeAudioService).getGroupId(any()); + + doReturn(returnOnGetConnectionStateLeAudio) + .when(mMockLeAudioService) + .getConnectionState(any()); + doReturn(returnOnGetConnectionStateAdapter) + .when(mNativeInterface) + .getConnectionState(any()); + + doReturn(returnOnSetAutoActiveModeState) + .when(mMockLeAudioService) + .setAutoActiveModeState(groupId, false); + doReturn(devices).when(mMockLeAudioService).getGroupDevices(groupId); + + return inOrder(mMockLeAudioService); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_GATT_CONNECT_FROM_THE_APPS_WITHOUT_MAKING_LEAUDIO_DEVICE_ACTIVE) + public void testGattConnectionToLeAudioDevice_whenDeviceIsNotConnected_success() { + int groupId = 1; + int getConnectionState_LeAudioService = BluetoothProfile.STATE_CONNECTED; + int getConnectionState_AdapterService = + BluetoothDevice.CONNECTION_STATE_ENCRYPTED_LE + | BluetoothDevice.CONNECTION_STATE_CONNECTED; + InOrder order = + prepareLeAudioWithConnectedDevices( + List.of(mDevice), + groupId, + true, + getConnectionState_LeAudioService, + getConnectionState_AdapterService); + + mAdapterService.notifyDirectLeGattClientConnect(1, mDevice); + + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, false); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(1); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_GATT_CONNECT_FROM_THE_APPS_WITHOUT_MAKING_LEAUDIO_DEVICE_ACTIVE) + public void testGattConnectionToLeAudioDevice_whenDeviceIsConnected_ignore() { + int groupId = 1; + int getConnectionState_LeAudioService = BluetoothProfile.STATE_CONNECTED; + int getConnectionState_AdapterService = + BluetoothDevice.CONNECTION_STATE_ENCRYPTED_LE + | BluetoothDevice.CONNECTION_STATE_CONNECTED; + InOrder order = + prepareLeAudioWithConnectedDevices( + List.of(mDevice), + groupId, + false, + getConnectionState_LeAudioService, + getConnectionState_AdapterService); + + mAdapterService.notifyDirectLeGattClientConnect(1, mDevice); + + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, false); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_GATT_CONNECT_FROM_THE_APPS_WITHOUT_MAKING_LEAUDIO_DEVICE_ACTIVE) + public void testGattConnectionToLeAudioDevice_whenLeAudioIsNotAllowed_ignore() { + int groupId = 1; + int getConnectionState_LeAudioService = BluetoothProfile.STATE_DISCONNECTED; + int getConnectionState_AdapterService = + BluetoothDevice.CONNECTION_STATE_ENCRYPTED_LE + | BluetoothDevice.CONNECTION_STATE_CONNECTED; + InOrder order = + prepareLeAudioWithConnectedDevices( + List.of(mDevice), + groupId, + false, + getConnectionState_LeAudioService, + getConnectionState_AdapterService); + + doReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) + .when(mMockLeAudioService) + .getConnectionPolicy(any()); + mAdapterService.notifyDirectLeGattClientConnect(1, mDevice); + + order.verify(mMockLeAudioService, never()).setAutoActiveModeState(groupId, false); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_GATT_CONNECT_FROM_THE_APPS_WITHOUT_MAKING_LEAUDIO_DEVICE_ACTIVE) + public void testGattConnectionToLeAudioDevice_failedToConnect() { + int groupId = 1; + int clientIf = 1; + + int getConnectionState_LeAudioService = BluetoothProfile.STATE_CONNECTED; + int getConnectionState_AdapterService = + BluetoothDevice.CONNECTION_STATE_ENCRYPTED_LE + | BluetoothDevice.CONNECTION_STATE_CONNECTED; + InOrder order = + prepareLeAudioWithConnectedDevices( + List.of(mDevice), + groupId, + true, + getConnectionState_LeAudioService, + getConnectionState_AdapterService); + + mAdapterService.notifyDirectLeGattClientConnect(clientIf, mDevice); + + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, false); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(1); + + mAdapterService.notifyGattClientConnectFailed(clientIf, mDevice); + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, true); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_GATT_CONNECT_FROM_THE_APPS_WITHOUT_MAKING_LEAUDIO_DEVICE_ACTIVE) + public void testGattConnectionToLeAudioDevice_triggerDisconnected() { + int groupId = 1; + int clientIf = 1; + + int getConnectionState_LeAudioService = BluetoothProfile.STATE_DISCONNECTED; + int getConnectionState_AdapterService = BluetoothDevice.CONNECTION_STATE_DISCONNECTED; + InOrder order = + prepareLeAudioWithConnectedDevices( + List.of(mDevice), + groupId, + true, + getConnectionState_LeAudioService, + getConnectionState_AdapterService); + InOrder orderNative = inOrder(mNativeInterface); + + mAdapterService.notifyDirectLeGattClientConnect(clientIf, mDevice); + + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, false); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(1); + + mAdapterService.notifyGattClientDisconnect(clientIf, mDevice); + orderNative.verify(mNativeInterface, never()).disconnectAcl(any(), anyInt()); + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, true); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_GATT_CONNECT_FROM_THE_APPS_WITHOUT_MAKING_LEAUDIO_DEVICE_ACTIVE) + public void testGattConnectionToLeAudioDevice_triggerDisconnecting() { + int groupId = 1; + int clientIf = 1; + int getConnectionState_LeAudioService = BluetoothProfile.STATE_CONNECTED; + int getConnectionState_AdapterService = + BluetoothDevice.CONNECTION_STATE_ENCRYPTED_LE + | BluetoothDevice.CONNECTION_STATE_CONNECTED; + InOrder order = + prepareLeAudioWithConnectedDevices( + List.of(mDevice), + groupId, + true, + getConnectionState_LeAudioService, + getConnectionState_AdapterService); + + InOrder orderNative = inOrder(mNativeInterface); + + mAdapterService.notifyDirectLeGattClientConnect(clientIf, mDevice); + + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, false); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(1); + + mAdapterService.notifyGattClientDisconnect(clientIf, mDevice); + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, true); + orderNative.verify(mNativeInterface).disconnectAcl(any(), eq(BluetoothDevice.TRANSPORT_LE)); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_GATT_CONNECT_FROM_THE_APPS_WITHOUT_MAKING_LEAUDIO_DEVICE_ACTIVE) + public void testGattConnectionToLeAudioDevice_connectingMultipleClients() { + int groupId = 1; + int clientIf = 1; + int clientIfTwo = 2; + + int getConnectionState_LeAudioService = BluetoothProfile.STATE_CONNECTED; + int getConnectionState_AdapterService = + BluetoothDevice.CONNECTION_STATE_ENCRYPTED_LE + | BluetoothDevice.CONNECTION_STATE_CONNECTED; + InOrder order = + prepareLeAudioWithConnectedDevices( + List.of(mDevice), + groupId, + true, + getConnectionState_LeAudioService, + getConnectionState_AdapterService); + + InOrder orderNative = inOrder(mNativeInterface); + + // Connect first client to device + mAdapterService.notifyDirectLeGattClientConnect(clientIf, mDevice); + + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, false); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(1); + + // Connect second client to device + mAdapterService.notifyDirectLeGattClientConnect(clientIfTwo, mDevice); + + order.verify(mMockLeAudioService, never()).setAutoActiveModeState(groupId, false); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(2); + + // Disconnect first client to device + mAdapterService.notifyGattClientDisconnect(clientIf, mDevice); + order.verify(mMockLeAudioService, never()).setAutoActiveModeState(groupId, true); + orderNative.verify(mNativeInterface, never()).disconnectAcl(any(), anyInt()); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(1); + + // Disconnect second client to device + mAdapterService.notifyGattClientDisconnect(clientIfTwo, mDevice); + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, true); + orderNative + .verify(mNativeInterface, times(1)) + .disconnectAcl(any(), eq(BluetoothDevice.TRANSPORT_LE)); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_GATT_CONNECT_FROM_THE_APPS_WITHOUT_MAKING_LEAUDIO_DEVICE_ACTIVE) + public void testGattConnectionToLeAudioDevice_connectingMultipleDevicesInSameGroup() { + int groupId = 1; + int clientIf = 1; + int clientIfTwo = 2; + + int getConnectionState_LeAudioService = BluetoothProfile.STATE_CONNECTED; + int getConnectionState_AdapterService = + BluetoothDevice.CONNECTION_STATE_ENCRYPTED_LE + | BluetoothDevice.CONNECTION_STATE_CONNECTED; + InOrder order = + prepareLeAudioWithConnectedDevices( + List.of(mDevice, mDeviceTwo), + groupId, + true, + getConnectionState_LeAudioService, + getConnectionState_AdapterService); + + InOrder orderNative = inOrder(mNativeInterface); + + // Connecting device one + when(mMockLeAudioService.setAutoActiveModeState(groupId, false)).thenReturn(true); + mAdapterService.notifyDirectLeGattClientConnect(clientIf, mDevice); + + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, false); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(1); + + // Connecting device two + mAdapterService.notifyDirectLeGattClientConnect(clientIfTwo, mDeviceTwo); + + order.verify(mMockLeAudioService, never()).setAutoActiveModeState(groupId, false); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(2); + + // Disconnect first device + mAdapterService.notifyGattClientDisconnect(clientIf, mDevice); + order.verify(mMockLeAudioService, never()).setAutoActiveModeState(groupId, true); + orderNative.verify(mNativeInterface, never()).disconnectAcl(any(), anyInt()); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(1); + + // Disconnect second device + mAdapterService.notifyGattClientDisconnect(clientIfTwo, mDeviceTwo); + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, true); + orderNative + .verify(mNativeInterface, times(2)) + .disconnectAcl(any(), eq(BluetoothDevice.TRANSPORT_LE)); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_ALLOW_GATT_CONNECT_FROM_THE_APPS_WITHOUT_MAKING_LEAUDIO_DEVICE_ACTIVE) + public void testGattConnectionToLeAudioDevice_remoteSwitchesToActiveBeforeDisconnect() { + int groupId = 1; + int clientIf = 1; + int clientIfTwo = 2; + + int getConnectionState_LeAudioService = BluetoothProfile.STATE_CONNECTED; + int getConnectionState_AdapterService = + BluetoothDevice.CONNECTION_STATE_ENCRYPTED_LE + | BluetoothDevice.CONNECTION_STATE_CONNECTED; + InOrder order = + prepareLeAudioWithConnectedDevices( + List.of(mDevice, mDeviceTwo), + groupId, + true, + getConnectionState_LeAudioService, + getConnectionState_AdapterService); + + InOrder orderNative = inOrder(mNativeInterface); + + // Connecting device one + when(mMockLeAudioService.setAutoActiveModeState(groupId, false)).thenReturn(true); + mAdapterService.notifyDirectLeGattClientConnect(clientIf, mDevice); + + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, false); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(1); + + // Connecting device two + mAdapterService.notifyDirectLeGattClientConnect(clientIfTwo, mDeviceTwo); + + order.verify(mMockLeAudioService, never()).setAutoActiveModeState(groupId, false); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(2); + + // Remote switches to Active + when(mMockLeAudioService.isAutoActiveModeEnabled(groupId)).thenReturn(true); + + // Disconnect first device + mAdapterService.notifyGattClientDisconnect(clientIf, mDevice); + order.verify(mMockLeAudioService, never()).setAutoActiveModeState(groupId, true); + orderNative.verify(mNativeInterface, never()).disconnectAcl(any(), anyInt()); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode.size()).isEqualTo(1); + + // Disconnect second device + mAdapterService.notifyGattClientDisconnect(clientIfTwo, mDeviceTwo); + + // Verify devices will not be disconnected + order.verify(mMockLeAudioService).setAutoActiveModeState(groupId, true); + orderNative.verify(mNativeInterface, never()).disconnectAcl(any(), anyInt()); + assertThat(mAdapterService.mLeGattClientsControllingAutoActiveMode).isEmpty(); + } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java index f24c1ff2aa..e274ac6eed 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java @@ -40,7 +40,6 @@ import com.android.bluetooth.TestUtils; import com.android.bluetooth.Utils; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -219,7 +218,7 @@ public class BondStateMachineTest { RemoteDevices.DeviceProperties testDeviceProperties = mRemoteDevices.addDeviceProperties(TEST_BT_ADDR_BYTES); - testDeviceProperties.mUuids = TEST_UUIDS; + testDeviceProperties.mUuidsBrEdr = TEST_UUIDS; BluetoothDevice testDevice = testDeviceProperties.getDevice(); assertThat(testDevice).isNotNull(); @@ -229,7 +228,7 @@ public class BondStateMachineTest { bondingMsg.arg2 = AbstractionLayer.BT_STATUS_RMT_DEV_DOWN; mBondStateMachine.sendMessage(bondingMsg); - pendingDeviceProperties.mUuids = TEST_UUIDS; + pendingDeviceProperties.mUuidsBrEdr = TEST_UUIDS; Message uuidUpdateMsg = mBondStateMachine.obtainMessage(BondStateMachine.UUID_UPDATE); uuidUpdateMsg.obj = pendingDevice; @@ -587,7 +586,7 @@ public class BondStateMachineTest { // Properties are removed when bond is removed if (newState != BluetoothDevice.BOND_NONE) { - Assert.assertEquals(expectedNewState, mDeviceProperties.getBondState()); + assertThat(mDeviceProperties.getBondState()).isEqualTo(expectedNewState); } // Check for bond state Intent status. @@ -635,7 +634,7 @@ public class BondStateMachineTest { } if (uuids != null) { // Add dummy UUID for the device. - mDeviceProperties.mUuids = TEST_UUIDS; + mDeviceProperties.mUuidsBrEdr = TEST_UUIDS; } testSendIntentCase( oldState, @@ -739,16 +738,17 @@ public class BondStateMachineTest { private void verifyBondStateChangeIntent(int oldState, int newState, Intent intent) { assertThat(intent).isNotNull(); - Assert.assertEquals(BluetoothDevice.ACTION_BOND_STATE_CHANGED, intent.getAction()); - Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); - Assert.assertEquals(newState, intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1)); - Assert.assertEquals( - oldState, intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1)); + assertThat(intent.getAction()).isEqualTo(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + assertThat(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class)) + .isEqualTo(mDevice); + assertThat(intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1)).isEqualTo(newState); + assertThat(intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1)) + .isEqualTo(oldState); if (newState == BOND_NONE) { - Assert.assertEquals( - TEST_BOND_REASON, intent.getIntExtra(BluetoothDevice.EXTRA_UNBOND_REASON, -1)); + assertThat(intent.getIntExtra(BluetoothDevice.EXTRA_UNBOND_REASON, -1)) + .isEqualTo(TEST_BOND_REASON); } else { - Assert.assertEquals(-1, intent.getIntExtra(BluetoothDevice.EXTRA_UNBOND_REASON, -1)); + assertThat(intent.getIntExtra(BluetoothDevice.EXTRA_UNBOND_REASON, -1)).isEqualTo(-1); } } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java index aa49e0d0a7..a46a805941 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java @@ -34,7 +34,6 @@ import com.google.common.hash.Funnels; import com.google.protobuf.InvalidProtocolBufferException; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -132,15 +131,15 @@ public class MetricsLoggerTest { BluetoothLog.Builder metricsBuilder = BluetoothLog.newBuilder(); MetricsLogger.dumpProto(metricsBuilder); BluetoothLog metricsProto = metricsBuilder.build(); - Assert.assertEquals(1, metricsProto.getProfileConnectionStatsCount()); + assertThat(metricsProto.getProfileConnectionStatsCount()).isEqualTo(1); ProfileConnectionStats profileUsageStatsAvrcp = metricsProto.getProfileConnectionStats(0); - Assert.assertEquals(ProfileId.AVRCP, profileUsageStatsAvrcp.getProfileId()); - Assert.assertEquals(1, profileUsageStatsAvrcp.getNumTimesConnected()); + assertThat(profileUsageStatsAvrcp.getProfileId()).isEqualTo(ProfileId.AVRCP); + assertThat(profileUsageStatsAvrcp.getNumTimesConnected()).isEqualTo(1); // Verify that MetricsLogger's internal state is cleared after a dump BluetoothLog.Builder metricsBuilderAfterDump = BluetoothLog.newBuilder(); MetricsLogger.dumpProto(metricsBuilderAfterDump); BluetoothLog metricsProtoAfterDump = metricsBuilderAfterDump.build(); - Assert.assertEquals(0, metricsProtoAfterDump.getProfileConnectionStatsCount()); + assertThat(metricsProtoAfterDump.getProfileConnectionStatsCount()).isEqualTo(0); } /** Test whether multiple profile's connection events can be logged interleaving */ @@ -152,20 +151,20 @@ public class MetricsLoggerTest { BluetoothLog.Builder metricsBuilder = BluetoothLog.newBuilder(); MetricsLogger.dumpProto(metricsBuilder); BluetoothLog metricsProto = metricsBuilder.build(); - Assert.assertEquals(2, metricsProto.getProfileConnectionStatsCount()); + assertThat(metricsProto.getProfileConnectionStatsCount()).isEqualTo(2); Map<ProfileId, ProfileConnectionStats> profileConnectionCountMap = getProfileUsageStatsMap(metricsProto.getProfileConnectionStatsList()); assertThat(profileConnectionCountMap).containsKey(ProfileId.AVRCP); - Assert.assertEquals( - 2, profileConnectionCountMap.get(ProfileId.AVRCP).getNumTimesConnected()); + assertThat(profileConnectionCountMap.get(ProfileId.AVRCP).getNumTimesConnected()) + .isEqualTo(2); assertThat(profileConnectionCountMap).containsKey(ProfileId.HEADSET); - Assert.assertEquals( - 1, profileConnectionCountMap.get(ProfileId.HEADSET).getNumTimesConnected()); + assertThat(profileConnectionCountMap.get(ProfileId.HEADSET).getNumTimesConnected()) + .isEqualTo(1); // Verify that MetricsLogger's internal state is cleared after a dump BluetoothLog.Builder metricsBuilderAfterDump = BluetoothLog.newBuilder(); MetricsLogger.dumpProto(metricsBuilderAfterDump); BluetoothLog metricsProtoAfterDump = metricsBuilderAfterDump.build(); - Assert.assertEquals(0, metricsProtoAfterDump.getProfileConnectionStatsCount()); + assertThat(metricsProtoAfterDump.getProfileConnectionStatsCount()).isEqualTo(0); } private static Map<ProfileId, ProfileConnectionStats> getProfileUsageStatsMap( @@ -183,17 +182,17 @@ public class MetricsLoggerTest { mTestableMetricsLogger.cacheCount(2, 5); mTestableMetricsLogger.drainBufferedCounters(); - Assert.assertEquals(20L, mTestableMetricsLogger.mTestableCounters.get(1).longValue()); - Assert.assertEquals(5L, mTestableMetricsLogger.mTestableCounters.get(2).longValue()); + assertThat(mTestableMetricsLogger.mTestableCounters.get(1).longValue()).isEqualTo(20L); + assertThat(mTestableMetricsLogger.mTestableCounters.get(2).longValue()).isEqualTo(5L); mTestableMetricsLogger.cacheCount(1, 3); mTestableMetricsLogger.cacheCount(2, 5); mTestableMetricsLogger.cacheCount(2, 5); mTestableMetricsLogger.cacheCount(3, 1); mTestableMetricsLogger.drainBufferedCounters(); - Assert.assertEquals(3L, mTestableMetricsLogger.mTestableCounters.get(1).longValue()); - Assert.assertEquals(10L, mTestableMetricsLogger.mTestableCounters.get(2).longValue()); - Assert.assertEquals(1L, mTestableMetricsLogger.mTestableCounters.get(3).longValue()); + assertThat(mTestableMetricsLogger.mTestableCounters.get(1).longValue()).isEqualTo(3L); + assertThat(mTestableMetricsLogger.mTestableCounters.get(2).longValue()).isEqualTo(10L); + assertThat(mTestableMetricsLogger.mTestableCounters.get(3).longValue()).isEqualTo(1L); } @Test @@ -207,8 +206,8 @@ public class MetricsLoggerTest { assertThat(mTestableMetricsLogger.mTestableCounters).doesNotContainKey(1); assertThat(mTestableMetricsLogger.mTestableCounters).doesNotContainKey(3); - Assert.assertEquals( - Long.MAX_VALUE, mTestableMetricsLogger.mTestableCounters.get(2).longValue()); + assertThat(mTestableMetricsLogger.mTestableCounters.get(2).longValue()) + .isEqualTo(Long.MAX_VALUE); } @Test @@ -218,9 +217,9 @@ public class MetricsLoggerTest { mTestableMetricsLogger.cacheCount(2, Long.MAX_VALUE); mTestableMetricsLogger.close(); - Assert.assertEquals(1, mTestableMetricsLogger.mTestableCounters.get(1).longValue()); - Assert.assertEquals( - Long.MAX_VALUE, mTestableMetricsLogger.mTestableCounters.get(2).longValue()); + assertThat(mTestableMetricsLogger.mTestableCounters.get(1).longValue()).isEqualTo(1); + assertThat(mTestableMetricsLogger.mTestableCounters.get(2).longValue()) + .isEqualTo(Long.MAX_VALUE); } @Test @@ -244,10 +243,8 @@ public class MetricsLoggerTest { for (Map.Entry<String, String> entry : SANITIZED_DEVICE_NAME_MAP.entrySet()) { String deviceName = entry.getKey(); String sha256 = MetricsLogger.getSha256String(entry.getValue()); - Assert.assertEquals( - deviceName, - sha256, - mTestableMetricsLogger.logAllowlistedDeviceNameHash(1, deviceName)); + assertThat(mTestableMetricsLogger.logAllowlistedDeviceNameHash(1, deviceName)) + .isEqualTo(sha256); } } @@ -263,7 +260,7 @@ public class MetricsLoggerTest { BluetoothRemoteDeviceInformation bluetoothRemoteDeviceInformation = BluetoothRemoteDeviceInformation.parseFrom(remoteDeviceInformationBytes); int oui = (0 << 16) | (1 << 8) | 2; // OUI from the above mac address - Assert.assertEquals(bluetoothRemoteDeviceInformation.getOui(), oui); + assertThat(bluetoothRemoteDeviceInformation.getOui()).isEqualTo(oui); } catch (InvalidProtocolBufferException e) { assertThat(e.getMessage()).isNull(); // test failure here @@ -278,7 +275,7 @@ public class MetricsLoggerTest { String actualMedicalDeviceSha256 = mTestableMetricsLogger.getAllowlistedDeviceNameHash(deviceName, true); - Assert.assertEquals(expectMedicalDeviceSha256, actualMedicalDeviceSha256); + assertThat(actualMedicalDeviceSha256).isEqualTo(expectMedicalDeviceSha256); } @Test @@ -289,13 +286,13 @@ public class MetricsLoggerTest { String actualMedicalDeviceSha256 = mTestableMetricsLogger.getAllowlistedDeviceNameHash(deviceName, false); - Assert.assertEquals(expectMedicalDeviceSha256, actualMedicalDeviceSha256); + assertThat(actualMedicalDeviceSha256).isEqualTo(expectMedicalDeviceSha256); } @Test public void uploadEmptyDeviceName() throws IOException { initTestingBloomfilter(); - Assert.assertEquals("", mTestableMetricsLogger.logAllowlistedDeviceNameHash(1, "")); + assertThat(mTestableMetricsLogger.logAllowlistedDeviceNameHash(1, "")).isEmpty(); } private void initTestingBloomfilter() throws IOException { diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java index 2e7225e74b..f02ed4098d 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java @@ -44,10 +44,8 @@ import com.android.bluetooth.hfpclient.NativeInterface; import com.android.bluetooth.hid.HidDeviceNativeInterface; import com.android.bluetooth.hid.HidHostNativeInterface; import com.android.bluetooth.le_audio.LeAudioNativeInterface; -import com.android.bluetooth.pan.PanNativeInterface; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -89,7 +87,6 @@ public class ProfileServiceTest { @Mock private HearingAidNativeInterface mHearingAidNativeInterface; @Mock private HidDeviceNativeInterface mHidDeviceNativeInterface; @Mock private HidHostNativeInterface mHidHostNativeInterface; - @Mock private PanNativeInterface mPanNativeInterface; @Mock private LeAudioNativeInterface mLeAudioInterface; private void setProfileState(int profile, int state) { @@ -121,9 +118,7 @@ public class ProfileServiceTest { .collect(Collectors.groupingBy(Object::getClass, Collectors.counting())); counts.forEach( - (clazz, count) -> - Assert.assertEquals( - clazz.getSimpleName(), (long) invocationNumber, count.longValue())); + (clazz, count) -> assertThat((long) invocationNumber).isEqualTo(count.longValue())); } @Before @@ -155,6 +150,7 @@ public class ProfileServiceTest { && profile != BluetoothProfile.VOLUME_CONTROL && profile != BluetoothProfile.CSIP_SET_COORDINATOR && profile != BluetoothProfile.GATT + && profile != BluetoothProfile.PAN && profile != BluetoothProfile.A2DP) .toArray(); TestUtils.setAdapterService(mAdapterService); @@ -169,7 +165,6 @@ public class ProfileServiceTest { HearingAidNativeInterface.setInstance(mHearingAidNativeInterface); HidDeviceNativeInterface.setInstance(mHidDeviceNativeInterface); HidHostNativeInterface.setInstance(mHidHostNativeInterface); - PanNativeInterface.setInstance(mPanNativeInterface); LeAudioNativeInterface.setInstance(mLeAudioInterface); } @@ -187,7 +182,6 @@ public class ProfileServiceTest { HearingAidNativeInterface.setInstance(null); HidDeviceNativeInterface.setInstance(null); HidHostNativeInterface.setInstance(null); - PanNativeInterface.setInstance(null); LeAudioNativeInterface.setInstance(null); } @@ -236,7 +230,7 @@ public class ProfileServiceTest { List<ProfileService> startedArguments = starts.getAllValues(); List<ProfileService> stoppedArguments = stops.getAllValues(); - Assert.assertEquals(startedArguments.size(), stoppedArguments.size()); + assertThat(startedArguments).hasSize(stoppedArguments.size()); for (ProfileService service : startedArguments) { assertThat(stoppedArguments).contains(service); stoppedArguments.remove(service); @@ -263,7 +257,7 @@ public class ProfileServiceTest { verify(mAdapterService, times(NUM_REPEATS * profileNumber + i + 1)) .onProfileServiceStateChanged( stop.capture(), eq(BluetoothAdapter.STATE_OFF)); - Assert.assertEquals(start.getValue(), stop.getValue()); + assertThat(start.getValue()).isEqualTo(stop.getValue()); } profileNumber += 1; } @@ -287,7 +281,7 @@ public class ProfileServiceTest { ArgumentCaptor<ProfileService> stop = ArgumentCaptor.forClass(ProfileService.class); verify(mAdapterService, times(NUM_REPEATS * profileNumber + i + 1)) .removeProfile(stop.capture()); - Assert.assertEquals(start.getValue(), stop.getValue()); + assertThat(start.getValue()).isEqualTo(stop.getValue()); } profileNumber += 1; } diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java index 28747da680..f9d4d0f045 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java @@ -33,7 +33,6 @@ import com.android.bluetooth.flags.Flags; import com.android.bluetooth.hfp.HeadsetHalConstants; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -123,12 +122,12 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify that user can get battery level after the update assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); // Verify that update same battery level for the same device does not trigger intent mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel, /* fromBas= */ false); @@ -140,12 +139,12 @@ public class RemoteDevicesTest { verify(mAdapterService, times(2)) .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); // Verify that user can get battery level after the update - Assert.assertEquals( - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); verifyNoMoreInteractions(mAdapterService); } @@ -209,12 +208,12 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify that user can get battery level after the update assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); // Verify that resetting battery level changes it back to BluetoothDevice // .BATTERY_LEVEL_UNKNOWN @@ -225,12 +224,11 @@ public class RemoteDevicesTest { mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent( mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify value is reset in properties assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), - BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); // Verify no intent is sent after second reset mRemoteDevices.resetBatteryLevel(mDevice1, /* fromBas= */ false); @@ -242,7 +240,7 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); verifyNoMoreInteractions(mAdapterService); } @@ -260,12 +258,12 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify that user can get battery level after the update assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); // Verify that resetting battery level changes it back to BluetoothDevice // .BATTERY_LEVEL_UNKNOWN @@ -279,12 +277,11 @@ public class RemoteDevicesTest { mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent( mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify value is reset in properties assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), - BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent again mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel, /* fromBas= */ false); @@ -292,7 +289,7 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); verifyNoMoreInteractions(mAdapterService); } @@ -313,12 +310,12 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify that user can get battery level after the update assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); // Verify that battery level is not reset mRemoteDevices.onHeadsetConnectionStateChanged( @@ -327,8 +324,8 @@ public class RemoteDevicesTest { BluetoothProfile.STATE_DISCONNECTED); assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - batteryLevel, mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); // Recover the previous battery service if exists clearBatteryServiceForTesting(oldBatteryService); @@ -350,12 +347,12 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify that user can get battery level after the update assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - batteryLevel, mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); // Verify that when device is completely disconnected, RemoteDevices reset battery level to // BluetoothDevice.BATTERY_LEVEL_UNKNOWN @@ -376,17 +373,15 @@ public class RemoteDevicesTest { mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN, mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2)); - Assert.assertEquals( - BLUETOOTH_CONNECT, - mStringArgument.getAllValues().get(mStringArgument.getAllValues().size() - 2)); - Assert.assertEquals( - BluetoothDevice.ACTION_ACL_DISCONNECTED, mIntentArgument.getValue().getAction()); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getAllValues().get(mStringArgument.getAllValues().size() - 2)) + .isEqualTo(BLUETOOTH_CONNECT); + assertThat(mIntentArgument.getValue().getAction()) + .isEqualTo(BluetoothDevice.ACTION_ACL_DISCONNECTED); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify value is reset in properties assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - BluetoothDevice.BATTERY_LEVEL_UNKNOWN, - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent again mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel, /* fromBas= */ false); @@ -394,7 +389,7 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); } @Test @@ -411,7 +406,7 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); } @Test @@ -444,7 +439,7 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, 42, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); } @Test @@ -471,107 +466,102 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, 60, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); } @Test public void testGetBatteryLevelFromXEventVsc() { - Assert.assertEquals(42, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 8))); - Assert.assertEquals( - 100, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(10, 11))); - Assert.assertEquals( - BluetoothDevice.BATTERY_LEVEL_UNKNOWN, - RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(1, 1))); - Assert.assertEquals( - BluetoothDevice.BATTERY_LEVEL_UNKNOWN, - RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 1))); - Assert.assertEquals( - BluetoothDevice.BATTERY_LEVEL_UNKNOWN, - RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(-1, 1))); - Assert.assertEquals( - BluetoothDevice.BATTERY_LEVEL_UNKNOWN, - RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(-1, -1))); + assertThat(RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 8))).isEqualTo(42); + assertThat(RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(10, 11))) + .isEqualTo(100); + assertThat(RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(1, 1))) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + assertThat(RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 1))) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + assertThat(RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(-1, 1))) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + assertThat(RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(-1, -1))) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); } @Test public void testGetBatteryLevelFromAppleBatteryVsc() { - Assert.assertEquals( - 10, - RemoteDevices.getBatteryLevelFromAppleBatteryVsc( - new Object[] { - 1, - BluetoothHeadset - .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, - 0 - })); - Assert.assertEquals( - 100, - RemoteDevices.getBatteryLevelFromAppleBatteryVsc( - new Object[] { - 1, - BluetoothHeadset - .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, - 9 - })); - Assert.assertEquals( - 60, - RemoteDevices.getBatteryLevelFromAppleBatteryVsc( - new Object[] { - 3, - BluetoothHeadset - .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, - 5, - 2, - 1, - 3, - 10 - })); - Assert.assertEquals( - BluetoothDevice.BATTERY_LEVEL_UNKNOWN, - RemoteDevices.getBatteryLevelFromAppleBatteryVsc( - new Object[] { - 3, - BluetoothHeadset - .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, - 5, - 2, - 1, - 3 - })); - Assert.assertEquals( - BluetoothDevice.BATTERY_LEVEL_UNKNOWN, - RemoteDevices.getBatteryLevelFromAppleBatteryVsc( - new Object[] { - 1, - BluetoothHeadset - .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, - 10 - })); - Assert.assertEquals( - BluetoothDevice.BATTERY_LEVEL_UNKNOWN, - RemoteDevices.getBatteryLevelFromAppleBatteryVsc( - new Object[] { - 1, - BluetoothHeadset - .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, - -1 - })); - Assert.assertEquals( - BluetoothDevice.BATTERY_LEVEL_UNKNOWN, - RemoteDevices.getBatteryLevelFromAppleBatteryVsc( - new Object[] { - 1, - BluetoothHeadset - .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, - "5" - })); - Assert.assertEquals( - BluetoothDevice.BATTERY_LEVEL_UNKNOWN, - RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, 35, 37})); - Assert.assertEquals( - BluetoothDevice.BATTERY_LEVEL_UNKNOWN, - RemoteDevices.getBatteryLevelFromAppleBatteryVsc( - new Object[] {1, "WRONG", "WRONG"})); + assertThat( + RemoteDevices.getBatteryLevelFromAppleBatteryVsc( + new Object[] { + 1, + BluetoothHeadset + .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, + 0 + })) + .isEqualTo(10); + assertThat( + RemoteDevices.getBatteryLevelFromAppleBatteryVsc( + new Object[] { + 1, + BluetoothHeadset + .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, + 9 + })) + .isEqualTo(100); + assertThat( + RemoteDevices.getBatteryLevelFromAppleBatteryVsc( + new Object[] { + 3, + BluetoothHeadset + .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, + 5, + 2, + 1, + 3, + 10 + })) + .isEqualTo(60); + assertThat( + RemoteDevices.getBatteryLevelFromAppleBatteryVsc( + new Object[] { + 3, + BluetoothHeadset + .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, + 5, + 2, + 1, + 3 + })) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + assertThat( + RemoteDevices.getBatteryLevelFromAppleBatteryVsc( + new Object[] { + 1, + BluetoothHeadset + .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, + 10 + })) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + assertThat( + RemoteDevices.getBatteryLevelFromAppleBatteryVsc( + new Object[] { + 1, + BluetoothHeadset + .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, + -1 + })) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + assertThat( + RemoteDevices.getBatteryLevelFromAppleBatteryVsc( + new Object[] { + 1, + BluetoothHeadset + .VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, + "5" + })) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + assertThat(RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, 35, 37})) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + assertThat( + RemoteDevices.getBatteryLevelFromAppleBatteryVsc( + new Object[] {1, "WRONG", "WRONG"})) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); } @Test @@ -587,12 +577,12 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify that user can get battery level after the update assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); // Verify that resetting battery level changes it back to BluetoothDevice // .BATTERY_LEVEL_UNKNOWN @@ -607,13 +597,12 @@ public class RemoteDevicesTest { mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent( mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify value is reset in properties assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), - BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent again mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel, /* fromBas= */ false); @@ -621,7 +610,7 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); verifyNoMoreInteractions(mAdapterService); } @@ -642,12 +631,12 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify that user can get battery level after the update assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); // Verify that battery level is not reset. mRemoteDevices.onHeadsetClientConnectionStateChanged( @@ -656,8 +645,8 @@ public class RemoteDevicesTest { BluetoothProfile.STATE_DISCONNECTED); assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - batteryLevel, mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); clearBatteryServiceForTesting(oldBatteryService); @@ -681,12 +670,12 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify that user can get battery level after the update assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); // Verify that updating battery service overrides hfp battery level mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel2, /* fromBas= */ true); @@ -697,8 +686,8 @@ public class RemoteDevicesTest { // Verify that the battery level isn't reset mRemoteDevices.resetBatteryLevel(mDevice1, /* fromBas= */ true); - Assert.assertEquals( - batteryLevel, mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); verify(mAdapterService, times(3)) .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); @@ -725,12 +714,12 @@ public class RemoteDevicesTest { .sendBroadcast( mIntentArgument.capture(), mStringArgument.capture(), any(Bundle.class)); verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); // Verify that user can get battery level after the update assertThat(mRemoteDevices.getDeviceProperties(mDevice1)).isNotNull(); - Assert.assertEquals( - mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); // Verify that updating battery service doesn't send broadcast mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel, /* fromBas= */ true); @@ -738,8 +727,8 @@ public class RemoteDevicesTest { // Verify that the battery level isn't reset mRemoteDevices.resetBatteryLevel(mDevice1, /* fromBas= */ true); - Assert.assertEquals( - batteryLevel, mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel()) + .isEqualTo(batteryLevel); verifyNoMoreInteractions(mAdapterService); clearBatteryServiceForTesting(oldBatteryService); @@ -763,7 +752,7 @@ public class RemoteDevicesTest { mDevice1, RemoteDevices.batteryChargeIndicatorToPercentge(batteryLevel), mIntentArgument); - Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue()); + assertThat(mStringArgument.getValue()).isEqualTo(BLUETOOTH_CONNECT); } @Test @@ -775,7 +764,7 @@ public class RemoteDevicesTest { mRemoteDevices.addDeviceProperties(Utils.getBytesFromAddress(TEST_BT_ADDR_1)); DeviceProperties prop2 = mRemoteDevices.addDeviceProperties(Utils.getBytesFromAddress(TEST_BT_ADDR_1)); - Assert.assertEquals(prop2, prop1); + assertThat(prop1).isEqualTo(prop2); } @Test @@ -796,9 +785,8 @@ public class RemoteDevicesTest { deviceProp.setHfAudioPolicyForRemoteAg(policies); // Verify that the audio policy properties are set and get properly - Assert.assertEquals( - policies, - mRemoteDevices.getDeviceProperties(mDevice1).getHfAudioPolicyForRemoteAg()); + assertThat(mRemoteDevices.getDeviceProperties(mDevice1).getHfAudioPolicyForRemoteAg()) + .isEqualTo(policies); } @Test @@ -845,14 +833,15 @@ public class RemoteDevicesTest { private static void verifyBatteryLevelChangedIntent( BluetoothDevice device, int batteryLevel, Intent intent) { - Assert.assertEquals(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, intent.getAction()); - Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); - Assert.assertEquals( - batteryLevel, intent.getIntExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, -15)); - Assert.assertEquals( - Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT - | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, - intent.getFlags()); + assertThat(intent.getAction()).isEqualTo(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED); + assertThat(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class)) + .isEqualTo(device); + assertThat(intent.getIntExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, -15)) + .isEqualTo(batteryLevel); + assertThat(intent.getFlags()) + .isEqualTo( + Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); } private static Object[] getXEventArray(int batteryLevel, int numLevels) { diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java index c616c90aa7..593e422776 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java @@ -39,7 +39,6 @@ import com.android.bluetooth.a2dp.A2dpService; import com.android.bluetooth.hfp.HeadsetService; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -122,7 +121,7 @@ public class SilenceDeviceManagerTest { // Set silence state and check whether state changed successfully assertThat(mSilenceDeviceManager.setSilenceMode(mTestDevice, enableSilence)).isTrue(); TestUtils.waitForLooperToFinishScheduledTask(mLooper); - Assert.assertEquals(enableSilence, mSilenceDeviceManager.getSilenceMode(mTestDevice)); + assertThat(mSilenceDeviceManager.getSilenceMode(mTestDevice)).isEqualTo(enableSilence); // Check for silence state changed intent if (wasSilenced != enableSilence) { @@ -163,8 +162,9 @@ public class SilenceDeviceManagerTest { } void verifySilenceStateIntent(Intent intent) { - Assert.assertEquals(BluetoothDevice.ACTION_SILENCE_MODE_CHANGED, intent.getAction()); - Assert.assertEquals(mTestDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); + assertThat(intent.getAction()).isEqualTo(BluetoothDevice.ACTION_SILENCE_MODE_CHANGED); + assertThat(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class)) + .isEqualTo(mTestDevice); } /** Helper to indicate A2dp connected for a device. */ diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreServiceTest.java index 668743ad31..93ce02664c 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreServiceTest.java @@ -155,14 +155,6 @@ public final class BluetoothKeystoreServiceTest { "aec555555555555555555555555555555555555555555555"); } - private boolean doCompareKeySet(Map<String, String> map1, Map<String, String> map2) { - return map1.keySet().equals(map2.keySet()); - } - - private boolean doCompareMap(Map<String, String> map1, Map<String, String> map2) { - return map1.equals(map2); - } - private boolean parseConfigFile(String filePathString) { try { mBluetoothKeystoreService.parseConfigFile(filePathString); @@ -205,11 +197,7 @@ public final class BluetoothKeystoreServiceTest { // load config file. assertThat(parseConfigFile(CONFIG_FILE_PATH)).isTrue(); // make sure it is same with createNameDecryptKeyResult - assertThat( - doCompareMap( - mNameDecryptKeyResult, - mBluetoothKeystoreService.getNameDecryptKey())) - .isTrue(); + assertThat(mBluetoothKeystoreService.getNameDecryptKey()).isEqualTo(mNameDecryptKeyResult); } @Test @@ -219,11 +207,8 @@ public final class BluetoothKeystoreServiceTest { // Wait for encryption to complete mBluetoothKeystoreService.stopThread(); - assertThat( - doCompareKeySet( - mNameDecryptKeyResult, - mBluetoothKeystoreService.getNameEncryptKey())) - .isTrue(); + assertThat(mBluetoothKeystoreService.getNameDecryptKey().keySet()) + .containsExactlyElementsIn(mNameDecryptKeyResult.keySet()); } @Test @@ -238,11 +223,7 @@ public final class BluetoothKeystoreServiceTest { // Wait for encryption to complete mBluetoothKeystoreService.stopThread(); - assertThat( - doCompareMap( - mNameDecryptKeyResult, - mBluetoothKeystoreService.getNameDecryptKey())) - .isTrue(); + assertThat(mBluetoothKeystoreService.getNameDecryptKey()).isEqualTo(mNameDecryptKeyResult); } @Test @@ -279,11 +260,7 @@ public final class BluetoothKeystoreServiceTest { // remove hash data avoid interfering result. mBluetoothKeystoreService.getNameDecryptKey().remove(CONFIG_FILE_PREFIX); - assertThat( - doCompareMap( - mNameDecryptKeyResult, - mBluetoothKeystoreService.getNameDecryptKey())) - .isTrue(); + assertThat(mBluetoothKeystoreService.getNameDecryptKey()).isEqualTo(mNameDecryptKeyResult); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java index bcaccfd94f..2753ee7b17 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java @@ -18,6 +18,7 @@ package com.android.bluetooth.btservice.storage; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; @@ -152,18 +153,15 @@ public final class DatabaseManagerTest { restartDatabaseManagerHelper(); for (int id = 0; id < BluetoothProfile.MAX_PROFILE_ID; id++) { - Assert.assertEquals( - BluetoothProfile.CONNECTION_POLICY_UNKNOWN, - mDatabaseManager.getProfileConnectionPolicy(mTestDevice, id)); + assertThat(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, id)) + .isEqualTo(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); } - Assert.assertEquals( - BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, - mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice)); + assertThat(mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice)) + .isEqualTo(BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN); - Assert.assertEquals( - BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN, - mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice)); + assertThat(mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice)) + .isEqualTo(BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN); for (int id = 0; id < MAX_META_ID; id++) { assertThat(mDatabaseManager.getCustomMeta(mTestDevice, id)).isNull(); @@ -340,11 +338,11 @@ public final class DatabaseManagerTest { List<Metadata> list = mDatabase.load(); // Check number of metadata in the database - Assert.assertEquals(1, list.size()); + assertThat(list).hasSize(1); // Check whether the device is in database Metadata checkData = list.get(0); - Assert.assertEquals(TEST_BT_ADDR, checkData.getAddress()); + assertThat(checkData.getAddress()).isEqualTo(TEST_BT_ADDR); mDatabaseManager.factoryReset(); mDatabaseManager.mMetadataCache.clear(); @@ -388,13 +386,13 @@ public final class DatabaseManagerTest { // Check number of metadata in the database List<Metadata> list = mDatabase.load(); // OTHER_BT_ADDR1 and OTHER_BT_ADDR2 should still in database - Assert.assertEquals(2, list.size()); + assertThat(list).hasSize(2); // Check whether the devices are in the database Metadata checkData1 = list.get(0); - Assert.assertEquals(OTHER_BT_ADDR2, checkData1.getAddress()); + assertThat(checkData1.getAddress()).isEqualTo(OTHER_BT_ADDR2); Metadata checkData2 = list.get(1); - Assert.assertEquals(OTHER_BT_ADDR1, checkData2.getAddress()); + assertThat(checkData2.getAddress()).isEqualTo(OTHER_BT_ADDR1); mDatabaseManager.factoryReset(); mDatabaseManager.mMetadataCache.clear(); @@ -522,7 +520,7 @@ public final class DatabaseManagerTest { public void testSetConnectionHeadset() { mSetFlagsRule.disableFlags(Flags.FLAG_AUTO_CONNECT_ON_MULTIPLE_HFP_WHEN_NO_A2DP_DEVICE); // Verify pre-conditions to ensure a fresh test - Assert.assertEquals(0, mDatabaseManager.mMetadataCache.size()); + assertThat(mDatabaseManager.mMetadataCache).isEmpty(); assertThat(mTestDevice).isNotNull(); assertThat(mTestDevice2).isNotNull(); assertThat(mDatabaseManager.getMostRecentlyActiveHfpDevice()).isNull(); @@ -535,9 +533,8 @@ public final class DatabaseManagerTest { .isTrue(); List<BluetoothDevice> mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyActiveHfpDevice()); - Assert.assertEquals(1, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0)); + assertThat(mDatabaseManager.getMostRecentlyActiveHfpDevice()).isEqualTo(mTestDevice); + assertThat(mostRecentlyConnectedDevicesOrdered).containsExactly(mTestDevice); // Setting the second device's connection mDatabaseManager.setConnection(mTestDevice2, BluetoothProfile.HEADSET); @@ -547,11 +544,11 @@ public final class DatabaseManagerTest { .isFalse(); assertThat(mDatabaseManager.mMetadataCache.get(mTestDevice2.getAddress()).isActiveHfpDevice) .isTrue(); - Assert.assertEquals(mTestDevice2, mDatabaseManager.getMostRecentlyActiveHfpDevice()); + assertThat(mDatabaseManager.getMostRecentlyActiveHfpDevice()).isEqualTo(mTestDevice2); mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(0)); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1)); + assertThat(mostRecentlyConnectedDevicesOrdered) + .containsExactly(mTestDevice2, mTestDevice) + .inOrder(); // Disconnect first test device's connection mDatabaseManager.setDisconnection(mTestDevice, BluetoothProfile.HEADSET); @@ -561,9 +558,9 @@ public final class DatabaseManagerTest { .isFalse(); assertThat(mDatabaseManager.getMostRecentlyActiveHfpDevice()).isNotNull(); mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1)); - Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(0)); + assertThat(mostRecentlyConnectedDevicesOrdered) + .containsExactly(mTestDevice2, mTestDevice) + .inOrder(); mDatabaseManager.factoryReset(); mDatabaseManager.mMetadataCache.clear(); @@ -575,7 +572,7 @@ public final class DatabaseManagerTest { public void testSetConnection() { mSetFlagsRule.disableFlags(Flags.FLAG_AUTO_CONNECT_ON_MULTIPLE_HFP_WHEN_NO_A2DP_DEVICE); // Verify pre-conditions to ensure a fresh test - Assert.assertEquals(0, mDatabaseManager.mMetadataCache.size()); + assertThat(mDatabaseManager.mMetadataCache).isEmpty(); assertThat(mTestDevice).isNotNull(); assertThat(mTestDevice2).isNotNull(); assertThat(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).isNull(); @@ -590,9 +587,8 @@ public final class DatabaseManagerTest { .isTrue(); List<BluetoothDevice> mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice()); - Assert.assertEquals(1, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0)); + assertThat(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).isEqualTo(mTestDevice); + assertThat(mostRecentlyConnectedDevicesOrdered).containsExactly(mTestDevice); // Setting the second device's connection mDatabaseManager.setConnection(mTestDevice2, BluetoothProfile.A2DP); @@ -606,11 +602,11 @@ public final class DatabaseManagerTest { mDatabaseManager.mMetadataCache.get(mTestDevice2.getAddress()) .is_active_a2dp_device) .isTrue(); - Assert.assertEquals(mTestDevice2, mDatabaseManager.getMostRecentlyConnectedA2dpDevice()); + assertThat(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).isEqualTo(mTestDevice2); mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(0)); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1)); + assertThat(mostRecentlyConnectedDevicesOrdered) + .containsExactly(mTestDevice2, mTestDevice) + .inOrder(); // Connect first test device again mDatabaseManager.setConnection(mTestDevice, BluetoothProfile.A2DP); @@ -624,11 +620,11 @@ public final class DatabaseManagerTest { mDatabaseManager.mMetadataCache.get(mTestDevice2.getAddress()) .is_active_a2dp_device) .isFalse(); - Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice()); + assertThat(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).isEqualTo(mTestDevice); mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0)); - Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(1)); + assertThat(mostRecentlyConnectedDevicesOrdered) + .containsExactly(mTestDevice, mTestDevice2) + .inOrder(); // Disconnect first test device's connection mDatabaseManager.setDisconnection(mTestDevice, BluetoothProfile.A2DP); @@ -644,9 +640,9 @@ public final class DatabaseManagerTest { .isFalse(); assertThat(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).isNull(); mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0)); - Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(1)); + assertThat(mostRecentlyConnectedDevicesOrdered) + .containsExactly(mTestDevice, mTestDevice2) + .inOrder(); // Connect third test device (non-a2dp device) mDatabaseManager.setConnection(mTestDevice3, BluetoothProfile.HEADSET); @@ -666,10 +662,9 @@ public final class DatabaseManagerTest { .isFalse(); assertThat(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).isNull(); mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0)); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1)); - Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2)); + assertThat(mostRecentlyConnectedDevicesOrdered) + .containsExactly(mTestDevice3, mTestDevice, mTestDevice2) + .inOrder(); // Connect first test device again mDatabaseManager.setConnection(mTestDevice, BluetoothProfile.A2DP); @@ -687,12 +682,11 @@ public final class DatabaseManagerTest { mDatabaseManager.mMetadataCache.get(mTestDevice3.getAddress()) .is_active_a2dp_device) .isFalse(); - Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice()); + assertThat(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).isEqualTo(mTestDevice); mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0)); - Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(1)); - Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2)); + assertThat(mostRecentlyConnectedDevicesOrdered) + .containsExactly(mTestDevice, mTestDevice3, mTestDevice2) + .inOrder(); // Connect third test device again and ensure it doesn't reset active a2dp device mDatabaseManager.setConnection(mTestDevice3, BluetoothProfile.HEADSET); @@ -710,12 +704,11 @@ public final class DatabaseManagerTest { mDatabaseManager.mMetadataCache.get(mTestDevice3.getAddress()) .is_active_a2dp_device) .isFalse(); - Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice()); + assertThat(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).isEqualTo(mTestDevice); mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0)); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1)); - Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2)); + assertThat(mostRecentlyConnectedDevicesOrdered) + .containsExactly(mTestDevice3, mTestDevice, mTestDevice2) + .inOrder(); // Disconnect second test device mDatabaseManager.setDisconnection(mTestDevice2, BluetoothProfile.A2DP); @@ -733,12 +726,11 @@ public final class DatabaseManagerTest { mDatabaseManager.mMetadataCache.get(mTestDevice3.getAddress()) .is_active_a2dp_device) .isFalse(); - Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice()); + assertThat(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).isEqualTo(mTestDevice); mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0)); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1)); - Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2)); + assertThat(mostRecentlyConnectedDevicesOrdered) + .containsExactly(mTestDevice3, mTestDevice, mTestDevice2) + .inOrder(); // Disconnect first test device mDatabaseManager.setDisconnection(mTestDevice, BluetoothProfile.A2DP); @@ -758,10 +750,9 @@ public final class DatabaseManagerTest { .isFalse(); assertThat(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).isNull(); mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0)); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1)); - Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2)); + assertThat(mostRecentlyConnectedDevicesOrdered) + .containsExactly(mTestDevice3, mTestDevice, mTestDevice2) + .inOrder(); // Disconnect third test device mDatabaseManager.setDisconnection(mTestDevice3, BluetoothProfile.A2DP); @@ -781,10 +772,9 @@ public final class DatabaseManagerTest { .isFalse(); assertThat(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).isNull(); mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices(); - Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size()); - Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0)); - Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1)); - Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2)); + assertThat(mostRecentlyConnectedDevicesOrdered) + .containsExactly(mTestDevice3, mTestDevice, mTestDevice2) + .inOrder(); mDatabaseManager.factoryReset(); mDatabaseManager.mMetadataCache.clear(); @@ -799,16 +789,16 @@ public final class DatabaseManagerTest { preferences.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.LE_AUDIO); // TEST 1: If input is invalid, throws the right Exception - Assert.assertThrows( + assertThrows( NullPointerException.class, () -> mDatabaseManager.setPreferredAudioProfiles(null, preferences)); - Assert.assertThrows( + assertThrows( NullPointerException.class, () -> mDatabaseManager.setPreferredAudioProfiles(new ArrayList<>(), null)); - Assert.assertThrows( + assertThrows( IllegalArgumentException.class, () -> mDatabaseManager.setPreferredAudioProfiles(new ArrayList<>(), preferences)); - Assert.assertThrows( + assertThrows( IllegalArgumentException.class, () -> mDatabaseManager.getPreferredAudioProfiles(null)); @@ -1651,13 +1641,14 @@ public final class DatabaseManagerTest { mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data); mDatabase.insert(data); } - Assert.assertEquals( - expectedSetResult, - mDatabaseManager.setProfileConnectionPolicy( - mTestDevice, BluetoothProfile.HEADSET, connectionPolicy)); - Assert.assertEquals( - expectedConnectionPolicy, - mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.HEADSET)); + assertThat( + mDatabaseManager.setProfileConnectionPolicy( + mTestDevice, BluetoothProfile.HEADSET, connectionPolicy)) + .isEqualTo(expectedSetResult); + assertThat( + mDatabaseManager.getProfileConnectionPolicy( + mTestDevice, BluetoothProfile.HEADSET)) + .isEqualTo(expectedConnectionPolicy); // Wait for database update TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper()); @@ -1668,17 +1659,18 @@ public final class DatabaseManagerTest { if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { // Database won't be updated - Assert.assertEquals(0, list.size()); + assertThat(list).isEmpty(); return; } } - Assert.assertEquals(1, list.size()); + assertThat(list).hasSize(1); // Check whether the device is in database restartDatabaseManagerHelper(); - Assert.assertEquals( - expectedConnectionPolicy, - mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.HEADSET)); + assertThat( + mDatabaseManager.getProfileConnectionPolicy( + mTestDevice, BluetoothProfile.HEADSET)) + .isEqualTo(expectedConnectionPolicy); mDatabaseManager.factoryReset(); mDatabaseManager.mMetadataCache.clear(); @@ -1694,12 +1686,12 @@ public final class DatabaseManagerTest { } if (test == A2DP_SUPPORT_OP_CODEC_TEST) { mDatabaseManager.setA2dpSupportsOptionalCodecs(mTestDevice, value); - Assert.assertEquals( - expectedValue, mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice)); + assertThat(mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice)) + .isEqualTo(expectedValue); } else { mDatabaseManager.setA2dpOptionalCodecsEnabled(mTestDevice, value); - Assert.assertEquals( - expectedValue, mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice)); + assertThat(mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice)) + .isEqualTo(expectedValue); } // Wait for database update TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper()); @@ -1709,19 +1701,19 @@ public final class DatabaseManagerTest { // Check number of metadata in the database if (!stored) { // Database won't be updated - Assert.assertEquals(0, list.size()); + assertThat(list).isEmpty(); return; } - Assert.assertEquals(1, list.size()); + assertThat(list).hasSize(1); // Check whether the device is in database restartDatabaseManagerHelper(); if (test == A2DP_SUPPORT_OP_CODEC_TEST) { - Assert.assertEquals( - expectedValue, mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice)); + assertThat(mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice)) + .isEqualTo(expectedValue); } else { - Assert.assertEquals( - expectedValue, mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice)); + assertThat(mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice)) + .isEqualTo(expectedValue); } mDatabaseManager.factoryReset(); @@ -1737,17 +1729,17 @@ public final class DatabaseManagerTest { Metadata data = new Metadata(TEST_BT_ADDR); mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data); mDatabase.insert(data); - Assert.assertEquals( - expectedResult, mDatabaseManager.setCustomMeta(mTestDevice, key, testValue)); + assertThat(mDatabaseManager.setCustomMeta(mTestDevice, key, testValue)) + .isEqualTo(expectedResult); verify(mAdapterService).onMetadataChanged(mTestDevice, key, testValue); verifyTime++; } - Assert.assertEquals( - expectedResult, mDatabaseManager.setCustomMeta(mTestDevice, key, value)); + assertThat(mDatabaseManager.setCustomMeta(mTestDevice, key, value)) + .isEqualTo(expectedResult); if (expectedResult) { // Check for callback and get value verify(mAdapterService, times(verifyTime)).onMetadataChanged(mTestDevice, key, value); - Assert.assertEquals(value, mDatabaseManager.getCustomMeta(mTestDevice, key)); + assertThat(mDatabaseManager.getCustomMeta(mTestDevice, key)).isEqualTo(value); } else { assertThat(mDatabaseManager.getCustomMeta(mTestDevice, key)).isNull(); return; @@ -1757,7 +1749,7 @@ public final class DatabaseManagerTest { // Check whether the value is saved in database restartDatabaseManagerHelper(); - Assert.assertArrayEquals(value, mDatabaseManager.getCustomMeta(mTestDevice, key)); + assertThat(mDatabaseManager.getCustomMeta(mTestDevice, key)).isEqualTo(value); mDatabaseManager.factoryReset(); mDatabaseManager.mMetadataCache.clear(); @@ -1772,15 +1764,14 @@ public final class DatabaseManagerTest { Metadata data = new Metadata(TEST_BT_ADDR); mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data); mDatabase.insert(data); - Assert.assertEquals( - expectedResult, - mDatabaseManager.setAudioPolicyMetadata(mTestDevice, testPolicy)); + assertThat(mDatabaseManager.setAudioPolicyMetadata(mTestDevice, testPolicy)) + .isEqualTo(expectedResult); } - Assert.assertEquals( - expectedResult, mDatabaseManager.setAudioPolicyMetadata(mTestDevice, policy)); + assertThat(mDatabaseManager.setAudioPolicyMetadata(mTestDevice, policy)) + .isEqualTo(expectedResult); if (expectedResult) { // Check for callback and get value - Assert.assertEquals(policy, mDatabaseManager.getAudioPolicyMetadata(mTestDevice)); + assertThat(mDatabaseManager.getAudioPolicyMetadata(mTestDevice)).isEqualTo(policy); } else { assertThat(mDatabaseManager.getAudioPolicyMetadata(mTestDevice)).isNull(); return; @@ -1790,7 +1781,7 @@ public final class DatabaseManagerTest { // Check whether the value is saved in database restartDatabaseManagerHelper(); - Assert.assertEquals(policy, mDatabaseManager.getAudioPolicyMetadata(mTestDevice)); + assertThat(mDatabaseManager.getAudioPolicyMetadata(mTestDevice)).isEqualTo(policy); mDatabaseManager.factoryReset(); mDatabaseManager.mMetadataCache.clear(); @@ -1815,23 +1806,20 @@ public final class DatabaseManagerTest { groupDevices.add(mTestDevice); groupDevices.add(mTestDevice2); - Assert.assertEquals( - expectedSetResult, - mDatabaseManager.setPreferredAudioProfiles(groupDevices, preferencesToSet)); + assertThat(mDatabaseManager.setPreferredAudioProfiles(groupDevices, preferencesToSet)) + .isEqualTo(expectedSetResult); Bundle testDevicePreferences = mDatabaseManager.getPreferredAudioProfiles(mTestDevice); Bundle testDevice2Preferences = mDatabaseManager.getPreferredAudioProfiles(mTestDevice2); assertThat(testDevicePreferences).isNotNull(); assertThat(testDevice2Preferences).isNotNull(); - Assert.assertEquals( - expectedPreferences.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY), - testDevicePreferences.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)); - Assert.assertEquals( - expectedPreferences.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX), - testDevicePreferences.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); - Assert.assertEquals( - 0, testDevice2Preferences.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)); - Assert.assertEquals(0, testDevice2Preferences.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); + assertThat(testDevicePreferences.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)) + .isEqualTo(expectedPreferences.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)); + assertThat(testDevicePreferences.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)) + .isEqualTo(expectedPreferences.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); + assertThat(testDevice2Preferences.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)) + .isEqualTo(0); + assertThat(testDevice2Preferences.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)).isEqualTo(0); // Wait for database update TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper()); @@ -1840,10 +1828,10 @@ public final class DatabaseManagerTest { // Check number of metadata in the database if (!stored) { - Assert.assertEquals(0, list.size()); + assertThat(list).isEmpty(); return; } - Assert.assertEquals(2, list.size()); + assertThat(list).hasSize(2); // Check whether the device is in database restartDatabaseManagerHelper(); @@ -1852,15 +1840,13 @@ public final class DatabaseManagerTest { assertThat(testDevicePreferences).isNotNull(); assertThat(testDevice2Preferences).isNotNull(); - Assert.assertEquals( - expectedPreferences.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY), - testDevicePreferences.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)); - Assert.assertEquals( - expectedPreferences.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX), - testDevicePreferences.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); - Assert.assertEquals( - 0, testDevice2Preferences.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)); - Assert.assertEquals(0, testDevice2Preferences.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); + assertThat(testDevicePreferences.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)) + .isEqualTo(expectedPreferences.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)); + assertThat(testDevicePreferences.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)) + .isEqualTo(expectedPreferences.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); + assertThat(testDevice2Preferences.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)) + .isEqualTo(0); + assertThat(testDevice2Preferences.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)).isEqualTo(0); mDatabaseManager.factoryReset(); mDatabaseManager.mMetadataCache.clear(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java index 8502c240d1..a2b46c9aa2 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java @@ -43,7 +43,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; -import org.hamcrest.core.IsInstanceOf; import org.junit.*; import org.junit.Rule; import org.junit.runner.RunWith; @@ -112,7 +111,7 @@ public class CsipSetCoordinatorStateMachineTest { /** Test that default state is disconnected */ @Test public void testDefaultDisconnectedState() { - Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTED); } /** @@ -140,9 +139,8 @@ public class CsipSetCoordinatorStateMachineTest { // Verify that no connection state broadcast is executed verify(mService, after(TIMEOUT_MS).never()).sendBroadcast(any(Intent.class), anyString()); // Check that we are in Disconnected state - Assert.assertThat( - mStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(CsipSetCoordinatorStateMachine.Disconnected.class)); + assertThat(mStateMachine.getCurrentState()) + .isInstanceOf(CsipSetCoordinatorStateMachine.Disconnected.class); } /** Test that an incoming connection with policy allowing connection is accepted */ @@ -162,14 +160,11 @@ public class CsipSetCoordinatorStateMachineTest { ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class); verify(mService, timeout(TIMEOUT_MS).times(1)) .sendBroadcast(intentArgument1.capture(), anyString()); - Assert.assertEquals( - STATE_CONNECTING, - intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); + assertThat(intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)) + .isEqualTo(STATE_CONNECTING); - // Check that we are in Connecting state - Assert.assertThat( - mStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(CsipSetCoordinatorStateMachine.Connecting.class)); + assertThat(mStateMachine.getCurrentState()) + .isInstanceOf(CsipSetCoordinatorStateMachine.Connecting.class); // Send a message to trigger connection completed CsipSetCoordinatorStackEvent connCompletedEvent = @@ -185,10 +180,9 @@ public class CsipSetCoordinatorStateMachineTest { ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class); verify(mService, timeout(TIMEOUT_MS).times(2)) .sendBroadcast(intentArgument2.capture(), anyString()); - // Check that we are in Connected state - Assert.assertThat( - mStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(CsipSetCoordinatorStateMachine.Connected.class)); + + assertThat(mStateMachine.getCurrentState()) + .isInstanceOf(CsipSetCoordinatorStateMachine.Connected.class); } /** Test that an outgoing connection times out */ @@ -205,27 +199,21 @@ public class CsipSetCoordinatorStateMachineTest { ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class); verify(mService, timeout(TIMEOUT_MS).times(1)) .sendBroadcast(intentArgument1.capture(), anyString()); - Assert.assertEquals( - STATE_CONNECTING, - intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); + assertThat(intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)) + .isEqualTo(STATE_CONNECTING); - // Check that we are in Connecting state - Assert.assertThat( - mStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(CsipSetCoordinatorStateMachine.Connecting.class)); + assertThat(mStateMachine.getCurrentState()) + .isInstanceOf(CsipSetCoordinatorStateMachine.Connecting.class); // Verify that one connection state broadcast is executed ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class); verify(mService, timeout(CsipSetCoordinatorStateMachine.sConnectTimeoutMs * 2L).times(2)) .sendBroadcast(intentArgument2.capture(), anyString()); - Assert.assertEquals( - STATE_DISCONNECTED, - intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); + assertThat(intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)) + .isEqualTo(STATE_DISCONNECTED); - // Check that we are in Disconnected state - Assert.assertThat( - mStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(CsipSetCoordinatorStateMachine.Disconnected.class)); + assertThat(mStateMachine.getCurrentState()) + .isInstanceOf(CsipSetCoordinatorStateMachine.Disconnected.class); verify(mNativeInterface).disconnect(eq(mTestDevice)); } @@ -248,33 +236,27 @@ public class CsipSetCoordinatorStateMachineTest { ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class); verify(mService, timeout(TIMEOUT_MS).times(1)) .sendBroadcast(intentArgument1.capture(), anyString()); - Assert.assertEquals( - STATE_CONNECTING, - intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); + assertThat(intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)) + .isEqualTo(STATE_CONNECTING); - // Check that we are in Connecting state - Assert.assertThat( - mStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(CsipSetCoordinatorStateMachine.Connecting.class)); + assertThat(mStateMachine.getCurrentState()) + .isInstanceOf(CsipSetCoordinatorStateMachine.Connecting.class); // Verify that one connection state broadcast is executed ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class); verify(mService, timeout(CsipSetCoordinatorStateMachine.sConnectTimeoutMs * 2L).times(2)) .sendBroadcast(intentArgument2.capture(), anyString()); - Assert.assertEquals( - STATE_DISCONNECTED, - intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); + assertThat(intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)) + .isEqualTo(STATE_DISCONNECTED); - // Check that we are in Disconnected state - Assert.assertThat( - mStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(CsipSetCoordinatorStateMachine.Disconnected.class)); + assertThat(mStateMachine.getCurrentState()) + .isInstanceOf(CsipSetCoordinatorStateMachine.Disconnected.class); verify(mNativeInterface).disconnect(eq(mTestDevice)); } @Test public void testGetDevice() { - Assert.assertEquals(mTestDevice, mStateMachine.getDevice()); + assertThat(mStateMachine.getDevice()).isEqualTo(mTestDevice); } @Test @@ -294,7 +276,7 @@ public class CsipSetCoordinatorStateMachineTest { public void testProcessDisconnectMessage_onDisconnectedState() { mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.DISCONNECT); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTED); } @Test @@ -302,12 +284,12 @@ public class CsipSetCoordinatorStateMachineTest { allowConnection(false); mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.CONNECT); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTED); allowConnection(false); mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.CONNECT); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTED); allowConnection(true); doReturn(true).when(mNativeInterface).connect(any(BluetoothDevice.class)); @@ -323,7 +305,7 @@ public class CsipSetCoordinatorStateMachineTest { CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(-1); mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTED); event = new CsipSetCoordinatorStackEvent( @@ -331,7 +313,7 @@ public class CsipSetCoordinatorStateMachineTest { event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTED; mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTED); event = new CsipSetCoordinatorStackEvent( @@ -339,7 +321,7 @@ public class CsipSetCoordinatorStateMachineTest { event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING; mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTED); verify(mNativeInterface).disconnect(mTestDevice); Mockito.clearInvocations(mNativeInterface); @@ -349,7 +331,7 @@ public class CsipSetCoordinatorStateMachineTest { event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED; mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTED); verify(mNativeInterface).disconnect(mTestDevice); event = @@ -358,7 +340,7 @@ public class CsipSetCoordinatorStateMachineTest { event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTING; mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTED); event = new CsipSetCoordinatorStackEvent( @@ -366,7 +348,7 @@ public class CsipSetCoordinatorStateMachineTest { event.valueInt1 = -1; mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTED); } @Test @@ -424,7 +406,7 @@ public class CsipSetCoordinatorStateMachineTest { CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(-1); mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_CONNECTING, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_CONNECTING); event = new CsipSetCoordinatorStackEvent( @@ -432,7 +414,7 @@ public class CsipSetCoordinatorStateMachineTest { event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING; mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_CONNECTING, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_CONNECTING); event = new CsipSetCoordinatorStackEvent( @@ -440,7 +422,7 @@ public class CsipSetCoordinatorStateMachineTest { event.valueInt1 = 10000; mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_CONNECTING, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_CONNECTING); } @Test @@ -484,7 +466,7 @@ public class CsipSetCoordinatorStateMachineTest { initToConnectedState(); mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.CONNECT); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_CONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_CONNECTED); } @Test @@ -511,7 +493,7 @@ public class CsipSetCoordinatorStateMachineTest { CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(-1); mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_CONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_CONNECTED); event = new CsipSetCoordinatorStackEvent( @@ -519,7 +501,7 @@ public class CsipSetCoordinatorStateMachineTest { event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING; mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_CONNECTED, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_CONNECTED); } @Test @@ -577,7 +559,7 @@ public class CsipSetCoordinatorStateMachineTest { CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(-1); mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_DISCONNECTING, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTING); allowConnection(false); event = @@ -603,7 +585,7 @@ public class CsipSetCoordinatorStateMachineTest { event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTING; mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_DISCONNECTING, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTING); event = new CsipSetCoordinatorStackEvent( @@ -611,7 +593,7 @@ public class CsipSetCoordinatorStateMachineTest { event.valueInt1 = 10000; mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertEquals(STATE_DISCONNECTING, mStateMachine.getConnectionState()); + assertThat(mStateMachine.getConnectionState()).isEqualTo(STATE_DISCONNECTING); } @Test @@ -706,7 +688,7 @@ public class CsipSetCoordinatorStateMachineTest { mStateMachine.sendMessage(msg); // Verify that one connection state broadcast is executed verify(mService, timeout(TIMEOUT_MS)).sendBroadcast(any(Intent.class), anyString()); - Assert.assertThat(mStateMachine.getCurrentState(), IsInstanceOf.instanceOf(type)); + assertThat(mStateMachine.getCurrentState()).isInstanceOf(type); } public static class CsipSetCoordinatorStateMachineWrapper diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseBinderTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseBinderTest.java new file mode 100644 index 0000000000..1eca1e2483 --- /dev/null +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseBinderTest.java @@ -0,0 +1,197 @@ +/* + * Copyright 2025 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 static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.le.AdvertiseData; +import android.bluetooth.le.AdvertisingSetParameters; +import android.bluetooth.le.IAdvertisingSetCallback; +import android.bluetooth.le.PeriodicAdvertisingParameters; +import android.content.AttributionSource; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.bluetooth.btservice.AdapterService; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Test cases for {@link AdvertiseBinder}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AdvertiseBinderTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock private AdapterService mAdapterService; + @Mock private AdvertiseManager mAdvertiseManager; + + private final AttributionSource mAttributionSource = + BluetoothAdapter.getDefaultAdapter().getAttributionSource(); + private AdvertiseBinder mBinder; + + @Before + public void setUp() { + doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mAdvertiseManager) + .doOnAdvertiseThread(any()); + mBinder = new AdvertiseBinder(mAdapterService, mAdvertiseManager); + } + + @Test + public void startAdvertisingSet() { + AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder().build(); + AdvertiseData advertiseData = new AdvertiseData.Builder().build(); + AdvertiseData scanResponse = new AdvertiseData.Builder().build(); + PeriodicAdvertisingParameters periodicParameters = + new PeriodicAdvertisingParameters.Builder().build(); + AdvertiseData periodicData = new AdvertiseData.Builder().build(); + int duration = 1; + int maxExtAdvEvents = 2; + int serverIf = 3; + IAdvertisingSetCallback callback = mock(IAdvertisingSetCallback.class); + + mBinder.startAdvertisingSet( + parameters, + advertiseData, + scanResponse, + periodicParameters, + periodicData, + duration, + maxExtAdvEvents, + serverIf, + callback, + mAttributionSource); + + verify(mAdvertiseManager) + .startAdvertisingSet( + parameters, + advertiseData, + scanResponse, + periodicParameters, + periodicData, + duration, + maxExtAdvEvents, + serverIf, + callback, + mAttributionSource); + } + + @Test + public void stopAdvertisingSet() { + IAdvertisingSetCallback callback = mock(IAdvertisingSetCallback.class); + + mBinder.stopAdvertisingSet(callback, mAttributionSource); + + verify(mAdvertiseManager).stopAdvertisingSet(callback); + } + + @Test + public void setAdvertisingData() { + int advertiserId = 1; + AdvertiseData data = new AdvertiseData.Builder().build(); + + mBinder.setAdvertisingData(advertiserId, data, mAttributionSource); + verify(mAdvertiseManager).setAdvertisingData(advertiserId, data); + } + + @Test + public void setAdvertisingParameters() { + int advertiserId = 1; + AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder().build(); + + mBinder.setAdvertisingParameters(advertiserId, parameters, mAttributionSource); + verify(mAdvertiseManager).setAdvertisingParameters(advertiserId, parameters); + } + + @Test + public void setPeriodicAdvertisingData() { + int advertiserId = 1; + AdvertiseData data = new AdvertiseData.Builder().build(); + + mBinder.setPeriodicAdvertisingData(advertiserId, data, mAttributionSource); + verify(mAdvertiseManager).setPeriodicAdvertisingData(advertiserId, data); + } + + @Test + public void setPeriodicAdvertisingEnable() { + int advertiserId = 1; + boolean enable = true; + + mBinder.setPeriodicAdvertisingEnable(advertiserId, enable, mAttributionSource); + verify(mAdvertiseManager).setPeriodicAdvertisingEnable(advertiserId, enable); + } + + @Test + public void setPeriodicAdvertisingParameters() { + int advertiserId = 1; + PeriodicAdvertisingParameters parameters = + new PeriodicAdvertisingParameters.Builder().build(); + + mBinder.setPeriodicAdvertisingParameters(advertiserId, parameters, mAttributionSource); + verify(mAdvertiseManager).setPeriodicAdvertisingParameters(advertiserId, parameters); + } + + @Test + public void setScanResponseData() { + int advertiserId = 1; + AdvertiseData data = new AdvertiseData.Builder().build(); + + mBinder.setScanResponseData(advertiserId, data, mAttributionSource); + verify(mAdvertiseManager).setScanResponseData(advertiserId, data); + } + + @Test + public void getOwnAddress() { + int advertiserId = 1; + + mBinder.getOwnAddress(advertiserId, mAttributionSource); + verify(mAdvertiseManager).getOwnAddress(advertiserId); + } + + @Test + public void enableAdvertisingSet() { + int advertiserId = 1; + boolean enable = true; + int duration = 3; + int maxExtAdvEvents = 4; + + mBinder.enableAdvertisingSet( + advertiserId, enable, duration, maxExtAdvEvents, mAttributionSource); + verify(mAdvertiseManager) + .enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents); + } + + @Test + public void cleanUp_doesNotCrash() { + mBinder.cleanup(); + } +} diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseManagerTest.java index 6621a43331..4cd5423ce9 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseManagerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseManagerTest.java @@ -27,15 +27,16 @@ import android.bluetooth.le.AdvertisingSetParameters; import android.bluetooth.le.IAdvertisingSetCallback; import android.bluetooth.le.PeriodicAdvertisingParameters; import android.os.IBinder; +import android.os.test.TestLooper; +import android.platform.test.flag.junit.FlagsParameterization; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; -import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.flags.Flags; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -44,17 +45,21 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +import java.util.List; + /** Test cases for {@link AdvertiseManager}. */ @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) public class AdvertiseManagerTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule; @Mock private AdapterService mAdapterService; - @Mock private GattService mService; - @Mock private AdvertiserMap mAdvertiserMap; @Mock private AdvertiseManagerNativeInterface mNativeInterface; @@ -66,10 +71,23 @@ public class AdvertiseManagerTest { private AdvertiseManager mAdvertiseManager; private int mAdvertiserId; + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf(Flags.FLAG_ADVERTISE_THREAD); + } + + public AdvertiseManagerTest(FlagsParameterization flags) { + mSetFlagsRule = new SetFlagsRule(flags); + } + @Before public void setUp() throws Exception { - TestUtils.setAdapterService(mAdapterService); - mAdvertiseManager = new AdvertiseManager(mService, mNativeInterface, mAdvertiserMap); + mAdvertiseManager = + new AdvertiseManager( + mAdapterService, + new TestLooper().getLooper(), + mNativeInterface, + mAdvertiserMap); AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder().build(); AdvertiseData advertiseData = new AdvertiseData.Builder().build(); @@ -95,12 +113,7 @@ public class AdvertiseManagerTest { mCallback, InstrumentationRegistry.getTargetContext().getAttributionSource()); - mAdvertiserId = AdvertiseManager.sTempRegistrationId; - } - - @After - public void tearDown() throws Exception { - TestUtils.clearAdapterService(mAdapterService); + mAdvertiserId = mAdvertiseManager.mTempRegistrationId; } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/AppAdvertiseStatsTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/AppAdvertiseStatsTest.java index cb3aa0dc88..cef16ca6ca 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/AppAdvertiseStatsTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/AppAdvertiseStatsTest.java @@ -102,7 +102,7 @@ public class AppAdvertiseStatsTest { AppAdvertiseStats appAdvertiseStats = new AppAdvertiseStats(appUid, id, name, mAttributionSource); - assertThat(appAdvertiseStats.mAdvertiserRecords.size()).isEqualTo(0); + assertThat(appAdvertiseStats.mAdvertiserRecords).isEmpty(); int duration = 1; int maxExtAdvEvents = 2; @@ -129,7 +129,7 @@ public class AppAdvertiseStatsTest { int numOfExpectedRecords = 2; - assertThat(appAdvertiseStats.mAdvertiserRecords.size()).isEqualTo(numOfExpectedRecords); + assertThat(appAdvertiseStats.mAdvertiserRecords).hasSize(numOfExpectedRecords); } @Test @@ -145,7 +145,7 @@ public class AppAdvertiseStatsTest { int maxExtAdvEvents = 2; int instanceCount = 3; - assertThat(appAdvertiseStats.mAdvertiserRecords.size()).isEqualTo(0); + assertThat(appAdvertiseStats.mAdvertiserRecords).isEmpty(); appAdvertiseStats.recordAdvertiseStart(duration, maxExtAdvEvents, instanceCount); @@ -170,7 +170,7 @@ public class AppAdvertiseStatsTest { int numOfExpectedRecords = 2; - assertThat(appAdvertiseStats.mAdvertiserRecords.size()).isEqualTo(numOfExpectedRecords); + assertThat(appAdvertiseStats.mAdvertiserRecords).hasSize(numOfExpectedRecords); } @Test @@ -186,14 +186,14 @@ public class AppAdvertiseStatsTest { int maxExtAdvEvents = 2; int instanceCount = 3; - assertThat(appAdvertiseStats.mAdvertiserRecords.size()).isEqualTo(0); + assertThat(appAdvertiseStats.mAdvertiserRecords).isEmpty(); appAdvertiseStats.enableAdvertisingSet(true, duration, maxExtAdvEvents, instanceCount); appAdvertiseStats.enableAdvertisingSet(false, duration, maxExtAdvEvents, instanceCount); int numOfExpectedRecords = 1; - assertThat(appAdvertiseStats.mAdvertiserRecords.size()).isEqualTo(numOfExpectedRecords); + assertThat(appAdvertiseStats.mAdvertiserRecords).hasSize(numOfExpectedRecords); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/ContextMapTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/ContextMapTest.java index e0fb8671df..151f607c1e 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/ContextMapTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/ContextMapTest.java @@ -140,15 +140,15 @@ public class ContextMapTest { @Test public void removeMethods() { ContextMap<IBluetoothGattCallback> contextMap = getMapWithAppAndConnection(); - contextMap.remove(APP_ID1); + contextMap.remove(APP_ID1, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); assertThat(contextMap.getAllAppsIds()).isNotEmpty(); - contextMap.remove(APP_ID2); + contextMap.remove(APP_ID2, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); assertThat(contextMap.getAllAppsIds()).isEmpty(); contextMap = getMapWithAppAndConnection(); - contextMap.remove(RANDOM_UUID1); + contextMap.remove(RANDOM_UUID1, ContextMap.RemoveReason.REASON_REGISTER_FAILED); assertThat(contextMap.getAllAppsIds()).isNotEmpty(); - contextMap.remove(RANDOM_UUID2); + contextMap.remove(RANDOM_UUID2, ContextMap.RemoveReason.REASON_REGISTER_FAILED); assertThat(contextMap.getAllAppsIds()).isEmpty(); contextMap = getMapWithAppAndConnection(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/DistanceMeasurementBinderTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/DistanceMeasurementBinderTest.java new file mode 100644 index 0000000000..3ed9b80092 --- /dev/null +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/DistanceMeasurementBinderTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 2025 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 static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.le.DistanceMeasurementMethod; +import android.bluetooth.le.DistanceMeasurementParams; +import android.bluetooth.le.IDistanceMeasurementCallback; +import android.content.AttributionSource; +import android.os.ParcelUuid; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.bluetooth.btservice.AdapterService; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.UUID; + +/** Test cases for {@link DistanceMeasurementBinder}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DistanceMeasurementBinderTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock private DistanceMeasurementManager mDistanceMeasurementManager; + @Mock private AdapterService mAdapterService; + + private final AttributionSource mAttributionSource = + BluetoothAdapter.getDefaultAdapter().getAttributionSource(); + + private DistanceMeasurementBinder mBinder; + + @Before + public void setUp() { + mBinder = new DistanceMeasurementBinder(mAdapterService, mDistanceMeasurementManager); + when(mDistanceMeasurementManager.getSupportedDistanceMeasurementMethods()) + .thenReturn(new DistanceMeasurementMethod[0]); + } + + @Test + public void getSupportedDistanceMeasurementMethods() { + mBinder.getSupportedDistanceMeasurementMethods(mAttributionSource); + verify(mDistanceMeasurementManager).getSupportedDistanceMeasurementMethods(); + } + + @Test + public void startDistanceMeasurement() { + UUID uuid = UUID.randomUUID(); + BluetoothDevice device = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05"); + DistanceMeasurementParams params = + new DistanceMeasurementParams.Builder(device) + .setDurationSeconds(123) + .setFrequency(DistanceMeasurementParams.REPORT_FREQUENCY_LOW) + .build(); + IDistanceMeasurementCallback callback = mock(IDistanceMeasurementCallback.class); + mBinder.startDistanceMeasurement( + new ParcelUuid(uuid), params, callback, mAttributionSource); + verify(mDistanceMeasurementManager).startDistanceMeasurement(uuid, params, callback); + } + + @Test + public void stopDistanceMeasurement() { + UUID uuid = UUID.randomUUID(); + BluetoothDevice device = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05"); + int method = DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_RSSI; + mBinder.stopDistanceMeasurement(new ParcelUuid(uuid), device, method, mAttributionSource); + verify(mDistanceMeasurementManager).stopDistanceMeasurement(uuid, device, method, false); + } +} diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/DistanceMeasurementManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/DistanceMeasurementManagerTest.java index 2b34af7690..2fb7a5e8b6 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/DistanceMeasurementManagerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/DistanceMeasurementManagerTest.java @@ -210,18 +210,29 @@ public class DistanceMeasurementManagerTest { IDENTITY_ADDRESS, 100, 0, + 100, 0, + 45, 0, - 0, - 0, - 0, + 10000, 1, + /* delayedSpreadMeters= */ 10.0, + /* detectedAttackLevel= */ DistanceMeasurementResult.NADM_ATTACK_IS_POSSIBLE, + /* velocityMetersPerSecond= */ 1.0, DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_CHANNEL_SOUNDING); ArgumentCaptor<DistanceMeasurementResult> result = ArgumentCaptor.forClass(DistanceMeasurementResult.class); verify(mCallback).onResult(eq(mDevice), result.capture()); assertThat(result.getValue().getResultMeters()).isEqualTo(1.00); + assertThat(result.getValue().getAzimuthAngle()).isEqualTo(100); + assertThat(result.getValue().getAltitudeAngle()).isEqualTo(45); + assertThat(result.getValue().getMeasurementTimestampNanos()).isEqualTo(10000); + assertThat(result.getValue().getConfidenceLevel()).isEqualTo(0.01); + assertThat(result.getValue().getDelaySpreadMeters()).isEqualTo(10.0); + assertThat(result.getValue().getDetectedAttackLevel()) + .isEqualTo(DistanceMeasurementResult.NADM_ATTACK_IS_POSSIBLE); + assertThat(result.getValue().getVelocityMetersPerSecond()).isEqualTo(1.0); } @Test @@ -267,6 +278,9 @@ public class DistanceMeasurementManagerTest { -1, 1000L, -1, + /* delayedSpreadMeters= */ 10.0, + /* detectedAttackLevel= */ DistanceMeasurementResult.NADM_ATTACK_IS_POSSIBLE, + /* velocityMetersPerSecond= */ 0.0, DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_RSSI); ArgumentCaptor<DistanceMeasurementResult> result = ArgumentCaptor.forClass(DistanceMeasurementResult.class); @@ -278,6 +292,10 @@ public class DistanceMeasurementManagerTest { assertThat(result.getValue().getAltitudeAngle()).isEqualTo(Double.NaN); assertThat(result.getValue().getErrorAltitudeAngle()).isEqualTo(Double.NaN); assertThat(result.getValue().getMeasurementTimestampNanos()).isEqualTo(1000L); + assertThat(result.getValue().getDelaySpreadMeters()).isEqualTo(Double.NaN); + assertThat(result.getValue().getDetectedAttackLevel()) + .isEqualTo(DistanceMeasurementResult.NADM_UNKNOWN); + assertThat(result.getValue().getVelocityMetersPerSecond()).isEqualTo(Double.NaN); } @Test @@ -306,6 +324,9 @@ public class DistanceMeasurementManagerTest { -1, 1000L, -1, + /* delayedSpreadMeters= */ 10.0, + /* detectedAttackLevel= */ DistanceMeasurementResult.NADM_ATTACK_IS_POSSIBLE, + /* velocityMetersPerSecond= */ 0.0, DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_RSSI); DistanceMeasurementResult result = new DistanceMeasurementResult.Builder(1.00, 1.00).build(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceBinderTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceBinderTest.java index 0e5bfaac94..3d749abb73 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceBinderTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceBinderTest.java @@ -25,10 +25,6 @@ import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothGattCallback; import android.bluetooth.IBluetoothGattServerCallback; -import android.bluetooth.le.AdvertiseData; -import android.bluetooth.le.AdvertisingSetParameters; -import android.bluetooth.le.IAdvertisingSetCallback; -import android.bluetooth.le.PeriodicAdvertisingParameters; import android.content.AttributionSource; import android.os.ParcelUuid; @@ -92,7 +88,11 @@ public class GattServiceBinderTest { mBinder.unregisterClient(clientIf, mAttributionSource); - verify(mService).unregisterClient(clientIf, mAttributionSource); + verify(mService) + .unregisterClient( + clientIf, + mAttributionSource, + ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); } @Test @@ -515,140 +515,6 @@ public class GattServiceBinderTest { } @Test - public void startAdvertisingSet() throws Exception { - AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder().build(); - AdvertiseData advertiseData = new AdvertiseData.Builder().build(); - AdvertiseData scanResponse = new AdvertiseData.Builder().build(); - PeriodicAdvertisingParameters periodicParameters = - new PeriodicAdvertisingParameters.Builder().build(); - AdvertiseData periodicData = new AdvertiseData.Builder().build(); - int duration = 1; - int maxExtAdvEvents = 2; - int serverIf = 3; - IAdvertisingSetCallback callback = mock(IAdvertisingSetCallback.class); - - mBinder.startAdvertisingSet( - parameters, - advertiseData, - scanResponse, - periodicParameters, - periodicData, - duration, - maxExtAdvEvents, - serverIf, - callback, - mAttributionSource); - - verify(mService) - .startAdvertisingSet( - parameters, - advertiseData, - scanResponse, - periodicParameters, - periodicData, - duration, - maxExtAdvEvents, - serverIf, - callback, - mAttributionSource); - } - - @Test - public void stopAdvertisingSet() throws Exception { - IAdvertisingSetCallback callback = mock(IAdvertisingSetCallback.class); - - mBinder.stopAdvertisingSet(callback, mAttributionSource); - - verify(mService).stopAdvertisingSet(callback, mAttributionSource); - } - - @Test - public void getOwnAddress() throws Exception { - int advertiserId = 1; - - mBinder.getOwnAddress(advertiserId, mAttributionSource); - - verify(mService).getOwnAddress(advertiserId, mAttributionSource); - } - - @Test - public void enableAdvertisingSet() throws Exception { - int advertiserId = 1; - boolean enable = true; - int duration = 3; - int maxExtAdvEvents = 4; - - mBinder.enableAdvertisingSet( - advertiserId, enable, duration, maxExtAdvEvents, mAttributionSource); - - verify(mService) - .enableAdvertisingSet( - advertiserId, enable, duration, maxExtAdvEvents, mAttributionSource); - } - - @Test - public void setAdvertisingData() throws Exception { - int advertiserId = 1; - AdvertiseData data = new AdvertiseData.Builder().build(); - - mBinder.setAdvertisingData(advertiserId, data, mAttributionSource); - - verify(mService).setAdvertisingData(advertiserId, data, mAttributionSource); - } - - @Test - public void setScanResponseData() throws Exception { - int advertiserId = 1; - AdvertiseData data = new AdvertiseData.Builder().build(); - - mBinder.setScanResponseData(advertiserId, data, mAttributionSource); - - verify(mService).setScanResponseData(advertiserId, data, mAttributionSource); - } - - @Test - public void setAdvertisingParameters() throws Exception { - int advertiserId = 1; - AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder().build(); - - mBinder.setAdvertisingParameters(advertiserId, parameters, mAttributionSource); - - verify(mService).setAdvertisingParameters(advertiserId, parameters, mAttributionSource); - } - - @Test - public void setPeriodicAdvertisingParameters() throws Exception { - int advertiserId = 1; - PeriodicAdvertisingParameters parameters = - new PeriodicAdvertisingParameters.Builder().build(); - - mBinder.setPeriodicAdvertisingParameters(advertiserId, parameters, mAttributionSource); - - verify(mService) - .setPeriodicAdvertisingParameters(advertiserId, parameters, mAttributionSource); - } - - @Test - public void setPeriodicAdvertisingData() throws Exception { - int advertiserId = 1; - AdvertiseData data = new AdvertiseData.Builder().build(); - - mBinder.setPeriodicAdvertisingData(advertiserId, data, mAttributionSource); - - verify(mService).setPeriodicAdvertisingData(advertiserId, data, mAttributionSource); - } - - @Test - public void setPeriodicAdvertisingEnable() throws Exception { - int advertiserId = 1; - boolean enable = true; - - mBinder.setPeriodicAdvertisingEnable(advertiserId, enable, mAttributionSource); - - verify(mService).setPeriodicAdvertisingEnable(advertiserId, enable, mAttributionSource); - } - - @Test public void disconnectAll() throws Exception { mBinder.disconnectAll(mAttributionSource); diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java index 04a46a96b3..2ff148fa41 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java @@ -30,18 +30,13 @@ import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.IBluetoothGattCallback; import android.bluetooth.IBluetoothGattServerCallback; -import android.bluetooth.le.AdvertiseData; -import android.bluetooth.le.AdvertisingSetParameters; -import android.bluetooth.le.DistanceMeasurementMethod; -import android.bluetooth.le.DistanceMeasurementParams; -import android.bluetooth.le.IDistanceMeasurementCallback; -import android.bluetooth.le.PeriodicAdvertisingParameters; import android.companion.CompanionDeviceManager; import android.content.AttributionSource; import android.content.Context; import android.content.res.Resources; import android.location.LocationManager; import android.os.Bundle; +import android.os.Process; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.test.mock.MockContentProvider; @@ -55,6 +50,7 @@ import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.CompanionManager; import com.android.bluetooth.flags.Flags; +import com.android.bluetooth.le_scan.PeriodicScanManager; import com.android.bluetooth.le_scan.ScanManager; import com.android.bluetooth.le_scan.ScanObjectsFactory; @@ -84,6 +80,7 @@ public class GattServiceTest { @Mock private ContextMap<IBluetoothGattCallback> mClientMap; @Mock private ScanManager mScanManager; + @Mock private PeriodicScanManager mPeriodicScanManager; @Mock private Set<String> mReliableQueue; @Mock private ContextMap<IBluetoothGattServerCallback> mServerMap; @Mock private DistanceMeasurementManager mDistanceMeasurementManager; @@ -130,6 +127,7 @@ public class GattServiceTest { doReturn(mScanManager) .when(mScanObjectsFactory) .createScanManager(any(), any(), any(), any()); + doReturn(mPeriodicScanManager).when(mScanObjectsFactory).createPeriodicScanManager(any()); doReturn(mContext.getPackageManager()).when(mAdapterService).getPackageManager(); doReturn(mContext.getSharedPreferences("GattServiceTestPrefs", Context.MODE_PRIVATE)) .when(mAdapterService) @@ -272,66 +270,129 @@ public class GattServiceTest { } @Test - public void disconnectAll() { - Map<Integer, String> connMap = new HashMap<>(); + public void clientConnectOverLeFailed() throws Exception { int clientIf = 1; - String address = "02:00:00:00:00:00"; - connMap.put(clientIf, address); - doReturn(connMap).when(mClientMap).getConnectedMap(); - Integer connId = 1; - doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address); + String address = REMOTE_DEVICE_ADDRESS; + int addressType = BluetoothDevice.ADDRESS_TYPE_RANDOM; + boolean isDirect = true; + int transport = BluetoothDevice.TRANSPORT_LE; + boolean opportunistic = false; + int phy = 3; - mService.disconnectAll(mAttributionSource); - verify(mNativeInterface).gattClientDisconnect(clientIf, address, connId); - } + AttributionSource testAttributeSource = + new AttributionSource.Builder(Process.SYSTEM_UID) + .setPid(Process.myPid()) + .setDeviceId(Context.DEVICE_ID_DEFAULT) + .setPackageName("com.google.android.gms") + .setAttributionTag("com.google.android.gms.findmydevice") + .build(); - @Test - public void setAdvertisingData() { - int advertiserId = 1; - AdvertiseData data = new AdvertiseData.Builder().build(); + mService.clientConnect( + clientIf, + address, + addressType, + isDirect, + transport, + opportunistic, + phy, + testAttributeSource); - mService.setAdvertisingData(advertiserId, data, mAttributionSource); + verify(mAdapterService).notifyDirectLeGattClientConnect(anyInt(), any()); + verify(mNativeInterface) + .gattClientConnect( + clientIf, address, addressType, isDirect, transport, opportunistic, phy, 0); + mService.onConnected(clientIf, 0, BluetoothGatt.GATT_CONNECTION_TIMEOUT, address); + verify(mAdapterService).notifyGattClientConnectFailed(anyInt(), any()); } @Test - public void setAdvertisingParameters() { - int advertiserId = 1; - AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder().build(); + public void clientConnectDisconnectOverLe() throws Exception { + int clientIf = 1; + String address = REMOTE_DEVICE_ADDRESS; + int addressType = BluetoothDevice.ADDRESS_TYPE_RANDOM; + boolean isDirect = true; + int transport = BluetoothDevice.TRANSPORT_LE; + boolean opportunistic = false; + int phy = 3; - mService.setAdvertisingParameters(advertiserId, parameters, mAttributionSource); - } + AttributionSource testAttributeSource = + new AttributionSource.Builder(Process.SYSTEM_UID) + .setPid(Process.myPid()) + .setDeviceId(Context.DEVICE_ID_DEFAULT) + .setPackageName("com.google.android.gms") + .setAttributionTag("com.google.android.gms.findmydevice") + .build(); - @Test - public void setPeriodicAdvertisingData() { - int advertiserId = 1; - AdvertiseData data = new AdvertiseData.Builder().build(); + mService.clientConnect( + clientIf, + address, + addressType, + isDirect, + transport, + opportunistic, + phy, + testAttributeSource); + + verify(mAdapterService).notifyDirectLeGattClientConnect(anyInt(), any()); + verify(mNativeInterface) + .gattClientConnect( + clientIf, address, addressType, isDirect, transport, opportunistic, phy, 0); + mService.onConnected(clientIf, 15, BluetoothGatt.GATT_SUCCESS, address); + mService.clientDisconnect(clientIf, address, mAttributionSource); - mService.setPeriodicAdvertisingData(advertiserId, data, mAttributionSource); + verify(mAdapterService).notifyGattClientDisconnect(anyInt(), any()); } @Test - public void setPeriodicAdvertisingEnable() { - int advertiserId = 1; - boolean enable = true; + public void clientConnectOverLeDisconnectedByRemote() throws Exception { + int clientIf = 1; + String address = REMOTE_DEVICE_ADDRESS; + int addressType = BluetoothDevice.ADDRESS_TYPE_RANDOM; + boolean isDirect = true; + int transport = BluetoothDevice.TRANSPORT_LE; + boolean opportunistic = false; + int phy = 3; - mService.setPeriodicAdvertisingEnable(advertiserId, enable, mAttributionSource); - } + AttributionSource testAttributeSource = + new AttributionSource.Builder(Process.SYSTEM_UID) + .setPid(Process.myPid()) + .setDeviceId(Context.DEVICE_ID_DEFAULT) + .setPackageName("com.google.android.gms") + .setAttributionTag("com.google.android.gms.findmydevice") + .build(); - @Test - public void setPeriodicAdvertisingParameters() { - int advertiserId = 1; - PeriodicAdvertisingParameters parameters = - new PeriodicAdvertisingParameters.Builder().build(); + mService.clientConnect( + clientIf, + address, + addressType, + isDirect, + transport, + opportunistic, + phy, + testAttributeSource); - mService.setPeriodicAdvertisingParameters(advertiserId, parameters, mAttributionSource); + verify(mAdapterService).notifyDirectLeGattClientConnect(anyInt(), any()); + verify(mNativeInterface) + .gattClientConnect( + clientIf, address, addressType, isDirect, transport, opportunistic, phy, 0); + mService.onConnected(clientIf, 15, BluetoothGatt.GATT_SUCCESS, address); + mService.onDisconnected(clientIf, 15, 1, address); + + verify(mAdapterService).notifyGattClientDisconnect(anyInt(), any()); } @Test - public void setScanResponseData() { - int advertiserId = 1; - AdvertiseData data = new AdvertiseData.Builder().build(); + public void disconnectAll() { + Map<Integer, String> connMap = new HashMap<>(); + int clientIf = 1; + String address = "02:00:00:00:00:00"; + connMap.put(clientIf, address); + doReturn(connMap).when(mClientMap).getConnectedMap(); + Integer connId = 1; + doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address); - mService.setScanResponseData(advertiserId, data, mAttributionSource); + mService.disconnectAll(mAttributionSource); + verify(mNativeInterface).gattClientDisconnect(clientIf, address, connId); } @Test @@ -351,7 +412,7 @@ public class GattServiceTest { mService.getDevicesMatchingConnectionStates(states, mAttributionSource); int expectedSize = 1; - assertThat(deviceList.size()).isEqualTo(expectedSize); + assertThat(deviceList).hasSize(expectedSize); BluetoothDevice bluetoothDevice = deviceList.get(0); assertThat(bluetoothDevice.getAddress()).isEqualTo(address); @@ -366,7 +427,10 @@ public class GattServiceTest { mService.registerClient(uuid, callback, eattSupport, mAttributionSource); verify(mNativeInterface) .gattClientRegisterApp( - uuid.getLeastSignificantBits(), uuid.getMostSignificantBits(), eattSupport); + uuid.getLeastSignificantBits(), + uuid.getMostSignificantBits(), + mAttributionSource.getPackageName(), + eattSupport); } @Test @@ -378,15 +442,17 @@ public class GattServiceTest { mService.registerClient(uuid, callback, /* eattSupport= */ true, mAttributionSource); verify(mClientMap, never()).add(any(), any(), any(), any()); - verify(mNativeInterface, never()).gattClientRegisterApp(anyLong(), anyLong(), anyBoolean()); + verify(mNativeInterface, never()) + .gattClientRegisterApp(anyLong(), anyLong(), anyString(), anyBoolean()); } @Test public void unregisterClient() { int clientIf = 3; - mService.unregisterClient(clientIf, mAttributionSource); - verify(mClientMap).remove(clientIf); + mService.unregisterClient( + clientIf, mAttributionSource, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); + verify(mClientMap).remove(clientIf, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); verify(mNativeInterface).gattClientUnregisterApp(clientIf); } @@ -626,24 +692,6 @@ public class GattServiceTest { } @Test - public void getOwnAddress() throws Exception { - int advertiserId = 1; - - mService.getOwnAddress(advertiserId, mAttributionSource); - } - - @Test - public void enableAdvertisingSet() throws Exception { - int advertiserId = 1; - boolean enable = true; - int duration = 3; - int maxExtAdvEvents = 4; - - mService.enableAdvertisingSet( - advertiserId, enable, duration, maxExtAdvEvents, mAttributionSource); - } - - @Test public void unregAll() throws Exception { int appId = 1; List<Integer> appIds = new ArrayList<>(); @@ -651,40 +699,11 @@ public class GattServiceTest { doReturn(appIds).when(mClientMap).getAllAppsIds(); mService.unregAll(mAttributionSource); - verify(mClientMap).remove(appId); + verify(mClientMap).remove(appId, ContextMap.RemoveReason.REASON_UNREGISTER_ALL); verify(mNativeInterface).gattClientUnregisterApp(appId); } @Test - public void getSupportedDistanceMeasurementMethods() { - mService.getSupportedDistanceMeasurementMethods(); - verify(mDistanceMeasurementManager).getSupportedDistanceMeasurementMethods(); - } - - @Test - public void startDistanceMeasurement() { - UUID uuid = UUID.randomUUID(); - BluetoothDevice device = mAdapter.getRemoteDevice("00:01:02:03:04:05"); - DistanceMeasurementParams params = - new DistanceMeasurementParams.Builder(device) - .setDurationSeconds(123) - .setFrequency(DistanceMeasurementParams.REPORT_FREQUENCY_LOW) - .build(); - IDistanceMeasurementCallback callback = mock(IDistanceMeasurementCallback.class); - mService.startDistanceMeasurement(uuid, params, callback); - verify(mDistanceMeasurementManager).startDistanceMeasurement(uuid, params, callback); - } - - @Test - public void stopDistanceMeasurement() { - UUID uuid = UUID.randomUUID(); - BluetoothDevice device = mAdapter.getRemoteDevice("00:01:02:03:04:05"); - int method = DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_RSSI; - mService.stopDistanceMeasurement(uuid, device, method); - verify(mDistanceMeasurementManager).stopDistanceMeasurement(uuid, device, method, false); - } - - @Test public void cleanUp_doesNotCrash() { mService.cleanup(); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientServiceTest.java index 81c4865b84..815f4c7de9 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientServiceTest.java @@ -513,7 +513,7 @@ public class HapClientServiceTest { eq(BluetoothStatusCodes.REASON_REMOTE_REQUEST)); List<BluetoothHapPresetInfo> presets = presetsCaptor.getValue(); - assertThat(presets.size()).isEqualTo(3); + assertThat(presets).hasSize(3); Optional<BluetoothHapPresetInfo> preset = presetsCaptor.getValue().stream().filter(p -> 0x01 == p.getIndex()).findFirst(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java index a1095a6c59..f14884bcb8 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java @@ -18,6 +18,7 @@ package com.android.bluetooth.hfp; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; @@ -34,6 +35,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothProfile; @@ -58,9 +60,7 @@ import com.android.bluetooth.btservice.SilenceDeviceManager; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.flags.Flags; -import org.hamcrest.Matchers; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -71,8 +71,8 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Set; /** Tests for {@link HeadsetService} */ @@ -192,15 +192,13 @@ public class HeadsetServiceTest { /** Test to verify that HeadsetService can be successfully started */ @Test public void testGetHeadsetService() { - Assert.assertEquals(mHeadsetService, HeadsetService.getHeadsetService()); + assertThat(HeadsetService.getHeadsetService()).isEqualTo(mHeadsetService); // Verify default connection and audio states mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); - Assert.assertEquals( - BluetoothHeadset.STATE_AUDIO_DISCONNECTED, - mHeadsetService.getAudioState(mCurrentDevice)); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + assertThat(mHeadsetService.getAudioState(mCurrentDevice)) + .isEqualTo(BluetoothHeadset.STATE_AUDIO_DISCONNECTED); } /** Test okToAcceptConnection method using various test cases */ @@ -300,16 +298,13 @@ public class HeadsetServiceTest { when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getConnectionState()) .thenReturn(BluetoothProfile.STATE_CONNECTING); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTING, - mHeadsetService.getConnectionState(mCurrentDevice)); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTING); when(mStateMachines.get(mCurrentDevice).getConnectionState()) .thenReturn(BluetoothProfile.STATE_CONNECTED); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); - Assert.assertEquals( - Collections.singletonList(mCurrentDevice), mHeadsetService.getConnectedDevices()); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); + assertThat(mHeadsetService.getConnectedDevices()).isEqualTo(List.of(mCurrentDevice)); // 2nd connection attempt will fail assertThat(mHeadsetService.connect(mCurrentDevice)).isFalse(); // Verify makeStateMachine is only called once @@ -346,11 +341,9 @@ public class HeadsetServiceTest { when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getConnectionState()) .thenReturn(BluetoothProfile.STATE_CONNECTED); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); - Assert.assertEquals( - Collections.singletonList(mCurrentDevice), mHeadsetService.getConnectedDevices()); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); + assertThat(mHeadsetService.getConnectedDevices()).isEqualTo(List.of(mCurrentDevice)); // Test disconnect from native HeadsetStackEvent disconnectEvent = new HeadsetStackEvent( @@ -362,10 +355,9 @@ public class HeadsetServiceTest { .sendMessage(HeadsetStateMachine.STACK_EVENT, disconnectEvent); when(mStateMachines.get(mCurrentDevice).getConnectionState()) .thenReturn(BluetoothProfile.STATE_DISCONNECTED); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); - Assert.assertEquals(Collections.EMPTY_LIST, mHeadsetService.getConnectedDevices()); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + assertThat(mHeadsetService.getConnectedDevices()).isEmpty(); } /** @@ -405,12 +397,9 @@ public class HeadsetServiceTest { HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mCurrentDevice); - try { - mHeadsetService.messageFromNative(connectingEvent); - Assert.fail("Expect an IllegalStateException"); - } catch (IllegalStateException exception) { - // Do nothing - } + assertThrows( + IllegalStateException.class, + () -> mHeadsetService.messageFromNative(connectingEvent)); verifyNoMoreInteractions(mObjectsFactory); } @@ -450,20 +439,17 @@ public class HeadsetServiceTest { // Put device to connecting when(mStateMachines.get(mCurrentDevice).getConnectionState()) .thenReturn(BluetoothProfile.STATE_CONNECTING); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); // Put device to connected connectedDevices.add(mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getConnectionState()) .thenReturn(BluetoothProfile.STATE_CONNECTED); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); } // Connect the next device will fail mCurrentDevice = TestUtils.getTestDevice(mAdapter, MAX_HEADSET_CONNECTIONS); @@ -477,12 +463,10 @@ public class HeadsetServiceTest { eq(mAdapterService), eq(mNativeInterface), eq(mSystemInterface)); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); } /** @@ -512,40 +496,37 @@ public class HeadsetServiceTest { .thenReturn(BluetoothProfile.STATE_CONNECTED); when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()) .thenReturn(SystemClock.uptimeMillis()); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); - Assert.assertEquals( - Collections.singletonList(mCurrentDevice), mHeadsetService.getConnectedDevices()); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); + assertThat(mHeadsetService.getConnectedDevices()).isEqualTo(List.of(mCurrentDevice)); mHeadsetService.onConnectionStateChangedFromStateMachine( mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED); // Test connect audio - set the device first as the active device assertThat(mHeadsetService.setActiveDevice(mCurrentDevice)).isTrue(); - Assert.assertEquals( - BluetoothStatusCodes.SUCCESS, mHeadsetService.connectAudio(mCurrentDevice)); + assertThat(mHeadsetService.connectAudio(mCurrentDevice)) + .isEqualTo(BluetoothStatusCodes.SUCCESS); verify(mStateMachines.get(mCurrentDevice)) .sendMessage(HeadsetStateMachine.CONNECT_AUDIO, mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getAudioState()) .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTING); // 2nd connection attempt for the same device will succeed as well - Assert.assertEquals( - BluetoothStatusCodes.SUCCESS, mHeadsetService.connectAudio(mCurrentDevice)); + assertThat(mHeadsetService.connectAudio(mCurrentDevice)) + .isEqualTo(BluetoothStatusCodes.SUCCESS); // Verify CONNECT_AUDIO is only sent once verify(mStateMachines.get(mCurrentDevice)) .sendMessage(eq(HeadsetStateMachine.CONNECT_AUDIO), any()); // Test disconnect audio - Assert.assertEquals( - BluetoothStatusCodes.SUCCESS, mHeadsetService.disconnectAudio(mCurrentDevice)); + assertThat(mHeadsetService.disconnectAudio(mCurrentDevice)) + .isEqualTo(BluetoothStatusCodes.SUCCESS); verify(mStateMachines.get(mCurrentDevice)) .sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getAudioState()) .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED); // Further disconnection requests will fail - Assert.assertEquals( - BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED, - mHeadsetService.disconnectAudio(mCurrentDevice)); + assertThat(mHeadsetService.disconnectAudio(mCurrentDevice)) + .isEqualTo(BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED); verify(mStateMachines.get(mCurrentDevice)) .sendMessage(eq(HeadsetStateMachine.DISCONNECT_AUDIO), any(BluetoothDevice.class)); } @@ -590,9 +571,8 @@ public class HeadsetServiceTest { mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); // Put device to connected connectedDevices.add(mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); @@ -600,33 +580,30 @@ public class HeadsetServiceTest { .thenReturn(BluetoothProfile.STATE_CONNECTED); when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()) .thenReturn(SystemClock.uptimeMillis()); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); mHeadsetService.onConnectionStateChangedFromStateMachine( mCurrentDevice, BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); // Try to connect audio // Should fail - Assert.assertEquals( - BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE, - mHeadsetService.connectAudio(mCurrentDevice)); + assertThat(mHeadsetService.connectAudio(mCurrentDevice)) + .isEqualTo(BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE); // Should succeed after setActiveDevice() assertThat(mHeadsetService.setActiveDevice(mCurrentDevice)).isTrue(); - Assert.assertEquals( - BluetoothStatusCodes.SUCCESS, mHeadsetService.connectAudio(mCurrentDevice)); + assertThat(mHeadsetService.connectAudio(mCurrentDevice)) + .isEqualTo(BluetoothStatusCodes.SUCCESS); verify(mStateMachines.get(mCurrentDevice)) .sendMessage(HeadsetStateMachine.CONNECT_AUDIO, mCurrentDevice); // Put device to audio connecting state when(mStateMachines.get(mCurrentDevice).getAudioState()) .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTING); // 2nd connection attempt will also succeed - Assert.assertEquals( - BluetoothStatusCodes.SUCCESS, mHeadsetService.connectAudio(mCurrentDevice)); + assertThat(mHeadsetService.connectAudio(mCurrentDevice)) + .isEqualTo(BluetoothStatusCodes.SUCCESS); // Verify CONNECT_AUDIO is only sent once verify(mStateMachines.get(mCurrentDevice)) .sendMessage(eq(HeadsetStateMachine.CONNECT_AUDIO), any()); @@ -634,16 +611,15 @@ public class HeadsetServiceTest { when(mStateMachines.get(mCurrentDevice).getAudioState()) .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED); // Disconnect audio - Assert.assertEquals( - BluetoothStatusCodes.SUCCESS, mHeadsetService.disconnectAudio(mCurrentDevice)); + assertThat(mHeadsetService.disconnectAudio(mCurrentDevice)) + .isEqualTo(BluetoothStatusCodes.SUCCESS); verify(mStateMachines.get(mCurrentDevice)) .sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getAudioState()) .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED); // Further disconnection requests will fail - Assert.assertEquals( - BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED, - mHeadsetService.disconnectAudio(mCurrentDevice)); + assertThat(mHeadsetService.disconnectAudio(mCurrentDevice)) + .isEqualTo(BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED); verify(mStateMachines.get(mCurrentDevice)) .sendMessage( eq(HeadsetStateMachine.DISCONNECT_AUDIO), any(BluetoothDevice.class)); @@ -691,9 +667,8 @@ public class HeadsetServiceTest { mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); // Put device to connected connectedDevices.add(mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); @@ -705,12 +680,10 @@ public class HeadsetServiceTest { mCurrentDevice, BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); } if (MAX_HEADSET_CONNECTIONS >= 2) { // Try to connect audio @@ -718,29 +691,27 @@ public class HeadsetServiceTest { BluetoothDevice secondDevice = connectedDevices.get(1); // Set the first device as the active device assertThat(mHeadsetService.setActiveDevice(firstDevice)).isTrue(); - Assert.assertEquals( - BluetoothStatusCodes.SUCCESS, mHeadsetService.connectAudio(firstDevice)); + assertThat(mHeadsetService.connectAudio(firstDevice)) + .isEqualTo(BluetoothStatusCodes.SUCCESS); verify(mStateMachines.get(firstDevice)) .sendMessage(HeadsetStateMachine.CONNECT_AUDIO, firstDevice); // Put device to audio connecting state when(mStateMachines.get(firstDevice).getAudioState()) .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTING); // 2nd connection attempt will succeed for the same device - Assert.assertEquals( - BluetoothStatusCodes.SUCCESS, mHeadsetService.connectAudio(firstDevice)); + assertThat(mHeadsetService.connectAudio(firstDevice)) + .isEqualTo(BluetoothStatusCodes.SUCCESS); // Connect to 2nd device will fail - Assert.assertEquals( - BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE, - mHeadsetService.connectAudio(secondDevice)); + assertThat(mHeadsetService.connectAudio(secondDevice)) + .isEqualTo(BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE); verify(mStateMachines.get(secondDevice), never()) .sendMessage(HeadsetStateMachine.CONNECT_AUDIO, secondDevice); // Put device to audio connected state when(mStateMachines.get(firstDevice).getAudioState()) .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED); // Connect to 2nd device will fail - Assert.assertEquals( - BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE, - mHeadsetService.connectAudio(secondDevice)); + assertThat(mHeadsetService.connectAudio(secondDevice)) + .isEqualTo(BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE); verify(mStateMachines.get(secondDevice), never()) .sendMessage(HeadsetStateMachine.CONNECT_AUDIO, secondDevice); } @@ -796,9 +767,8 @@ public class HeadsetServiceTest { mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); // Put device to connected connectedDevices.add(mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); @@ -806,12 +776,10 @@ public class HeadsetServiceTest { .thenReturn(BluetoothProfile.STATE_CONNECTED); when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()) .thenReturn(SystemClock.uptimeMillis()); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); mHeadsetService.onConnectionStateChangedFromStateMachine( mCurrentDevice, BluetoothProfile.STATE_CONNECTING, @@ -820,7 +788,7 @@ public class HeadsetServiceTest { // Try to connect audio BluetoothDevice firstDevice = connectedDevices.get(0); assertThat(mHeadsetService.setActiveDevice(firstDevice)).isTrue(); - Assert.assertEquals(BluetoothStatusCodes.SUCCESS, mHeadsetService.connectAudio()); + assertThat(mHeadsetService.connectAudio()).isEqualTo(BluetoothStatusCodes.SUCCESS); verify(mStateMachines.get(firstDevice)) .sendMessage(HeadsetStateMachine.CONNECT_AUDIO, firstDevice); } @@ -832,9 +800,8 @@ public class HeadsetServiceTest { @Test public void testConnectAudio_deviceNeverConnected() { mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); - Assert.assertEquals( - BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED, - mHeadsetService.connectAudio(mCurrentDevice)); + assertThat(mHeadsetService.connectAudio(mCurrentDevice)) + .isEqualTo(BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED); } /** @@ -862,18 +829,16 @@ public class HeadsetServiceTest { // Put device in disconnected state when(mStateMachines.get(mCurrentDevice).getConnectionState()) .thenReturn(BluetoothProfile.STATE_DISCONNECTED); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); - Assert.assertEquals(Collections.EMPTY_LIST, mHeadsetService.getConnectedDevices()); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + assertThat(mHeadsetService.getConnectedDevices()).isEmpty(); mHeadsetService.onConnectionStateChangedFromStateMachine( mCurrentDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); // connectAudio should fail - Assert.assertEquals( - BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE, - mHeadsetService.connectAudio(mCurrentDevice)); + assertThat(mHeadsetService.connectAudio(mCurrentDevice)) + .isEqualTo(BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE); verify(mStateMachines.get(mCurrentDevice), never()) .sendMessage(eq(HeadsetStateMachine.CONNECT_AUDIO), any()); } @@ -943,9 +908,8 @@ public class HeadsetServiceTest { mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); // Put device to connected connectedDevices.add(mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); @@ -953,12 +917,10 @@ public class HeadsetServiceTest { .thenReturn(BluetoothProfile.STATE_CONNECTED); when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()) .thenReturn(SystemClock.uptimeMillis()); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); mHeadsetService.onConnectionStateChangedFromStateMachine( mCurrentDevice, BluetoothProfile.STATE_CONNECTING, @@ -1055,9 +1017,8 @@ public class HeadsetServiceTest { mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); // Put device to connected connectedDevices.add(mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); @@ -1065,12 +1026,10 @@ public class HeadsetServiceTest { .thenReturn(BluetoothProfile.STATE_CONNECTED); when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()) .thenReturn(SystemClock.uptimeMillis()); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); - Assert.assertThat( - mHeadsetService.getConnectedDevices(), - Matchers.containsInAnyOrder(connectedDevices.toArray())); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); + assertThat(mHeadsetService.getConnectedDevices()) + .containsExactlyElementsIn(connectedDevices); mHeadsetService.onConnectionStateChangedFromStateMachine( mCurrentDevice, BluetoothProfile.STATE_CONNECTING, @@ -1118,9 +1077,8 @@ public class HeadsetServiceTest { when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); when(mStateMachines.get(mCurrentDevice).getConnectionState()) .thenReturn(BluetoothProfile.STATE_CONNECTED); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, - mHeadsetService.getConnectionState(mCurrentDevice)); + assertThat(mHeadsetService.getConnectionState(mCurrentDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); mHeadsetService.clccResponse(1, 0, 0, 0, false, "8225319000", 0); // index 0 is the end mark of CLCC response. mHeadsetService.clccResponse(0, 0, 0, 0, false, "8225319000", 0); @@ -1185,23 +1143,23 @@ public class HeadsetServiceTest { // Test whether active device been removed after enable silence mode. assertThat(mHeadsetService.setActiveDevice(mCurrentDevice)).isTrue(); - Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice()); + assertThat(mHeadsetService.getActiveDevice()).isEqualTo(mCurrentDevice); assertThat(mHeadsetService.setSilenceMode(mCurrentDevice, true)).isTrue(); assertThat(mHeadsetService.getActiveDevice()).isNull(); // Test whether active device been resumed after disable silence mode. assertThat(mHeadsetService.setSilenceMode(mCurrentDevice, false)).isTrue(); - Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice()); + assertThat(mHeadsetService.getActiveDevice()).isEqualTo(mCurrentDevice); // Test that active device should not be changed when silence a non-active device assertThat(mHeadsetService.setActiveDevice(mCurrentDevice)).isTrue(); - Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice()); + assertThat(mHeadsetService.getActiveDevice()).isEqualTo(mCurrentDevice); assertThat(mHeadsetService.setSilenceMode(otherDevice, true)).isTrue(); - Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice()); + assertThat(mHeadsetService.getActiveDevice()).isEqualTo(mCurrentDevice); // Test that active device should not be changed when another device exits silence mode assertThat(mHeadsetService.setSilenceMode(otherDevice, false)).isTrue(); - Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice()); + assertThat(mHeadsetService.getActiveDevice()).isEqualTo(mCurrentDevice); } /** Test that whether active device been removed after enable silence mode */ @@ -1224,7 +1182,7 @@ public class HeadsetServiceTest { // Test that active device should not be changed if audio is not allowed assertThat(mHeadsetService.setActiveDevice(mCurrentDevice)).isFalse(); - Assert.assertEquals(null, mHeadsetService.getActiveDevice()); + assertThat(mHeadsetService.getActiveDevice()).isNull(); } @Test @@ -1277,6 +1235,27 @@ public class HeadsetServiceTest { } @Test + public void testGetFallbackCandidates_HasWatchDeviceWithCod() { + BluetoothDevice deviceWatch = TestUtils.getTestDevice(mAdapter, 0); + BluetoothDevice deviceRegular = TestUtils.getTestDevice(mAdapter, 1); + + // Make deviceWatch as watch with COD + when(mAdapterService.getRemoteClass(deviceWatch)) + .thenReturn(BluetoothClass.Device.WEARABLE_WRIST_WATCH); + when(mAdapterService.getRemoteClass(deviceRegular)) + .thenReturn(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE); + + // Has a connected watch device + addConnectedDeviceHelper(deviceWatch); + assertThat(mHeadsetService.getFallbackCandidates(mDatabaseManager)).isEmpty(); + + // Two connected devices with one watch + addConnectedDeviceHelper(deviceRegular); + assertThat(mHeadsetService.getFallbackCandidates(mDatabaseManager)) + .containsExactly(deviceRegular); + } + + @Test public void testConnectDeviceNotAllowedInbandRingPolicy_InbandRingStatus() { when(mDatabaseManager.getProfileConnectionPolicy( any(BluetoothDevice.class), eq(BluetoothProfile.HEADSET))) @@ -1288,8 +1267,7 @@ public class HeadsetServiceTest { .thenReturn(BluetoothProfile.STATE_CONNECTED); when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()) .thenReturn(SystemClock.uptimeMillis()); - Assert.assertEquals( - Collections.singletonList(mCurrentDevice), mHeadsetService.getConnectedDevices()); + assertThat(mHeadsetService.getConnectedDevices()).isEqualTo(List.of(mCurrentDevice)); mHeadsetService.onConnectionStateChangedFromStateMachine( mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED, @@ -1304,7 +1282,7 @@ public class HeadsetServiceTest { BluetoothSinkAudioPolicy.POLICY_ALLOWED) .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED) .build()); - Assert.assertEquals(true, mHeadsetService.isInbandRingingEnabled()); + assertThat(mHeadsetService.isInbandRingingEnabled()).isTrue(); when(mStateMachines.get(mCurrentDevice).getHfpCallAudioPolicy()) .thenReturn( @@ -1315,7 +1293,7 @@ public class HeadsetServiceTest { .setInBandRingtonePolicy( BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) .build()); - Assert.assertEquals(false, mHeadsetService.isInbandRingingEnabled()); + assertThat(mHeadsetService.isInbandRingingEnabled()).isFalse(); } private void addConnectedDeviceHelper(BluetoothDevice device) { @@ -1327,12 +1305,12 @@ public class HeadsetServiceTest { when(mStateMachines.get(device).getDevice()).thenReturn(device); when(mStateMachines.get(device).getConnectionState()) .thenReturn(BluetoothProfile.STATE_CONNECTING); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTING, mHeadsetService.getConnectionState(device)); + assertThat(mHeadsetService.getConnectionState(device)) + .isEqualTo(BluetoothProfile.STATE_CONNECTING); when(mStateMachines.get(mCurrentDevice).getConnectionState()) .thenReturn(BluetoothProfile.STATE_CONNECTED); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, mHeadsetService.getConnectionState(device)); + assertThat(mHeadsetService.getConnectionState(device)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); assertThat(mHeadsetService.getConnectedDevices()).contains(device); } @@ -1349,6 +1327,6 @@ public class HeadsetServiceTest { doReturn(bondState).when(mAdapterService).getBondState(device); when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET)) .thenReturn(priority); - Assert.assertEquals(expected, mHeadsetService.okToAcceptConnection(device, false)); + assertThat(mHeadsetService.okToAcceptConnection(device, false)).isEqualTo(expected); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java index 75dfc971a1..0b42a50d92 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java @@ -66,9 +66,7 @@ import com.android.bluetooth.btservice.SilenceDeviceManager; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.flags.Flags; -import org.hamcrest.core.IsInstanceOf; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -186,22 +184,20 @@ public class HeadsetStateMachineTest { /** Test that default state is Disconnected */ @Test public void testDefaultDisconnectedState() { - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, mHeadsetStateMachine.getConnectionState()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class)); + assertThat(mHeadsetStateMachine.getConnectionState()) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnected.class); } /** Test that state is Connected after calling setUpConnectedState() */ @Test public void testSetupConnectedState() { setUpConnectedState(); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, mHeadsetStateMachine.getConnectionState()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class)); + assertThat(mHeadsetStateMachine.getConnectionState()) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connected.class); } /** Test state transition from Disconnected to Connecting state via CONNECT message */ @@ -219,9 +215,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connecting.class); } /** @@ -246,9 +241,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connecting.class); } /** @@ -273,9 +267,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connecting.class); } /** @@ -296,9 +289,8 @@ public class HeadsetStateMachineTest { verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)) .sendBroadcastAsUser( any(Intent.class), any(UserHandle.class), anyString(), any(Bundle.class)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connecting.class); // Indicate connection failed to test state machine mHeadsetStateMachine.sendMessage( @@ -320,9 +312,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnected.class); } /** Test state transition from Connecting to Disconnected state via CONNECT_TIMEOUT message */ @@ -342,9 +333,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnected.class); } /** @@ -364,9 +354,8 @@ public class HeadsetStateMachineTest { verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)) .sendBroadcastAsUser( any(Intent.class), any(UserHandle.class), anyString(), any(Bundle.class)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connecting.class); // Indicate RFCOMM connection is successful to test state machine mHeadsetStateMachine.sendMessage( @@ -379,9 +368,8 @@ public class HeadsetStateMachineTest { verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)) .sendBroadcastAsUser( any(Intent.class), any(UserHandle.class), anyString(), any(Bundle.class)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connecting.class); // Indicate SLC connection is successful to test state machine numBroadcastsSent++; @@ -402,9 +390,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connected.class); } /** @@ -433,9 +420,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_DISCONNECTING, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnected.class); } /** @@ -457,9 +443,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_DISCONNECTING, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnected.class); } /** @@ -488,9 +473,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTING, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connected.class); } /** Test state transition from Connected to Disconnecting state via DISCONNECT message */ @@ -511,9 +495,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnecting.class); } /** @@ -542,9 +525,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnecting.class); } /** @@ -573,9 +555,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnected.class); } /** Test state transition from Connected to AudioConnecting state via CONNECT_AUDIO message */ @@ -596,9 +577,8 @@ public class HeadsetStateMachineTest { BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioConnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioConnecting.class); } /** @@ -616,9 +596,8 @@ public class HeadsetStateMachineTest { // verify no native connect audio verify(mNativeInterface, never()).connectAudio(mTestDevice); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioConnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioConnecting.class); Utils.setIsScoManagedByAudioEnabled(false); } @@ -648,9 +627,8 @@ public class HeadsetStateMachineTest { BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioConnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioConnecting.class); } /** @@ -678,9 +656,8 @@ public class HeadsetStateMachineTest { BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioOn.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioOn.class); } /** Test state transition from AudioConnecting to Connected state via CONNECT_TIMEOUT message */ @@ -700,9 +677,8 @@ public class HeadsetStateMachineTest { BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connected.class); } /** @@ -731,9 +707,8 @@ public class HeadsetStateMachineTest { BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connected.class); } /** @@ -767,9 +742,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED, mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 1)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnected.class); } /** @@ -803,9 +777,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED, mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 1)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnecting.class); } /** @@ -834,9 +807,8 @@ public class HeadsetStateMachineTest { BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioOn.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioOn.class); } /** @@ -859,9 +831,8 @@ public class HeadsetStateMachineTest { eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT), any(Bundle.class)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioDisconnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioDisconnecting.class); } /** @@ -879,9 +850,8 @@ public class HeadsetStateMachineTest { eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT), any(Bundle.class)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioDisconnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioDisconnecting.class); } /** @@ -910,9 +880,8 @@ public class HeadsetStateMachineTest { BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connected.class); } /** Test state transition from AudioOn to Disconnected state via Stack.DISCONNECTED message */ @@ -943,9 +912,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED, mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 1)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnected.class); } /** Test state transition from AudioOn to Disconnecting state via Stack.DISCONNECTING message */ @@ -976,9 +944,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED, mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 1)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnecting.class); } /** @@ -1000,9 +967,8 @@ public class HeadsetStateMachineTest { eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT), any(Bundle.class)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioDisconnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioDisconnecting.class); if (i == MAX_RETRY_DISCONNECT_AUDIO) { // Increment twice numBroadcastsSent as DISCONNECT message is added on max retry numBroadcastsSent += 2; @@ -1024,18 +990,16 @@ public class HeadsetStateMachineTest { BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioOn.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioOn.class); } else { // Max retry count reached, test Disconnecting state HeadsetTestUtils.verifyConnectionStateBroadcast( mTestDevice, BluetoothHeadset.STATE_DISCONNECTING, BluetoothHeadset.STATE_CONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnecting.class); } } } @@ -1066,9 +1030,8 @@ public class HeadsetStateMachineTest { BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connected.class); } /** @@ -1097,9 +1060,8 @@ public class HeadsetStateMachineTest { BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioOn.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioOn.class); } /** @@ -1133,9 +1095,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED, mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 1)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnecting.class); } /** @@ -1169,9 +1130,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED, mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 1)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnected.class); } /** @@ -1331,7 +1291,7 @@ public class HeadsetStateMachineTest { verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).disconnectAudio(mTestDevice); } - /** A test to verfiy that we correctly handles AT+BIND event with driver safety case from HF */ + /** A test to verify that we correctly handles AT+BIND event with driver safety case from HF */ @Test public void testAtBindWithDriverSafetyEventWhenConnecting() { setUpConnectingState(); @@ -1344,22 +1304,21 @@ public class HeadsetStateMachineTest { verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) .sendBroadcast(intentArgument.capture(), eq(BLUETOOTH_CONNECT), any(Bundle.class)); verify(mHeadsetService).sendBroadcast(any(), any(), any()); - Assert.assertEquals( - mTestDevice, - intentArgument.getValue().getExtra(BluetoothDevice.EXTRA_DEVICE, null)); - Assert.assertEquals( - HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY, - intentArgument - .getValue() - .getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1)); - Assert.assertEquals( - -1, - intentArgument - .getValue() - .getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -2)); + assertThat(intentArgument.getValue().getExtra(BluetoothDevice.EXTRA_DEVICE, null)) + .isEqualTo(mTestDevice); + assertThat( + intentArgument + .getValue() + .getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1)) + .isEqualTo(HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY); + assertThat( + intentArgument + .getValue() + .getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -2)) + .isEqualTo(-1); } - /** A test to verfiy that we correctly handles AT+BIND event with battery level case from HF */ + /** A test to verify that we correctly handles AT+BIND event with battery level case from HF */ @Test public void testAtBindEventWithBatteryLevelEventWhenConnecting() { setUpConnectingState(); @@ -1372,22 +1331,21 @@ public class HeadsetStateMachineTest { verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) .sendBroadcast(intentArgument.capture(), eq(BLUETOOTH_CONNECT), any(Bundle.class)); verify(mHeadsetService).sendBroadcast(any(), any(), any()); - Assert.assertEquals( - mTestDevice, - intentArgument.getValue().getExtra(BluetoothDevice.EXTRA_DEVICE, null)); - Assert.assertEquals( - HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS, - intentArgument - .getValue() - .getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1)); - Assert.assertEquals( - -1, - intentArgument - .getValue() - .getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -2)); + assertThat(intentArgument.getValue().getExtra(BluetoothDevice.EXTRA_DEVICE, null)) + .isEqualTo(mTestDevice); + assertThat( + intentArgument + .getValue() + .getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1)) + .isEqualTo(HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS); + assertThat( + intentArgument + .getValue() + .getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -2)) + .isEqualTo(-1); } - /** A test to verfiy that we correctly handles AT+BIND event with error case from HF */ + /** A test to verify that we correctly handles AT+BIND event with error case from HF */ @Test public void testAtBindEventWithErrorEventWhenConnecting() { setUpConnectingState(); @@ -1400,19 +1358,18 @@ public class HeadsetStateMachineTest { verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) .sendBroadcast(intentArgument.capture(), eq(BLUETOOTH_CONNECT), any(Bundle.class)); verify(mHeadsetService).sendBroadcast(any(), any(), any()); - Assert.assertEquals( - mTestDevice, - intentArgument.getValue().getExtra(BluetoothDevice.EXTRA_DEVICE, null)); - Assert.assertEquals( - HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY, - intentArgument - .getValue() - .getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1)); - Assert.assertEquals( - -1, - intentArgument - .getValue() - .getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -2)); + assertThat(intentArgument.getValue().getExtra(BluetoothDevice.EXTRA_DEVICE, null)) + .isEqualTo(mTestDevice); + assertThat( + intentArgument + .getValue() + .getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1)) + .isEqualTo(HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY); + assertThat( + intentArgument + .getValue() + .getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -2)) + .isEqualTo(-1); } /** A test to verify that we correctly set AG indicator mask when enter/exit silence mode */ @@ -1442,7 +1399,7 @@ public class HeadsetStateMachineTest { String input = "test"; int fromIndex = 0; - Assert.assertEquals(HeadsetStateMachine.findChar(ch, input, fromIndex), 2); + assertThat(HeadsetStateMachine.findChar(ch, input, fromIndex)).isEqualTo(2); } @Test @@ -1451,7 +1408,7 @@ public class HeadsetStateMachineTest { String input = "test"; int fromIndex = 0; - Assert.assertEquals(HeadsetStateMachine.findChar(ch, input, fromIndex), input.length()); + assertThat(HeadsetStateMachine.findChar(ch, input, fromIndex)).isEqualTo(input.length()); } @Test @@ -1460,7 +1417,7 @@ public class HeadsetStateMachineTest { String input = "te\"st"; int fromIndex = 0; - Assert.assertEquals(HeadsetStateMachine.findChar(ch, input, fromIndex), input.length()); + assertThat(HeadsetStateMachine.findChar(ch, input, fromIndex)).isEqualTo(input.length()); } @Test @@ -1470,46 +1427,47 @@ public class HeadsetStateMachineTest { expected.add(11); expected.add("notint"); - Assert.assertEquals(HeadsetStateMachine.generateArgs(input), expected.toArray()); + assertThat(HeadsetStateMachine.generateArgs(input)).isEqualTo(expected.toArray()); } @Test public void testGetAtCommandType() { String atCommand = "start?"; - Assert.assertEquals( - mHeadsetStateMachine.getAtCommandType(atCommand), AtPhonebook.TYPE_READ); + assertThat(mHeadsetStateMachine.getAtCommandType(atCommand)) + .isEqualTo(AtPhonebook.TYPE_READ); atCommand = "start=?"; - Assert.assertEquals( - mHeadsetStateMachine.getAtCommandType(atCommand), AtPhonebook.TYPE_TEST); + assertThat(mHeadsetStateMachine.getAtCommandType(atCommand)) + .isEqualTo(AtPhonebook.TYPE_TEST); atCommand = "start=comm"; - Assert.assertEquals(mHeadsetStateMachine.getAtCommandType(atCommand), AtPhonebook.TYPE_SET); + assertThat(mHeadsetStateMachine.getAtCommandType(atCommand)) + .isEqualTo(AtPhonebook.TYPE_SET); atCommand = "start!"; - Assert.assertEquals( - mHeadsetStateMachine.getAtCommandType(atCommand), AtPhonebook.TYPE_UNKNOWN); + assertThat(mHeadsetStateMachine.getAtCommandType(atCommand)) + .isEqualTo(AtPhonebook.TYPE_UNKNOWN); } @Test public void testParseUnknownAt() { String atString = "\"command\""; - Assert.assertEquals(mHeadsetStateMachine.parseUnknownAt(atString), "\"command\""); + assertThat(mHeadsetStateMachine.parseUnknownAt(atString)).isEqualTo("\"command\""); } @Test public void testParseUnknownAt_withUnmatchingQuotes() { String atString = "\"command"; - Assert.assertEquals(mHeadsetStateMachine.parseUnknownAt(atString), "\"command\""); + assertThat(mHeadsetStateMachine.parseUnknownAt(atString)).isEqualTo("\"command\""); } @Test public void testParseUnknownAt_withCharOutsideQuotes() { String atString = "a\"command\""; - Assert.assertEquals(mHeadsetStateMachine.parseUnknownAt(atString), "A\"command\""); + assertThat(mHeadsetStateMachine.parseUnknownAt(atString)).isEqualTo("A\"command\""); } @Ignore("b/265556073") @@ -1803,7 +1761,7 @@ public class HeadsetStateMachineTest { mHeadsetStateMachine.processVolumeEvent(HeadsetHalConstants.VOLUME_TYPE_MIC, 1); - Assert.assertEquals(mHeadsetStateMachine.mMicVolume, 1); + assertThat(mHeadsetStateMachine.mMicVolume).isEqualTo(1); } @RequiresFlagsDisabled(FLAG_DEPRECATE_STREAM_BT_SCO) @@ -1816,7 +1774,7 @@ public class HeadsetStateMachineTest { mHeadsetStateMachine.processVolumeEvent(HeadsetHalConstants.VOLUME_TYPE_SPK, 2); - Assert.assertEquals(mHeadsetStateMachine.mSpeakerVolume, 2); + assertThat(mHeadsetStateMachine.mSpeakerVolume).isEqualTo(2); verify(mockAudioManager).setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, 2, 0); } @@ -1830,33 +1788,11 @@ public class HeadsetStateMachineTest { mHeadsetStateMachine.processVolumeEvent(HeadsetHalConstants.VOLUME_TYPE_SPK, 2); - Assert.assertEquals(mHeadsetStateMachine.mSpeakerVolume, 2); + assertThat(mHeadsetStateMachine.mSpeakerVolume).isEqualTo(2); verify(mockAudioManager).setStreamVolume(AudioManager.STREAM_VOICE_CALL, 2, 0); } @Test - @EnableFlags({Flags.FLAG_HFP_ALLOW_VOLUME_CHANGE_WITHOUT_SCO}) - public void testVolumeChangeEvent_fromIntentWhenConnected() { - setUpConnectedState(); - int originalVolume = mHeadsetStateMachine.mSpeakerVolume; - mHeadsetStateMachine.mSpeakerVolume = 0; - int vol = 10; - - // Send INTENT_SCO_VOLUME_CHANGED message - Intent volumeChange = new Intent(AudioManager.ACTION_VOLUME_CHANGED); - volumeChange.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, vol); - - mHeadsetStateMachine.sendMessage( - HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED, volumeChange); - TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - - // verify volume processed - verify(mNativeInterface).setVolume(mTestDevice, HeadsetHalConstants.VOLUME_TYPE_SPK, vol); - - mHeadsetStateMachine.mSpeakerVolume = originalVolume; - } - - @Test public void testVolumeChangeEvent_fromIntentWhenAudioOn() { setUpAudioOnState(); int originalVolume = mHeadsetStateMachine.mSpeakerVolume; @@ -2169,9 +2105,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connecting.class); return 1; } @@ -2199,9 +2134,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connecting.class); mHeadsetStateMachine.sendMessage( HeadsetStateMachine.STACK_EVENT, new HeadsetStackEvent( @@ -2219,9 +2153,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Connected.class); return 2; } @@ -2241,9 +2174,8 @@ public class HeadsetStateMachineTest { BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioConnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioConnecting.class); return numBroadcastsSent; } @@ -2268,9 +2200,8 @@ public class HeadsetStateMachineTest { BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioOn.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioOn.class); return numBroadcastsSent; } @@ -2284,9 +2215,8 @@ public class HeadsetStateMachineTest { eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT), any(Bundle.class)); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.AudioDisconnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.AudioDisconnecting.class); return numBroadcastsSent; } @@ -2306,9 +2236,8 @@ public class HeadsetStateMachineTest { BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED, mIntentArgument.getValue()); - Assert.assertThat( - mHeadsetStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class)); + assertThat(mHeadsetStateMachine.getCurrentState()) + .isInstanceOf(HeadsetStateMachine.Disconnecting.class); return numBroadcastsSent; } diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetTestUtils.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetTestUtils.java index e80048dafa..4b2d0b291c 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetTestUtils.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetTestUtils.java @@ -25,8 +25,6 @@ import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothProfile; import android.content.Intent; -import org.junit.Assert; - /** Helper functions for HFP related tests */ public class HeadsetTestUtils { @@ -41,11 +39,12 @@ public class HeadsetTestUtils { public static void verifyAudioStateBroadcast( BluetoothDevice device, int toState, int fromState, Intent intent) { assertThat(intent).isNotNull(); - Assert.assertEquals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED, intent.getAction()); - Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); - Assert.assertEquals(toState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); - Assert.assertEquals( - fromState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1)); + assertThat(intent.getAction()).isEqualTo(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); + assertThat(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class)) + .isEqualTo(device); + assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)).isEqualTo(toState); + assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1)) + .isEqualTo(fromState); } /** @@ -61,14 +60,15 @@ public class HeadsetTestUtils { public static void verifyConnectionStateBroadcast( BluetoothDevice device, int toState, int fromState, Intent intent, boolean checkFlag) { assertThat(intent).isNotNull(); - Assert.assertEquals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED, intent.getAction()); + assertThat(intent.getAction()).isEqualTo(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); if (checkFlag) { - Assert.assertEquals(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, intent.getFlags()); + assertThat(intent.getFlags()).isEqualTo(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); } - Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); - Assert.assertEquals(toState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); - Assert.assertEquals( - fromState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1)); + assertThat(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class)) + .isEqualTo(device); + assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)).isEqualTo(toState); + assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1)) + .isEqualTo(fromState); } /** @@ -97,13 +97,14 @@ public class HeadsetTestUtils { public static void verifyActiveDeviceChangedBroadcast( BluetoothDevice device, Intent intent, boolean checkFlag) { assertThat(intent).isNotNull(); - Assert.assertEquals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED, intent.getAction()); - Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); + assertThat(intent.getAction()).isEqualTo(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); + assertThat(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class)) + .isEqualTo(device); if (checkFlag) { - Assert.assertEquals( - Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT - | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, - intent.getFlags()); + assertThat(intent.getFlags()) + .isEqualTo( + Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java index 3541670116..543aca2578 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java @@ -39,8 +39,6 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.media.AudioManager; import android.os.BatteryManager; -import android.platform.test.annotations.EnableFlags; -import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; @@ -49,10 +47,8 @@ import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.RemoteDevices; import com.android.bluetooth.btservice.storage.DatabaseManager; -import com.android.bluetooth.flags.Flags; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -71,7 +67,6 @@ import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class HeadsetClientServiceTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private AdapterService mAdapterService; @Mock private HeadsetClientStateMachine mStateMachine; @@ -211,7 +206,6 @@ public class HeadsetClientServiceTest { * back HF values and checks if they match AM. This proves that the conversion is symmetric. */ @Test - @EnableFlags(Flags.FLAG_HEADSET_CLIENT_AM_HF_VOLUME_SYMMETRIC) public void testAmHfVolumeSymmetric_AmLowerRange() { int amMin = 1; int amMax = 10; @@ -231,7 +225,7 @@ public class HeadsetClientServiceTest { for (Map.Entry entry : amToHfMap.entrySet()) { // Convert back from collected HF to AM and check if equal the saved AM value - Assert.assertEquals(service.hfToAmVol((int) entry.getValue()), entry.getKey()); + assertThat(service.hfToAmVol((int) entry.getValue())).isEqualTo(entry.getKey()); } } @@ -241,7 +235,6 @@ public class HeadsetClientServiceTest { * back AM values and checks if they match HF. This proves that the conversion is symmetric. */ @Test - @EnableFlags(Flags.FLAG_HEADSET_CLIENT_AM_HF_VOLUME_SYMMETRIC) public void testAmHfVolumeSymmetric_HfLowerRange() { int amMin = 1; int amMax = 20; @@ -261,7 +254,7 @@ public class HeadsetClientServiceTest { for (Map.Entry entry : hfToAmMap.entrySet()) { // Convert back from collected AM to HF and check if equal the saved HF value - Assert.assertEquals(service.amToHfVol((int) entry.getValue()), entry.getKey()); + assertThat(service.amToHfVol((int) entry.getValue())).isEqualTo(entry.getKey()); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java index 2ee7fa089b..0a74cdd777 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java @@ -1029,7 +1029,7 @@ public class HeadsetClientStateMachineTest { mHeadsetClientStateMachine.setAudioPolicyRemoteSupported(true); mHeadsetClientStateMachine.setAudioPolicy(dummyAudioPolicy); verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=SINKAUDIOPOLICY,1,2,1"); - assertThat(mHeadsetClientStateMachine.mQueuedActions.size()).isEqualTo(1); + assertThat(mHeadsetClientStateMachine.mQueuedActions).hasSize(1); mHeadsetClientStateMachine.mQueuedActions.clear(); // Test if fail to sendAndroidAt diff --git a/android/app/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java index fc33b519d7..337fb1faa3 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java @@ -41,7 +41,6 @@ import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.storage.DatabaseManager; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -124,7 +123,7 @@ public class HidDeviceTest { field.setAccessible(true); HidDeviceNativeInterface nativeInterface = (HidDeviceNativeInterface) field.get(mHidDeviceService); - Assert.assertEquals(nativeInterface, mHidDeviceNativeInterface); + assertThat(nativeInterface).isEqualTo(mHidDeviceNativeInterface); // Dummy SDP settings mSettings = @@ -184,11 +183,13 @@ public class HidDeviceTest { int timeoutMs, BluetoothDevice device, int newState, int prevState) { Intent intent = waitForIntent(timeoutMs, mConnectionStateChangedQueue); assertThat(intent).isNotNull(); - Assert.assertEquals(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED, intent.getAction()); - Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); - Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); - Assert.assertEquals( - prevState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1)); + assertThat(intent.getAction()) + .isEqualTo(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED); + assertThat(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class)) + .isEqualTo(device); + assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)).isEqualTo(newState); + assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1)) + .isEqualTo(prevState); } private void verifyCallback(int timeoutMs, int callbackType, BlockingQueue<Integer> queue) { @@ -196,7 +197,7 @@ public class HidDeviceTest { Integer lastCallback = queue.poll(timeoutMs, TimeUnit.MILLISECONDS); assertThat(lastCallback).isNotNull(); int lastCallbackType = lastCallback; - Assert.assertEquals(callbackType, lastCallbackType); + assertThat(callbackType).isEqualTo(lastCallbackType); } catch (InterruptedException e) { throw new AssertionError("Cannot obtain a callback from the queue", e); } @@ -261,7 +262,7 @@ public class HidDeviceTest { /** Test getting HidDeviceService: getHidDeviceService(). */ @Test public void testGetHidDeviceService() { - Assert.assertEquals(mHidDeviceService, HidDeviceService.getHidDeviceService()); + assertThat(HidDeviceService.getHidDeviceService()).isEqualTo(mHidDeviceService); } /** @@ -311,7 +312,7 @@ public class HidDeviceTest { // Unregister app doReturn(true).when(mHidDeviceNativeInterface).unregisterApp(); - Assert.assertEquals(true, mHidDeviceService.unregisterApp()); + assertThat(mHidDeviceService.unregisterApp()).isTrue(); verify(mHidDeviceNativeInterface).unregisterApp(); @@ -324,9 +325,8 @@ public class HidDeviceTest { public void testSendReport() throws Exception { doReturn(true).when(mHidDeviceNativeInterface).sendReport(anyInt(), any(byte[].class)); // sendReport() should fail without app registered - Assert.assertEquals( - false, - mHidDeviceService.sendReport(mTestDevice, SAMPLE_REPORT_ID, SAMPLE_HID_REPORT)); + assertThat(mHidDeviceService.sendReport(mTestDevice, SAMPLE_REPORT_ID, SAMPLE_HID_REPORT)) + .isFalse(); // Register app doReturn(true) @@ -349,16 +349,15 @@ public class HidDeviceTest { verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue); // sendReport() should work when app is registered - Assert.assertEquals( - true, - mHidDeviceService.sendReport(mTestDevice, SAMPLE_REPORT_ID, SAMPLE_HID_REPORT)); + assertThat(mHidDeviceService.sendReport(mTestDevice, SAMPLE_REPORT_ID, SAMPLE_HID_REPORT)) + .isTrue(); verify(mHidDeviceNativeInterface) .sendReport(eq((int) SAMPLE_REPORT_ID), eq(SAMPLE_HID_REPORT)); // Unregister app doReturn(true).when(mHidDeviceNativeInterface).unregisterApp(); - Assert.assertEquals(true, mHidDeviceService.unregisterApp()); + assertThat(mHidDeviceService.unregisterApp()).isTrue(); } /** Test the logic in replyReport(). This should fail when the app is not registered. */ @@ -368,10 +367,13 @@ public class HidDeviceTest { .when(mHidDeviceNativeInterface) .replyReport(anyByte(), anyByte(), any(byte[].class)); // replyReport() should fail without app registered - Assert.assertEquals( - false, - mHidDeviceService.replyReport( - mTestDevice, SAMPLE_REPORT_TYPE, SAMPLE_REPORT_ID, SAMPLE_HID_REPORT)); + assertThat( + mHidDeviceService.replyReport( + mTestDevice, + SAMPLE_REPORT_TYPE, + SAMPLE_REPORT_ID, + SAMPLE_HID_REPORT)) + .isFalse(); // Register app doReturn(true) @@ -394,17 +396,20 @@ public class HidDeviceTest { verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue); // replyReport() should work when app is registered - Assert.assertEquals( - true, - mHidDeviceService.replyReport( - mTestDevice, SAMPLE_REPORT_TYPE, SAMPLE_REPORT_ID, SAMPLE_HID_REPORT)); + assertThat( + mHidDeviceService.replyReport( + mTestDevice, + SAMPLE_REPORT_TYPE, + SAMPLE_REPORT_ID, + SAMPLE_HID_REPORT)) + .isTrue(); verify(mHidDeviceNativeInterface) .replyReport(eq(SAMPLE_REPORT_TYPE), eq(SAMPLE_REPORT_ID), eq(SAMPLE_HID_REPORT)); // Unregister app doReturn(true).when(mHidDeviceNativeInterface).unregisterApp(); - Assert.assertEquals(true, mHidDeviceService.unregisterApp()); + assertThat(mHidDeviceService.unregisterApp()).isTrue(); } /** Test the logic in reportError(). This should fail when the app is not registered. */ @@ -412,7 +417,7 @@ public class HidDeviceTest { public void testReportError() throws Exception { doReturn(true).when(mHidDeviceNativeInterface).reportError(anyByte()); // reportError() should fail without app registered - Assert.assertEquals(false, mHidDeviceService.reportError(mTestDevice, SAMPLE_REPORT_ERROR)); + assertThat(mHidDeviceService.reportError(mTestDevice, SAMPLE_REPORT_ERROR)).isFalse(); // Register app doReturn(true) @@ -435,13 +440,13 @@ public class HidDeviceTest { verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue); // reportError() should work when app is registered - Assert.assertEquals(true, mHidDeviceService.reportError(mTestDevice, SAMPLE_REPORT_ERROR)); + assertThat(mHidDeviceService.reportError(mTestDevice, SAMPLE_REPORT_ERROR)).isTrue(); verify(mHidDeviceNativeInterface).reportError(eq(SAMPLE_REPORT_ERROR)); // Unregister app doReturn(true).when(mHidDeviceNativeInterface).unregisterApp(); - Assert.assertEquals(true, mHidDeviceService.unregisterApp()); + assertThat(mHidDeviceService.unregisterApp()).isTrue(); } /** Test that an outgoing connection/disconnection succeeds */ @@ -477,9 +482,8 @@ public class HidDeviceTest { mTestDevice, BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTING, - mHidDeviceService.getConnectionState(mTestDevice)); + assertThat(mHidDeviceService.getConnectionState(mTestDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTING); mHidDeviceService.onConnectStateChangedFromNative( mTestDevice, HidDeviceService.HAL_CONN_STATE_CONNECTED); @@ -489,9 +493,8 @@ public class HidDeviceTest { mTestDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, - mHidDeviceService.getConnectionState(mTestDevice)); + assertThat(mHidDeviceService.getConnectionState(mTestDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); // Verify the list of connected devices assertThat( @@ -510,9 +513,8 @@ public class HidDeviceTest { mTestDevice, BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTING, - mHidDeviceService.getConnectionState(mTestDevice)); + assertThat(mHidDeviceService.getConnectionState(mTestDevice)) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTING); mHidDeviceService.onConnectStateChangedFromNative( mTestDevice, HidDeviceService.HAL_CONN_STATE_DISCONNECTED); @@ -522,9 +524,8 @@ public class HidDeviceTest { mTestDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_DISCONNECTING); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, - mHidDeviceService.getConnectionState(mTestDevice)); + assertThat(mHidDeviceService.getConnectionState(mTestDevice)) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); // Verify the list of connected devices assertThat( @@ -534,7 +535,7 @@ public class HidDeviceTest { // Unregister app doReturn(true).when(mHidDeviceNativeInterface).unregisterApp(); - Assert.assertEquals(true, mHidDeviceService.unregisterApp()); + assertThat(mHidDeviceService.unregisterApp()).isTrue(); } /** @@ -607,7 +608,7 @@ public class HidDeviceTest { // Unregister app doReturn(true).when(mHidDeviceNativeInterface).unregisterApp(); - Assert.assertEquals(true, mHidDeviceService.unregisterApp()); + assertThat(mHidDeviceService.unregisterApp()).isTrue(); verify(mHidDeviceNativeInterface).unregisterApp(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java index 7cf714c142..878279dae0 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java @@ -33,7 +33,6 @@ import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.flags.Flags; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -176,10 +175,10 @@ public class HidHostServiceTest { // Test when the AdapterService is in non-quiet mode. doReturn(false).when(mAdapterService).isQuietModeEnabled(); - Assert.assertEquals(expected, mService.okToConnect(device)); + assertThat(mService.okToConnect(device)).isEqualTo(expected); // Test when the AdapterService is in quiet mode. doReturn(true).when(mAdapterService).isQuietModeEnabled(); - Assert.assertEquals(false, mService.okToConnect(device)); + assertThat(mService.okToConnect(device)).isFalse(); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/ContentControlIdKeeperTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/ContentControlIdKeeperTest.java index fdd2aacad0..500e697502 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/ContentControlIdKeeperTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/ContentControlIdKeeperTest.java @@ -30,7 +30,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.btservice.ServiceFactory; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -64,15 +63,15 @@ public class ContentControlIdKeeperTest { public int testCcidAcquire(ParcelUuid uuid, int context, int expectedListSize) { int ccid = ContentControlIdKeeper.acquireCcid(uuid, context); - Assert.assertNotEquals(ccid, ContentControlIdKeeper.CCID_INVALID); + assertThat(ccid).isNotEqualTo(ContentControlIdKeeper.CCID_INVALID); verify(mLeAudioServiceMock).setCcidInformation(eq(uuid), eq(ccid), eq(context)); Map<ParcelUuid, Pair<Integer, Integer>> uuidToCcidContextPair = ContentControlIdKeeper.getUuidToCcidContextPairMap(); - Assert.assertEquals(expectedListSize, uuidToCcidContextPair.size()); + assertThat(uuidToCcidContextPair).hasSize(expectedListSize); assertThat(uuidToCcidContextPair).containsKey(uuid); - Assert.assertEquals(ccid, (long) uuidToCcidContextPair.get(uuid).first); - Assert.assertEquals(context, (long) uuidToCcidContextPair.get(uuid).second); + assertThat(uuidToCcidContextPair.get(uuid).first).isEqualTo(ccid); + assertThat(uuidToCcidContextPair.get(uuid).second).isEqualTo(context); return ccid; } @@ -88,7 +87,7 @@ public class ContentControlIdKeeperTest { verify(mLeAudioServiceMock).setCcidInformation(eq(uuid), eq(ccid), eq(0)); - Assert.assertEquals(expectedListSize, uuidToCcidContextPair.size()); + assertThat(uuidToCcidContextPair).hasSize(expectedListSize); } @Test @@ -98,7 +97,7 @@ public class ContentControlIdKeeperTest { int ccid_one = testCcidAcquire(uuid_one, BluetoothLeAudio.CONTEXT_TYPE_MEDIA, 1); int ccid_two = testCcidAcquire(uuid_two, BluetoothLeAudio.CONTEXT_TYPE_RINGTONE, 2); - Assert.assertNotEquals(ccid_one, ccid_two); + assertThat(ccid_one).isNotEqualTo(ccid_two); testCcidRelease(uuid_one, ccid_one, 1); testCcidRelease(uuid_two, ccid_two, 0); @@ -120,14 +119,14 @@ public class ContentControlIdKeeperTest { public void testAcquireInvalidContext() { ParcelUuid uuid = new ParcelUuid(UUID.randomUUID()); - int ccid = ContentControlIdKeeper.acquireCcid(uuid, 0); - Assert.assertEquals(ccid, ContentControlIdKeeper.CCID_INVALID); + assertThat(ContentControlIdKeeper.acquireCcid(uuid, 0)) + .isEqualTo(ContentControlIdKeeper.CCID_INVALID); verify(mLeAudioServiceMock, times(0)) .setCcidInformation(any(ParcelUuid.class), any(int.class), any(int.class)); Map<ParcelUuid, Pair<Integer, Integer>> uuidToCcidContextPair = ContentControlIdKeeper.getUuidToCcidContextPairMap(); - Assert.assertEquals(0, uuidToCcidContextPair.size()); + assertThat(uuidToCcidContextPair).isEmpty(); } @Test @@ -138,6 +137,6 @@ public class ContentControlIdKeeperTest { int ccid_two = testCcidAcquire(uuid, BluetoothLeAudio.CONTEXT_TYPE_RINGTONE, 1); // This is implementation specific but verifies that the previous CCID was recycled - Assert.assertEquals(ccid_one, ccid_two); + assertThat(ccid_two).isEqualTo(ccid_one); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java index b57b9beb69..8433d1398c 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java @@ -22,6 +22,7 @@ import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; import static com.android.bluetooth.bass_client.BassConstants.INVALID_BROADCAST_ID; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.Mockito.*; @@ -57,7 +58,6 @@ import com.android.bluetooth.flags.Flags; import com.android.bluetooth.tbs.TbsService; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -91,7 +91,6 @@ public class LeAudioBroadcastServiceTest { private LeAudioService mService; private LeAudioIntentReceiver mLeAudioIntentReceiver; private LinkedBlockingQueue<Intent> mIntentQueue; - private boolean onBroadcastToUnicastFallbackGroupChangedCallbackCalled = false; @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock private ActiveDeviceManager mActiveDeviceManager; @@ -105,6 +104,7 @@ public class LeAudioBroadcastServiceTest { @Mock private TbsService mTbsService; @Mock private MetricsLogger mMetricsLogger; @Mock private IBluetoothLeBroadcastCallback mCallbacks; + @Mock private IBluetoothLeAudioCallback mLeAudioCallbacks; @Mock private IBinder mBinder; @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance(); @@ -155,6 +155,7 @@ public class LeAudioBroadcastServiceTest { mTargetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); doReturn(mBinder).when(mCallbacks).asBinder(); + doReturn(mBinder).when(mLeAudioCallbacks).asBinder(); doNothing().when(mBinder).linkToDeath(any(), eq(0)); // Use spied objects factory @@ -237,7 +238,7 @@ public class LeAudioBroadcastServiceTest { /** Test getting LeAudio Service */ @Test public void testGetLeAudioService() { - Assert.assertEquals(mService, LeAudioService.getLeAudioService()); + assertThat(LeAudioService.getLeAudioService()).isEqualTo(mService); } void verifyBroadcastStarted(int broadcastId, BluetoothLeBroadcastSettings settings) { @@ -750,10 +751,8 @@ public class LeAudioBroadcastServiceTest { TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper()); - List<BluetoothLeBroadcastMetadata> meta_list = mService.getAllBroadcastMetadata(); - assertThat(meta_list).isNotNull(); - Assert.assertNotEquals(meta_list.size(), 0); - Assert.assertEquals(meta_list.get(0), state_event.broadcastMetadata); + assertThat(mService.getAllBroadcastMetadata()) + .containsExactly(state_event.broadcastMetadata); } @Test @@ -806,13 +805,13 @@ public class LeAudioBroadcastServiceTest { int timeoutMs, BluetoothDevice device, int newState, int prevState) { Intent intent = TestUtils.waitForIntent(timeoutMs, mIntentQueue); assertThat(intent).isNotNull(); - Assert.assertEquals( - BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED, intent.getAction()); - Assert.assertEquals( - (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE), device); - Assert.assertEquals(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1), newState); - Assert.assertEquals( - intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1), prevState); + assertThat(intent.getAction()) + .isEqualTo(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); + assertThat(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class)) + .isEqualTo(device); + assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)).isEqualTo(newState); + assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1)) + .isEqualTo(prevState); if (newState == BluetoothProfile.STATE_CONNECTED) { // ActiveDeviceManager calls deviceConnected when connected. @@ -854,7 +853,8 @@ public class LeAudioBroadcastServiceTest { device, BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); - Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mService.getConnectionState(device)); + assertThat(mService.getConnectionState(device)) + .isEqualTo(BluetoothProfile.STATE_CONNECTING); LeAudioStackEvent create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); @@ -867,7 +867,7 @@ public class LeAudioBroadcastServiceTest { device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING); - Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(device)); + assertThat(mService.getConnectionState(device)).isEqualTo(BluetoothProfile.STATE_CONNECTED); create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED); @@ -934,7 +934,7 @@ public class LeAudioBroadcastServiceTest { /* Active group should become inactive */ int activeGroup = mService.getActiveGroupId(); - Assert.assertEquals(activeGroup, LE_AUDIO_GROUP_ID_INVALID); + assertThat(activeGroup).isEqualTo(LE_AUDIO_GROUP_ID_INVALID); /* Imitate group inactivity to cause create broadcast */ stackEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); @@ -961,7 +961,7 @@ public class LeAudioBroadcastServiceTest { eq(expectedDataArray)); activeGroup = mService.getActiveGroupId(); - Assert.assertEquals(-1, activeGroup); + assertThat(activeGroup).isEqualTo(-1); } @Test @@ -1048,7 +1048,7 @@ public class LeAudioBroadcastServiceTest { /* Active group should become inactive */ int activeGroup = mService.getActiveGroupId(); - Assert.assertEquals(activeGroup, LE_AUDIO_GROUP_ID_INVALID); + assertThat(activeGroup).isEqualTo(LE_AUDIO_GROUP_ID_INVALID); /* Imitate group inactivity to cause create broadcast */ create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); @@ -1090,7 +1090,7 @@ public class LeAudioBroadcastServiceTest { } activeGroup = mService.getActiveGroupId(); - Assert.assertEquals(LE_AUDIO_GROUP_ID_INVALID, activeGroup); + assertThat(activeGroup).isEqualTo(LE_AUDIO_GROUP_ID_INVALID); /* Check if broadcast is started automatically when created */ create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED); @@ -1160,7 +1160,7 @@ public class LeAudioBroadcastServiceTest { /* Active group should become the one that was active before broadcasting */ int activeGroup = mService.getActiveGroupId(); - Assert.assertEquals(activeGroup, groupId); + assertThat(activeGroup).isEqualTo(groupId); /* Imitate setting device not in call */ mService.setInCall(false); @@ -1237,7 +1237,7 @@ public class LeAudioBroadcastServiceTest { /* Active group should become the one that was active before broadcasting */ int activeGroup = mService.getActiveGroupId(); - Assert.assertEquals(activeGroup, groupId); + assertThat(activeGroup).isEqualTo(groupId); /* Imitate group change request by Bluetooth Sink HAL suspend request */ create_event = @@ -1332,7 +1332,7 @@ public class LeAudioBroadcastServiceTest { /* Active group should become the one that was active before broadcasting */ int activeGroup = mService.getActiveGroupId(); - Assert.assertEquals(activeGroup, groupId); + assertThat(activeGroup).isEqualTo(groupId); /* Imitate group change request by Bluetooth Sink HAL suspend request */ create_event = @@ -1425,7 +1425,7 @@ public class LeAudioBroadcastServiceTest { /* Active group should become the one that was active before broadcasting */ int activeGroup = mService.getActiveGroupId(); - Assert.assertEquals(activeGroup, groupId); + assertThat(activeGroup).isEqualTo(groupId); /* Imitate setting device not in call */ mService.setInCall(false); @@ -1503,7 +1503,7 @@ public class LeAudioBroadcastServiceTest { /* Active group should become the one that was active before broadcasting */ int activeGroup = mService.getActiveGroupId(); - Assert.assertEquals(activeGroup, groupId); + assertThat(activeGroup).isEqualTo(groupId); /* Imitate group change request by Bluetooth Sink HAL suspend request */ create_event = @@ -1593,7 +1593,7 @@ public class LeAudioBroadcastServiceTest { /* Active group should become the one that was active before broadcasting */ int activeGroup = mService.getActiveGroupId(); - Assert.assertEquals(activeGroup, groupId); + assertThat(activeGroup).isEqualTo(groupId); /* Imitate group change request by Bluetooth Sink HAL suspend request */ create_event = @@ -1669,11 +1669,11 @@ public class LeAudioBroadcastServiceTest { prepareConnectedUnicastDevice(groupId2, mDevice2); prepareHandoverStreamingBroadcast(groupId, broadcastId, code); - Assert.assertEquals(mService.mUnicastGroupIdDeactivatedForBroadcastTransition, groupId); + assertThat(mService.mUnicastGroupIdDeactivatedForBroadcastTransition).isEqualTo(groupId); reset(mAudioManager); - Assert.assertTrue(mService.setActiveDevice(mDevice2)); + assertThat(mService.setActiveDevice(mDevice2)).isTrue(); if (!Flags.leaudioUseAudioRecordingListener()) { /* Update fallback active device (only input is active) */ @@ -1686,10 +1686,10 @@ public class LeAudioBroadcastServiceTest { eq(mDevice2), eq(mDevice), connectionInfoArgumentCaptor.capture()); List<BluetoothProfileConnectionInfo> connInfos = connectionInfoArgumentCaptor.getAllValues(); - Assert.assertEquals(connInfos.size(), 1); + assertThat(connInfos).hasSize(1); assertThat(connInfos.get(0).isLeOutput()).isFalse(); } - Assert.assertEquals(mService.mUnicastGroupIdDeactivatedForBroadcastTransition, groupId2); + assertThat(mService.mUnicastGroupIdDeactivatedForBroadcastTransition).isEqualTo(groupId2); } @Test @@ -1706,34 +1706,8 @@ public class LeAudioBroadcastServiceTest { when(mDatabaseManager.getMostRecentlyConnectedDevices()).thenReturn(devices); - onBroadcastToUnicastFallbackGroupChangedCallbackCalled = false; - - IBluetoothLeAudioCallback leAudioCallbacks = - new IBluetoothLeAudioCallback.Stub() { - @Override - public void onCodecConfigChanged(int gid, BluetoothLeAudioCodecStatus status) {} - - @Override - public void onGroupStatusChanged(int gid, int gStatus) {} - - @Override - public void onGroupNodeAdded(BluetoothDevice device, int gid) {} - - @Override - public void onGroupNodeRemoved(BluetoothDevice device, int gid) {} - - @Override - public void onGroupStreamStatusChanged(int groupId, int groupStreamStatus) {} - - @Override - public void onBroadcastToUnicastFallbackGroupChanged(int groupId) { - onBroadcastToUnicastFallbackGroupChangedCallbackCalled = true; - Assert.assertEquals(groupId2, groupId); - } - }; - synchronized (mService.mLeAudioCallbacks) { - mService.mLeAudioCallbacks.register(leAudioCallbacks); + mService.mLeAudioCallbacks.register(mLeAudioCallbacks); } initializeNative(); @@ -1743,22 +1717,50 @@ public class LeAudioBroadcastServiceTest { prepareHandoverStreamingBroadcast(groupId1, broadcastId, code); TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper()); - Assert.assertEquals(groupId2, mService.mUnicastGroupIdDeactivatedForBroadcastTransition); - assertThat(onBroadcastToUnicastFallbackGroupChangedCallbackCalled).isTrue(); + assertThat(mService.mUnicastGroupIdDeactivatedForBroadcastTransition).isEqualTo(groupId2); + + try { + verify(mLeAudioCallbacks).onBroadcastToUnicastFallbackGroupChanged(groupId2); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } - onBroadcastToUnicastFallbackGroupChangedCallbackCalled = false; synchronized (mService.mLeAudioCallbacks) { - mService.mLeAudioCallbacks.unregister(leAudioCallbacks); + mService.mLeAudioCallbacks.unregister(mLeAudioCallbacks); } } + @Test + public void testGetLocalBroadcastReceivers() { + int broadcastId = 243; + byte[] code = {0x00, 0x01, 0x00, 0x02}; + + synchronized (mService.mBroadcastCallbacks) { + mService.mBroadcastCallbacks.register(mCallbacks); + } + + BluetoothLeAudioContentMetadata.Builder meta_builder = + new BluetoothLeAudioContentMetadata.Builder(); + meta_builder.setLanguage("deu"); + meta_builder.setProgramInfo("Subgroup broadcast info"); + BluetoothLeAudioContentMetadata meta = meta_builder.build(); + + verifyBroadcastStarted(broadcastId, buildBroadcastSettingsFromMetadata(meta, code, 1)); + when(mBassClientService.getSyncedBroadcastSinks(broadcastId)).thenReturn(List.of(mDevice)); + assertThat(mService.getLocalBroadcastReceivers().size()).isEqualTo(1); + assertThat(mService.getLocalBroadcastReceivers()).containsExactly(mDevice); + + verifyBroadcastStopped(broadcastId); + assertThat(mService.getLocalBroadcastReceivers()).isEmpty(); + } + private class LeAudioIntentReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { try { mIntentQueue.put(intent); } catch (InterruptedException e) { - Assert.fail("Cannot add Intent to the queue: " + e.getMessage()); + assertWithMessage("Cannot add Intent to the queue: " + e.toString()).fail(); } } } @@ -1785,7 +1787,7 @@ public class LeAudioBroadcastServiceTest { devices.add(mDevice2); prepareConnectedUnicastDevice(groupId2, mDevice2); - Assert.assertEquals(mService.mUnicastGroupIdDeactivatedForBroadcastTransition, groupId); + assertThat(mService.mUnicastGroupIdDeactivatedForBroadcastTransition).isEqualTo(groupId); reset(mAudioManager); @@ -1801,11 +1803,11 @@ public class LeAudioBroadcastServiceTest { eq(mDevice2), eq(mDevice), connectionInfoArgumentCaptor.capture()); List<BluetoothProfileConnectionInfo> connInfos = connectionInfoArgumentCaptor.getAllValues(); - Assert.assertEquals(connInfos.size(), 1); + assertThat(connInfos.size()).isEqualTo(1); assertThat(connInfos.get(0).isLeOutput()).isFalse(); } - Assert.assertEquals(mService.getBroadcastToUnicastFallbackGroup(), groupId2); + assertThat(mService.getBroadcastToUnicastFallbackGroup()).isEqualTo(groupId2); } private void disconnectDevice(BluetoothDevice device) { @@ -1818,8 +1820,8 @@ public class LeAudioBroadcastServiceTest { device, BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTING, mService.getConnectionState(device)); + assertThat(mService.getConnectionState(device)) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTING); LeAudioStackEvent create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); @@ -1832,8 +1834,8 @@ public class LeAudioBroadcastServiceTest { device, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_DISCONNECTING); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, mService.getConnectionState(device)); + assertThat(mService.getConnectionState(device)) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); mService.deviceDisconnected(device, false); TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper()); } @@ -1847,18 +1849,17 @@ public class LeAudioBroadcastServiceTest { public void testSetDefaultBroadcastToUnicastFallbackGroup() { int groupId = 1; int groupId2 = 2; - int broadcastId = 243; - byte[] code = {0x00, 0x01, 0x00, 0x02}; List<BluetoothDevice> devices = new ArrayList<>(); when(mDatabaseManager.getMostRecentlyConnectedDevices()).thenReturn(devices); - Assert.assertEquals( - mService.getBroadcastToUnicastFallbackGroup(), LE_AUDIO_GROUP_ID_INVALID); + /* If no connected devices - no fallback device */ + assertThat(mService.getBroadcastToUnicastFallbackGroup()) + .isEqualTo(LE_AUDIO_GROUP_ID_INVALID); initializeNative(); devices.add(mDevice); - prepareHandoverStreamingBroadcast(groupId, broadcastId, code); + prepareConnectedUnicastDevice(groupId, mDevice); mService.deviceConnected(mDevice); devices.add(mDevice2); prepareConnectedUnicastDevice(groupId2, mDevice2); @@ -1870,24 +1871,79 @@ public class LeAudioBroadcastServiceTest { mService.messageFromNative(stackEvent); /* First connected group become fallback group */ - Assert.assertEquals(mService.mUnicastGroupIdDeactivatedForBroadcastTransition, groupId); - - reset(mAudioManager); + assertThat(mService.getBroadcastToUnicastFallbackGroup()).isEqualTo(groupId); + /* Force group as fallback 1 -> 2 */ mService.setBroadcastToUnicastFallbackGroup(groupId2); - - Assert.assertEquals(mService.getBroadcastToUnicastFallbackGroup(), groupId2); + assertThat(mService.getBroadcastToUnicastFallbackGroup()).isEqualTo(groupId2); /* Disconnected last device from fallback should trigger set default group 2 -> 1 */ disconnectDevice(mDevice2); - Assert.assertEquals(groupId, mService.getBroadcastToUnicastFallbackGroup()); + assertThat(mService.getBroadcastToUnicastFallbackGroup()).isEqualTo(groupId); stackEvent.valueInt1 = groupId; mService.messageFromNative(stackEvent); /* Disconnected last device from fallback should trigger set default group 1 -> -1 */ disconnectDevice(mDevice); - Assert.assertEquals( - LE_AUDIO_GROUP_ID_INVALID, mService.getBroadcastToUnicastFallbackGroup()); + assertThat(mService.getBroadcastToUnicastFallbackGroup()) + .isEqualTo(LE_AUDIO_GROUP_ID_INVALID); + } + + @Test + @EnableFlags({ + Flags.FLAG_LEAUDIO_BROADCAST_API_MANAGE_PRIMARY_GROUP, + Flags.FLAG_LEAUDIO_BROADCAST_PRIMARY_GROUP_SELECTION + }) + public void testUpdateFallbackDeviceWhileSettingActiveDevice() { + int groupId = 1; + int groupId2 = 2; + int broadcastId = 243; + byte[] code = {0x00, 0x01, 0x00, 0x02}; + List<BluetoothDevice> devices = new ArrayList<>(); + + when(mDatabaseManager.getMostRecentlyConnectedDevices()).thenReturn(devices); + + synchronized (mService.mLeAudioCallbacks) { + mService.mLeAudioCallbacks.register(mLeAudioCallbacks); + } + + initializeNative(); + devices.add(mDevice2); + prepareConnectedUnicastDevice(groupId2, mDevice2); + devices.add(mDevice); + prepareHandoverStreamingBroadcast(groupId, broadcastId, code); + + /* Earliest connected group (2) become fallback device */ + assertThat(mService.mUnicastGroupIdDeactivatedForBroadcastTransition).isEqualTo(groupId2); + try { + verify(mLeAudioCallbacks).onBroadcastToUnicastFallbackGroupChanged(groupId2); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + Mockito.clearInvocations(mLeAudioCallbacks); + reset(mAudioManager); + + /* Change active device while broadcasting - result in replacing fallback group 2->1 */ + assertThat(mService.setActiveDevice(mDevice)).isTrue(); + TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper()); + assertThat(mService.mUnicastGroupIdDeactivatedForBroadcastTransition).isEqualTo(groupId); + try { + verify(mLeAudioCallbacks).onBroadcastToUnicastFallbackGroupChanged(groupId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + /* Verify that fallback device is not changed when there is no running broadcast */ + Mockito.clearInvocations(mLeAudioCallbacks); + verifyBroadcastStopped(broadcastId); + assertThat(mService.setActiveDevice(mDevice2)).isTrue(); + TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper()); + try { + verify(mLeAudioCallbacks, times(0)).onBroadcastToUnicastFallbackGroupChanged(anyInt()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } private BluetoothLeBroadcastSettings buildBroadcastSettingsFromMetadata( diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java index e03a978949..312a031f82 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java @@ -46,6 +46,8 @@ import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothLeAudioCallback; +import android.bluetooth.le.IScannerCallback; +import android.bluetooth.le.ScanResult; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -56,6 +58,7 @@ import android.media.BluetoothProfileConnectionInfo; import android.os.Handler; import android.os.Looper; import android.os.ParcelUuid; +import android.os.RemoteException; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; @@ -77,7 +80,6 @@ import com.android.bluetooth.gatt.GattService; import com.android.bluetooth.hap.HapClientService; import com.android.bluetooth.hfp.HeadsetService; import com.android.bluetooth.le_scan.ScanController; -import com.android.bluetooth.le_scan.TransitionalScanHelper; import com.android.bluetooth.mcp.McpService; import com.android.bluetooth.tbs.TbsService; import com.android.bluetooth.vc.VolumeControlService; @@ -139,7 +141,6 @@ public class LeAudioServiceTest { @Mock private AdapterService mAdapterService; @Mock private GattService mGattService; @Mock private ScanController mScanController; - @Mock private TransitionalScanHelper mTransitionalScanHelper; @Mock private ActiveDeviceManager mActiveDeviceManager; @Mock private AudioManager mAudioManager; @Mock private DatabaseManager mDatabaseManager; @@ -229,7 +230,6 @@ public class LeAudioServiceTest { .getBondedDevices(); doReturn(mGattService).when(mAdapterService).getBluetoothGattService(); doReturn(mScanController).when(mAdapterService).getBluetoothScanController(); - doReturn(mTransitionalScanHelper).when(mScanController).getTransitionalScanHelper(); LeAudioBroadcasterNativeInterface.setInstance(mLeAudioBroadcasterNativeInterface); LeAudioNativeInterface.setInstance(mNativeInterface); @@ -285,6 +285,9 @@ public class LeAudioServiceTest { .when(mAdapterService) .getRemoteUuids(any(BluetoothDevice.class)); + doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); + doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); + verify(mNativeInterface, timeout(3000).times(1)).init(any()); } @@ -535,8 +538,6 @@ public class LeAudioServiceTest { .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); when(mDatabaseManager.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); - doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); // Return No UUID doReturn(new ParcelUuid[] {}) @@ -550,9 +551,6 @@ public class LeAudioServiceTest { /** Test that an outgoing connection to device with PRIORITY_OFF is rejected */ @Test public void testOutgoingConnectPriorityOff() { - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); - doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); - // Set the device priority to PRIORITY_OFF so connect() should fail when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO)) .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); @@ -571,8 +569,6 @@ public class LeAudioServiceTest { .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); when(mDatabaseManager.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); - doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); // Send a connect request assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue(); @@ -624,6 +620,13 @@ public class LeAudioServiceTest { } } + private void injectAndVerifyDeviceConnected(BluetoothDevice device) { + generateConnectionMessageFromNative( + device, + LeAudioStackEvent.CONNECTION_STATE_CONNECTED, + LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED); + } + private void injectNoVerifyDeviceConnected(BluetoothDevice device) { generateUnexpectedConnectionMessageFromNative( device, LeAudioStackEvent.CONNECTION_STATE_CONNECTED); @@ -651,8 +654,6 @@ public class LeAudioServiceTest { .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); when(mDatabaseManager.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); - doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); // Send a connect request assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue(); @@ -781,8 +782,6 @@ public class LeAudioServiceTest { .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); when(mDatabaseManager.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); - doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); // Create device descriptor with connect request assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue(); @@ -858,8 +857,6 @@ public class LeAudioServiceTest { .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); when(mDatabaseManager.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); - doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); // Create device descriptor with connect request assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue(); @@ -935,8 +932,6 @@ public class LeAudioServiceTest { .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); when(mDatabaseManager.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); - doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); // Create device descriptor with connect request assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue(); @@ -1076,8 +1071,6 @@ public class LeAudioServiceTest { /** Test setting connection policy */ @Test public void testSetConnectionPolicy() { - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); - doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); doReturn(true) .when(mDatabaseManager) .setProfileConnectionPolicy(any(BluetoothDevice.class), anyInt(), anyInt()); @@ -1190,6 +1183,13 @@ public class LeAudioServiceTest { // Make device bonded mBondedDevices.add(device); + LeAudioStackEvent nodeGroupAdded = + new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED); + nodeGroupAdded.device = device; + nodeGroupAdded.valueInt1 = GroupId; + nodeGroupAdded.valueInt2 = LeAudioStackEvent.GROUP_NODE_ADDED; + mService.messageFromNative(nodeGroupAdded); + // Wait ASYNC_CALL_TIMEOUT_MILLIS for state to settle, timing is also tested here and // 250ms for processing two messages should be way more than enough. Anything that breaks // this indicate some breakage in other part of Android OS @@ -1217,13 +1217,6 @@ public class LeAudioServiceTest { assertThat(mService.getConnectionState(device)).isEqualTo(BluetoothProfile.STATE_CONNECTED); - LeAudioStackEvent nodeGroupAdded = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED); - nodeGroupAdded.device = device; - nodeGroupAdded.valueInt1 = GroupId; - nodeGroupAdded.valueInt2 = LeAudioStackEvent.GROUP_NODE_ADDED; - mService.messageFromNative(nodeGroupAdded); - // Verify that the device is in the list of connected devices assertThat(mService.getConnectedDevices().contains(device)).isTrue(); // Verify the list of previously connected devices @@ -1257,8 +1250,7 @@ public class LeAudioServiceTest { // Not connected device assertThat(mService.setActiveDevice(mSingleDevice)).isFalse(); - // Connected device - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); + // Connect device connectTestDevice(mSingleDevice, testGroupId); // Add location support @@ -1308,8 +1300,7 @@ public class LeAudioServiceTest { // Not connected device assertThat(mService.setActiveDevice(mSingleDevice)).isFalse(); - // Connected device - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); + // Connect device connectTestDevice(mSingleDevice, testGroupId); // Add location support @@ -1376,8 +1367,7 @@ public class LeAudioServiceTest { // Not connected device assertThat(mService.setActiveDevice(mSingleDevice)).isFalse(); - // Connected device - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); + // Connect device connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); @@ -1407,7 +1397,7 @@ public class LeAudioServiceTest { eq(mLeftDevice), eq(null), connectionInfoArgumentCaptor.capture()); List<BluetoothProfileConnectionInfo> connInfos = connectionInfoArgumentCaptor.getAllValues(); - assertThat(connInfos.size()).isEqualTo(2); + assertThat(connInfos).hasSize(2); assertThat(connInfos.get(0).isLeOutput()).isEqualTo(true); assertThat(connInfos.get(1).isLeOutput()).isEqualTo(false); @@ -1418,7 +1408,7 @@ public class LeAudioServiceTest { .handleBluetoothActiveDeviceChanged( any(), any(), any(BluetoothProfileConnectionInfo.class)); connInfos = connectionInfoArgumentCaptor.getAllValues(); - assertThat(connInfos.size()).isEqualTo(2); + assertThat(connInfos).hasSize(2); } /** Test setting active device group with not available contexts */ @@ -1427,27 +1417,16 @@ public class LeAudioServiceTest { int groupId = 1; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 0; // Not connected device assertThat(mService.setActiveDevice(mSingleDevice)).isFalse(); - // Connected device - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); + // Connect device connectTestDevice(mSingleDevice, testGroupId); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.device = mSingleDevice; - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); assertThat(mService.setActiveDevice(mSingleDevice)).isFalse(); verify(mNativeInterface, times(0)).groupSetActive(groupId); @@ -1461,8 +1440,6 @@ public class LeAudioServiceTest { /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; // Not connected device @@ -1470,33 +1447,14 @@ public class LeAudioServiceTest { // Define some return values needed in test doReturn(-1).when(mVolumeControlService).getAudioDeviceGroupVolume(anyInt()); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); // Connect both connectTestDevice(mSingleDevice, groupId_1); connectTestDevice(mSingleDevice_2, groupId_2); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.device = mSingleDevice; - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId_1; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); - - // Add location support - audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.device = mSingleDevice_2; - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId_2; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId_1, availableContexts, direction); + injectAudioConfChanged(groupId_2, availableContexts, direction); assertThat(mService.setActiveDevice(mSingleDevice)).isTrue(); verify(mNativeInterface).groupSetActive(groupId_1); @@ -1559,8 +1517,6 @@ public class LeAudioServiceTest { /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; // Not connected device @@ -1574,15 +1530,7 @@ public class LeAudioServiceTest { connectTestDevice(mSingleDevice, groupId_1); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.device = mSingleDevice; - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId_1; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId_1, availableContexts, direction); assertThat(mService.setActiveDevice(mSingleDevice)).isTrue(); verify(mNativeInterface).groupSetActive(groupId_1); @@ -1620,27 +1568,16 @@ public class LeAudioServiceTest { int groupId = 1; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5; // Not connected device assertThat(mService.setActiveDevice(mSingleDevice)).isFalse(); - // Connected device - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); + // Connect device connectTestDevice(mSingleDevice, testGroupId); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.device = mSingleDevice; - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); assertThat(mService.setActiveDevice(mSingleDevice)).isTrue(); verify(mNativeInterface).groupSetActive(groupId); @@ -1694,8 +1631,9 @@ public class LeAudioServiceTest { @Test @DisableFlags(Flags.FLAG_LEAUDIO_BROADCAST_PRIMARY_GROUP_SELECTION) public void testUpdateUnicastFallbackActiveDeviceGroupDuringBroadcast() { + List<BluetoothDevice> devices = new ArrayList<>(); int groupId = 1; - int preGroupId = 2; + int groupId_2 = 2; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; int snkAudioLocation = 3; @@ -1704,14 +1642,23 @@ public class LeAudioServiceTest { int broadcastId = 243; byte[] code = {0x00, 0x01, 0x00, 0x02}; + when(mDatabaseManager.getMostRecentlyConnectedDevices()).thenReturn(devices); + // Not connected device assertThat(mService.setActiveDevice(mSingleDevice)).isFalse(); - // Connected device + // Connect devices doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); - connectTestDevice(mSingleDevice, testGroupId); + devices.add(mSingleDevice); + connectTestDevice(mSingleDevice, groupId); + devices.add(mSingleDevice_2); + connectTestDevice(mSingleDevice_2, groupId_2); - mService.mUnicastGroupIdDeactivatedForBroadcastTransition = preGroupId; + // Default fallback group is LE_AUDIO_GROUP_ID_INVALID + assertThat(mService.mUnicastGroupIdDeactivatedForBroadcastTransition) + .isEqualTo(LE_AUDIO_GROUP_ID_INVALID); + + mService.mUnicastGroupIdDeactivatedForBroadcastTransition = groupId_2; // mock create broadcast and currentlyActiveGroupId remains LE_AUDIO_GROUP_ID_INVALID BluetoothLeAudioContentMetadata.Builder meta_builder = new BluetoothLeAudioContentMetadata.Builder(); @@ -1723,24 +1670,31 @@ public class LeAudioServiceTest { LeAudioStackEvent broadcastCreatedEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED); - broadcastCreatedEvent.device = mSingleDevice; broadcastCreatedEvent.valueInt1 = broadcastId; broadcastCreatedEvent.valueBool1 = true; mService.messageFromNative(broadcastCreatedEvent); + LeAudioStackEvent broadcastStateStreamingEvent = + new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); + broadcastStateStreamingEvent.valueInt1 = broadcastId; + broadcastStateStreamingEvent.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_STREAMING; + mService.messageFromNative(broadcastStateStreamingEvent); + + injectAudioConfChanged(groupId, availableContexts, direction); + LeAudioStackEvent audioConfChangedEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.device = mSingleDevice; + audioConfChangedEvent.device = mSingleDevice_2; audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId; + audioConfChangedEvent.valueInt2 = groupId_2; audioConfChangedEvent.valueInt3 = snkAudioLocation; audioConfChangedEvent.valueInt4 = srcAudioLocation; audioConfChangedEvent.valueInt5 = availableContexts; mService.messageFromNative(audioConfChangedEvent); // Verify only update the fallback group and not proceed to change active - assertThat(mService.setActiveDevice(mSingleDevice)).isTrue(); - assertThat(mService.mUnicastGroupIdDeactivatedForBroadcastTransition).isEqualTo(groupId); + assertThat(mService.setActiveDevice(mSingleDevice_2)).isTrue(); + assertThat(mService.mUnicastGroupIdDeactivatedForBroadcastTransition).isEqualTo(groupId_2); // Verify only update the fallback group to INVALID and not proceed to change active assertThat(mService.setActiveDevice(null)).isTrue(); @@ -1756,14 +1710,11 @@ public class LeAudioServiceTest { int groupId = 1; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5; int nodeStatus = LeAudioStackEvent.GROUP_NODE_ADDED; int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE; // Single active device - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); // Add device to group @@ -1777,15 +1728,7 @@ public class LeAudioServiceTest { assertThat(mService.setActiveDevice(mSingleDevice)).isFalse(); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.device = mSingleDevice; - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); assertThat(mService.setActiveDevice(mSingleDevice)).isTrue(); @@ -1853,7 +1796,6 @@ public class LeAudioServiceTest { doReturn(testVolume).when(mVolumeControlService).getAudioDeviceGroupVolume(testGroupId); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); injectAudioConfChanged( testGroupId, @@ -1871,7 +1813,7 @@ public class LeAudioServiceTest { reset(mAudioManager); /* Verify input and output has been connected to AF*/ List<BluetoothProfileConnectionInfo> connInfos = testConnectioInfoCapture.getAllValues(); - assertThat(connInfos.size()).isEqualTo(2); + assertThat(connInfos).hasSize(2); assertThat(connInfos.get(0).isLeOutput()).isEqualTo(true); assertThat(connInfos.get(1).isLeOutput()).isEqualTo(false); @@ -1892,7 +1834,7 @@ public class LeAudioServiceTest { reset(mAudioManager); connInfos = testConnectioInfoCapture.getAllValues(); - assertThat(connInfos.size()).isEqualTo(3); + assertThat(connInfos).hasSize(3); assertThat(connInfos.get(2).isLeOutput()).isEqualTo(false); // remove Sink and add Source back @@ -1916,7 +1858,7 @@ public class LeAudioServiceTest { reset(mAudioManager); connInfos = testConnectioInfoCapture.getAllValues(); - assertThat(connInfos.size()).isEqualTo(5); + assertThat(connInfos).hasSize(5); assertThat(connInfos.get(3).isLeOutput()).isEqualTo(true); assertThat(connInfos.get(4).isLeOutput()).isEqualTo(false); } @@ -1924,7 +1866,6 @@ public class LeAudioServiceTest { /** Test native interface audio configuration changed message handling */ @Test public void testMessageFromNativeAudioConfChangedActiveGroup() { - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); injectAudioConfChanged( testGroupId, @@ -1946,7 +1887,6 @@ public class LeAudioServiceTest { /** Test native interface audio configuration changed message handling */ @Test public void testMessageFromNativeAudioConfChangedInactiveGroup() { - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); Integer contexts = @@ -1959,7 +1899,6 @@ public class LeAudioServiceTest { /** Test native interface audio configuration changed message handling */ @Test public void testMessageFromNativeAudioConfChangedNoGroupChanged() { - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); injectAudioConfChanged(testGroupId, 0, 3); @@ -1972,7 +1911,6 @@ public class LeAudioServiceTest { */ @Test public void testHealthBaseDeviceAction() { - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); LeAudioStackEvent healthBaseDevAction = @@ -1985,7 +1923,6 @@ public class LeAudioServiceTest { @Test public void testHealthBasedGroupAction() { - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); LeAudioStackEvent healthBasedGroupAction = @@ -2044,7 +1981,6 @@ public class LeAudioServiceTest { /** Test native interface group status message handling */ @Test public void testMessageFromNativeGroupStatusChanged() { - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); injectAudioConfChanged( @@ -2105,7 +2041,6 @@ public class LeAudioServiceTest { /** Test native interface group stream status message handling */ @Test public void testMessageFromNativeGroupStreamStatusChanged() { - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); injectAudioConfChanged( @@ -2167,7 +2102,6 @@ public class LeAudioServiceTest { injectLocalCodecConfigCapaChanged(INPUT_CAPABILITIES_CONFIG, OUTPUT_CAPABILITIES_CONFIG); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); testCodecStatus = @@ -2254,7 +2188,6 @@ public class LeAudioServiceTest { injectLocalCodecConfigCapaChanged(INPUT_CAPABILITIES_CONFIG, OUTPUT_CAPABILITIES_CONFIG); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); testCodecStatus = @@ -2350,7 +2283,6 @@ public class LeAudioServiceTest { injectLocalCodecConfigCapaChanged(INPUT_CAPABILITIES_CONFIG, OUTPUT_CAPABILITIES_CONFIG); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); testCodecStatus = @@ -2421,15 +2353,12 @@ public class LeAudioServiceTest { int groupId = 1; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; - ; + int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE; BluetoothDevice leadDevice; BluetoothDevice memberDevice = mLeftDevice; - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); @@ -2440,15 +2369,7 @@ public class LeAudioServiceTest { assertThat(mService.setActiveDevice(leadDevice)).isFalse(); - // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); assertThat(mService.setActiveDevice(leadDevice)).isTrue(); @@ -2497,15 +2418,12 @@ public class LeAudioServiceTest { int groupId = 1; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; - ; + int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE; BluetoothDevice leadDevice; BluetoothDevice memberDevice = mLeftDevice; - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); @@ -2517,14 +2435,7 @@ public class LeAudioServiceTest { assertThat(mService.setActiveDevice(leadDevice)).isFalse(); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); assertThat(mService.setActiveDevice(leadDevice)).isTrue(); @@ -2576,7 +2487,6 @@ public class LeAudioServiceTest { int direction = 1; int availableContexts = 4; - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); @@ -2635,7 +2545,6 @@ public class LeAudioServiceTest { int direction = 1; int availableContexts = 4; - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); assertThat(mService.setActiveDevice(mLeftDevice)).isFalse(); @@ -2696,7 +2605,6 @@ public class LeAudioServiceTest { @Test public void testGetAudioLocation() { - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); assertThat(mService.getAudioLocation(null)) @@ -2714,7 +2622,6 @@ public class LeAudioServiceTest { @Test public void testGetConnectedPeerDevices() { - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, testGroupId); connectTestDevice(mRightDevice, testGroupId); @@ -2775,8 +2682,6 @@ public class LeAudioServiceTest { doReturn(new BluetoothDevice[] {mSingleDevice}).when(mAdapterService).getBondedDevices(); when(mDatabaseManager.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); - doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); @@ -2790,7 +2695,7 @@ public class LeAudioServiceTest { int groupId = 1; mService.handleBluetoothEnabled(); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); + connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); verify(mMcpService).setDeviceAuthorized(mLeftDevice, true); @@ -2802,7 +2707,6 @@ public class LeAudioServiceTest { public void testAuthorizeMcpServiceOnBluetoothEnableAndNodeRemoval() { int groupId = 1; - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); @@ -2835,8 +2739,6 @@ public class LeAudioServiceTest { int groupId = 1; mService.handleBluetoothEnabled(); - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); - doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); doReturn(true) .when(mDatabaseManager) .setProfileConnectionPolicy(any(BluetoothDevice.class), anyInt(), anyInt()); @@ -2878,7 +2780,6 @@ public class LeAudioServiceTest { int firstGroupId = 1; int secondGroupId = 2; - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, firstGroupId); connectTestDevice(mRightDevice, firstGroupId); connectTestDevice(mSingleDevice, secondGroupId); @@ -2889,7 +2790,7 @@ public class LeAudioServiceTest { List<BluetoothDevice> firstGroupDevicesByRightDevice = mService.getGroupDevices(mRightDevice); - assertThat(firstGroupDevicesById.size()).isEqualTo(2); + assertThat(firstGroupDevicesById).hasSize(2); assertThat(firstGroupDevicesById.contains(mLeftDevice)).isTrue(); assertThat(firstGroupDevicesById.contains(mRightDevice)).isTrue(); assertThat(firstGroupDevicesById.contains(mSingleDevice)).isFalse(); @@ -2900,7 +2801,7 @@ public class LeAudioServiceTest { List<BluetoothDevice> secondGroupDevicesById = mService.getGroupDevices(secondGroupId); List<BluetoothDevice> secondGroupDevicesByDevice = mService.getGroupDevices(mSingleDevice); - assertThat(secondGroupDevicesById.size()).isEqualTo(1); + assertThat(secondGroupDevicesById).hasSize(1); assertThat(secondGroupDevicesById.contains(mSingleDevice)).isTrue(); assertThat(secondGroupDevicesById.contains(mLeftDevice)).isFalse(); assertThat(secondGroupDevicesById.contains(mRightDevice)).isFalse(); @@ -2926,14 +2827,11 @@ public class LeAudioServiceTest { int groupId = 1; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 | AUDIO_DIRECTION_INPUT_BIT = 0x02; */ int direction = 3; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5; int nodeStatus = LeAudioStackEvent.GROUP_NODE_ADDED; int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE; // Single active device - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); // Add device to group @@ -2947,15 +2845,7 @@ public class LeAudioServiceTest { assertThat(mService.setActiveDevice(mSingleDevice)).isFalse(); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.device = mSingleDevice; - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); assertThat(mService.setActiveDevice(mSingleDevice)).isTrue(); @@ -2976,30 +2866,20 @@ public class LeAudioServiceTest { int groupId = 1; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); // Checks group device lists for groupId 1 List<BluetoothDevice> groupDevicesById = mService.getGroupDevices(groupId); - assertThat(groupDevicesById.size()).isEqualTo(2); + assertThat(groupDevicesById).hasSize(2); assertThat(groupDevicesById.contains(mLeftDevice)).isTrue(); assertThat(groupDevicesById.contains(mRightDevice)).isTrue(); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); assertThat(mService.setActiveDevice(mLeftDevice)).isTrue(); verify(mNativeInterface).groupSetActive(groupId); @@ -3016,13 +2896,12 @@ public class LeAudioServiceTest { reset(mNativeInterface); /* Don't expect any change. */ - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); verify(mNativeInterface, times(0)).groupSetActive(groupId); reset(mNativeInterface); /* Expect device to be incactive */ - audioConfChangedEvent.valueInt5 = 0; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, 0, direction); verify(mNativeInterface).groupSetActive(-1); injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_INACTIVE); @@ -3037,8 +2916,7 @@ public class LeAudioServiceTest { reset(mAudioManager); /* Expect device to be incactive */ - audioConfChangedEvent.valueInt5 = 1; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, 1, direction); verify(mNativeInterface).groupSetActive(groupId); reset(mNativeInterface); @@ -3052,16 +2930,189 @@ public class LeAudioServiceTest { any(BluetoothProfileConnectionInfo.class)); } + @Test + public void testAutoActiveMode_verifyDefaultState() { + int groupId = 1; + + /* Test scenario: + * 1. Connected two devices + * 2. Verify that Auto Active Mode is true be default. + */ + + connectTestDevice(mLeftDevice, groupId); + connectTestDevice(mRightDevice, groupId); + + assertThat(mService.isAutoActiveModeEnabled(groupId)).isTrue(); + } + + @Test + public void testAutoActiveMode_whenDeviceIsConnected_failToDisableIt() { + int groupId = 1; + /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ + int direction = 1; + int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; + + /* Test scenario: + * 1. Connected two devices + * 2. Disconnect one device + * 3. Verify that Auto Active Mode cannot be set. + */ + + connectTestDevice(mLeftDevice, groupId); + connectTestDevice(mRightDevice, groupId); + + assertThat(mService.setAutoActiveModeState(groupId, false)).isFalse(); + + // Checks group device lists for groupId 1 + List<BluetoothDevice> groupDevicesById = mService.getGroupDevices(groupId); + + assertThat(groupDevicesById).containsExactly(mLeftDevice, mRightDevice); + + injectAudioConfChanged(groupId, availableContexts, direction); + assertThat(mService.isGroupAvailableForStream(groupId)).isTrue(); + + injectAndVerifyDeviceDisconnected(mLeftDevice); + + assertThat(mService.setAutoActiveModeState(groupId, false)).isFalse(); + } + + @Test + public void testAutoActiveMode_disabledWithSuccess() { + int groupId = 1; + /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ + int direction = 1; + int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; + + /* Test scenario: + * 1. Connected two devices + * 2. Disconnect both devices + * 3. Verify that Auto Active Mode can be set. + */ + + connectTestDevice(mLeftDevice, groupId); + connectTestDevice(mRightDevice, groupId); + + // Checks group device lists for groupId 1 + List<BluetoothDevice> groupDevicesById = mService.getGroupDevices(groupId); + + assertThat(groupDevicesById).containsExactly(mLeftDevice, mRightDevice); + + injectAudioConfChanged(groupId, availableContexts, direction); + + assertThat(mService.isGroupAvailableForStream(groupId)).isTrue(); + + injectAndVerifyDeviceDisconnected(mLeftDevice); + injectAndVerifyDeviceDisconnected(mRightDevice); + + assertThat(mService.setAutoActiveModeState(groupId, false)).isTrue(); + assertThat(mService.isAutoActiveModeEnabled(groupId)).isFalse(); + } + + @Test + public void testAutoActiveMode_whenUserSetsDeviceAsActive_resetToDefault() { + int groupId = 1; + /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ + int direction = 1; + int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; + + /* Test scenario: + * 1. Connected two devices + * 2. Disconnect both devices + * 3. Disable Auto Active Mode + * 4. Connect at least one device + * 5. Set group as Active + * 6. Verify Auto Active Mode is back to default + */ + + mService.handleBluetoothEnabled(); + + connectTestDevice(mLeftDevice, groupId); + connectTestDevice(mRightDevice, groupId); + + // Checks group device lists for groupId 1 + List<BluetoothDevice> groupDevicesById = mService.getGroupDevices(groupId); + + assertThat(groupDevicesById).containsExactly(mLeftDevice, mRightDevice); + + injectAudioConfChanged(groupId, availableContexts, direction); + + assertThat(mService.isGroupAvailableForStream(groupId)).isTrue(); + + injectAndVerifyDeviceDisconnected(mLeftDevice); + injectAndVerifyDeviceDisconnected(mRightDevice); + + assertThat(mService.setAutoActiveModeState(groupId, false)).isTrue(); + assertThat(mService.isAutoActiveModeEnabled(groupId)).isFalse(); + + injectAndVerifyDeviceConnected(mLeftDevice); + injectAudioConfChanged(groupId, availableContexts, direction); + + assertThat(mService.setActiveDevice(mLeftDevice)).isTrue(); + assertThat(mService.isAutoActiveModeEnabled(groupId)).isTrue(); + } + + @Test + public void testAutoActiveMode_whenRemoteUsesTargetedAnnouncements_resetToDefault() + throws RemoteException { + int groupId = 1; + /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ + int direction = 1; + int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; + + /* Test scenario: + * 1. Connected two devices + * 2. Disconnect both devices + * 3. Disable Auto Active Mode + * 4. Connect at least one device + * 5. Detect TA on remote device + * 6. Verify Auto Active Mode is back to default + */ + + mService.handleBluetoothEnabled(); + ArgumentCaptor<IScannerCallback> scanCallbacks = + ArgumentCaptor.forClass(IScannerCallback.class); + + connectTestDevice(mLeftDevice, groupId); + connectTestDevice(mRightDevice, groupId); + + // Checks group device lists for groupId 1 + List<BluetoothDevice> groupDevicesById = mService.getGroupDevices(groupId); + + assertThat(groupDevicesById).containsExactly(mLeftDevice, mRightDevice); + injectAudioConfChanged(groupId, availableContexts, direction); + + assertThat(mService.isGroupAvailableForStream(groupId)).isTrue(); + + injectAndVerifyDeviceDisconnected(mLeftDevice); + injectAndVerifyDeviceDisconnected(mRightDevice); + + assertThat(mService.setAutoActiveModeState(groupId, false)).isTrue(); + assertThat(mService.isAutoActiveModeEnabled(groupId)).isFalse(); + + injectAndVerifyDeviceConnected(mLeftDevice); + injectAudioConfChanged(groupId, availableContexts, direction); + + verify(mScanController).registerScannerInternal(scanCallbacks.capture(), any(), any()); + + ScanResult scanResult = new ScanResult(mRightDevice, null, 0, 0); + + scanCallbacks.getValue().onScanResult(scanResult); + + assertThat(mService.isAutoActiveModeEnabled(groupId)).isTrue(); + } + /** * Test the group is activated once the available contexts are back. * + * <pre> * Scenario: - * 1. Have a group of 2 devices that initially does not expose any available contexts. - * The group shall be inactive at this point. - * 2. Once the available contexts are updated with non-zero value, - * the group shall become active. - * 3. The available contexts are changed to zero. Group becomes inactive. - * 4. The available contexts are back again. Group becomes active. + * 1. Have a group of 2 devices that initially does not expose any available contexts. The + * group shall be inactive at this point. + * 2. Once the available contexts are updated with non-zero value, the group shall become + * active. + * 3. The available contexts are changed to zero. Group becomes inactive. + * 4. The available contexts are back again. Group becomes active. + * </pre> */ @Test @EnableFlags(Flags.FLAG_LEAUDIO_UNICAST_NO_AVAILABLE_CONTEXTS) @@ -3069,37 +3120,26 @@ public class LeAudioServiceTest { int groupId = 1; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); // Checks group device lists for groupId 1 List<BluetoothDevice> groupDevicesById = mService.getGroupDevices(groupId); - assertThat(groupDevicesById.size()).isEqualTo(2); + assertThat(groupDevicesById).hasSize(2); assertThat(groupDevicesById.contains(mLeftDevice)).isTrue(); assertThat(groupDevicesById.contains(mRightDevice)).isTrue(); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = 0; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, 0, direction); assertThat(mService.setActiveDevice(mLeftDevice)).isFalse(); verify(mNativeInterface, times(0)).groupSetActive(groupId); // Expect device to be active - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); verify(mNativeInterface).groupSetActive(groupId); @@ -3115,8 +3155,7 @@ public class LeAudioServiceTest { reset(mNativeInterface); // Expect device to be inactive - audioConfChangedEvent.valueInt5 = 0; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, 0, direction); verify(mNativeInterface).groupSetActive(-1); injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_INACTIVE); @@ -3131,8 +3170,7 @@ public class LeAudioServiceTest { reset(mAudioManager); // Expect device to be active - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); verify(mNativeInterface).groupSetActive(groupId); reset(mNativeInterface); @@ -3149,14 +3187,16 @@ public class LeAudioServiceTest { /** * Test the group is activated once the available contexts are back. * + * <pre> * Scenario: - * 1. Have a group of 2 devices. The available contexts are non-zero. - * The group shall be active at this point. - * 2. Once the available contexts are updated with zero value, - * the group shall become inactive. - * 3. All group devices are disconnected. - * 4. Group devices are reconnected. The available contexts are still zero. - * 4. The available contexts are updated with non-zero value. Group becomes active. + * 1. Have a group of 2 devices. The available contexts are non-zero. The group shall be + * active at this point. + * 2. Once the available contexts are updated with zero value, the group shall become + * inactive. + * 3. All group devices are disconnected. + * 4. Group devices are reconnected. The available contexts are still zero. + * 5. The available contexts are updated with non-zero value. Group becomes active. + * </pre> */ @Test @EnableFlags(Flags.FLAG_LEAUDIO_UNICAST_NO_AVAILABLE_CONTEXTS) @@ -3164,30 +3204,20 @@ public class LeAudioServiceTest { int groupId = 1; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); // Checks group device lists for groupId 1 List<BluetoothDevice> groupDevicesById = mService.getGroupDevices(groupId); - assertThat(groupDevicesById.size()).isEqualTo(2); + assertThat(groupDevicesById).hasSize(2); assertThat(groupDevicesById.contains(mLeftDevice)).isTrue(); assertThat(groupDevicesById.contains(mRightDevice)).isTrue(); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); assertThat(mService.setActiveDevice(mLeftDevice)).isTrue(); verify(mNativeInterface).groupSetActive(groupId); @@ -3204,8 +3234,7 @@ public class LeAudioServiceTest { reset(mNativeInterface); // Expect device to be inactive - audioConfChangedEvent.valueInt5 = 0; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, 0, direction); verify(mNativeInterface).groupSetActive(-1); injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_INACTIVE); @@ -3230,8 +3259,7 @@ public class LeAudioServiceTest { assertThat(mService.getConnectedDevices().contains(mRightDevice)).isFalse(); // Expect device to be inactive - audioConfChangedEvent.valueInt5 = 0; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, 0, direction); generateConnectionMessageFromNative( mLeftDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); @@ -3240,18 +3268,18 @@ public class LeAudioServiceTest { assertThat(mService.getConnectedDevices().contains(mLeftDevice)).isTrue(); // Expect device to be inactive - audioConfChangedEvent.valueInt5 = 0; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, 0, direction); generateConnectionMessageFromNative( - mRightDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); + mRightDevice, + BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_DISCONNECTED); assertThat(mService.getConnectionState(mRightDevice)) .isEqualTo(BluetoothProfile.STATE_CONNECTED); assertThat(mService.getConnectedDevices().contains(mRightDevice)).isTrue(); // Expect device to be active - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); verify(mNativeInterface).groupSetActive(groupId); @@ -3280,30 +3308,20 @@ public class LeAudioServiceTest { int groupId = 1; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); // Checks group device lists for groupId 1 List<BluetoothDevice> groupDevicesById = mService.getGroupDevices(groupId); - assertThat(groupDevicesById.size()).isEqualTo(2); + assertThat(groupDevicesById).hasSize(2); assertThat(groupDevicesById.contains(mLeftDevice)).isTrue(); assertThat(groupDevicesById.contains(mRightDevice)).isTrue(); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); assertThat(mService.setActiveDevice(mLeftDevice)).isTrue(); verify(mNativeInterface).groupSetActive(groupId); @@ -3329,8 +3347,7 @@ public class LeAudioServiceTest { reset(mAudioManager); // Expect device to be inactive - audioConfChangedEvent.valueInt5 = 0; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, 0, direction); generateConnectionMessageFromNative( mLeftDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); @@ -3339,8 +3356,7 @@ public class LeAudioServiceTest { assertThat(mService.getConnectedDevices().contains(mLeftDevice)).isTrue(); // Expect device to be inactive - audioConfChangedEvent.valueInt5 = 0; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, 0, direction); generateConnectionMessageFromNative( mRightDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); @@ -3349,8 +3365,7 @@ public class LeAudioServiceTest { assertThat(mService.getConnectedDevices().contains(mRightDevice)).isTrue(); // Expect device to be active - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); verify(mNativeInterface).groupSetActive(groupId); @@ -3371,27 +3386,16 @@ public class LeAudioServiceTest { int groupId = 1; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; // Not connected device assertThat(mService.setActiveDevice(mSingleDevice)).isFalse(); - // Connected device - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); + // Connect device connectTestDevice(mSingleDevice, testGroupId); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.device = mSingleDevice; - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = groupId; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(groupId, availableContexts, direction); assertThat(mService.setActiveDevice(mSingleDevice)).isTrue(); verify(mNativeInterface).groupSetActive(groupId); @@ -3439,8 +3443,6 @@ public class LeAudioServiceTest { int secondGroupId = 2; /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ int direction = 1; - int snkAudioLocation = 3; - int srcAudioLocation = 4; int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE; List<BluetoothDevice> devices = new ArrayList<>(); @@ -3451,8 +3453,7 @@ public class LeAudioServiceTest { assertThat(mService.getBroadcastToUnicastFallbackGroup()) .isEqualTo(BluetoothLeAudio.GROUP_ID_INVALID); - // Connected device - doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); + // Connect device devices.add(mSingleDevice); connectTestDevice(mSingleDevice, testGroupId); @@ -3460,15 +3461,7 @@ public class LeAudioServiceTest { assertThat(mService.getBroadcastToUnicastFallbackGroup()).isEqualTo(firstGroupId); // Add location support - LeAudioStackEvent audioConfChangedEvent = - new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); - audioConfChangedEvent.device = mSingleDevice; - audioConfChangedEvent.valueInt1 = direction; - audioConfChangedEvent.valueInt2 = firstGroupId; - audioConfChangedEvent.valueInt3 = snkAudioLocation; - audioConfChangedEvent.valueInt4 = srcAudioLocation; - audioConfChangedEvent.valueInt5 = availableContexts; - mService.messageFromNative(audioConfChangedEvent); + injectAudioConfChanged(firstGroupId, availableContexts, direction); assertThat(mService.setActiveDevice(mSingleDevice)).isTrue(); verify(mNativeInterface).groupSetActive(firstGroupId); diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioStateMachineTest.java index 103b5b80f9..3c87186545 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioStateMachineTest.java @@ -17,6 +17,9 @@ package com.android.bluetooth.le_audio; +import static com.android.bluetooth.le_audio.LeAudioStateMachine.CONNECT; +import static com.android.bluetooth.le_audio.LeAudioStateMachine.DISCONNECT; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.after; @@ -34,12 +37,15 @@ import android.bluetooth.BluetoothProfile; import android.content.Intent; import android.os.Bundle; import android.os.HandlerThread; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.flags.Flags; import org.junit.After; import org.junit.Before; @@ -60,6 +66,7 @@ public class LeAudioStateMachineTest { private static final int TIMEOUT_MS = 1000; @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private AdapterService mAdapterService; @Mock private LeAudioService mLeAudioService; @@ -232,4 +239,41 @@ public class LeAudioStateMachineTest { assertThat(mLeAudioStateMachine.getCurrentState()) .isInstanceOf(LeAudioStateMachine.Disconnected.class); } + + private void sendAndDispatchMessage(int what, Object obj) { + mLeAudioStateMachine.sendMessage(what, obj); + TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); + } + + @Test + @EnableFlags(Flags.FLAG_LEAUDIO_SM_IGNORE_CONNECT_EVENTS_IN_CONNECTING_STATE) + public void connectEventNeglectedWhileInConnectingState() { + allowConnection(true); + doReturn(true).when(mLeAudioNativeInterface).connectLeAudio(any(BluetoothDevice.class)); + doReturn(true).when(mLeAudioNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); + + sendAndDispatchMessage(CONNECT, mTestDevice); + // Verify that one connection state change is notified + verify(mLeAudioService, timeout(TIMEOUT_MS)) + .notifyConnectionStateChanged( + any(), + eq(BluetoothProfile.STATE_CONNECTING), + eq(BluetoothProfile.STATE_DISCONNECTED)); + assertThat(mLeAudioStateMachine.getCurrentState()) + .isInstanceOf(LeAudioStateMachine.Connecting.class); + + // Dispatch CONNECT event twice more + sendAndDispatchMessage(CONNECT, mTestDevice); + sendAndDispatchMessage(CONNECT, mTestDevice); + sendAndDispatchMessage(DISCONNECT, mTestDevice); + // Verify that one connection state change is notified + verify(mLeAudioService, timeout(TIMEOUT_MS)) + .notifyConnectionStateChanged( + any(), + eq(BluetoothProfile.STATE_DISCONNECTED), + eq(BluetoothProfile.STATE_CONNECTING)); + assertThat(mLeAudioStateMachine.getCurrentState()) + .isInstanceOf(LeAudioStateMachine.Disconnected.class); + TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); + } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_scan/AppScanStatsTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_scan/AppScanStatsTest.java index 24d8e2c0b5..b442924dae 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_scan/AppScanStatsTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_scan/AppScanStatsTest.java @@ -55,7 +55,7 @@ public class AppScanStatsTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock private ScannerMap map; - @Mock private TransitionalScanHelper mMockScanHelper; + @Mock private ScanController mMockScanController; @Mock private AdapterService mAdapterService; // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the @@ -79,10 +79,10 @@ public class AppScanStatsTest { AppScanStats appScanStats = new AppScanStats( - name, source, map, mAdapterService, mMockScanHelper, getSystemClock()); + name, source, map, mAdapterService, mMockScanController, getSystemClock()); assertThat(appScanStats.mScannerMap).isEqualTo(map); - assertThat(appScanStats.mScanHelper).isEqualTo(mMockScanHelper); + assertThat(appScanStats.mScanController).isEqualTo(mMockScanController); assertThat(appScanStats.isScanning()).isEqualTo(false); } @@ -94,7 +94,7 @@ public class AppScanStatsTest { AppScanStats appScanStats = new AppScanStats( - name, source, map, mAdapterService, mMockScanHelper, getSystemClock()); + name, source, map, mAdapterService, mMockScanController, getSystemClock()); ScanSettings settings = new ScanSettings.Builder().build(); List<ScanFilter> filters = new ArrayList<>(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_scan/BatchScanThrottlerTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_scan/BatchScanThrottlerTest.java new file mode 100644 index 0000000000..67f32c4f0c --- /dev/null +++ b/android/app/tests/unit/src/com/android/bluetooth/le_scan/BatchScanThrottlerTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2025 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.le_scan; + +import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED; + +import static com.android.bluetooth.le_scan.ScanController.DEFAULT_REPORT_DELAY_FLOOR; + +import static com.google.common.truth.Truth.assertThat; + +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanSettings; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.test.filters.SmallTest; + +import com.android.bluetooth.TestUtils.FakeTimeProvider; + +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.LongStream; + +/** Test cases for {@link BatchScanThrottler}. */ +@SmallTest +@RunWith(TestParameterInjector.class) +public class BatchScanThrottlerTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + private FakeTimeProvider mTimeProvider; + + @Before + public void setUp() { + mTimeProvider = new FakeTimeProvider(); + } + + private void advanceTime(long amountToAdvanceMillis) { + mTimeProvider.advanceTime(Duration.ofMillis(amountToAdvanceMillis)); + } + + @Test + public void basicThrottling( + @TestParameter boolean isFiltered, @TestParameter boolean isScreenOn) { + BatchScanThrottler throttler = new BatchScanThrottler(mTimeProvider, isScreenOn); + if (!isScreenOn) { + advanceTime(BatchScanThrottler.SCREEN_OFF_DELAY_MS); + } + Set<ScanClient> clients = + Collections.singleton( + createBatchScanClient(DEFAULT_REPORT_DELAY_FLOOR, isFiltered)); + long[] backoffIntervals = + getBackoffIntervals( + isScreenOn + ? DEFAULT_REPORT_DELAY_FLOOR + : BatchScanThrottler.SCREEN_OFF_MINIMUM_DELAY_FLOOR_MS); + for (long x : backoffIntervals) { + long expected = adjustExpectedInterval(x, isFiltered, isScreenOn); + assertThat(throttler.getBatchTriggerIntervalMillis(clients)).isEqualTo(expected); + } + long expected = + adjustExpectedInterval( + backoffIntervals[backoffIntervals.length - 1], isFiltered, isScreenOn); + // Ensure that subsequent calls continue to return the final throttled interval + assertThat(throttler.getBatchTriggerIntervalMillis(clients)).isEqualTo(expected); + assertThat(throttler.getBatchTriggerIntervalMillis(clients)).isEqualTo(expected); + } + + @Test + public void screenOffDelayAndReset(@TestParameter boolean screenOnAtStart) { + BatchScanThrottler throttler = new BatchScanThrottler(mTimeProvider, screenOnAtStart); + if (screenOnAtStart) { + throttler.onScreenOn(false); + } + Set<ScanClient> clients = + Collections.singleton(createBatchScanClient(DEFAULT_REPORT_DELAY_FLOOR, true)); + long[] backoffIntervals = getBackoffIntervals(DEFAULT_REPORT_DELAY_FLOOR); + advanceTime(BatchScanThrottler.SCREEN_OFF_DELAY_MS - 1); + for (long x : backoffIntervals) { + assertThat(throttler.getBatchTriggerIntervalMillis(clients)).isEqualTo(x); + } + + backoffIntervals = + getBackoffIntervals(BatchScanThrottler.SCREEN_OFF_MINIMUM_DELAY_FLOOR_MS); + advanceTime(1); + for (long x : backoffIntervals) { + assertThat(throttler.getBatchTriggerIntervalMillis(clients)).isEqualTo(x); + } + assertThat(throttler.getBatchTriggerIntervalMillis(clients)) + .isEqualTo(backoffIntervals[backoffIntervals.length - 1]); + } + + @Test + public void testScreenOnReset() { + BatchScanThrottler throttler = new BatchScanThrottler(mTimeProvider, false); + advanceTime(BatchScanThrottler.SCREEN_OFF_DELAY_MS); + Set<ScanClient> clients = + Collections.singleton(createBatchScanClient(DEFAULT_REPORT_DELAY_FLOOR, true)); + long[] backoffIntervals = + getBackoffIntervals(BatchScanThrottler.SCREEN_OFF_MINIMUM_DELAY_FLOOR_MS); + for (long x : backoffIntervals) { + assertThat(throttler.getBatchTriggerIntervalMillis(clients)).isEqualTo(x); + } + + throttler.onScreenOn(true); + backoffIntervals = getBackoffIntervals(DEFAULT_REPORT_DELAY_FLOOR); + for (long x : backoffIntervals) { + assertThat(throttler.getBatchTriggerIntervalMillis(clients)).isEqualTo(x); + } + assertThat(throttler.getBatchTriggerIntervalMillis(clients)) + .isEqualTo(backoffIntervals[backoffIntervals.length - 1]); + } + + @Test + public void resetBackoff_restartsToFirstStage(@TestParameter boolean isScreenOn) { + BatchScanThrottler throttler = new BatchScanThrottler(mTimeProvider, isScreenOn); + if (!isScreenOn) { + // Advance the time before we start the test to when the screen-off intervals should be + // used + advanceTime(BatchScanThrottler.SCREEN_OFF_DELAY_MS); + } + Set<ScanClient> clients = + Collections.singleton(createBatchScanClient(DEFAULT_REPORT_DELAY_FLOOR, true)); + long[] backoffIntervals = + getBackoffIntervals( + isScreenOn + ? DEFAULT_REPORT_DELAY_FLOOR + : BatchScanThrottler.SCREEN_OFF_MINIMUM_DELAY_FLOOR_MS); + for (long x : backoffIntervals) { + assertThat(throttler.getBatchTriggerIntervalMillis(clients)).isEqualTo(x); + } + assertThat(throttler.getBatchTriggerIntervalMillis(clients)) + .isEqualTo(backoffIntervals[backoffIntervals.length - 1]); + + throttler.resetBackoff(); + for (long x : backoffIntervals) { + assertThat(throttler.getBatchTriggerIntervalMillis(clients)).isEqualTo(x); + } + assertThat(throttler.getBatchTriggerIntervalMillis(clients)) + .isEqualTo(backoffIntervals[backoffIntervals.length - 1]); + } + + private long adjustExpectedInterval(long interval, boolean isFiltered, boolean isScreenOn) { + if (isFiltered) { + return interval; + } + long threshold = + isScreenOn + ? BatchScanThrottler.UNFILTERED_DELAY_FLOOR_MS + : BatchScanThrottler.UNFILTERED_SCREEN_OFF_DELAY_FLOOR_MS; + return Math.max(interval, threshold); + } + + private long[] getBackoffIntervals(long baseInterval) { + return LongStream.range(0, BatchScanThrottler.BACKOFF_MULTIPLIERS.length) + .map(x -> BatchScanThrottler.BACKOFF_MULTIPLIERS[(int) x] * baseInterval) + .toArray(); + } + + private ScanClient createBatchScanClient(long reportDelayMillis, boolean isFiltered) { + ScanSettings scanSettings = + new ScanSettings.Builder() + .setScanMode(SCAN_MODE_BALANCED) + .setReportDelay(reportDelayMillis) + .build(); + + return new ScanClient(1, scanSettings, createScanFilterList(isFiltered), 1); + } + + private List<ScanFilter> createScanFilterList(boolean isFiltered) { + List<ScanFilter> scanFilterList = null; + if (isFiltered) { + scanFilterList = List.of(new ScanFilter.Builder().setDeviceName("TestName").build()); + } + return scanFilterList; + } +} diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_scan/TransitionalScanHelperTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanControllerTest.java index e3db44b86b..ea051bc528 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_scan/TransitionalScanHelperTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanControllerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.bluetooth.BluetoothAdapter; @@ -43,20 +44,20 @@ import android.os.Binder; import android.os.RemoteException; import android.os.WorkSource; import android.os.test.TestLooper; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.CompanionManager; -import com.android.bluetooth.flags.Flags; import com.android.bluetooth.gatt.GattNativeInterface; import com.android.bluetooth.gatt.GattObjectsFactory; +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -73,16 +74,16 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; -/** Test cases for {@link TransitionalScanHelper}. */ +/** Test cases for {@link ScanController}. */ @SmallTest -@RunWith(AndroidJUnit4.class) -public class TransitionalScanHelperTest { +@RunWith(TestParameterInjector.class) +public class ScanControllerTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private ScannerMap mScannerMap; @Mock private ScannerMap.ScannerApp mApp; - @Mock private TransitionalScanHelper.PendingIntentInfo mPiInfo; + @Mock private ScanController.PendingIntentInfo mPiInfo; @Mock private PeriodicScanManager mPeriodicScanManager; @Mock private ScanManager mScanManager; @Mock private Resources mResources; @@ -96,7 +97,7 @@ public class TransitionalScanHelperTest { private final AttributionSource mAttributionSource = mAdapter.getAttributionSource(); private final Context mContext = InstrumentationRegistry.getTargetContext(); - private TransitionalScanHelper mScanHelper; + private ScanController mScanController; private CompanionManager mBtCompanionManager; @Before @@ -112,7 +113,7 @@ public class TransitionalScanHelperTest { doReturn(mResources).when(mAdapterService).getResources(); doReturn(mContext.getPackageManager()).when(mAdapterService).getPackageManager(); - doReturn(mContext.getSharedPreferences("TransitionalScanHelperTest", Context.MODE_PRIVATE)) + doReturn(mContext.getSharedPreferences("ScanControllerTest", Context.MODE_PRIVATE)) .when(mAdapterService) .getSharedPreferences(anyString(), anyInt()); @@ -125,15 +126,15 @@ public class TransitionalScanHelperTest { TestLooper testLooper = new TestLooper(); testLooper.startAutoDispatch(); - mScanHelper = new TransitionalScanHelper(mAdapterService, () -> false); - mScanHelper.start(testLooper.getLooper()); + mScanController = new ScanController(mAdapterService); + // mScanController.start(testLooper.getLooper()); - mScanHelper.setScannerMap(mScannerMap); + mScanController.setScannerMap(mScannerMap); } @After public void tearDown() throws Exception { - mScanHelper.stop(); + mScanController.stop(); GattObjectsFactory.setInstanceForTesting(null); ScanObjectsFactory.setInstanceForTesting(null); @@ -141,7 +142,7 @@ public class TransitionalScanHelperTest { @Test public void testParseBatchTimestamp() { - long timestampNanos = mScanHelper.parseTimestampNanos(new byte[] {-54, 7}); + long timestampNanos = mScanController.parseTimestampNanos(new byte[] {-54, 7}); assertThat(timestampNanos).isEqualTo(99700000000L); } @@ -155,7 +156,7 @@ public class TransitionalScanHelperTest { AppScanStats appScanStats = mock(AppScanStats.class); doReturn(appScanStats).when(mScannerMap).getAppScanStatsById(scannerId); - mScanHelper.continuePiStartScan(scannerId, mApp); + mScanController.continuePiStartScan(scannerId, mApp); verify(appScanStats) .recordScanStart(mPiInfo.settings, mPiInfo.filters, false, false, scannerId, null); @@ -173,7 +174,7 @@ public class TransitionalScanHelperTest { AppScanStats appScanStats = mock(AppScanStats.class); doReturn(appScanStats).when(mScannerMap).getAppScanStatsById(scannerId); - mScanHelper.continuePiStartScan(scannerId, mApp); + mScanController.continuePiStartScan(scannerId, mApp); verify(appScanStats) .recordScanStart(mPiInfo.settings, mPiInfo.filters, false, false, scannerId, null); @@ -189,7 +190,8 @@ public class TransitionalScanHelperTest { } @Test - public void onBatchScanReportsInternal_deliverBatchScan() throws RemoteException { + public void onBatchScanReportsInternal_deliverBatchScan_full( + @TestParameter boolean expectResults) throws RemoteException { int status = 1; int scannerId = 2; int reportType = ScanManager.SCAN_RESULT_TYPE_FULL; @@ -202,47 +204,79 @@ public class TransitionalScanHelperTest { Set<ScanClient> scanClientSet = new HashSet<>(); ScanClient scanClient = new ScanClient(scannerId); scanClient.associatedDevices = new ArrayList<>(); - scanClient.associatedDevices.add("02:00:00:00:00:00"); scanClient.scannerId = scannerId; + if (expectResults) { + scanClient.hasScanWithoutLocationPermission = true; + } scanClientSet.add(scanClient); doReturn(scanClientSet).when(mScanManager).getFullBatchScanQueue(); doReturn(mApp).when(mScannerMap).getById(scanClient.scannerId); + IScannerCallback callback = mock(IScannerCallback.class); + mApp.mCallback = callback; - mScanHelper.onBatchScanReportsInternal( + mScanController.onBatchScanReportsInternal( status, scannerId, reportType, numRecords, recordData); verify(mScanManager).callbackDone(scannerId, status); + if (expectResults) { + verify(callback).onBatchScanResults(any()); + } else { + verify(callback, never()).onBatchScanResults(any()); + } + } - reportType = ScanManager.SCAN_RESULT_TYPE_TRUNCATED; - recordData = + @Test + public void onBatchScanReportsInternal_deliverBatchScan_truncated( + @TestParameter boolean expectResults) throws RemoteException { + int status = 1; + int scannerId = 2; + int reportType = ScanManager.SCAN_RESULT_TYPE_TRUNCATED; + int numRecords = 1; + byte[] recordData = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x04, 0x02, 0x02, 0x00, 0x00, 0x02 }; + + Set<ScanClient> scanClientSet = new HashSet<>(); + ScanClient scanClient = new ScanClient(scannerId); + scanClient.associatedDevices = new ArrayList<>(); + if (expectResults) { + scanClient.associatedDevices.add("02:00:00:00:00:00"); + } + scanClient.scannerId = scannerId; + scanClientSet.add(scanClient); doReturn(scanClientSet).when(mScanManager).getBatchScanQueue(); + doReturn(mApp).when(mScannerMap).getById(scanClient.scannerId); IScannerCallback callback = mock(IScannerCallback.class); mApp.mCallback = callback; - mScanHelper.onBatchScanReportsInternal( + mScanController.onBatchScanReportsInternal( status, scannerId, reportType, numRecords, recordData); - verify(callback).onBatchScanResults(any()); + verify(mScanManager).callbackDone(scannerId, status); + if (expectResults) { + verify(callback).onBatchScanResults(any()); + } else { + verify(callback, never()).onBatchScanResults(any()); + } } @Test public void enforceReportDelayFloor() { - long reportDelayFloorHigher = TransitionalScanHelper.DEFAULT_REPORT_DELAY_FLOOR + 1; + long reportDelayFloorHigher = ScanController.DEFAULT_REPORT_DELAY_FLOOR + 1; ScanSettings scanSettings = new ScanSettings.Builder().setReportDelay(reportDelayFloorHigher).build(); - ScanSettings newScanSettings = mScanHelper.enforceReportDelayFloor(scanSettings); + ScanSettings newScanSettings = mScanController.enforceReportDelayFloor(scanSettings); assertThat(newScanSettings.getReportDelayMillis()) .isEqualTo(scanSettings.getReportDelayMillis()); ScanSettings scanSettingsFloor = new ScanSettings.Builder().setReportDelay(1).build(); - ScanSettings newScanSettingsFloor = mScanHelper.enforceReportDelayFloor(scanSettingsFloor); + ScanSettings newScanSettingsFloor = + mScanController.enforceReportDelayFloor(scanSettingsFloor); assertThat(newScanSettingsFloor.getReportDelayMillis()) - .isEqualTo(TransitionalScanHelper.DEFAULT_REPORT_DELAY_FLOOR); + .isEqualTo(ScanController.DEFAULT_REPORT_DELAY_FLOOR); } @Test @@ -253,7 +287,7 @@ public class TransitionalScanHelperTest { AppScanStats appScanStats = mock(AppScanStats.class); doReturn(appScanStats).when(mScannerMap).getAppScanStatsByUid(Binder.getCallingUid()); - mScanHelper.registerScanner(callback, workSource, mAttributionSource); + mScanController.registerScanner(callback, workSource, mAttributionSource); verify(mScannerMap) .add( any(), @@ -261,7 +295,7 @@ public class TransitionalScanHelperTest { eq(workSource), eq(callback), any(), - eq(mScanHelper)); + eq(mScanController)); verify(mScanManager).registerScanner(any()); } @@ -269,7 +303,7 @@ public class TransitionalScanHelperTest { public void flushPendingBatchResults() { int scannerId = 3; - mScanHelper.flushPendingBatchResults(scannerId, mAttributionSource); + mScanController.flushPendingBatchResults(scannerId, mAttributionSource); verify(mScanManager).flushBatchScanResults(new ScanClient(scannerId)); } @@ -313,7 +347,7 @@ public class TransitionalScanHelperTest { // Simulate remote client crash doThrow(new RemoteException()).when(callback).onScanResult(any()); - mScanHelper.onScanResult( + mScanController.onScanResult( eventType, addressType, address, @@ -337,7 +371,7 @@ public class TransitionalScanHelperTest { int timeout = 2; IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class); - mScanHelper.registerSync(scanResult, skip, timeout, callback, mAttributionSource); + mScanController.registerSync(scanResult, skip, timeout, callback, mAttributionSource); verify(mPeriodicScanManager).startSync(scanResult, skip, timeout, callback); } @@ -346,7 +380,7 @@ public class TransitionalScanHelperTest { int serviceData = 1; int syncHandle = 2; - mScanHelper.transferSync(mDevice, serviceData, syncHandle, mAttributionSource); + mScanController.transferSync(mDevice, serviceData, syncHandle, mAttributionSource); verify(mPeriodicScanManager).transferSync(mDevice, serviceData, syncHandle); } @@ -356,7 +390,8 @@ public class TransitionalScanHelperTest { int advHandle = 2; IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class); - mScanHelper.transferSetInfo(mDevice, serviceData, advHandle, callback, mAttributionSource); + mScanController.transferSetInfo( + mDevice, serviceData, advHandle, callback, mAttributionSource); verify(mPeriodicScanManager).transferSetInfo(mDevice, serviceData, advHandle, callback); } @@ -364,24 +399,13 @@ public class TransitionalScanHelperTest { public void unregisterSync() { IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class); - mScanHelper.unregisterSync(callback, mAttributionSource); + mScanController.unregisterSync(callback, mAttributionSource); verify(mPeriodicScanManager).stopSync(callback); } @Test - public void getCurrentUsedTrackingAdvertisement() { - mScanHelper.getCurrentUsedTrackingAdvertisement(); - verify(mScanManager).getCurrentUsedTrackingAdvertisement(); - } - - @Test - public void cleanUp_doesNotCrash() { - mScanHelper.cleanup(); - } - - @Test public void profileConnectionStateChanged_notifyScanManager() { - mScanHelper.notifyProfileConnectionStateChange( + mScanController.notifyProfileConnectionStateChange( BluetoothProfile.A2DP, BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED); @@ -401,7 +425,7 @@ public class TransitionalScanHelperTest { byte[] scanRsp = new byte[] {0x04}; int filtIndex = 5; - int advState = TransitionalScanHelper.ADVT_STATE_ONFOUND; + int advState = ScanController.ADVT_STATE_ONFOUND; int advInfoPresent = 7; String address = "00:11:22:33:FF:EE"; int addrType = BluetoothDevice.ADDRESS_TYPE_RANDOM; @@ -422,7 +446,7 @@ public class TransitionalScanHelperTest { IScannerCallback callback = mock(IScannerCallback.class); app.mCallback = callback; - app.mInfo = mock(TransitionalScanHelper.PendingIntentInfo.class); + app.mInfo = mock(ScanController.PendingIntentInfo.class); doReturn(app).when(mScannerMap).getById(scannerId); doReturn(scanClientSet).when(mScanManager).getRegularScanQueue(); @@ -443,7 +467,7 @@ public class TransitionalScanHelperTest { rssiValue, timeStamp); - mScanHelper.onTrackAdvFoundLost(advtFilterOnFoundOnLostInfo); + mScanController.onTrackAdvFoundLost(advtFilterOnFoundOnLostInfo); ArgumentCaptor<ScanResult> result = ArgumentCaptor.forClass(ScanResult.class); verify(callback).onFoundOrLost(eq(true), result.capture()); assertThat(result.getValue().getDevice()).isNotNull(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanManagerTest.java index e0f908d70b..041a982a9b 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanManagerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanManagerTest.java @@ -126,7 +126,7 @@ public class ScanManagerTest { @Mock private LocationManager mLocationManager; @Mock private MetricsLogger mMetricsLogger; @Mock private ScanNativeInterface mScanNativeInterface; - @Mock private TransitionalScanHelper mScanHelper; + @Mock private ScanController mScanController; @Spy private GattObjectsFactory mGattObjectsFactory = GattObjectsFactory.getInstance(); @Spy private ScanObjectsFactory mScanObjectsFactory = ScanObjectsFactory.getInstance(); @@ -238,7 +238,7 @@ public class ScanManagerTest { mScanManager = new ScanManager( mAdapterService, - mScanHelper, + mScanController, mBluetoothAdapterProxy, mLooper.getLooper(), mTimeProvider); @@ -251,7 +251,7 @@ public class ScanManagerTest { null, null, mAdapterService, - mScanHelper, + mScanController, mTimeProvider)); } @@ -1045,7 +1045,7 @@ public class ScanManagerTest { assertThat(mScanManager.getRegularScanQueue()).doesNotContain(client); assertThat(mScanManager.getSuspendedScanQueue()).doesNotContain(client); assertThat(mScanManager.getBatchScanQueue()).contains(client); - assertThat(mScanManager.getBatchScanParams().scanMode).isEqualTo(expectedScanMode); + assertThat(mScanManager.getBatchScanParams().mScanMode).isEqualTo(expectedScanMode); } } @@ -1075,13 +1075,13 @@ public class ScanManagerTest { sendMessageWaitForProcessed(createStartStopScanMessage(true, client)); assertThat(mScanManager.getRegularScanQueue()).doesNotContain(client); assertThat(mScanManager.getSuspendedScanQueue()).doesNotContain(client); - assertThat(mScanManager.getBatchScanParams().scanMode).isEqualTo(expectedScanMode); + assertThat(mScanManager.getBatchScanParams().mScanMode).isEqualTo(expectedScanMode); // Turn on screen sendMessageWaitForProcessed(createScreenOnOffMessage(true)); assertThat(mScanManager.getRegularScanQueue()).doesNotContain(client); assertThat(mScanManager.getSuspendedScanQueue()).doesNotContain(client); assertThat(mScanManager.getBatchScanQueue()).contains(client); - assertThat(mScanManager.getBatchScanParams().scanMode).isEqualTo(expectedScanMode); + assertThat(mScanManager.getBatchScanParams().mScanMode).isEqualTo(expectedScanMode); } } @@ -1152,7 +1152,7 @@ public class ScanManagerTest { assertThat(mScanManager.getRegularScanQueue()).doesNotContain(client); assertThat(mScanManager.getSuspendedScanQueue()).doesNotContain(client); assertThat(mScanManager.getBatchScanQueue()).contains(client); - assertThat(mScanManager.getBatchScanParams().scanMode) + assertThat(mScanManager.getBatchScanParams().mScanMode) .isEqualTo(expectedScanMode); // Turn on screen sendMessageWaitForProcessed(createScreenOnOffMessage(true)); @@ -1166,7 +1166,7 @@ public class ScanManagerTest { assertThat(mScanManager.getRegularScanQueue()).doesNotContain(client); assertThat(mScanManager.getSuspendedScanQueue()).doesNotContain(client); assertThat(mScanManager.getBatchScanQueue()).contains(client); - assertThat(mScanManager.getBatchScanParams().scanMode) + assertThat(mScanManager.getBatchScanParams().mScanMode) .isEqualTo(expectedScanMode); }); } @@ -1257,7 +1257,7 @@ public class ScanManagerTest { source, null, mAdapterService, - mScanHelper, + mScanController, mTimeProvider)); // Create scan client for the app, which also records scan start ScanClient client = createScanClient(isFiltered, scanMode, UID, appScanStats); @@ -1328,7 +1328,7 @@ public class ScanManagerTest { source1, null, mAdapterService, - mScanHelper, + mScanController, mTimeProvider)); // Create scan client for the first app ScanClient client1 = @@ -1350,7 +1350,7 @@ public class ScanManagerTest { source2, null, mAdapterService, - mScanHelper, + mScanController, mTimeProvider)); // Create scan client for the second app ScanClient client2 = createScanClient(isFiltered, SCAN_MODE_BALANCED, UID_2, appScanStats2); @@ -1386,7 +1386,7 @@ public class ScanManagerTest { source3, null, mAdapterService, - mScanHelper, + mScanController, mTimeProvider)); // Create scan client for the third app ScanClient client3 = @@ -1423,7 +1423,7 @@ public class ScanManagerTest { source4, null, mAdapterService, - mScanHelper, + mScanController, mTimeProvider)); // Create scan client for the fourth app ScanClient client4 = @@ -1909,7 +1909,7 @@ public class ScanManagerTest { mScanManager = new ScanManager( mAdapterService, - mScanHelper, + mScanController, mBluetoothAdapterProxy, mLooper.getLooper(), mTimeProvider); diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScannerMapTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScannerMapTest.java index 83cdcceae9..b8ad36f891 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScannerMapTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScannerMapTest.java @@ -61,7 +61,7 @@ public class ScannerMapTest { @Mock private AdapterService mAdapterService; @Mock private PackageManager mMockPackageManager; - @Mock private TransitionalScanHelper mMockTransitionalScanHelper; + @Mock private ScanController mMockScanController; @Mock private IScannerCallback mMockScannerCallback; private final AttributionSource mAttributionSource = InstrumentationRegistry.getTargetContext().getAttributionSource(); @@ -85,8 +85,7 @@ public class ScannerMapTest { @Test public void getByMethodsWithPii() { ScannerMap scannerMap = new ScannerMap(); - TransitionalScanHelper.PendingIntentInfo info = - new TransitionalScanHelper.PendingIntentInfo(); + ScanController.PendingIntentInfo info = new ScanController.PendingIntentInfo(); info.callingUid = UID; info.callingPackage = APP_NAME; info.intent = @@ -98,11 +97,7 @@ public class ScannerMapTest { UUID uuid = UUID.randomUUID(); ScannerMap.ScannerApp app = scannerMap.add( - uuid, - mAttributionSource, - info, - mAdapterService, - mMockTransitionalScanHelper); + uuid, mAttributionSource, info, mAdapterService, mMockScanController); app.mId = SCANNER_ID; ScannerMap.ScannerApp scannerMapById = scannerMap.getById(SCANNER_ID); @@ -132,7 +127,7 @@ public class ScannerMapTest { null, mMockScannerCallback, mAdapterService, - mMockTransitionalScanHelper); + mMockScanController); int appUid = Binder.getCallingUid(); app.mId = SCANNER_ID; @@ -161,7 +156,7 @@ public class ScannerMapTest { null, mMockScannerCallback, mAdapterService, - mMockTransitionalScanHelper); + mMockScanController); app.mId = SCANNER_ID; ScannerMap.ScannerApp scannerMapById = scannerMap.getById(SCANNER_ID); @@ -181,7 +176,7 @@ public class ScannerMapTest { null, mMockScannerCallback, mAdapterService, - mMockTransitionalScanHelper); + mMockScanController); scannerMap.dump(sb); scannerMap.dumpApps(sb, ProfileService::println); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapAccountItemTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapAccountItemTest.java index 4993b863a6..8e1be7da7f 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapAccountItemTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapAccountItemTest.java @@ -202,7 +202,7 @@ public class BluetoothMapAccountItemTest { TEST_UCI, TEST_UCI_PREFIX); - assertThat(accountItem).isNotEqualTo(null); + assertThat(accountItem).isNotNull(); } @SuppressWarnings("EqualsIncompatibleType") diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java index 316dd5f621..44cea7dd97 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java @@ -55,7 +55,6 @@ import com.android.obex.ResponseCodes; import com.google.android.mms.pdu.PduHeaders; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -233,9 +232,9 @@ public class BluetoothMapContentObserverTest { () -> observer.pushMessage(message, folderElement, appParams, null)); // Validate that 3 addresses were inserted into the database with 2 being the recipients - Assert.assertEquals(3, mProvider.mContents.size()); - assertThat(mProvider.mContents.contains(TEST_NUMBER_ONE)).isTrue(); - assertThat(mProvider.mContents.contains(TEST_NUMBER_TWO)).isTrue(); + assertThat(mProvider.mContents).hasSize(3); + assertThat(mProvider.mContents).contains(TEST_NUMBER_ONE); + assertThat(mProvider.mContents).contains(TEST_NUMBER_TWO); } @Test @@ -305,47 +304,40 @@ public class BluetoothMapContentObserverTest { @Test public void testSetContactList() { - Map<String, BluetoothMapConvoContactElement> map = Map.of(); + mObserver.setContactList(Map.of(), true); - mObserver.setContactList(map, true); - - Assert.assertEquals(mObserver.getContactList(), map); + assertThat(mObserver.getContactList()).isEmpty(); } @Test public void testSetMsgListSms() { - Map<Long, BluetoothMapContentObserver.Msg> map = Map.of(); - - mObserver.setMsgListSms(map, true); + mObserver.setMsgListSms(Map.of(), true); - Assert.assertEquals(mObserver.getMsgListSms(), map); + assertThat(mObserver.getMsgListSms()).isEmpty(); } @Test public void testSetMsgListMsg() { - Map<Long, BluetoothMapContentObserver.Msg> map = Map.of(); + mObserver.setMsgListMsg(Map.of(), true); - mObserver.setMsgListMsg(map, true); - - Assert.assertEquals(mObserver.getMsgListMsg(), map); + assertThat(mObserver.getMsgListMsg()).isEmpty(); } @Test public void testSetMsgListMms() { - Map<Long, BluetoothMapContentObserver.Msg> map = Map.of(); + mObserver.setMsgListMms(Map.of(), true); - mObserver.setMsgListMms(map, true); - - Assert.assertEquals(mObserver.getMsgListMms(), map); + assertThat(mObserver.getMsgListMms()).isEmpty(); } @Test public void testSetNotificationRegistration_withNullHandler() throws Exception { when(mClient.getMessageHandler()).thenReturn(null); - Assert.assertEquals( - mObserver.setNotificationRegistration(BluetoothMapAppParams.NOTIFICATION_STATUS_NO), - ResponseCodes.OBEX_HTTP_UNAVAILABLE); + assertThat( + mObserver.setNotificationRegistration( + BluetoothMapAppParams.NOTIFICATION_STATUS_NO)) + .isEqualTo(ResponseCodes.OBEX_HTTP_UNAVAILABLE); } @Test @@ -357,9 +349,10 @@ public class BluetoothMapContentObserverTest { when(mClient.getMessageHandler()).thenReturn(handler); when(mClient.isValidMnsRecord()).thenReturn(false); - Assert.assertEquals( - mObserver.setNotificationRegistration(BluetoothMapAppParams.NOTIFICATION_STATUS_NO), - ResponseCodes.OBEX_HTTP_OK); + assertThat( + mObserver.setNotificationRegistration( + BluetoothMapAppParams.NOTIFICATION_STATUS_NO)) + .isEqualTo(ResponseCodes.OBEX_HTTP_OK); } @Test @@ -371,9 +364,10 @@ public class BluetoothMapContentObserverTest { when(mClient.getMessageHandler()).thenReturn(handler); when(mClient.isValidMnsRecord()).thenReturn(true); - Assert.assertEquals( - mObserver.setNotificationRegistration(BluetoothMapAppParams.NOTIFICATION_STATUS_NO), - ResponseCodes.OBEX_HTTP_OK); + assertThat( + mObserver.setNotificationRegistration( + BluetoothMapAppParams.NOTIFICATION_STATUS_NO)) + .isEqualTo(ResponseCodes.OBEX_HTTP_OK); } @Test @@ -392,7 +386,7 @@ public class BluetoothMapContentObserverTest { TEST_HANDLE_ONE, type, TEST_URI_STR, TEST_STATUS_VALUE)) .isTrue(); - Assert.assertEquals(msg.flagRead, TEST_STATUS_VALUE); + assertThat(msg.flagRead).isEqualTo(TEST_STATUS_VALUE); } @Test @@ -411,7 +405,7 @@ public class BluetoothMapContentObserverTest { TEST_HANDLE_ONE, type, TEST_URI_STR, TEST_STATUS_VALUE)) .isTrue(); - Assert.assertEquals(msg.flagRead, TEST_STATUS_VALUE); + assertThat(msg.flagRead).isEqualTo(TEST_STATUS_VALUE); } @Test @@ -429,7 +423,7 @@ public class BluetoothMapContentObserverTest { TEST_HANDLE_ONE, type, TEST_URI_STR, TEST_STATUS_VALUE)) .isTrue(); - Assert.assertEquals(msg.flagRead, TEST_STATUS_VALUE); + assertThat(msg.flagRead).isEqualTo(TEST_STATUS_VALUE); } @Test @@ -439,7 +433,7 @@ public class BluetoothMapContentObserverTest { createMsgWithTypeAndThreadId(Mms.MESSAGE_BOX_ALL, TEST_THREAD_ID); map.put(TEST_HANDLE_ONE, msg); mObserver.setMsgListMms(map, true); - Assert.assertEquals(msg.threadId, TEST_THREAD_ID); + assertThat(msg.threadId).isEqualTo(TEST_THREAD_ID); MatrixCursor cursor = new MatrixCursor(new String[] {Mms.THREAD_ID}); cursor.addRow(new Object[] {TEST_THREAD_ID}); @@ -452,7 +446,7 @@ public class BluetoothMapContentObserverTest { assertThat(mObserver.deleteMessageMms(TEST_HANDLE_ONE)).isTrue(); - Assert.assertEquals(msg.threadId, BluetoothMapContentObserver.DELETED_THREAD_ID); + assertThat(msg.threadId).isEqualTo(BluetoothMapContentObserver.DELETED_THREAD_ID); } @Test @@ -485,7 +479,7 @@ public class BluetoothMapContentObserverTest { createMsgWithTypeAndThreadId(Sms.MESSAGE_TYPE_ALL, TEST_THREAD_ID); map.put(TEST_HANDLE_ONE, msg); mObserver.setMsgListSms(map, true); - Assert.assertEquals(msg.threadId, TEST_THREAD_ID); + assertThat(msg.threadId).isEqualTo(TEST_THREAD_ID); MatrixCursor cursor = new MatrixCursor(new String[] {Mms.THREAD_ID}); cursor.addRow(new Object[] {TEST_THREAD_ID}); @@ -498,7 +492,7 @@ public class BluetoothMapContentObserverTest { assertThat(mObserver.deleteMessageSms(TEST_HANDLE_ONE)).isTrue(); - Assert.assertEquals(msg.threadId, BluetoothMapContentObserver.DELETED_THREAD_ID); + assertThat(msg.threadId).isEqualTo(BluetoothMapContentObserver.DELETED_THREAD_ID); } @Test @@ -531,8 +525,8 @@ public class BluetoothMapContentObserverTest { createMsgWithTypeAndThreadId(Mms.MESSAGE_BOX_ALL, TEST_THREAD_ID); map.put(TEST_HANDLE_ONE, msg); mObserver.setMsgListMms(map, true); - Assert.assertEquals(msg.threadId, TEST_THREAD_ID); - Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_ALL); + assertThat(msg.threadId).isEqualTo(TEST_THREAD_ID); + assertThat(msg.type).isEqualTo(Mms.MESSAGE_BOX_ALL); MatrixCursor cursor = new MatrixCursor( @@ -556,8 +550,8 @@ public class BluetoothMapContentObserverTest { assertThat(mObserver.unDeleteMessageMms(TEST_HANDLE_ONE)).isTrue(); - Assert.assertEquals(msg.threadId, TEST_OLD_THREAD_ID); - Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_INBOX); + assertThat(msg.threadId).isEqualTo(TEST_OLD_THREAD_ID); + assertThat(msg.type).isEqualTo(Mms.MESSAGE_BOX_INBOX); } @Test @@ -567,8 +561,8 @@ public class BluetoothMapContentObserverTest { createMsgWithTypeAndThreadId(Mms.MESSAGE_BOX_ALL, TEST_THREAD_ID); map.put(TEST_HANDLE_ONE, msg); mObserver.setMsgListMms(map, true); - Assert.assertEquals(msg.threadId, TEST_THREAD_ID); - Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_ALL); + assertThat(msg.threadId).isEqualTo(TEST_THREAD_ID); + assertThat(msg.type).isEqualTo(Mms.MESSAGE_BOX_ALL); MatrixCursor cursor = new MatrixCursor( @@ -592,8 +586,8 @@ public class BluetoothMapContentObserverTest { assertThat(mObserver.unDeleteMessageMms(TEST_HANDLE_ONE)).isTrue(); - Assert.assertEquals(msg.threadId, TEST_OLD_THREAD_ID); - Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_INBOX); + assertThat(msg.threadId).isEqualTo(TEST_OLD_THREAD_ID); + assertThat(msg.type).isEqualTo(Mms.MESSAGE_BOX_INBOX); } @Test @@ -603,8 +597,8 @@ public class BluetoothMapContentObserverTest { createMsgWithTypeAndThreadId(Mms.MESSAGE_BOX_ALL, TEST_THREAD_ID); map.put(TEST_HANDLE_ONE, msg); mObserver.setMsgListMms(map, true); - Assert.assertEquals(msg.threadId, TEST_THREAD_ID); - Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_ALL); + assertThat(msg.threadId).isEqualTo(TEST_THREAD_ID); + assertThat(msg.type).isEqualTo(Mms.MESSAGE_BOX_ALL); MatrixCursor cursor = new MatrixCursor( @@ -622,8 +616,8 @@ public class BluetoothMapContentObserverTest { assertThat(mObserver.unDeleteMessageMms(TEST_HANDLE_ONE)).isTrue(); // Nothing changes when thread id is not BluetoothMapContentObserver.DELETED_THREAD_ID - Assert.assertEquals(msg.threadId, TEST_THREAD_ID); - Assert.assertEquals(msg.type, Sms.MESSAGE_TYPE_ALL); + assertThat(msg.threadId).isEqualTo(TEST_THREAD_ID); + assertThat(msg.type).isEqualTo(Sms.MESSAGE_TYPE_ALL); } @Test @@ -633,8 +627,8 @@ public class BluetoothMapContentObserverTest { createMsgWithTypeAndThreadId(Sms.MESSAGE_TYPE_ALL, TEST_THREAD_ID); map.put(TEST_HANDLE_ONE, msg); mObserver.setMsgListSms(map, true); - Assert.assertEquals(msg.threadId, TEST_THREAD_ID); - Assert.assertEquals(msg.type, Sms.MESSAGE_TYPE_ALL); + assertThat(msg.threadId).isEqualTo(TEST_THREAD_ID); + assertThat(msg.type).isEqualTo(Sms.MESSAGE_TYPE_ALL); MatrixCursor cursor = new MatrixCursor(new String[] {Sms.THREAD_ID, Sms.ADDRESS}); cursor.addRow(new Object[] {BluetoothMapContentObserver.DELETED_THREAD_ID, TEST_ADDRESS}); @@ -650,8 +644,8 @@ public class BluetoothMapContentObserverTest { assertThat(mObserver.unDeleteMessageSms(TEST_HANDLE_ONE)).isTrue(); - Assert.assertEquals(msg.threadId, TEST_OLD_THREAD_ID); - Assert.assertEquals(msg.type, Sms.MESSAGE_TYPE_INBOX); + assertThat(msg.threadId).isEqualTo(TEST_OLD_THREAD_ID); + assertThat(msg.type).isEqualTo(Sms.MESSAGE_TYPE_INBOX); } @Test @@ -661,8 +655,8 @@ public class BluetoothMapContentObserverTest { createMsgWithTypeAndThreadId(Sms.MESSAGE_TYPE_ALL, TEST_THREAD_ID); map.put(TEST_HANDLE_ONE, msg); mObserver.setMsgListSms(map, true); - Assert.assertEquals(msg.threadId, TEST_THREAD_ID); - Assert.assertEquals(msg.type, Sms.MESSAGE_TYPE_ALL); + assertThat(msg.threadId).isEqualTo(TEST_THREAD_ID); + assertThat(msg.type).isEqualTo(Sms.MESSAGE_TYPE_ALL); MatrixCursor cursor = new MatrixCursor(new String[] {Sms.THREAD_ID, Sms.ADDRESS}); cursor.addRow(new Object[] {TEST_THREAD_ID, TEST_ADDRESS}); @@ -676,8 +670,8 @@ public class BluetoothMapContentObserverTest { assertThat(mObserver.unDeleteMessageSms(TEST_HANDLE_ONE)).isTrue(); // Nothing changes when thread id is not BluetoothMapContentObserver.DELETED_THREAD_ID - Assert.assertEquals(msg.threadId, TEST_THREAD_ID); - Assert.assertEquals(msg.type, Sms.MESSAGE_TYPE_ALL); + assertThat(msg.threadId).isEqualTo(TEST_THREAD_ID); + assertThat(msg.type).isEqualTo(Sms.MESSAGE_TYPE_ALL); } @Test @@ -691,11 +685,11 @@ public class BluetoothMapContentObserverTest { BluetoothMapContentObserver.PushMsgInfo msgInfo = new BluetoothMapContentObserver.PushMsgInfo(id, transparent, retry, phone, uri); - Assert.assertEquals(msgInfo.id, id); - Assert.assertEquals(msgInfo.transparent, transparent); - Assert.assertEquals(msgInfo.retry, retry); - Assert.assertEquals(msgInfo.phone, phone); - Assert.assertEquals(msgInfo.uri, uri); + assertThat(msgInfo.id).isEqualTo(id); + assertThat(msgInfo.transparent).isEqualTo(transparent); + assertThat(msgInfo.retry).isEqualTo(retry); + assertThat(msgInfo.phone).isEqualTo(phone); + assertThat(msgInfo.uri).isEqualTo(uri); } @Test @@ -719,7 +713,7 @@ public class BluetoothMapContentObserverTest { TEST_HANDLE_ONE, BluetoothMapAppParams.STATUS_VALUE_YES)) .isTrue(); - Assert.assertEquals(msg.folderId, TEST_DELETE_FOLDER_ID); + assertThat(msg.folderId).isEqualTo(TEST_DELETE_FOLDER_ID); } @Test @@ -768,7 +762,7 @@ public class BluetoothMapContentObserverTest { TEST_HANDLE_ONE, BluetoothMapAppParams.STATUS_VALUE_NO)) .isTrue(); - Assert.assertEquals(msg.folderId, TEST_INBOX_FOLDER_ID); + assertThat(msg.folderId).isEqualTo(TEST_INBOX_FOLDER_ID); } @Test @@ -797,7 +791,7 @@ public class BluetoothMapContentObserverTest { TEST_HANDLE_ONE, BluetoothMapAppParams.STATUS_VALUE_NO)) .isTrue(); - Assert.assertEquals(msg.folderId, TEST_INBOX_FOLDER_ID); + assertThat(msg.folderId).isEqualTo(TEST_INBOX_FOLDER_ID); } @Test @@ -828,7 +822,7 @@ public class BluetoothMapContentObserverTest { TEST_HANDLE_ONE, BluetoothMapAppParams.STATUS_VALUE_NO)) .isTrue(); - Assert.assertEquals(msg.folderId, TEST_OLD_FOLDER_ID); + assertThat(msg.folderId).isEqualTo(TEST_OLD_FOLDER_ID); } @Test @@ -949,10 +943,10 @@ public class BluetoothMapContentObserverTest { mObserver.initMsgList(); BluetoothMapContentObserver.Msg msg = mObserver.getMsgListSms().get((long) TEST_ID); - Assert.assertEquals(msg.id, TEST_ID); - Assert.assertEquals(msg.type, TEST_SMS_TYPE_ALL); - Assert.assertEquals(msg.threadId, TEST_THREAD_ID); - Assert.assertEquals(msg.flagRead, TEST_READ_FLAG_ONE); + assertThat(msg.id).isEqualTo(TEST_ID); + assertThat(msg.type).isEqualTo(TEST_SMS_TYPE_ALL); + assertThat(msg.threadId).isEqualTo(TEST_THREAD_ID); + assertThat(msg.flagRead).isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -988,10 +982,10 @@ public class BluetoothMapContentObserverTest { mObserver.initMsgList(); BluetoothMapContentObserver.Msg msg = mObserver.getMsgListMms().get((long) TEST_ID); - Assert.assertEquals(msg.id, TEST_ID); - Assert.assertEquals(msg.type, TEST_MMS_TYPE_ALL); - Assert.assertEquals(msg.threadId, TEST_THREAD_ID); - Assert.assertEquals(msg.flagRead, TEST_READ_FLAG_ZERO); + assertThat(msg.id).isEqualTo(TEST_ID); + assertThat(msg.type).isEqualTo(TEST_MMS_TYPE_ALL); + assertThat(msg.threadId).isEqualTo(TEST_THREAD_ID); + assertThat(msg.flagRead).isEqualTo(TEST_READ_FLAG_ZERO); } @Test @@ -1028,9 +1022,9 @@ public class BluetoothMapContentObserverTest { mObserver.initMsgList(); BluetoothMapContentObserver.Msg msg = mObserver.getMsgListMsg().get((long) TEST_ID); - Assert.assertEquals(msg.id, TEST_ID); - Assert.assertEquals(msg.folderId, TEST_INBOX_FOLDER_ID); - Assert.assertEquals(msg.flagRead, TEST_READ_FLAG_ONE); + assertThat(msg.id).isEqualTo(TEST_ID); + assertThat(msg.folderId).isEqualTo(TEST_INBOX_FOLDER_ID); + assertThat(msg.flagRead).isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1077,16 +1071,16 @@ public class BluetoothMapContentObserverTest { BluetoothMapConvoContactElement contactElement = mObserver.getContactList().get(TEST_UCI); final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); - Assert.assertEquals(contactElement.getContactId(), TEST_UCI); - Assert.assertEquals(contactElement.getName(), TEST_NAME); - Assert.assertEquals(contactElement.getDisplayName(), TEST_DISPLAY_NAME); - Assert.assertEquals(contactElement.getBtUid(), TEST_BT_UID); - Assert.assertEquals(contactElement.getChatState(), TEST_CHAT_STATE); - Assert.assertEquals(contactElement.getPresenceStatus(), TEST_STATUS_TEXT); - Assert.assertEquals(contactElement.getPresenceAvailability(), TEST_PRESENCE_STATE); - Assert.assertEquals( - contactElement.getLastActivityString(), format.format(TEST_LAST_ACTIVITY)); - Assert.assertEquals(contactElement.getPriority(), TEST_PRIORITY); + assertThat(contactElement.getContactId()).isEqualTo(TEST_UCI); + assertThat(contactElement.getName()).isEqualTo(TEST_NAME); + assertThat(contactElement.getDisplayName()).isEqualTo(TEST_DISPLAY_NAME); + assertThat(contactElement.getBtUid()).isEqualTo(TEST_BT_UID); + assertThat(contactElement.getChatState()).isEqualTo(TEST_CHAT_STATE); + assertThat(contactElement.getPresenceStatus()).isEqualTo(TEST_STATUS_TEXT); + assertThat(contactElement.getPresenceAvailability()).isEqualTo(TEST_PRESENCE_STATE); + assertThat(contactElement.getLastActivityString()) + .isEqualTo(format.format(TEST_LAST_ACTIVITY)); + assertThat(contactElement.getPriority()).isEqualTo(TEST_PRIORITY); } @Test @@ -1130,11 +1124,11 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMsg(TEST_URI); - Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).type, TEST_INBOX_FOLDER_ID); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_INBOX_FOLDER_ID); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1180,11 +1174,11 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMsg(TEST_URI); - Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).type, TEST_INBOX_FOLDER_ID); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_INBOX_FOLDER_ID); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1215,11 +1209,11 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMsg(TEST_URI); - Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).type, TEST_INBOX_FOLDER_ID); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_INBOX_FOLDER_ID); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1250,11 +1244,11 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMsg(TEST_URI); - Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).folderId, TEST_DELETE_FOLDER_ID); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).folderId) + .isEqualTo(TEST_DELETE_FOLDER_ID); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1287,11 +1281,11 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMsg(TEST_URI); - Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).folderId, TEST_SENT_FOLDER_ID); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).folderId) + .isEqualTo(TEST_SENT_FOLDER_ID); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1328,11 +1322,11 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMsg(TEST_URI); - Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).folderId, TEST_SENT_FOLDER_ID); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).folderId) + .isEqualTo(TEST_SENT_FOLDER_ID); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1364,11 +1358,11 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMsg(TEST_URI); - Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).folderId, TEST_INBOX_FOLDER_ID); - Assert.assertEquals( - mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).folderId) + .isEqualTo(TEST_INBOX_FOLDER_ID); + assertThat(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1414,12 +1408,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMms(); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId, TEST_THREAD_ID); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_MMS_TYPE_ALL); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(TEST_THREAD_ID); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1465,12 +1460,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMms(); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId, TEST_THREAD_ID); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_MMS_TYPE_ALL); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(TEST_THREAD_ID); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1521,7 +1517,7 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMms(); - Assert.assertEquals(null, mObserver.getMsgListMms().get(TEST_HANDLE_ONE)); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE)).isNull(); } @Test @@ -1555,12 +1551,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMms(); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId, TEST_THREAD_ID); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_MMS_TYPE_ALL); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(TEST_THREAD_ID); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1595,12 +1592,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMms(); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId, TEST_THREAD_ID); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_MMS_TYPE_ALL); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(TEST_THREAD_ID); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1635,12 +1633,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMms(); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId, TEST_THREAD_ID); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_MMS_TYPE_ALL); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(TEST_THREAD_ID); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1675,13 +1674,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMms(); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId, - BluetoothMapContentObserver.DELETED_THREAD_ID); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_MMS_TYPE_ALL); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(BluetoothMapContentObserver.DELETED_THREAD_ID); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1717,12 +1716,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesMms(); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId, undeletedThreadId); - Assert.assertEquals( - mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_MMS_TYPE_ALL); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(undeletedThreadId); + assertThat(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1766,13 +1766,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesSms(); - Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type, TEST_SMS_TYPE_INBOX); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId, TEST_THREAD_ID); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_SMS_TYPE_INBOX); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(TEST_THREAD_ID); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1814,12 +1814,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesSms(); - Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type, TEST_SMS_TYPE_ALL); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId, TEST_THREAD_ID); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_SMS_TYPE_ALL); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(TEST_THREAD_ID); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1865,7 +1866,7 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesSms(); - Assert.assertEquals(null, mObserver.getMsgListSms().get(TEST_HANDLE_ONE)); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE)).isNull(); } @Test @@ -1892,12 +1893,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesSms(); - Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type, TEST_SMS_TYPE_ALL); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId, TEST_THREAD_ID); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_SMS_TYPE_ALL); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(TEST_THREAD_ID); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1924,12 +1926,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesSms(); - Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type, TEST_SMS_TYPE_ALL); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId, TEST_THREAD_ID); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_SMS_TYPE_ALL); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(TEST_THREAD_ID); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1959,13 +1962,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesSms(); - Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type, TEST_SMS_TYPE_ALL); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId, - BluetoothMapContentObserver.DELETED_THREAD_ID); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_SMS_TYPE_ALL); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(BluetoothMapContentObserver.DELETED_THREAD_ID); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -1993,12 +1996,13 @@ public class BluetoothMapContentObserverTest { mObserver.handleMsgListChangesSms(); - Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE); - Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type, TEST_SMS_TYPE_ALL); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId, undeletedThreadId); - Assert.assertEquals( - mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead, TEST_READ_FLAG_ONE); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id).isEqualTo(TEST_HANDLE_ONE); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type) + .isEqualTo(TEST_SMS_TYPE_ALL); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId) + .isEqualTo(undeletedThreadId); + assertThat(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead) + .isEqualTo(TEST_READ_FLAG_ONE); } @Test @@ -2074,7 +2078,7 @@ public class BluetoothMapContentObserverTest { mObserver.actionMessageSentDisconnected(mContext, mIntent, 1); - assertThat(mmsMsgList.containsKey(TEST_HANDLE_ONE)).isTrue(); + assertThat(mmsMsgList).containsKey(TEST_HANDLE_ONE); } @Test @@ -2111,7 +2115,7 @@ public class BluetoothMapContentObserverTest { mObserver.actionMmsSent(mContext, mIntent, 1, mmsMsgList); - assertThat(mmsMsgList.containsKey(TEST_HANDLE_ONE)).isTrue(); + assertThat(mmsMsgList).containsKey(TEST_HANDLE_ONE); } @Test @@ -2158,7 +2162,7 @@ public class BluetoothMapContentObserverTest { mObserver.actionMmsSent(mContext, mIntent, Activity.RESULT_OK, mmsMsgList); - assertThat(mmsMsgList.containsKey(TEST_HANDLE_ONE)).isTrue(); + assertThat(mmsMsgList).containsKey(TEST_HANDLE_ONE); } @Test @@ -2176,7 +2180,7 @@ public class BluetoothMapContentObserverTest { mObserver.actionMmsSent(mContext, mIntent, Activity.RESULT_FIRST_USER, mmsMsgList); - Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_OUTBOX); + assertThat(msg.type).isEqualTo(Mms.MESSAGE_BOX_OUTBOX); } @Test @@ -2315,16 +2319,16 @@ public class BluetoothMapContentObserverTest { BluetoothMapConvoContactElement contactElement = mObserver.getContactList().get(TEST_UCI); final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); - Assert.assertEquals(contactElement.getContactId(), TEST_UCI); - Assert.assertEquals(contactElement.getName(), TEST_NAME); - Assert.assertEquals(contactElement.getDisplayName(), TEST_DISPLAY_NAME); - Assert.assertEquals(contactElement.getBtUid(), TEST_BT_UID); - Assert.assertEquals(contactElement.getChatState(), TEST_CHAT_STATE); - Assert.assertEquals(contactElement.getPresenceStatus(), TEST_STATUS_TEXT); - Assert.assertEquals(contactElement.getPresenceAvailability(), TEST_PRESENCE_STATE); - Assert.assertEquals( - contactElement.getLastActivityString(), format.format(TEST_LAST_ACTIVITY)); - Assert.assertEquals(contactElement.getPriority(), TEST_PRIORITY); + assertThat(contactElement.getContactId()).isEqualTo(TEST_UCI); + assertThat(contactElement.getName()).isEqualTo(TEST_NAME); + assertThat(contactElement.getDisplayName()).isEqualTo(TEST_DISPLAY_NAME); + assertThat(contactElement.getBtUid()).isEqualTo(TEST_BT_UID); + assertThat(contactElement.getChatState()).isEqualTo(TEST_CHAT_STATE); + assertThat(contactElement.getPresenceStatus()).isEqualTo(TEST_STATUS_TEXT); + assertThat(contactElement.getPresenceAvailability()).isEqualTo(TEST_PRESENCE_STATE); + assertThat(contactElement.getLastActivityString()) + .isEqualTo(format.format(TEST_LAST_ACTIVITY)); + assertThat(contactElement.getPriority()).isEqualTo(TEST_PRIORITY); } @Test @@ -2386,16 +2390,16 @@ public class BluetoothMapContentObserverTest { BluetoothMapConvoContactElement contactElement = mObserver.getContactList().get(TEST_UCI); final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); - Assert.assertEquals(contactElement.getContactId(), TEST_UCI); - Assert.assertEquals(contactElement.getName(), TEST_NAME); - Assert.assertEquals(contactElement.getDisplayName(), TEST_DISPLAY_NAME); - Assert.assertEquals(contactElement.getBtUid(), TEST_BT_UID); - Assert.assertEquals(contactElement.getChatState(), TEST_CHAT_STATE); - Assert.assertEquals(contactElement.getPresenceStatus(), TEST_STATUS_TEXT); - Assert.assertEquals(contactElement.getPresenceAvailability(), TEST_PRESENCE_STATE); - Assert.assertEquals( - contactElement.getLastActivityString(), format.format(TEST_LAST_ACTIVITY)); - Assert.assertEquals(contactElement.getPriority(), TEST_PRIORITY); + assertThat(contactElement.getContactId()).isEqualTo(TEST_UCI); + assertThat(contactElement.getName()).isEqualTo(TEST_NAME); + assertThat(contactElement.getDisplayName()).isEqualTo(TEST_DISPLAY_NAME); + assertThat(contactElement.getBtUid()).isEqualTo(TEST_BT_UID); + assertThat(contactElement.getChatState()).isEqualTo(TEST_CHAT_STATE); + assertThat(contactElement.getPresenceStatus()).isEqualTo(TEST_STATUS_TEXT); + assertThat(contactElement.getPresenceAvailability()).isEqualTo(TEST_PRESENCE_STATE); + assertThat(contactElement.getLastActivityString()) + .isEqualTo(format.format(TEST_LAST_ACTIVITY)); + assertThat(contactElement.getPriority()).isEqualTo(TEST_PRIORITY); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java index c464035f44..99d69c8084 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java @@ -1106,8 +1106,8 @@ public class BluetoothMapContentTest { assertThat(messageMimeParsed.getVersionString()) .isEqualTo("VERSION:" + mContent.mMessageVersion); assertThat(messageMimeParsed.getFolder()).isEqualTo(mCurrentFolder.getFullPath()); - assertThat(messageMimeParsed.getRecipients().size()).isEqualTo(1); - assertThat(messageMimeParsed.getOriginators().size()).isEqualTo(1); + assertThat(messageMimeParsed.getRecipients()).hasSize(1); + assertThat(messageMimeParsed.getOriginators()).hasSize(1); assertThat(messageMimeParsed.getOriginators().get(0).getName()).isEmpty(); assertThat(messageMimeParsed.getRecipients().get(0).getName()) .isEqualTo(TEST_FORMATTED_NAME); @@ -1166,8 +1166,8 @@ public class BluetoothMapContentTest { assertThat(messageMimeParsed.getVersionString()) .isEqualTo("VERSION:" + mContent.mMessageVersion); assertThat(messageMimeParsed.getFolder()).isEqualTo(mCurrentFolder.getFullPath()); - assertThat(messageMimeParsed.getRecipients().size()).isEqualTo(1); - assertThat(messageMimeParsed.getOriginators().size()).isEqualTo(1); + assertThat(messageMimeParsed.getRecipients()).hasSize(1); + assertThat(messageMimeParsed.getOriginators()).hasSize(1); assertThat(messageMimeParsed.getOriginators().get(0).getName()) .isEqualTo(TEST_FORMATTED_NAME); assertThat(messageMimeParsed.getRecipients().get(0).getName()).isEmpty(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoListingElementTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoListingElementTest.java index 87739756e6..07dad9acb5 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoListingElementTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoListingElementTest.java @@ -124,13 +124,13 @@ public class BluetoothMapConvoListingElementTest { @Test public void removeContactWithObject() { mListingElement.removeContact(TEST_CONTACT_ELEMENT_TWO); - assertThat(mListingElement.getContacts().size()).isEqualTo(1); + assertThat(mListingElement.getContacts()).hasSize(1); } @Test public void removeContactWithIndex() { mListingElement.removeContact(1); - assertThat(mListingElement.getContacts().size()).isEqualTo(1); + assertThat(mListingElement.getContacts()).hasSize(1); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoListingTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoListingTest.java index 4f8629f577..97ec95ea25 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoListingTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoListingTest.java @@ -74,19 +74,19 @@ public class BluetoothMapConvoListingTest { @Test public void segment_whenCountIsLessThanOne_returnsOffsetToEnd() { mListing.segment(0, 1); - assertThat(mListing.getList().size()).isEqualTo(2); + assertThat(mListing.getList()).hasSize(2); } @Test public void segment_whenOffsetIsBiggerThanSize_returnsEmptyList() { mListing.segment(1, 4); - assertThat(mListing.getList().size()).isEqualTo(0); + assertThat(mListing.getList()).isEmpty(); } @Test public void segment_whenOffsetCountCombinationIsValid_returnsCorrectly() { mListing.segment(1, 1); - assertThat(mListing.getList().size()).isEqualTo(1); + assertThat(mListing.getList()).hasSize(1); } @Test @@ -173,7 +173,7 @@ public class BluetoothMapConvoListingTest { BluetoothMapConvoListing listing = new BluetoothMapConvoListing(); listing.appendFromXml(listingStream); - assertThat(listing.getList().size()).isEqualTo(2); + assertThat(listing.getList()).hasSize(2); assertThat(listing.getList().get(0).getConvoId()) .isEqualTo(signedLongLongIdOne.toHexString()); assertThat(listing.getList().get(1).getConvoId()) diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapMessageListingTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapMessageListingTest.java index 051b131773..ccc9170eee 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapMessageListingTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapMessageListingTest.java @@ -84,19 +84,19 @@ public class BluetoothMapMessageListingTest { @Test public void segment_whenCountIsLessThanOne_returnsOffsetToEnd() { mListing.segment(0, 1); - assertThat(mListing.getList().size()).isEqualTo(2); + assertThat(mListing.getList()).hasSize(2); } @Test public void segment_whenOffsetIsBiggerThanSize_returnsEmptyList() { mListing.segment(1, 4); - assertThat(mListing.getList().size()).isEqualTo(0); + assertThat(mListing.getList()).isEmpty(); } @Test public void segment_whenOffsetCountCombinationIsValid_returnsCorrectly() { mListing.segment(1, 1); - assertThat(mListing.getList().size()).isEqualTo(1); + assertThat(mListing.getList()).hasSize(1); } @Test @@ -122,14 +122,14 @@ public class BluetoothMapMessageListingTest { listingToAppend.add(listingElementToAppendOne); listingToAppend.add(listingElementToAppendTwo); - assertThat(listingToAppend.getList().size()).isEqualTo(2); + assertThat(listingToAppend.getList()).hasSize(2); final InputStream listingStream = new ByteArrayInputStream(listingToAppend.encode(false, TEST_VERSION)); BluetoothMapMessageListing listing = new BluetoothMapMessageListing(); appendFromXml(listingStream, listing); - assertThat(listing.getList().size()).isEqualTo(2); + assertThat(listing.getList()).hasSize(2); assertThat(listing.getList().get(0).getDateTime()).isEqualTo(TEST_DATE_TIME_EARLIEST); assertThat(listing.getList().get(1).getReadBool()).isTrue(); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSmsPduTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSmsPduTest.java index 98c44f03c7..33bcd7b5b4 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSmsPduTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSmsPduTest.java @@ -118,7 +118,7 @@ public class BluetoothMapSmsPduTest { BluetoothMapSmsPdu.getSubmitPdus( mTargetContext, TEST_TEXT_WITH_TWO_SMS_PARTS, null); - assertThat(pdus.size()).isEqualTo(2); + assertThat(pdus).hasSize(2); assertThat(pdus.get(0).getType()).isEqualTo(BluetoothMapSmsPdu.SMS_TYPE_GSM); BluetoothMapbMessageSms messageSmsToEncode = new BluetoothMapbMessageSms(); @@ -145,7 +145,7 @@ public class BluetoothMapSmsPduTest { List<SmsPdu> pdus = BluetoothMapSmsPdu.getSubmitPdus(mTargetContext, TEST_TEXT, null); - assertThat(pdus.size()).isEqualTo(1); + assertThat(pdus).hasSize(1); assertThat(pdus.get(0).getType()).isEqualTo(BluetoothMapSmsPdu.SMS_TYPE_CDMA); BluetoothMapbMessageSms messageSmsToEncode = new BluetoothMapbMessageSms(); @@ -172,7 +172,7 @@ public class BluetoothMapSmsPduTest { BluetoothMapSmsPdu.getDeliverPdus( mTargetContext, TEST_TEXT, TEST_DESTINATION_ADDRESS, TEST_DATE); - assertThat(pdus.size()).isEqualTo(1); + assertThat(pdus).hasSize(1); assertThat(pdus.get(0).getType()).isEqualTo(BluetoothMapSmsPdu.SMS_TYPE_GSM); BluetoothMapbMessageSms messageSmsToEncode = new BluetoothMapbMessageSms(); @@ -201,7 +201,7 @@ public class BluetoothMapSmsPduTest { BluetoothMapSmsPdu.getDeliverPdus( mTargetContext, TEST_TEXT, TEST_DESTINATION_ADDRESS, TEST_DATE); - assertThat(pdus.size()).isEqualTo(1); + assertThat(pdus).hasSize(1); assertThat(pdus.get(0).getType()).isEqualTo(BluetoothMapSmsPdu.SMS_TYPE_CDMA); BluetoothMapbMessageSms messageSmsToEncode = new BluetoothMapbMessageSms(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageMimeTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageMimeTest.java index 11f4bcf0d3..7aa1ad9b1c 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageMimeTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageMimeTest.java @@ -105,7 +105,7 @@ public class BluetoothMapbMessageMimeTest { assertThat(mMime.getBcc()).isEqualTo(TEST_BCC); assertThat(mMime.getReplyTo()).isEqualTo(TEST_REPLY_TO); - assertThat(mMime.getMimeParts().size()).isEqualTo(1); + assertThat(mMime.getMimeParts()).hasSize(1); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageTest.java index 299e2d4f46..1344d9b0f6 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageTest.java @@ -69,7 +69,7 @@ public class BluetoothMapbMessageTest { assertThat(messageMime.getVersionString()).isEqualTo("VERSION:" + TEST_VERSION_STRING); assertThat(messageMime.getType()).isEqualTo(TEST_TYPE); assertThat(messageMime.getFolder()).isEqualTo("telecom/msg/" + TEST_FOLDER); - assertThat(messageMime.getRecipients().size()).isEqualTo(1); + assertThat(messageMime.getRecipients()).hasSize(1); assertThat(messageMime.getOriginators()).isNull(); } @@ -189,7 +189,7 @@ public class BluetoothMapbMessageTest { .isEqualTo("VERSION:" + TEST_VERSION_STRING); assertThat(messageMimeParsed.getType()).isEqualTo(TEST_TYPE); assertThat(messageMimeParsed.getFolder()).isEqualTo(TEST_FOLDER); - assertThat(messageMimeParsed.getRecipients().size()).isEqualTo(1); - assertThat(messageMimeParsed.getOriginators().size()).isEqualTo(1); + assertThat(messageMimeParsed.getRecipients()).hasSize(1); + assertThat(messageMimeParsed.getOriginators()).hasSize(1); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/SmsMmsContactsTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/SmsMmsContactsTest.java index b929fa65bc..bcf9a266a8 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/map/SmsMmsContactsTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/map/SmsMmsContactsTest.java @@ -137,7 +137,7 @@ public class SmsMmsContactsTest { .when(mMapMethodProxy) .contentResolverQuery(any(), any(), any(), any(), any(), any()); assertThat(mContacts.mNames).isEmpty(); - assertThat(mContacts.getPhoneNumber(mResolver, TEST_ID)).isEqualTo(null); + assertThat(mContacts.getPhoneNumber(mResolver, TEST_ID)).isNull(); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/BmessageTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/BmessageTest.java index e6716b487c..2366add7d0 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/BmessageTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/BmessageTest.java @@ -23,7 +23,6 @@ import static org.mockito.Mockito.*; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -96,7 +95,7 @@ public class BmessageTest { message.setCharset("UTF-8"); - Assert.assertEquals(message.getCharset(), "UTF-8"); + assertThat(message.getCharset()).isEqualTo("UTF-8"); } @Test @@ -105,7 +104,7 @@ public class BmessageTest { message.setEncoding("test_encoding"); - Assert.assertEquals(message.getEncoding(), "test_encoding"); + assertThat(message.getEncoding()).isEqualTo("test_encoding"); } @Test @@ -114,6 +113,6 @@ public class BmessageTest { message.setStatus(Bmessage.Status.READ); - Assert.assertEquals(message.getStatus(), Bmessage.Status.READ); + assertThat(message.getStatus()).isEqualTo(Bmessage.Status.READ); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java index 57a0e72941..37f3153718 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java @@ -51,7 +51,6 @@ import com.android.vcard.VCardEntry; import com.android.vcard.VCardProperty; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -138,7 +137,7 @@ public class MapClientContentTest { any(), anyInt(), eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); - Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size()); + assertThat(mMockSmsContentProvider.mContentValues).isEmpty(); } /** Test that a dirty database gets cleaned at startup. */ @@ -153,9 +152,9 @@ public class MapClientContentTest { any(), anyInt(), eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); - Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size()); + assertThat(mMockSmsContentProvider.mContentValues).hasSize(1); mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); - Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size()); + assertThat(mMockSmsContentProvider.mContentValues).isEmpty(); } /** Test inserting 2 SMS messages and then clearing out the database. */ @@ -170,16 +169,16 @@ public class MapClientContentTest { any(), anyInt(), eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); - Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size()); + assertThat(mMockSmsContentProvider.mContentValues).hasSize(1); mMapClientContent.storeMessage( mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp, MESSAGE_SEEN); - Assert.assertEquals(2, mMockSmsContentProvider.mContentValues.size()); - Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size()); + assertThat(mMockSmsContentProvider.mContentValues).hasSize(2); + assertThat(mMockMmsContentProvider.mContentValues).isEmpty(); mMapClientContent.cleanUp(); - Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size()); - Assert.assertEquals(0, mMockThreadContentProvider.mContentValues.size()); + assertThat(mMockSmsContentProvider.mContentValues).isEmpty(); + assertThat(mMockThreadContentProvider.mContentValues).isEmpty(); } /** Test inserting 2 MMS messages and then clearing out the database. */ @@ -194,14 +193,14 @@ public class MapClientContentTest { any(), anyInt(), eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); - Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size()); + assertThat(mMockMmsContentProvider.mContentValues).hasSize(1); mMapClientContent.storeMessage( mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp, MESSAGE_SEEN); - Assert.assertEquals(2, mMockMmsContentProvider.mContentValues.size()); + assertThat(mMockMmsContentProvider.mContentValues).hasSize(2); mMapClientContent.cleanUp(); - Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size()); + assertThat(mMockMmsContentProvider.mContentValues).isEmpty(); } /** Test that SMS and MMS messages end up in their respective databases. */ @@ -216,14 +215,14 @@ public class MapClientContentTest { any(), anyInt(), eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); - Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size()); + assertThat(mMockMmsContentProvider.mContentValues).hasSize(1); mMapClientContent.storeMessage( mTestMessage2, mTestMessage2Handle, mTestMessage1Timestamp, MESSAGE_SEEN); - Assert.assertEquals(2, mMockMmsContentProvider.mContentValues.size()); + assertThat(mMockMmsContentProvider.mContentValues).hasSize(2); mMapClientContent.cleanUp(); - Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size()); + assertThat(mMockMmsContentProvider.mContentValues).isEmpty(); } /** Test read status changed */ @@ -238,16 +237,16 @@ public class MapClientContentTest { any(), anyInt(), eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); - Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size()); + assertThat(mMockMmsContentProvider.mContentValues).hasSize(1); mMapClientContent.storeMessage( mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp, MESSAGE_SEEN); - Assert.assertEquals(2, mMockMmsContentProvider.mContentValues.size()); + assertThat(mMockMmsContentProvider.mContentValues).hasSize(2); mMapClientContent.markRead(mTestMessage1Handle); mMapClientContent.cleanUp(); - Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size()); + assertThat(mMockMmsContentProvider.mContentValues).isEmpty(); } /** @@ -262,7 +261,7 @@ public class MapClientContentTest { mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); mMapClientContent.storeMessage( mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp, MESSAGE_SEEN); - Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size()); + assertThat(mMockMmsContentProvider.mContentValues).hasSize(1); mMapClientContent.mContentObserver.onChange(false); verify(mCallbacks) .onMessageStatusChanged(eq(mTestMessage1Handle), eq(BluetoothMapClient.READ)); @@ -274,7 +273,7 @@ public class MapClientContentTest { mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); mMapClientContent.storeMessage( mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp, MESSAGE_SEEN); - assertThat(mMockSmsContentProvider.mContentValues.size()).isEqualTo(1); + assertThat(mMockSmsContentProvider.mContentValues).hasSize(1); ContentValues storedSMS = (ContentValues) mMockSmsContentProvider.mContentValues.values().toArray()[0]; @@ -288,7 +287,7 @@ public class MapClientContentTest { mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); mMapClientContent.storeMessage( mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp, MESSAGE_NOT_SEEN); - assertThat(mMockSmsContentProvider.mContentValues.size()).isEqualTo(1); + assertThat(mMockSmsContentProvider.mContentValues).hasSize(1); ContentValues storedSMS = (ContentValues) mMockSmsContentProvider.mContentValues.values().toArray()[0]; @@ -302,7 +301,7 @@ public class MapClientContentTest { mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); mMapClientContent.storeMessage( mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp, MESSAGE_SEEN); - assertThat(mMockMmsContentProvider.mContentValues.size()).isEqualTo(1); + assertThat(mMockMmsContentProvider.mContentValues).hasSize(1); ContentValues storedMMS = (ContentValues) mMockMmsContentProvider.mContentValues.values().toArray()[0]; @@ -316,7 +315,7 @@ public class MapClientContentTest { mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); mMapClientContent.storeMessage( mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp, MESSAGE_NOT_SEEN); - assertThat(mMockMmsContentProvider.mContentValues.size()).isEqualTo(1); + assertThat(mMockMmsContentProvider.mContentValues).hasSize(1); ContentValues storedMMS = (ContentValues) mMockMmsContentProvider.mContentValues.values().toArray()[0]; @@ -341,14 +340,14 @@ public class MapClientContentTest { any(), anyInt(), eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); - Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size()); + assertThat(mMockSmsContentProvider.mContentValues).hasSize(1); // attempt to delete an invalid handle, nothing should be removed. mMapClientContent.deleteMessage(mTestMessage2Handle); - Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size()); + assertThat(mMockSmsContentProvider.mContentValues).hasSize(1); // delete a valid handle mMapClientContent.deleteMessage(mTestMessage1Handle); - Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size()); + assertThat(mMockSmsContentProvider.mContentValues).isEmpty(); } /** @@ -368,7 +367,7 @@ public class MapClientContentTest { any(), anyInt(), eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); - Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size()); + assertThat(mMockSmsContentProvider.mContentValues).hasSize(1); mMockSmsContentProvider.mContentValues.clear(); mMapClientContent.mContentObserver.onChange(false); verify(mCallbacks) diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientServiceTest.java index 0a757e1d07..3ae4dd1077 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientServiceTest.java @@ -15,8 +15,14 @@ */ package com.android.bluetooth.mapclient; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_UNKNOWN; + import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -30,10 +36,9 @@ import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.bluetooth.SdpMasRecord; import android.content.Context; -import android.os.Looper; import android.os.test.TestLooper; +import android.telephony.SubscriptionManager; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; @@ -50,48 +55,48 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.ArrayList; +import java.util.List; + @MediumTest @RunWith(AndroidJUnit4.class) public class MapClientServiceTest { - private static final String REMOTE_DEVICE_ADDRESS = "00:00:00:00:00:00"; - @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock private AdapterService mAdapterService; @Mock private DatabaseManager mDatabaseManager; + @Mock private MnsService mMnsService; + + private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); + private final BluetoothDevice mRemoteDevice = TestUtils.getTestDevice(mAdapter, 0); - private MapClientService mService = null; - private BluetoothAdapter mAdapter = null; - private BluetoothDevice mRemoteDevice; + private MapClientService mService; private TestLooper mTestLooper; @Before public void setUp() throws Exception { - Context targetContext = InstrumentationRegistry.getTargetContext(); - TestUtils.setAdapterService(mAdapterService); + doReturn(CONNECTION_POLICY_ALLOWED) + .when(mDatabaseManager) + .getProfileConnectionPolicy(any(), anyInt()); + doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); + TestUtils.mockGetSystemService( + mAdapterService, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class); mTestLooper = new TestLooper(); - MnsService mnsServer = null; - mService = new MapClientService(targetContext, mTestLooper.getLooper(), mnsServer); - mService.start(); + mService = new MapClientService(mAdapterService, mTestLooper.getLooper(), mMnsService); mService.setAvailable(true); // Try getting the Bluetooth adapter - mAdapter = BluetoothAdapter.getDefaultAdapter(); assertThat(mAdapter).isNotNull(); - mRemoteDevice = mAdapter.getRemoteDevice(REMOTE_DEVICE_ADDRESS); } @After public void tearDown() throws Exception { mService.stop(); mService.cleanup(); - mService = MapClientService.getMapClientService(); - assertThat(mService).isNull(); - TestUtils.clearAdapterService(mAdapterService); - mTestLooper.dispatchAll(); + assertThat(MapClientService.getMapClientService()).isNull(); } @Test @@ -119,41 +124,37 @@ public class MapClientServiceTest { @Test public void setConnectionPolicy() { - int connectionPolicy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN; - when(mDatabaseManager.setProfileConnectionPolicy( - mRemoteDevice, BluetoothProfile.MAP_CLIENT, connectionPolicy)) - .thenReturn(true); + doReturn(true).when(mDatabaseManager).setProfileConnectionPolicy(any(), anyInt(), anyInt()); - assertThat(mService.setConnectionPolicy(mRemoteDevice, connectionPolicy)).isTrue(); + assertThat(mService.setConnectionPolicy(mRemoteDevice, CONNECTION_POLICY_UNKNOWN)).isTrue(); + verify(mDatabaseManager) + .setProfileConnectionPolicy( + mRemoteDevice, BluetoothProfile.MAP_CLIENT, CONNECTION_POLICY_UNKNOWN); } @Test public void getConnectionPolicy() { - int connectionPolicy = BluetoothProfile.CONNECTION_POLICY_ALLOWED; - when(mDatabaseManager.getProfileConnectionPolicy( - mRemoteDevice, BluetoothProfile.MAP_CLIENT)) - .thenReturn(connectionPolicy); - - assertThat(mService.getConnectionPolicy(mRemoteDevice)).isEqualTo(connectionPolicy); + for (int policy : + List.of( + CONNECTION_POLICY_UNKNOWN, + CONNECTION_POLICY_FORBIDDEN, + CONNECTION_POLICY_ALLOWED)) { + doReturn(policy).when(mDatabaseManager).getProfileConnectionPolicy(any(), anyInt()); + assertThat(mService.getConnectionPolicy(mRemoteDevice)).isEqualTo(policy); + } } @Test public void connect_whenPolicyIsForbidden_returnsFalse() { - int connectionPolicy = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - when(mDatabaseManager.getProfileConnectionPolicy( - mRemoteDevice, BluetoothProfile.MAP_CLIENT)) - .thenReturn(connectionPolicy); + doReturn(CONNECTION_POLICY_FORBIDDEN) + .when(mDatabaseManager) + .getProfileConnectionPolicy(any(), anyInt()); assertThat(mService.connect(mRemoteDevice)).isFalse(); } @Test public void connect_whenPolicyIsAllowed_returnsTrue() { - int connectionPolicy = BluetoothProfile.CONNECTION_POLICY_ALLOWED; - when(mDatabaseManager.getProfileConnectionPolicy( - mRemoteDevice, BluetoothProfile.MAP_CLIENT)) - .thenReturn(connectionPolicy); - assertThat(mService.connect(mRemoteDevice)).isTrue(); } @@ -277,7 +278,6 @@ public class MapClientServiceTest { when(sm.getState()).thenReturn(connectionState); mService.aclDisconnected(mRemoteDevice, BluetoothDevice.ERROR); - TestUtils.waitForLooperToBeIdle(Looper.getMainLooper()); mTestLooper.dispatchAll(); verify(sm, never()).disconnect(); @@ -291,7 +291,7 @@ public class MapClientServiceTest { when(sm.getState()).thenReturn(connectionState); mService.aclDisconnected(mRemoteDevice, BluetoothDevice.TRANSPORT_LE); - TestUtils.waitForLooperToBeIdle(Looper.getMainLooper()); + mTestLooper.dispatchAll(); verify(sm, never()).disconnect(); } @@ -304,7 +304,7 @@ public class MapClientServiceTest { when(sm.getState()).thenReturn(connectionState); mService.aclDisconnected(mRemoteDevice, BluetoothDevice.TRANSPORT_BREDR); - TestUtils.waitForLooperToBeIdle(Looper.getMainLooper()); + mTestLooper.dispatchAll(); verify(sm).disconnect(); } @@ -317,7 +317,7 @@ public class MapClientServiceTest { mService.receiveSdpSearchRecord( mRemoteDevice, MceStateMachine.SDP_SUCCESS, mockSdpRecord, BluetoothUuid.MAS); - TestUtils.waitForLooperToBeIdle(Looper.getMainLooper()); + mTestLooper.dispatchAll(); verify(sm).sendSdpResult(eq(MceStateMachine.SDP_SUCCESS), eq(mockSdpRecord)); } @@ -329,7 +329,7 @@ public class MapClientServiceTest { mService.receiveSdpSearchRecord( mRemoteDevice, MceStateMachine.SDP_SUCCESS, null, BluetoothUuid.MAS); - TestUtils.waitForLooperToBeIdle(Looper.getMainLooper()); + mTestLooper.dispatchAll(); // Verify message: SDP was successfully complete, but no record was returned verify(sm).sendSdpResult(eq(MceStateMachine.SDP_SUCCESS), eq(null)); @@ -342,7 +342,7 @@ public class MapClientServiceTest { mService.receiveSdpSearchRecord( mRemoteDevice, MceStateMachine.SDP_BUSY, null, BluetoothUuid.MAS); - TestUtils.waitForLooperToBeIdle(Looper.getMainLooper()); + mTestLooper.dispatchAll(); // Verify message: SDP was busy and no record was returned verify(sm).sendSdpResult(eq(MceStateMachine.SDP_BUSY), eq(null)); @@ -355,9 +355,39 @@ public class MapClientServiceTest { mService.receiveSdpSearchRecord( mRemoteDevice, MceStateMachine.SDP_FAILED, null, BluetoothUuid.MAS); - TestUtils.waitForLooperToBeIdle(Looper.getMainLooper()); + mTestLooper.dispatchAll(); // Verify message: SDP was failed for some reason and no record was returned verify(sm).sendSdpResult(eq(MceStateMachine.SDP_FAILED), eq(null)); } + + @Test + public void connectOneDevice_whenAllowed_isConnected() { + assertThat(mService.getInstanceMap()).doesNotContainKey(mRemoteDevice); + + assertThat(mService.connect(mRemoteDevice)).isTrue(); + assertThat(mService.getInstanceMap().keySet()).containsExactly(mRemoteDevice); + + mTestLooper.dispatchAll(); + assertThat(mService.getConnectionState(mRemoteDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTING); + } + + @Test + public void connectDevice_whenMaxDevicesAreConnected_isRejected() { + List<BluetoothDevice> list = new ArrayList<>(); + for (int i = 0; i < MapClientService.MAXIMUM_CONNECTED_DEVICES; ++i) { + BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i); + assertThat(mService.getInstanceMap().get(testDevice)).isNull(); + assertThat(mService.connect(testDevice)).isTrue(); + + list.add(testDevice); + } + + mTestLooper.dispatchAll(); + assertThat(mService.getInstanceMap().keySet()).containsExactlyElementsIn(list); + + // Try to connect one more device. Should fail. + assertThat(mService.connect(TestUtils.getTestDevice(mAdapter, 0xAF))).isFalse(); + } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java index 89a0cda5c6..109b7ab82a 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java @@ -16,10 +16,25 @@ package com.android.bluetooth.mapclient; +import static android.Manifest.permission.BLUETOOTH_CONNECT; +import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; +import static android.bluetooth.BluetoothProfile.EXTRA_PREVIOUS_STATE; +import static android.bluetooth.BluetoothProfile.EXTRA_STATE; +import static android.bluetooth.BluetoothProfile.STATE_CONNECTED; +import static android.bluetooth.BluetoothProfile.STATE_CONNECTING; +import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED; +import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTING; + +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasPackage; + import static com.google.common.truth.Truth.assertThat; +import static org.hamcrest.Matchers.nullValue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; +import static org.mockito.Mockito.inOrder; import android.app.Activity; import android.app.BroadcastOptions; @@ -27,7 +42,6 @@ import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothMapClient; -import android.bluetooth.BluetoothProfile; import android.bluetooth.SdpMasRecord; import android.content.BroadcastReceiver; import android.content.ContentValues; @@ -42,7 +56,6 @@ import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Telephony.Sms; import android.telephony.SmsManager; -import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; @@ -53,9 +66,7 @@ import androidx.test.filters.MediumTest; import androidx.test.rule.ServiceTestRule; import com.android.bluetooth.ObexAppParameters; -import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; -import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.flags.Flags; import com.android.obex.HeaderSet; import com.android.vcard.VCardConstants; @@ -64,15 +75,18 @@ import com.android.vcard.VCardProperty; import com.google.common.truth.Correspondence; +import org.hamcrest.Matcher; +import org.hamcrest.core.AllOf; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.hamcrest.MockitoHamcrest; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -90,24 +104,24 @@ import java.util.concurrent.TimeUnit; @MediumTest @RunWith(ParameterizedAndroidJunit4.class) public class MapClientStateMachineTest { - private static final String TAG = "MapClientStateMachineTest"; - @Rule public final SetFlagsRule mSetFlagsRule; @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); - private static final long PENDING_INTENT_TIMEOUT_MS = 3_000; - - private static final int CONNECTION_STATE_UNDEFINED = -1; + @Mock private AdapterService mAdapterService; + @Mock private MapClientService mService; + @Mock private MapClientContent mDatabase; + @Mock private TelephonyManager mTelephonyManager; + @Mock private MasClient mMasClient; + @Mock private RequestPushMessage mRequestPushMessage; + @Mock private RequestGetMessagesListingForOwnNumber mRequestOwnNumberCompletedWithNumber; + @Mock private RequestGetMessagesListingForOwnNumber mRequestOwnNumberIncompleteSearch; + @Mock private RequestGetMessage mRequestGetMessage; + @Mock private RequestGetMessagesListing mRequestGetMessagesListing; - private Bmessage mTestIncomingSmsBmessage; - private Bmessage mTestIncomingMmsBmessage; - private String mTestMessageSmsHandle = "0001"; - private String mTestMessageMmsHandle = "0002"; - private String mTestMessageUnknownHandle = "0003"; - boolean mIsAdapterServiceSet; - boolean mIsMapClientServiceStarted; + private static final String TAG = "MapClientStateMachineTest"; + private static final long PENDING_INTENT_TIMEOUT_MS = 3_000; private static final boolean MESSAGE_SEEN = true; private static final boolean MESSAGE_NOT_SEEN = false; @@ -116,50 +130,29 @@ public class MapClientStateMachineTest { private static final String SENT_PATH = "telecom/msg/sent"; private static final Uri[] TEST_CONTACTS_ONE_PHONENUM = new Uri[] {Uri.parse("tel://5551234")}; private static final String TEST_DATETIME = "19991231T235959"; - - private VCardEntry mOriginator; - - private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); - private final BluetoothDevice mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); - private final Context mTargetContext = InstrumentationRegistry.getTargetContext(); - - private MceStateMachine mMceStateMachine; - private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class); - - @Mock private AdapterService mAdapterService; - @Mock private DatabaseManager mDatabaseManager; - @Mock private MapClientService mMockMapClientService; - @Mock private MapClientContent mMockDatabase; - private MockContentResolver mMockContentResolver; - private MockSmsContentProvider mMockContentProvider; - - @Mock private TelephonyManager mMockTelephonyManager; - - @Mock private MasClient mMockMasClient; - - @Mock private RequestPushMessage mMockRequestPushMessage; - - @Mock private SubscriptionManager mMockSubscriptionManager; - private static final String TEST_OWN_PHONE_NUMBER = "555-1234"; - @Mock private RequestGetMessagesListingForOwnNumber mMockRequestOwnNumberCompletedWithNumber; - @Mock private RequestGetMessagesListingForOwnNumber mMockRequestOwnNumberIncompleteSearch; - @Mock private RequestGetMessage mMockRequestGetMessage; - @Mock private RequestGetMessagesListing mMockRequestGetMessagesListing; - private static final Correspondence<Request, String> GET_FOLDER_NAME = Correspondence.transforming( MapClientStateMachineTest::getFolderNameFromRequestGetMessagesListing, "has folder name of"); - private static final String ACTION_MESSAGE_SENT = "com.android.bluetooth.mapclient.MapClientStateMachineTest.action.MESSAGE_SENT"; private static final String ACTION_MESSAGE_DELIVERED = "com.android.bluetooth.mapclient.MapClientStateMachineTest.action.MESSAGE_DELIVERED"; - private SentDeliveryReceiver mSentDeliveryReceiver; + private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); + private final BluetoothDevice mDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); + private final Context mTargetContext = InstrumentationRegistry.getTargetContext(); + private final String mTestMessageSmsHandle = "0001"; + private final String mTestMessageMmsHandle = "0002"; + private final String mTestMessageUnknownHandle = "0003"; + private Bmessage mTestIncomingSmsBmessage; + private Bmessage mTestIncomingMmsBmessage; + private MceStateMachine mStateMachine; + private SentDeliveryReceiver mSentDeliveryReceiver; private TestLooper mLooper; + private InOrder mInOrder; private static class SentDeliveryReceiver extends BroadcastReceiver { private CountDownLatch mActionReceivedLatch; @@ -205,52 +198,43 @@ public class MapClientStateMachineTest { public void setUp() throws Exception { mLooper = new TestLooper(); - TestUtils.setAdapterService(mAdapterService); - mIsAdapterServiceSet = true; - mMockContentProvider = new MockSmsContentProvider(); - mMockContentResolver = new MockContentResolver(); - when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); - mIsMapClientServiceStarted = true; - mMockContentResolver.addProvider("sms", mMockContentProvider); - mMockContentResolver.addProvider("mms", mMockContentProvider); - mMockContentResolver.addProvider("mms-sms", mMockContentProvider); + mInOrder = inOrder(mService); - when(mMockMapClientService.getContentResolver()).thenReturn(mMockContentResolver); - when(mMockMapClientService.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)) - .thenReturn(mMockSubscriptionManager); - when(mMockMapClientService.getSystemServiceName(SubscriptionManager.class)) - .thenReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + MockSmsContentProvider contentProvider = new MockSmsContentProvider(); + MockContentResolver contentResolver = new MockContentResolver(); + contentResolver.addProvider("sms", contentProvider); + contentResolver.addProvider("mms", contentProvider); + contentResolver.addProvider("mms-sms", contentProvider); - doReturn(mTargetContext.getResources()).when(mMockMapClientService).getResources(); + when(mService.getContentResolver()).thenReturn(contentResolver); + doReturn(mTargetContext.getResources()).when(mService).getResources(); - when(mMockMasClient.makeRequest(any(Request.class))).thenReturn(true); - mMceStateMachine = + when(mMasClient.makeRequest(any(Request.class))).thenReturn(true); + mStateMachine = new MceStateMachine( - mMockMapClientService, - mTestDevice, - mMockMasClient, - mMockDatabase, - mLooper.getLooper()); + mService, + mDevice, + mAdapterService, + mLooper.getLooper(), + mMasClient, + mDatabase); mLooper.dispatchAll(); + verifyStateTransitionAndIntent( + STATE_DISCONNECTED, STATE_CONNECTING); - int initialExpectedState = BluetoothProfile.STATE_CONNECTING; - assertThat(mMceStateMachine.getState()).isEqualTo(initialExpectedState); - - when(mMockRequestOwnNumberCompletedWithNumber.isSearchCompleted()).thenReturn(true); - when(mMockRequestOwnNumberCompletedWithNumber.getOwnNumber()) - .thenReturn(TEST_OWN_PHONE_NUMBER); - when(mMockRequestOwnNumberIncompleteSearch.isSearchCompleted()).thenReturn(false); - when(mMockRequestOwnNumberIncompleteSearch.getOwnNumber()).thenReturn(null); + when(mRequestOwnNumberCompletedWithNumber.isSearchCompleted()).thenReturn(true); + when(mRequestOwnNumberCompletedWithNumber.getOwnNumber()).thenReturn(TEST_OWN_PHONE_NUMBER); + when(mRequestOwnNumberIncompleteSearch.isSearchCompleted()).thenReturn(false); + when(mRequestOwnNumberIncompleteSearch.getOwnNumber()).thenReturn(null); createTestMessages(); - when(mMockRequestGetMessage.getMessage()).thenReturn(mTestIncomingSmsBmessage); - when(mMockRequestGetMessage.getHandle()).thenReturn(mTestMessageSmsHandle); + when(mRequestGetMessage.getMessage()).thenReturn(mTestIncomingSmsBmessage); + when(mRequestGetMessage.getHandle()).thenReturn(mTestMessageSmsHandle); - when(mMockMapClientService.getSystemService(Context.TELEPHONY_SERVICE)) - .thenReturn(mMockTelephonyManager); - when(mMockTelephonyManager.isSmsCapable()).thenReturn(false); + when(mService.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); + when(mTelephonyManager.isSmsCapable()).thenReturn(false); // Set up receiver for 'Sent' and 'Delivered' PendingIntents IntentFilter filter = new IntentFilter(); @@ -263,20 +247,17 @@ public class MapClientStateMachineTest { @After public void tearDown() throws Exception { - if (mMceStateMachine != null) { - mMceStateMachine.doQuit(); + if (mStateMachine != null) { + mStateMachine.doQuit(); } - if (mIsAdapterServiceSet) { - TestUtils.clearAdapterService(mAdapterService); - } mTargetContext.unregisterReceiver(mSentDeliveryReceiver); } /** Test that default state is STATE_CONNECTING */ @Test public void testDefaultConnectingState() { - Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState()); + assertThat(mStateMachine.getState()).isEqualTo(STATE_CONNECTING); } /** @@ -288,171 +269,111 @@ public class MapClientStateMachineTest { setupSdpRecordReceipt(); sendAndDispatchMessage(MceStateMachine.MSG_MAS_DISCONNECTED); - // Wait until the message is processed and a broadcast request is sent to - // to MapClientService to change - // state from STATE_CONNECTING to STATE_DISCONNECTED - verify(mMockMapClientService, times(2)) - .sendBroadcastMultiplePermissions( - mIntentArgument.capture(), - any(String[].class), - any(BroadcastOptions.class)); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + verifyStateTransitionAndIntent(STATE_CONNECTING, STATE_DISCONNECTED); } - /** Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED */ @Test - public void testStateTransitionFromConnectingToConnected() { + public void masConnected_whenConnecting_isConnected() { setupSdpRecordReceipt(); - - int expectedFromState = BluetoothProfile.STATE_CONNECTING; - int expectedToState = BluetoothProfile.STATE_CONNECTED; sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - verifyStateTransitionAndIntent(expectedFromState, expectedToState); + verifyStateTransitionAndIntent(STATE_CONNECTING, STATE_CONNECTED); } - /** - * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED --> - * (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTING --> STATE_DISCONNECTED - */ @Test - public void testStateTransitionFromConnectedToDisconnected() { + public void masDisconnected_whenConnected_isDisconnected() { + masConnected_whenConnecting_isConnected(); // transition to the connected state - setupSdpRecordReceipt(); - // transition to the connected state - testStateTransitionFromConnectingToConnected(); - - int expectedFromState = BluetoothProfile.STATE_DISCONNECTING; - int expectedToState = BluetoothProfile.STATE_DISCONNECTED; sendAndDispatchMessage(MceStateMachine.MSG_MAS_DISCONNECTED); - verifyStateTransitionAndIntent(expectedFromState, expectedToState); + verifyStateTransitionAndIntent(STATE_DISCONNECTING, STATE_DISCONNECTED); } /** Test receiving an empty event report */ @Test public void testReceiveEmptyEvent() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - - // broadcast request is sent to change state from STATE_CONNECTING to STATE_CONNECTED - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state - // Send an empty notification event, verify the mMceStateMachine is still connected + // Send an empty notification event, verify the mStateMachine is still connected sendAndDispatchMessage(MceStateMachine.MSG_NOTIFICATION); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + assertThat(mStateMachine.getState()).isEqualTo(STATE_CONNECTED); } /** Test set message status */ @Test public void testSetMessageStatus() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state // broadcast request is sent to change state from STATE_CONNECTING to STATE_CONNECTED - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); - assertThat(mMceStateMachine.setMessageStatus("123456789AB", BluetoothMapClient.READ)) - .isTrue(); + assertThat(mStateMachine.getState()).isEqualTo(STATE_CONNECTED); + assertThat(mStateMachine.setMessageStatus("123456789AB", BluetoothMapClient.READ)).isTrue(); } /** Test MceStateMachine#disconnect */ @Test public void testDisconnect() { - setupSdpRecordReceipt(); - doAnswer( - invocation -> { - mMceStateMachine.sendMessage(MceStateMachine.MSG_MAS_DISCONNECTED); - return null; - }) - .when(mMockMasClient) - .shutdown(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - - // broadcast request is sent to change state from STATE_CONNECTING to STATE_CONNECTED - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state - mMceStateMachine.disconnect(); + mStateMachine.disconnect(); mLooper.dispatchAll(); + verifyStateTransitionAndIntent(STATE_CONNECTED, STATE_DISCONNECTING); - verify(mMockMapClientService, times(4)) - .sendBroadcastMultiplePermissions( - mIntentArgument.capture(), - any(String[].class), - any(BroadcastOptions.class)); - - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + verify(mMasClient).shutdown(); + sendAndDispatchMessage(MceStateMachine.MSG_MAS_DISCONNECTED); + verifyStateTransitionAndIntent( + STATE_DISCONNECTING, STATE_DISCONNECTED); } /** Test disconnect timeout */ @Test public void testDisconnectTimeout() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state - // broadcast request is sent to change state from STATE_CONNECTING to STATE_CONNECTED - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); - - mMceStateMachine.disconnect(); + mStateMachine.disconnect(); mLooper.dispatchAll(); - verify(mMockMapClientService, times(3)) - .sendBroadcastMultiplePermissions( - mIntentArgument.capture(), - any(String[].class), - any(BroadcastOptions.class)); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTING); + verifyStateTransitionAndIntent(STATE_CONNECTED, STATE_DISCONNECTING); mLooper.moveTimeForward(MceStateMachine.DISCONNECT_TIMEOUT.toMillis()); mLooper.dispatchAll(); - verify(mMockMapClientService, times(4)) - .sendBroadcastMultiplePermissions( - mIntentArgument.capture(), - any(String[].class), - any(BroadcastOptions.class)); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + verifyStateTransitionAndIntent(STATE_DISCONNECTING, STATE_DISCONNECTED); } /** Test sending a message to a phone */ @Test public void testSendSMSMessageToPhone() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state String testMessage = "Hello World!"; Uri[] contacts = new Uri[] {Uri.parse("tel://5551212")}; - verify(mMockMasClient, times(0)).makeRequest(any(RequestPushMessage.class)); - mMceStateMachine.sendMapMessage(contacts, testMessage, null, null); + verify(mMasClient, never()).makeRequest(any(RequestPushMessage.class)); + mStateMachine.sendMapMessage(contacts, testMessage, null, null); mLooper.dispatchAll(); - verify(mMockMasClient, times(1)).makeRequest(any(RequestPushMessage.class)); + verify(mMasClient).makeRequest(any(RequestPushMessage.class)); } /** Test sending a message to an email */ @Test public void testSendSMSMessageToEmail() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state String testMessage = "Hello World!"; Uri[] contacts = new Uri[] {Uri.parse("mailto://sms-test@google.com")}; - verify(mMockMasClient, never()).makeRequest(any(RequestPushMessage.class)); - mMceStateMachine.sendMapMessage(contacts, testMessage, null, null); + verify(mMasClient, never()).makeRequest(any(RequestPushMessage.class)); + mStateMachine.sendMapMessage(contacts, testMessage, null, null); mLooper.dispatchAll(); - verify(mMockMasClient).makeRequest(any(RequestPushMessage.class)); + verify(mMasClient).makeRequest(any(RequestPushMessage.class)); } /** Test message sent successfully */ @Test public void testSMSMessageSent() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state - when(mMockRequestPushMessage.getMsgHandle()).thenReturn(mTestMessageSmsHandle); - when(mMockRequestPushMessage.getBMsg()).thenReturn(mTestIncomingSmsBmessage); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mMockRequestPushMessage); + when(mRequestPushMessage.getMsgHandle()).thenReturn(mTestMessageSmsHandle); + when(mRequestPushMessage.getBMsg()).thenReturn(mTestIncomingSmsBmessage); + sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mRequestPushMessage); - verify(mMockDatabase) + verify(mDatabase) .storeMessage( eq(mTestIncomingSmsBmessage), eq(mTestMessageSmsHandle), @@ -467,11 +388,11 @@ public class MapClientStateMachineTest { * MessageListing of INBOX folder not sent */ private void testGetOwnNumber_setup() { - testStateTransitionFromConnectingToConnected(); - verify(mMockMasClient, never()).makeRequest(any(RequestSetNotificationRegistration.class)); - verify(mMockMasClient, never()).makeRequest(any(RequestGetMessagesListing.class)); + masConnected_whenConnecting_isConnected(); + verify(mMasClient, never()).makeRequest(any(RequestSetNotificationRegistration.class)); + verify(mMasClient, never()).makeRequest(any(RequestGetMessagesListing.class)); assertThat( - mMceStateMachine + mStateMachine .getHandler() .hasMessages(MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT)) .isTrue(); @@ -485,11 +406,11 @@ public class MapClientStateMachineTest { */ private void testGetOwnNumber_assertNextStageStarted(boolean hasStarted) { if (hasStarted) { - verify(mMockMasClient).makeRequest(any(RequestSetNotificationRegistration.class)); - verify(mMockMasClient, times(2)).makeRequest(any(RequestGetMessagesListing.class)); + verify(mMasClient).makeRequest(any(RequestSetNotificationRegistration.class)); + verify(mMasClient, times(2)).makeRequest(any(RequestGetMessagesListing.class)); ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class); - verify(mMockMasClient, atLeastOnce()).makeRequest(requestCaptor.capture()); + verify(mMasClient, atLeastOnce()).makeRequest(requestCaptor.capture()); // There will be multiple calls to {@link MasClient#makeRequest} with different // {@link Request} subtypes; not all of them will be {@link // RequestGetMessagesListing}. @@ -501,9 +422,8 @@ public class MapClientStateMachineTest { .comparingElementsUsing(GET_FOLDER_NAME) .contains(MceStateMachine.FOLDER_SENT); } else { - verify(mMockMasClient, never()) - .makeRequest(any(RequestSetNotificationRegistration.class)); - verify(mMockMasClient, never()).makeRequest(any(RequestGetMessagesListing.class)); + verify(mMasClient, never()).makeRequest(any(RequestSetNotificationRegistration.class)); + verify(mMasClient, never()).makeRequest(any(RequestGetMessagesListing.class)); } } @@ -523,12 +443,11 @@ public class MapClientStateMachineTest { testGetOwnNumber_setup(); sendAndDispatchMessage( - MceStateMachine.MSG_MAS_REQUEST_COMPLETED, - mMockRequestOwnNumberCompletedWithNumber); + MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mRequestOwnNumberCompletedWithNumber); - verify(mMockMasClient, never()).makeRequest(eq(mMockRequestOwnNumberCompletedWithNumber)); + verify(mMasClient, never()).makeRequest(eq(mRequestOwnNumberCompletedWithNumber)); assertThat( - mMceStateMachine + mStateMachine .getHandler() .hasMessages(MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT)) .isFalse(); @@ -552,12 +471,11 @@ public class MapClientStateMachineTest { testGetOwnNumber_setup(); sendAndDispatchMessage( - MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT, - mMockRequestOwnNumberIncompleteSearch); + MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT, mRequestOwnNumberIncompleteSearch); - verify(mMockMasClient).abortRequest(mMockRequestOwnNumberIncompleteSearch); + verify(mMasClient).abortRequest(mRequestOwnNumberIncompleteSearch); assertThat( - mMceStateMachine + mStateMachine .getHandler() .hasMessages(MceStateMachine.MSG_MAS_REQUEST_COMPLETED)) .isFalse(); @@ -581,11 +499,11 @@ public class MapClientStateMachineTest { testGetOwnNumber_setup(); sendAndDispatchMessage( - MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mMockRequestOwnNumberIncompleteSearch); + MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mRequestOwnNumberIncompleteSearch); - verify(mMockMasClient).makeRequest(eq(mMockRequestOwnNumberIncompleteSearch)); + verify(mMasClient).makeRequest(eq(mRequestOwnNumberIncompleteSearch)); assertThat( - mMceStateMachine + mStateMachine .getHandler() .hasMessages(MceStateMachine.MSG_SEARCH_OWN_NUMBER_TIMEOUT)) .isTrue(); @@ -595,9 +513,7 @@ public class MapClientStateMachineTest { /** Test seen status set for new SMS */ @Test public void testReceivedNewSms_messageStoredAsUnseen() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state String dateTime = new ObexTime(Instant.now()).toString(); EventReport event = @@ -611,11 +527,11 @@ public class MapClientStateMachineTest { sendAndDispatchEvent(event); - verify(mMockMasClient).makeRequest(any(RequestGetMessage.class)); + verify(mMasClient).makeRequest(any(RequestGetMessage.class)); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mMockRequestGetMessage); + sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mRequestGetMessage); - verify(mMockDatabase) + verify(mDatabase) .storeMessage( eq(mTestIncomingSmsBmessage), eq(mTestMessageSmsHandle), @@ -626,9 +542,7 @@ public class MapClientStateMachineTest { /** Test seen status set for new MMS */ @Test public void testReceivedNewMms_messageStoredAsUnseen() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state String dateTime = new ObexTime(Instant.now()).toString(); EventReport event = @@ -640,16 +554,16 @@ public class MapClientStateMachineTest { null, "MMS"); - when(mMockRequestGetMessage.getMessage()).thenReturn(mTestIncomingMmsBmessage); - when(mMockRequestGetMessage.getHandle()).thenReturn(mTestMessageMmsHandle); + when(mRequestGetMessage.getMessage()).thenReturn(mTestIncomingMmsBmessage); + when(mRequestGetMessage.getHandle()).thenReturn(mTestMessageMmsHandle); sendAndDispatchEvent(event); - verify(mMockMasClient).makeRequest(any(RequestGetMessage.class)); + verify(mMasClient).makeRequest(any(RequestGetMessage.class)); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mMockRequestGetMessage); + sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mRequestGetMessage); - verify(mMockDatabase) + verify(mDatabase) .storeMessage( eq(mTestIncomingMmsBmessage), eq(mTestMessageMmsHandle), @@ -659,9 +573,7 @@ public class MapClientStateMachineTest { @Test public void testReceiveNewMessage_handleNotRecognized_messageDropped() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state // Send new message event with handle A String dateTime = new ObexTime(Instant.now()).toString(); @@ -675,85 +587,79 @@ public class MapClientStateMachineTest { "MMS"); // Prepare to send back message content, but use handle B - when(mMockRequestGetMessage.getHandle()).thenReturn(mTestMessageUnknownHandle); - when(mMockRequestGetMessage.getMessage()).thenReturn(mTestIncomingMmsBmessage); + when(mRequestGetMessage.getHandle()).thenReturn(mTestMessageUnknownHandle); + when(mRequestGetMessage.getMessage()).thenReturn(mTestIncomingMmsBmessage); sendAndDispatchEvent(event); - verify(mMockMasClient).makeRequest(any(RequestGetMessage.class)); + verify(mMasClient).makeRequest(any(RequestGetMessage.class)); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mMockRequestGetMessage); + sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mRequestGetMessage); // We should drop the message and not store it, as it's not one we requested - verify(mMockDatabase, never()) + verify(mDatabase, never()) .storeMessage(any(Bmessage.class), anyString(), anyLong(), anyBoolean()); } /** Test seen status set in database on initial download */ @Test public void testDownloadExistingSms_messageStoredAsSeen() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state com.android.bluetooth.mapclient.Message testMessageListingSms = createNewMessage("SMS_GSM", mTestMessageSmsHandle); ArrayList<com.android.bluetooth.mapclient.Message> messageListSms = new ArrayList<>(); messageListSms.add(testMessageListingSms); - when(mMockRequestGetMessagesListing.getList()).thenReturn(messageListSms); + when(mRequestGetMessagesListing.getList()).thenReturn(messageListSms); sendAndDispatchMessage( MceStateMachine.MSG_GET_MESSAGE_LISTING, MceStateMachine.FOLDER_INBOX); - verify(mMockMasClient).makeRequest(any(RequestGetMessagesListing.class)); + verify(mMasClient).makeRequest(any(RequestGetMessagesListing.class)); sendAndDispatchMessage( - MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mMockRequestGetMessagesListing); + MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mRequestGetMessagesListing); - verify(mMockMasClient).makeRequest(any(RequestGetMessage.class)); + verify(mMasClient).makeRequest(any(RequestGetMessage.class)); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mMockRequestGetMessage); + sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mRequestGetMessage); - verify(mMockDatabase).storeMessage(any(), any(), any(), eq(MESSAGE_SEEN)); + verify(mDatabase).storeMessage(any(), any(), any(), eq(MESSAGE_SEEN)); } /** Test seen status set in database on initial download */ @Test public void testDownloadExistingMms_messageStoredAsSeen() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state com.android.bluetooth.mapclient.Message testMessageListingMms = createNewMessage("MMS", mTestMessageMmsHandle); ArrayList<com.android.bluetooth.mapclient.Message> messageListMms = new ArrayList<>(); messageListMms.add(testMessageListingMms); - when(mMockRequestGetMessage.getMessage()).thenReturn(mTestIncomingMmsBmessage); - when(mMockRequestGetMessage.getHandle()).thenReturn(mTestMessageMmsHandle); - when(mMockRequestGetMessagesListing.getList()).thenReturn(messageListMms); + when(mRequestGetMessage.getMessage()).thenReturn(mTestIncomingMmsBmessage); + when(mRequestGetMessage.getHandle()).thenReturn(mTestMessageMmsHandle); + when(mRequestGetMessagesListing.getList()).thenReturn(messageListMms); sendAndDispatchMessage( MceStateMachine.MSG_GET_MESSAGE_LISTING, MceStateMachine.FOLDER_INBOX); - verify(mMockMasClient).makeRequest(any(RequestGetMessagesListing.class)); + verify(mMasClient).makeRequest(any(RequestGetMessagesListing.class)); sendAndDispatchMessage( - MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mMockRequestGetMessagesListing); + MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mRequestGetMessagesListing); - verify(mMockMasClient).makeRequest(any(RequestGetMessage.class)); + verify(mMasClient).makeRequest(any(RequestGetMessage.class)); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mMockRequestGetMessage); + sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mRequestGetMessage); - verify(mMockDatabase).storeMessage(any(), any(), any(), eq(MESSAGE_SEEN)); + verify(mDatabase).storeMessage(any(), any(), any(), eq(MESSAGE_SEEN)); } /** Test receiving a new message notification. */ @Test public void testReceiveNewMessageNotification() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state // Receive a new message notification. String dateTime = new ObexTime(Instant.now()).toString(); @@ -768,14 +674,13 @@ public class MapClientStateMachineTest { sendAndDispatchEvent(event); - verify(mMockMasClient).makeRequest(any(RequestGetMessage.class)); + verify(mMasClient).makeRequest(any(RequestGetMessage.class)); MceStateMachine.MessageMetadata messageMetadata = - mMceStateMachine.mMessages.get(mTestMessageSmsHandle); - Assert.assertEquals(messageMetadata.getHandle(), mTestMessageSmsHandle); - Assert.assertEquals( - new ObexTime(Instant.ofEpochMilli(messageMetadata.getTimestamp())).toString(), - dateTime); + mStateMachine.mMessages.get(mTestMessageSmsHandle); + assertThat(messageMetadata.getHandle()).isEqualTo(mTestMessageSmsHandle); + assertThat(new ObexTime(Instant.ofEpochMilli(messageMetadata.getTimestamp())).toString()) + .isEqualTo(dateTime); } /** @@ -784,11 +689,10 @@ public class MapClientStateMachineTest { */ @Test public void testMsgGetMessageListing_unsupportedMessageTypesNotRequested() { - setupSdpRecordReceipt(); - clearInvocations(mMockMasClient); + masConnected_whenConnecting_isConnected(); // transition to the connected state + + clearInvocations(mMasClient); byte expectedFilter = MessagesFilter.MESSAGE_TYPE_EMAIL | MessagesFilter.MESSAGE_TYPE_IM; - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); sendAndDispatchMessage( MceStateMachine.MSG_GET_MESSAGE_LISTING, MceStateMachine.FOLDER_INBOX); @@ -796,7 +700,7 @@ public class MapClientStateMachineTest { // using Request class as captor grabs all Request sub-classes even if // RequestGetMessagesListing is specifically requested ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class); - verify(mMockMasClient, atLeastOnce()).makeRequest(requestCaptor.capture()); + verify(mMasClient, atLeastOnce()).makeRequest(requestCaptor.capture()); List<Request> requests = requestCaptor.getAllValues(); // iterating through captured values to grab RequestGetMessagesListing object @@ -816,9 +720,7 @@ public class MapClientStateMachineTest { @Test public void testReceivedNewMmsNoSMSDefaultPackage_broadcastToSMSReplyPackage() { - setupSdpRecordReceipt(); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + masConnected_whenConnecting_isConnected(); // transition to the connected state String dateTime = new ObexTime(Instant.now()).toString(); EventReport event = @@ -832,78 +734,70 @@ public class MapClientStateMachineTest { sendAndDispatchEvent(event); - verify(mMockMasClient).makeRequest(any(RequestGetMessage.class)); + verify(mMasClient).makeRequest(any(RequestGetMessage.class)); - sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mMockRequestGetMessage); + sendAndDispatchMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, mRequestGetMessage); - verify(mMockMapClientService, times(1)) - .sendBroadcast( - mIntentArgument.capture(), eq(android.Manifest.permission.RECEIVE_SMS)); - assertThat(mIntentArgument.getValue().getPackage()).isNull(); + verifyIntentSent( + android.Manifest.permission.RECEIVE_SMS, hasPackage(nullValue(String.class))); } @Test public void testSdpBusyWhileConnecting_sdpRetried() { - assertCurrentStateAfterScheduledTask(BluetoothProfile.STATE_CONNECTING); + assertCurrentStateAfterScheduledTask(STATE_CONNECTING); // Send SDP Failed with status "busy" // Note: There's no way to validate the BluetoothDevice#sdpSearch call - mMceStateMachine.sendSdpResult(MceStateMachine.SDP_BUSY, null); + mStateMachine.sendSdpResult(MceStateMachine.SDP_BUSY, null); // Send successful SDP record, then send MAS Client connected SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "MasRecord"); - mMceStateMachine.sendSdpResult(MceStateMachine.SDP_SUCCESS, record); + mStateMachine.sendSdpResult(MceStateMachine.SDP_SUCCESS, record); sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED); + verifyStateTransitionAndIntent(STATE_CONNECTING, STATE_CONNECTED); } @Test public void testSdpBusyWhileConnectingAndRetryResultsReceivedAfterTimeout_resultsIgnored() { - assertCurrentStateAfterScheduledTask(BluetoothProfile.STATE_CONNECTING); + assertCurrentStateAfterScheduledTask(STATE_CONNECTING); // Send SDP Failed with status "busy" // Note: There's no way to validate the BluetoothDevice#sdpSearch call - mMceStateMachine.sendSdpResult(MceStateMachine.SDP_BUSY, null); - - // Timeout waiting for record - sendAndDispatchMessage(MceStateMachine.MSG_CONNECTING_TIMEOUT); + mStateMachine.sendSdpResult(MceStateMachine.SDP_BUSY, null); - // Verify we move into the disconnecting state - verify(mMockMapClientService, times(2)) - .sendBroadcastMultiplePermissions( - mIntentArgument.capture(), - any(String[].class), - any(BroadcastOptions.class)); + // Simulate timeout waiting for record + mLooper.moveTimeForward(MceStateMachine.CONNECT_TIMEOUT.toMillis()); + mLooper.dispatchAll(); - assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTING); + verifyStateTransitionAndIntent(STATE_CONNECTING, STATE_DISCONNECTING); // Send successful SDP record, then send MAS Client connected SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "MasRecord"); - mMceStateMachine.sendSdpResult(MceStateMachine.SDP_SUCCESS, record); + mStateMachine.sendSdpResult(MceStateMachine.SDP_SUCCESS, record); // Verify nothing happens - verifyNoMoreInteractions(mMockMapClientService); + verifyNoMoreInteractions(mService); } @Test public void testSdpFailedWithNoRecordWhileConnecting_deviceDisconnecting() { - assertCurrentStateAfterScheduledTask(BluetoothProfile.STATE_CONNECTING); + assertCurrentStateAfterScheduledTask(STATE_CONNECTING); // Send SDP process success with no record found - mMceStateMachine.sendSdpResult(MceStateMachine.SDP_SUCCESS, null); + mStateMachine.sendSdpResult(MceStateMachine.SDP_SUCCESS, null); // Verify we move into the disconnecting state - assertCurrentStateAfterScheduledTask(BluetoothProfile.STATE_DISCONNECTING); + assertCurrentStateAfterScheduledTask(STATE_DISCONNECTING); } @Test public void testSdpOrganicFailure_deviceDisconnecting() { - assertCurrentStateAfterScheduledTask(BluetoothProfile.STATE_CONNECTING); + assertCurrentStateAfterScheduledTask(STATE_CONNECTING); // Send SDP Failed entirely - mMceStateMachine.sendSdpResult(MceStateMachine.SDP_FAILED, null); + mStateMachine.sendSdpResult(MceStateMachine.SDP_FAILED, null); - assertCurrentStateAfterScheduledTask(BluetoothProfile.STATE_DISCONNECTING); + assertCurrentStateAfterScheduledTask(STATE_DISCONNECTING); } /** @@ -1001,10 +895,8 @@ public class MapClientStateMachineTest { * 'Success'/'Failure'. */ private void testSendMapMessagePendingIntents_base(String action, EventReport.Type type) { - int expectedFromState = BluetoothProfile.STATE_CONNECTING; - int expectedToState = BluetoothProfile.STATE_CONNECTED; sendAndDispatchMessage(MceStateMachine.MSG_MAS_CONNECTED); - verifyStateTransitionAndIntent(expectedFromState, expectedToState); + verifyStateTransitionAndIntent(STATE_CONNECTING, STATE_CONNECTED); PendingIntent pendingIntentSent; PendingIntent pendingIntentDelivered; @@ -1040,7 +932,7 @@ public class MapClientStateMachineTest { PendingIntent pendingIntentSent, PendingIntent pendingIntentDelivered, String messageHandle) { - mMceStateMachine.sendMapMessage( + mStateMachine.sendMapMessage( TEST_CONTACTS_ONE_PHONENUM, TEST_MESSAGE, pendingIntentSent, pendingIntentDelivered); mLooper.dispatchAll(); @@ -1056,7 +948,7 @@ public class MapClientStateMachineTest { ArgumentCaptor<RequestPushMessage> requestCaptor = ArgumentCaptor.forClass(RequestPushMessage.class); - verify(mMockMasClient, atLeastOnce()).makeRequest(requestCaptor.capture()); + verify(mMasClient, atLeastOnce()).makeRequest(requestCaptor.capture()); RequestPushMessage spyRequestPushMessage = spy(requestCaptor.getValue()); when(spyRequestPushMessage.getMsgHandle()).thenReturn(messageHandle); @@ -1064,34 +956,25 @@ public class MapClientStateMachineTest { } private void setupSdpRecordReceipt() { - assertCurrentStateAfterScheduledTask(BluetoothProfile.STATE_CONNECTING); + assertCurrentStateAfterScheduledTask(STATE_CONNECTING); // Setup receipt of SDP record SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "MasRecord"); - mMceStateMachine.sendSdpResult(MceStateMachine.SDP_SUCCESS, record); + mStateMachine.sendSdpResult(MceStateMachine.SDP_SUCCESS, record); } private void assertCurrentStateAfterScheduledTask(int expectedState) { mLooper.dispatchAll(); - assertThat(mMceStateMachine.getState()).isEqualTo(expectedState); + assertThat(mStateMachine.getState()).isEqualTo(expectedState); } - private void verifyStateTransitionAndIntent(int expectedFromState, int expectedToState) { - assertThat(mMceStateMachine.getState()).isEqualTo(expectedToState); - verify(mMockMapClientService, atLeastOnce()) - .sendBroadcastMultiplePermissions( - mIntentArgument.capture(), - any(String[].class), - any(BroadcastOptions.class)); - Intent capturedIntent = mIntentArgument.getValue(); - int intentFromState = - capturedIntent.getIntExtra( - BluetoothProfile.EXTRA_PREVIOUS_STATE, CONNECTION_STATE_UNDEFINED); - int intentToState = - capturedIntent.getIntExtra( - BluetoothProfile.EXTRA_STATE, CONNECTION_STATE_UNDEFINED); - assertThat(intentFromState).isEqualTo(expectedFromState); - assertThat(intentToState).isEqualTo(expectedToState); + private void verifyStateTransitionAndIntent(int oldState, int newState) { + assertThat(mStateMachine.getState()).isEqualTo(newState); + verifyIntentSent( + new String[] {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}, + hasAction(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED), + hasExtra(EXTRA_STATE, newState), + hasExtra(EXTRA_PREVIOUS_STATE, oldState)); } private static class MockSmsContentProvider extends MockContentProvider { @@ -1179,25 +1062,25 @@ public class MapClientStateMachineTest { // create new Bmessages for testing void createTestMessages() { - mOriginator = new VCardEntry(); + VCardEntry originator = new VCardEntry(); VCardProperty property = new VCardProperty(); property.setName(VCardConstants.PROPERTY_TEL); property.addValues("555-1212"); - mOriginator.addProperty(property); + originator.addProperty(property); mTestIncomingSmsBmessage = new Bmessage(); mTestIncomingSmsBmessage.setBodyContent("HelloWorld"); mTestIncomingSmsBmessage.setType(Bmessage.Type.SMS_GSM); mTestIncomingSmsBmessage.setFolder("telecom/msg/inbox"); - mTestIncomingSmsBmessage.addOriginator(mOriginator); - mTestIncomingSmsBmessage.addRecipient(mOriginator); + mTestIncomingSmsBmessage.addOriginator(originator); + mTestIncomingSmsBmessage.addRecipient(originator); mTestIncomingMmsBmessage = new Bmessage(); mTestIncomingMmsBmessage.setBodyContent("HelloWorld"); mTestIncomingMmsBmessage.setType(Bmessage.Type.MMS); mTestIncomingMmsBmessage.setFolder("telecom/msg/inbox"); - mTestIncomingMmsBmessage.addOriginator(mOriginator); - mTestIncomingMmsBmessage.addRecipient(mOriginator); + mTestIncomingMmsBmessage.addOriginator(originator); + mTestIncomingMmsBmessage.addRecipient(originator); } private void sendAndDispatchEvent(EventReport ev) { @@ -1209,7 +1092,22 @@ public class MapClientStateMachineTest { } private void sendAndDispatchMessage(int what, Object obj) { - mMceStateMachine.sendMessage(what, obj); + mStateMachine.sendMessage(what, obj); mLooper.dispatchAll(); } + + @SafeVarargs + private void verifyIntentSent(String permission, Matcher<Intent>... matchers) { + mInOrder.verify(mService) + .sendBroadcast(MockitoHamcrest.argThat(AllOf.allOf(matchers)), eq(permission)); + } + + @SafeVarargs + private void verifyIntentSent(String[] permissions, Matcher<Intent>... matchers) { + mInOrder.verify(mService) + .sendBroadcastMultiplePermissions( + MockitoHamcrest.argThat(AllOf.allOf(matchers)), + eq(permissions), + any(BroadcastOptions.class)); + } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java deleted file mode 100644 index 4086981761..0000000000 --- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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.mapclient; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.*; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; -import android.content.Context; -import android.os.Looper; -import android.os.UserHandle; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.MediumTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.bluetooth.TestUtils; -import com.android.bluetooth.Utils; -import com.android.bluetooth.btservice.AdapterService; -import com.android.bluetooth.btservice.storage.DatabaseManager; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -@MediumTest -@RunWith(AndroidJUnit4.class) -public class MapClientTest { - private static final String TAG = MapClientTest.class.getSimpleName(); - private MapClientService mService = null; - private BluetoothAdapter mAdapter = null; - private Context mTargetContext; - private boolean mIsAdapterServiceSet; - private boolean mIsMapClientServiceStarted; - - @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); - - @Mock private AdapterService mAdapterService; - @Mock private MnsService mMockMnsService; - @Mock private DatabaseManager mDatabaseManager; - - @Before - public void setUp() throws Exception { - mTargetContext = InstrumentationRegistry.getTargetContext(); - TestUtils.setAdapterService(mAdapterService); - mIsAdapterServiceSet = true; - when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); - mIsMapClientServiceStarted = true; - Looper looper = null; - mService = new MapClientService(mTargetContext, looper, mMockMnsService); - mService.start(); - mService.setAvailable(true); - mAdapter = BluetoothAdapter.getDefaultAdapter(); - } - - @After - public void tearDown() throws Exception { - if (mIsMapClientServiceStarted) { - mService.stop(); - mService.cleanup(); - mService = MapClientService.getMapClientService(); - assertThat(mService).isNull(); - } - if (mIsAdapterServiceSet) { - TestUtils.clearAdapterService(mAdapterService); - } - } - - /** - * Mock the priority of a bluetooth device - * - * @param device - The bluetooth device you wish to mock the priority of - * @param priority - The priority value you want the device to have - */ - private void mockDevicePriority(BluetoothDevice device, int priority) { - when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT)) - .thenReturn(priority); - } - - @Test - public void testInitialize() { - assertThat(MapClientService.getMapClientService()).isNotNull(); - } - - /** Test connection of one device. */ - @Test - public void testConnect() { - // make sure there is no statemachine already defined for this device - BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11"); - assertThat(mService.getInstanceMap()).doesNotContainKey(device); - - // connect a bluetooth device - mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - assertThat(mService.connect(device)).isTrue(); - - // is the statemachine created - Map<BluetoothDevice, MceStateMachine> map = mService.getInstanceMap(); - - Assert.assertEquals(1, map.size()); - MceStateMachine sm = map.get(device); - assertThat(sm).isNotNull(); - TestUtils.waitForLooperToFinishScheduledTask(sm.getHandler().getLooper()); - - Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, sm.getState()); - mService.cleanupDevice(device, sm); - assertThat(mService.getInstanceMap()).doesNotContainKey(device); - } - - /** Test that a PRIORITY_OFF device is not connected to */ - @Test - public void testConnectPriorityOffDevice() { - // make sure there is no statemachine already defined for this device - BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11"); - assertThat(mService.getInstanceMap()).doesNotContainKey(device); - - // connect a bluetooth device - mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); - assertThat(mService.connect(device)).isFalse(); - - // is the statemachine created - assertThat(mService.getInstanceMap()).isEmpty(); - } - - /** Test connecting MAXIMUM_CONNECTED_DEVICES devices. */ - @Test - public void testConnectMaxDevices() { - // Create bluetoothdevice & mock statemachine objects to be used in this test - List<BluetoothDevice> list = new ArrayList<>(); - String address = "11:11:11:11:11:1"; - for (int i = 0; i < MapClientService.MAXIMUM_CONNECTED_DEVICES; ++i) { - list.add(makeBluetoothDevice(address + i)); - } - - // make sure there is no statemachine already defined for the devices defined above - for (BluetoothDevice d : list) { - assertThat(mService.getInstanceMap().get(d)).isNull(); - } - - // run the test - connect all devices, set their priorities to on - for (BluetoothDevice d : list) { - mockDevicePriority(d, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - assertThat(mService.connect(d)).isTrue(); - } - - // verify - Map<BluetoothDevice, MceStateMachine> map = mService.getInstanceMap(); - Assert.assertEquals(MapClientService.MAXIMUM_CONNECTED_DEVICES, map.size()); - for (BluetoothDevice d : list) { - assertThat(map).containsKey(d); - } - - // Try to connect one more device. Should fail. - BluetoothDevice last = makeBluetoothDevice("11:22:33:44:55:66"); - assertThat(mService.connect(last)).isFalse(); - } - - /** Test calling connect via Binder */ - @Test - public void testConnectViaBinder() { - BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11"); - mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - Utils.setForegroundUserId(UserHandle.getCallingUserId()); - assertThat(mService.connect(device)).isTrue(); - } - - private BluetoothDevice makeBluetoothDevice(String address) { - return mAdapter.getRemoteDevice(address); - } -} diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java index 85e5e18607..12bcf80f3c 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java @@ -39,10 +39,10 @@ public class MessagesFilterTest { assertThat(filter.originator).isEqualTo(originator); filter.setOriginator(""); - assertThat(filter.originator).isEqualTo(null); // Empty string is stored as null + assertThat(filter.originator).isNull(); // Empty string is stored as null filter.setOriginator(null); - assertThat(filter.originator).isEqualTo(null); + assertThat(filter.originator).isNull(); } @Test @@ -74,10 +74,10 @@ public class MessagesFilterTest { assertThat(filter.recipient).isEqualTo(recipient); filter.setRecipient(""); - assertThat(filter.recipient).isEqualTo(null); // Empty string is stored as null + assertThat(filter.recipient).isNull(); // Empty string is stored as null filter.setRecipient(null); - assertThat(filter.recipient).isEqualTo(null); + assertThat(filter.recipient).isNull(); } /** Test Builder creates and sets everything correctly. */ diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java index 5b472d15a4..6f8bad385c 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java @@ -16,11 +16,12 @@ package com.android.bluetooth.mapclient; +import static com.google.common.truth.Truth.assertThat; + import android.annotation.SuppressLint; import androidx.test.runner.AndroidJUnit4; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -78,94 +79,60 @@ public class ObexTimeTest { @Test public void createWithValidDateTimeString_TimestampCorrect() { ObexTime timestamp = new ObexTime(VALID_TIME_STRING); - Assert.assertEquals( - "Parsed instant must match expected", - VALID_INSTANT_LOCAL_TZ, - timestamp.getInstant()); - Assert.assertEquals( - "Parsed date must match expected", VALID_DATE_LOCAL_TZ, timestamp.getTime()); + assertThat(timestamp.getInstant()).isEqualTo(VALID_INSTANT_LOCAL_TZ); + assertThat(timestamp.getTime()).isEqualTo(VALID_DATE_LOCAL_TZ); } @Test public void createWithValidDateTimeStringWithPosOffset_TimestampCorrect() { ObexTime timestamp = new ObexTime(VALID_TIME_STRING_WITH_OFFSET_POS); - Assert.assertEquals( - "Parsed instant must match expected", - VALID_INSTANT_WITH_OFFSET_POS, - timestamp.getInstant()); - Assert.assertEquals( - "Parsed date must match expected", VALID_DATE_WITH_OFFSET_POS, timestamp.getTime()); + assertThat(timestamp.getInstant()).isEqualTo(VALID_INSTANT_WITH_OFFSET_POS); + assertThat(timestamp.getTime()).isEqualTo(VALID_DATE_WITH_OFFSET_POS); } @Test public void createWithValidDateTimeStringWithNegOffset_TimestampCorrect() { ObexTime timestamp = new ObexTime(VALID_TIME_STRING_WITH_OFFSET_NEG); - Assert.assertEquals( - "Parsed instant must match expected", - VALID_INSTANT_WITH_OFFSET_NEG, - timestamp.getInstant()); - Assert.assertEquals( - "Parsed date must match expected", VALID_DATE_WITH_OFFSET_NEG, timestamp.getTime()); + assertThat(timestamp.getInstant()).isEqualTo(VALID_INSTANT_WITH_OFFSET_NEG); + assertThat(timestamp.getTime()).isEqualTo(VALID_DATE_WITH_OFFSET_NEG); } @Test public void createWithValidDate_TimestampCorrect() { ObexTime timestamp = new ObexTime(VALID_DATE_LOCAL_TZ); - Assert.assertEquals( - "ObexTime created with a date must return the expected instant", - VALID_INSTANT_LOCAL_TZ, - timestamp.getInstant()); - Assert.assertEquals( - "ObexTime created with a date must return the same date", - VALID_DATE_LOCAL_TZ, - timestamp.getTime()); + assertThat(timestamp.getInstant()).isEqualTo(VALID_INSTANT_LOCAL_TZ); + assertThat(timestamp.getTime()).isEqualTo(VALID_DATE_LOCAL_TZ); } @SuppressWarnings("JavaUtilDate") @Test public void createWithValidInstant_TimestampCorrect() { ObexTime timestamp = new ObexTime(VALID_INSTANT); - Assert.assertEquals( - "ObexTime created with a instant must return the same instant", - VALID_INSTANT, - timestamp.getInstant()); - Assert.assertEquals( - "ObexTime created with a instant must return the expected date", - VALID_DATE, - timestamp.getTime()); + assertThat(timestamp.getInstant()).isEqualTo(VALID_INSTANT); + assertThat(timestamp.getTime()).isEqualTo(VALID_DATE); } @Test public void printValidTime_TimestampMatchesInput() { ObexTime timestamp = new ObexTime(VALID_TIME_STRING); - Assert.assertEquals( - "Timestamp as a string must match the input string", - VALID_TIME_STRING, - timestamp.toString()); + assertThat(timestamp.toString()).isEqualTo(VALID_TIME_STRING); } @Test public void createWithInvalidDelimiterString_TimestampIsNull() { ObexTime timestamp = new ObexTime(INVALID_TIME_STRING_BAD_DELIMITER); - Assert.assertEquals( - "Parsed timestamp was invalid and must result in a null object", - null, - timestamp.getTime()); + assertThat(timestamp.getTime()).isNull(); } @Test public void createWithInvalidOffsetString_TimestampIsNull() { ObexTime timestamp = new ObexTime(INVALID_TIME_STRING_OFFSET_EXTRA_DIGITS); - Assert.assertEquals( - "Parsed timestamp was invalid and must result in a null object", - null, - timestamp.getTime()); + assertThat(timestamp.getTime()).isNull(); } @Test public void printInvalidTime_ReturnsNull() { ObexTime timestamp = new ObexTime(INVALID_TIME_STRING_BAD_DELIMITER); - Assert.assertEquals( - "Invalid timestamps must return null for toString()", null, timestamp.toString()); + assertThat(timestamp.toString()).isNull(); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/RequestTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/RequestTest.java index 16cc355cdd..2320b71b1c 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/RequestTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/RequestTest.java @@ -134,7 +134,7 @@ public class RequestTest { false, /*retry*/ false); assertThat(newRequest).isNotNull(); - assertThat(newRequest.getMsgHandle()).isEqualTo(null); + assertThat(newRequest.getMsgHandle()).isNull(); newRequest.execute(mFakeClientSession); assertThat(newRequest.isSuccess()).isTrue(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/mcp/McpServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/mcp/McpServiceTest.java index dd9e61ae7f..35c7e125a5 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/mcp/McpServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/mcp/McpServiceTest.java @@ -34,7 +34,6 @@ import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -88,7 +87,7 @@ public class McpServiceTest { public void testGetService() { McpService mMcpServiceDuplicate = McpService.getMcpService(); assertThat(mMcpServiceDuplicate).isNotNull(); - Assert.assertSame(mMcpServiceDuplicate, mMcpService); + assertThat(mMcpServiceDuplicate).isSameInstanceAs(mMcpService); } @Test @@ -100,13 +99,13 @@ public class McpServiceTest { mMcpService.setDeviceAuthorized(device0, true); verify(mMediaControlProfile).onDeviceAuthorizationSet(eq(device0)); - Assert.assertEquals( - BluetoothDevice.ACCESS_ALLOWED, mMcpService.getDeviceAuthorization(device0)); + assertThat(mMcpService.getDeviceAuthorization(device0)) + .isEqualTo(BluetoothDevice.ACCESS_ALLOWED); mMcpService.setDeviceAuthorized(device1, false); verify(mMediaControlProfile).onDeviceAuthorizationSet(eq(device1)); - Assert.assertEquals( - BluetoothDevice.ACCESS_REJECTED, mMcpService.getDeviceAuthorization(device1)); + assertThat(mMcpService.getDeviceAuthorization(device1)) + .isEqualTo(BluetoothDevice.ACCESS_REJECTED); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java index 3da40b9bbd..1e9b210a77 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java @@ -39,7 +39,6 @@ import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.le_audio.LeAudioService; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -135,8 +134,8 @@ public class MediaControlGattServiceTest { doReturn(mMandatoryFeatures).when(mMockMcsCallbacks).onGetFeatureFlags(); assertThat(mMcpService.init(UUID_GMCS)).isTrue(); - Assert.assertEquals(mMcpService.getServiceUuid(), UUID_GMCS); - Assert.assertEquals(mMcpService.getContentControlId(), TEST_CCID); + assertThat(mMcpService.getServiceUuid()).isEqualTo(UUID_GMCS); + assertThat(mMcpService.getContentControlId()).isEqualTo(TEST_CCID); doReturn(true).when(mMockGattServer).removeService(any(BluetoothGattService.class)); mMcpService.destroy(); @@ -210,216 +209,226 @@ public class MediaControlGattServiceTest { BluetoothGattCharacteristic characteristic = service.getCharacteristic(MediaControlGattService.UUID_PLAYER_NAME); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); - Assert.assertEquals("", characteristic.getStringValue(0)); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat(characteristic.getStringValue(0)).isEmpty(); characteristic = service.getCharacteristic(MediaControlGattService.UUID_TRACK_TITLE); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); - Assert.assertEquals("", characteristic.getStringValue(0)); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat(characteristic.getStringValue(0)).isEmpty(); characteristic = service.getCharacteristic(MediaControlGattService.UUID_TRACK_DURATION); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); - Assert.assertEquals( - 0xFFFFFFFF, - characteristic - .getIntValue(BluetoothGattCharacteristic.FORMAT_SINT32, 0) - .intValue()); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_SINT32, 0) + .intValue()) + .isEqualTo(0xFFFFFFFF); characteristic = service.getCharacteristic(MediaControlGattService.UUID_TRACK_POSITION); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_WRITE - | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); - Assert.assertEquals( - 0xFFFFFFFF, - characteristic - .getIntValue(BluetoothGattCharacteristic.FORMAT_SINT32, 0) - .intValue()); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_WRITE + | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_SINT32, 0) + .intValue()) + .isEqualTo(0xFFFFFFFF); characteristic = service.getCharacteristic(MediaControlGattService.UUID_MEDIA_STATE); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); - Assert.assertEquals( - MediaState.INACTIVE.getValue(), - characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0).intValue()); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0) + .intValue()) + .isEqualTo(MediaState.INACTIVE.getValue()); characteristic = service.getCharacteristic(MediaControlGattService.UUID_CONTENT_CONTROL_ID); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), BluetoothGattCharacteristic.PROPERTY_READ); - Assert.assertEquals( - TEST_CCID, - characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0).intValue()); + assertThat(characteristic.getProperties()) + .isEqualTo(BluetoothGattCharacteristic.PROPERTY_READ); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0) + .intValue()) + .isEqualTo(TEST_CCID); // Check initial state of all optional characteristics characteristic = service.getCharacteristic(MediaControlGattService.UUID_PLAYER_ICON_OBJ_ID); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), BluetoothGattCharacteristic.PROPERTY_READ); + assertThat(characteristic.getProperties()) + .isEqualTo(BluetoothGattCharacteristic.PROPERTY_READ); assertThat(characteristic.getValue().length).isEqualTo(0); characteristic = service.getCharacteristic(MediaControlGattService.UUID_PLAYER_ICON_URL); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), BluetoothGattCharacteristic.PROPERTY_READ); - Assert.assertEquals("", characteristic.getStringValue(0)); + assertThat(characteristic.getProperties()) + .isEqualTo(BluetoothGattCharacteristic.PROPERTY_READ); + assertThat(characteristic.getStringValue(0)).isEmpty(); characteristic = service.getCharacteristic(MediaControlGattService.UUID_TRACK_CHANGED); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat(characteristic.getProperties()) + .isEqualTo(BluetoothGattCharacteristic.PROPERTY_NOTIFY); characteristic = service.getCharacteristic(MediaControlGattService.UUID_PLAYBACK_SPEED); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_WRITE - | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); - Assert.assertEquals( - 0, - characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT8, 0).intValue()); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_WRITE + | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_SINT8, 0) + .intValue()) + .isEqualTo(0); characteristic = service.getCharacteristic(MediaControlGattService.UUID_SEEKING_SPEED); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); - Assert.assertEquals( - 0, - characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT8, 0).intValue()); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_SINT8, 0) + .intValue()) + .isEqualTo(0); characteristic = service.getCharacteristic( MediaControlGattService.UUID_CURRENT_TRACK_SEGMENT_OBJ_ID); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), BluetoothGattCharacteristic.PROPERTY_READ); + assertThat(characteristic.getProperties()) + .isEqualTo(BluetoothGattCharacteristic.PROPERTY_READ); assertThat(characteristic.getValue().length).isEqualTo(0); characteristic = service.getCharacteristic(MediaControlGattService.UUID_CURRENT_TRACK_OBJ_ID); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_WRITE - | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_WRITE + | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); assertThat(characteristic.getValue().length).isEqualTo(0); characteristic = service.getCharacteristic(MediaControlGattService.UUID_NEXT_TRACK_OBJ_ID); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_WRITE - | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_WRITE + | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); assertThat(characteristic.getValue().length).isEqualTo(0); characteristic = service.getCharacteristic(MediaControlGattService.UUID_CURRENT_GROUP_OBJ_ID); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_WRITE - | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_WRITE + | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); assertThat(characteristic.getValue().length).isEqualTo(0); characteristic = service.getCharacteristic(MediaControlGattService.UUID_PARENT_GROUP_OBJ_ID); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); assertThat(characteristic.getValue().length).isEqualTo(0); characteristic = service.getCharacteristic(MediaControlGattService.UUID_PLAYING_ORDER); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_WRITE - | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); - Assert.assertEquals( - PlayingOrder.SINGLE_ONCE.getValue(), - characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0).intValue()); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_WRITE + | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0) + .intValue()) + .isEqualTo(PlayingOrder.SINGLE_ONCE.getValue()); characteristic = service.getCharacteristic(MediaControlGattService.UUID_PLAYING_ORDER_SUPPORTED); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), BluetoothGattCharacteristic.PROPERTY_READ); - Assert.assertEquals( - SupportedPlayingOrder.SINGLE_ONCE, - characteristic - .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 0) - .intValue()); + assertThat(characteristic.getProperties()) + .isEqualTo(BluetoothGattCharacteristic.PROPERTY_READ); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 0) + .intValue()) + .isEqualTo(SupportedPlayingOrder.SINGLE_ONCE); characteristic = service.getCharacteristic(MediaControlGattService.UUID_MEDIA_CONTROL_POINT); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_NOTIFY - | BluetoothGattCharacteristic.PROPERTY_WRITE - | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_NOTIFY + | BluetoothGattCharacteristic.PROPERTY_WRITE + | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE); characteristic = service.getCharacteristic( MediaControlGattService.UUID_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); - Assert.assertEquals( - MediaControlGattService.INITIAL_SUPPORTED_OPCODES, - characteristic - .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0) - .intValue()); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0) + .intValue()) + .isEqualTo(MediaControlGattService.INITIAL_SUPPORTED_OPCODES); characteristic = service.getCharacteristic(MediaControlGattService.UUID_SEARCH_RESULT_OBJ_ID); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_READ - | BluetoothGattCharacteristic.PROPERTY_NOTIFY); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_READ + | BluetoothGattCharacteristic.PROPERTY_NOTIFY); assertThat(characteristic.getValue().length).isEqualTo(0); characteristic = service.getCharacteristic(MediaControlGattService.UUID_SEARCH_CONTROL_POINT); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - characteristic.getProperties(), - BluetoothGattCharacteristic.PROPERTY_NOTIFY - | BluetoothGattCharacteristic.PROPERTY_WRITE - | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE); + assertThat(characteristic.getProperties()) + .isEqualTo( + BluetoothGattCharacteristic.PROPERTY_NOTIFY + | BluetoothGattCharacteristic.PROPERTY_WRITE + | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE); } @Test @@ -488,78 +497,81 @@ public class MediaControlGattServiceTest { BluetoothGattCharacteristic characteristic = service.getCharacteristic(MediaControlGattService.UUID_PLAYBACK_SPEED); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - playback_speed, mMcpService.getPlaybackSpeedChar().floatValue(), 0.001f); + assertThat(mMcpService.getPlaybackSpeedChar().floatValue()).isEqualTo(playback_speed); characteristic = service.getCharacteristic(MediaControlGattService.UUID_PLAYING_ORDER); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - playing_order.getValue(), - characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0).intValue()); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0) + .intValue()) + .isEqualTo(playing_order.getValue()); characteristic = service.getCharacteristic(MediaControlGattService.UUID_TRACK_POSITION); assertThat(characteristic).isNotNull(); // Set value as ms, kept in characteristic as 0.01s - Assert.assertEquals( - track_position / 10, - characteristic - .getIntValue(BluetoothGattCharacteristic.FORMAT_SINT32, 0) - .intValue()); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_SINT32, 0) + .intValue()) + .isEqualTo(track_position / 10); characteristic = service.getCharacteristic(MediaControlGattService.UUID_PLAYER_NAME); assertThat(characteristic).isNotNull(); - Assert.assertEquals(player_name, characteristic.getStringValue(0)); + assertThat(characteristic.getStringValue(0)).isEqualTo(player_name); characteristic = service.getCharacteristic(MediaControlGattService.UUID_PLAYER_ICON_URL); assertThat(characteristic).isNotNull(); - Assert.assertEquals(icon_url, characteristic.getStringValue(0)); + assertThat(characteristic.getStringValue(0)).isEqualTo(icon_url); characteristic = service.getCharacteristic(MediaControlGattService.UUID_PLAYER_ICON_OBJ_ID); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - icon_obj_id.longValue(), mMcpService.byteArray2ObjId(characteristic.getValue())); + assertThat(mMcpService.byteArray2ObjId(characteristic.getValue())) + .isEqualTo(icon_obj_id.longValue()); characteristic = service.getCharacteristic(MediaControlGattService.UUID_PLAYING_ORDER_SUPPORTED); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - playing_order_supported.intValue(), - characteristic - .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 0) - .intValue()); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 0) + .intValue()) + .isEqualTo(playing_order_supported.intValue()); characteristic = service.getCharacteristic( MediaControlGattService.UUID_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - opcodes_supported.intValue(), - characteristic - .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0) - .intValue()); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0) + .intValue()) + .isEqualTo(opcodes_supported.intValue()); characteristic = service.getCharacteristic(MediaControlGattService.UUID_TRACK_TITLE); assertThat(characteristic).isNotNull(); - Assert.assertEquals(track_title, characteristic.getStringValue(0)); + assertThat(characteristic.getStringValue(0)).isEqualTo(track_title); characteristic = service.getCharacteristic(MediaControlGattService.UUID_TRACK_DURATION); assertThat(characteristic).isNotNull(); // Set value as ms, kept in characteristic as 0.01s - Assert.assertEquals( - track_duration / 10, - characteristic - .getIntValue(BluetoothGattCharacteristic.FORMAT_SINT32, 0) - .intValue()); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_SINT32, 0) + .intValue()) + .isEqualTo(track_duration / 10); characteristic = service.getCharacteristic(MediaControlGattService.UUID_MEDIA_STATE); assertThat(characteristic).isNotNull(); - Assert.assertEquals( - playback_state.getValue(), - characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0).intValue()); + assertThat( + characteristic + .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0) + .intValue()) + .isEqualTo(playback_state.getValue()); characteristic = service.getCharacteristic(MediaControlGattService.UUID_SEEKING_SPEED); assertThat(characteristic).isNotNull(); - Assert.assertEquals(seeking_speed, mMcpService.getSeekingSpeedChar().floatValue(), 0.001f); + assertThat(mMcpService.getSeekingSpeedChar().floatValue()).isEqualTo(seeking_speed); } private void verifyWriteObjIdsValid( @@ -931,9 +943,8 @@ public class MediaControlGattServiceTest { bb.putInt(value); } - Assert.assertEquals( - expectedGattResult, - mMcpService.handleMediaControlPointRequest(mCurrentDevice, bb.array())); + assertThat(mMcpService.handleMediaControlPointRequest(mCurrentDevice, bb.array())) + .isEqualTo(expectedGattResult); if (expectedGattResult == BluetoothGatt.GATT_SUCCESS) { // Verify if callback comes to profile diff --git a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlProfileTest.java b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlProfileTest.java index 1da98c68af..6dede08e29 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlProfileTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlProfileTest.java @@ -41,7 +41,6 @@ import com.android.bluetooth.audio_util.Metadata; import com.android.bluetooth.btservice.AdapterService; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -156,57 +155,46 @@ public class MediaControlProfileTest { // Some duration mMockMetadata.duration = Long.toString(duration); - Assert.assertEquals(duration, mMediaControlProfile.getCurrentTrackDuration()); + assertThat(mMediaControlProfile.getCurrentTrackDuration()).isEqualTo(duration); // No metadata equals no track duration mMockMediaData.metadata = null; - Assert.assertEquals( - MediaControlGattServiceInterface.TRACK_DURATION_UNAVAILABLE, - mMediaControlProfile.getCurrentTrackDuration()); + assertThat(mMediaControlProfile.getCurrentTrackDuration()) + .isEqualTo(MediaControlGattServiceInterface.TRACK_DURATION_UNAVAILABLE); } @Test public void testPlayerState2McsState() { - Assert.assertEquals( - mMediaControlProfile.playerState2McsState(PlaybackState.STATE_PLAYING), - MediaState.PLAYING); - Assert.assertEquals( - mMediaControlProfile.playerState2McsState(PlaybackState.STATE_NONE), - MediaState.INACTIVE); - Assert.assertEquals( - mMediaControlProfile.playerState2McsState(PlaybackState.STATE_STOPPED), - MediaState.PAUSED); - Assert.assertEquals( - mMediaControlProfile.playerState2McsState(PlaybackState.STATE_PAUSED), - MediaState.PAUSED); - Assert.assertEquals( - mMediaControlProfile.playerState2McsState(PlaybackState.STATE_PLAYING), - MediaState.PLAYING); - Assert.assertEquals( - mMediaControlProfile.playerState2McsState(PlaybackState.STATE_FAST_FORWARDING), - MediaState.SEEKING); - Assert.assertEquals( - mMediaControlProfile.playerState2McsState(PlaybackState.STATE_REWINDING), - MediaState.SEEKING); - Assert.assertEquals( - mMediaControlProfile.playerState2McsState(PlaybackState.STATE_BUFFERING), - MediaState.PAUSED); - Assert.assertEquals( - mMediaControlProfile.playerState2McsState(PlaybackState.STATE_ERROR), - MediaState.INACTIVE); - Assert.assertEquals( - mMediaControlProfile.playerState2McsState(PlaybackState.STATE_CONNECTING), - MediaState.INACTIVE); - Assert.assertEquals( - mMediaControlProfile.playerState2McsState(PlaybackState.STATE_SKIPPING_TO_PREVIOUS), - MediaState.PAUSED); - Assert.assertEquals( - mMediaControlProfile.playerState2McsState(PlaybackState.STATE_SKIPPING_TO_NEXT), - MediaState.PAUSED); - Assert.assertEquals( - mMediaControlProfile.playerState2McsState( - PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM), - MediaState.PAUSED); + assertThat(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_PLAYING)) + .isEqualTo(MediaState.PLAYING); + assertThat(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_NONE)) + .isEqualTo(MediaState.INACTIVE); + assertThat(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_STOPPED)) + .isEqualTo(MediaState.PAUSED); + assertThat(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_PAUSED)) + .isEqualTo(MediaState.PAUSED); + assertThat(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_PLAYING)) + .isEqualTo(MediaState.PLAYING); + assertThat(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_FAST_FORWARDING)) + .isEqualTo(MediaState.SEEKING); + assertThat(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_REWINDING)) + .isEqualTo(MediaState.SEEKING); + assertThat(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_BUFFERING)) + .isEqualTo(MediaState.PAUSED); + assertThat(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_ERROR)) + .isEqualTo(MediaState.INACTIVE); + assertThat(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_CONNECTING)) + .isEqualTo(MediaState.INACTIVE); + assertThat( + mMediaControlProfile.playerState2McsState( + PlaybackState.STATE_SKIPPING_TO_PREVIOUS)) + .isEqualTo(MediaState.PAUSED); + assertThat(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_SKIPPING_TO_NEXT)) + .isEqualTo(MediaState.PAUSED); + assertThat( + mMediaControlProfile.playerState2McsState( + PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM)) + .isEqualTo(MediaState.PAUSED); } @Test @@ -215,18 +203,16 @@ public class MediaControlProfileTest { long position = 10; float playback_speed = 1.5f; - Assert.assertEquals( - mMcpServiceCallbacks.onGetCurrentTrackPosition(), - MediaControlGattServiceInterface.TRACK_POSITION_UNAVAILABLE); + assertThat(mMcpServiceCallbacks.onGetCurrentTrackPosition()) + .isEqualTo(MediaControlGattServiceInterface.TRACK_POSITION_UNAVAILABLE); PlaybackState.Builder bob = new PlaybackState.Builder(mMockMediaData.state); bob.setState(state, position, playback_speed); mMockMediaData.state = bob.build(); doReturn(mMockMediaData.state).when(mMockMediaPlayerWrapper).getPlaybackState(); - Assert.assertNotEquals( - mMcpServiceCallbacks.onGetCurrentTrackPosition(), - MediaControlGattServiceInterface.TRACK_POSITION_UNAVAILABLE); + assertThat(mMcpServiceCallbacks.onGetCurrentTrackPosition()) + .isNotEqualTo(MediaControlGattServiceInterface.TRACK_POSITION_UNAVAILABLE); } @Test @@ -272,11 +258,11 @@ public class MediaControlProfileTest { verify(mMockMediaPlayerWrapper, timeout(100).times(times)).seekTo(positionCaptor.capture()); // position cannot be negative and bigger than track duration - if (position < 0) Assert.assertEquals(positionCaptor.getValue().longValue(), 0); + if (position < 0) assertThat(positionCaptor.getValue().longValue()).isEqualTo(0); else if (position > duration) { - Assert.assertEquals(positionCaptor.getValue().longValue(), duration); + assertThat(positionCaptor.getValue().longValue()).isEqualTo(duration); } else { - Assert.assertEquals(positionCaptor.getValue().longValue(), position); + assertThat(positionCaptor.getValue().longValue()).isEqualTo(position); } } @@ -362,7 +348,7 @@ public class MediaControlProfileTest { verify(mMockMediaPlayerWrapper, timeout(100)).fastForward(); mMockMetadata.duration = Long.toString(duration); - Assert.assertEquals(duration, mMediaControlProfile.getCurrentTrackDuration()); + assertThat(mMediaControlProfile.getCurrentTrackDuration()).isEqualTo(duration); request = new Request(Request.Opcodes.MOVE_RELATIVE, 100); mMcpServiceCallbacks.onMediaControlRequest(request); verify(mMockMediaPlayerWrapper, timeout(100)).seekTo(duration); @@ -398,8 +384,8 @@ public class MediaControlProfileTest { | PlaybackState.ACTION_FAST_FORWARD | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS; - Assert.assertEquals( - actions | baseFeatures, mMediaControlProfile.getCurrentPlayerSupportedActions()); + assertThat(mMediaControlProfile.getCurrentPlayerSupportedActions()) + .isEqualTo(actions | baseFeatures); } @Test @@ -423,15 +409,15 @@ public class MediaControlProfileTest { | Request.SupportedOpcodes.FAST_FORWARD | Request.SupportedOpcodes.MOVE_RELATIVE; - Assert.assertEquals( - mMediaControlProfile.playerActions2McsSupportedOpcodes(actions), opcodes_supported); + assertThat(mMediaControlProfile.playerActions2McsSupportedOpcodes(actions)) + .isEqualTo(opcodes_supported); // Verify toggle-style play/pause control support actions = PlaybackState.ACTION_PLAY_PAUSE; opcodes_supported = Request.SupportedOpcodes.PAUSE | Request.SupportedOpcodes.PLAY; - Assert.assertEquals( - mMediaControlProfile.playerActions2McsSupportedOpcodes(actions), opcodes_supported); + assertThat(mMediaControlProfile.playerActions2McsSupportedOpcodes(actions)) + .isEqualTo(opcodes_supported); } @Test @@ -481,7 +467,7 @@ public class MediaControlProfileTest { PlayingOrder expected_value, boolean is_shuffle_set, boolean is_repeat_set) { doReturn(is_shuffle_set).when(mMockMediaPlayerWrapper).isShuffleSet(); doReturn(is_repeat_set).when(mMockMediaPlayerWrapper).isRepeatSet(); - Assert.assertEquals(expected_value, mMediaControlProfile.getCurrentPlayerPlayingOrder()); + assertThat(mMediaControlProfile.getCurrentPlayerPlayingOrder()).isEqualTo(expected_value); } @Test @@ -503,8 +489,8 @@ public class MediaControlProfileTest { doReturn(is_shuffle_set).when(mMockMediaPlayerWrapper).isShuffleSupported(); doReturn(is_repeat_set).when(mMockMediaPlayerWrapper).isRepeatSupported(); - Assert.assertEquals( - expected_value, mMediaControlProfile.getSupportedPlayingOrder().intValue()); + assertThat(mMediaControlProfile.getSupportedPlayingOrder().intValue()) + .isEqualTo(expected_value); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppManagerTest.java index 77163fa53b..3ce61fd510 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppManagerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppManagerTest.java @@ -254,12 +254,12 @@ public class BluetoothOppManagerTest { public void cleanUpSendingFileInfo_fileInfoCleaned() { BluetoothOppUtility.sSendFileMap.clear(); Uri uri = Uri.parse("content:///a/new/folder/abc/xyz.txt"); - assertThat(BluetoothOppUtility.sSendFileMap.size()).isEqualTo(0); + assertThat(BluetoothOppUtility.sSendFileMap).isEmpty(); BluetoothOppManager.getInstance(mContext) .saveSendingFileInfo("text/plain", uri.toString(), false, true); - assertThat(BluetoothOppUtility.sSendFileMap.size()).isEqualTo(1); + assertThat(BluetoothOppUtility.sSendFileMap).hasSize(1); BluetoothOppManager.getInstance(mContext).cleanUpSendingFileInfo(); - assertThat(BluetoothOppUtility.sSendFileMap.size()).isEqualTo(0); + assertThat(BluetoothOppUtility.sSendFileMap).isEmpty(); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceCleanupTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceCleanupTest.java index 2183c19347..935c1bf471 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceCleanupTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceCleanupTest.java @@ -61,7 +61,6 @@ public class BluetoothOppServiceCleanupTest { BluetoothOppService service = null; try { service = new BluetoothOppService(adapterService); - service.start(); service.setAvailable(true); // Call stop while UpdateThread is running. diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java index 7e54701383..dd14319b17 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java @@ -81,7 +81,6 @@ public class BluetoothOppServiceTest { AdapterService adapterService = new AdapterService(mTargetContext); mService = new BluetoothOppService(adapterService); - mService.start(); mService.setAvailable(true); mIsBluetoothOppServiceStarted = true; diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppUtilityTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppUtilityTest.java index d10246229b..76fd28de89 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppUtilityTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppUtilityTest.java @@ -319,7 +319,7 @@ public class BluetoothOppUtilityTest { assertThat(info.mCurrentBytes).isEqualTo(currentBytesValue); assertThat(info.mTimeStamp).isEqualTo(timestampValue); assertThat(info.mDestAddr).isEqualTo(destinationValue); - assertThat(info.mFileUri).isEqualTo(null); + assertThat(info.mFileUri).isNull(); assertThat(info.mFileType).isEqualTo(fileTypeValue); assertThat(info.mDeviceName).isEqualTo(deviceNameValue); assertThat(info.mHandoverInitiated).isEqualTo(false); diff --git a/android/app/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java index d2fddd8078..37927d28ec 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java @@ -32,6 +32,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.net.TetheringInterface; +import android.net.TetheringManager; import android.os.UserManager; import androidx.test.InstrumentationRegistry; @@ -55,15 +56,6 @@ import org.mockito.junit.MockitoRule; @MediumTest @RunWith(AndroidJUnit4.class) public class PanServiceTest { - private static final String REMOTE_DEVICE_ADDRESS = "00:00:00:00:00:00"; - private static final byte[] REMOTE_DEVICE_ADDRESS_AS_ARRAY = new byte[] {0, 0, 0, 0, 0, 0}; - - private static final int TIMEOUT_MS = 5_000; - - private PanService mService = null; - private BluetoothAdapter mAdapter = null; - private BluetoothDevice mRemoteDevice; - @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock private AdapterService mAdapterService; @@ -71,31 +63,34 @@ public class PanServiceTest { @Mock private PanNativeInterface mNativeInterface; @Mock private UserManager mMockUserManager; + private static final byte[] REMOTE_DEVICE_ADDRESS_AS_ARRAY = new byte[] {0, 0, 0, 0, 0, 0}; + + private static final int TIMEOUT_MS = 5_000; + + private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); + private final BluetoothDevice mRemoteDevice = TestUtils.getTestDevice(mAdapter, 0); + private final Context mTargetContext = InstrumentationRegistry.getTargetContext(); + + private PanService mService; + @Before - public void setUp() throws Exception { - Context targetContext = InstrumentationRegistry.getTargetContext(); - TestUtils.setAdapterService(mAdapterService); + public void setUp() { + doReturn(mTargetContext.getResources()).when(mAdapterService).getResources(); doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); - PanNativeInterface.setInstance(mNativeInterface); - mService = new PanService(targetContext); - mService.start(); - mService.setAvailable(true); + TestUtils.mockGetSystemService( + mAdapterService, Context.USER_SERVICE, UserManager.class, mMockUserManager); + TestUtils.mockGetSystemService( + mAdapterService, Context.TETHERING_SERVICE, TetheringManager.class); - // Try getting the Bluetooth adapter - mAdapter = BluetoothAdapter.getDefaultAdapter(); - assertThat(mAdapter).isNotNull(); - mService.mUserManager = mMockUserManager; - mRemoteDevice = mAdapter.getRemoteDevice(REMOTE_DEVICE_ADDRESS); + mService = new PanService(mAdapterService, mNativeInterface); + mService.setAvailable(true); } @After - public void tearDown() throws Exception { + public void tearDown() { mService.stop(); mService.cleanup(); - PanNativeInterface.setInstance(null); - mService = PanService.getPanService(); - assertThat(mService).isNull(); - TestUtils.clearAdapterService(mAdapterService); + assertThat(PanService.getPanService()).isNull(); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java index a1426dedef..5e4abee154 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java @@ -16,6 +16,8 @@ package com.android.bluetooth.pbap; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.*; import android.bluetooth.BluetoothAdapter; @@ -31,9 +33,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; -import org.hamcrest.core.IsInstanceOf; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -97,11 +97,10 @@ public class PbapStateMachineTest { /** Test that initial state is WaitingForAuth */ @Test public void testInitialState() { - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTING, mPbapStateMachine.getConnectionState()); - Assert.assertThat( - mPbapStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(PbapStateMachine.WaitingForAuth.class)); + assertThat(mPbapStateMachine.getConnectionState()) + .isEqualTo(BluetoothProfile.STATE_CONNECTING); + assertThat(mPbapStateMachine.getCurrentState()) + .isInstanceOf(PbapStateMachine.WaitingForAuth.class); } /** Test state transition from WaitingForAuth to Finished when the user rejected */ @@ -109,11 +108,10 @@ public class PbapStateMachineTest { @Test public void testStateTransition_WaitingForAuthToFinished() throws Exception { mPbapStateMachine.sendMessage(PbapStateMachine.REJECTED); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, mPbapStateMachine.getConnectionState()); - Assert.assertThat( - mPbapStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(PbapStateMachine.Finished.class)); + assertThat(mPbapStateMachine.getConnectionState()) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + assertThat(mPbapStateMachine.getCurrentState()) + .isInstanceOf(PbapStateMachine.Finished.class); } /** Test state transition from WaitingForAuth to Finished when the user rejected */ @@ -121,11 +119,10 @@ public class PbapStateMachineTest { @Test public void testStateTransition_WaitingForAuthToConnected() throws Exception { mPbapStateMachine.sendMessage(PbapStateMachine.AUTHORIZED); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, mPbapStateMachine.getConnectionState()); - Assert.assertThat( - mPbapStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(PbapStateMachine.Connected.class)); + assertThat(mPbapStateMachine.getConnectionState()) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); + assertThat(mPbapStateMachine.getCurrentState()) + .isInstanceOf(PbapStateMachine.Connected.class); } /** Test state transition from Connected to Finished when the OBEX server is done */ @@ -133,18 +130,16 @@ public class PbapStateMachineTest { @Test public void testStateTransition_ConnectedToFinished() throws Exception { mPbapStateMachine.sendMessage(PbapStateMachine.AUTHORIZED); - Assert.assertEquals( - BluetoothProfile.STATE_CONNECTED, mPbapStateMachine.getConnectionState()); - Assert.assertThat( - mPbapStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(PbapStateMachine.Connected.class)); + assertThat(mPbapStateMachine.getConnectionState()) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); + assertThat(mPbapStateMachine.getCurrentState()) + .isInstanceOf(PbapStateMachine.Connected.class); // PBAP OBEX transport is done. mPbapStateMachine.sendMessage(PbapStateMachine.DISCONNECT); - Assert.assertEquals( - BluetoothProfile.STATE_DISCONNECTED, mPbapStateMachine.getConnectionState()); - Assert.assertThat( - mPbapStateMachine.getCurrentState(), - IsInstanceOf.instanceOf(PbapStateMachine.Finished.class)); + assertThat(mPbapStateMachine.getConnectionState()) + .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + assertThat(mPbapStateMachine.getCurrentState()) + .isInstanceOf(PbapStateMachine.Finished.class); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbapclient/CallLogPullRequestTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbapclient/CallLogPullRequestTest.java index f3e5ddd821..43d12765c2 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/pbapclient/CallLogPullRequestTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/pbapclient/CallLogPullRequestTest.java @@ -88,7 +88,7 @@ public class CallLogPullRequestTest { request.onPullComplete(); // No operation has been done. - assertThat(mCallCounter.size()).isEqualTo(0); + assertThat(mCallCounter).isEmpty(); } @Test @@ -102,7 +102,7 @@ public class CallLogPullRequestTest { request.onPullComplete(); // No operation has been done. - assertThat(mCallCounter.size()).isEqualTo(0); + assertThat(mCallCounter).isEmpty(); } @Test @@ -116,7 +116,7 @@ public class CallLogPullRequestTest { request.onPullComplete(); // Call counter should remain same. - assertThat(mCallCounter.size()).isEqualTo(0); + assertThat(mCallCounter).isEmpty(); } @Test @@ -140,7 +140,7 @@ public class CallLogPullRequestTest { request.onPullComplete(); // Call counter should remain same. - assertThat(mCallCounter.size()).isEqualTo(0); + assertThat(mCallCounter).isEmpty(); } @Test @@ -165,7 +165,7 @@ public class CallLogPullRequestTest { request.onPullComplete(); - assertThat(mCallCounter.size()).isEqualTo(1); + assertThat(mCallCounter).hasSize(1); for (String key : mCallCounter.keySet()) { assertThat(mCallCounter.get(key)).isEqualTo(2); break; diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientAccountManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientAccountManagerTest.java index 1611399d98..26a0ffe2be 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientAccountManagerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientAccountManagerTest.java @@ -175,7 +175,7 @@ public class PbapClientAccountManagerTest { assertThat(fromAccounts).isNull(); assertThat(toAccounts).isNotNull(); - assertThat(toAccounts.size()).isEqualTo(2); + assertThat(toAccounts).hasSize(2); assertThat(toAccounts).contains(accounts[0]); assertThat(toAccounts).contains(accounts[1]); } @@ -250,7 +250,7 @@ public class PbapClientAccountManagerTest { mTestLooper.dispatchAll(); assertThat(mAccountManager.getAccounts()).isNotNull(); - assertThat(mAccountManager.getAccounts().size()).isEqualTo(2); + assertThat(mAccountManager.getAccounts()).hasSize(2); assertThat(mAccountManager.getAccounts()).contains(accounts[0]); assertThat(mAccountManager.getAccounts()).contains(accounts[1]); } @@ -289,7 +289,7 @@ public class PbapClientAccountManagerTest { // Add again once its already in there assertThat(mAccountManager.addAccount(account)).isTrue(); - assertThat(mAccountManager.getAccounts().size()).isEqualTo(1); + assertThat(mAccountManager.getAccounts()).hasSize(1); assertThat(mAccountManager.getAccounts()).contains(account); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientContactsStorageTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientContactsStorageTest.java index 18cae7d34d..68ab51a0b4 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientContactsStorageTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientContactsStorageTest.java @@ -244,7 +244,7 @@ public class PbapClientContactsStorageTest { assertThat(mStorage.getStorageAccounts()).contains(account1); mStorage.removeAccount(account2); - assertThat(mStorage.getStorageAccounts().size()).isEqualTo(mMockedAccounts.size()); + assertThat(mStorage.getStorageAccounts()).hasSize(mMockedAccounts.size()); assertThat(mStorage.getStorageAccounts()).contains(account1); assertThat(mStorage.getStorageAccounts()).doesNotContain(account2); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientObexClientTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientObexClientTest.java index 723d219790..a5897cf1c6 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientObexClientTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientObexClientTest.java @@ -270,12 +270,12 @@ public class PbapClientObexClientTest { assertThat(phonebook.getOffset()).isEqualTo(0); assertThat(phonebook.getCount()).isEqualTo(1); assertThat(phonebook.getList()).isNotEmpty(); - assertThat(phonebook.getList().size()).isEqualTo(1); + assertThat(phonebook.getList()).hasSize(1); VCardEntry contact1 = phonebook.getList().get(0); assertThat(contact1.getDisplayName()).isEqualTo("Foo Bar"); assertThat(contact1.getPhoneList()).isNotNull(); - assertThat(contact1.getPhoneList().size()).isEqualTo(1); + assertThat(contact1.getPhoneList()).hasSize(1); assertThat(contact1.getPhoneList().get(0).getNumber()).isEqualTo("+1-234-567-8901"); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java index 63a52d7ac4..6da23ea3f9 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java @@ -18,17 +18,16 @@ package com.android.bluetooth.sap; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; -import android.content.Context; import android.content.Intent; import android.os.Looper; import androidx.test.filters.MediumTest; -import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; @@ -47,40 +46,32 @@ import org.mockito.junit.MockitoRule; @MediumTest @RunWith(AndroidJUnit4.class) public class SapServiceTest { - private SapService mService = null; - private BluetoothAdapter mAdapter = null; - private Context mTargetContext; - @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock private AdapterService mAdapterService; @Mock private DatabaseManager mDatabaseManager; - private BluetoothDevice mDevice; + + private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); + private final BluetoothDevice mDevice = TestUtils.getTestDevice(mAdapter, 0); + + private SapService mService; @Before - public void setUp() throws Exception { - mTargetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - TestUtils.setAdapterService(mAdapterService); + public void setUp() { + doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); if (Looper.myLooper() == null) { Looper.prepare(); } - mService = new SapService(mTargetContext); - mService.start(); + mService = new SapService(mAdapterService); mService.setAvailable(true); - // Try getting the Bluetooth adapter - mAdapter = BluetoothAdapter.getDefaultAdapter(); - assertThat(mAdapter).isNotNull(); - mDevice = TestUtils.getTestDevice(mAdapter, 0); } @After - public void tearDown() throws Exception { + public void tearDown() { mService.stop(); - mService = SapService.getSapService(); - assertThat(mService).isNull(); - TestUtils.clearAdapterService(mAdapterService); + assertThat(SapService.getSapService()).isNull(); } @Test @@ -89,22 +80,9 @@ public class SapServiceTest { assertThat(mService.getConnectedDevices()).isEmpty(); } - /** Test stop SAP Service */ - @Test - public void testStopSapService() throws Exception { - // SAP Service is already running: test stop(). Note: must be done on the main thread - InstrumentationRegistry.getInstrumentation() - .runOnMainSync( - () -> { - mService.stop(); - mService.start(); - }); - } - /** Test get connection policy for BluetoothDevice */ @Test public void testGetConnectionPolicy() { - when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); when(mDatabaseManager.getProfileConnectionPolicy(mDevice, BluetoothProfile.SAP)) .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); assertThat(mService.getConnectionPolicy(mDevice)) @@ -137,6 +115,6 @@ public class SapServiceTest { Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); intent.putExtra( BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, BluetoothDevice.REQUEST_TYPE_SIM_ACCESS); - mService.mSapReceiver.onReceive(mTargetContext, intent); + mService.mSapReceiver.onReceive(null, intent); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/sdp/DipTest.java b/android/app/tests/unit/src/com/android/bluetooth/sdp/DipTest.java index 3ed17fb529..2e49a1f6e1 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/sdp/DipTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/sdp/DipTest.java @@ -99,7 +99,7 @@ public class DipTest { boolean primaryRecord) { Intent intent = intentArgument.getValue(); - assertThat(intent).isNotEqualTo(null); + assertThat(intent).isNotNull(); assertThat(intent.getAction()).isEqualTo(BluetoothDevice.ACTION_SDP_RECORD); assertThat(device).isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); assertThat(Utils.byteArrayToUuid(uuid)[0]) @@ -108,7 +108,7 @@ public class DipTest { .isEqualTo(intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1)); SdpDipRecord record = intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD); - assertThat(record).isNotEqualTo(null); + assertThat(record).isNotNull(); assertThat(specificationId).isEqualTo(record.getSpecificationId()); assertThat(vendorId).isEqualTo(record.getVendorId()); assertThat(vendorIdSource).isEqualTo(record.getVendorIdSource()); diff --git a/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGattTest.java b/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGattTest.java index 798984b273..a959a4a764 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGattTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGattTest.java @@ -17,7 +17,6 @@ package com.android.bluetooth.tbs; -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -40,8 +39,6 @@ import com.android.bluetooth.btservice.AdapterService; import com.google.common.primitives.Bytes; -import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -63,9 +60,18 @@ import java.util.UUID; @MediumTest @RunWith(AndroidJUnit4.class) public class TbsGattTest { - private BluetoothAdapter mAdapter; - private BluetoothDevice mFirstDevice; - private BluetoothDevice mSecondDevice; + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); + + @Mock private AdapterService mAdapterService; + @Mock private BluetoothGattServerProxy mGattServer; + @Mock private TbsGatt.Callback mCallback; + @Mock private TbsService mService; + @Captor private ArgumentCaptor<BluetoothGattService> mGattServiceCaptor; + + private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); + private final BluetoothDevice mFirstDevice = TestUtils.getTestDevice(mAdapter, 0); + private final BluetoothDevice mSecondDevice = TestUtils.getTestDevice(mAdapter, 1); private Integer mCurrentCcid; private String mCurrentUci; @@ -75,50 +81,19 @@ public class TbsGattTest { private TbsGatt mTbsGatt; - @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); - - @Mock private AdapterService mAdapterService; - @Mock private BluetoothGattServerProxy mMockGattServer; - @Mock private TbsGatt.Callback mMockTbsGattCallback; - @Mock private TbsService mMockTbsService; - - @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); - - @Captor private ArgumentCaptor<BluetoothGattService> mGattServiceCaptor; - @Before - public void setUp() throws Exception { + public void setUp() { if (Looper.myLooper() == null) { Looper.prepare(); } - getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(); - - TestUtils.setAdapterService(mAdapterService); - mAdapter = BluetoothAdapter.getDefaultAdapter(); - - doReturn(true).when(mMockGattServer).addService(any(BluetoothGattService.class)); - doReturn(true).when(mMockGattServer).open(any(BluetoothGattServerCallback.class)); + doReturn(true).when(mGattServer).addService(any(BluetoothGattService.class)); + doReturn(true).when(mGattServer).open(any(BluetoothGattServerCallback.class)); doReturn(BluetoothDevice.ACCESS_ALLOWED) - .when(mMockTbsService) + .when(mService) .getDeviceAuthorization(any(BluetoothDevice.class)); - mTbsGatt = new TbsGatt(mMockTbsService); - mTbsGatt.setBluetoothGattServerForTesting(mMockGattServer); - - mFirstDevice = TestUtils.getTestDevice(mAdapter, 0); - mSecondDevice = TestUtils.getTestDevice(mAdapter, 1); - - when(mMockTbsService.getDeviceAuthorization(any(BluetoothDevice.class))) - .thenReturn(BluetoothDevice.ACCESS_ALLOWED); - } - - @After - public void tearDown() throws Exception { - mFirstDevice = null; - mSecondDevice = null; - mTbsGatt = null; - TestUtils.clearAdapterService(mAdapterService); + mTbsGatt = new TbsGatt(mAdapterService, mService, mGattServer); } private void prepareDefaultService() { @@ -137,12 +112,12 @@ public class TbsGattTest { true, mCurrentProviderName, mCurrentTechnology, - mMockTbsGattCallback)) + mCallback)) .isTrue(); verify(mAdapterService).registerBluetoothStateCallback(any(), any()); - verify(mMockGattServer).addService(mGattServiceCaptor.capture()); - doReturn(mGattServiceCaptor.getValue()).when(mMockGattServer).getService(any(UUID.class)); + verify(mGattServer).addService(mGattServiceCaptor.capture()); + doReturn(mGattServiceCaptor.getValue()).when(mGattServer).getService(any(UUID.class)); } private BluetoothGattCharacteristic getCharacteristic(UUID uuid) { @@ -169,9 +144,9 @@ public class TbsGattTest { enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); - verify(mMockGattServer) + verify(mGattServer) .sendResponse(eq(device), eq(1), eq(BluetoothGatt.GATT_SUCCESS), eq(0), any()); - reset(mMockGattServer); + reset(mGattServer); } private void verifySetValue( @@ -189,13 +164,12 @@ public class TbsGattTest { } else { assertThat(mTbsGatt.setBearerProviderName((String) value)).isFalse(); } - Assert.assertEquals((String) value, characteristic.getStringValue(0)); + assertThat(characteristic.getStringValue(0)).isEqualTo((String) value); } else if (characteristic.getUuid().equals(TbsGatt.UUID_BEARER_TECHNOLOGY)) { assertThat(mTbsGatt.setBearerTechnology((Integer) value)).isTrue(); - Assert.assertEquals( - (Integer) value, - characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0)); + assertThat(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0)) + .isEqualTo((Integer) value); } else if (characteristic .getUuid() @@ -209,7 +183,7 @@ public class TbsGattTest { assertThat(mTbsGatt.setBearerUriSchemesSupportedList((List<String>) value)) .isFalse(); } - Assert.assertEquals(valueString, characteristic.getStringValue(0)); + assertThat(characteristic.getStringValue(0)).isEqualTo(valueString); } else if (characteristic.getUuid().equals(TbsGatt.UUID_STATUS_FLAGS)) { Pair<Integer, Boolean> flagStatePair = (Pair<Integer, Boolean>) value; @@ -252,14 +226,15 @@ public class TbsGattTest { assertThat(mTbsGatt.setTerminationReason(indexReasonPair.first, indexReasonPair.second)) .isTrue(); assertThat(characteristic.getValue()) - .asList() - .containsExactly( - indexReasonPair.first.byteValue(), indexReasonPair.second.byteValue()) - .inOrder(); + .isEqualTo( + new byte[] { + indexReasonPair.first.byteValue(), + indexReasonPair.second.byteValue() + }); } else if (characteristic.getUuid().equals(TbsGatt.UUID_INCOMING_CALL)) { if (value == null) { assertThat(mTbsGatt.clearIncomingCall()).isTrue(); - Assert.assertEquals(0, characteristic.getValue().length); + assertThat(characteristic.getValue()).isEmpty(); } else { Pair<Integer, String> indexStrPair = (Pair<Integer, String>) value; assertThat(mTbsGatt.setIncomingCall(indexStrPair.first, indexStrPair.second)) @@ -274,7 +249,7 @@ public class TbsGattTest { } else if (characteristic.getUuid().equals(TbsGatt.UUID_CALL_FRIENDLY_NAME)) { if (value == null) { assertThat(mTbsGatt.clearFriendlyName()).isTrue(); - Assert.assertEquals(0, characteristic.getValue().length); + assertThat(characteristic.getValue()).isEmpty(); } else { Pair<Integer, String> indexNamePair = (Pair<Integer, String>) value; assertThat(mTbsGatt.setCallFriendlyName(indexNamePair.first, indexNamePair.second)) @@ -289,26 +264,26 @@ public class TbsGattTest { if (shouldNotify) { if (notifyWithValue) { - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged( eq(device), eq(characteristic), eq(false), any()); } else { - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged(eq(device), eq(characteristic), eq(false)); } } else { if (notifyWithValue) { - verify(mMockGattServer, times(0)) + verify(mGattServer, never()) .notifyCharacteristicChanged( eq(device), eq(characteristic), anyBoolean(), any()); } else { - verify(mMockGattServer, times(0)) + verify(mGattServer, never()) .notifyCharacteristicChanged(eq(device), eq(characteristic), anyBoolean()); } } if (clearGattMock) { - reset(mMockGattServer); + reset(mGattServer); } } @@ -576,9 +551,9 @@ public class TbsGattTest { .asList() .containsExactly(requestedOpcode, callIndex, result) .inOrder(); - verify(mMockGattServer, after(2000)) + verify(mGattServer, after(2000)) .notifyCharacteristicChanged(eq(mFirstDevice), eq(characteristic), eq(false)); - reset(mMockGattServer); + reset(mGattServer); callIndex = 0x02; @@ -589,7 +564,7 @@ public class TbsGattTest { .asList() .containsExactly(requestedOpcode, callIndex, result) .inOrder(); - verify(mMockGattServer, after(2000).times(0)) + verify(mGattServer, after(2000).never()) .notifyCharacteristicChanged(any(), any(), anyBoolean()); } @@ -696,7 +671,7 @@ public class TbsGattTest { mTbsGatt.mGattServerCallback.onCharacteristicWriteRequest( mFirstDevice, 1, characteristic, false, true, 0, value); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mFirstDevice), eq(1), @@ -705,7 +680,7 @@ public class TbsGattTest { aryEq(new byte[] {0x00, 0x0A})); // Verify the higher layer callback call - verify(mMockTbsGattCallback) + verify(mCallback) .onCallControlPointRequest(eq(mFirstDevice), eq(0x00), aryEq(new byte[] {0x0A})); } @@ -720,7 +695,7 @@ public class TbsGattTest { mTbsGatt.mGattServerCallback.onCharacteristicWriteRequest( mFirstDevice, 1, characteristic, false, true, 0, value); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mFirstDevice), eq(1), @@ -747,15 +722,15 @@ public class TbsGattTest { mTbsGatt.setInbandRingtoneFlag(mFirstDevice); mTbsGatt.setInbandRingtoneFlag(mFirstDevice); - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged( eq(mFirstDevice), eq(characteristic), eq(false), eq(valueBytes)); - reset(mMockGattServer); + reset(mGattServer); mTbsGatt.setInbandRingtoneFlag(mSecondDevice); mTbsGatt.setInbandRingtoneFlag(mSecondDevice); - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged( eq(mSecondDevice), eq(characteristic), eq(false), eq(valueBytes)); } @@ -774,18 +749,18 @@ public class TbsGattTest { valueBytes[1] = (byte) ((statusFlagValue >> 8) & 0xFF); mTbsGatt.setInbandRingtoneFlag(mFirstDevice); - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged( eq(mFirstDevice), eq(characteristic), eq(false), eq(valueBytes)); - reset(mMockGattServer); + reset(mGattServer); mTbsGatt.setInbandRingtoneFlag(mSecondDevice); - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged( eq(mSecondDevice), eq(characteristic), eq(false), eq(valueBytes)); - reset(mMockGattServer); + reset(mGattServer); // clear flag statusFlagValue = 0; @@ -794,14 +769,14 @@ public class TbsGattTest { mTbsGatt.clearInbandRingtoneFlag(mFirstDevice); mTbsGatt.clearInbandRingtoneFlag(mFirstDevice); - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged( eq(mFirstDevice), eq(characteristic), eq(false), eq(valueBytes)); - reset(mMockGattServer); + reset(mGattServer); mTbsGatt.clearInbandRingtoneFlag(mSecondDevice); mTbsGatt.clearInbandRingtoneFlag(mSecondDevice); - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged( eq(mSecondDevice), eq(characteristic), eq(false), eq(valueBytes)); } @@ -823,10 +798,10 @@ public class TbsGattTest { mTbsGatt.setSilentModeFlag(); mTbsGatt.setSilentModeFlag(); mTbsGatt.setSilentModeFlag(); - verify(mMockGattServer, times(2)) + verify(mGattServer, times(2)) .notifyCharacteristicChanged(any(), eq(characteristic), eq(false), eq(valueBytes)); - reset(mMockGattServer); + reset(mGattServer); statusFlagValue = TbsGatt.STATUS_FLAG_INBAND_RINGTONE_ENABLED @@ -836,17 +811,17 @@ public class TbsGattTest { mTbsGatt.setInbandRingtoneFlag(mFirstDevice); - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged( eq(mFirstDevice), eq(characteristic), eq(false), eq(valueBytes)); - reset(mMockGattServer); + reset(mGattServer); mTbsGatt.setInbandRingtoneFlag(mSecondDevice); - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged( eq(mSecondDevice), eq(characteristic), eq(false), eq(valueBytes)); - reset(mMockGattServer); + reset(mGattServer); statusFlagValue = TbsGatt.STATUS_FLAG_INBAND_RINGTONE_ENABLED; valueBytes[0] = (byte) (statusFlagValue & 0xFF); @@ -856,7 +831,7 @@ public class TbsGattTest { mTbsGatt.clearSilentModeFlag(); mTbsGatt.clearSilentModeFlag(); mTbsGatt.clearSilentModeFlag(); - verify(mMockGattServer, times(2)) + verify(mGattServer, times(2)) .notifyCharacteristicChanged(any(), eq(characteristic), eq(false), eq(valueBytes)); } @@ -868,7 +843,7 @@ public class TbsGattTest { mTbsGatt.mGattServerCallback.onCharacteristicReadRequest( mFirstDevice, 1, 0, characteristic); // Verify the higher layer callback call - verify(mMockTbsGattCallback).isInbandRingtoneEnabled(eq(mFirstDevice)); + verify(mCallback).isInbandRingtoneEnabled(eq(mFirstDevice)); } @Test @@ -882,31 +857,31 @@ public class TbsGattTest { // Check with no configuration mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mFirstDevice, 1, 0, descriptor); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mFirstDevice), eq(1), eq(BluetoothGatt.GATT_SUCCESS), eq(0), eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)); - reset(mMockGattServer); + reset(mGattServer); // Check with notifications enabled configureNotifications(mFirstDevice, characteristic, true); mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mFirstDevice, 1, 0, descriptor); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mFirstDevice), eq(1), eq(BluetoothGatt.GATT_SUCCESS), eq(0), eq(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)); - reset(mMockGattServer); + reset(mGattServer); // Check with notifications disabled configureNotifications(mFirstDevice, characteristic, false); mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mFirstDevice, 1, 0, descriptor); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mFirstDevice), eq(1), @@ -926,7 +901,7 @@ public class TbsGattTest { // Check with no configuration mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mFirstDevice, 1, 0, descriptor); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mFirstDevice), eq(1), @@ -935,79 +910,79 @@ public class TbsGattTest { eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)); mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mSecondDevice, 1, 0, descriptor); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mSecondDevice), eq(1), eq(BluetoothGatt.GATT_SUCCESS), eq(0), eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)); - reset(mMockGattServer); + reset(mGattServer); // Check with notifications enabled for first device configureNotifications(mFirstDevice, characteristic, true); verifySetValue(characteristic, 4, true, mFirstDevice, true); mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mFirstDevice, 1, 0, descriptor); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mFirstDevice), eq(1), eq(BluetoothGatt.GATT_SUCCESS), eq(0), eq(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)); - reset(mMockGattServer); + reset(mGattServer); // Check if second device is still not subscribed for notifications and will not get it verifySetValue(characteristic, 5, false, mSecondDevice, false); - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged(eq(mFirstDevice), eq(characteristic), eq(false)); mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mSecondDevice, 1, 0, descriptor); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mSecondDevice), eq(1), eq(BluetoothGatt.GATT_SUCCESS), eq(0), eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)); - reset(mMockGattServer); + reset(mGattServer); // Check with notifications enabled for first and second device configureNotifications(mSecondDevice, characteristic, true); verifySetValue(characteristic, 6, true, mSecondDevice, false); - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged(eq(mFirstDevice), eq(characteristic), eq(false)); mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mSecondDevice, 1, 0, descriptor); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mSecondDevice), eq(1), eq(BluetoothGatt.GATT_SUCCESS), eq(0), eq(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)); - reset(mMockGattServer); + reset(mGattServer); // Disable notification for first device, check if second will get notification configureNotifications(mFirstDevice, characteristic, false); verifySetValue(characteristic, 7, false, mFirstDevice, false); - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged(eq(mSecondDevice), eq(characteristic), eq(false)); mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mFirstDevice, 1, 0, descriptor); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mFirstDevice), eq(1), eq(BluetoothGatt.GATT_SUCCESS), eq(0), eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)); - reset(mMockGattServer); + reset(mGattServer); // Check with notifications disabled of both device configureNotifications(mSecondDevice, characteristic, false); verifySetValue(characteristic, 4, false, mFirstDevice, false); - verify(mMockGattServer, times(0)) + verify(mGattServer, never()) .notifyCharacteristicChanged(eq(mSecondDevice), eq(characteristic), eq(false)); mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mSecondDevice, 1, 0, descriptor); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mSecondDevice), eq(1), @@ -1024,13 +999,13 @@ public class TbsGattTest { getCharacteristic(TbsGatt.UUID_BEARER_TECHNOLOGY); doReturn(BluetoothDevice.ACCESS_REJECTED) - .when(mMockTbsService) + .when(mService) .getDeviceAuthorization(any(BluetoothDevice.class)); mTbsGatt.mGattServerCallback.onCharacteristicReadRequest( mFirstDevice, 1, 0, characteristic); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mFirstDevice), eq(1), @@ -1048,12 +1023,12 @@ public class TbsGattTest { configureNotifications(mFirstDevice, characteristic, true); configureNotifications(mSecondDevice, characteristic, true); - doReturn(mGattServiceCaptor.getValue()).when(mMockGattServer).getService(any(UUID.class)); + doReturn(mGattServiceCaptor.getValue()).when(mGattServer).getService(any(UUID.class)); assertThat(mGattServiceCaptor.getValue()).isNotNull(); // Leave it as unauthorized yet doReturn(BluetoothDevice.ACCESS_REJECTED) - .when(mMockTbsService) + .when(mService) .getDeviceAuthorization(any(BluetoothDevice.class)); int statusFlagValue = TbsGatt.STATUS_FLAG_SILENT_MODE_ENABLED; @@ -1069,18 +1044,18 @@ public class TbsGattTest { valueBytes[0] = (byte) (statusFlagValue & 0xFF); valueBytes[1] = (byte) ((statusFlagValue >> 8) & 0xFF); mTbsGatt.setSilentModeFlag(); - verify(mMockGattServer, times(0)) + verify(mGattServer, never()) .notifyCharacteristicChanged(any(), eq(characteristic), eq(false), eq(valueBytes)); // Expect a single notification for the just authorized device doReturn(BluetoothDevice.ACCESS_ALLOWED) - .when(mMockTbsService) + .when(mService) .getDeviceAuthorization(any(BluetoothDevice.class)); assertThat(mGattServiceCaptor.getValue()).isNotNull(); mTbsGatt.onDeviceAuthorizationSet(mFirstDevice); - verify(mMockGattServer, times(0)) + verify(mGattServer, never()) .notifyCharacteristicChanged(any(), eq(characteristic2), eq(false)); - verify(mMockGattServer) + verify(mGattServer) .notifyCharacteristicChanged(any(), eq(characteristic), eq(false), eq(valueBytes)); } @@ -1092,13 +1067,13 @@ public class TbsGattTest { getCharacteristic(TbsGatt.UUID_BEARER_TECHNOLOGY); doReturn(BluetoothDevice.ACCESS_UNKNOWN) - .when(mMockTbsService) + .when(mService) .getDeviceAuthorization(any(BluetoothDevice.class)); mTbsGatt.mGattServerCallback.onCharacteristicReadRequest( mFirstDevice, 1, 0, characteristic); - verify(mMockTbsService, times(0)).onDeviceUnauthorized(eq(mFirstDevice)); + verify(mService, never()).onDeviceUnauthorized(eq(mFirstDevice)); } @Test @@ -1109,7 +1084,7 @@ public class TbsGattTest { getCharacteristic(TbsGatt.UUID_CALL_CONTROL_POINT); doReturn(BluetoothDevice.ACCESS_REJECTED) - .when(mMockTbsService) + .when(mService) .getDeviceAuthorization(any(BluetoothDevice.class)); byte[] value = @@ -1120,7 +1095,7 @@ public class TbsGattTest { mTbsGatt.mGattServerCallback.onCharacteristicWriteRequest( mFirstDevice, 1, characteristic, false, true, 0, value); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mFirstDevice), eq(1), @@ -1137,7 +1112,7 @@ public class TbsGattTest { getCharacteristic(TbsGatt.UUID_CALL_CONTROL_POINT); doReturn(BluetoothDevice.ACCESS_UNKNOWN) - .when(mMockTbsService) + .when(mService) .getDeviceAuthorization(any(BluetoothDevice.class)); byte[] value = @@ -1148,7 +1123,7 @@ public class TbsGattTest { mTbsGatt.mGattServerCallback.onCharacteristicWriteRequest( mFirstDevice, 1, characteristic, false, true, 0, value); - verify(mMockTbsService).onDeviceUnauthorized(eq(mFirstDevice)); + verify(mService).onDeviceUnauthorized(eq(mFirstDevice)); } @Test @@ -1161,12 +1136,12 @@ public class TbsGattTest { assertThat(descriptor).isNotNull(); doReturn(BluetoothDevice.ACCESS_REJECTED) - .when(mMockTbsService) + .when(mService) .getDeviceAuthorization(any(BluetoothDevice.class)); mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mFirstDevice, 1, 0, descriptor); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mFirstDevice), eq(1), @@ -1185,12 +1160,12 @@ public class TbsGattTest { assertThat(descriptor).isNotNull(); doReturn(BluetoothDevice.ACCESS_UNKNOWN) - .when(mMockTbsService) + .when(mService) .getDeviceAuthorization(any(BluetoothDevice.class)); mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mFirstDevice, 1, 0, descriptor); - verify(mMockTbsService, times(0)).onDeviceUnauthorized(eq(mFirstDevice)); + verify(mService, never()).onDeviceUnauthorized(eq(mFirstDevice)); } @Test @@ -1203,7 +1178,7 @@ public class TbsGattTest { assertThat(descriptor).isNotNull(); doReturn(BluetoothDevice.ACCESS_REJECTED) - .when(mMockTbsService) + .when(mService) .getDeviceAuthorization(any(BluetoothDevice.class)); byte[] value = @@ -1214,7 +1189,7 @@ public class TbsGattTest { mTbsGatt.mGattServerCallback.onDescriptorWriteRequest( mFirstDevice, 1, descriptor, false, true, 0, value); - verify(mMockGattServer) + verify(mGattServer) .sendResponse( eq(mFirstDevice), eq(1), @@ -1233,7 +1208,7 @@ public class TbsGattTest { assertThat(descriptor).isNotNull(); doReturn(BluetoothDevice.ACCESS_UNKNOWN) - .when(mMockTbsService) + .when(mService) .getDeviceAuthorization(any(BluetoothDevice.class)); byte[] value = @@ -1244,6 +1219,6 @@ public class TbsGattTest { mTbsGatt.mGattServerCallback.onDescriptorWriteRequest( mFirstDevice, 1, descriptor, false, true, 0, value); - verify(mMockTbsService, times(0)).onDeviceUnauthorized(eq(mFirstDevice)); + verify(mService, never()).onDeviceUnauthorized(eq(mFirstDevice)); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGenericTest.java b/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGenericTest.java index d7ed9eee6c..345dd8a5ee 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGenericTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGenericTest.java @@ -33,7 +33,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.le_audio.LeAudioService; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -46,6 +45,7 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.UUID; @@ -53,33 +53,27 @@ import java.util.UUID; @MediumTest @RunWith(AndroidJUnit4.class) public class TbsGenericTest { - private BluetoothAdapter mAdapter; - private BluetoothDevice mCurrentDevice; - - private TbsGeneric mTbsGeneric; - @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock private TbsGatt mTbsGatt; + @Mock private IBluetoothLeCallControlCallback mIBluetoothLeCallControlCallback; + @Captor private ArgumentCaptor<Integer> mGtbsCcidCaptor; + @Captor private ArgumentCaptor<String> mGtbsUciCaptor; - private @Mock TbsGatt mTbsGatt; - private @Mock IBluetoothLeCallControlCallback mIBluetoothLeCallControlCallback; - private @Captor ArgumentCaptor<Integer> mGtbsCcidCaptor; - private @Captor ArgumentCaptor<String> mGtbsUciCaptor; - private @Captor ArgumentCaptor<List> mDefaultGtbsUriSchemesCaptor = - ArgumentCaptor.forClass(List.class); - private @Captor ArgumentCaptor<String> mDefaultGtbsProviderNameCaptor; - private @Captor ArgumentCaptor<Integer> mDefaultGtbsTechnologyCaptor; + @Captor + private ArgumentCaptor<List> mDefaultGtbsUriSchemesCaptor = ArgumentCaptor.forClass(List.class); - private @Captor ArgumentCaptor<TbsGatt.Callback> mTbsGattCallback; - private static Context mContext; - - @Before - public void setUp() throws Exception { + @Captor private ArgumentCaptor<String> mDefaultGtbsProviderNameCaptor; + @Captor private ArgumentCaptor<Integer> mDefaultGtbsTechnologyCaptor; + @Captor private ArgumentCaptor<TbsGatt.Callback> mTbsGattCallback; - mAdapter = BluetoothAdapter.getDefaultAdapter(); - mContext = getInstrumentation().getTargetContext(); + private final Context mContext = getInstrumentation().getTargetContext(); + private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); + private final BluetoothDevice mDevice = TestUtils.getTestDevice(mAdapter, 32); - getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(); + private TbsGeneric mTbsGeneric; + @Before + public void setUp() { // Default TbsGatt mock behavior doReturn(true) .when(mTbsGatt) @@ -106,15 +100,8 @@ public class TbsGenericTest { doReturn(true).when(mTbsGatt).clearIncomingCall(); doReturn(true).when(mTbsGatt).setCallFriendlyName(anyInt(), anyString()); doReturn(true).when(mTbsGatt).clearFriendlyName(); - doReturn(mContext).when(mTbsGatt).getContext(); - mTbsGeneric = new TbsGeneric(); - mTbsGeneric.init(mTbsGatt); - } - - @After - public void tearDown() throws Exception { - mTbsGeneric = null; + mTbsGeneric = new TbsGeneric(mContext, mTbsGatt); } private Integer prepareTestBearer() { @@ -150,14 +137,13 @@ public class TbsGenericTest { @Test public void testSetClearInbandRingtone() { - mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); prepareTestBearer(); - mTbsGeneric.setInbandRingtoneSupport(mCurrentDevice); - verify(mTbsGatt).setInbandRingtoneFlag(mCurrentDevice); + mTbsGeneric.setInbandRingtoneSupport(mDevice); + verify(mTbsGatt).setInbandRingtoneFlag(mDevice); - mTbsGeneric.clearInbandRingtoneSupport(mCurrentDevice); - verify(mTbsGatt).clearInbandRingtoneFlag(mCurrentDevice); + mTbsGeneric.clearInbandRingtoneSupport(mDevice); + verify(mTbsGatt).clearInbandRingtoneFlag(mDevice); } @Test @@ -212,7 +198,7 @@ public class TbsGenericTest { ArgumentCaptor<Map> currentCallsCaptor = ArgumentCaptor.forClass(Map.class); verify(mTbsGatt).setCallState(currentCallsCaptor.capture()); Map<Integer, TbsCall> capturedCurrentCalls = currentCallsCaptor.getValue(); - assertThat(capturedCurrentCalls.size()).isEqualTo(1); + assertThat(capturedCurrentCalls).hasSize(1); TbsCall capturedTbsCall = capturedCurrentCalls.get(capturedCallIndex); assertThat(capturedTbsCall).isNotNull(); assertThat(capturedTbsCall.getState()).isEqualTo(BluetoothLeCall.STATE_INCOMING); @@ -243,12 +229,12 @@ public class TbsGenericTest { ArgumentCaptor<Map> currentCallsCaptor = ArgumentCaptor.forClass(Map.class); verify(mTbsGatt).setCallState(currentCallsCaptor.capture()); Map<Integer, TbsCall> capturedCurrentCalls = currentCallsCaptor.getValue(); - assertThat(capturedCurrentCalls.size()).isEqualTo(1); + assertThat(capturedCurrentCalls).hasSize(1); TbsCall capturedTbsCall = capturedCurrentCalls.get(capturedCallIndex); assertThat(capturedTbsCall).isNotNull(); assertThat(capturedTbsCall.getState()).isEqualTo(BluetoothLeCall.STATE_INCOMING); - assertThat(capturedTbsCall.getUri()).isEqualTo(null); - assertThat(capturedTbsCall.getSafeUri()).isEqualTo(null); + assertThat(capturedTbsCall.getUri()).isNull(); + assertThat(capturedTbsCall.getSafeUri()).isNull(); assertThat(capturedTbsCall.getFlags()).isEqualTo(0); assertThat(capturedTbsCall.isIncoming()).isTrue(); assertThat(capturedTbsCall.getFriendlyName()).isEqualTo("aFriendlyCaller"); @@ -283,9 +269,9 @@ public class TbsGenericTest { ArgumentCaptor<Map> currentCallsCaptor = ArgumentCaptor.forClass(Map.class); verify(mTbsGatt).setCallState(currentCallsCaptor.capture()); Map<Integer, TbsCall> capturedCurrentCalls = currentCallsCaptor.getValue(); - assertThat(capturedCurrentCalls.size()).isEqualTo(0); + assertThat(capturedCurrentCalls).isEmpty(); verify(mTbsGatt).setBearerListCurrentCalls(currentCallsCaptor.capture()); - assertThat(capturedCurrentCalls.size()).isEqualTo(0); + assertThat(capturedCurrentCalls).isEmpty(); } @Test @@ -312,9 +298,9 @@ public class TbsGenericTest { ArgumentCaptor<Map> currentCallsCaptor = ArgumentCaptor.forClass(Map.class); verify(mTbsGatt).setCallState(currentCallsCaptor.capture()); Map<Integer, TbsCall> capturedCurrentCalls = currentCallsCaptor.getValue(); - assertThat(capturedCurrentCalls.size()).isEqualTo(1); + assertThat(capturedCurrentCalls).hasSize(1); verify(mTbsGatt).setBearerListCurrentCalls(currentCallsCaptor.capture()); - assertThat(capturedCurrentCalls.size()).isEqualTo(1); + assertThat(capturedCurrentCalls).hasSize(1); TbsCall capturedTbsCall = capturedCurrentCalls.get(capturedCallIndex); assertThat(capturedTbsCall).isNotNull(); assertThat(capturedTbsCall.getState()).isEqualTo(BluetoothLeCall.STATE_ACTIVE); @@ -355,14 +341,13 @@ public class TbsGenericTest { ArgumentCaptor<Map> currentCallsCaptor = ArgumentCaptor.forClass(Map.class); verify(mTbsGatt).setCallState(currentCallsCaptor.capture()); Map<Integer, TbsCall> capturedCurrentCalls = currentCallsCaptor.getValue(); - assertThat(capturedCurrentCalls.size()).isEqualTo(2); + assertThat(capturedCurrentCalls).hasSize(2); verify(mTbsGatt).setBearerListCurrentCalls(currentCallsCaptor.capture()); - assertThat(capturedCurrentCalls.size()).isEqualTo(2); + assertThat(capturedCurrentCalls).hasSize(2); } @Test public void testCallAccept() { - mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); Integer ccid = prepareTestBearer(); reset(mTbsGatt); @@ -384,7 +369,7 @@ public class TbsGenericTest { ArgumentCaptor<Map> currentCallsCaptor = ArgumentCaptor.forClass(Map.class); verify(mTbsGatt).setCallState(currentCallsCaptor.capture()); Map<Integer, TbsCall> capturedCurrentCalls = currentCallsCaptor.getValue(); - assertThat(capturedCurrentCalls.size()).isEqualTo(1); + assertThat(capturedCurrentCalls).hasSize(1); Integer callIndex = capturedCurrentCalls.entrySet().iterator().next().getKey(); reset(mTbsGatt); @@ -392,8 +377,7 @@ public class TbsGenericTest { args[0] = (byte) (callIndex & 0xFF); mTbsGattCallback .getValue() - .onCallControlPointRequest( - mCurrentDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT, args); + .onCallControlPointRequest(mDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT, args); ArgumentCaptor<Integer> requestIdCaptor = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor<ParcelUuid> callUuidCaptor = ArgumentCaptor.forClass(ParcelUuid.class); @@ -405,7 +389,7 @@ public class TbsGenericTest { } assertThat(callUuidCaptor.getValue().getUuid()).isEqualTo(callUuid); // Active device should be changed - verify(leAudioService).setActiveDevice(mCurrentDevice); + verify(leAudioService).setActiveDevice(mDevice); // Respond with requestComplete... mTbsGeneric.requestResult( @@ -415,7 +399,7 @@ public class TbsGenericTest { // ..and verify if GTBS control point is updated to notifier the peer about the result verify(mTbsGatt) .setCallControlPointResult( - eq(mCurrentDevice), + eq(mDevice), eq(TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT), eq(callIndex), eq(BluetoothLeCallControl.RESULT_SUCCESS)); @@ -423,7 +407,6 @@ public class TbsGenericTest { @Test public void testCallTerminate() { - mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); Integer ccid = prepareTestBearer(); reset(mTbsGatt); @@ -442,7 +425,7 @@ public class TbsGenericTest { ArgumentCaptor<Map> currentCallsCaptor = ArgumentCaptor.forClass(Map.class); verify(mTbsGatt).setCallState(currentCallsCaptor.capture()); Map<Integer, TbsCall> capturedCurrentCalls = currentCallsCaptor.getValue(); - assertThat(capturedCurrentCalls.size()).isEqualTo(1); + assertThat(capturedCurrentCalls).hasSize(1); Integer callIndex = capturedCurrentCalls.entrySet().iterator().next().getKey(); reset(mTbsGatt); @@ -451,7 +434,7 @@ public class TbsGenericTest { mTbsGattCallback .getValue() .onCallControlPointRequest( - mCurrentDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE, args); + mDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE, args); ArgumentCaptor<Integer> requestIdCaptor = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor<ParcelUuid> callUuidCaptor = ArgumentCaptor.forClass(ParcelUuid.class); @@ -471,7 +454,7 @@ public class TbsGenericTest { // ..and verify if GTBS control point is updated to notifier the peer about the result verify(mTbsGatt) .setCallControlPointResult( - eq(mCurrentDevice), + eq(mDevice), eq(TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE), eq(callIndex), eq(BluetoothLeCallControl.RESULT_SUCCESS)); @@ -479,7 +462,6 @@ public class TbsGenericTest { @Test public void testCallHold() { - mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); Integer ccid = prepareTestBearer(); reset(mTbsGatt); @@ -498,7 +480,7 @@ public class TbsGenericTest { ArgumentCaptor<Map> currentCallsCaptor = ArgumentCaptor.forClass(Map.class); verify(mTbsGatt).setCallState(currentCallsCaptor.capture()); Map<Integer, TbsCall> capturedCurrentCalls = currentCallsCaptor.getValue(); - assertThat(capturedCurrentCalls.size()).isEqualTo(1); + assertThat(capturedCurrentCalls).hasSize(1); Integer callIndex = capturedCurrentCalls.entrySet().iterator().next().getKey(); reset(mTbsGatt); @@ -507,7 +489,7 @@ public class TbsGenericTest { mTbsGattCallback .getValue() .onCallControlPointRequest( - mCurrentDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD, args); + mDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD, args); ArgumentCaptor<Integer> requestIdCaptor = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor<ParcelUuid> callUuidCaptor = ArgumentCaptor.forClass(ParcelUuid.class); @@ -527,7 +509,7 @@ public class TbsGenericTest { // ..and verify if GTBS control point is updated to notifier the peer about the result verify(mTbsGatt) .setCallControlPointResult( - eq(mCurrentDevice), + eq(mDevice), eq(TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD), eq(callIndex), eq(BluetoothLeCallControl.RESULT_SUCCESS)); @@ -535,7 +517,6 @@ public class TbsGenericTest { @Test public void testCallRetrieve() { - mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); Integer ccid = prepareTestBearer(); reset(mTbsGatt); @@ -554,7 +535,7 @@ public class TbsGenericTest { ArgumentCaptor<Map> currentCallsCaptor = ArgumentCaptor.forClass(Map.class); verify(mTbsGatt).setCallState(currentCallsCaptor.capture()); Map<Integer, TbsCall> capturedCurrentCalls = currentCallsCaptor.getValue(); - assertThat(capturedCurrentCalls.size()).isEqualTo(1); + assertThat(capturedCurrentCalls).hasSize(1); Integer callIndex = capturedCurrentCalls.entrySet().iterator().next().getKey(); reset(mTbsGatt); @@ -563,7 +544,7 @@ public class TbsGenericTest { mTbsGattCallback .getValue() .onCallControlPointRequest( - mCurrentDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE, args); + mDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE, args); ArgumentCaptor<Integer> requestIdCaptor = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor<ParcelUuid> callUuidCaptor = ArgumentCaptor.forClass(ParcelUuid.class); @@ -583,7 +564,7 @@ public class TbsGenericTest { // ..and verify if GTBS control point is updated to notifier the peer about the result verify(mTbsGatt) .setCallControlPointResult( - eq(mCurrentDevice), + eq(mDevice), eq(TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE), eq(callIndex), eq(BluetoothLeCallControl.RESULT_SUCCESS)); @@ -591,7 +572,6 @@ public class TbsGenericTest { @Test public void testCallOriginate() { - mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); Integer ccid = prepareTestBearer(); reset(mTbsGatt); @@ -603,9 +583,7 @@ public class TbsGenericTest { mTbsGattCallback .getValue() .onCallControlPointRequest( - mCurrentDevice, - TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE, - uri.getBytes()); + mDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE, uri.getBytes()); ArgumentCaptor<Integer> requestIdCaptor = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor<ParcelUuid> callUuidCaptor = ArgumentCaptor.forClass(ParcelUuid.class); @@ -617,7 +595,7 @@ public class TbsGenericTest { } // Active device should be changed - verify(leAudioService).setActiveDevice(mCurrentDevice); + verify(leAudioService).setActiveDevice(mDevice); // Respond with requestComplete... mTbsGeneric.requestResult( @@ -634,7 +612,7 @@ public class TbsGenericTest { // ..and verify if GTBS control point is updated to notifier the peer about the result verify(mTbsGatt) .setCallControlPointResult( - eq(mCurrentDevice), + eq(mDevice), eq(TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE), anyInt(), eq(BluetoothLeCallControl.RESULT_SUCCESS)); @@ -642,7 +620,6 @@ public class TbsGenericTest { @Test public void testCallJoin() { - mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); Integer ccid = prepareTestBearer(); reset(mTbsGatt); @@ -668,7 +645,7 @@ public class TbsGenericTest { ArgumentCaptor<Map> currentCallsCaptor = ArgumentCaptor.forClass(Map.class); verify(mTbsGatt).setCallState(currentCallsCaptor.capture()); Map<Integer, TbsCall> capturedCurrentCalls = currentCallsCaptor.getValue(); - assertThat(capturedCurrentCalls.size()).isEqualTo(2); + assertThat(capturedCurrentCalls).hasSize(2); reset(mTbsGatt); byte args[] = new byte[capturedCurrentCalls.size()]; @@ -678,8 +655,7 @@ public class TbsGenericTest { } mTbsGattCallback .getValue() - .onCallControlPointRequest( - mCurrentDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_JOIN, args); + .onCallControlPointRequest(mDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_JOIN, args); ArgumentCaptor<Integer> requestIdCaptor = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor<List<ParcelUuid>> callUuidCaptor = ArgumentCaptor.forClass(List.class); @@ -690,7 +666,7 @@ public class TbsGenericTest { throw e.rethrowFromSystemServer(); } List<ParcelUuid> callParcelUuids = callUuidCaptor.getValue(); - assertThat(callParcelUuids.size()).isEqualTo(2); + assertThat(callParcelUuids).hasSize(2); for (ParcelUuid callParcelUuid : callParcelUuids) { assertThat(callUuids.contains(callParcelUuid.getUuid())).isEqualTo(true); } @@ -703,9 +679,85 @@ public class TbsGenericTest { // ..and verify if GTBS control point is updated to notifier the peer about the result verify(mTbsGatt) .setCallControlPointResult( - eq(mCurrentDevice), + eq(mDevice), eq(TbsGatt.CALL_CONTROL_POINT_OPCODE_JOIN), anyInt(), eq(BluetoothLeCallControl.RESULT_SUCCESS)); } + + @Test + public void testCallOperationsBlockedForBroadcastReceiver() { + Integer ccid = prepareTestBearer(); + reset(mTbsGatt); + + LeAudioService leAudioService = mock(LeAudioService.class); + mTbsGeneric.setLeAudioServiceForTesting(leAudioService); + + // Prepare the incoming call + UUID callUuid = UUID.randomUUID(); + List<BluetoothLeCall> tbsCalls = new ArrayList<>(); + tbsCalls.add( + new BluetoothLeCall( + callUuid, + "tel:987654321", + "aFriendlyCaller", + BluetoothLeCall.STATE_INCOMING, + 0)); + mTbsGeneric.currentCallsList(ccid, tbsCalls); + + ArgumentCaptor<Map> currentCallsCaptor = ArgumentCaptor.forClass(Map.class); + verify(mTbsGatt).setCallState(currentCallsCaptor.capture()); + Map<Integer, TbsCall> capturedCurrentCalls = currentCallsCaptor.getValue(); + assertThat(capturedCurrentCalls.size()).isEqualTo(1); + Integer callIndex = capturedCurrentCalls.entrySet().iterator().next().getKey(); + reset(mTbsGatt); + + doReturn(new HashSet<>(Arrays.asList(mDevice))) + .when(leAudioService) + .getLocalBroadcastReceivers(); + + doReturn(false).when(leAudioService).isPrimaryDevice(mDevice); + + // Verify call accept + byte args[] = new byte[1]; + args[0] = (byte) (callIndex & 0xFF); + mTbsGattCallback + .getValue() + .onCallControlPointRequest( + mDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT, args); + + // Active device should not be changed + verify(leAudioService, never()).setActiveDevice(mDevice); + // Verify if GTBS control point is updated to notify the peer about the result + verify(mTbsGatt) + .setCallControlPointResult( + eq(mDevice), + eq(TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT), + eq(0), + eq(TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE)); + + // Verify call terminate + tbsCalls.clear(); + tbsCalls.add( + new BluetoothLeCall( + callUuid, + "tel:987654321", + "aFriendlyCaller", + BluetoothLeCall.STATE_ACTIVE, + 0)); + mTbsGeneric.currentCallsList(ccid, tbsCalls); + + mTbsGattCallback + .getValue() + .onCallControlPointRequest( + mDevice, TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE, args); + + // Verify if GTBS control point is updated to notify the peer about the result + verify(mTbsGatt) + .setCallControlPointResult( + eq(mDevice), + eq(TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE), + eq(0), + eq(TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE)); + } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java index c0f275b149..1a908912f4 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java @@ -51,7 +51,6 @@ import com.android.bluetooth.flags.Flags; import com.android.bluetooth.hfp.BluetoothHeadsetProxy; import com.android.bluetooth.tbs.BluetoothLeCallControlProxy; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -172,7 +171,7 @@ public class BluetoothInCallServiceTest { doReturn(fakePhoneAccount).when(mMockCallInfo).getBestPhoneAccount(); String networkOperator = mBluetoothInCallService.getNetworkOperator(); - Assert.assertEquals(networkOperator, "label0"); + assertThat(networkOperator).isEqualTo("label0"); } @Test @@ -181,7 +180,7 @@ public class BluetoothInCallServiceTest { doReturn(fakeOperator).when(mMockTelephonyManager).getNetworkOperatorName(); String networkOperator = mBluetoothInCallService.getNetworkOperator(); - Assert.assertEquals(networkOperator, fakeOperator); + assertThat(networkOperator).isEqualTo(fakeOperator); } @Test @@ -190,7 +189,7 @@ public class BluetoothInCallServiceTest { doReturn(fakePhoneAccount).when(mMockCallInfo).getBestPhoneAccount(); String subscriberNumber = mBluetoothInCallService.getSubscriberNumber(); - Assert.assertEquals(subscriberNumber, TEST_ACCOUNT_ADDRESS + TEST_ACCOUNT_INDEX); + assertThat(subscriberNumber).isEqualTo(TEST_ACCOUNT_ADDRESS + TEST_ACCOUNT_INDEX); } @Test @@ -199,7 +198,7 @@ public class BluetoothInCallServiceTest { doReturn(fakeNumber).when(mMockTelephonyManager).getLine1Number(); String subscriberNumber = mBluetoothInCallService.getSubscriberNumber(); - Assert.assertEquals(subscriberNumber, fakeNumber); + assertThat(subscriberNumber).isEqualTo(fakeNumber); } @Test @@ -253,12 +252,12 @@ public class BluetoothInCallServiceTest { BluetoothCallQualityReport report = (BluetoothCallQualityReport) bundle.get(BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT); - Assert.assertEquals(10, report.getSentTimestampMillis()); - Assert.assertEquals(20, report.getRssiDbm()); - Assert.assertEquals(30, report.getSnrDb()); - Assert.assertEquals(40, report.getRetransmittedPacketsCount()); - Assert.assertEquals(50, report.getPacketsNotReceivedCount()); - Assert.assertEquals(60, report.getNegativeAcknowledgementCount()); + assertThat(report.getSentTimestampMillis()).isEqualTo(10); + assertThat(report.getRssiDbm()).isEqualTo(20); + assertThat(report.getSnrDb()).isEqualTo(30); + assertThat(report.getRetransmittedPacketsCount()).isEqualTo(40); + assertThat(report.getPacketsNotReceivedCount()).isEqualTo(50); + assertThat(report.getNegativeAcknowledgementCount()).isEqualTo(60); } @Test @@ -907,7 +906,7 @@ public class BluetoothInCallServiceTest { // parent call arrived, but children have not, then do inference on children calls.add(conferenceCall); - Assert.assertEquals(calls.size(), 1); + assertThat(calls).hasSize(1); mBluetoothInCallService.onCallAdded(conferenceCall); clearInvocations(mMockBluetoothHeadset); @@ -946,7 +945,7 @@ public class BluetoothInCallServiceTest { mBluetoothInCallService.onCallRemoved(holdingCall, true); calls.remove(activeCall); calls.remove(holdingCall); - Assert.assertEquals(calls.size(), 1); + assertThat(calls).hasSize(1); clearInvocations(mMockBluetoothHeadset); mBluetoothInCallService.listCurrentCalls(); @@ -1042,7 +1041,7 @@ public class BluetoothInCallServiceTest { // parent call arrived, but children have not, then do inference on children calls.add(conferenceCall); - Assert.assertEquals(calls.size(), 1); + assertThat(calls).hasSize(1); mBluetoothInCallService.onCallAdded(conferenceCall); clearInvocations(mMockBluetoothHeadset); @@ -1080,7 +1079,7 @@ public class BluetoothInCallServiceTest { mBluetoothInCallService.onCallRemoved(activeCall_1, true); doReturn(false).when(activeCall_1).isConference(); calls.remove(activeCall_1); - Assert.assertEquals(calls.size(), 2); + assertThat(calls).hasSize(2); // Call 2 removed from conf doReturn(cause).when(activeCall_2).getDisconnectCause(); @@ -1690,134 +1689,116 @@ public class BluetoothInCallServiceTest { doReturn(TelephonyManager.NETWORK_TYPE_GSM) .when(mMockTelephonyManager) .getDataNetworkType(); - Assert.assertEquals( - mBluetoothInCallService.getBearerTechnology(), - BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM); + assertThat(mBluetoothInCallService.getBearerTechnology()) + .isEqualTo(BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM); doReturn(TelephonyManager.NETWORK_TYPE_GPRS) .when(mMockTelephonyManager) .getDataNetworkType(); - Assert.assertEquals( - mBluetoothInCallService.getBearerTechnology(), - BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_2G); + assertThat(mBluetoothInCallService.getBearerTechnology()) + .isEqualTo(BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_2G); doReturn(TelephonyManager.NETWORK_TYPE_EVDO_B) .when(mMockTelephonyManager) .getDataNetworkType(); - Assert.assertEquals( - mBluetoothInCallService.getBearerTechnology(), - BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_3G); + assertThat(mBluetoothInCallService.getBearerTechnology()) + .isEqualTo(BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_3G); doReturn(TelephonyManager.NETWORK_TYPE_TD_SCDMA) .when(mMockTelephonyManager) .getDataNetworkType(); - Assert.assertEquals( - mBluetoothInCallService.getBearerTechnology(), - BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_WCDMA); + assertThat(mBluetoothInCallService.getBearerTechnology()) + .isEqualTo(BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_WCDMA); doReturn(TelephonyManager.NETWORK_TYPE_LTE) .when(mMockTelephonyManager) .getDataNetworkType(); - Assert.assertEquals( - mBluetoothInCallService.getBearerTechnology(), - BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_LTE); + assertThat(mBluetoothInCallService.getBearerTechnology()) + .isEqualTo(BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_LTE); doReturn(TelephonyManager.NETWORK_TYPE_1xRTT) .when(mMockTelephonyManager) .getDataNetworkType(); - Assert.assertEquals( - mBluetoothInCallService.getBearerTechnology(), - BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_CDMA); + assertThat(mBluetoothInCallService.getBearerTechnology()) + .isEqualTo(BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_CDMA); doReturn(TelephonyManager.NETWORK_TYPE_HSPAP) .when(mMockTelephonyManager) .getDataNetworkType(); - Assert.assertEquals( - mBluetoothInCallService.getBearerTechnology(), - BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_4G); + assertThat(mBluetoothInCallService.getBearerTechnology()) + .isEqualTo(BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_4G); doReturn(TelephonyManager.NETWORK_TYPE_IWLAN) .when(mMockTelephonyManager) .getDataNetworkType(); - Assert.assertEquals( - mBluetoothInCallService.getBearerTechnology(), - BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_WIFI); + assertThat(mBluetoothInCallService.getBearerTechnology()) + .isEqualTo(BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_WIFI); doReturn(TelephonyManager.NETWORK_TYPE_NR).when(mMockTelephonyManager).getDataNetworkType(); - Assert.assertEquals( - mBluetoothInCallService.getBearerTechnology(), - BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_5G); + assertThat(mBluetoothInCallService.getBearerTechnology()) + .isEqualTo(BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_5G); doReturn(TelephonyManager.NETWORK_TYPE_LTE_CA) .when(mMockTelephonyManager) .getDataNetworkType(); - Assert.assertEquals( - mBluetoothInCallService.getBearerTechnology(), - BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM); + assertThat(mBluetoothInCallService.getBearerTechnology()) + .isEqualTo(BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM); } @Test public void getTbsTerminationReason() { BluetoothCall call = getMockCall(UUID.randomUUID()); - Assert.assertEquals( - mBluetoothInCallService.getTbsTerminationReason(call), - BluetoothLeCallControl.TERMINATION_REASON_FAIL); + assertThat(mBluetoothInCallService.getTbsTerminationReason(call)) + .isEqualTo(BluetoothLeCallControl.TERMINATION_REASON_FAIL); DisconnectCause cause = new DisconnectCause(DisconnectCause.BUSY, null, null, null, 1); doReturn(cause).when(call).getDisconnectCause(); - Assert.assertEquals( - mBluetoothInCallService.getTbsTerminationReason(call), - BluetoothLeCallControl.TERMINATION_REASON_LINE_BUSY); + assertThat(mBluetoothInCallService.getTbsTerminationReason(call)) + .isEqualTo(BluetoothLeCallControl.TERMINATION_REASON_LINE_BUSY); cause = new DisconnectCause(DisconnectCause.REJECTED, null, null, null, 1); doReturn(cause).when(call).getDisconnectCause(); - Assert.assertEquals( - mBluetoothInCallService.getTbsTerminationReason(call), - BluetoothLeCallControl.TERMINATION_REASON_REMOTE_HANGUP); + assertThat(mBluetoothInCallService.getTbsTerminationReason(call)) + .isEqualTo(BluetoothLeCallControl.TERMINATION_REASON_REMOTE_HANGUP); cause = new DisconnectCause(DisconnectCause.LOCAL, null, null, null, 1); doReturn(cause).when(call).getDisconnectCause(); mBluetoothInCallService.mIsTerminatedByClient = false; - Assert.assertEquals( - mBluetoothInCallService.getTbsTerminationReason(call), - BluetoothLeCallControl.TERMINATION_REASON_SERVER_HANGUP); + assertThat(mBluetoothInCallService.getTbsTerminationReason(call)) + .isEqualTo(BluetoothLeCallControl.TERMINATION_REASON_SERVER_HANGUP); cause = new DisconnectCause(DisconnectCause.LOCAL, null, null, null, 1); doReturn(cause).when(call).getDisconnectCause(); mBluetoothInCallService.mIsTerminatedByClient = true; - Assert.assertEquals( - mBluetoothInCallService.getTbsTerminationReason(call), - BluetoothLeCallControl.TERMINATION_REASON_CLIENT_HANGUP); + assertThat(mBluetoothInCallService.getTbsTerminationReason(call)) + .isEqualTo(BluetoothLeCallControl.TERMINATION_REASON_CLIENT_HANGUP); cause = new DisconnectCause(DisconnectCause.ERROR, null, null, null, 1); doReturn(cause).when(call).getDisconnectCause(); - Assert.assertEquals( - mBluetoothInCallService.getTbsTerminationReason(call), - BluetoothLeCallControl.TERMINATION_REASON_NETWORK_CONGESTION); + assertThat(mBluetoothInCallService.getTbsTerminationReason(call)) + .isEqualTo(BluetoothLeCallControl.TERMINATION_REASON_NETWORK_CONGESTION); cause = new DisconnectCause( DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED, null, null, null, 1); doReturn(cause).when(call).getDisconnectCause(); - Assert.assertEquals( - mBluetoothInCallService.getTbsTerminationReason(call), - BluetoothLeCallControl.TERMINATION_REASON_INVALID_URI); + assertThat(mBluetoothInCallService.getTbsTerminationReason(call)) + .isEqualTo(BluetoothLeCallControl.TERMINATION_REASON_INVALID_URI); cause = new DisconnectCause(DisconnectCause.ERROR, null, null, null, 1); doReturn(cause).when(call).getDisconnectCause(); - Assert.assertEquals( - mBluetoothInCallService.getTbsTerminationReason(call), - BluetoothLeCallControl.TERMINATION_REASON_NETWORK_CONGESTION); + assertThat(mBluetoothInCallService.getTbsTerminationReason(call)) + .isEqualTo(BluetoothLeCallControl.TERMINATION_REASON_NETWORK_CONGESTION); } @Test public void onDestroy() { - assertThat(mBluetoothInCallService.mOnCreateCalled).isTrue(); + assertThat(BluetoothInCallService.getInstance()).isNotNull(); mBluetoothInCallService.onDestroy(); - assertThat(mBluetoothInCallService.mOnCreateCalled).isFalse(); + assertThat(BluetoothInCallService.getInstance()).isNull(); } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlOffsetDescriptorTest.java b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlOffsetDescriptorTest.java index 3ef5c767f6..4f7389b8f8 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlOffsetDescriptorTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlOffsetDescriptorTest.java @@ -22,25 +22,12 @@ import static org.mockito.Mockito.*; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; -import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @MediumTest @RunWith(AndroidJUnit4.class) public class VolumeControlOffsetDescriptorTest { - - @Before - public void setUp() throws Exception { - // placeholder - } - - @After - public void tearDown() throws Exception { - // placeholder - } - @Test public void testVolumeControlOffsetDescriptorInvalidIdOperations() { VolumeControlOffsetDescriptor descriptor = new VolumeControlOffsetDescriptor(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java index 3e912701f6..94e1db83b1 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java @@ -58,6 +58,7 @@ import android.media.AudioManager; import android.os.Binder; import android.os.ParcelUuid; import android.os.test.TestLooper; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; @@ -76,7 +77,6 @@ import com.android.bluetooth.le_audio.LeAudioService; import org.hamcrest.Matcher; import org.hamcrest.core.AllOf; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -461,7 +461,8 @@ public class VolumeControlServiceTest { } @Test - public void volumeCache() { + @DisableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS) + public void volumeCacheDeprecated() { int groupId = 1; int volume = 6; @@ -478,6 +479,56 @@ public class VolumeControlServiceTest { } @Test + @EnableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS) + public void volumeCache() { + int groupId = 1; + int groupVolume = 6; + int devOneVolume = 20; + int devTwoVolume = 30; + + // Both devices are in the same group + when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId); + when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId); + when(mCsipService.getGroupDevicesOrdered(groupId)) + .thenReturn(Arrays.asList(mDevice, mDeviceTwo)); + + assertThat(mService.getGroupVolume(groupId)).isEqualTo(VOLUME_CONTROL_UNKNOWN_VOLUME); + assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(VOLUME_CONTROL_UNKNOWN_VOLUME); + assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(VOLUME_CONTROL_UNKNOWN_VOLUME); + + // Set group volume + mService.setGroupVolume(groupId, groupVolume); + assertThat(mService.getGroupVolume(groupId)).isEqualTo(groupVolume); + assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(groupVolume); + assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(groupVolume); + + // Send autonomous volume change. + int autonomousVolume = 10; + generateVolumeStateChanged(null, groupId, autonomousVolume, 0, false, true); + assertThat(mService.getGroupVolume(groupId)).isEqualTo(autonomousVolume); + assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(autonomousVolume); + assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(autonomousVolume); + + // Set first device volume + mService.setDeviceVolume(mDevice, devOneVolume, false); + assertThat(mService.getGroupVolume(groupId)).isEqualTo(autonomousVolume); + assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(devOneVolume); + assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(autonomousVolume); + + // Set second device volume + mService.setDeviceVolume(mDeviceTwo, devTwoVolume, false); + assertThat(mService.getGroupVolume(groupId)).isEqualTo(autonomousVolume); + assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(devOneVolume); + assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(devTwoVolume); + + // Set group volume again + mService.setGroupVolume(groupId, groupVolume); + assertThat(mService.getGroupVolume(groupId)).isEqualTo(groupVolume); + assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(groupVolume); + assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(groupVolume); + } + + @Test public void activeGroupChange() { int groupId_1 = 1; int volume_groupId_1 = 6; @@ -509,7 +560,8 @@ public class VolumeControlServiceTest { } @Test - public void muteCache() { + @DisableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS) + public void muteCacheDeprecated() { int groupId = 1; int volume = 6; @@ -531,6 +583,61 @@ public class VolumeControlServiceTest { assertThat(mService.getGroupMute(groupId)).isFalse(); } + @Test + @EnableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS) + public void muteCache() { + int groupId = 1; + int groupVolume = 6; + + // Both devices are in the same group + when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId); + when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId); + when(mCsipService.getGroupDevicesOrdered(groupId)) + .thenReturn(Arrays.asList(mDevice, mDeviceTwo)); + + assertThat(mService.getGroupMute(groupId)).isFalse(); + assertThat(mService.getMute(mDevice)).isFalse(); + assertThat(mService.getMute(mDeviceTwo)).isFalse(); + + // Send autonomous volume change + generateVolumeStateChanged(null, groupId, groupVolume, 0, false, true); + + // Mute + mService.muteGroup(groupId); + assertThat(mService.getGroupMute(groupId)).isTrue(); + assertThat(mService.getMute(mDevice)).isTrue(); + assertThat(mService.getMute(mDeviceTwo)).isTrue(); + + // Make sure the volume is kept even when muted + assertThat(mService.getGroupVolume(groupId)).isEqualTo(groupVolume); + assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(groupVolume); + assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(groupVolume); + + // Send autonomous unmute + generateVolumeStateChanged(null, groupId, groupVolume, 0, false, true); + assertThat(mService.getGroupMute(groupId)).isFalse(); + assertThat(mService.getMute(mDevice)).isFalse(); + assertThat(mService.getMute(mDeviceTwo)).isFalse(); + + // Mute first device + mService.mute(mDevice); + assertThat(mService.getGroupMute(groupId)).isFalse(); + assertThat(mService.getMute(mDevice)).isTrue(); + assertThat(mService.getMute(mDeviceTwo)).isFalse(); + + // Mute second device + mService.mute(mDeviceTwo); + assertThat(mService.getGroupMute(groupId)).isFalse(); + assertThat(mService.getMute(mDevice)).isTrue(); + assertThat(mService.getMute(mDeviceTwo)).isTrue(); + + // Unmute group should unmute devices even if group is unmuted + mService.unmuteGroup(groupId); + assertThat(mService.getGroupMute(groupId)).isFalse(); + assertThat(mService.getMute(mDevice)).isFalse(); + assertThat(mService.getMute(mDeviceTwo)).isFalse(); + } + /** Test Volume Control with muted stream. */ @Test public void volumeChangeWhileMuted() { @@ -612,6 +719,14 @@ public class VolumeControlServiceTest { InOrder inOrderAudio = inOrder(mAudioManager); inOrderAudio.verify(mAudioManager, never()).setStreamVolume(anyInt(), anyInt(), anyInt()); + InOrder inOrderNative = inOrder(mNativeInterface); + if (Flags.vcpDeviceVolumeApiImprovements()) { + // AF always call setVolume via LeAudioService at first connected remote from group + mService.setGroupVolume(groupId, 123); + // It should be ignored and not set to native + inOrderNative.verify(mNativeInterface, never()).setGroupVolume(anyInt(), anyInt()); + } + // Make device Active now. This will trigger setting volume to AF when(mLeAudioService.getActiveGroupId()).thenReturn(groupId); mService.setGroupActive(groupId, true); @@ -634,7 +749,11 @@ public class VolumeControlServiceTest { initialAutonomousFlag); inOrderAudio.verify(mAudioManager, never()).setStreamVolume(anyInt(), anyInt(), anyInt()); - verify(mNativeInterface).setGroupVolume(eq(groupId), eq(volumeDevice)); + if (Flags.vcpDeviceVolumeApiImprovements()) { + inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(volumeDevice)); + } else { + inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(volumeDevice)); + } } private void testConnectedDeviceWithResetFlag( @@ -676,6 +795,10 @@ public class VolumeControlServiceTest { InOrder inOrderAudio = inOrder(mAudioManager); inOrderAudio.verify(mAudioManager, never()).setStreamVolume(anyInt(), anyInt(), anyInt()); InOrder inOrderNative = inOrder(mNativeInterface); + if (Flags.vcpDeviceVolumeApiImprovements()) { + // AF always call setVolume via LeAudioService at first connected remote from group + mService.setGroupVolume(groupId, expectedAfVol); + } inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(expectedAfVol)); // Make device Active now. This will trigger setting volume to AF @@ -698,7 +821,11 @@ public class VolumeControlServiceTest { initialAutonomousFlag); inOrderAudio.verify(mAudioManager, never()).setStreamVolume(anyInt(), anyInt(), anyInt()); - inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(expectedAfVol)); + if (Flags.vcpDeviceVolumeApiImprovements()) { + inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(expectedAfVol)); + } else { + inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(expectedAfVol)); + } } /** Test if phone will set volume which is read from the buds */ @@ -742,8 +869,12 @@ public class VolumeControlServiceTest { assertThat(mService.getDevices()).contains(mDeviceTwo); generateVolumeStateChanged(mDeviceTwo, LE_AUDIO_GROUP_ID_INVALID, volume_2, 0, false, true); - inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume)); - inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(groupVolume)); + if (Flags.vcpDeviceVolumeApiImprovements()) { + inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume)); + } else { + inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume)); + inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(groupVolume)); + } } /** @@ -817,9 +948,14 @@ public class VolumeControlServiceTest { generateVolumeStateChanged(mDeviceTwo, LE_AUDIO_GROUP_ID_INVALID, volume_2, 0, false, true); // Check if new device was muted - inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(volume)); - inOrderNative.verify(mNativeInterface).mute(eq(mDeviceTwo)); - inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(volume)); + if (Flags.vcpDeviceVolumeApiImprovements()) { + inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(volume)); + inOrderNative.verify(mNativeInterface).mute(eq(mDeviceTwo)); + } else { + inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(volume)); + inOrderNative.verify(mNativeInterface).mute(eq(mDeviceTwo)); + inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(volume)); + } } /** @@ -988,13 +1124,13 @@ public class VolumeControlServiceTest { mBinder.setDeviceVolume(mDevice, deviceOneVolume, false, mAttributionSource); inOrderNative.verify(mNativeInterface).setVolume(mDevice, deviceOneVolume); assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(deviceOneVolume); - Assert.assertNotEquals(deviceOneVolume, mService.getDeviceVolume(mDeviceTwo)); + assertThat(mService.getDeviceVolume(mDeviceTwo)).isNotEqualTo(deviceOneVolume); inOrderNative.verify(mNativeInterface, never()).setGroupVolume(anyInt(), anyInt()); mBinder.setDeviceVolume(mDeviceTwo, deviceTwoVolume, false, mAttributionSource); inOrderNative.verify(mNativeInterface).setVolume(mDeviceTwo, deviceTwoVolume); assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(deviceTwoVolume); - Assert.assertNotEquals(deviceTwoVolume, mService.getDeviceVolume(mDevice)); + assertThat(mService.getDeviceVolume(mDevice)).isNotEqualTo(deviceTwoVolume); inOrderNative.verify(mNativeInterface, never()).setGroupVolume(anyInt(), anyInt()); } @@ -1064,8 +1200,12 @@ public class VolumeControlServiceTest { generateVolumeStateChanged( mDeviceTwo, LE_AUDIO_GROUP_ID_INVALID, groupVolume, 0, false, true); - inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume)); - inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(groupVolume)); + if (Flags.vcpDeviceVolumeApiImprovements()) { + inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume)); + } else { + inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume)); + inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(groupVolume)); + } // Generate events for both devices generateDeviceOffsetChangedMessageFromNative(mDevice, 1, 100); diff --git a/android/leaudio/app/src/androidTest/java/com/android/bluetooth/leaudio/ExampleInstrumentedTest.java b/android/leaudio/app/src/androidTest/java/com/android/bluetooth/leaudio/ExampleInstrumentedTest.java deleted file mode 100644 index 48e81bb636..0000000000 --- a/android/leaudio/app/src/androidTest/java/com/android/bluetooth/leaudio/ExampleInstrumentedTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.android.bluetooth.leaudio; - -import static org.junit.Assert.assertEquals; - -import android.content.Context; - -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("com.android.bluetooth.leaudio", appContext.getPackageName()); - } -} diff --git a/android/leaudio/app/src/test/java/pl/codecoup/ehima/leaudio/ExampleUnitTest.java b/android/leaudio/app/src/test/java/pl/codecoup/ehima/leaudio/ExampleUnitTest.java deleted file mode 100644 index 53e6ef47b3..0000000000 --- a/android/leaudio/app/src/test/java/pl/codecoup/ehima/leaudio/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.android.bluetooth.leaudio; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} diff --git a/android/pandora/gen_cov.py b/android/pandora/gen_cov.py index b3d85b32d6..ba62530633 100755 --- a/android/pandora/gen_cov.py +++ b/android/pandora/gen_cov.py @@ -310,8 +310,8 @@ if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument( '--apex-name', - default='com.android.btservices', - help='bluetooth apex name. Default: com.android.btservices') + default='com.android.bt', + help='bluetooth apex name. Default: com.android.bt') parser.add_argument( '--java', action='store_true', help='generate Java coverage') parser.add_argument( diff --git a/android/pandora/server/configs/PtsBotTestMts.xml b/android/pandora/server/configs/PtsBotTestMts.xml index 168eee7dea..9785a39c18 100644 --- a/android/pandora/server/configs/PtsBotTestMts.xml +++ b/android/pandora/server/configs/PtsBotTestMts.xml @@ -31,13 +31,16 @@ <!-- TODO(b/267785204): Import python dependencies --> <option name="dep-module" value="grpcio" /> <option name="dep-module" value="protobuf==3.20.1" /> - <option name="dep-module" value="scipy" /> + + <!-- Re-enable when A2DP audio streaming tests are active, disabling to speed up atest runtime + (installation takes roughly 30s each time, never cached) --> + <!-- <option name="dep-module" value="scipy" /> --> </target_preparer> <test class="com.android.tradefed.testtype.pandora.PtsBotTest" > <!-- Creates a randomized temp dir for pts-bot binaries and avoid conflicts when running multiple pts-bot on the same machine --> - <option name="create-bin-temp-dir" value="true"/> + <!-- <option name="create-bin-temp-dir" value="true"/> --> <!-- mmi2grpc is contained inside pts-bot-mts folder --> <option name="mmi2grpc" value="pts-bot-mts" /> <option name="tests-config-file" value="pts_bot_tests_config.json" /> @@ -53,6 +56,7 @@ <option name="profile" value="BNEP" /> <option name="profile" value="GAP" /> <option name="profile" value="GATT" /> + <option name="profile" value="HAP" /> <option name="profile" value="HFP/AG" /> <option name="profile" value="HFP/HF" /> <option name="profile" value="HID/HOS" /> @@ -63,17 +67,17 @@ <option name="profile" value="L2CAP/LE" /> <option name="profile" value="MAP" /> <option name="profile" value="OPP" /> - <!-- TODO(b/272303629): Reenable --> - <!--option name="profile" value="PAN" /--> + <option name="profile" value="PAN" /> <option name="profile" value="PBAP/PSE" /> <option name="profile" value="RFCOMM" /> <option name="profile" value="SDP" /> <option name="profile" value="SM" /> + <option name="profile" value="VCP" /> </test> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> - <option name="mainline-module-package-name" value="com.android.btservices" /> - <option name="mainline-module-package-name" value="com.google.android.btservices" /> + <option name="mainline-module-package-name" value="com.android.bt" /> + <option name="mainline-module-package-name" value="com.google.android.bt" /> </object> </configuration> diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json index 758d21f07c..7b44a66f18 100644 --- a/android/pandora/server/configs/pts_bot_tests_config.json +++ b/android/pandora/server/configs/pts_bot_tests_config.json @@ -3210,8 +3210,7 @@ "SM": {}, "SPP": {}, "SUM ICS": {}, - "VCP": { - } + "VCP": {} }, "flags": [ { @@ -3252,6 +3251,69 @@ "VCP/VC/SPE/BI-19-C", "VCP/VC/SPE/BI-20-C" ] + }, + { + "flags": [ + "avdt_accept_open_timeout_ms" + ], + "tests": [ + "A2DP/SNK/SYN/BV-01-C" + ] + } + ], + "system_properties": [ + { + "system_properties": { + "bluetooth.profile.a2dp.sink.enabled": "true", + "bluetooth.profile.a2dp.source.enabled": "false" + }, + "tests": [ + "A2DP/SNK", + "AVCTP/CT", + "AVDTP/SNK", + "AVRCP/CT/CEC", + "AVRCP/CT/CRC", + "AVRCP/CT/PTH", + "AVRCP/CT/PTT" + ] + }, + { + "system_properties": { + "bluetooth.profile.a2dp.sink.enabled": "false", + "bluetooth.profile.a2dp.source.enabled": "true" + }, + "tests": [ + "A2DP/SRC", + "AVCTP/TG", + "AVDTP/SRC", + "AVRCP/TG" + ] + }, + { + "system_properties": { + "bluetooth.profile.hfp.hf.enabled": "true", + "bluetooth.profile.hfp.ag.enabled": "false" + }, + "tests": [ + "HFP/HF" + ] + }, + { + "system_properties": { + "bluetooth.profile.hfp.hf.enabled": "false", + "bluetooth.profile.hfp.ag.enabled": "true" + }, + "tests": [ + "HFP/AG" + ] + }, + { + "system_properties": { + "bluetooth.a2dp.avdt_accept_open_timeout_ms": "15000" + }, + "tests": [ + "A2DP/SNK/SYN/BV-01-C" + ] } ] } diff --git a/android/pandora/server/configs/pts_bot_tests_config_auto.json b/android/pandora/server/configs/pts_bot_tests_config_auto.json index abc188046c..425b79c8e8 100644 --- a/android/pandora/server/configs/pts_bot_tests_config_auto.json +++ b/android/pandora/server/configs/pts_bot_tests_config_auto.json @@ -375,5 +375,67 @@ "SPP": {}, "VCP": {} }, - "flags": {} + "flags": { + "flags": [ + "avdt_accept_open_timeout_ms" + ], + "tests": [ + "A2DP/SNK/SYN/BV-01-C" + ] + }, + "system_properties": [ + { + "system_properties": { + "bluetooth.profile.a2dp.sink.enabled": "true", + "bluetooth.profile.a2dp.source.enabled": "false" + }, + "tests": [ + "A2DP/SNK", + "AVCTP/CT", + "AVDTP/SNK", + "AVRCP/CT/CEC", + "AVRCP/CT/CRC", + "AVRCP/CT/PTH", + "AVRCP/CT/PTT" + ] + }, + { + "system_properties": { + "bluetooth.profile.a2dp.sink.enabled": "false", + "bluetooth.profile.a2dp.source.enabled": "true" + }, + "tests": [ + "A2DP/SRC", + "AVCTP/TG", + "AVDTP/SRC", + "AVRCP/TG" + ] + }, + { + "system_properties": { + "bluetooth.profile.hfp.hf.enabled": "true", + "bluetooth.profile.hfp.ag.enabled": "false" + }, + "tests": [ + "HFP/HF" + ] + }, + { + "system_properties": { + "bluetooth.profile.hfp.hf.enabled": "false", + "bluetooth.profile.hfp.ag.enabled": "true" + }, + "tests": [ + "HFP/AG" + ] + }, + { + "system_properties": { + "bluetooth.a2dp.avdt_accept_open_timeout_ms": "15000" + }, + "tests": [ + "A2DP/SNK/SYN/BV-01-C" + ] + } + ] } diff --git a/android/pandora/server/src/A2dp.kt b/android/pandora/server/src/A2dp.kt index 6708dd1c46..0b5a0d6640 100644 --- a/android/pandora/server/src/A2dp.kt +++ b/android/pandora/server/src/A2dp.kt @@ -41,7 +41,6 @@ import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filter @@ -110,10 +109,6 @@ class A2dp(val context: Context) : A2DPImplBase(), Closeable { } } - // TODO: b/234891800, AVDTP start request sometimes never sent if playback starts too - // early. - delay(2000L) - val source = Source.newBuilder().setCookie(ByteString.copyFrom(device.getAddress(), "UTF-8")) OpenSourceResponse.newBuilder().setSource(source).build() @@ -147,10 +142,6 @@ class A2dp(val context: Context) : A2DPImplBase(), Closeable { } } - // TODO: b/234891800, AVDTP start request sometimes never sent if playback starts too - // early. - delay(2000L) - val source = Source.newBuilder().setCookie(ByteString.copyFrom(device.getAddress(), "UTF-8")) WaitSourceResponse.newBuilder().setSource(source).build() diff --git a/android/pandora/test/a2dp/signaling_channel.py b/android/pandora/test/a2dp/signaling_channel.py new file mode 100644 index 0000000000..0fbda414c8 --- /dev/null +++ b/android/pandora/test/a2dp/signaling_channel.py @@ -0,0 +1,200 @@ +# Copyright 2024 Google LLC +# +# 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 +# +# https://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. + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- +from __future__ import annotations + +from bumble.device import Connection +try: + from packets import avdtp as avdt_packet_module + from packets.avdtp import * +except ImportError: + from .packets import avdtp as avdt_packet_module + from .packets.avdtp import * +from pyee import EventEmitter +from typing import Union + +import asyncio +import bumble.avdtp as avdtp +import bumble.l2cap as l2cap +import logging + +# ----------------------------------------------------------------------------- +# Logging +# ----------------------------------------------------------------------------- +logger = logging.getLogger(__name__) + +avdt_packet_module.print = lambda *args, **kwargs: logger.debug(" ".join(map(str, args))) + + +class Any: + """Helper class that will match all other values. + Use an element of this class in expected packets to match any value + returned by the AVDTP signaling.""" + + def __eq__(self, other) -> bool: + return True + + def __format__(self, format_spec: str) -> str: + return "_" + + def __len__(self) -> int: + return 1 + + def show(self, prefix: str = "") -> str: + return prefix + "_" + + +class SignalingChannel(EventEmitter): + connection: Connection + signaling_channel: Optional[l2cap.ClassicChannel] = None + transport_channel: Optional[l2cap.ClassicChannel] = None + avdtp_server: Optional[l2cap.ClassicChannelServer] = None + role: Optional[str] = None + + def __init__(self, connection: Connection): + super().__init__() + self.connection = connection + self.signaling_queue = asyncio.Queue() + self.transport_queue = asyncio.Queue() + + @classmethod + async def initiate(cls, connection: Connection) -> SignalingChannel: + channel = cls(connection) + await channel._initiate_signaling_channel() + return channel + + @classmethod + def accept(cls, connection: Connection) -> SignalingChannel: + channel = cls(connection) + channel._accept_signaling_channel() + return channel + + async def disconnect(self): + if not self.signaling_channel: + raise ValueError("No connected signaling channel") + await self.signaling_channel.disconnect() + self.signaling_channel = None + + async def initiate_transport_channel(self): + if self.transport_channel: + raise ValueError("RTP L2CAP channel already exists") + self.transport_channel = await self.connection.create_l2cap_channel( + l2cap.ClassicChannelSpec(psm=avdtp.AVDTP_PSM)) + + async def disconnect_transport_channel(self): + if not self.transport_channel: + raise ValueError("No connected RTP channel") + await self.transport_channel.disconnect() + self.transport_channel = None + + async def expect_signal(self, expected_sig: Union[SignalingPacket, type], timeout: float = 3) -> SignalingPacket: + packet = await asyncio.wait_for(self.signaling_queue.get(), timeout=timeout) + sig = SignalingPacket.parse_all(packet) + + if isinstance(expected_sig, type) and not isinstance(sig, expected_sig): + logger.error("Received unexpected signal") + logger.error(f"Expected signal: {expected_sig.__class__.__name__}") + logger.error("Received signal:") + sig.show() + raise ValueError(f"Received unexpected signal") + + if isinstance(expected_sig, SignalingPacket) and sig != expected_sig: + logger.error("Received unexpected signal") + logger.error("Expected signal:") + expected_sig.show() + logger.error("Received signal:") + sig.show() + raise ValueError(f"Received unexpected signal") + + logger.debug(f"<<< {self.connection.self_address} {self.role} received signal: <<<") + sig.show() + return sig + + async def expect_media(self, timeout: float = 3.0) -> bytes: + packet = await asyncio.wait_for(self.transport_queue.get(), timeout=timeout) + logger.debug(f"<<< {self.connection.self_address} {self.role} received media <<<") + logger.debug(f"RTP Packet: {packet.hex()}") + return packet + + def send_signal(self, packet: SignalingPacket): + logger.debug(f">>> {self.connection.self_address} {self.role} sending signal: >>>") + packet.show() + self.signaling_channel.send_pdu(packet.serialize()) + + def send_media(self, packet: bytes): + logger.debug(f">>> {self.connection.self_address} {self.role} sending media >>>") + self.transport_channel.send_pdu(packet) + + async def _initiate_signaling_channel(self): + if self.signaling_channel: + raise ValueError("Signaling L2CAP channel already exists") + self.role = "initiator" + self.signaling_channel = await self.connection.create_l2cap_channel(spec=l2cap.ClassicChannelSpec( + psm=avdtp.AVDTP_PSM)) + # Register to receive PDUs from the channel + self.signaling_channel.sink = self._on_pdu + + def _accept_signaling_channel(self): + if self.avdtp_server: + raise ValueError("L2CAP server already exists") + self.role = "acceptor" + avdtp_server = self.connection.device.l2cap_channel_manager.servers.get(avdtp.AVDTP_PSM) + if not avdtp_server: + self.avdtp_server = self.connection.device.create_l2cap_server(spec=l2cap.ClassicChannelSpec( + psm=avdtp.AVDTP_PSM)) + else: + self.avdtp_server = avdtp_server + self.avdtp_server.on('connection', self._on_l2cap_connection) + + def _on_l2cap_connection(self, channel: l2cap.ClassicChannel): + logger.info(f"Incoming L2CAP channel: {channel}") + + if not self.signaling_channel: + + def _on_channel_open(): + logger.info(f"Signaling opened on channel {self.signaling_channel}") + # Register to receive PDUs from the channel + self.signaling_channel.sink = self._on_pdu + self.emit('connection') + + def _on_channel_close(): + logger.info("Signaling channel closed") + self.signaling_channel = None + + self.signaling_channel = channel + self.signaling_channel.on('open', _on_channel_open) + self.signaling_channel.on('close', _on_channel_close) + elif not self.transport_channel: + + def _on_channel_open(): + logger.info(f"RTP opened on channel {self.transport_channel}") + # Register to receive PDUs from the channel + self.transport_channel.sink = self._on_avdtp_packet + + def _on_channel_close(): + logger.info('RTP channel closed') + self.transport_channel = None + + self.transport_channel = channel + self.transport_channel.on('open', _on_channel_open) + self.transport_channel.on('close', _on_channel_close) + + def _on_pdu(self, pdu: bytes): + self.signaling_queue.put_nowait(pdu) + + def _on_avdtp_packet(self, packet): + self.transport_queue.put_nowait(packet) diff --git a/android/pandora/test/a2dp/signaling_channel_test.py b/android/pandora/test/a2dp/signaling_channel_test.py new file mode 100644 index 0000000000..daf5c8f40b --- /dev/null +++ b/android/pandora/test/a2dp/signaling_channel_test.py @@ -0,0 +1,378 @@ +# Copyright 2024 Google LLC +# +# 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 +# +# https://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. +""" +Bumble tests for SignalingChannel implementation. + +Create venv and upgrade pip: + + python -m venv .venv + source .venv/bin/activate + python -m pip install --upgrade pip + +Install the required dependencies using pip: + + pip install pyee pytest bumble + +Run the tests: + python /path/signaling_channel_test.py +""" + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- +import asyncio +import logging +import os +import pytest +import bumble.avdtp as avdtp +from bumble.a2dp import (A2DP_SBC_CODEC_TYPE, SbcMediaCodecInformation) +from bumble.controller import Controller +from bumble.core import BT_BR_EDR_TRANSPORT +from bumble.device import Device +from bumble.host import Host +from bumble.link import LocalLink +from bumble.transport import AsyncPipeSink +from packets.avdtp import * +from signaling_channel import SignalingChannel, Any + +# ----------------------------------------------------------------------------- +# Logging +# ----------------------------------------------------------------------------- +logger = logging.getLogger(__name__) + + +# ----------------------------------------------------------------------------- +class TwoDevices: + + def __init__(self): + self.connections = [None, None] + + addresses = ['F0:F1:F2:F3:F4:F5', 'F5:F4:F3:F2:F1:F0'] + self.link = LocalLink() + self.controllers = [ + Controller('C1', link=self.link, public_address=addresses[0]), + Controller('C2', link=self.link, public_address=addresses[1]), + ] + self.devices = [ + Device( + address=addresses[0], + host=Host(self.controllers[0], AsyncPipeSink(self.controllers[0])), + ), + Device( + address=addresses[1], + host=Host(self.controllers[1], AsyncPipeSink(self.controllers[1])), + ), + ] + + self.paired = [None, None] + + def on_connection(self, which, connection): + self.connections[which] = connection + + def on_paired(self, which, keys): + self.paired[which] = keys + + +# ----------------------------------------------------------------------------- +@pytest.mark.asyncio +async def test_self_connection(): + # Create two devices, each with a controller, attached to the same link + two_devices = TwoDevices() + + # Attach listeners + two_devices.devices[0].on('connection', lambda connection: two_devices.on_connection(0, connection)) + two_devices.devices[1].on('connection', lambda connection: two_devices.on_connection(1, connection)) + + # Enable Classic connections + two_devices.devices[0].classic_enabled = True + two_devices.devices[1].classic_enabled = True + + # Start + await two_devices.devices[0].power_on() + await two_devices.devices[1].power_on() + + # Connect the two devices + await asyncio.gather( + two_devices.devices[0].connect(two_devices.devices[1].public_address, transport=BT_BR_EDR_TRANSPORT), + two_devices.devices[1].accept(two_devices.devices[0].public_address), + ) + + # Check the post conditions + assert two_devices.connections[0] is not None + assert two_devices.connections[1] is not None + + +# ----------------------------------------------------------------------------- +def sink_codec_capabilities(): + return avdtp.MediaCodecCapabilities( + media_type=avdtp.AVDTP_AUDIO_MEDIA_TYPE, + media_codec_type=A2DP_SBC_CODEC_TYPE, + media_codec_information=SbcMediaCodecInformation.from_bytes(bytes([255, 255, 2, 53])), + ) + + +# ----------------------------------------------------------------------------- +@pytest.mark.asyncio +async def test_signaling_channel_as_source(): + any = Any() + + two_devices = TwoDevices() + # Enable Classic connections + two_devices.devices[0].classic_enabled = True + two_devices.devices[1].classic_enabled = True + await two_devices.devices[0].power_on() + await two_devices.devices[1].power_on() + + def on_rtp_packet(packet): + rtp_packets.append(packet) + if len(rtp_packets) == rtp_packets_expected: + rtp_packets_fully_received.set_result(None) + + device_1_avdt_sink = None + avdtp_future = asyncio.get_running_loop().create_future() + + def on_avdtp_connection(server): + logger.info("AVDTP Opened") + nonlocal device_1_avdt_sink + device_1_avdt_sink = server.add_sink(sink_codec_capabilities()) + device_1_avdt_sink.on('rtp_packet', on_rtp_packet) + nonlocal avdtp_future + avdtp_future.set_result(None) + + # Create a listener to wait for AVDTP connections + listener = avdtp.Listener.for_device(two_devices.devices[1]) + listener.on('connection', on_avdtp_connection) + + async def make_connection(): + connections = await asyncio.gather( + two_devices.devices[0].connect(two_devices.devices[1].public_address, BT_BR_EDR_TRANSPORT), + two_devices.devices[1].accept(two_devices.devices[0].public_address), + ) + return connections[0] + + connection = await make_connection() + + channel_int = await SignalingChannel.initiate(connection) + + channel_int.send_signal(DiscoverCommand()) + + result = await channel_int.expect_signal( + DiscoverResponse(transaction_label=any, seid_information=[SeidInformation(acp_seid=1, tsep=Tsep.SINK)])) + + acp_seid = result.seid_information[0].acp_seid + + channel_int.send_signal(GetAllCapabilitiesCommand(acp_seid=acp_seid)) + + result = await channel_int.expect_signal( + GetAllCapabilitiesResponse(transaction_label=any, + service_capabilities=[ + MediaTransportCapability(), + MediaCodecCapability(service_category=ServiceCategory.MEDIA_CODEC, + media_codec_specific_information_elements=[255, 255, 2, 53]) + ])) + + channel_int.send_signal( + SetConfigurationCommand(acp_seid=acp_seid, service_capabilities=[result.service_capabilities[0]])) + + await channel_int.expect_signal(SetConfigurationResponse(transaction_label=any)) + + channel_int.send_signal(OpenCommand(acp_seid=acp_seid)) + + await channel_int.expect_signal(OpenResponse(transaction_label=any)) + + await asyncio.wait_for(avdtp_future, timeout=10.0) + + assert device_1_avdt_sink.in_use == 1 + assert device_1_avdt_sink.stream is not None + assert device_1_avdt_sink.stream.state == avdtp.AVDTP_OPEN_STATE + + async def generate_packets(packet_count): + sequence_number = 0 + timestamp = 0 + for i in range(packet_count): + payload = bytes([sequence_number % 256]) + packet = avdtp.MediaPacket(2, 0, 0, 0, sequence_number, timestamp, 0, [], 96, payload) + packet.timestamp_seconds = timestamp / 44100 + timestamp += 10 + sequence_number += 1 + yield packet + + # # Send packets using a pump object + rtp_packets_fully_received = asyncio.get_running_loop().create_future() + rtp_packets_expected = 3 + rtp_packets = [] + pump = avdtp.MediaPacketPump(generate_packets(3)) + + await channel_int.initiate_transport_channel() + + channel_int.send_signal(StartCommand(acp_seid=acp_seid)) + + await channel_int.expect_signal(StartResponse(transaction_label=any)) + + assert device_1_avdt_sink.in_use == 1 + assert device_1_avdt_sink.stream is not None + assert device_1_avdt_sink.stream.state == avdtp.AVDTP_STREAMING_STATE + + await pump.start(channel_int.transport_channel) + + await rtp_packets_fully_received + + await pump.stop() + + channel_int.send_signal(CloseCommand(acp_seid=acp_seid)) + + await channel_int.expect_signal(CloseResponse(transaction_label=any)) + + await channel_int.disconnect_transport_channel() + + assert device_1_avdt_sink.in_use == 0 + assert device_1_avdt_sink.stream.state == avdtp.AVDTP_IDLE_STATE + + +# ----------------------------------------------------------------------------- +@pytest.mark.asyncio +async def test_signaling_channel_as_sink(): + any = Any() + + two_devices = TwoDevices() + # Enable Classic connections + two_devices.devices[0].classic_enabled = True + two_devices.devices[1].classic_enabled = True + await two_devices.devices[0].power_on() + await two_devices.devices[1].power_on() + + dev_0_dev_1_conn, dev_1_dev_0_conn = await asyncio.gather( + two_devices.devices[0].connect(two_devices.devices[1].public_address, BT_BR_EDR_TRANSPORT), + two_devices.devices[1].accept(two_devices.devices[0].public_address), + ) + + channel_acp = SignalingChannel.accept(dev_1_dev_0_conn) + + avdtp_future = asyncio.get_running_loop().create_future() + + def on_avdtp_connection(): + logger.info(f" AVDTP Opened") + nonlocal avdtp_future + avdtp_future.set_result(None) + + channel_acp.on('connection', on_avdtp_connection) + + channel_int = await SignalingChannel.initiate(dev_0_dev_1_conn) + + channel_int.send_signal(DiscoverCommand()) + + cmd = await channel_acp.expect_signal(DiscoverCommand(transaction_label=any)) + + seid_information = [SeidInformation(tsep=Tsep.SINK, media_type=avdtp.AVDTP_AUDIO_MEDIA_TYPE)] + + channel_acp.send_signal(DiscoverResponse(transaction_label=cmd.transaction_label, + seid_information=seid_information)) + + result = await channel_int.expect_signal( + DiscoverResponse( + seid_information=[SeidInformation(acp_seid=0x0, tsep=Tsep.SINK, media_type=avdtp.AVDTP_AUDIO_MEDIA_TYPE)])) + + int_to_acp_seid = result.seid_information[0].acp_seid + + channel_int.send_signal(GetAllCapabilitiesCommand(acp_seid=int_to_acp_seid)) + + cmd = await channel_acp.expect_signal(GetAllCapabilitiesCommand(acp_seid=int_to_acp_seid, transaction_label=any)) + + acceptor_service_capabilities = [ + MediaTransportCapability(), + MediaCodecCapability(service_category=ServiceCategory.MEDIA_CODEC, + media_codec_specific_information_elements=[255, 255, 2, 53]) + ] + + channel_acp.send_signal( + GetAllCapabilitiesResponse(transaction_label=cmd.transaction_label, + service_capabilities=acceptor_service_capabilities)) + + result = await channel_int.expect_signal( + GetAllCapabilitiesResponse(transaction_label=any, + service_capabilities=[ + MediaTransportCapability(), + MediaCodecCapability(service_category=ServiceCategory.MEDIA_CODEC, + media_codec_specific_information_elements=[255, 255, 2, 53]) + ])) + + channel_int.send_signal( + SetConfigurationCommand(acp_seid=int_to_acp_seid, service_capabilities=[result.service_capabilities[0]])) + + cmd = await channel_acp.expect_signal( + SetConfigurationCommand(transaction_label=any, + acp_seid=int_to_acp_seid, + service_capabilities=[result.service_capabilities[0]])) + + channel_acp.send_signal(SetConfigurationResponse(transaction_label=cmd.transaction_label)) + + await channel_int.expect_signal(SetConfigurationResponse(transaction_label=any)) + + channel_int.send_signal(OpenCommand(acp_seid=int_to_acp_seid)) + + cmd = await channel_acp.expect_signal(OpenCommand(transaction_label=any, acp_seid=int_to_acp_seid)) + + channel_acp.send_signal(OpenResponse(transaction_label=cmd.transaction_label)) + + await channel_int.expect_signal(OpenResponse(transaction_label=any)) + + await asyncio.wait_for(avdtp_future, timeout=10.0) + + rtp_packets_expected = 3 + received_rtp_packets = [] + source_packets = [ + avdtp.MediaPacket(2, 0, 0, 0, i, i * 10, 0, [], 96, bytes([i])) for i in range(rtp_packets_expected) + ] + + await channel_int.initiate_transport_channel() + + channel_int.send_signal(StartCommand(acp_seid=int_to_acp_seid)) + + cmd = await channel_acp.expect_signal(StartCommand(transaction_label=any, acp_seid=int_to_acp_seid)) + + channel_acp.send_signal(StartResponse(transaction_label=cmd.transaction_label)) + + await channel_int.expect_signal(StartResponse(transaction_label=any)) + + channel_int.send_media(bytes(source_packets[0])) + channel_int.send_media(bytes(source_packets[1])) + channel_int.send_media(bytes(source_packets[2])) + + for _ in range(rtp_packets_expected): + received_rtp_packets.append(await channel_acp.expect_media()) + assert channel_acp.transport_queue.empty() + + channel_int.send_signal(CloseCommand(acp_seid=int_to_acp_seid)) + + cmd = await channel_acp.expect_signal(CloseCommand(transaction_label=any, acp_seid=int_to_acp_seid)) + + channel_acp.send_signal(CloseResponse(transaction_label=cmd.transaction_label)) + + await channel_int.expect_signal(CloseResponse(transaction_label=any)) + + await channel_int.disconnect_transport_channel() + await channel_int.disconnect() + + +# ----------------------------------------------------------------------------- +async def run_test_self(): + await test_self_connection() + await test_signaling_channel_as_source() + await test_signaling_channel_as_sink() + + +# ----------------------------------------------------------------------------- +if __name__ == '__main__': + logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper()) + asyncio.run(run_test_self()) diff --git a/android/pandora/test/a2dp_test.py b/android/pandora/test/a2dp_test.py index 92df133d62..a1efc015c3 100644 --- a/android/pandora/test/a2dp_test.py +++ b/android/pandora/test/a2dp_test.py @@ -20,9 +20,9 @@ import itertools import logging import numpy as np -from a2dp.packets import avdtp -from avatar import BumblePandoraDevice, PandoraDevice, PandoraDevices, pandora -from avatar.pandora_server import AndroidPandoraServer +from a2dp.packets.avdtp import * +from a2dp.signaling_channel import Any, SignalingChannel +from avatar import BumblePandoraDevice, PandoraDevice, PandoraDevices, pandora_snippet, enableFlag from bumble.a2dp import ( A2DP_MPEG_2_4_AAC_CODEC_TYPE, A2DP_SBC_CODEC_TYPE, @@ -41,8 +41,6 @@ from bumble.avctp import AVCTP_PSM from bumble.avdtp import ( AVDTP_AUDIO_MEDIA_TYPE, AVDTP_BAD_STATE_ERROR, - AVDTP_CLOSING_STATE, - AVDTP_IDLE_STATE, AVDTP_OPEN_STATE, AVDTP_PSM, AVDTP_STREAMING_STATE, @@ -50,6 +48,7 @@ from bumble.avdtp import ( Listener, MediaCodecCapabilities, Protocol, + Stream, Suspend_Reject, ) from bumble.l2cap import ( @@ -58,6 +57,7 @@ from bumble.l2cap import ( ClassicChannel, ClassicChannelSpec, L2CAP_Configure_Request, + L2CAP_Connection_Request, L2CAP_Connection_Response, ) from bumble.pairing import PairingDelegate @@ -76,6 +76,7 @@ logger = logging.getLogger(__name__) AVDTP_HANDLE_SUSPEND_CFM_BAD_STATE = 'com.android.bluetooth.flags.avdt_handle_suspend_cfm_bad_state' AVDTP_HANDLE_SIGNALING_ON_PEER_FAILURE = 'com.android.bluetooth.flags.avdt_handle_signaling_on_peer_failure' +A2DP_SM_IGNORE_CONNECT_EVENTS_IN_CONNECTING_STATE = 'com.android.bluetooth.flags.a2dp_sm_ignore_connect_events_in_connecting_state' async def initiate_pairing(device, address) -> Connection: @@ -290,6 +291,94 @@ class A2dpTest(base_test.BaseTestClass): # type: ignore[misc] assert_equal(self.ref1.a2dp_sink.stream.state, AVDTP_OPEN_STATE) @avatar.asynchronous + async def test_signaling_channel_and_streaming(self) -> None: + """Basic A2DP connection and streaming with SignalingChannel used by acceptor device test. + + 1. Pair and Connect RD1 + 2. Setup the acceptor expectations on signalling channel + 2. Start streaming + 4. Stop streaming + """ + any = Any() + + # Connect and pair RD1. + dut_ref1, ref1_dut = await asyncio.gather( + initiate_pairing(self.dut, self.ref1.address), + accept_pairing(self.ref1, self.dut.address), + ) + + connection = pandora_snippet.get_raw_connection(device=self.ref1, connection=ref1_dut) + channel = SignalingChannel.accept(connection) + + async def acceptor_avdt_open(channel: SignalingChannel): + + avdtp_future = asyncio.get_running_loop().create_future() + + def on_avdtp_connection(): + logger.info(f"AVDTP Opened") + nonlocal avdtp_future + avdtp_future.set_result(None) + + channel.on('connection', on_avdtp_connection) + + cmd = await channel.expect_signal(DiscoverCommand(transaction_label=any)) + + seid_information = [SeidInformation(acp_seid=0x01, tsep=Tsep.SINK, media_type=AVDTP_AUDIO_MEDIA_TYPE)] + + channel.send_signal( + DiscoverResponse(transaction_label=cmd.transaction_label, seid_information=seid_information)) + + cmd = await channel.expect_signal(GetAllCapabilitiesCommand(acp_seid=any, transaction_label=any)) + + acceptor_service_capabilities = [ + MediaTransportCapability(), + MediaCodecCapability(service_category=ServiceCategory.MEDIA_CODEC, + media_codec_specific_information_elements=[255, 255, 2, 53]) + ] + + channel.send_signal( + GetAllCapabilitiesResponse(transaction_label=cmd.transaction_label, + service_capabilities=acceptor_service_capabilities)) + + cmd = await channel.expect_signal( + SetConfigurationCommand(transaction_label=any, + acp_seid=any, + int_seid=any, + service_capabilities=[MediaTransportCapability(), any])) + + channel.send_signal(SetConfigurationResponse(transaction_label=cmd.transaction_label)) + + cmd = await channel.expect_signal(OpenCommand(transaction_label=any, acp_seid=any)) + + channel.send_signal(OpenResponse(transaction_label=cmd.transaction_label)) + + await asyncio.wait_for(avdtp_future, timeout=10.0) + + # Connect AVDTP to RD1. + _, dut_ref1_source = await asyncio.gather(acceptor_avdt_open(channel), open_source(self.dut, dut_ref1)) + + async def acceptor_avdt_start(channel: SignalingChannel): + cmd = await channel.expect_signal(StartCommand(transaction_label=any, acp_seid=any)) + + channel.send_signal(StartResponse(transaction_label=cmd.transaction_label)) + + # Start streaming to RD1. + await asyncio.gather(self.dut.a2dp.Start(source=dut_ref1_source), acceptor_avdt_start(channel)) + + audio = AudioSignal(self.dut.a2dp, dut_ref1_source, 0.8, 44100) + + # Verify that at least one audio frame is received on the transport channel. + await asyncio.wait_for(channel.expect_media(), 5.0) + + async def acceptor_avdt_suspend(channel: SignalingChannel): + cmd = await channel.expect_signal(SuspendCommand(transaction_label=any, acp_seid=any)) + + channel.send_signal(SuspendResponse(transaction_label=cmd.transaction_label)) + + # Stop streaming to RD1. + await asyncio.gather(self.dut.a2dp.Suspend(source=dut_ref1_source), acceptor_avdt_suspend(channel)) + + @avatar.asynchronous async def test_avdtp_autoconnect_when_only_avctp_connected(self) -> None: """Test AVDTP automatically connects if peer device connects only AVCTP. @@ -316,7 +405,7 @@ class A2dpTest(base_test.BaseTestClass): # type: ignore[misc] self.ref1.a2dp.on('connection', on_avdtp_connection) # Retrieve Bumble connection object from Pandora connection token - connection = pandora.get_raw_connection(device=self.ref1, connection=ref1_dut) + connection = pandora_snippet.get_raw_connection(device=self.ref1, connection=ref1_dut) # Open AVCTP L2CAP channel avctp = await connection.create_l2cap_channel(spec=ClassicChannelSpec(AVCTP_PSM)) @@ -326,7 +415,7 @@ class A2dpTest(base_test.BaseTestClass): # type: ignore[misc] await asyncio.wait_for(avdtp_future, timeout=10.0) @avatar.asynchronous - async def test_avdt_signaling_channel_connection_collision(self) -> None: + async def test_avdt_signaling_channel_connection_collision_case1(self) -> None: """Test AVDTP signaling channel connection collision. Test steps after DUT and RD1 connected and paired: @@ -416,7 +505,7 @@ class A2dpTest(base_test.BaseTestClass): # type: ignore[misc] ) # Retrieve Bumble connection object from Pandora connection token - connection = pandora.get_raw_connection(device=self.ref1, connection=ref1_dut) + connection = pandora_snippet.get_raw_connection(device=self.ref1, connection=ref1_dut) # Find a free CID for a new channel connection_channels = self.ref1.device.l2cap_channel_manager.channels.setdefault(connection.handle, {}) source_cid = self.ref1.device.l2cap_channel_manager.find_free_br_edr_cid(connection_channels) @@ -561,6 +650,7 @@ class A2dpTest(base_test.BaseTestClass): # type: ignore[misc] assert configurationResponse.configuration.id.HasField('mpeg_aac') @avatar.asynchronous + @enableFlag(AVDTP_HANDLE_SUSPEND_CFM_BAD_STATE) async def test_avdt_handle_suspend_cfm_bad_state_error(self) -> None: """Test AVDTP handling of suspend confirmation BAD_STATE error. @@ -611,13 +701,6 @@ class A2dpTest(base_test.BaseTestClass): # type: ignore[misc] channel.on('open', on_channel_open) channel.on('close', on_channel_close) - # Enable BAD_STATE handling - for server in self.devices._servers: - if isinstance(server, AndroidPandoraServer): - server.device.adb.shell( - ['device_config override bluetooth', AVDTP_HANDLE_SUSPEND_CFM_BAD_STATE, 'true']) # type: ignore - break - self.ref1.device.l2cap_channel_manager.servers.pop(AVDTP_PSM) self.ref1.a2dp = TestA2dpListener.for_device(self.ref1.device) self.ref1.a2dp_sink = None @@ -663,6 +746,7 @@ class A2dpTest(base_test.BaseTestClass): # type: ignore[misc] await asyncio.wait_for(avdtp_future, timeout=10.0) @avatar.asynchronous + @enableFlag(AVDTP_HANDLE_SIGNALING_ON_PEER_FAILURE) async def test_avdt_open_after_timeout(self) -> None: """Test AVDTP automatically opens stream after timeout if peer device only configures codec. @@ -679,14 +763,6 @@ class A2dpTest(base_test.BaseTestClass): # type: ignore[misc] avdtp_future.set_result(None) return super().on_open_command(command) - # Enable BAD_STATE handling - for server in self.devices._servers: - if isinstance(server, AndroidPandoraServer): - server.device.adb.shell( - ['device_config override bluetooth', AVDTP_HANDLE_SIGNALING_ON_PEER_FAILURE, - 'true']) # type: ignore - break - # Connect and pair RD1. ref1_dut, dut_ref1 = await asyncio.gather( initiate_pairing(self.ref1, self.dut.address), @@ -697,7 +773,7 @@ class A2dpTest(base_test.BaseTestClass): # type: ignore[misc] avdtp_future = asyncio.get_running_loop().create_future() # Retrieve Bumble connection object from Pandora connection token - connection = pandora.get_raw_connection(device=self.ref1, connection=ref1_dut) + connection = pandora_snippet.get_raw_connection(device=self.ref1, connection=ref1_dut) assert connection is not None channel = await connection.create_l2cap_channel(spec=ClassicChannelSpec(psm=AVDTP_PSM)) @@ -705,7 +781,7 @@ class A2dpTest(base_test.BaseTestClass): # type: ignore[misc] sink = client.add_sink(sbc_codec_capabilities()) endpoints = await client.discover_remote_endpoints() logger.info(f"endpoints: {endpoints}") - assert len(endpoints) >= 1 + assert endpoints remote_source = list(endpoints)[0] assert remote_source.in_use == 0 assert remote_source.media_type == AVDTP_AUDIO_MEDIA_TYPE @@ -732,6 +808,166 @@ class A2dpTest(base_test.BaseTestClass): # type: ignore[misc] # Wait for AVDTP Open from DUT await asyncio.wait_for(avdtp_future, timeout=10.0) + @avatar.asynchronous + @enableFlag(A2DP_SM_IGNORE_CONNECT_EVENTS_IN_CONNECTING_STATE) + async def test_avdt_signaling_channel_connection_collision_case2(self) -> None: + """Test AVDTP signaling channel connection collision with Android as initiator. + + Test steps after DUT and RD1 connected and paired: + 1. RD1 waits for connection request from DUT + 2. DUT connects RD1 over AVDTP - first AVDTP signaling channel + 3. RD1 sends connection request to DUT to simulate collision + 4. RD1 rejects connection from DUT + 5. DUT closed initiated connection and allowed for the incoming to proceed. RD1 opens AVDT connection + 6. DUT A2DP source configured and connected + """ + + wait_for_l2cap_open = asyncio.get_running_loop().create_future() + + class TestClassicChannel(ClassicChannel): + + def test_connect(self, connection: Connection, cid: int, request: L2CAP_Connection_Request) -> None: + assert self.state == self.State.CLOSED + + # Check that we can start a new connection + assert not self.connection_result + + self._change_state(self.State.WAIT_CONNECT_RSP) + logger.info("<< 3. RD1 sends connection request to DUT to simulate collision >>") + self.send_control_frame( + L2CAP_Connection_Request( + identifier=self.manager.next_identifier(self.connection), + psm=self.psm, + source_cid=self.source_cid, + )) + if (self.psm == AVDTP_PSM): + logger.info("<< 4. RD1 rejects connection from DUT >>") + self.manager.send_control_frame( + connection, cid, + L2CAP_Connection_Response( + identifier=request.identifier, + destination_cid=0, + source_cid=request.source_cid, + result=L2CAP_Connection_Response.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE, + status=0x0000, + )) + + class TestChannelManager(ChannelManager): + + def __init__( + self, + device: BumblePandoraDevice, + ) -> None: + super().__init__( + device.l2cap_channel_manager.extended_features, + device.l2cap_channel_manager.connectionless_mtu, + ) + self.register_fixed_channel(bumble.smp.SMP_CID, device.on_smp_pdu) + device.sdp_server.register(self) + self.register_fixed_channel(bumble.att.ATT_CID, device.on_gatt_pdu) + self.host = device.host + + def on_l2cap_connection_request(self, connection: Connection, cid: int, + request: L2CAP_Connection_Request) -> None: + if (request.psm == AVDTP_PSM): + logger.info("<< 2. DUT connects RD1 over AVDTP - first AVDTP signaling channel >>") + spec = ClassicChannelSpec(AVDTP_PSM) + assert spec.psm is not None + + # Find a free CID for a new channel + connection_channels = self.channels.setdefault(connection.handle, {}) + source_cid = self.find_free_br_edr_cid(connection_channels) + assert source_cid is not None + + # Create the channel + logger.debug(f'creating client channel with cid={source_cid} for psm {spec.psm}') + channel = TestClassicChannel( + self, + connection, + L2CAP_SIGNALING_CID, + AVDTP_PSM, + source_cid, + spec.mtu, + ) + connection_channels[source_cid] = channel + + def on_channel_open(): + # Initiate AVDTP with connected L2CAP signaling channel + nonlocal wait_for_l2cap_open + wait_for_l2cap_open.set_result(channel) + + channel.on('open', on_channel_open) + channel.test_connect(connection, cid, request) + return + + super().on_l2cap_connection_request(connection, cid, request) + + handle = 0x00010001 + self.ref1.device.sdp_service_records = {handle: make_audio_sink_service_sdp_records(handle)} + + # Override L2CAP Channel Manager to control signaling + self.ref1.device.l2cap_channel_manager = TestChannelManager(self.ref1.device) + + # Create listener on RD1 for initial incoming AVDT connection from DUT + self.ref1.a2dp = Listener.for_device(self.ref1.device) + + logger.info("<< 1. RD1 waits for connection request from DUT >>") + + # Connect and pair DUT -> RD1. + dut_ref1, ref1_dut = await asyncio.gather( + initiate_pairing(self.dut, self.ref1.address), + accept_pairing(self.ref1, self.dut.address), + ) + + # Wait until RD1 will initiate and open L2CAP channel for AVDTP + channel = await asyncio.wait_for(wait_for_l2cap_open, timeout=10.0) + + logger.info( + "<< 5. DUT closed initiated connection and allowed for the incoming to proceed. RD1 opens AVDT connection >>" + ) + + protocol = Protocol(channel) + sink = protocol.add_sink(sbc_codec_capabilities()) + endpoints = await protocol.discover_remote_endpoints() + logger.debug(f"endpoints: {endpoints}") + assert endpoints + remote_source = list(endpoints)[0] + assert remote_source.in_use == 0 + assert remote_source.media_type == AVDTP_AUDIO_MEDIA_TYPE + assert remote_source.tsep == AVDTP_TSEP_SRC + logger.debug(f"remote_source: {remote_source}") + + sink.configuration = [ + MediaCodecCapabilities( + media_type=AVDTP_AUDIO_MEDIA_TYPE, + media_codec_type=A2DP_SBC_CODEC_TYPE, + media_codec_information=SbcMediaCodecInformation.from_lists( + sampling_frequencies=[44100], + channel_modes=[SBC_JOINT_STEREO_CHANNEL_MODE], + block_lengths=[16], + subbands=[8], + allocation_methods=[SBC_LOUDNESS_ALLOCATION_METHOD], + minimum_bitpool_value=2, + maximum_bitpool_value=53, + ), + ) + ] + + # Start waiting for DUT A2DP source configured and connected + wait_source = self.dut.a2dp.WaitSource(connection=dut_ref1) + + # Open stream + stream = Stream(protocol, sink, remote_source) + protocol.streams[sink.seid] = stream + await stream.configure() + await stream.open() + + # Check that DUT source is configured and connected + result = await wait_source + assert result.source + + logger.info("<< 6. DUT A2DP source configured and connected >>") + if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) diff --git a/android/pandora/test/main.py b/android/pandora/test/main.py index d8eadf1e60..d5c5da0bad 100644 --- a/android/pandora/test/main.py +++ b/android/pandora/test/main.py @@ -4,18 +4,18 @@ site.main() import argparse import logging +import itertools import os import sys from argparse import Namespace from mobly import suite_runner -from typing import List, Tuple +from typing import List, Tuple, Union, Literal _BUMBLE_BTSNOOP_FMT = 'bumble_btsnoop_{pid}_{instance}.log' -import a2dp_test - # Import test cases modules. +import a2dp_test import aics_test import asha_test import avatar.cases.host_test @@ -25,12 +25,53 @@ import avatar.cases.security_test import gatt_test import hap_test import hfpclient_test -from pairing import _test_class_list as _pairing_test_class_list import sdp_test +from pairing import _test_class_list as _pairing_test_class_list +from pandora.host_pb2 import PrimaryPhy, PRIMARY_1M, PRIMARY_CODED + + +class LeHostTestFiltered(avatar.cases.le_host_test.LeHostTest): + """ + LeHostTestFiltered inherits from LeHostTest to skip currently broken and unfeasible to fix tests. + Overridden tests will be visible as PASS when run. + """ + skipped_tests = [ + # Reason for skipping tests: b/272120114 + "test_extended_scan('non_connectable_scannable','directed',150,0)", + "test_extended_scan('non_connectable_scannable','undirected',150,0)", + "test_extended_scan('non_connectable_scannable','directed',150,2)", + "test_extended_scan('non_connectable_scannable','undirected',150,2)", + ] + + @avatar.parameterized( + *itertools.product( + # The advertisement cannot be both connectable and scannable. + ('connectable', 'non_connectable', 'non_connectable_scannable'), + ('directed', 'undirected'), + # Bumble does not send multiple HCI commands, so it must also fit in + # 1 HCI command (max length 251 minus overhead). + (0, 150), + (PRIMARY_1M, PRIMARY_CODED), + ),) # type: ignore[misc] + def test_extended_scan( + self, + connectable_scannable: Union[Literal['connectable'], Literal['non_connectable'], + Literal['non_connectable_scannable']], + directed: Union[Literal['directed'], Literal['undirected']], + data_len: int, + primary_phy: PrimaryPhy, + ) -> None: + current_test = f"test_extended_scan('{connectable_scannable}','{directed}',{data_len},{primary_phy})" + logging.info(f"current test: {current_test}") + if current_test not in self.skipped_tests: + assert current_test in avatar.cases.le_host_test.LeHostTest.__dict__ + avatar.cases.le_host_test.LeHostTest.__dict__[current_test](self) + + _TEST_CLASSES_LIST = [ avatar.cases.host_test.HostTest, - avatar.cases.le_host_test.LeHostTest, + LeHostTestFiltered, avatar.cases.security_test.SecurityTest, avatar.cases.le_security_test.LeSecurityTest, a2dp_test.A2dpTest, diff --git a/apex/Android.bp b/apex/Android.bp index 4eccda2a11..d8f4603a8c 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -23,39 +23,14 @@ linker_config { } // Legacy Bluetooth apex prior to Baklava +// This is kept for soong purposes but this apex doesn't do anything anymore // TODO b/383863941 delete apex { + enabled: false, name: "com.android.btservices", defaults: ["t-launched-apex-module"], manifest: "apex_manifest.json", - bootclasspath_fragments: ["com.android.btservices-bootclasspath-fragment"], - systemserverclasspath_fragments: ["com.android.btservices-systemserverclasspath-fragment"], - compat_configs: [ - "bluetooth-compat-config", - "bluetoothapk-platform-compat-config", - "framework-bluetooth-compat-config", - ], - apps: ["Bluetooth"], - multilib: { - first: { - // Extractor process runs only with the primary ABI. - jni_libs: [ - "libbluetooth_jni", - ], - }, - }, - - prebuilts: [ - "audio_set_configurations_bfbs", - "audio_set_configurations_json", - "audio_set_scenarios_bfbs", - "audio_set_scenarios_json", - "bt_did.conf", - "bt_stack.conf", - "btservices-linker-config", - "interop_database.conf", - ], key: "com.android.btservices.key", certificate: ":com.android.btservices.certificate", updatable: true, @@ -74,71 +49,6 @@ android_app_certificate { certificate: "com.android.btservices", } -sdk { - name: "btservices-module-sdk", - apexes: [ - // Adds exportable dependencies of the APEX to the sdk, - // e.g. *classpath_fragments. - "com.android.btservices", - ], -} - -// Encapsulate the contributions made by the com.android.bluetooth to the bootclasspath. -bootclasspath_fragment { - name: "com.android.btservices-bootclasspath-fragment", - contents: ["framework-bluetooth"], - apex_available: ["com.android.btservices"], - - // The bootclasspath_fragments that provide APIs on which this depends. - fragments: [ - { - apex: "com.android.art", - module: "art-bootclasspath-fragment", - }, - ], - - // Additional stubs libraries that this fragment's contents use which are - // not provided by another bootclasspath_fragment. - additional_stubs: [ - "android-non-updatable", - ], - - // Additional hidden API flag files to override the defaults. This must only be - // modified by the Soong or platform compat team. - hidden_api: { - max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], - max_target_r_low_priority: ["hiddenapi/hiddenapi-max-target-r-low-priority.txt"], - unsupported: ["hiddenapi/hiddenapi-unsupported.txt"], - - // The following packages contain classes from other modules on the - // bootclasspath. That means that the hidden API flags for this module - // has to explicitly list every single class this module provides in - // that package to differentiate them from the classes provided by other - // modules. That can include private classes that are not part of the - // API. - split_packages: [ - "android.bluetooth", - ], - - // The following packages and all their subpackages currently only - // contain classes from this bootclasspath_fragment. Listing a package - // here won't prevent other bootclasspath modules from adding classes in - // any of those packages but it will prevent them from adding those - // classes into an API surface, e.g. public, system, etc.. Doing so will - // result in a build failure due to inconsistent flags. - package_prefixes: [ - "android.bluetooth.le", - "com.android.bluetooth", - ], - }, -} - -systemserverclasspath_fragment { - name: "com.android.btservices-systemserverclasspath-fragment", - standalone_contents: ["service-bluetooth"], - apex_available: ["com.android.btservices"], -} - // Mainline bluetooth apex module. apex { name: "com.android.bt", @@ -242,6 +152,6 @@ bootclasspath_fragment { systemserverclasspath_fragment { name: "com.android.bt-systemserverclasspath-fragment", - standalone_contents: ["service-bluetooth-new"], + standalone_contents: ["service-bluetooth"], apex_available: ["com.android.bt"], } diff --git a/common/Android.bp b/common/Android.bp index 900cc365d7..f472c9e3d0 100644 --- a/common/Android.bp +++ b/common/Android.bp @@ -22,7 +22,7 @@ java_library { "bluetooth/constants/aics/GainMode.aidl", "bluetooth/constants/aics/Mute.aidl", ], - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", sdk_version: "module_current", visibility: ["//packages/modules/Bluetooth:__subpackages__"], diff --git a/flags/Android.bp b/flags/Android.bp index f43322d4ed..80a57891a5 100644 --- a/flags/Android.bp +++ b/flags/Android.bp @@ -6,7 +6,7 @@ package { aconfig_declarations { name: "bluetooth_aconfig_flags", package: "com.android.bluetooth.flags", - container: "com.android.btservices", + container: "com.android.bt", visibility: ["//packages/modules/Bluetooth/framework"], // LINT.IfChange srcs: [ @@ -19,7 +19,6 @@ aconfig_declarations { "btif_dm.aconfig", "btm_ble.aconfig", "connectivity.aconfig", - "device_iot_config.aconfig", "dis.aconfig", "framework.aconfig", "gap.aconfig", @@ -32,6 +31,7 @@ aconfig_declarations { "hid.aconfig", "l2cap.aconfig", "le_advertising.aconfig", + "le_scanning.aconfig", "leaudio.aconfig", "mapclient.aconfig", "mcp.aconfig", @@ -59,7 +59,7 @@ java_aconfig_library { name: "bluetooth_flags_java_lib", aconfig_declarations: "bluetooth_aconfig_flags", visibility: ["//packages/modules/Bluetooth:__subpackages__"], - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], libs: ["framework-configinfrastructure.stubs.module_lib"], sdk_version: "module_current", min_sdk_version: "Tiramisu", @@ -83,7 +83,7 @@ cc_aconfig_library { aconfig_declarations: "bluetooth_aconfig_flags", host_supported: true, visibility: ["//packages/modules/Bluetooth:__subpackages__"], - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", } @@ -101,7 +101,7 @@ rust_aconfig_library { host_supported: true, crate_name: "bluetooth_aconfig_flags_rust", aconfig_declarations: "bluetooth_aconfig_flags", - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", visibility: ["//packages/modules/Bluetooth/system:__subpackages__"], } diff --git a/flags/BUILD.gn b/flags/BUILD.gn index 6c7185a24a..da5aa8621b 100644 --- a/flags/BUILD.gn +++ b/flags/BUILD.gn @@ -12,7 +12,6 @@ aconfig("bluetooth_flags_c_lib") { "btif_dm.aconfig", "btm_ble.aconfig", "connectivity.aconfig", - "device_iot_config.aconfig", "dis.aconfig", "framework.aconfig", "gap.aconfig", @@ -25,6 +24,7 @@ aconfig("bluetooth_flags_c_lib") { "hid.aconfig", "l2cap.aconfig", "le_advertising.aconfig", + "le_scanning.aconfig", "leaudio.aconfig", "mapclient.aconfig", "mcp.aconfig", diff --git a/flags/a2dp.aconfig b/flags/a2dp.aconfig index e6275bf010..06d46a7f76 100644 --- a/flags/a2dp.aconfig +++ b/flags/a2dp.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "bta_av_use_peer_codec" diff --git a/flags/active_device_manager.aconfig b/flags/active_device_manager.aconfig index fefcdd1d87..b2438be452 100644 --- a/flags/active_device_manager.aconfig +++ b/flags/active_device_manager.aconfig @@ -1,41 +1,31 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { - name: "adm_fallback_when_wired_audio_disconnected" - namespace: "bluetooth" - description: "Fallback to other connected device when wired audio device disconnects" - bug: "348124361" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "adm_always_fallback_to_available_device" + name: "adm_verify_active_fallback_device" namespace: "bluetooth" - description: "Fix audio path and always fallback to available device" - bug: "351820274" + description: "Verify if device selected for fallback is different then last one" + bug: "369799111" metadata { purpose: PURPOSE_BUGFIX } } flag { - name: "adm_verify_active_fallback_device" + name: "adm_fix_disconnect_of_set_member" namespace: "bluetooth" - description: "Verify if device selected for fallback is different then last one" - bug: "369799111" + description: "Fix disconnecting of the set member device. Make sure the other set member is not considered as fallback device." + bug: "374320313" metadata { purpose: PURPOSE_BUGFIX } } flag { - name: "adm_fix_disconnect_of_set_member" + name: "adm_remove_handling_wired" namespace: "bluetooth" - description: "Fix disconnecting of the set member device. Make sure the other set member is not considered as fallback device." - bug: "374320313" + description: "ActiveDeviceManager doesn't need to handle adding and removing wired devices." + bug: "390372480" metadata { purpose: PURPOSE_BUGFIX } diff --git a/flags/adapter.aconfig b/flags/adapter.aconfig index f355113fe0..f6bf65e1f6 100644 --- a/flags/adapter.aconfig +++ b/flags/adapter.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "adapter_properties_looper" diff --git a/flags/avrcp.aconfig b/flags/avrcp.aconfig index 5e806efb11..7a30583c07 100644 --- a/flags/avrcp.aconfig +++ b/flags/avrcp.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "abs_volume_sdp_conflict" diff --git a/flags/avrcp_controller.aconfig b/flags/avrcp_controller.aconfig index 4374eef8fa..be732ee1ad 100644 --- a/flags/avrcp_controller.aconfig +++ b/flags/avrcp_controller.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "uncache_player_when_browsed_player_changes" diff --git a/flags/bta_dm.aconfig b/flags/bta_dm.aconfig index f35d562ebf..287a0f0187 100644 --- a/flags/bta_dm.aconfig +++ b/flags/bta_dm.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "bta_dm_defer_device_discovery_state_change_until_rnr_complete" @@ -9,13 +9,6 @@ flag { } flag { - name: "bta_dm_discover_both" - namespace: "bluetooth" - description: "perform both LE and Classic service discovery simulteanously on capable devices" - bug: "339217881" -} - -flag { name: "cancel_open_discovery_client" namespace: "bluetooth" description: "Cancel connection from discovery client correctly" @@ -34,3 +27,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "extend_and_randomize_role_switch_delay" + namespace: "bluetooth" + description: "Fix the possible conflicts between role switch and authentication" + bug: "388459732" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/flags/btif_dm.aconfig b/flags/btif_dm.aconfig index 2f16904b8f..b8802a9db1 100644 --- a/flags/btif_dm.aconfig +++ b/flags/btif_dm.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "bond_transport_after_bond_cancel_fix" diff --git a/flags/btm_ble.aconfig b/flags/btm_ble.aconfig index 0922fd1aa0..2413c74be1 100644 --- a/flags/btm_ble.aconfig +++ b/flags/btm_ble.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "floss_separate_host_privacy_and_llprivacy" diff --git a/flags/connectivity.aconfig b/flags/connectivity.aconfig index 80efed1aa2..c8dca9cb5f 100644 --- a/flags/connectivity.aconfig +++ b/flags/connectivity.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "api_get_connection_state_using_identity_address" @@ -34,3 +34,11 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "allow_gatt_connect_from_the_apps_without_making_leaudio_device_active" + namespace: "bluetooth" + description: "Allows for GATT connection without making LeAudio device active after connection" + bug: "389274300" +} + diff --git a/flags/device_iot_config.aconfig b/flags/device_iot_config.aconfig deleted file mode 100644 index 77ede517d4..0000000000 --- a/flags/device_iot_config.aconfig +++ /dev/null @@ -1,9 +0,0 @@ -package: "com.android.bluetooth.flags" -container: "com.android.btservices" - -flag { - name: "device_iot_config_logging" - namespace: "bluetooth" - description: "Enable device IOT information storage." - bug: "290844229" -} diff --git a/flags/dis.aconfig b/flags/dis.aconfig index f96d6c3b5f..ace19bca21 100644 --- a/flags/dis.aconfig +++ b/flags/dis.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "queue_dis_requests" diff --git a/flags/framework.aconfig b/flags/framework.aconfig index ae23329345..6464f91927 100644 --- a/flags/framework.aconfig +++ b/flags/framework.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "support_metadata_device_types_apis" @@ -86,3 +86,13 @@ flag { description: "Make BluetoothDevice.ACTION_KEY_MISSING into public API" bug: "379729762" } + +flag { + name: "set_component_available_fix" + namespace: "bluetooth" + description: "Ensure the state in PackageManager has DISABLED to ENABLED to trigger PACKAGE_CHANGED" + bug: "391084450" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/flags/gap.aconfig b/flags/gap.aconfig index 0ef4a7aa20..5da5144aa1 100644 --- a/flags/gap.aconfig +++ b/flags/gap.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "encrypted_advertising_data" @@ -44,26 +44,6 @@ flag { } flag { - name: "ble_context_map_remove_fix" - namespace: "bluetooth" - description: "Fix connection removal logic in ContextMap class" - bug: "329154715" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "scan_record_manufacturer_data_merge" - namespace: "bluetooth" - description: "If a scan record has multiple datas under same manufacturer id, merge the values" - bug: "331723396" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "msft_addr_tracking_quirk" namespace: "bluetooth" description: "Scanning with MSFT paddress tracking for Realtek BT controllers" @@ -131,16 +111,6 @@ flag { } flag { - name: "le_inquiry_duration" - namespace: "bluetooth" - description: "Use the same duration for LE inquiry scan that classic discovery uses" - bug: "357894405" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "non_wake_alarm_for_rpa_rotation" namespace: "bluetooth" description: "Use non-wake alarm for LE RPA rotation. go/non-wake-alarm-for-rpa-rotation" @@ -281,3 +251,43 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "change_default_trackable_adv_number" + namespace: "bluetooth" + description: "Change the default value for number of trackable advertisements for onFound" + bug: "389568695" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "fix_unusable_adv_slot_due_to_map_access" + namespace: "bluetooth" + description: "Fixes the advertising slot becoming unusable due to incorrect map access via [] operator." + bug: "388615378" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "fix_bluetooth_gatt_getting_duplicate_services" + namespace: "bluetooth" + description: "Fixes BluetoothGatt getting duplicate GATT services" + bug: "391773937" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "batch_scan_optimization" + namespace: "bluetooth" + description: "Optimized batch scan for less wakeups" + bug: "392132489" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/flags/gatt.aconfig b/flags/gatt.aconfig index 55e5b27077..e054fb3f7a 100644 --- a/flags/gatt.aconfig +++ b/flags/gatt.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "default_gatt_transport" @@ -18,3 +18,14 @@ flag { bug: "384794418" is_exported: true } + +flag { + name: "advertise_thread" + namespace: "bluetooth" + description: "Run all advertise functions on a single thread" + bug: "391508617" + metadata { + purpose: PURPOSE_BUGFIX + } +} + diff --git a/flags/hal.aconfig b/flags/hal.aconfig index 286d1fceb4..3e16fa5355 100644 --- a/flags/hal.aconfig +++ b/flags/hal.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "audio_port_binder_inherit_rt" @@ -19,4 +19,14 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "snoop_logger_recreate_logs_directory" + namespace: "bluetooth" + description: "Recreate logs directory if removed" + bug: "383876267" + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/flags/hap.aconfig b/flags/hap.aconfig index 3ce320f88d..634000d0bd 100644 --- a/flags/hap.aconfig +++ b/flags/hap.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "aics_api" diff --git a/flags/hci.aconfig b/flags/hci.aconfig index 79da26788b..cdd5ca3ab6 100644 --- a/flags/hci.aconfig +++ b/flags/hci.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "encryption_change_v2" diff --git a/flags/hfp.aconfig b/flags/hfp.aconfig index 96985f2750..026c3b22b3 100644 --- a/flags/hfp.aconfig +++ b/flags/hfp.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "auto_connect_on_multiple_hfp_when_no_a2dp_device" @@ -60,16 +60,6 @@ flag { } flag { - name: "headset_client_am_hf_volume_symmetric" - namespace: "bluetooth" - description: "Fix AM/HF volume conversion to be symmetric" - bug: "340482648" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "maintain_call_index_after_conference" namespace: "bluetooth" description: "Avoid change of clcc call index after call disconnects from conference" @@ -110,16 +100,6 @@ flag { } flag { - name: "hfp_allow_volume_change_without_sco" - namespace: "bluetooth" - description: "Allow Audio Fwk to change SCO volume when HFP profile is connected and SCO not connected" - bug: "362313390" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "choose_wrong_hfp_codec_in_specific_config" namespace: "bluetooth" description: "Flag to fix codec selection in nego when the peer device only support NB and SWB." diff --git a/flags/hfpclient.aconfig b/flags/hfpclient.aconfig index ded90b3578..c134baac7e 100644 --- a/flags/hfpclient.aconfig +++ b/flags/hfpclient.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "hfp_client_disconnecting_state" diff --git a/flags/hid.aconfig b/flags/hid.aconfig index 5085df326b..17c66db035 100644 --- a/flags/hid.aconfig +++ b/flags/hid.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "allow_switching_hid_and_hogp" diff --git a/flags/l2cap.aconfig b/flags/l2cap.aconfig index fb53c10bef..7e1ca0b016 100644 --- a/flags/l2cap.aconfig +++ b/flags/l2cap.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "l2cap_tx_complete_cb_info" diff --git a/flags/le_advertising.aconfig b/flags/le_advertising.aconfig index 524d0461e5..56f849ab7a 100644 --- a/flags/le_advertising.aconfig +++ b/flags/le_advertising.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "nrpa_non_connectable_adv" diff --git a/flags/le_scanning.aconfig b/flags/le_scanning.aconfig new file mode 100644 index 0000000000..0b4985e45e --- /dev/null +++ b/flags/le_scanning.aconfig @@ -0,0 +1,12 @@ +package: "com.android.bluetooth.flags" +container: "com.android.bt" + +flag { + name: "scan_results_in_main_thread" + namespace: "bluetooth" + description: "Use main thread for handling scan results" + bug: "392693506" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/flags/leaudio.aconfig b/flags/leaudio.aconfig index 9c5132afd5..03cc9a4c02 100644 --- a/flags/leaudio.aconfig +++ b/flags/leaudio.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "leaudio_broadcast_monitor_source_sync_status" @@ -441,3 +441,23 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "leaudio_sm_ignore_connect_events_in_connecting_state" + namespace: "bluetooth" + description: "When received CONNECT event in Connecting state, with no prior DISCONNECT - ignore the event" + bug: "384460395" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "leaudio_disable_broadcast_for_hap_device" + namespace: "bluetooth" + description: "Disable broadcast feature for HAP device" + bug: "391702876" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/flags/mapclient.aconfig b/flags/mapclient.aconfig index 97d0411683..fe8f90d81d 100644 --- a/flags/mapclient.aconfig +++ b/flags/mapclient.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "handle_delivery_sending_failure_events" diff --git a/flags/mcp.aconfig b/flags/mcp.aconfig index 430bacd761..80567acae7 100644 --- a/flags/mcp.aconfig +++ b/flags/mcp.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "mcp_allow_play_without_active_player" diff --git a/flags/metric.aconfig b/flags/metric.aconfig index f0c62e37fc..e8b8628ba2 100644 --- a/flags/metric.aconfig +++ b/flags/metric.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "bluetooth_power_telemetry" diff --git a/flags/opp.aconfig b/flags/opp.aconfig index bd0f380c45..0e95bd42f3 100644 --- a/flags/opp.aconfig +++ b/flags/opp.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "opp_ignore_content_observer_after_service_stop" @@ -20,3 +20,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "update_opp_launcher_user_changed" + namespace: "bluetooth" + description: "Enable/disable OPP launcher when user changed" + bug: "389596902" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/flags/pairing.aconfig b/flags/pairing.aconfig index a668b8dfb1..ae3d19a6ac 100644 --- a/flags/pairing.aconfig +++ b/flags/pairing.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "clear_auth_collision_state_on_pairing_complete" diff --git a/flags/pbapclient.aconfig b/flags/pbapclient.aconfig index dbbbb7773b..2464557d58 100644 --- a/flags/pbapclient.aconfig +++ b/flags/pbapclient.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "pbap_client_storage_refactor" diff --git a/flags/ranging.aconfig b/flags/ranging.aconfig index 6b75003163..7851d5ff82 100644 --- a/flags/ranging.aconfig +++ b/flags/ranging.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "channel_sounding" diff --git a/flags/rfcomm.aconfig b/flags/rfcomm.aconfig index df53931078..a5d1ec94ce 100644 --- a/flags/rfcomm.aconfig +++ b/flags/rfcomm.aconfig @@ -1,12 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" - -flag { - name: "rfcomm_always_use_mitm" - namespace: "bluetooth" - description: "Use MITM initially to avoid abrupt peer disconnection b/312840315" - bug: "316824288" -} +container: "com.android.bt" flag { name: "rfcomm_prevent_unnecessary_collisions" diff --git a/flags/rnr.aconfig b/flags/rnr.aconfig index 6b19a5f77b..425db568d6 100644 --- a/flags/rnr.aconfig +++ b/flags/rnr.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "rnr_store_device_type" diff --git a/flags/sco.aconfig b/flags/sco.aconfig index 26379f4ea1..51c6e14e24 100644 --- a/flags/sco.aconfig +++ b/flags/sco.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "fix_sco_command_status_handling" diff --git a/flags/sdp.aconfig b/flags/sdp.aconfig index a20adb914a..418fa04663 100644 --- a/flags/sdp.aconfig +++ b/flags/sdp.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "stack_sdp_detect_nil_property_type" diff --git a/flags/security.aconfig b/flags/security.aconfig index 2bb69eac95..ddfd78611c 100644 --- a/flags/security.aconfig +++ b/flags/security.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "key_missing_classic_device" @@ -36,10 +36,10 @@ flag { } flag { - name: "le_enc_on_reconnection" + name: "le_enc_on_reconnect" namespace: "bluetooth" description: "Encrypt LE link on reconnection with bonded devices" - bug: "356201480" + bug: "388864535" metadata { purpose: PURPOSE_BUGFIX } diff --git a/flags/service_discovery.aconfig b/flags/service_discovery.aconfig index f5569086b9..00c0741ff3 100644 --- a/flags/service_discovery.aconfig +++ b/flags/service_discovery.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "prevent_duplicate_uuid_intent" diff --git a/flags/sockets.aconfig b/flags/sockets.aconfig index 688d27323c..ded22e3da0 100644 --- a/flags/sockets.aconfig +++ b/flags/sockets.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "unix_file_socket_creation_failure" diff --git a/flags/system_service.aconfig b/flags/system_service.aconfig index fba0792eca..65caf1e60c 100644 --- a/flags/system_service.aconfig +++ b/flags/system_service.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "enforce_resolve_system_service_behavior" diff --git a/flags/vcp.aconfig b/flags/vcp.aconfig index 2c7e4983ea..12e55de6ba 100644 --- a/flags/vcp.aconfig +++ b/flags/vcp.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "vcp_mute_unmute" diff --git a/flags/vsc.aconfig b/flags/vsc.aconfig index bd67729689..af4f375849 100644 --- a/flags/vsc.aconfig +++ b/flags/vsc.aconfig @@ -1,5 +1,5 @@ package: "com.android.bluetooth.flags" -container: "com.android.btservices" +container: "com.android.bt" flag { name: "hci_vendor_specific_extension" diff --git a/framework/Android.bp b/framework/Android.bp index 6e5dafc3e1..cd09db00e6 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -82,9 +82,7 @@ java_sdk_library { ":__subpackages__", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], permitted_packages: [ "android.bluetooth", diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 62b4aa6062..60657262e3 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -53,7 +53,7 @@ package android.bluetooth { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void disableOptionalCodecs(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void enableOptionalCodecs(@NonNull android.bluetooth.BluetoothDevice); method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BufferConstraints getBufferConstraints(); - method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BluetoothCodecStatus getCodecStatus(@NonNull android.bluetooth.BluetoothDevice); + method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true) public android.bluetooth.BluetoothCodecStatus getCodecStatus(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getDynamicBufferSupport(); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int isOptionalCodecsEnabled(@NonNull android.bluetooth.BluetoothDevice); @@ -383,16 +383,16 @@ package android.bluetooth { public final class BluetoothHapClient implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { method public void close(); method protected void finalize(); - method @FlaggedApi("com.android.bluetooth.flags.settings_can_control_hap_preset") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getActivePresetIndex(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getActivePresetIndex(@NonNull android.bluetooth.BluetoothDevice); method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BluetoothHapPresetInfo getActivePresetInfo(@NonNull android.bluetooth.BluetoothDevice); method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothHapPresetInfo> getAllPresetInfo(@NonNull android.bluetooth.BluetoothDevice); method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice); method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]); - method @FlaggedApi("com.android.bluetooth.flags.settings_can_control_hap_preset") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getHapGroup(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getHapGroup(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getHearingAidType(@NonNull android.bluetooth.BluetoothDevice); - method @FlaggedApi("com.android.bluetooth.flags.settings_can_control_hap_preset") @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BluetoothHapPresetInfo getPresetInfo(@NonNull android.bluetooth.BluetoothDevice, int); + method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BluetoothHapPresetInfo getPresetInfo(@NonNull android.bluetooth.BluetoothDevice, int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothHapClient.Callback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void selectPreset(@NonNull android.bluetooth.BluetoothDevice, int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void selectPresetForGroup(int, int); @@ -403,13 +403,13 @@ package android.bluetooth { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean supportsIndependentPresets(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean supportsSynchronizedPresets(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean supportsWritablePresets(@NonNull android.bluetooth.BluetoothDevice); - method @FlaggedApi("com.android.bluetooth.flags.settings_can_control_hap_preset") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void switchToNextPreset(@NonNull android.bluetooth.BluetoothDevice); - method @FlaggedApi("com.android.bluetooth.flags.settings_can_control_hap_preset") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void switchToNextPresetForGroup(int); - method @FlaggedApi("com.android.bluetooth.flags.settings_can_control_hap_preset") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void switchToPreviousPreset(@NonNull android.bluetooth.BluetoothDevice); - method @FlaggedApi("com.android.bluetooth.flags.settings_can_control_hap_preset") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void switchToPreviousPresetForGroup(int); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void switchToNextPreset(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void switchToNextPresetForGroup(int); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void switchToPreviousPreset(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void switchToPreviousPresetForGroup(int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void unregisterCallback(@NonNull android.bluetooth.BluetoothHapClient.Callback); field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_HAP_CONNECTION_STATE_CHANGED = "android.bluetooth.action.HAP_CONNECTION_STATE_CHANGED"; - field @FlaggedApi("com.android.bluetooth.flags.settings_can_control_hap_preset") public static final int PRESET_INDEX_UNAVAILABLE = 0; // 0x0 + field public static final int PRESET_INDEX_UNAVAILABLE = 0; // 0x0 field public static final int TYPE_BANDED = 2; // 0x2 field public static final int TYPE_BINAURAL = 0; // 0x0 field public static final int TYPE_MONAURAL = 1; // 0x1 @@ -1416,7 +1416,6 @@ package android.bluetooth.le { public final class BluetoothLeScanner { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(android.os.WorkSource, android.bluetooth.le.ScanCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.os.WorkSource, android.bluetooth.le.ScanCallback); - method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startTruncatedScan(java.util.List<android.bluetooth.le.TruncatedFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback); } public final class ChannelSoundingParams implements android.os.Parcelable { diff --git a/framework/api/system-removed.txt b/framework/api/system-removed.txt index d802177e24..ac62cca0a3 100644 --- a/framework/api/system-removed.txt +++ b/framework/api/system-removed.txt @@ -1 +1,9 @@ // Signature format: 2.0 +package android.bluetooth.le { + + public final class BluetoothLeScanner { + method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startTruncatedScan(java.util.List<android.bluetooth.le.TruncatedFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback); + } + +} + diff --git a/framework/java/android/bluetooth/BluetoothA2dp.java b/framework/java/android/bluetooth/BluetoothA2dp.java index 5bcd0789ab..54f2c702aa 100644 --- a/framework/java/android/bluetooth/BluetoothA2dp.java +++ b/framework/java/android/bluetooth/BluetoothA2dp.java @@ -724,20 +724,23 @@ public final class BluetoothA2dp implements BluetoothProfile { /** * Gets the current codec status (configuration and capability). * + * <p>This method requires the calling app to have the {@link + * android.Manifest.permission#BLUETOOTH_CONNECT} permission. Additionally, an app must either + * have the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} or be associated with the + * Companion Device manager (see {@link android.companion.CompanionDeviceManager#associate( + * AssociationRequest, android.companion.CompanionDeviceManager.Callback, Handler)}) + * * @param device the remote Bluetooth device. * @return the current codec status * @hide */ @SystemApi - @Nullable @RequiresLegacyBluetoothPermission @RequiresBluetoothConnectPermission @RequiresPermission( - allOf = { - BLUETOOTH_CONNECT, - BLUETOOTH_PRIVILEGED, - }) - public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) { + allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}, + conditional = true) + public @Nullable BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) { if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); verifyDeviceNotNull(device, "getCodecStatus"); final IBluetoothA2dp service = getService(); diff --git a/framework/java/android/bluetooth/BluetoothAdapter.java b/framework/java/android/bluetooth/BluetoothAdapter.java index fad88d4b5e..08f880e04a 100644 --- a/framework/java/android/bluetooth/BluetoothAdapter.java +++ b/framework/java/android/bluetooth/BluetoothAdapter.java @@ -4203,6 +4203,46 @@ public final class BluetoothAdapter { return null; } + /** + * Return a binder to BluetoothAdvertise + * + * @hide + */ + @RequiresNoPermission + public @Nullable IBluetoothAdvertise getBluetoothAdvertise() { + mServiceLock.readLock().lock(); + try { + if (mService != null) { + return IBluetoothAdvertise.Stub.asInterface(mService.getBluetoothAdvertise()); + } + } catch (RemoteException e) { + logRemoteException(TAG, e); + } finally { + mServiceLock.readLock().unlock(); + } + return null; + } + + /** + * Return a binder to DistanceMeasurement + * + * @hide + */ + @RequiresNoPermission + public @Nullable IDistanceMeasurement getDistanceMeasurement() { + mServiceLock.readLock().lock(); + try { + if (mService != null) { + return IDistanceMeasurement.Stub.asInterface(mService.getDistanceMeasurement()); + } + } catch (RemoteException e) { + logRemoteException(TAG, e); + } finally { + mServiceLock.readLock().unlock(); + } + return null; + } + /** Return a binder to a Profile service */ private @Nullable IBinder getProfile(int profile) { mServiceLock.readLock().lock(); @@ -5830,8 +5870,8 @@ public final class BluetoothAdapter { * 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. + * vendor-specific event code must be in the range 0x00 to 0x4f or 0x60 to 0xff. The + * inclusive range 0x52-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 @@ -5851,7 +5891,7 @@ public final class BluetoothAdapter { requireNonNull(executor); requireNonNull(callback); if (eventCodeSet.stream() - .anyMatch((n) -> (n < 0) || (n >= 0x50 && n < 0x60) || (n > 0xff))) { + .anyMatch((n) -> (n < 0) || (n >= 0x52 && n < 0x60) || (n > 0xff))) { throw new IllegalArgumentException("Event code not in valid range"); } diff --git a/framework/java/android/bluetooth/BluetoothDevice.java b/framework/java/android/bluetooth/BluetoothDevice.java index 58dd3ba1da..439c9349cb 100644 --- a/framework/java/android/bluetooth/BluetoothDevice.java +++ b/framework/java/android/bluetooth/BluetoothDevice.java @@ -1726,7 +1726,9 @@ public final class BluetoothDevice implements Parcelable, Attributable { } /** - * Returns the identity address and identity address type of this BluetoothDevice. + * Returns the identity address and identity address type of this BluetoothDevice. An identity + * address is a public or static random Bluetooth LE device address that serves as a + * unique identifier. * * @return a {@link BluetoothAddress} containing identity address and identity address type. If * Bluetooth is not enabled or identity address type is not available, it will return a diff --git a/framework/java/android/bluetooth/BluetoothGatt.java b/framework/java/android/bluetooth/BluetoothGatt.java index e67d823c7e..a69c8ba792 100644 --- a/framework/java/android/bluetooth/BluetoothGatt.java +++ b/framework/java/android/bluetooth/BluetoothGatt.java @@ -37,6 +37,8 @@ import android.os.ParcelUuid; import android.os.RemoteException; import android.util.Log; +import com.android.bluetooth.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -243,6 +245,9 @@ public final class BluetoothGatt implements BluetoothProfile { + " unregistering"); } unregisterApp(); + if (Flags.unregisterGattClientDisconnected()) { + mCallback = null; + } return; } if (VDBG) { @@ -274,7 +279,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { // autoConnect is inverse of "isDirect" mService.clientConnect( - mClientIf, + clientIf, mDevice.getAddress(), mDevice.getAddressType(), !mAutoConnect, @@ -361,6 +366,8 @@ public final class BluetoothGatt implements BluetoothProfile { * @hide */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(BLUETOOTH_CONNECT) public void onClientConnectionState( int status, int clientIf, boolean connected, String address) { if (DBG) { @@ -381,6 +388,10 @@ public final class BluetoothGatt implements BluetoothProfile { ? BluetoothProfile.STATE_CONNECTED : BluetoothProfile.STATE_DISCONNECTED; + if (Flags.unregisterGattClientDisconnected() && !connected && !mAutoConnect) { + unregisterApp(); + } + runOrQueueCallback( new Runnable() { @Override @@ -433,6 +444,10 @@ public final class BluetoothGatt implements BluetoothProfile { s.setDevice(mDevice); } + if (Flags.fixBluetoothGattGettingDuplicateServices()) { + mServices.clear(); + } + mServices.addAll(services); // Fix references to included services, as they doesn't point to right objects. @@ -493,16 +508,18 @@ public final class BluetoothGatt implements BluetoothProfile { mDeviceBusy = false; } + int clientIf = mClientIf; if ((status == GATT_INSUFFICIENT_AUTHENTICATION || status == GATT_INSUFFICIENT_ENCRYPTION) - && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { + && (mAuthRetryState != AUTH_RETRY_STATE_MITM) + && (clientIf > 0)) { try { final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; mService.readCharacteristic( - mClientIf, address, handle, authReq, mAttributionSource); + clientIf, address, handle, authReq, mAttributionSource); mAuthRetryState++; return; } catch (RemoteException e) { @@ -573,10 +590,13 @@ public final class BluetoothGatt implements BluetoothProfile { ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; int requestStatus = BluetoothStatusCodes.ERROR_UNKNOWN; - for (int i = 0; i < WRITE_CHARACTERISTIC_MAX_RETRIES; i++) { + int clientIf = mClientIf; + for (int i = 0; + (i < WRITE_CHARACTERISTIC_MAX_RETRIES) && (clientIf > 0); + i++) { requestStatus = mService.writeCharacteristic( - mClientIf, + clientIf, address, handle, characteristic.getWriteType(), @@ -679,16 +699,18 @@ public final class BluetoothGatt implements BluetoothProfile { BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle); if (descriptor == null) return; + int clientIf = mClientIf; if ((status == GATT_INSUFFICIENT_AUTHENTICATION || status == GATT_INSUFFICIENT_ENCRYPTION) - && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { + && (mAuthRetryState != AUTH_RETRY_STATE_MITM) + && (clientIf > 0)) { try { final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; mService.readDescriptor( - mClientIf, address, handle, authReq, mAttributionSource); + clientIf, address, handle, authReq, mAttributionSource); mAuthRetryState++; return; } catch (RemoteException e) { @@ -741,16 +763,18 @@ public final class BluetoothGatt implements BluetoothProfile { BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle); if (descriptor == null) return; + int clientIf = mClientIf; if ((status == GATT_INSUFFICIENT_AUTHENTICATION || status == GATT_INSUFFICIENT_ENCRYPTION) - && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { + && (mAuthRetryState != AUTH_RETRY_STATE_MITM) + && (clientIf > 0)) { try { final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; mService.writeDescriptor( - mClientIf, address, handle, authReq, value, mAttributionSource); + clientIf, address, handle, authReq, value, mAttributionSource); mAuthRetryState++; return; } catch (RemoteException e) { @@ -1033,6 +1057,10 @@ public final class BluetoothGatt implements BluetoothProfile { if (DBG) Log.d(TAG, "close()"); unregisterApp(); + if (Flags.unregisterGattClientDisconnected()) { + mCallback = null; + } + mConnState = CONN_STATE_CLOSED; mAuthRetryState = AUTH_RETRY_STATE_IDLE; } @@ -1166,7 +1194,9 @@ public final class BluetoothGatt implements BluetoothProfile { if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf); try { - mCallback = null; + if (!Flags.unregisterGattClientDisconnected()) { + mCallback = null; + } mService.unregisterClient(mClientIf, mAttributionSource); mClientIf = 0; } catch (RemoteException e) { @@ -1229,10 +1259,11 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresPermission(BLUETOOTH_CONNECT) public void disconnect() { if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice); - if (mService == null || mClientIf == 0) return; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return; try { - mService.clientDisconnect(mClientIf, mDevice.getAddress(), mAttributionSource); + mService.clientDisconnect(clientIf, mDevice.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1250,6 +1281,40 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresBluetoothConnectPermission @RequiresPermission(BLUETOOTH_CONNECT) public boolean connect() { + int clientIf = mClientIf; + if (mService == null) return false; + if (clientIf == 0) { + if (!Flags.unregisterGattClientDisconnected()) { + return false; + } + synchronized (mStateLock) { + if (mConnState != CONN_STATE_IDLE) { + return false; + } + mConnState = CONN_STATE_CONNECTING; + } + + UUID uuid = UUID.randomUUID(); + if (DBG) Log.d(TAG, "reconnect from connect(), UUID=" + uuid); + + try { + mService.registerClient( + new ParcelUuid(uuid), + mBluetoothGattCallback, + /* eatt_support= */ false, + mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, "", e); + synchronized (mStateLock) { + mConnState = CONN_STATE_IDLE; + } + Log.e(TAG, "Failed to register callback"); + return false; + } + + return true; + } + try { if (DBG) { Log.d(TAG, "connect(void) - device: " + mDevice + ", auto=" + mAutoConnect); @@ -1257,7 +1322,7 @@ public final class BluetoothGatt implements BluetoothProfile { // autoConnect is inverse of "isDirect" mService.clientConnect( - mClientIf, + clientIf, mDevice.getAddress(), mDevice.getAddressType(), !mAutoConnect, @@ -1293,9 +1358,12 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresBluetoothConnectPermission @RequiresPermission(BLUETOOTH_CONNECT) public void setPreferredPhy(int txPhy, int rxPhy, int phyOptions) { + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return; + try { mService.clientSetPreferredPhy( - mClientIf, mDevice.getAddress(), txPhy, rxPhy, phyOptions, mAttributionSource); + clientIf, mDevice.getAddress(), txPhy, rxPhy, phyOptions, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1308,8 +1376,11 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresBluetoothConnectPermission @RequiresPermission(BLUETOOTH_CONNECT) public void readPhy() { + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return; + try { - mService.clientReadPhy(mClientIf, mDevice.getAddress(), mAttributionSource); + mService.clientReadPhy(clientIf, mDevice.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1340,12 +1411,16 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresPermission(BLUETOOTH_CONNECT) public boolean discoverServices() { if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice); - if (mService == null || mClientIf == 0) return false; - mServices.clear(); + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; + + if (!Flags.fixBluetoothGattGettingDuplicateServices()) { + mServices.clear(); + } try { - mService.discoverServices(mClientIf, mDevice.getAddress(), mAttributionSource); + mService.discoverServices(clientIf, mDevice.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1367,13 +1442,16 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresPermission(BLUETOOTH_CONNECT) public boolean discoverServiceByUuid(UUID uuid) { if (DBG) Log.d(TAG, "discoverServiceByUuid() - device: " + mDevice); - if (mService == null || mClientIf == 0) return false; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; - mServices.clear(); + if (!Flags.fixBluetoothGattGettingDuplicateServices()) { + mServices.clear(); + } try { mService.discoverServiceByUuid( - mClientIf, mDevice.getAddress(), new ParcelUuid(uuid), mAttributionSource); + clientIf, mDevice.getAddress(), new ParcelUuid(uuid), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1447,7 +1525,8 @@ public final class BluetoothGatt implements BluetoothProfile { } if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid()); - if (mService == null || mClientIf == 0) return false; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; BluetoothGattService service = characteristic.getService(); if (service == null) return false; @@ -1462,7 +1541,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mService.readCharacteristic( - mClientIf, + clientIf, device.getAddress(), characteristic.getInstanceId(), AUTHENTICATION_NONE, @@ -1494,7 +1573,8 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresPermission(BLUETOOTH_CONNECT) public boolean readUsingCharacteristicUuid(UUID uuid, int startHandle, int endHandle) { if (VDBG) Log.d(TAG, "readUsingCharacteristicUuid() - uuid: " + uuid); - if (mService == null || mClientIf == 0) return false; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; synchronized (mDeviceBusyLock) { if (mDeviceBusy) return false; @@ -1503,7 +1583,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mService.readUsingCharacteristicUuid( - mClientIf, + clientIf, mDevice.getAddress(), new ParcelUuid(uuid), startHandle, @@ -1601,7 +1681,8 @@ public final class BluetoothGatt implements BluetoothProfile { == 0) { return BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED; } - if (mService == null || mClientIf == 0) { + int clientIf = mClientIf; + if (mService == null || clientIf == 0) { return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; } @@ -1627,7 +1708,7 @@ public final class BluetoothGatt implements BluetoothProfile { for (int i = 0; i < WRITE_CHARACTERISTIC_MAX_RETRIES; i++) { requestStatus = mService.writeCharacteristic( - mClientIf, + clientIf, device.getAddress(), characteristic.getInstanceId(), writeType, @@ -1674,7 +1755,8 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresPermission(BLUETOOTH_CONNECT) public boolean readDescriptor(BluetoothGattDescriptor descriptor) { if (VDBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid()); - if (mService == null || mClientIf == 0) return false; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); if (characteristic == null) return false; @@ -1692,7 +1774,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mService.readDescriptor( - mClientIf, + clientIf, device.getAddress(), descriptor.getInstanceId(), AUTHENTICATION_NONE, @@ -1755,7 +1837,8 @@ public final class BluetoothGatt implements BluetoothProfile { throw new IllegalArgumentException("value must not be null"); } if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid()); - if (mService == null || mClientIf == 0) { + int clientIf = mClientIf; + if (mService == null || clientIf == 0) { return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; } @@ -1781,7 +1864,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { return mService.writeDescriptor( - mClientIf, + clientIf, device.getAddress(), descriptor.getInstanceId(), AUTHENTICATION_NONE, @@ -1818,10 +1901,11 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresPermission(BLUETOOTH_CONNECT) public boolean beginReliableWrite() { if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice); - if (mService == null || mClientIf == 0) return false; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; try { - mService.beginReliableWrite(mClientIf, mDevice.getAddress(), mAttributionSource); + mService.beginReliableWrite(clientIf, mDevice.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1846,7 +1930,8 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresPermission(BLUETOOTH_CONNECT) public boolean executeReliableWrite() { if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice); - if (mService == null || mClientIf == 0) return false; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; synchronized (mDeviceBusyLock) { if (mDeviceBusy) return false; @@ -1854,7 +1939,7 @@ public final class BluetoothGatt implements BluetoothProfile { } try { - mService.endReliableWrite(mClientIf, mDevice.getAddress(), true, mAttributionSource); + mService.endReliableWrite(clientIf, mDevice.getAddress(), true, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); synchronized (mDeviceBusyLock) { @@ -1877,10 +1962,11 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresPermission(BLUETOOTH_CONNECT) public void abortReliableWrite() { if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice); - if (mService == null || mClientIf == 0) return; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return; try { - mService.endReliableWrite(mClientIf, mDevice.getAddress(), false, mAttributionSource); + mService.endReliableWrite(clientIf, mDevice.getAddress(), false, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1921,7 +2007,8 @@ public final class BluetoothGatt implements BluetoothProfile { + " enable: " + enable); } - if (mService == null || mClientIf == 0) return false; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; BluetoothGattService service = characteristic.getService(); if (service == null) return false; @@ -1931,7 +2018,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mService.registerForNotification( - mClientIf, + clientIf, device.getAddress(), characteristic.getInstanceId(), enable, @@ -1954,10 +2041,11 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresPermission(BLUETOOTH_CONNECT) public boolean refresh() { if (DBG) Log.d(TAG, "refresh() - device: " + mDevice); - if (mService == null || mClientIf == 0) return false; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; try { - mService.refreshDevice(mClientIf, mDevice.getAddress(), mAttributionSource); + mService.refreshDevice(clientIf, mDevice.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1979,10 +2067,11 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresPermission(BLUETOOTH_CONNECT) public boolean readRemoteRssi() { if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice); - if (mService == null || mClientIf == 0) return false; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; try { - mService.readRemoteRssi(mClientIf, mDevice.getAddress(), mAttributionSource); + mService.readRemoteRssi(clientIf, mDevice.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -2014,10 +2103,11 @@ public final class BluetoothGatt implements BluetoothProfile { if (DBG) { Log.d(TAG, "configureMTU() - device: " + mDevice + " mtu: " + mtu); } - if (mService == null || mClientIf == 0) return false; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; try { - mService.configureMTU(mClientIf, mDevice.getAddress(), mtu, mAttributionSource); + mService.configureMTU(clientIf, mDevice.getAddress(), mtu, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -2047,11 +2137,12 @@ public final class BluetoothGatt implements BluetoothProfile { } if (DBG) Log.d(TAG, "requestConnectionPriority() - params: " + connectionPriority); - if (mService == null || mClientIf == 0) return false; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; try { mService.connectionParameterUpdate( - mClientIf, mDevice.getAddress(), connectionPriority, mAttributionSource); + clientIf, mDevice.getAddress(), connectionPriority, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -2098,11 +2189,12 @@ public final class BluetoothGatt implements BluetoothProfile { + ", max_ce=" + maxConnectionEventLen); } - if (mService == null || mClientIf == 0) return false; + int clientIf = mClientIf; + if (mService == null || clientIf == 0) return false; try { mService.leConnectionUpdate( - mClientIf, + clientIf, mDevice.getAddress(), minConnectionInterval, maxConnectionInterval, @@ -2148,12 +2240,13 @@ public final class BluetoothGatt implements BluetoothProfile { if (DBG) { Log.d(TAG, "requestsubrateMode(" + subrateMode + ")"); } - if (mService == null || mClientIf == 0) { + int clientIf = mClientIf; + if (mService == null || clientIf == 0) { return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; } try { - return mService.subrateModeRequest(mClientIf, mDevice, subrateMode, mAttributionSource); + return mService.subrateModeRequest(clientIf, mDevice, subrateMode, mAttributionSource); } catch (RemoteException e) { logRemoteException(TAG, e); return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; diff --git a/framework/java/android/bluetooth/BluetoothHapClient.java b/framework/java/android/bluetooth/BluetoothHapClient.java index 910faa7faa..c11249bc9e 100644 --- a/framework/java/android/bluetooth/BluetoothHapClient.java +++ b/framework/java/android/bluetooth/BluetoothHapClient.java @@ -24,7 +24,6 @@ import static android.bluetooth.BluetoothUtils.callServiceIfEnabled; import static java.util.Objects.requireNonNull; import android.annotation.CallbackExecutor; -import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -41,8 +40,6 @@ import android.os.RemoteException; import android.util.CloseGuard; import android.util.Log; -import com.android.bluetooth.flags.Flags; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; @@ -336,7 +333,6 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) public static final int PRESET_INDEX_UNAVAILABLE = IBluetoothHapClient.PRESET_INDEX_UNAVAILABLE; /** @@ -691,7 +687,6 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) @RequiresBluetoothConnectPermission @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) public int getHapGroup(@NonNull BluetoothDevice device) { @@ -715,7 +710,6 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) @RequiresBluetoothConnectPermission @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) public int getActivePresetIndex(@NonNull BluetoothDevice device) { @@ -815,7 +809,6 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) @RequiresBluetoothConnectPermission @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) public void switchToNextPreset(@NonNull BluetoothDevice device) { @@ -840,7 +833,6 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) @RequiresBluetoothConnectPermission @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) public void switchToNextPresetForGroup(int groupId) { @@ -860,7 +852,6 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) @RequiresBluetoothConnectPermission @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) public void switchToPreviousPreset(@NonNull BluetoothDevice device) { @@ -887,7 +878,6 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) @RequiresBluetoothConnectPermission @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) public void switchToPreviousPresetForGroup(int groupId) { @@ -906,7 +896,6 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) @RequiresBluetoothConnectPermission @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) @Nullable diff --git a/framework/java/android/bluetooth/BluetoothSocket.java b/framework/java/android/bluetooth/BluetoothSocket.java index d33542e6d6..cde23baf4d 100644 --- a/framework/java/android/bluetooth/BluetoothSocket.java +++ b/framework/java/android/bluetooth/BluetoothSocket.java @@ -978,6 +978,8 @@ public final class BluetoothSocket implements Closeable { if (mL2capBuffer.remaining() == 0) { if (VDBG) Log.v(TAG, "l2cap buffer empty, refilling..."); if (fillL2capRxBuffer() == -1) { + Log.d(TAG, "socket EOF, returning -1"); + mSocketState = SocketState.CLOSED; return -1; } } @@ -994,7 +996,8 @@ public final class BluetoothSocket implements Closeable { ret = mSocketIS.read(b, offset, length); } if (ret < 0) { - return -1; + mSocketState = SocketState.CLOSED; + throw new IOException("bt socket closed, read return: " + ret); } if (VDBG) Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret); return ret; diff --git a/framework/java/android/bluetooth/le/AdvertisingSet.java b/framework/java/android/bluetooth/le/AdvertisingSet.java index 8632fcf671..5b3299b120 100644 --- a/framework/java/android/bluetooth/le/AdvertisingSet.java +++ b/framework/java/android/bluetooth/le/AdvertisingSet.java @@ -25,7 +25,7 @@ import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothAdvertise; import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; import android.content.AttributionSource; @@ -43,18 +43,18 @@ import android.util.Log; public final class AdvertisingSet { private static final String TAG = "AdvertisingSet"; - private final IBluetoothGatt mGatt; + private final IBluetoothAdvertise mAdvertise; private int mAdvertiserId; private AttributionSource mAttributionSource; AdvertisingSet( - IBluetoothGatt gatt, + IBluetoothAdvertise advertise, int advertiserId, BluetoothAdapter bluetoothAdapter, AttributionSource attributionSource) { mAdvertiserId = advertiserId; mAttributionSource = attributionSource; - mGatt = requireNonNull(gatt, "Bluetooth gatt cannot be null"); + mAdvertise = requireNonNull(advertise, "Bluetooth advertise cannot be null"); } /* package */ void setAdvertiserId(int advertiserId) { @@ -77,7 +77,7 @@ public final class AdvertisingSet { @RequiresPermission(BLUETOOTH_ADVERTISE) public void enableAdvertising(boolean enable, int duration, int maxExtendedAdvertisingEvents) { try { - mGatt.enableAdvertisingSet( + mAdvertise.enableAdvertisingSet( mAdvertiserId, enable, duration, @@ -105,7 +105,7 @@ public final class AdvertisingSet { @RequiresPermission(BLUETOOTH_ADVERTISE) public void setAdvertisingData(AdvertiseData advertiseData) { try { - mGatt.setAdvertisingData(mAdvertiserId, advertiseData, mAttributionSource); + mAdvertise.setAdvertisingData(mAdvertiserId, advertiseData, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -125,7 +125,7 @@ public final class AdvertisingSet { @RequiresPermission(BLUETOOTH_ADVERTISE) public void setScanResponseData(AdvertiseData scanResponse) { try { - mGatt.setScanResponseData(mAdvertiserId, scanResponse, mAttributionSource); + mAdvertise.setScanResponseData(mAdvertiserId, scanResponse, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -149,7 +149,7 @@ public final class AdvertisingSet { conditional = true) public void setAdvertisingParameters(AdvertisingSetParameters parameters) { try { - mGatt.setAdvertisingParameters(mAdvertiserId, parameters, mAttributionSource); + mAdvertise.setAdvertisingParameters(mAdvertiserId, parameters, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -165,7 +165,8 @@ public final class AdvertisingSet { @RequiresPermission(BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters) { try { - mGatt.setPeriodicAdvertisingParameters(mAdvertiserId, parameters, mAttributionSource); + mAdvertise.setPeriodicAdvertisingParameters( + mAdvertiserId, parameters, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -186,7 +187,7 @@ public final class AdvertisingSet { @RequiresPermission(BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingData(AdvertiseData periodicData) { try { - mGatt.setPeriodicAdvertisingData(mAdvertiserId, periodicData, mAttributionSource); + mAdvertise.setPeriodicAdvertisingData(mAdvertiserId, periodicData, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -203,7 +204,7 @@ public final class AdvertisingSet { @RequiresPermission(BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingEnabled(boolean enable) { try { - mGatt.setPeriodicAdvertisingEnable(mAdvertiserId, enable, mAttributionSource); + mAdvertise.setPeriodicAdvertisingEnable(mAdvertiserId, enable, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -223,7 +224,7 @@ public final class AdvertisingSet { }) public void getOwnAddress() { try { - mGatt.getOwnAddress(mAdvertiserId, mAttributionSource); + mAdvertise.getOwnAddress(mAdvertiserId, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } diff --git a/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java index 057053e817..e43838f8be 100644 --- a/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java +++ b/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java @@ -29,7 +29,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothUuid; -import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothAdvertise; import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; import android.content.AttributionSource; @@ -610,10 +610,10 @@ public final class BluetoothLeAdvertiser { throw new IllegalArgumentException("duration out of range: " + duration); } - IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); + IBluetoothAdvertise advertise = mBluetoothAdapter.getBluetoothAdvertise(); - if (gatt == null) { - Log.e(TAG, "Bluetooth GATT is null"); + if (advertise == null) { + Log.e(TAG, "Bluetooth Advertise is null"); postStartSetFailure( handler, callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); return; @@ -626,7 +626,7 @@ public final class BluetoothLeAdvertiser { } try { - gatt.startAdvertisingSet( + advertise.startAdvertisingSet( parameters, advertiseData, scanResponse, @@ -665,13 +665,13 @@ public final class BluetoothLeAdvertiser { return; } - IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); - if (gatt == null) { - Log.e(TAG, "Bluetooth GATT is null"); + IBluetoothAdvertise advertise = mBluetoothAdapter.getBluetoothAdvertise(); + if (advertise == null) { + Log.e(TAG, "Bluetooth Advertise is null"); return; } try { - gatt.stopAdvertisingSet(wrapped, mAttributionSource); + advertise.stopAdvertisingSet(wrapped, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to stop advertising - ", e); } @@ -789,7 +789,7 @@ public final class BluetoothLeAdvertiser { return new IAdvertisingSetCallback.Stub() { @Override public void onAdvertisingSetStarted( - IBinder gattBinder, int advertiserId, int txPower, int status) { + IBinder advertiseBinder, int advertiserId, int txPower, int status) { handler.post( () -> { if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { @@ -800,7 +800,7 @@ public final class BluetoothLeAdvertiser { AdvertisingSet advertisingSet = new AdvertisingSet( - IBluetoothGatt.Stub.asInterface(gattBinder), + IBluetoothAdvertise.Stub.asInterface(advertiseBinder), advertiserId, mBluetoothAdapter, mAttributionSource); diff --git a/framework/java/android/bluetooth/le/BluetoothLeScanner.java b/framework/java/android/bluetooth/le/BluetoothLeScanner.java index 1d6087b19e..c116540496 100644 --- a/framework/java/android/bluetooth/le/BluetoothLeScanner.java +++ b/framework/java/android/bluetooth/le/BluetoothLeScanner.java @@ -348,7 +348,7 @@ public final class BluetoothLeScanner { /** * Start truncated scan. * - * @deprecated this is not used anywhere + * @removed this is not used anywhere * @hide */ @Deprecated diff --git a/framework/java/android/bluetooth/le/DistanceMeasurementManager.java b/framework/java/android/bluetooth/le/DistanceMeasurementManager.java index 1760bc0bf1..b68d3556e4 100644 --- a/framework/java/android/bluetooth/le/DistanceMeasurementManager.java +++ b/framework/java/android/bluetooth/le/DistanceMeasurementManager.java @@ -27,7 +27,7 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IDistanceMeasurement; import android.bluetooth.annotations.RequiresBluetoothConnectPermission; import android.bluetooth.le.ChannelSoundingParams.CsSecurityLevel; import android.content.AttributionSource; @@ -94,12 +94,12 @@ public final class DistanceMeasurementManager { public @NonNull List<DistanceMeasurementMethod> getSupportedMethods() { final List<DistanceMeasurementMethod> supportedMethods = new ArrayList<>(); try { - IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); - if (gatt == null) { - Log.e(TAG, "Bluetooth GATT is null"); + IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement(); + if (distanceMeasurement == null) { + Log.e(TAG, "Distance Measurement is null"); return supportedMethods; } - return gatt.getSupportedDistanceMeasurementMethods(mAttributionSource); + return distanceMeasurement.getSupportedDistanceMeasurementMethods(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to get supported methods - ", e); } @@ -137,14 +137,19 @@ public final class DistanceMeasurementManager { Objects.requireNonNull(executor, "executor is null"); Objects.requireNonNull(callback, "callback is null"); try { - IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); - if (gatt == null) { - Log.e(TAG, "Bluetooth GATT is null"); + IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement(); + if (distanceMeasurement == null) { + Log.e(TAG, "Distance Measurement is null"); return null; } DistanceMeasurementSession session = new DistanceMeasurementSession( - gatt, mUuid, params, executor, mAttributionSource, callback); + distanceMeasurement, + mUuid, + params, + executor, + mAttributionSource, + callback); CancellationSignal cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener(() -> session.stopSession()); @@ -154,7 +159,8 @@ public final class DistanceMeasurementManager { } mSessionMap.put(params.getDevice(), session); - gatt.startDistanceMeasurement(mUuid, params, mCallbackWrapper, mAttributionSource); + distanceMeasurement.startDistanceMeasurement( + mUuid, params, mCallbackWrapper, mAttributionSource); return cancellationSignal; } catch (RemoteException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); @@ -185,12 +191,12 @@ public final class DistanceMeasurementManager { Objects.requireNonNull(remoteDevice, "remote device is null"); final int defaultValue = ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN; try { - IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); - if (gatt == null) { - Log.e(TAG, "Bluetooth GATT is null"); + IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement(); + if (distanceMeasurement == null) { + Log.e(TAG, "Distance Measurement is null"); return defaultValue; } - return gatt.getChannelSoundingMaxSupportedSecurityLevel( + return distanceMeasurement.getChannelSoundingMaxSupportedSecurityLevel( remoteDevice, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to get supported security Level - ", e); @@ -217,12 +223,13 @@ public final class DistanceMeasurementManager { public @CsSecurityLevel int getLocalChannelSoundingMaxSupportedSecurityLevel() { final int defaultValue = ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN; try { - IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); - if (gatt == null) { - Log.e(TAG, "Bluetooth GATT is null"); + IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement(); + if (distanceMeasurement == null) { + Log.e(TAG, "Distance Measurement is null"); return defaultValue; } - return gatt.getLocalChannelSoundingMaxSupportedSecurityLevel(mAttributionSource); + return distanceMeasurement.getLocalChannelSoundingMaxSupportedSecurityLevel( + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to get supported security Level - ", e); } @@ -247,12 +254,14 @@ public final class DistanceMeasurementManager { @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) public @NonNull Set<@CsSecurityLevel Integer> getChannelSoundingSupportedSecurityLevels() { try { - IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); - if (gatt == null) { - Log.e(TAG, "Bluetooth GATT is null"); + IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement(); + if (distanceMeasurement == null) { + Log.e(TAG, "Distance Measurement is null"); return Collections.emptySet(); } - return Arrays.stream(gatt.getChannelSoundingSupportedSecurityLevels(mAttributionSource)) + return Arrays.stream( + distanceMeasurement.getChannelSoundingSupportedSecurityLevels( + mAttributionSource)) .boxed() .collect(Collectors.toUnmodifiableSet()); } catch (RemoteException e) { diff --git a/framework/java/android/bluetooth/le/DistanceMeasurementSession.java b/framework/java/android/bluetooth/le/DistanceMeasurementSession.java index a36887ba9b..d0c9547967 100644 --- a/framework/java/android/bluetooth/le/DistanceMeasurementSession.java +++ b/framework/java/android/bluetooth/le/DistanceMeasurementSession.java @@ -28,7 +28,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothStatusCodes; -import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IDistanceMeasurement; import android.bluetooth.annotations.RequiresBluetoothConnectPermission; import android.content.AttributionSource; import android.os.ParcelUuid; @@ -60,7 +60,7 @@ import java.util.concurrent.Executor; public final class DistanceMeasurementSession { private static final String TAG = "DistanceMeasurementSession"; - private final IBluetoothGatt mGatt; + private final IDistanceMeasurement mDistanceMeasurement; private final ParcelUuid mUuid; private final DistanceMeasurementParams mDistanceMeasurementParams; private final Executor mExecutor; @@ -79,13 +79,13 @@ public final class DistanceMeasurementSession { /** @hide */ public DistanceMeasurementSession( - IBluetoothGatt gatt, + IDistanceMeasurement distanceMeasurement, ParcelUuid uuid, DistanceMeasurementParams params, Executor executor, AttributionSource attributionSource, Callback callback) { - mGatt = requireNonNull(gatt); + mDistanceMeasurement = requireNonNull(distanceMeasurement); mDistanceMeasurementParams = requireNonNull(params); mExecutor = requireNonNull(executor); mCallback = requireNonNull(callback); @@ -104,7 +104,7 @@ public final class DistanceMeasurementSession { @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) public @StopSessionReturnValues int stopSession() { try { - return mGatt.stopDistanceMeasurement( + return mDistanceMeasurement.stopDistanceMeasurement( mUuid, mDistanceMeasurementParams.getDevice(), mDistanceMeasurementParams.getMethodId(), diff --git a/framework/java/android/bluetooth/le/ScanRecord.java b/framework/java/android/bluetooth/le/ScanRecord.java index 57bd7445c7..d5b3b46e4e 100644 --- a/framework/java/android/bluetooth/le/ScanRecord.java +++ b/framework/java/android/bluetooth/le/ScanRecord.java @@ -28,8 +28,6 @@ import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; -import com.android.bluetooth.flags.Flags; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -644,18 +642,14 @@ public final class ScanRecord { + (scanRecord[currentPos] & 0xFF); byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2, dataLength - 2); - if (Flags.scanRecordManufacturerDataMerge()) { - if (manufacturerData.contains(manufacturerId)) { - byte[] firstValue = manufacturerData.get(manufacturerId); - ByteBuffer buffer = - ByteBuffer.allocate( - firstValue.length + manufacturerDataBytes.length); - buffer.put(firstValue); - buffer.put(manufacturerDataBytes); - manufacturerData.put(manufacturerId, buffer.array()); - } else { - manufacturerData.put(manufacturerId, manufacturerDataBytes); - } + if (manufacturerData.contains(manufacturerId)) { + byte[] firstValue = manufacturerData.get(manufacturerId); + ByteBuffer buffer = + ByteBuffer.allocate( + firstValue.length + manufacturerDataBytes.length); + buffer.put(firstValue); + buffer.put(manufacturerDataBytes); + manufacturerData.put(manufacturerId, buffer.array()); } else { manufacturerData.put(manufacturerId, manufacturerDataBytes); } diff --git a/framework/tests/bumble/AndroidPhyTest.xml b/framework/tests/bumble/AndroidPhyTest.xml index 4e5eb02e5f..b762ec7480 100644 --- a/framework/tests/bumble/AndroidPhyTest.xml +++ b/framework/tests/bumble/AndroidPhyTest.xml @@ -47,8 +47,8 @@ <!-- Only run if the Bluetooth Mainline module is installed. --> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> - <option name="mainline-module-package-name" value="com.android.btservices" /> - <option name="mainline-module-package-name" value="com.google.android.btservices" /> + <option name="mainline-module-package-name" value="com.android.bt" /> + <option name="mainline-module-package-name" value="com.google.android.bt" /> </object> <!-- Collect Bluetooth snoop logs for each test run --> diff --git a/framework/tests/bumble/AndroidTest.xml b/framework/tests/bumble/AndroidTest.xml index 4855fec134..04aedce061 100644 --- a/framework/tests/bumble/AndroidTest.xml +++ b/framework/tests/bumble/AndroidTest.xml @@ -48,8 +48,8 @@ <!-- Only run if the Bluetooth Mainline module is installed. --> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> - <option name="mainline-module-package-name" value="com.android.btservices" /> - <option name="mainline-module-package-name" value="com.google.android.btservices" /> + <option name="mainline-module-package-name" value="com.android.bt" /> + <option name="mainline-module-package-name" value="com.google.android.bt" /> </object> <!-- Collect Bluetooth snoop logs for each test run --> diff --git a/framework/tests/bumble/src/android/bluetooth/DckL2capTest.kt b/framework/tests/bumble/src/android/bluetooth/DckL2capTest.kt index e658e8c645..ea41c55ee0 100644 --- a/framework/tests/bumble/src/android/bluetooth/DckL2capTest.kt +++ b/framework/tests/bumble/src/android/bluetooth/DckL2capTest.kt @@ -226,6 +226,51 @@ public class DckL2capTest() : Closeable { Log.d(TAG, "testReceive: done") } + @Test + @VirtualOnly + fun testReadReturnOnRemoteSocketDisconnect() { + Log.d(TAG, "testReadReturnonSocketDisconnect: Connect L2CAP") + var bluetoothSocket: BluetoothSocket? + val l2capServer = bluetoothAdapter.listenUsingInsecureL2capChannel() + val socketFlow = flow { emit(l2capServer.accept()) } + val connectResponse = createAndConnectL2capChannelWithBumble(l2capServer.psm) + runBlocking { + bluetoothSocket = socketFlow.first() + assertThat(connectResponse.hasChannel()).isTrue() + } + + val inputStream = bluetoothSocket!!.inputStream + + // block on read() on server thread + val readThread = Thread { + Log.d(TAG, "testReadReturnOnRemoteSocketDisconnect: Receive data on Android") + val ret = inputStream.read() + Log.d(TAG, "testReadReturnOnRemoteSocketDisconnect: read returns : " + ret) + Log.d( + TAG, + "testReadReturnOnRemoteSocketDisconnect: isConnected() : " + + bluetoothSocket!!.isConnected(), + ) + assertThat(ret).isEqualTo(-1) + assertThat(bluetoothSocket!!.isConnected()).isFalse() + } + readThread.start() + // check that socket is still connected + assertThat(bluetoothSocket!!.isConnected()).isTrue() + + // read() would be blocking till underlying l2cap is disconnected + Thread.sleep(1000 * 10) + Log.d(TAG, "testReadReturnOnRemoteSocketDisconnect: disconnect after 10 secs") + val disconnectRequest = + DisconnectRequest.newBuilder().setChannel(connectResponse.channel).build() + val disconnectResponse = mBumble.l2capBlocking().disconnect(disconnectRequest) + assertThat(disconnectResponse.hasSuccess()).isTrue() + inputStream.close() + bluetoothSocket?.close() + l2capServer.close() + Log.d(TAG, "testReadReturnOnRemoteSocketDisconnect: done") + } + private fun createAndConnectL2capChannelWithBumble(psm: Int): ConnectResponse { Log.d(TAG, "createAndConnectL2capChannelWithBumble") val remoteDevice = diff --git a/framework/tests/bumble/src/android/bluetooth/GattClientTest.java b/framework/tests/bumble/src/android/bluetooth/GattClientTest.java index c9fa50bbd9..64ada411d6 100644 --- a/framework/tests/bumble/src/android/bluetooth/GattClientTest.java +++ b/framework/tests/bumble/src/android/bluetooth/GattClientTest.java @@ -720,6 +720,44 @@ public class GattClientTest { } } + @Test + @RequiresFlagsEnabled(Flags.FLAG_UNREGISTER_GATT_CLIENT_DISCONNECTED) + public void connectAndDisconnectManyClientsWithoutClose() throws Exception { + advertiseWithBumble(); + + List<BluetoothGatt> gatts = new ArrayList<>(); + try { + for (int i = 0; i < 100; i++) { + BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class); + InOrder inOrder = inOrder(gattCallback); + + BluetoothGatt gatt = mRemoteLeDevice.connectGatt(mContext, false, gattCallback); + gatts.add(gatt); + + inOrder.verify(gattCallback, timeout(1000)) + .onConnectionStateChange(any(), anyInt(), eq(STATE_CONNECTED)); + + gatt.disconnect(); + inOrder.verify(gattCallback, timeout(1000)) + .onConnectionStateChange( + any(), anyInt(), eq(BluetoothProfile.STATE_DISCONNECTED)); + + gatt.connect(); + inOrder.verify(gattCallback, timeout(1000)) + .onConnectionStateChange(any(), anyInt(), eq(STATE_CONNECTED)); + + gatt.disconnect(); + inOrder.verify(gattCallback, timeout(1000)) + .onConnectionStateChange( + any(), anyInt(), eq(BluetoothProfile.STATE_DISCONNECTED)); + } + } finally { + for (BluetoothGatt gatt : gatts) { + gatt.close(); + } + } + } + private void createLeBondAndWaitBonding(BluetoothDevice device) { advertiseWithBumble(); mHost.createBondAndVerify(device); diff --git a/framework/tests/bumble/src/android/bluetooth/LeScanningTest.java b/framework/tests/bumble/src/android/bluetooth/LeScanningTest.java index 506586bbe3..9ee8c279f1 100644 --- a/framework/tests/bumble/src/android/bluetooth/LeScanningTest.java +++ b/framework/tests/bumble/src/android/bluetooth/LeScanningTest.java @@ -68,7 +68,7 @@ import java.util.stream.Stream; @RunWith(TestParameterInjector.class) public class LeScanningTest { private static final String TAG = "LeScanningTest"; - private static final int TIMEOUT_SCANNING_MS = 2000; + private static final int TIMEOUT_SCANNING_MS = 3000; private static final String TEST_UUID_STRING = "00001805-0000-1000-8000-00805f9b34fb"; private static final String TEST_ADDRESS_RANDOM_STATIC = "F0:43:A8:23:10:11"; private static final String ACTION_DYNAMIC_RECEIVER_SCAN_RESULT = @@ -376,6 +376,7 @@ public class LeScanningTest { // Test against UUIDs that are close to TEST_UUID_STRING, one that has a few bits unset and one // that has an extra bit set. @Test + @VirtualOnly public void startBleScan_withServiceData_uuidDoesntMatch( @TestParameter({"00001800", "00001815"}) String uuid) { advertiseWithBumbleWithServiceData(); diff --git a/framework/tests/bumble/src/android/bluetooth/pairing/PairingTest.java b/framework/tests/bumble/src/android/bluetooth/pairing/PairingTest.java index 44c6564803..d289b7c43e 100644 --- a/framework/tests/bumble/src/android/bluetooth/pairing/PairingTest.java +++ b/framework/tests/bumble/src/android/bluetooth/pairing/PairingTest.java @@ -22,12 +22,8 @@ import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; @@ -43,10 +39,8 @@ import android.bluetooth.StreamObserverSpliterator; import android.bluetooth.Utils; import android.bluetooth.test_utils.BlockingBluetoothAdapter; import android.bluetooth.test_utils.EnableBluetoothRule; -import android.content.BroadcastReceiver; +import android.bluetooth.pairing.utils.IntentReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.os.ParcelUuid; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -63,19 +57,15 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector; import io.grpc.stub.StreamObserver; -import org.hamcrest.Matcher; import org.hamcrest.Matchers; -import org.hamcrest.core.AllOf; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.hamcrest.MockitoHamcrest; import pandora.GattProto; import pandora.HostProto.AdvertiseRequest; @@ -90,9 +80,6 @@ import pandora.SecurityProto.SecureRequest; import pandora.SecurityProto.SecureResponse; import java.time.Duration; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -127,12 +114,9 @@ public class PairingTest { public final EnableBluetoothRule mEnableBluetoothRule = new EnableBluetoothRule(false /* enableTestMode */, true /* toggleBluetooth */); - private final Map<String, Integer> mActionRegistrationCounts = new HashMap<>(); private final StreamObserverSpliterator<PairingEvent> mPairingEventStreamObserver = new StreamObserverSpliterator<>(); - @Mock private BroadcastReceiver mReceiver; @Mock private BluetoothProfile.ServiceListener mProfileServiceListener; - private InOrder mInOrder = null; private BluetoothDevice mBumbleDevice; private BluetoothDevice mRemoteLeDevice; private BluetoothHidHost mHidService; @@ -142,30 +126,6 @@ public class PairingTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - doAnswer( - inv -> { - Log.d( - TAG, - "onReceive(): intent=" + Arrays.toString(inv.getArguments())); - Intent intent = inv.getArgument(1); - String action = intent.getAction(); - if (BluetoothDevice.ACTION_UUID.equals(action)) { - ParcelUuid[] uuids = - intent.getParcelableArrayExtra( - BluetoothDevice.EXTRA_UUID, ParcelUuid.class); - Log.d(TAG, "onReceive(): UUID=" + Arrays.toString(uuids)); - } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) { - int bondState = - intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); - Log.d(TAG, "onReceive(): bondState=" + bondState); - } - return null; - }) - .when(mReceiver) - .onReceive(any(), any()); - - mInOrder = inOrder(mReceiver); - // Get profile proxies mHidService = (BluetoothHidHost) getProfileProxy(BluetoothProfile.HID_HOST); mHfpService = (BluetoothHeadset) getProfileProxy(BluetoothProfile.HEADSET); @@ -175,28 +135,54 @@ public class PairingTest { sAdapter.getRemoteLeDevice( Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM); + /* + * Note: Since there was no IntentReceiver registered, passing the instance as + * NULL in testStep_RemoveBond(). But, if there is an instance already present, that + * must be passed instead of NULL. + */ for (BluetoothDevice device : sAdapter.getBondedDevices()) { - removeBond(device); + testStep_RemoveBond(null, device); } } @After public void tearDown() throws Exception { Set<BluetoothDevice> bondedDevices = sAdapter.getBondedDevices(); + + /* + * Note: Since there was no IntentReceiver registered, passing the instance as + * NULL in testStep_RemoveBond(). But, if there is an instance already present, that + * must be passed instead of NULL. + */ if (bondedDevices.contains(mBumbleDevice)) { - removeBond(mBumbleDevice); + testStep_RemoveBond(null, mBumbleDevice); } if (bondedDevices.contains(mRemoteLeDevice)) { - removeBond(mRemoteLeDevice); + testStep_RemoveBond(null, mRemoteLeDevice); } mBumbleDevice = null; mRemoteLeDevice = null; - if (getTotalActionRegistrationCounts() > 0) { - sTargetContext.unregisterReceiver(mReceiver); - mActionRegistrationCounts.clear(); - } } + /** All the test function goes here */ + + /** + * Process of writing a test function + * + * 1. Create an IntentReceiver object first with following way: + * IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, + * BluetoothDevice.ACTION_1, + * BluetoothDevice.ACTION_2) + * .setIntentListener(--) // optional + * .setIntentTimeout(--) // optional + * .build(); + * 2. Use the intentReceiver instance for all Intent related verification, and pass + * the same instance to all the helper/testStep functions which has similar Intent + * requirements. + * 3. Once all the verification is done, call `intentReceiver.close()` before returning + * from the function. + */ + /** * Test a simple BR/EDR just works pairing flow in the follow steps: * @@ -211,8 +197,10 @@ public class PairingTest { */ @Test public void testBrEdrPairing_phoneInitiatedBrEdrInquiryOnlyJustWorks() { - registerIntentActions( - BluetoothDevice.ACTION_BOND_STATE_CHANGED, BluetoothDevice.ACTION_PAIRING_REQUEST); + IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, + BluetoothDevice.ACTION_BOND_STATE_CHANGED, + BluetoothDevice.ACTION_PAIRING_REQUEST) + .build(); StreamObserver<PairingEventAnswer> pairingEventAnswerObserver = mBumble.security() @@ -220,12 +208,12 @@ public class PairingTest { .onPairing(mPairingEventStreamObserver); assertThat(mBumbleDevice.createBond()).isTrue(); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING)); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra( @@ -238,15 +226,12 @@ public class PairingTest { pairingEventAnswerObserver.onNext( PairingEventAnswer.newBuilder().setEvent(pairingEvent).setConfirm(true).build()); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED)); - verifyNoMoreInteractions(mReceiver); - - unregisterIntentActions( - BluetoothDevice.ACTION_BOND_STATE_CHANGED, BluetoothDevice.ACTION_PAIRING_REQUEST); + intentReceiver.close(); } /** @@ -266,8 +251,10 @@ public class PairingTest { @Test @RequiresFlagsEnabled({Flags.FLAG_IGNORE_UNRELATED_CANCEL_BOND}) public void testBrEdrPairing_cancelBond_forUnrelatedDevice() { - registerIntentActions( - BluetoothDevice.ACTION_BOND_STATE_CHANGED, BluetoothDevice.ACTION_PAIRING_REQUEST); + IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, + BluetoothDevice.ACTION_BOND_STATE_CHANGED, + BluetoothDevice.ACTION_PAIRING_REQUEST) + .build(); StreamObserver<PairingEventAnswer> pairingEventAnswerObserver = mBumble.security() @@ -275,12 +262,12 @@ public class PairingTest { .onPairing(mPairingEventStreamObserver); assertThat(mBumbleDevice.createBond()).isTrue(); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING)); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra( @@ -296,15 +283,12 @@ public class PairingTest { pairingEventAnswerObserver.onNext( PairingEventAnswer.newBuilder().setEvent(pairingEvent).setConfirm(true).build()); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED)); - verifyNoMoreInteractions(mReceiver); - - unregisterIntentActions( - BluetoothDevice.ACTION_BOND_STATE_CHANGED, BluetoothDevice.ACTION_PAIRING_REQUEST); + intentReceiver.close(); } /** @@ -322,10 +306,11 @@ public class PairingTest { */ @Test public void testBrEdrPairing_phoneInitiatedBrEdrInquiryOnlyJustWorksWhileSdpConnected() { - registerIntentActions( + IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, BluetoothDevice.ACTION_ACL_CONNECTED, BluetoothDevice.ACTION_BOND_STATE_CHANGED, - BluetoothDevice.ACTION_PAIRING_REQUEST); + BluetoothDevice.ACTION_PAIRING_REQUEST) + .build(); StreamObserver<PairingEventAnswer> pairingEventAnswerObserver = mBumble.security() @@ -335,17 +320,17 @@ public class PairingTest { // Start SDP. This will create an ACL connection before the bonding starts. assertThat(mBumbleDevice.fetchUuidsWithSdp(BluetoothDevice.TRANSPORT_BREDR)).isTrue(); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_ACL_CONNECTED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice)); assertThat(mBumbleDevice.createBond()).isTrue(); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING)); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra( @@ -358,17 +343,12 @@ public class PairingTest { pairingEventAnswerObserver.onNext( PairingEventAnswer.newBuilder().setEvent(pairingEvent).setConfirm(true).build()); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED)); - verifyNoMoreInteractions(mReceiver); - - unregisterIntentActions( - BluetoothDevice.ACTION_ACL_CONNECTED, - BluetoothDevice.ACTION_BOND_STATE_CHANGED, - BluetoothDevice.ACTION_PAIRING_REQUEST); + intentReceiver.close(); } /** @@ -398,11 +378,13 @@ public class PairingTest { @Test @RequiresFlagsEnabled({Flags.FLAG_PREVENT_DUPLICATE_UUID_INTENT}) public void testCancelBondLe_WithGattServiceDiscovery() { - registerIntentActions(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, + BluetoothDevice.ACTION_BOND_STATE_CHANGED) + .build(); // Outgoing GATT service discovery and incoming LE pairing in parallel StreamObserverSpliterator<SecureResponse> responseObserver = - helper_OutgoingGattServiceDiscoveryWithIncomingLePairing(); + helper_OutgoingGattServiceDiscoveryWithIncomingLePairing(intentReceiver); // Cancel pairing from Android assertThat(mBumbleDevice.cancelBondProcess()).isTrue(); @@ -412,14 +394,12 @@ public class PairingTest { // Pairing should be cancelled in a moment instead of timing out in 30 // seconds - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE)); - verifyNoMoreInteractions(mReceiver); - - unregisterIntentActions(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + intentReceiver.close(); } /** @@ -449,11 +429,13 @@ public class PairingTest { @Test @RequiresFlagsEnabled({Flags.FLAG_PREVENT_DUPLICATE_UUID_INTENT}) public void testBondLe_WithGattServiceDiscovery() { - registerIntentActions(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, + BluetoothDevice.ACTION_BOND_STATE_CHANGED) + .build(); // Outgoing GATT service discovery and incoming LE pairing in parallel StreamObserverSpliterator<SecureResponse> responseObserver = - helper_OutgoingGattServiceDiscoveryWithIncomingLePairing(); + helper_OutgoingGattServiceDiscoveryWithIncomingLePairing(intentReceiver); // Approve pairing from Android assertThat(mBumbleDevice.setPairingConfirmation(true)).isTrue(); @@ -462,14 +444,12 @@ public class PairingTest { assertThat(secureResponse.hasSuccess()).isTrue(); // Ensure that pairing succeeds - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED)); - verifyNoMoreInteractions(mReceiver); - - unregisterIntentActions(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + intentReceiver.close(); } /** @@ -495,9 +475,11 @@ public class PairingTest { */ @Test public void testBondLe_Reconnect() { - registerIntentActions(BluetoothDevice.ACTION_ACL_CONNECTED); + IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, + BluetoothDevice.ACTION_ACL_CONNECTED) + .build(); - testStep_BondLe(mBumbleDevice, OwnAddressType.PUBLIC); + testStep_BondLe(intentReceiver, mBumbleDevice, OwnAddressType.PUBLIC); assertThat(sAdapter.getBondedDevices()).contains(mBumbleDevice); testStep_restartBt(); @@ -521,12 +503,12 @@ public class PairingTest { .build()); assertThat(mBumbleDevice.connect()).isEqualTo(BluetoothStatusCodes.SUCCESS); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_ACL_CONNECTED), hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_LE), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice)); - verifyNoMoreInteractions(mReceiver); - unregisterIntentActions(BluetoothDevice.ACTION_ACL_CONNECTED); + + intentReceiver.close(); } /** @@ -559,98 +541,6 @@ public class PairingTest { } } - private void doTestIdentityAddressWithType( - BluetoothDevice device, OwnAddressType ownAddressType) { - BluetoothAddress identityAddress = device.getIdentityAddressWithType(); - assertThat(identityAddress.getAddress()).isNull(); - assertThat(identityAddress.getAddressType()) - .isEqualTo(BluetoothDevice.ADDRESS_TYPE_UNKNOWN); - - testStep_BondLe(device, ownAddressType); - assertThat(sAdapter.getBondedDevices()).contains(device); - - identityAddress = device.getIdentityAddressWithType(); - assertThat(identityAddress.getAddress()).isEqualTo(device.getAddress()); - assertThat(identityAddress.getAddressType()) - .isEqualTo( - ownAddressType == OwnAddressType.RANDOM - ? BluetoothDevice.ADDRESS_TYPE_RANDOM - : BluetoothDevice.ADDRESS_TYPE_PUBLIC); - } - - private void testStep_BondLe(BluetoothDevice device, OwnAddressType ownAddressType) { - registerIntentActions( - BluetoothDevice.ACTION_BOND_STATE_CHANGED, - BluetoothDevice.ACTION_ACL_CONNECTED, - BluetoothDevice.ACTION_PAIRING_REQUEST); - - mBumble.gattBlocking() - .registerService( - GattProto.RegisterServiceRequest.newBuilder() - .setService( - GattProto.GattServiceParams.newBuilder() - .setUuid(BATTERY_UUID.toString()) - .build()) - .build()); - mBumble.gattBlocking() - .registerService( - GattProto.RegisterServiceRequest.newBuilder() - .setService( - GattProto.GattServiceParams.newBuilder() - .setUuid(HOGP_UUID.toString()) - .build()) - .build()); - - mBumble.hostBlocking() - .advertise( - AdvertiseRequest.newBuilder() - .setLegacy(true) - .setConnectable(true) - .setOwnAddressType(ownAddressType) - .build()); - - StreamObserver<PairingEventAnswer> pairingEventAnswerObserver = - mBumble.security() - .withDeadlineAfter(BOND_INTENT_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS) - .onPairing(mPairingEventStreamObserver); - - assertThat(device.createBond(BluetoothDevice.TRANSPORT_LE)).isTrue(); - - verifyIntentReceivedUnordered( - hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), - hasExtra(BluetoothDevice.EXTRA_DEVICE, device), - hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING)); - verifyIntentReceived( - hasAction(BluetoothDevice.ACTION_ACL_CONNECTED), - hasExtra(BluetoothDevice.EXTRA_DEVICE, device), - hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_LE)); - verifyIntentReceivedUnordered( - hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST), - hasExtra(BluetoothDevice.EXTRA_DEVICE, device), - hasExtra( - BluetoothDevice.EXTRA_PAIRING_VARIANT, - BluetoothDevice.PAIRING_VARIANT_CONSENT)); - - // Approve pairing from Android - assertThat(device.setPairingConfirmation(true)).isTrue(); - - PairingEvent pairingEvent = mPairingEventStreamObserver.iterator().next(); - assertThat(pairingEvent.hasJustWorks()).isTrue(); - pairingEventAnswerObserver.onNext( - PairingEventAnswer.newBuilder().setEvent(pairingEvent).setConfirm(true).build()); - - // Ensure that pairing succeeds - verifyIntentReceived( - hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), - hasExtra(BluetoothDevice.EXTRA_DEVICE, device), - hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED)); - - unregisterIntentActions( - BluetoothDevice.ACTION_BOND_STATE_CHANGED, - BluetoothDevice.ACTION_ACL_CONNECTED, - BluetoothDevice.ACTION_PAIRING_REQUEST); - } - /** * Test if bonded BR/EDR device can reconnect after BT restart * @@ -674,9 +564,11 @@ public class PairingTest { */ @Test public void testBondBredr_Reconnect() { - registerIntentActions(BluetoothDevice.ACTION_ACL_CONNECTED); + IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, + BluetoothDevice.ACTION_ACL_CONNECTED) + .build(); - testStep_BondBredr(); + testStep_BondBredr(intentReceiver); assertThat(sAdapter.getBondedDevices()).contains(mBumbleDevice); testStep_restartBt(); @@ -689,12 +581,12 @@ public class PairingTest { .build(); mBumble.hostBlocking().setConnectabilityMode(request); assertThat(mBumbleDevice.connect()).isEqualTo(BluetoothStatusCodes.SUCCESS); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_ACL_CONNECTED), hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_BREDR), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice)); - verifyNoMoreInteractions(mReceiver); - unregisterIntentActions(BluetoothDevice.ACTION_ACL_CONNECTED); + + intentReceiver.close(); } /** @@ -720,27 +612,27 @@ public class PairingTest { @Test @RequiresFlagsEnabled({Flags.FLAG_WAIT_FOR_DISCONNECT_BEFORE_UNBOND}) public void testRemoveBondLe_WhenConnected() { - registerIntentActions( - BluetoothDevice.ACTION_ACL_DISCONNECTED, BluetoothDevice.ACTION_BOND_STATE_CHANGED); + IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, + BluetoothDevice.ACTION_ACL_DISCONNECTED, + BluetoothDevice.ACTION_BOND_STATE_CHANGED) + .build(); - testStep_BondLe(mBumbleDevice, OwnAddressType.PUBLIC); + testStep_BondLe(intentReceiver, mBumbleDevice, OwnAddressType.PUBLIC); assertThat(sAdapter.getBondedDevices()).contains(mBumbleDevice); assertThat(mBumbleDevice.removeBond()).isTrue(); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_ACL_DISCONNECTED), hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_LE), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice)); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE)); assertThat(sAdapter.getBondedDevices()).doesNotContain(mBumbleDevice); - verifyNoMoreInteractions(mReceiver); - unregisterIntentActions( - BluetoothDevice.ACTION_ACL_DISCONNECTED, BluetoothDevice.ACTION_BOND_STATE_CHANGED); + intentReceiver.close(); } /** @@ -766,27 +658,27 @@ public class PairingTest { @Test @RequiresFlagsEnabled({Flags.FLAG_WAIT_FOR_DISCONNECT_BEFORE_UNBOND}) public void testRemoveBondBredr_WhenConnected() { - registerIntentActions( - BluetoothDevice.ACTION_ACL_DISCONNECTED, BluetoothDevice.ACTION_BOND_STATE_CHANGED); + IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, + BluetoothDevice.ACTION_ACL_DISCONNECTED, + BluetoothDevice.ACTION_BOND_STATE_CHANGED) + .build(); - testStep_BondBredr(); + testStep_BondBredr(intentReceiver); assertThat(sAdapter.getBondedDevices()).contains(mBumbleDevice); assertThat(mBumbleDevice.removeBond()).isTrue(); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_ACL_DISCONNECTED), hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_BREDR), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice)); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE)); assertThat(sAdapter.getBondedDevices()).doesNotContain(mBumbleDevice); - verifyNoMoreInteractions(mReceiver); - unregisterIntentActions( - BluetoothDevice.ACTION_ACL_DISCONNECTED, BluetoothDevice.ACTION_BOND_STATE_CHANGED); + intentReceiver.close(); } /** @@ -813,54 +705,51 @@ public class PairingTest { */ @Test public void testRemoveBondLe_WhenDisconnected() { - registerIntentActions( + IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, BluetoothDevice.ACTION_ACL_DISCONNECTED, BluetoothDevice.ACTION_BOND_STATE_CHANGED, - BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED); + BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED) + .build(); - testStep_BondLe(mBumbleDevice, OwnAddressType.PUBLIC); + testStep_BondLe(intentReceiver, mBumbleDevice, OwnAddressType.PUBLIC); assertThat(sAdapter.getBondedDevices()).contains(mBumbleDevice); // Wait for profiles to get connected - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothHidHost.EXTRA_STATE, BluetoothHidHost.STATE_CONNECTING)); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothHidHost.EXTRA_STATE, BluetoothHidHost.STATE_CONNECTED)); // Disconnect Bumble assertThat(mBumbleDevice.disconnect()).isEqualTo(BluetoothStatusCodes.SUCCESS); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothHidHost.EXTRA_STATE, BluetoothHidHost.STATE_DISCONNECTING)); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothHidHost.EXTRA_STATE, BluetoothHidHost.STATE_DISCONNECTED)); // Wait for ACL to get disconnected - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_ACL_DISCONNECTED), hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_LE), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice)); // Remove bond assertThat(mBumbleDevice.removeBond()).isTrue(); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE)); assertThat(sAdapter.getBondedDevices()).doesNotContain(mBumbleDevice); - verifyNoMoreInteractions(mReceiver); - unregisterIntentActions( - BluetoothDevice.ACTION_ACL_DISCONNECTED, - BluetoothDevice.ACTION_BOND_STATE_CHANGED, - BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED); + intentReceiver.close(); } /** @@ -887,10 +776,11 @@ public class PairingTest { */ @Test public void testRemoveBondBredr_WhenDisconnected() { - registerIntentActions( + IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, BluetoothDevice.ACTION_ACL_DISCONNECTED, BluetoothDevice.ACTION_BOND_STATE_CHANGED, - BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); + BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED) + .build(); // Disable all profiles other than A2DP as profile connections take too long assertThat( @@ -902,15 +792,15 @@ public class PairingTest { mBumbleDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)) .isTrue(); - testStep_BondBredr(); + testStep_BondBredr(intentReceiver); assertThat(sAdapter.getBondedDevices()).contains(mBumbleDevice); // Wait for profiles to get connected - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED), hasExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_CONNECTING), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice)); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED), hasExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_CONNECTED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice)); @@ -921,58 +811,78 @@ public class PairingTest { future.completeOnTimeout(null, TEST_DELAY_MS, TimeUnit.MILLISECONDS).join(); // Disconnect all profiles assertThat(mBumbleDevice.disconnect()).isEqualTo(BluetoothStatusCodes.SUCCESS); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED), hasExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_DISCONNECTING), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice)); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED), hasExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_DISCONNECTED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice)); // Wait for the ACL to get disconnected - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_ACL_DISCONNECTED), hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_BREDR), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice)); // Remove bond assertThat(mBumbleDevice.removeBond()).isTrue(); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE)); assertThat(sAdapter.getBondedDevices()).doesNotContain(mBumbleDevice); - verifyNoMoreInteractions(mReceiver); - unregisterIntentActions( - BluetoothDevice.ACTION_ACL_DISCONNECTED, - BluetoothDevice.ACTION_BOND_STATE_CHANGED, - BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); + intentReceiver.close(); } - private void testStep_BondBredr() { - registerIntentActions( + /** Helper/testStep functions goes here */ + + /** + * Process of writing a helper/test_step function. + * + * 1. All the helper functions should have IntentReceiver instance passed as an + * argument to them (if any intents needs to be registered). + * 2. The caller (if a test function) can initiate a fresh instance of IntentReceiver + * and use it for all subsequent helper/testStep functions. + * 3. The helper function should first register all required intent actions through the + * helper -> IntentReceiver.updateNewIntentActionsInParentReceiver() + * which either modifies the intentReceiver instance, or creates + * one (if the caller has passed a `null`). + * 4. At the end, all functions should call `intentReceiver.close()` which either + * unregisters the recent actions, or frees the original instance as per the call. + */ + + private void testStep_BondBredr(IntentReceiver parentIntentReceiver) { + IntentReceiver intentReceiver = + IntentReceiver.updateNewIntentActionsInParentReceiver( + parentIntentReceiver, + sTargetContext, BluetoothDevice.ACTION_BOND_STATE_CHANGED, BluetoothDevice.ACTION_ACL_CONNECTED, BluetoothDevice.ACTION_PAIRING_REQUEST); StreamObserver<PairingEventAnswer> pairingEventAnswerObserver = mBumble.security() - .withDeadlineAfter(BOND_INTENT_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS) + .withDeadlineAfter(BOND_INTENT_TIMEOUT.toMillis(), + TimeUnit.MILLISECONDS) .onPairing(mPairingEventStreamObserver); - assertThat(mBumbleDevice.createBond(BluetoothDevice.TRANSPORT_BREDR)).isTrue(); + assertThat(mBumbleDevice.createBond(BluetoothDevice.TRANSPORT_BREDR)). + isTrue(); - verifyIntentReceivedUnordered( + intentReceiver.verifyReceived( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), - hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING)); - verifyIntentReceived( + hasExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.BOND_BONDING)); + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_ACL_CONNECTED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), - hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_BREDR)); - verifyIntentReceivedUnordered( + hasExtra(BluetoothDevice.EXTRA_TRANSPORT, + BluetoothDevice.TRANSPORT_BREDR)); + intentReceiver.verifyReceived( hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra( @@ -985,18 +895,18 @@ public class PairingTest { PairingEvent pairingEvent = mPairingEventStreamObserver.iterator().next(); assertThat(pairingEvent.hasJustWorks()).isTrue(); pairingEventAnswerObserver.onNext( - PairingEventAnswer.newBuilder().setEvent(pairingEvent).setConfirm(true).build()); + PairingEventAnswer.newBuilder().setEvent(pairingEvent) + .setConfirm(true).build()); // Ensure that pairing succeeds - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), - hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED)); + hasExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.BOND_BONDED)); - unregisterIntentActions( - BluetoothDevice.ACTION_BOND_STATE_CHANGED, - BluetoothDevice.ACTION_ACL_CONNECTED, - BluetoothDevice.ACTION_PAIRING_REQUEST); + /* Unregisters all intent actions registered in this function */ + intentReceiver.close(); } private void testStep_restartBt() { @@ -1006,9 +916,13 @@ public class PairingTest { /* Starts outgoing GATT service discovery and incoming LE pairing in parallel */ private StreamObserverSpliterator<SecureResponse> - helper_OutgoingGattServiceDiscoveryWithIncomingLePairing() { - // Setup intent filters - registerIntentActions( + helper_OutgoingGattServiceDiscoveryWithIncomingLePairing( + IntentReceiver parentIntentReceiver) { + // Register new actions specific to this helper function + IntentReceiver intentReceiver = + IntentReceiver.updateNewIntentActionsInParentReceiver( + parentIntentReceiver, + sTargetContext, BluetoothDevice.ACTION_BOND_STATE_CHANGED, BluetoothDevice.ACTION_PAIRING_REQUEST, BluetoothDevice.ACTION_UUID, @@ -1027,7 +941,8 @@ public class PairingTest { } // Start GATT service discovery, this will establish LE ACL - assertThat(mBumbleDevice.fetchUuidsWithSdp(BluetoothDevice.TRANSPORT_LE)).isTrue(); + assertThat(mBumbleDevice.fetchUuidsWithSdp(BluetoothDevice.TRANSPORT_LE)) + .isTrue(); // Make Bumble connectable AdvertiseResponse advertiseResponse = @@ -1041,12 +956,13 @@ public class PairingTest { .next(); // Todo: Unexpected empty ACTION_UUID intent is generated - verifyIntentReceivedUnordered(hasAction(BluetoothDevice.ACTION_UUID)); + intentReceiver.verifyReceived(hasAction(BluetoothDevice.ACTION_UUID)); // Wait for connection on Android - verifyIntentReceivedUnordered( + intentReceiver.verifyReceived( hasAction(BluetoothDevice.ACTION_ACL_CONNECTED), - hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_LE)); + hasExtra(BluetoothDevice.EXTRA_TRANSPORT, + BluetoothDevice.TRANSPORT_LE)); // Start pairing from Bumble StreamObserverSpliterator<SecureResponse> responseObserver = @@ -1061,11 +977,12 @@ public class PairingTest { // Wait for incoming pairing notification on Android // TODO: Order of these events is not deterministic - verifyIntentReceivedUnordered( + intentReceiver.verifyReceived( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), - hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING)); - verifyIntentReceivedUnordered( + hasExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.BOND_BONDING)); + intentReceiver.verifyReceived( hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), hasExtra( @@ -1076,7 +993,7 @@ public class PairingTest { assertThat(mBumbleDevice.setPairingConfirmation(true)).isTrue(); // Wait for pairing approval notification on Android - verifyIntentReceivedUnordered( + intentReceiver.verifyReceived( 2, hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST), hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice), @@ -1086,134 +1003,142 @@ public class PairingTest { // Wait for GATT service discovery to complete on Android // so that ACTION_UUID is received here. - verifyIntentReceivedUnordered( + intentReceiver.verifyReceived( hasAction(BluetoothDevice.ACTION_UUID), - hasExtra(BluetoothDevice.EXTRA_UUID, Matchers.hasItemInArray(BATTERY_UUID))); - - unregisterIntentActions( - BluetoothDevice.ACTION_BOND_STATE_CHANGED, - BluetoothDevice.ACTION_PAIRING_REQUEST, - BluetoothDevice.ACTION_UUID, - BluetoothDevice.ACTION_ACL_CONNECTED); + hasExtra(BluetoothDevice.EXTRA_UUID, + Matchers.hasItemInArray(BATTERY_UUID))); + intentReceiver.close(); return responseObserver; } - private void removeBond(BluetoothDevice device) { - registerIntentActions(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + private void testStep_RemoveBond(IntentReceiver parentIntentReceiver, + BluetoothDevice device) { + IntentReceiver intentReceiver = + IntentReceiver.updateNewIntentActionsInParentReceiver( + parentIntentReceiver, + sTargetContext, + BluetoothDevice.ACTION_BOND_STATE_CHANGED); assertThat(device.removeBond()).isTrue(); - verifyIntentReceived( + intentReceiver.verifyReceivedOrdered( hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), hasExtra(BluetoothDevice.EXTRA_DEVICE, device), - hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE)); + hasExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.BOND_NONE)); - unregisterIntentActions(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + intentReceiver.close(); } - @SafeVarargs - private void verifyIntentReceived(Matcher<Intent>... matchers) { - mInOrder.verify(mReceiver, timeout(BOND_INTENT_TIMEOUT.toMillis())) - .onReceive(any(Context.class), MockitoHamcrest.argThat(AllOf.allOf(matchers))); + private BluetoothProfile getProfileProxy(int profile) { + sAdapter.getProfileProxy(sTargetContext, mProfileServiceListener, profile); + ArgumentCaptor<BluetoothProfile> proxyCaptor = + ArgumentCaptor.forClass(BluetoothProfile.class); + verify(mProfileServiceListener, timeout(BOND_INTENT_TIMEOUT.toMillis())) + .onServiceConnected(eq(profile), proxyCaptor.capture()); + return proxyCaptor.getValue(); } - @SafeVarargs - private void verifyIntentReceivedUnordered(int num, Matcher<Intent>... matchers) { - verify(mReceiver, timeout(BOND_INTENT_TIMEOUT.toMillis()).times(num)) - .onReceive(any(Context.class), MockitoHamcrest.argThat(AllOf.allOf(matchers))); - } + private void testStep_BondLe(IntentReceiver parentIntentReceiver, + BluetoothDevice device, OwnAddressType ownAddressType) { + IntentReceiver intentReceiver = + IntentReceiver.updateNewIntentActionsInParentReceiver( + parentIntentReceiver, + sTargetContext, + BluetoothDevice.ACTION_BOND_STATE_CHANGED, + BluetoothDevice.ACTION_ACL_CONNECTED, + BluetoothDevice.ACTION_PAIRING_REQUEST); - @SafeVarargs - private void verifyIntentReceivedUnordered(Matcher<Intent>... matchers) { - verifyIntentReceivedUnordered(1, matchers); - } + mBumble.gattBlocking() + .registerService( + GattProto.RegisterServiceRequest.newBuilder() + .setService( + GattProto.GattServiceParams.newBuilder() + .setUuid(BATTERY_UUID.toString()) + .build()) + .build()); + mBumble.gattBlocking() + .registerService( + GattProto.RegisterServiceRequest.newBuilder() + .setService( + GattProto.GattServiceParams.newBuilder() + .setUuid(HOGP_UUID.toString()) + .build()) + .build()); - /** - * Helper function to add reference count to registered intent actions - * - * @param actions new intent actions to add. If the array is empty, it is a no-op. - */ - private void registerIntentActions(String... actions) { - if (actions.length == 0) { - return; - } - if (getTotalActionRegistrationCounts() > 0) { - Log.d(TAG, "registerIntentActions(): unregister ALL intents"); - sTargetContext.unregisterReceiver(mReceiver); - } - for (String action : actions) { - mActionRegistrationCounts.merge(action, 1, Integer::sum); - } - IntentFilter filter = new IntentFilter(); - mActionRegistrationCounts.entrySet().stream() - .filter(entry -> entry.getValue() > 0) - .forEach( - entry -> { - Log.d( - TAG, - "registerIntentActions(): Registering action = " - + entry.getKey()); - filter.addAction(entry.getKey()); - }); - sTargetContext.registerReceiver(mReceiver, filter); - } + mBumble.hostBlocking() + .advertise( + AdvertiseRequest.newBuilder() + .setLegacy(true) + .setConnectable(true) + .setOwnAddressType(ownAddressType) + .build()); - /** - * Helper function to reduce reference count to registered intent actions If total reference - * count is zero after removal, no broadcast receiver will be registered. - * - * @param actions intent actions to be removed. If some action is not registered, it is no-op - * for that action. If the actions array is empty, it is also a no-op. - */ - private void unregisterIntentActions(String... actions) { - if (actions.length == 0) { - return; - } - if (getTotalActionRegistrationCounts() <= 0) { - return; - } - Log.d(TAG, "unregisterIntentActions(): unregister ALL intents"); - sTargetContext.unregisterReceiver(mReceiver); - for (String action : actions) { - if (!mActionRegistrationCounts.containsKey(action)) { - continue; - } - mActionRegistrationCounts.put(action, mActionRegistrationCounts.get(action) - 1); - if (mActionRegistrationCounts.get(action) <= 0) { - mActionRegistrationCounts.remove(action); - } - } - if (getTotalActionRegistrationCounts() > 0) { - IntentFilter filter = new IntentFilter(); - mActionRegistrationCounts.entrySet().stream() - .filter(entry -> entry.getValue() > 0) - .forEach( - entry -> { - Log.d( - TAG, - "unregisterIntentActions(): Registering action = " - + entry.getKey()); - filter.addAction(entry.getKey()); - }); - sTargetContext.registerReceiver(mReceiver, filter); - } - } + StreamObserver<PairingEventAnswer> pairingEventAnswerObserver = + mBumble.security() + .withDeadlineAfter(BOND_INTENT_TIMEOUT.toMillis(), + TimeUnit.MILLISECONDS) + .onPairing(mPairingEventStreamObserver); - /** - * Get sum of reference count from all registered actions - * - * @return sum of reference count from all registered actions - */ - private int getTotalActionRegistrationCounts() { - return mActionRegistrationCounts.values().stream().reduce(0, Integer::sum); + assertThat(device.createBond(BluetoothDevice.TRANSPORT_LE)).isTrue(); + + intentReceiver.verifyReceived( + hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), + hasExtra(BluetoothDevice.EXTRA_DEVICE, device), + hasExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.BOND_BONDING)); + intentReceiver.verifyReceivedOrdered( + hasAction(BluetoothDevice.ACTION_ACL_CONNECTED), + hasExtra(BluetoothDevice.EXTRA_DEVICE, device), + hasExtra(BluetoothDevice.EXTRA_TRANSPORT, + BluetoothDevice.TRANSPORT_LE)); + intentReceiver.verifyReceived( + hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST), + hasExtra(BluetoothDevice.EXTRA_DEVICE, device), + hasExtra( + BluetoothDevice.EXTRA_PAIRING_VARIANT, + BluetoothDevice.PAIRING_VARIANT_CONSENT)); + + // Approve pairing from Android + assertThat(device.setPairingConfirmation(true)).isTrue(); + + PairingEvent pairingEvent = mPairingEventStreamObserver.iterator().next(); + assertThat(pairingEvent.hasJustWorks()).isTrue(); + pairingEventAnswerObserver.onNext( + PairingEventAnswer.newBuilder().setEvent(pairingEvent) + .setConfirm(true).build()); + + // Ensure that pairing succeeds + intentReceiver.verifyReceivedOrdered( + hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED), + hasExtra(BluetoothDevice.EXTRA_DEVICE, device), + hasExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.BOND_BONDED)); + + intentReceiver.close(); } - private BluetoothProfile getProfileProxy(int profile) { - sAdapter.getProfileProxy(sTargetContext, mProfileServiceListener, profile); - ArgumentCaptor<BluetoothProfile> proxyCaptor = - ArgumentCaptor.forClass(BluetoothProfile.class); - verify(mProfileServiceListener, timeout(BOND_INTENT_TIMEOUT.toMillis())) - .onServiceConnected(eq(profile), proxyCaptor.capture()); - return proxyCaptor.getValue(); + private void doTestIdentityAddressWithType(BluetoothDevice device, + OwnAddressType ownAddressType) { + BluetoothAddress identityAddress = device.getIdentityAddressWithType(); + assertThat(identityAddress.getAddress()).isNull(); + assertThat(identityAddress.getAddressType()) + .isEqualTo(BluetoothDevice.ADDRESS_TYPE_UNKNOWN); + + /* + * Note: Since there was no IntentReceiver registered, passing the + * instance as NULL. But, if there is an instance already present, that + * must be passed instead of NULL. + */ + testStep_BondLe(null, device, ownAddressType); + assertThat(sAdapter.getBondedDevices()).contains(device); + + identityAddress = device.getIdentityAddressWithType(); + assertThat(identityAddress.getAddress()).isEqualTo(device.getAddress()); + assertThat(identityAddress.getAddressType()) + .isEqualTo( + ownAddressType == OwnAddressType.RANDOM + ? BluetoothDevice.ADDRESS_TYPE_RANDOM + : BluetoothDevice.ADDRESS_TYPE_PUBLIC); } } diff --git a/framework/tests/bumble/src/android/bluetooth/pairing/utils/IntentReceiver.java b/framework/tests/bumble/src/android/bluetooth/pairing/utils/IntentReceiver.java new file mode 100644 index 0000000000..ef8ab310dd --- /dev/null +++ b/framework/tests/bumble/src/android/bluetooth/pairing/utils/IntentReceiver.java @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2025 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.pairing.utils; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import com.google.common.collect.Iterators; +import org.hamcrest.Matcher; +import org.hamcrest.core.AllOf; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.hamcrest.MockitoHamcrest; + +import java.time.Duration; +import java.util.Arrays; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; + +/** + * IntentReceiver helps in managing the Intents received through the Broadcast + * receiver, with specific intent actions registered. + * It uses Builder pattern for instance creation, and also allows setting up + * a custom listener's onReceive(). + * + * Use the following way to create an instance of the IntentReceiver. + * IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext, + * BluetoothDevice.ACTION_1, + * BluetoothDevice.ACTION_2) + * .setIntentListener(--) // optional + * .setIntentTimeout(--) // optional + * .build(); + * + * Ordered and unordered verification mechanisms are also provided through public methods. + */ + +public class IntentReceiver { + private static final String TAG = IntentReceiver.class.getSimpleName(); + + /** Interface for listening & processing the received intents */ + public interface IntentListener { + /** + * Callback for receiving intents + * + * @param intent Received intent + */ + void onReceive(Intent intent); + } + + @Mock private BroadcastReceiver mReceiver; + + /** Intent timeout value, can be configured through constructor, or setter method */ + private final Duration mIntentTimeout; + + /** To verify the received intents in-order */ + private final InOrder mInOrder; + private final Context mContext; + private final String[] mIntentStrings; + private final Deque<IntentFilter> mDqIntentFilter; + /* + * Note: Since we are using Builder pattern, also add new variables added + * to the Builder class + */ + + /** Listener for the received intents */ + private final IntentListener mIntentListener; + + /** + * Creates an Intent receiver from the builder instance + * Note: This is a private constructor, so always prepare IntentReceiver's + * instance through Builder(). + * + * @param builder Pre-built builder instance + */ + private IntentReceiver(Builder builder) { + this.mIntentTimeout = builder.mIntentTimeout; + this.mContext = builder.mContext; + this.mIntentStrings = builder.mIntentStrings; + this.mIntentListener = builder.mIntentListener; + + /* Perform other calls required for instantiation */ + MockitoAnnotations.initMocks(this); + mInOrder = inOrder(mReceiver); + mDqIntentFilter = new ArrayDeque<>(); + mDqIntentFilter.addFirst(prepareIntentFilter(mIntentStrings)); + + setupListener(); + registerReceiver(); + } + + /** Private constructor to avoid creation of IntentReceiver instance directly */ + private IntentReceiver() { + mIntentTimeout = null; + mInOrder = null; + mContext = null; + mIntentStrings = null; + mDqIntentFilter = null; + mIntentListener = null; + } + + /** + * Builder class which helps in avoiding overloading constructors (as the class grows) + * Usage: + * new IntentReceiver.Builder(ARGS) + * .setterMethods() **Optional calls, as these are default params + * .build(); + */ + public static class Builder { + /** + * Add all the instance variables from IntentReceiver, + * which needs to be initiated from the constructor, + * with either default, or user provided value. + */ + private final Context mContext; + private final String[] mIntentStrings; + + /** Non-final variables as there are setters available */ + private Duration mIntentTimeout; + private IntentListener mIntentListener; + + /** + * Private default constructor to avoid creation of Builder default + * instance directly as we need some instance variables to be initiated + * with user defined values. + */ + private Builder() { + mContext = null; + mIntentStrings = null; + } + + /** + * Creates a Builder instance with following required params + * + * @param context Context + * @param intentStrings Array of intents to filter and register + */ + public Builder(@NonNull Context context, String... intentStrings) { + mContext = context; + mIntentStrings = requireNonNull(intentStrings, + "IntentReceiver.Builder(): Intent string cannot be null"); + + if (mIntentStrings.length == 0) { + throw new RuntimeException("IntentReceiver.Builder(): No intents to register"); + } + + /* Default values for remaining vars */ + mIntentTimeout = Duration.ofSeconds(10); + mIntentListener = null; + } + + public Builder setIntentListener(IntentListener intentListener) { + mIntentListener = intentListener; + return this; + } + + public Builder setIntentTimeout(Duration intentTimeout) { + mIntentTimeout = intentTimeout; + return this; + } + + /** + * Builds and returns the IntentReceiver object with all the passed, + * and default params supplied to Builder(). + */ + public IntentReceiver build() { + return new IntentReceiver(this); + } + } + + /** + * Verifies if the intent is received in order + * + * @param matchers Matchers + */ + public void verifyReceivedOrdered(Matcher<Intent>... matchers) { + mInOrder.verify(mReceiver, timeout(mIntentTimeout.toMillis())) + .onReceive(any(Context.class), MockitoHamcrest.argThat(AllOf.allOf(matchers))); + } + + /** + * Verifies if requested number of intents are received (unordered) + * + * @param num Number of intents + * @param matchers Matchers + */ + public void verifyReceived(int num, Matcher<Intent>... matchers) { + verify(mReceiver, timeout(mIntentTimeout.toMillis()).times(num)) + .onReceive(any(Context.class), MockitoHamcrest.argThat(AllOf.allOf(matchers))); + } + + /** + * Verifies if the intent is received (unordered) + * + * @param matchers Matchers + */ + public void verifyReceived(Matcher<Intent>... matchers) { + verifyReceived(1, matchers); + } + + /** + * This function will make sure that the instance is properly cleared + * based on the registered actions. + * Note: This function MUST be called before returning from the caller function, + * as this either unregisters the latest registered actions, or free resources. + */ + public void close() { + Log.d(TAG, "close(): " + mDqIntentFilter.size()); + + /* More than 1 IntentFilters are present */ + if(mDqIntentFilter.size() > 1) { + /* + * It represents there are IntentFilters present to be rolled back. + * So, unregister and roll back to previous IntentFilter. + */ + unregisterRecentAllIntentActions(); + } + else { + /* + * It represents that this close() is called in the scope of creation of + * the object, and hence there is only 1 IntentFilter which is present. + * So, we can safely close this instance. + */ + verifyNoMoreInteractions(); + unregisterReceiver(); + } + } + + /** + * Registers the new actions passed as argument. + * 1. Unregister the receiver, and in turn old IntentFilter. + * 2. Creates a new IntentFilter from the String[], and treat that as latest. + * 3. Registers the new IntentFilter with the receiver to the current context. + */ + public void registerIntentActions(String... intentStrings) { + IntentFilter intentFilter = prepareIntentFilter(intentStrings); + + unregisterReceiver(); + /* Pushes the new intentFilter to top to make it the latest registered */ + mDqIntentFilter.addFirst(intentFilter); + registerReceiver(); + } + + /** + * Helper function to register intent actions, and get the IntentReceiver + * instance. + * + * @param parentIntentReceiver IntentReceiver instance from the parent test caller + * This should be `null` if there is no parent IntentReceiver instance. + * @param targetContext Context instance + * @param intentStrings Intent actions string array + * + * This should be used to register new intent actions in a testStep + * function always. + */ + public static IntentReceiver updateNewIntentActionsInParentReceiver( + IntentReceiver parentIntentReceiver, Context targetContext, String... intentStrings) { + /* + * If parentIntentReceiver is NULL, it indicates that the caller + * is a fresh test/testStep and a new IntentReceiver will be returned. + * else, update the intent actions and return the same instance. + */ + // Create a new instance for the current test/testStep function. + if(parentIntentReceiver == null) + return new IntentReceiver.Builder(targetContext, intentStrings) + .build(); + + /* Update the intent actions in the parent IntentReceiver instance */ + parentIntentReceiver.registerIntentActions(intentStrings); + return parentIntentReceiver; + } + + /** Helper functions are added below, usually private */ + + /** Registers the listener for the received intents, and perform a custom logic as required */ + private void setupListener() { + doAnswer( + inv -> { + Log.d( + TAG, + "onReceive(): intent=" + + Arrays.toString(inv.getArguments())); + + if (mIntentListener == null) return null; + + Intent intent = inv.getArgument(1); + + /* Custom `onReceive` will be provided by the caller */ + mIntentListener.onReceive(intent); + return null; + }) + .when(mReceiver) + .onReceive(any(), any()); + } + + private IntentFilter prepareIntentFilter(String... intentStrings) { + IntentFilter intentFilter = new IntentFilter(); + for (String intentString : intentStrings) { + intentFilter.addAction(intentString); + } + + return intentFilter; + } + + /** + * Registers the latest intent filter which is at the deque.peekFirst() + * Note: The mDqIntentFilter must not be empty here. + */ + private void registerReceiver() { + Log.d(TAG, "registerReceiver(): Registering for intents: " + + getActionsFromIntentFilter(mDqIntentFilter.peekFirst())); + + /* ArrayDeque should not be empty at all while registering a receiver */ + assertThat(mDqIntentFilter.isEmpty()).isFalse(); + mContext.registerReceiver(mReceiver, + (IntentFilter)mDqIntentFilter.peekFirst()); + } + + /** + * Unregisters the receiver from the list of active receivers. + * Also, we can now re-use the same receiver, or register a new + * receiver with the same or different intent filter, the old + * registration is no longer valid. + * Source: Intents and intent filters (Android Developers) + */ + private void unregisterReceiver() { + Log.d(TAG, "unregisterReceiver()"); + mContext.unregisterReceiver(mReceiver); + } + + /** Verifies that no more intents are received */ + private void verifyNoMoreInteractions() { + Log.d(TAG, "verifyNoMoreInteractions()"); + Mockito.verifyNoMoreInteractions(mReceiver); + } + + /** + * Registers the new actions passed as argument. + * 1. Unregister the receiver, and in turn new IntentFilter. + * 2. Pops the new IntentFilter to roll-back to the old one. + * 3. Registers the old IntentFilter with the receiver to the current context. + */ + private void unregisterRecentAllIntentActions() { + assertThat(mDqIntentFilter.isEmpty()).isFalse(); + + unregisterReceiver(); + /* Restores the previous intent filter, and discard the latest */ + mDqIntentFilter.removeFirst(); + registerReceiver(); + } + + /** + * Helper function to get the actions from the IntentFilter + * + * @param intentFilter IntentFilter instance + * + * This is a helper function to get the actions from the IntentFilter, + * and return as a String. + */ + private String getActionsFromIntentFilter( + IntentFilter intentFilter) { + Iterator<String> iterator = intentFilter.actionsIterator(); + StringBuilder allIntentActions = new StringBuilder(); + while (iterator.hasNext()) { + allIntentActions.append(iterator.next() + ", "); + } + + return allIntentActions.toString(); + } +}
\ No newline at end of file diff --git a/framework/tests/unit/AndroidTest.xml b/framework/tests/unit/AndroidTest.xml index f3e71f3b75..2f675b852e 100644 --- a/framework/tests/unit/AndroidTest.xml +++ b/framework/tests/unit/AndroidTest.xml @@ -39,7 +39,7 @@ <!-- Only run FrameworkBluetoothTests in MTS if the Bluetooth Mainline module is installed. --> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> - <option name="mainline-module-package-name" value="com.android.btservices" /> - <option name="mainline-module-package-name" value="com.google.android.btservices" /> + <option name="mainline-module-package-name" value="com.android.bt" /> + <option name="mainline-module-package-name" value="com.google.android.bt" /> </object> </configuration> diff --git a/framework/tests/unit/src/android/bluetooth/BluetoothActivityEnergyInfoTest.java b/framework/tests/unit/src/android/bluetooth/BluetoothActivityEnergyInfoTest.java index 75f604fe36..eb8e074953 100644 --- a/framework/tests/unit/src/android/bluetooth/BluetoothActivityEnergyInfoTest.java +++ b/framework/tests/unit/src/android/bluetooth/BluetoothActivityEnergyInfoTest.java @@ -74,7 +74,7 @@ public class BluetoothActivityEnergyInfoTest { traffics.add(traffic); info.setUidTraffic(traffics); - assertThat(info.getUidTraffic().size()).isEqualTo(1); + assertThat(info.getUidTraffic()).hasSize(1); assertThat(info.getUidTraffic().get(0)).isEqualTo(traffic); } diff --git a/framework/tests/unit/src/android/bluetooth/le/ScanRecordTest.java b/framework/tests/unit/src/android/bluetooth/le/ScanRecordTest.java index a690e510f5..4292583191 100644 --- a/framework/tests/unit/src/android/bluetooth/le/ScanRecordTest.java +++ b/framework/tests/unit/src/android/bluetooth/le/ScanRecordTest.java @@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import android.os.ParcelUuid; import android.platform.test.flag.junit.SetFlagsRule; -import com.android.bluetooth.flags.Flags; import com.android.modules.utils.BytesMatcher; import org.junit.Rule; @@ -195,8 +194,6 @@ public class ScanRecordTest { @Test public void testParserMultipleManufacturerSpecificData() { - mSetFlagsRule.enableFlags(Flags.FLAG_SCAN_RECORD_MANUFACTURER_DATA_MERGE); - byte[] scanRecord = new byte[] { 0x02, diff --git a/framework/tests/util/src/android/bluetooth/cts/TestUtils.java b/framework/tests/util/src/android/bluetooth/cts/TestUtils.java index 4d3a39fe60..dd1e777164 100644 --- a/framework/tests/util/src/android/bluetooth/cts/TestUtils.java +++ b/framework/tests/util/src/android/bluetooth/cts/TestUtils.java @@ -30,8 +30,6 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.test.platform.app.InstrumentationRegistry; -import com.google.errorprone.annotations.InlineMe; - public class TestUtils extends android.bluetooth.test_utils.TestUtils { /** * Get the current enabled status of a given profile. @@ -161,23 +159,6 @@ public class TestUtils extends android.bluetooth.test_utils.TestUtils { } /** - * Utility method to assert two byte arrays are equal. - * - * @param expected expected value - * @param actual actual value - * @deprecated Please use {@link com.google.common.truth.Truth}, - * "assertThat(actual).isEqualTo(expected)". Keeping it here since some tests are still - * using it. - */ - @Deprecated - @InlineMe( - replacement = "assertThat(actual).isEqualTo(expected)", - staticImports = "com.google.common.truth.Truth.assertThat") - public static void assertArrayEquals(byte[] expected, byte[] actual) { - assertThat(actual).isEqualTo(expected); - } - - /** * DANGER: Put the current thread to sleep. Please only use this when it is ok to block the * current thread. * diff --git a/offload/hal/Android.bp b/offload/hal/Android.bp new file mode 100644 index 0000000000..91324d0d94 --- /dev/null +++ b/offload/hal/Android.bp @@ -0,0 +1,48 @@ +// Copyright 2025, 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +rust_library { + name: "libbluetooth_offload_hal", + vendor_available: true, + crate_name: "bluetooth_offload_hal", + crate_root: "lib.rs", + edition: "2021", + rustlibs: [ + "android.hardware.bluetooth-V1-rust", + "libbinder_rs", + "libbluetooth_offload_hci", + "liblog_rust", + "liblogger", + ], + visibility: [ + "//hardware/interfaces/bluetooth:__subpackages__", + "//packages/modules/Bluetooth/offload:__subpackages__", + ], +} + +cc_library_headers { + name: "libbluetooth_offload_hal_headers", + vendor_available: true, + host_supported: true, + export_include_dirs: [ + "include", + ], + visibility: [ + "//hardware/interfaces/bluetooth:__subpackages__", + ], +} diff --git a/offload/hal/ffi.rs b/offload/hal/ffi.rs new file mode 100644 index 0000000000..762e3c9d6b --- /dev/null +++ b/offload/hal/ffi.rs @@ -0,0 +1,279 @@ +// 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. + +use core::{ffi::c_void, slice}; +use std::sync::{Mutex, RwLock}; + +/// Callbacks from C to Rust +/// `handle` is allocated as an `Option<T: Callbacks>`; It must be valid from the +/// `CInterface.initialize()` call to the `CInterface.close()` call. This value +/// is returned as the first parameter to all other functions. +/// To prevent scheduling issues from the HAL Implementer, we enforce the validity +/// until the end of `Ffi<T>` instance; aka until the end of process life. +#[repr(C)] +#[allow(dead_code)] +pub struct CCallbacks { + handle: *const c_void, + initialization_complete: unsafe extern "C" fn(*mut c_void, CStatus), + event_received: unsafe extern "C" fn(*mut c_void, *const u8, usize), + acl_received: unsafe extern "C" fn(*mut c_void, *const u8, usize), + sco_received: unsafe extern "C" fn(*mut c_void, *const u8, usize), + iso_received: unsafe extern "C" fn(*mut c_void, *const u8, usize), +} + +/// C Interface called from Rust +/// `handle` is a pointer initialized by the C code and passed to all other functions. +/// `callbacks` is only valid during the `initialize()` call. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct CInterface { + handle: *mut c_void, + initialize: unsafe extern "C" fn(handle: *mut c_void, callbacks: *const CCallbacks), + close: unsafe extern "C" fn(handle: *mut c_void), + send_command: unsafe extern "C" fn(handle: *mut c_void, data: *const u8, len: usize), + send_acl: unsafe extern "C" fn(handle: *mut c_void, data: *const u8, len: usize), + send_sco: unsafe extern "C" fn(handle: *mut c_void, data: *const u8, len: usize), + send_iso: unsafe extern "C" fn(handle: *mut c_void, data: *const u8, len: usize), +} + +//SAFETY: CInterface is safe to send between threads because we require the C code +// which initialises it to only use pointers to functions which are safe +// to call from any thread. +unsafe impl Send for CInterface {} + +#[repr(C)] +#[allow(dead_code)] +#[derive(Debug, PartialEq)] +pub(crate) enum CStatus { + Success, + AlreadyInitialized, + UnableToOpenInterface, + HardwareInitializationError, + Unknown, +} + +pub(crate) trait Callbacks: DataCallbacks { + fn initialization_complete(&self, status: CStatus); +} + +pub(crate) trait DataCallbacks: Send + Sync { + fn event_received(&self, data: &[u8]); + fn acl_received(&self, data: &[u8]); + fn sco_received(&self, data: &[u8]); + fn iso_received(&self, data: &[u8]); +} + +pub(crate) struct Ffi<T: Callbacks> { + intf: Mutex<CInterface>, + wrapper: RwLock<Option<T>>, +} + +impl<T: Callbacks> Ffi<T> { + pub(crate) fn new(intf: CInterface) -> Self { + Self { intf: Mutex::new(intf), wrapper: RwLock::new(None) } + } + + pub(crate) fn initialize(&self, client: T) { + let intf = self.intf.lock().unwrap(); + self.set_client(client); + + // SAFETY: The C Code has initialized the `CInterface` with a valid + // function pointer. + unsafe { + (intf.initialize)(intf.handle, &CCallbacks::new(&self.wrapper)); + } + } + + pub(crate) fn send_command(&self, data: &[u8]) { + let intf = self.intf.lock().unwrap(); + + // SAFETY: The C Code has initialized the `CInterface` with a valid + // function pointer and an initialized `handle`. + unsafe { + (intf.send_command)(intf.handle, data.as_ptr(), data.len()); + } + } + + pub(crate) fn send_acl(&self, data: &[u8]) { + let intf = self.intf.lock().unwrap(); + + // SAFETY: The C Code has initialized the `CInterface` with a valid + // function pointer and an initialized `handle`. + unsafe { + (intf.send_acl)(intf.handle, data.as_ptr(), data.len()); + } + } + + pub(crate) fn send_iso(&self, data: &[u8]) { + let intf = self.intf.lock().unwrap(); + + // SAFETY: The C Code has initialized the `CInterface` with a valid + // function pointer and an initialized `handle`. + unsafe { + (intf.send_iso)(intf.handle, data.as_ptr(), data.len()); + } + } + + pub(crate) fn send_sco(&self, data: &[u8]) { + let intf = self.intf.lock().unwrap(); + + // SAFETY: The C Code has initialized the `CInterface` with a valid + // function pointer and an initialized `handle`. + unsafe { + (intf.send_sco)(intf.handle, data.as_ptr(), data.len()); + } + } + + pub(crate) fn close(&self) { + let intf = self.intf.lock().unwrap(); + + // SAFETY: The C Code has initialized the `CInterface` with a valid + // function pointer and an initialized `handle`. + unsafe { + (intf.close)(intf.handle); + } + self.remove_client(); + } + + fn set_client(&self, client: T) { + *self.wrapper.write().unwrap() = Some(client); + } + + fn remove_client(&self) { + *self.wrapper.write().unwrap() = None; + } +} + +impl CCallbacks { + fn new<T: Callbacks>(wrapper: &RwLock<Option<T>>) -> Self { + Self { + handle: (wrapper as *const RwLock<Option<T>>).cast(), + initialization_complete: Self::initialization_complete::<T>, + event_received: Self::event_received::<T>, + acl_received: Self::acl_received::<T>, + sco_received: Self::sco_received::<T>, + iso_received: Self::iso_received::<T>, + } + } + + /// #Safety + /// + /// `handle` must be a valid pointer previously passed to the corresponding `initialize()`, + /// and not yet destroyed (this is in fact an `RwLock<Option<T>>`). + unsafe fn unwrap_client<T: Callbacks, F: FnOnce(&T)>(handle: *mut c_void, f: F) { + let wrapper: *const RwLock<Option<T>> = handle.cast(); + + // SAFETY: The `handle` points the `RwLock<Option<T>>` wrapper object; it was allocated + // at the creation of the `Ffi` object and remain alive until its destruction. + if let Some(client) = unsafe { &*(*wrapper).read().unwrap() } { + f(client); + } else { + log::error!("FFI Callback called in bad state"); + } + } + + /// #Safety + /// + /// The C Interface requires that `handle` is a copy of the value given in `CCallbacks.handle` + unsafe extern "C" fn initialization_complete<T: Callbacks>( + handle: *mut c_void, + status: CStatus, + ) { + // SAFETY: The vendor HAL returns `handle` pointing `wrapper` object which has + // the same lifetime as the base `Ffi` instance. + unsafe { + Self::unwrap_client(handle, |client: &T| client.initialization_complete(status)); + } + } + + /// #Safety + /// + /// The C Interface requires that `handle` is a copy of the value given in `CCallbacks.handle`. + /// `data` must be a valid pointer to at least `len` bytes of memory, which remains valid and + /// is not mutated for the duration of this call. + unsafe extern "C" fn event_received<T: Callbacks>( + handle: *mut c_void, + data: *const u8, + len: usize, + ) { + // SAFETY: The C code returns `handle` pointing `wrapper` object which has + // the same lifetime as the base `Ffi` instance. `data` points to a buffer + // of `len` bytes valid until the function returns. + unsafe { + Self::unwrap_client(handle, |client: &T| { + client.event_received(slice::from_raw_parts(data, len)) + }); + } + } + + /// #Safety + /// + /// The C Interface requires that `handle` is a copy of the value given in `CCallbacks.handle`. + /// `data` must be a valid pointer to at least `len` bytes of memory, which remains valid and + /// is not mutated for the duration of this call. + unsafe extern "C" fn acl_received<T: Callbacks>( + handle: *mut c_void, + data: *const u8, + len: usize, + ) { + // SAFETY: The C code returns `handle` pointing `wrapper` object which has + // the same lifetime as the base `Ffi` instance. `data` points to a buffer + // of `len` bytes valid until the function returns. + unsafe { + Self::unwrap_client(handle, |client: &T| { + client.acl_received(slice::from_raw_parts(data, len)) + }); + } + } + + /// #Safety + /// + /// The C Interface requires that `handle` is a copy of the value given in `CCallbacks.handle`. + /// `data` must be a valid pointer to at least `len` bytes of memory, which remains valid and + /// is not mutated for the duration of this call. + unsafe extern "C" fn sco_received<T: Callbacks>( + handle: *mut c_void, + data: *const u8, + len: usize, + ) { + // SAFETY: The C code returns `handle` pointing `wrapper` object which has + // the same lifetime as the base `Ffi` instance. `data` points to a buffer + // of `len` bytes valid until the function returns. + unsafe { + Self::unwrap_client(handle, |client: &T| { + client.sco_received(slice::from_raw_parts(data, len)) + }); + } + } + + /// #Safety + /// + /// The C Interface requires that `handle` is a copy of the value given in `CCallbacks.handle`. + /// `data` must be a valid pointer to at least `len` bytes of memory, which remains valid and + /// is not mutated for the duration of this call. + unsafe extern "C" fn iso_received<T: Callbacks>( + handle: *mut c_void, + data: *const u8, + len: usize, + ) { + // SAFETY: The C code returns `handle` pointing `wrapper` object which has + // the same lifetime as the base `Ffi` instance. `data` points to a buffer + // of `len` bytes valid until the function returns. + unsafe { + Self::unwrap_client(handle, |client: &T| { + client.iso_received(slice::from_raw_parts(data, len)) + }); + } + } +} diff --git a/offload/hal/include/hal/ffi.h b/offload/hal/include/hal/ffi.h new file mode 100644 index 0000000000..e066c68a09 --- /dev/null +++ b/offload/hal/include/hal/ffi.h @@ -0,0 +1,62 @@ +/** + * 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. + */ + +extern "C" { + +#include <stddef.h> +#include <stdint.h> + +/** + * Callabcks from C to Rust + * The given `handle` must be passed as the first parameter of all functions. + * The functions can be called from `hal_interface.initialize()` call to + * `hal_interface.close()` call. + */ + +enum HalStatus { + STATUS_SUCCESS, + STATUS_ALREADY_INITIALIZED, + STATUS_UNABLE_TO_OPEN_INTERFACE, + STATUS_HARDWARE_INITIALIZATION_ERROR, + STATUS_UNKNOWN, +}; + +struct hal_callbacks { + void *handle; + void (*initialization_complete)(const void *handle, enum HalStatus); + void (*event_received)(const void *handle, const uint8_t *data, size_t len); + void (*acl_received)(const void *handle, const uint8_t *data, size_t len); + void (*sco_received)(const void *handle, const uint8_t *data, size_t len); + void (*iso_received)(const void *handle, const uint8_t *data, size_t len); +}; + +/** + * Interface from Rust to C + * The `handle` value is passed as the first parameter of all functions. + * Theses functions can be called from different threads, but NOT concurrently. + * Locking over `handle` is not necessary. + */ + +struct hal_interface { + void *handle; + void (*initialize)(void *handle, const struct hal_callbacks *); + void (*close)(void *handle); + void (*send_command)(void *handle, const uint8_t *data, size_t len); + void (*send_acl)(void *handle, const uint8_t *data, size_t len); + void (*send_sco)(void *handle, const uint8_t *data, size_t len); + void (*send_iso)(void *handle, const uint8_t *data, size_t len); +}; +} diff --git a/offload/hal/lib.rs b/offload/hal/lib.rs new file mode 100644 index 0000000000..76730428fa --- /dev/null +++ b/offload/hal/lib.rs @@ -0,0 +1,22 @@ +// 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. + +//! HCI HAL Binder implementation with proxy integration +//! The Binder HAL interface is replicated as C Interface in `ffi` module + +mod ffi; +mod service; + +pub use ffi::{CCallbacks, CInterface}; +pub use service::HciHalProxy; diff --git a/offload/hal/service.rs b/offload/hal/service.rs new file mode 100644 index 0000000000..779be6da8c --- /dev/null +++ b/offload/hal/service.rs @@ -0,0 +1,245 @@ +// 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. + +use crate::ffi::{CInterface, CStatus, Callbacks, DataCallbacks, Ffi}; +use android_hardware_bluetooth::aidl::android::hardware::bluetooth::{ + IBluetoothHci::IBluetoothHci, IBluetoothHciCallbacks::IBluetoothHciCallbacks, Status::Status, +}; +use binder::{DeathRecipient, ExceptionCode, Interface, Result as BinderResult, Strong}; +use bluetooth_offload_hci::{Module, ModuleBuilder}; +use std::sync::{Arc, RwLock}; + +/// Service Implementation of AIDL interface `hardware/interface/bluetoot/aidl`, +/// including a proxy interface usable by third party modules. +pub struct HciHalProxy { + modules: Vec<Box<dyn ModuleBuilder>>, + ffi: Arc<Ffi<FfiCallbacks>>, + state: Arc<RwLock<State>>, +} + +struct FfiCallbacks { + callbacks: Strong<dyn IBluetoothHciCallbacks>, + proxy: Arc<dyn Module>, + state: Arc<RwLock<State>>, +} + +struct SinkModule<T: Callbacks> { + ffi: Arc<Ffi<T>>, + callbacks: Strong<dyn IBluetoothHciCallbacks>, +} + +enum State { + Closed, + Opening { ffi: Arc<Ffi<FfiCallbacks>>, proxy: Arc<dyn Module> }, + Opened { proxy: Arc<dyn Module>, _death_recipient: DeathRecipient }, +} + +impl Interface for HciHalProxy {} + +impl HciHalProxy { + /// Create the HAL Proxy interface binded to the Bluetooth HCI HAL interface. + pub fn new(modules: Vec<Box<dyn ModuleBuilder>>, cintf: CInterface) -> Self { + Self { + modules, + ffi: Arc::new(Ffi::new(cintf)), + state: Arc::new(RwLock::new(State::Closed)), + } + } +} + +impl IBluetoothHci for HciHalProxy { + fn initialize(&self, callbacks: &Strong<dyn IBluetoothHciCallbacks>) -> BinderResult<()> { + let (ffi, callbacks) = { + let mut state = self.state.write().unwrap(); + + if !matches!(*state, State::Closed) { + let _ = callbacks.initializationComplete(Status::ALREADY_INITIALIZED); + return Ok(()); + } + + let mut proxy: Arc<dyn Module> = + Arc::new(SinkModule::new(self.ffi.clone(), callbacks.clone())); + for m in self.modules.iter().rev() { + proxy = m.build(proxy); + } + let callbacks = FfiCallbacks::new(callbacks.clone(), proxy.clone(), self.state.clone()); + + *state = State::Opening { ffi: self.ffi.clone(), proxy: proxy.clone() }; + (self.ffi.clone(), callbacks) + }; + + ffi.initialize(callbacks); + Ok(()) + } + + fn close(&self) -> BinderResult<()> { + *self.state.write().unwrap() = State::Closed; + self.ffi.close(); + Ok(()) + } + + fn sendHciCommand(&self, data: &[u8]) -> BinderResult<()> { + let State::Opened { ref proxy, .. } = *self.state.read().unwrap() else { + return Err(ExceptionCode::ILLEGAL_STATE.into()); + }; + + proxy.out_cmd(data); + Ok(()) + } + + fn sendAclData(&self, data: &[u8]) -> BinderResult<()> { + let State::Opened { ref proxy, .. } = *self.state.read().unwrap() else { + return Err(ExceptionCode::ILLEGAL_STATE.into()); + }; + + proxy.out_acl(data); + Ok(()) + } + + fn sendScoData(&self, data: &[u8]) -> BinderResult<()> { + let State::Opened { ref proxy, .. } = *self.state.read().unwrap() else { + return Err(ExceptionCode::ILLEGAL_STATE.into()); + }; + + proxy.out_sco(data); + Ok(()) + } + + fn sendIsoData(&self, data: &[u8]) -> BinderResult<()> { + let State::Opened { ref proxy, .. } = *self.state.read().unwrap() else { + return Err(ExceptionCode::ILLEGAL_STATE.into()); + }; + + proxy.out_iso(data); + Ok(()) + } +} + +impl<T: Callbacks> SinkModule<T> { + pub(crate) fn new(ffi: Arc<Ffi<T>>, callbacks: Strong<dyn IBluetoothHciCallbacks>) -> Self { + Self { ffi, callbacks } + } +} + +impl<T: Callbacks> Module for SinkModule<T> { + fn next(&self) -> &dyn Module { + unreachable!() + } + + fn out_cmd(&self, data: &[u8]) { + self.ffi.send_command(data); + } + fn out_acl(&self, data: &[u8]) { + self.ffi.send_acl(data); + } + fn out_iso(&self, data: &[u8]) { + self.ffi.send_iso(data); + } + fn out_sco(&self, data: &[u8]) { + self.ffi.send_sco(data); + } + + fn in_evt(&self, data: &[u8]) { + if let Err(e) = self.callbacks.hciEventReceived(data) { + log::error!("Cannot send event to client: {:?}", e); + } + } + fn in_acl(&self, data: &[u8]) { + if let Err(e) = self.callbacks.aclDataReceived(data) { + log::error!("Cannot send ACL to client: {:?}", e); + } + } + fn in_sco(&self, data: &[u8]) { + if let Err(e) = self.callbacks.scoDataReceived(data) { + log::error!("Cannot send SCO to client: {:?}", e); + } + } + fn in_iso(&self, data: &[u8]) { + if let Err(e) = self.callbacks.isoDataReceived(data) { + log::error!("Cannot send ISO to client: {:?}", e); + } + } +} + +impl FfiCallbacks { + fn new( + callbacks: Strong<dyn IBluetoothHciCallbacks>, + proxy: Arc<dyn Module>, + state: Arc<RwLock<State>>, + ) -> Self { + Self { callbacks, proxy, state } + } +} + +impl Callbacks for FfiCallbacks { + fn initialization_complete(&self, status: CStatus) { + let mut state = self.state.write().unwrap(); + match status { + CStatus::Success => { + let State::Opening { ref ffi, ref proxy } = *state else { + panic!("Initialization completed called in bad state"); + }; + + *state = State::Opened { + proxy: proxy.clone(), + _death_recipient: { + let (ffi, state) = (ffi.clone(), self.state.clone()); + DeathRecipient::new(move || { + log::info!("Bluetooth stack has died"); + *state.write().unwrap() = State::Closed; + ffi.close(); + }) + }, + }; + } + + CStatus::AlreadyInitialized => panic!("Initialization completed called in bad state"), + _ => *state = State::Closed, + }; + + if let Err(e) = self.callbacks.initializationComplete(status.into()) { + log::error!("Cannot call-back client: {:?}", e); + } + } +} + +impl DataCallbacks for FfiCallbacks { + fn event_received(&self, data: &[u8]) { + self.proxy.in_evt(data); + } + + fn acl_received(&self, data: &[u8]) { + self.proxy.in_acl(data); + } + + fn sco_received(&self, data: &[u8]) { + self.proxy.in_sco(data); + } + + fn iso_received(&self, data: &[u8]) { + self.proxy.in_iso(data); + } +} + +impl From<CStatus> for Status { + fn from(value: CStatus) -> Self { + match value { + CStatus::Success => Status::SUCCESS, + CStatus::AlreadyInitialized => Status::ALREADY_INITIALIZED, + CStatus::UnableToOpenInterface => Status::UNABLE_TO_OPEN_INTERFACE, + CStatus::HardwareInitializationError => Status::HARDWARE_INITIALIZATION_ERROR, + CStatus::Unknown => Status::UNKNOWN, + } + } +} diff --git a/offload/hci/Android.bp b/offload/hci/Android.bp new file mode 100644 index 0000000000..323e9245b6 --- /dev/null +++ b/offload/hci/Android.bp @@ -0,0 +1,53 @@ +// Copyright 2025, 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +rust_proc_macro { + name: "libbluetooth_offload_hci_derive", + crate_name: "bluetooth_offload_hci_derive", + crate_root: "derive/lib.rs", + edition: "2021", + rustlibs: [ + "libproc_macro2", + "libquote", + "libsyn", + ], +} + +rust_defaults { + name: "bluetooth_offload_hci_defaults", + crate_root: "lib.rs", + crate_name: "bluetooth_offload_hci", + edition: "2021", + proc_macros: [ + "libbluetooth_offload_hci_derive", + ], + visibility: [ + "//packages/modules/Bluetooth/offload:__subpackages__", + ], +} + +rust_library { + name: "libbluetooth_offload_hci", + defaults: ["bluetooth_offload_hci_defaults"], + vendor_available: true, +} + +rust_test_host { + name: "libbluetooth_offload_hci_test", + defaults: ["bluetooth_offload_hci_defaults"], +} diff --git a/offload/hci/command.rs b/offload/hci/command.rs new file mode 100644 index 0000000000..a4bbe6f701 --- /dev/null +++ b/offload/hci/command.rs @@ -0,0 +1,544 @@ +// 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. + +use crate::derive::{Read, Write}; +use crate::reader::{Read, Reader}; +use crate::writer::{pack, Write, Writer}; + +/// HCI Command, as defined in Part E - 5.4.1 +#[derive(Debug)] +pub enum Command { + /// 7.3.2 Reset Command + Reset(Reset), + /// 7.8.97 LE Set CIG Parameters + LeSetCigParameters(LeSetCigParameters), + /// 7.8.99 LE Create CIS + LeCreateCis(LeCreateCis), + /// 7.8.100 LE Remove CIG + LeRemoveCig(LeRemoveCig), + /// 7.8.103 LE Create BIG + LeCreateBig(LeCreateBig), + /// 7.8.109 LE Setup ISO Data Path + LeSetupIsoDataPath(LeSetupIsoDataPath), + /// 7.8.110 LE Remove ISO Data Path + LeRemoveIsoDataPath(LeRemoveIsoDataPath), + /// Unknown command + Unknown(OpCode), +} + +/// HCI Command Return Parameters +#[derive(Debug, Read, Write)] +pub enum ReturnParameters { + /// 7.3.2 Reset Command + Reset(ResetComplete), + /// 7.8.2 LE Read Buffer Size [V1] + LeReadBufferSizeV1(LeReadBufferSizeV1Complete), + /// 7.8.2 LE Read Buffer Size [V2] + LeReadBufferSizeV2(LeReadBufferSizeV2Complete), + /// 7.8.97 LE Set CIG Parameters + LeSetCigParameters(LeSetCigParametersComplete), + /// 7.8.100 LE Remove CIG + LeRemoveCig(LeRemoveCigComplete), + /// 7.8.109 LE Setup ISO Data Path + LeSetupIsoDataPath(LeIsoDataPathComplete), + /// 7.8.110 LE Remove ISO Data Path + LeRemoveIsoDataPath(LeIsoDataPathComplete), + /// Unknown command + Unknown(OpCode), +} + +impl Command { + /// Read an HCI Command packet + pub fn from_bytes(data: &[u8]) -> Result<Self, Option<OpCode>> { + fn parse_packet(data: &[u8]) -> Option<(OpCode, Reader)> { + let mut r = Reader::new(data); + let opcode = r.read()?; + let len = r.read_u8()? as usize; + Some((opcode, Reader::new(r.get(len)?))) + } + + let Some((opcode, mut r)) = parse_packet(data) else { + return Err(None); + }; + Self::dispatch_read(opcode, &mut r).ok_or(Some(opcode)) + } + + fn dispatch_read(opcode: OpCode, r: &mut Reader) -> Option<Command> { + Some(match opcode { + Reset::OPCODE => Self::Reset(r.read()?), + LeSetCigParameters::OPCODE => Self::LeSetCigParameters(r.read()?), + LeCreateCis::OPCODE => Self::LeCreateCis(r.read()?), + LeRemoveCig::OPCODE => Self::LeRemoveCig(r.read()?), + LeCreateBig::OPCODE => Self::LeCreateBig(r.read()?), + LeSetupIsoDataPath::OPCODE => Self::LeSetupIsoDataPath(r.read()?), + LeRemoveIsoDataPath::OPCODE => Self::LeRemoveIsoDataPath(r.read()?), + opcode => Self::Unknown(opcode), + }) + } + + fn to_bytes<T: CommandOpCode + Write>(command: &T) -> Vec<u8> { + let mut w = Writer::new(Vec::with_capacity(3 + 255)); + w.write(&T::OPCODE); + w.write_u8(0); + w.write(command); + + let mut vec = w.into_vec(); + vec[2] = (vec.len() - 3).try_into().unwrap(); + vec + } +} + +/// OpCode of HCI Command, as defined in Part E - 5.4.1 +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct OpCode(u16); + +impl OpCode { + /// OpCode from OpCode Group Field (OGF) and OpCode Command Field (OCF). + pub const fn from(ogf: u16, ocf: u16) -> Self { + Self(pack!((ocf, 10), (ogf, 6))) + } +} + +impl From<u16> for OpCode { + fn from(v: u16) -> Self { + OpCode(v) + } +} + +impl Read for OpCode { + fn read(r: &mut Reader) -> Option<Self> { + Some(r.read_u16()?.into()) + } +} + +impl Write for OpCode { + fn write(&self, w: &mut Writer) { + w.write_u16(self.0) + } +} + +/// Define command OpCode +pub trait CommandOpCode { + /// OpCode of the command + const OPCODE: OpCode; +} + +/// Build command from definition +pub trait CommandToBytes: CommandOpCode + Write { + /// Output the HCI Command packet + fn to_bytes(&self) -> Vec<u8> + where + Self: Sized + CommandOpCode + Write; +} + +pub use defs::*; + +#[allow(missing_docs)] +#[rustfmt::skip] +mod defs { + +use super::*; +use crate::derive::CommandToBytes; +use crate::status::*; + +#[cfg(test)] +use crate::{Event, EventToBytes}; + + +// 7.3.2 Reset Command + +impl CommandOpCode for Reset { + const OPCODE: OpCode = OpCode::from(0x03, 0x003); +} + +#[derive(Debug, Read, Write, CommandToBytes)] +pub struct Reset {} + +#[derive(Debug, Read, Write)] +pub struct ResetComplete { + pub status: Status, +} + +#[test] +fn test_reset() { + let dump = [0x03, 0x0c, 0x00]; + let Ok(Command::Reset(c)) = Command::from_bytes(&dump) else { panic!() }; + assert_eq!(c.to_bytes(), &dump[..]); +} + +#[test] +fn test_reset_complete() { + let dump = [0x0e, 0x04, 0x01, 0x03, 0x0c, 0x00]; + let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; + let ReturnParameters::Reset(ref p) = e.return_parameters else { panic!() }; + assert_eq!(p.status, Status::Success); + assert_eq!(e.to_bytes(), &dump[..]); +} + + +// 7.8.2 LE Read Buffer Size + +impl CommandOpCode for LeReadBufferSizeV1 { + const OPCODE: OpCode = OpCode::from(0x08, 0x002); +} + +#[derive(Debug)] +pub struct LeReadBufferSizeV1; + +#[derive(Debug, Read, Write)] +pub struct LeReadBufferSizeV1Complete { + pub status: Status, + pub le_acl_data_packet_length: u16, + pub total_num_le_acl_data_packets: u8, +} + +#[test] +fn test_le_read_buffer_size_v1_complete() { + let dump = [0x0e, 0x07, 0x01, 0x02, 0x20, 0x00, 0xfb, 0x00, 0x0f]; + let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; + let ReturnParameters::LeReadBufferSizeV1(ref p) = e.return_parameters else { panic!() }; + assert_eq!(p.status, Status::Success); + assert_eq!(p.le_acl_data_packet_length, 251); + assert_eq!(p.total_num_le_acl_data_packets, 15); + assert_eq!(e.to_bytes(), &dump[..]); +} + +impl CommandOpCode for LeReadBufferSizeV2 { + const OPCODE: OpCode = OpCode::from(0x08, 0x060); +} + +#[derive(Debug)] +pub struct LeReadBufferSizeV2; + +#[derive(Debug, Read, Write)] +pub struct LeReadBufferSizeV2Complete { + pub status: Status, + pub le_acl_data_packet_length: u16, + pub total_num_le_acl_data_packets: u8, + pub iso_data_packet_length: u16, + pub total_num_iso_data_packets: u8, +} + +#[test] +fn test_le_read_buffer_size_v2_complete() { + let dump = [0x0e, 0x0a, 0x01, 0x60, 0x20, 0x00, 0xfb, 0x00, 0x0f, 0xfd, 0x03, 0x18]; + let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; + let ReturnParameters::LeReadBufferSizeV2(ref p) = e.return_parameters else { panic!() }; + assert_eq!(p.status, Status::Success); + assert_eq!(p.le_acl_data_packet_length, 251); + assert_eq!(p.total_num_le_acl_data_packets, 15); + assert_eq!(p.iso_data_packet_length, 1021); + assert_eq!(p.total_num_iso_data_packets, 24); + assert_eq!(e.to_bytes(), &dump[..]); +} + + +// 7.8.97 LE Set CIG Parameters + +impl CommandOpCode for LeSetCigParameters { + const OPCODE: OpCode = OpCode::from(0x08, 0x062); +} + +#[derive(Debug, Read, Write, CommandToBytes)] +pub struct LeSetCigParameters { + pub cig_id: u8, + #[N(3)] pub sdu_interval_c_to_p: u32, + #[N(3)] pub sdu_interval_p_to_c: u32, + pub worst_case_sca: u8, + pub packing: u8, + pub framing: u8, + pub max_transport_latency_c_to_p: u16, + pub max_transport_latency_p_to_c: u16, + pub cis: Vec<LeCisInCigParameters>, +} + +#[derive(Debug, Read, Write)] +pub struct LeCisInCigParameters { + pub cis_id: u8, + pub max_sdu_c_to_p: u16, + pub max_sdu_p_to_c: u16, + pub phy_c_to_p: u8, + pub phy_p_to_c: u8, + pub rtn_c_to_p: u8, + pub rtn_p_to_c: u8, +} + +#[test] +fn test_le_set_cig_parameters() { + let dump = [ + 0x62, 0x20, 0x21, 0x01, 0x10, 0x27, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x64, 0x00, 0x05, + 0x00, 0x02, 0x00, 0x78, 0x00, 0x00, 0x00, 0x02, 0x03, 0x0d, 0x00, 0x01, 0x78, 0x00, 0x00, 0x00, + 0x02, 0x03, 0x0d, 0x00 + ]; + let Ok(Command::LeSetCigParameters(c)) = Command::from_bytes(&dump) else { panic!() }; + assert_eq!(c.cig_id, 0x01); + assert_eq!(c.sdu_interval_c_to_p, 10_000); + assert_eq!(c.sdu_interval_p_to_c, 0); + assert_eq!(c.worst_case_sca, 1); + assert_eq!(c.packing, 0); + assert_eq!(c.framing, 0); + assert_eq!(c.max_transport_latency_c_to_p, 100); + assert_eq!(c.max_transport_latency_p_to_c, 5); + assert_eq!(c.cis.len(), 2); + assert_eq!(c.cis[0].cis_id, 0); + assert_eq!(c.cis[0].max_sdu_c_to_p, 120); + assert_eq!(c.cis[0].max_sdu_p_to_c, 0); + assert_eq!(c.cis[0].phy_c_to_p, 0x02); + assert_eq!(c.cis[0].phy_p_to_c, 0x03); + assert_eq!(c.cis[0].rtn_c_to_p, 13); + assert_eq!(c.cis[0].rtn_p_to_c, 0); + assert_eq!(c.cis[1].cis_id, 1); + assert_eq!(c.cis[1].max_sdu_c_to_p, 120); + assert_eq!(c.cis[1].max_sdu_p_to_c, 0); + assert_eq!(c.cis[1].phy_c_to_p, 0x02); + assert_eq!(c.cis[1].phy_p_to_c, 0x03); + assert_eq!(c.cis[1].rtn_c_to_p, 13); + assert_eq!(c.cis[1].rtn_p_to_c, 0); + assert_eq!(c.to_bytes(), &dump[..]); +} + +#[derive(Debug, Read, Write)] +pub struct LeSetCigParametersComplete { + pub status: Status, + pub cig_id: u8, + pub connection_handles: Vec<u16>, +} + +#[test] +fn test_le_set_cig_parameters_complete() { + let dump = [0x0e, 0x0a, 0x01, 0x62, 0x20, 0x00, 0x01, 0x02, 0x60, 0x00, 0x61, 0x00]; + let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; + let ReturnParameters::LeSetCigParameters(ref p) = e.return_parameters else { panic!() }; + assert_eq!(p.status, Status::Success); + assert_eq!(p.cig_id, 1); + assert_eq!(p.connection_handles.len(), 2); + assert_eq!(p.connection_handles[0], 0x60); + assert_eq!(p.connection_handles[1], 0x61); + assert_eq!(e.to_bytes(), &dump[..]); +} + + +// 7.8.99 LE Create CIS + +impl CommandOpCode for LeCreateCis { + const OPCODE: OpCode = OpCode::from(0x08, 0x064); +} + +#[derive(Debug, Read, Write, CommandToBytes)] +pub struct LeCreateCis { + pub connection_handles: Vec<CisAclConnectionHandle>, +} + +#[derive(Debug, Read, Write)] +pub struct CisAclConnectionHandle { + pub cis: u16, + pub acl: u16, +} + +#[test] +fn test_le_create_cis () { + let dump = [0x64, 0x20, 0x09, 0x02, 0x60, 0x00, 0x40, 0x00, 0x61, 0x00, 0x41, 0x00]; + let Ok(Command::LeCreateCis(c)) = Command::from_bytes(&dump) else { panic!() }; + assert_eq!(c.connection_handles.len(), 2); + assert_eq!(c.connection_handles[0].cis, 0x60); + assert_eq!(c.connection_handles[0].acl, 0x40); + assert_eq!(c.connection_handles[1].cis, 0x61); + assert_eq!(c.connection_handles[1].acl, 0x41); + assert_eq!(c.to_bytes(), &dump[..]); +} + + +// 7.8.100 LE Remove CIG + +impl CommandOpCode for LeRemoveCig { + const OPCODE: OpCode = OpCode::from(0x08, 0x065); +} + +#[derive(Debug, Read, Write, CommandToBytes)] +pub struct LeRemoveCig { + pub cig_id: u8, +} + +#[derive(Debug, Read, Write)] +pub struct LeRemoveCigComplete { + pub status: Status, + pub cig_id: u8, +} + +#[test] +fn test_le_remove_cig() { + let dump = [0x65, 0x20, 0x01, 0x01]; + let Ok(Command::LeRemoveCig(c)) = Command::from_bytes(&dump) else { panic!() }; + assert_eq!(c.cig_id, 0x01); + assert_eq!(c.to_bytes(), &dump[..]); +} + +#[test] +fn test_le_remove_cig_complete() { + let dump = [0x0e, 0x05, 0x01, 0x65, 0x20, 0x00, 0x01]; + let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; + let ReturnParameters::LeRemoveCig(ref p) = e.return_parameters else { panic!() }; + assert_eq!(p.status, Status::Success); + assert_eq!(p.cig_id, 0x01); + assert_eq!(e.to_bytes(), &dump[..]); +} + + +// 7.8.103 LE Create BIG + +impl CommandOpCode for LeCreateBig { + const OPCODE: OpCode = OpCode::from(0x08, 0x068); +} + +#[derive(Debug, Read, Write, CommandToBytes)] +pub struct LeCreateBig { + pub big_handle: u8, + pub advertising_handle: u8, + pub num_bis: u8, + #[N(3)] pub sdu_interval: u32, + pub max_sdu: u16, + pub max_transport_latency: u16, + pub rtn: u8, + pub phy: u8, + pub packing: u8, + pub framing: u8, + pub encryption: u8, + pub broadcast_code: [u8; 16], +} + +#[test] +fn test_le_create_big() { + let dump = [ + 0x68, 0x20, 0x1f, 0x00, 0x00, 0x02, 0x10, 0x27, 0x00, 0x78, 0x00, 0x3c, 0x00, 0x04, 0x02, 0x00, + 0x00, 0x01, 0x31, 0x32, 0x33, 0x34, 0x31, 0x32, 0x33, 0x34, 0x31, 0x32, 0x33, 0x34, 0x31, 0x32, + 0x33, 0x34 + ]; + let Ok(Command::LeCreateBig(c)) = Command::from_bytes(&dump) else { panic!() }; + assert_eq!(c.big_handle, 0x00); + assert_eq!(c.advertising_handle, 0x00); + assert_eq!(c.num_bis, 2); + assert_eq!(c.sdu_interval, 10_000); + assert_eq!(c.max_sdu, 120); + assert_eq!(c.max_transport_latency, 60); + assert_eq!(c.rtn, 4); + assert_eq!(c.phy, 0x02); + assert_eq!(c.packing, 0x00); + assert_eq!(c.framing, 0x00); + assert_eq!(c.encryption, 1); + assert_eq!(c.broadcast_code, [ + 0x31, 0x32, 0x33, 0x34, 0x31, 0x32, 0x33, 0x34, + 0x31, 0x32, 0x33, 0x34, 0x31, 0x32, 0x33, 0x34 + ]); + assert_eq!(c.to_bytes(), &dump[..]); +} + + +// 7.8.109 LE Setup ISO Data Path + +impl CommandOpCode for LeSetupIsoDataPath { + const OPCODE: OpCode = OpCode::from(0x08, 0x06e); +} + +#[derive(Debug, Read, Write, CommandToBytes)] +pub struct LeSetupIsoDataPath { + pub connection_handle: u16, + pub data_path_direction: LeDataPathDirection, + pub data_path_id: u8, + pub codec_id: LeCodecId, + #[N(3)] pub controller_delay: u32, + pub codec_configuration: Vec<u8>, +} + +#[derive(Debug, PartialEq, Read, Write)] +pub enum LeDataPathDirection { + Input = 0x00, + Output = 0x01, +} + +#[derive(Debug, Read, Write)] +pub struct LeCodecId { + pub coding_format: CodingFormat, + pub company_id: u16, + pub vendor_id: u16, +} + +#[derive(Debug, PartialEq, Read, Write)] +pub enum CodingFormat { + ULawLog = 0x00, + ALawLog = 0x01, + Cvsd = 0x02, + Transparent = 0x03, + LinearPcm = 0x04, + MSbc = 0x05, + Lc3 = 0x06, + G729A = 0x07, + VendorSpecific = 0xff, +} + +#[derive(Debug, Read, Write)] +pub struct LeIsoDataPathComplete { + pub status: Status, + pub connection_handle: u16, +} + +#[test] +fn test_le_setup_iso_data_path() { + let dump = [ + 0x6e, 0x20, 0x0d, 0x60, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ]; + let Ok(Command::LeSetupIsoDataPath(c)) = Command::from_bytes(&dump) else { panic!() }; + assert_eq!(c.connection_handle, 0x60); + assert_eq!(c.data_path_direction, LeDataPathDirection::Input); + assert_eq!(c.data_path_id, 0x00); + assert_eq!(c.codec_id.coding_format, CodingFormat::Transparent); + assert_eq!(c.codec_id.company_id, 0); + assert_eq!(c.codec_id.vendor_id, 0); + assert_eq!(c.controller_delay, 0); + assert_eq!(c.codec_configuration.len(), 0); + assert_eq!(c.to_bytes(), &dump[..]); +} + +#[test] +fn test_le_setup_iso_data_path_complete() { + let dump = [0x0e, 0x06, 0x01, 0x6e, 0x20, 0x00, 0x60, 0x00]; + let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; + let ReturnParameters::LeSetupIsoDataPath(ref p) = e.return_parameters else { panic!() }; + assert_eq!(p.status, Status::Success); + assert_eq!(p.connection_handle, 0x60); + assert_eq!(e.to_bytes(), &dump[..]); +} + + +// 7.8.110 LE Remove ISO Data Path + +impl CommandOpCode for LeRemoveIsoDataPath { + const OPCODE: OpCode = OpCode::from(0x08, 0x06f); +} + +#[derive(Debug, Read, Write, CommandToBytes)] +pub struct LeRemoveIsoDataPath { + pub connection_handle: u16, + pub data_path_direction: u8, +} + +#[test] +fn test_le_remove_iso_data_path() { + let dump = [0x6f, 0x20, 0x03, 0x60, 0x00, 0x01]; + let Ok(Command::LeRemoveIsoDataPath(c)) = Command::from_bytes(&dump) else { panic!() }; + assert_eq!(c.connection_handle, 0x60); + assert_eq!(c.data_path_direction, 0x01); + assert_eq!(c.to_bytes(), &dump[..]); +} + +} diff --git a/offload/hci/data.rs b/offload/hci/data.rs new file mode 100644 index 0000000000..bb4a452a59 --- /dev/null +++ b/offload/hci/data.rs @@ -0,0 +1,204 @@ +// 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. + +use crate::reader::{unpack, Reader}; +use crate::writer::{pack, Write, Writer}; + +/// 5.4.5 ISO Data Packets + +/// Exchange of Isochronous Data between the Host and Controller +#[derive(Debug)] +pub struct IsoData<'a> { + /// Identify the connection + pub connection_handle: u16, + /// Fragmentation of the packet + pub sdu_fragment: IsoSduFragment, + /// Payload + pub payload: &'a [u8], +} + +/// Fragmentation indication of the SDU +#[derive(Debug)] +pub enum IsoSduFragment { + /// First SDU Fragment + First { + /// SDU Header + hdr: IsoSduHeader, + /// Last SDU fragment indication + is_last: bool, + }, + /// Continuous fragment + Continue { + /// Last SDU fragment indication + is_last: bool, + }, +} + +/// SDU Header information, when ISO Data in a first SDU fragment +#[derive(Debug, Default)] +pub struct IsoSduHeader { + /// Optional timestamp in microseconds + pub timestamp: Option<u32>, + /// Sequence number of the SDU + pub sequence_number: u16, + /// Total length of the SDU (sum of all fragments) + pub sdu_length: u16, + /// Only valid from Controller, indicate valid SDU data when 0 + pub status: u16, +} + +impl<'a> IsoData<'a> { + /// Read an HCI ISO Data packet + pub fn from_bytes(data: &'a [u8]) -> Option<Self> { + Self::parse(&mut Reader::new(data)) + } + + /// Output the HCI ISO Data packet + pub fn to_bytes(&self) -> Vec<u8> { + let mut w = Writer::new(Vec::with_capacity(12 + self.payload.len())); + w.write(self); + w.into_vec() + } + + /// New ISO Data packet, including a complete SDU + pub fn new(connection_handle: u16, sequence_number: u16, data: &'a [u8]) -> Self { + Self { + connection_handle, + sdu_fragment: IsoSduFragment::First { + hdr: IsoSduHeader { + sequence_number, + sdu_length: data.len().try_into().unwrap(), + ..Default::default() + }, + is_last: true, + }, + payload: data, + } + } + + fn parse(r: &mut Reader<'a>) -> Option<Self> { + let (connection_handle, pb_flag, ts_present) = unpack!(r.read_u16()?, (12, 2, 1)); + let data_len = unpack!(r.read_u16()?, 14) as usize; + + let sdu_fragment = match pb_flag { + 0b00 => IsoSduFragment::First { + hdr: IsoSduHeader::parse(r, ts_present != 0)?, + is_last: false, + }, + 0b10 => IsoSduFragment::First { + hdr: IsoSduHeader::parse(r, ts_present != 0)?, + is_last: true, + }, + 0b01 => IsoSduFragment::Continue { is_last: false }, + 0b11 => IsoSduFragment::Continue { is_last: true }, + _ => unreachable!(), + }; + let sdu_header_len = Self::sdu_header_len(&sdu_fragment); + if data_len < sdu_header_len { + return None; + } + + Some(Self { connection_handle, sdu_fragment, payload: r.get(data_len - sdu_header_len)? }) + } + + fn sdu_header_len(sdu_fragment: &IsoSduFragment) -> usize { + match sdu_fragment { + IsoSduFragment::First { ref hdr, .. } => 4 * (1 + hdr.timestamp.is_some() as usize), + IsoSduFragment::Continue { .. } => 0, + } + } +} + +impl Write for IsoData<'_> { + fn write(&self, w: &mut Writer) { + let (pb_flag, hdr) = match self.sdu_fragment { + IsoSduFragment::First { ref hdr, is_last: false } => (0b00, Some(hdr)), + IsoSduFragment::First { ref hdr, is_last: true } => (0b10, Some(hdr)), + IsoSduFragment::Continue { is_last: false } => (0b01, None), + IsoSduFragment::Continue { is_last: true } => (0b11, None), + }; + + let ts_present = hdr.is_some() && hdr.unwrap().timestamp.is_some(); + w.write_u16(pack!((self.connection_handle, 12), (pb_flag, 2), ((ts_present as u16), 1))); + + let packet_len = Self::sdu_header_len(&self.sdu_fragment) + self.payload.len(); + w.write_u16(pack!(u16::try_from(packet_len).unwrap(), 14)); + + if let Some(hdr) = hdr { + w.write(hdr); + } + w.put(self.payload); + } +} + +impl IsoSduHeader { + fn parse(r: &mut Reader, ts_present: bool) -> Option<Self> { + let timestamp = match ts_present { + true => Some(r.read_u32::<4>()?), + false => None, + }; + let sequence_number = r.read_u16()?; + let (sdu_length, _, status) = unpack!(r.read_u16()?, (12, 2, 2)); + Some(Self { timestamp, sequence_number, sdu_length, status }) + } +} + +impl Write for IsoSduHeader { + fn write(&self, w: &mut Writer) { + if let Some(timestamp) = self.timestamp { + w.write_u32::<4>(timestamp); + }; + w.write_u16(self.sequence_number); + w.write_u16(pack!((self.sdu_length, 12), (0, 2), (self.status, 2))); + } +} + +#[test] +fn test_iso_data() { + let dump = [ + 0x60, 0x60, 0x80, 0x00, 0x4d, 0xc8, 0xd0, 0x2f, 0x19, 0x03, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xe0, 0x93, 0xe5, 0x28, 0x34, 0x00, 0x00, 0x04, + ]; + let Some(pkt) = IsoData::from_bytes(&dump) else { panic!() }; + assert_eq!(pkt.connection_handle, 0x060); + + let IsoSduFragment::First { ref hdr, is_last } = pkt.sdu_fragment else { panic!() }; + assert_eq!(hdr.timestamp, Some(802_211_917)); + assert_eq!(hdr.sequence_number, 793); + assert_eq!(hdr.sdu_length, 120); + assert!(is_last); + + assert_eq!( + pkt.payload, + &[ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xe0, 0x93, 0xe5, 0x28, 0x34, 0x00, 0x00, 0x04 + ] + ); + assert_eq!(pkt.to_bytes(), &dump[..]); +} diff --git a/offload/hci/derive/enum_data.rs b/offload/hci/derive/enum_data.rs new file mode 100644 index 0000000000..c758fd756c --- /dev/null +++ b/offload/hci/derive/enum_data.rs @@ -0,0 +1,95 @@ +// 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. + +//! Derive of `hci::reader::Read` and `hci::writer::Write` traits on an `enum` +//! +//! ``` +//! #[derive(Read, Write)] +//! enum Example { +//! FirstVariant = 0, +//! SecondVariant = 1, +//! } +//! ``` +//! +//! Produces: +//! +//! ``` +//! impl Read for Example { +//! fn read(r: &mut Reader) -> Option<Self> { +//! match r.read_u8()? { +//! 0 => Some(Self::FirstVariant), +//! 1 => Some(Self::SecondVariant), +//! _ => None, +//! } +//! } +//! } +//! +//! impl Write for Example { +//! fn write(&self, w: &mut Writer) { +//! w.write_u8(match self { +//! Self::FirstVariant => 0, +//! Self::SecondVariant => 1, +//! }) +//! } +//! } +//! ``` + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{spanned::Spanned, Error}; + +pub(crate) fn derive_read(name: &syn::Ident, data: &syn::DataEnum) -> Result<TokenStream, Error> { + let mut variants: Vec<TokenStream> = Vec::new(); + for variant in &data.variants { + let ident = &variant.ident; + let Some((_, ref discriminant)) = variant.discriminant else { + return Err(Error::new(variant.span(), "Missing discriminant")); + }; + variants.push(quote! { #discriminant => Some(Self::#ident) }); + } + variants.push(quote! { _ => None }); + + Ok(quote! { + impl Read for #name { + fn read(r: &mut Reader) -> Option<Self> { + match r.read_u8()? { + #( #variants ),* + } + } + } + }) +} + +pub(crate) fn derive_write(name: &syn::Ident, data: &syn::DataEnum) -> Result<TokenStream, Error> { + let mut variants: Vec<TokenStream> = Vec::new(); + for variant in &data.variants { + let ident = &variant.ident; + let Some((_, ref discriminant)) = variant.discriminant else { + return Err(Error::new(variant.span(), "Missing discriminant")); + }; + variants.push(quote! { Self::#ident => #discriminant }); + } + + Ok(quote! { + impl Write for #name { + fn write(&self, w: &mut Writer) { + w.write_u8( + match self { + #( #variants ),* + } + ) + } + } + }) +} diff --git a/offload/hci/derive/lib.rs b/offload/hci/derive/lib.rs new file mode 100644 index 0000000000..97b5d15789 --- /dev/null +++ b/offload/hci/derive/lib.rs @@ -0,0 +1,89 @@ +// 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. + +//! Derive of traits : +//! - `hci::reader::Read`, `hci::writer::Write` +//! - `hci::command::CommandToBytes` +//! - `hci::event::EventToBytes` + +extern crate proc_macro; +mod enum_data; +mod return_parameters; +mod struct_data; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{DeriveInput, Error}; + +/// Derive of `hci::reader::Read` trait +#[proc_macro_derive(Read, attributes(N))] +pub fn derive_read(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as DeriveInput); + let (ident, data) = (&input.ident, &input.data); + let expanded = match (ident.to_string().as_str(), data) { + ("ReturnParameters", syn::Data::Enum(ref data)) => { + return_parameters::derive_read(ident, data) + } + (_, syn::Data::Enum(ref data)) => enum_data::derive_read(ident, data), + (_, syn::Data::Struct(ref data)) => struct_data::derive_read(ident, data), + (_, _) => panic!("Unsupported kind of input"), + } + .unwrap_or_else(Error::into_compile_error); + TokenStream::from(expanded) +} + +/// Derive of `hci::reader::Write` trait +#[proc_macro_derive(Write)] +pub fn derive_write(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as DeriveInput); + let (ident, data) = (&input.ident, &input.data); + let expanded = match (ident.to_string().as_str(), &data) { + ("ReturnParameters", syn::Data::Enum(ref data)) => { + return_parameters::derive_write(ident, data) + } + (_, syn::Data::Enum(ref data)) => enum_data::derive_write(ident, data), + (_, syn::Data::Struct(ref data)) => struct_data::derive_write(ident, data), + (_, _) => panic!("Unsupported kind of input"), + } + .unwrap_or_else(Error::into_compile_error); + TokenStream::from(expanded) +} + +/// Derive of `hci::command::CommandToBytes` +#[proc_macro_derive(CommandToBytes)] +pub fn derive_command_to_bytes(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as DeriveInput); + let name = &input.ident; + TokenStream::from(quote! { + impl CommandToBytes for #name { + fn to_bytes(&self) -> Vec<u8> { + Command::to_bytes(self) + } + } + }) +} + +/// Derive of `hci::command::EventToBytes` +#[proc_macro_derive(EventToBytes)] +pub fn derive_event_to_bytes(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as DeriveInput); + let name = &input.ident; + TokenStream::from(quote! { + impl EventToBytes for #name { + fn to_bytes(&self) -> Vec<u8> { + Event::to_bytes(self) + } + } + }) +} diff --git a/offload/hci/derive/return_parameters.rs b/offload/hci/derive/return_parameters.rs new file mode 100644 index 0000000000..20baf439c5 --- /dev/null +++ b/offload/hci/derive/return_parameters.rs @@ -0,0 +1,116 @@ +// 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. + +//! Derive of `hci::reader::Read` and `hci::writer::Write` traits on +//! `enum returnReturnParameters`. +//! +//! ``` +//! #[derive(Read, Write)] +//! enum ReturnParameters { +//! CommandOne(CommandOneComplete), +//! CommandTwo(CommandTwoComplete), +//! LastIsDefault(OpCode), +//! } +//! ``` +//! +//! Produces: +//! +//! ``` +//! impl Read for ReturnParameters { +//! fn read(r: &mut Reader) -> Option<Self> { +//! Some(match r.read_u16()?.into() { +//! CommandOne::OPCODE => Self::CommandOne(r.read()?), +//! CommandTwo::OPCODE => Self::CommandTwo(r.read()?), +//! opcode => Self::LastIsDefault(opcode), +//! }) +//! } +//! } +//! +//! impl Write for ReturnParameters { +//! fn write(&self, w: &mut Writer) { +//! match self { +//! Self::CommandOne(p) => { +//! w.write(&CommandOne::OPCODE); +//! w.write(p); +//! } +//! Self::CommandTwo(p) => { +//! w.write(&CommandOne::OPCODE); +//! w.write(p); +//! } +//! Self::LastIsDefault(..) => panic!(), +//! }; +//! } +//! } +//! ``` + +use proc_macro2::TokenStream; +use quote::quote; +use syn::Error; + +pub(crate) fn derive_read(name: &syn::Ident, data: &syn::DataEnum) -> Result<TokenStream, Error> { + let mut variants = Vec::new(); + for (i, variant) in data.variants.iter().enumerate() { + let ident = &variant.ident; + + if i < data.variants.len() - 1 { + variants.push(quote! { + #ident::OPCODE => Self::#ident(r.read()?), + }); + } else { + variants.push(quote! { + opcode => Self::#ident(opcode), + }); + } + } + + Ok(quote! { + impl Read for #name { + fn read(r: &mut Reader) -> Option<Self> { + Some(match r.read_u16()?.into() { + #( #variants )* + }) + } + } + }) +} + +pub(crate) fn derive_write(name: &syn::Ident, data: &syn::DataEnum) -> Result<TokenStream, Error> { + let mut variants = Vec::new(); + for (i, variant) in data.variants.iter().enumerate() { + let ident = &variant.ident; + + if i < data.variants.len() - 1 { + variants.push(quote! { + Self::#ident(p) => { + w.write(&#ident::OPCODE); + w.write(p); + } + }); + } else { + variants.push(quote! { + Self::#ident(..) => panic!(), + }); + } + } + + Ok(quote! { + impl Write for #name { + fn write(&self, w: &mut Writer) { + match self { + #( #variants )* + }; + } + } + }) +} diff --git a/offload/hci/derive/struct_data.rs b/offload/hci/derive/struct_data.rs new file mode 100644 index 0000000000..757a71baec --- /dev/null +++ b/offload/hci/derive/struct_data.rs @@ -0,0 +1,178 @@ +// 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. + +//! Derive of `hci::reader::Read` and `hci::writer::Write` traits on a `struct` +//! +//! ``` +//! #[derive(Read, Write)] +//! struct Example { +//! one_byte: u8, +//! two_bytes: u16, +//! #[N(3)] three_bytes: u32, +//! #[N(4)] four_bytes: u32, +//! bytes: [u8; 123], +//! other: OtherType, +//! } +//! ``` +//! +//! Produces: +//! +//! ``` +//! impl Read for Example { +//! fn read(r: &mut Reader) -> Option<Self> { +//! Some(Self { +//! one_byte: r.read_u8()?, +//! two_bytes: r.read_u16()?, +//! three_bytes: r.read_u32::<3>()?, +//! four_bytes: r.read_u32::<4>()?, +//! bytes: r.read_bytes()?, +//! other_type: r.read()?, +//! }) +//! } +//! } +//! +//! impl Write for Example { +//! fn write(&self, w: &mut Writer) { +//! w.write_u8(self.one_byte); +//! w.write_u16(self.two_bytes); +//! w.write_u32::<3>(self.three_bytes); +//! w.write_u32::<4>(self.four_bytes); +//! w.write_bytes(&self.bytes); +//! w.write(&self.other_type); +//! } +//! } +//! ``` + +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::{spanned::Spanned, Error}; + +struct Attributes { + n: Option<usize>, +} + +impl Attributes { + fn parse(syn_attrs: &[syn::Attribute]) -> Result<Attributes, Error> { + let mut n = None; + for attr in syn_attrs.iter() { + match attr { + attr if attr.path().is_ident("N") => { + let lit: syn::LitInt = attr.parse_args()?; + n = Some(lit.base10_parse()?); + } + attr => return Err(Error::new(attr.span(), "Unrecognized attribute")), + } + } + Ok(Attributes { n }) + } +} + +pub(crate) fn derive_read(name: &syn::Ident, data: &syn::DataStruct) -> Result<TokenStream, Error> { + let mut fields = Vec::new(); + for field in &data.fields { + let ident = &field.ident.as_ref().unwrap(); + let attrs = Attributes::parse(&field.attrs)?; + let fn_token = match &field.ty { + syn::Type::Path(v) if v.path.is_ident("u8") => { + if attrs.n.unwrap_or(1) != 1 { + return Err(Error::new(v.span(), "Expected N(1) for type `u8`")); + } + quote_spanned! { v.span() => read_u8()? } + } + syn::Type::Path(v) if v.path.is_ident("u16") => { + if attrs.n.unwrap_or(2) != 2 { + return Err(Error::new(v.span(), "Expected N(2) for type `u16`")); + } + quote_spanned! { v.span() => read_u16()? } + } + syn::Type::Path(v) if v.path.is_ident("u32") => { + let Some(n) = attrs.n else { + return Err(Error::new(v.span(), "`N()` attribute required")); + }; + if n > 4 { + return Err(Error::new(v.span(), "Expected N(n <= 4)")); + } + quote_spanned! { v.span() => read_u32::<#n>()? } + } + syn::Type::Array(v) => match &*v.elem { + syn::Type::Path(v) if v.path.is_ident("u8") => { + quote_spanned! { v.span() => read_bytes()? } + } + _ => return Err(Error::new(v.elem.span(), "Only Byte array supported")), + }, + ty => quote_spanned! { ty.span() => read()? }, + }; + fields.push(quote! { #ident: r.#fn_token }); + } + + Ok(quote! { + impl Read for #name { + fn read(r: &mut Reader) -> Option<Self> { + Some(Self { + #( #fields ),* + }) + } + } + }) +} + +pub(crate) fn derive_write( + name: &syn::Ident, + data: &syn::DataStruct, +) -> Result<TokenStream, Error> { + let mut fields = Vec::new(); + for field in &data.fields { + let ident = &field.ident.as_ref().unwrap(); + let attrs = Attributes::parse(&field.attrs)?; + let fn_token = match &field.ty { + syn::Type::Path(v) if v.path.is_ident("u8") => { + if attrs.n.unwrap_or(1) != 1 { + return Err(Error::new(v.span(), "Expected N(1) for type `u8`")); + } + quote_spanned! { v.span() => write_u8(self.#ident) } + } + syn::Type::Path(v) if v.path.is_ident("u16") => { + if attrs.n.unwrap_or(2) != 2 { + return Err(Error::new(v.span(), "Expected N(2) for type `u16`")); + } + quote_spanned! { v.span() => write_u16(self.#ident) } + } + syn::Type::Path(v) if v.path.is_ident("u32") => { + let Some(n) = attrs.n else { + return Err(Error::new(v.span(), "`N()` attribute required")); + }; + if n > 4 { + return Err(Error::new(v.span(), "Expected N(n <= 4)")); + } + quote_spanned! { v.span() => write_u32::<#n>(self.#ident) } + } + syn::Type::Array(v) => match &*v.elem { + syn::Type::Path(v) if v.path.is_ident("u8") => { + quote_spanned! { v.span() => write_bytes(&self.#ident) } + } + _ => return Err(Error::new(v.elem.span(), "Only Byte array supported")), + }, + ty => quote_spanned! { ty.span() => write(&self.#ident) }, + }; + fields.push(quote! { w.#fn_token; }); + } + + Ok(quote! { + impl Write for #name { + fn write(&self, w: &mut Writer) { + #( #fields )* + } + } + }) +} diff --git a/offload/hci/event.rs b/offload/hci/event.rs new file mode 100644 index 0000000000..222d0e3546 --- /dev/null +++ b/offload/hci/event.rs @@ -0,0 +1,343 @@ +// 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. + +use crate::reader::{Read, Reader}; +use crate::writer::{Write, Writer}; + +/// HCI Event Packet, as defined in Part E - 5.4.4 +#[derive(Debug)] +pub enum Event { + /// 7.7.5 Disconnection Complete + DisconnectionComplete(DisconnectionComplete), + /// 7.7.14 Command Complete + CommandComplete(CommandComplete), + /// 7.7.15 Command Status + CommandStatus(CommandStatus), + /// 7.7.19 Number Of Completed Packets + NumberOfCompletedPackets(NumberOfCompletedPackets), + /// 7.7.65.25 LE CIS Established + LeCisEstablished(LeCisEstablished), + /// 7.7.65.27 LE Create BIG Complete + LeCreateBigComplete(LeCreateBigComplete), + /// 7.7.65.28 LE Terminate BIG Complete + LeTerminateBigComplete(LeTerminateBigComplete), + /// Unknown Event + Unknown(Code), +} + +impl Event { + /// Read an HCI Event packet + pub fn from_bytes(data: &[u8]) -> Result<Self, Option<Code>> { + fn parse_packet(data: &[u8]) -> Option<(Code, Reader)> { + let mut r = Reader::new(data); + let code = r.read_u8()?; + let len = r.read_u8()? as usize; + + let mut r = Reader::new(r.get(len)?); + let code = match code { + Code::LE_META => Code(Code::LE_META, Some(r.read_u8()?)), + _ => Code(code, None), + }; + + Some((code, r)) + } + + let Some((code, mut r)) = parse_packet(data) else { + return Err(None); + }; + Self::dispatch_read(code, &mut r).ok_or(Some(code)) + } + + fn dispatch_read(code: Code, r: &mut Reader) -> Option<Event> { + Some(match code { + CommandComplete::CODE => Self::CommandComplete(r.read()?), + CommandStatus::CODE => Self::CommandStatus(r.read()?), + DisconnectionComplete::CODE => Self::DisconnectionComplete(r.read()?), + NumberOfCompletedPackets::CODE => Self::NumberOfCompletedPackets(r.read()?), + LeCisEstablished::CODE => Self::LeCisEstablished(r.read()?), + LeCreateBigComplete::CODE => Self::LeCreateBigComplete(r.read()?), + LeTerminateBigComplete::CODE => Self::LeTerminateBigComplete(r.read()?), + code => Self::Unknown(code), + }) + } + + fn to_bytes<T: EventCode + Write>(event: &T) -> Vec<u8> { + let mut w = Writer::new(Vec::with_capacity(2 + 255)); + w.write_u8(T::CODE.0); + w.write_u8(0); + if let Some(sub_code) = T::CODE.1 { + w.write_u8(sub_code) + } + w.write(event); + + let mut vec = w.into_vec(); + vec[1] = (vec.len() - 2).try_into().unwrap(); + vec + } +} + +/// Code of HCI Event, as defined in Part E - 5.4.4 +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Code(u8, Option<u8>); + +impl Code { + const LE_META: u8 = 0x3e; +} + +/// Define event Code +pub trait EventCode { + /// Code of the event + const CODE: Code; +} + +/// Build event from definition +pub trait EventToBytes: EventCode + Write { + /// Output the HCI Event packet + fn to_bytes(&self) -> Vec<u8> + where + Self: Sized + EventCode + Write; +} + +pub use defs::*; + +#[allow(missing_docs)] +#[rustfmt::skip] +mod defs { + +use super::*; +use crate::derive::{Read, Write, EventToBytes}; +use crate::command::{OpCode, ReturnParameters}; +use crate::status::Status; + + +// 7.7.5 Disconnection Complete + +impl EventCode for DisconnectionComplete { + const CODE: Code = Code(0x05, None); +} + +#[derive(Debug, Read, Write, EventToBytes)] +pub struct DisconnectionComplete { + pub status: Status, + pub connection_handle: u16, + pub reason: u8, +} + +#[test] +fn test_disconnection_complete() { + let dump = [0x05, 0x04, 0x00, 0x60, 0x00, 0x16]; + let Ok(Event::DisconnectionComplete(e)) = Event::from_bytes(&dump) else { panic!() }; + assert_eq!(e.status, Status::Success); + assert_eq!(e.connection_handle, 0x60); + assert_eq!(e.reason, 0x16); + assert_eq!(e.to_bytes(), &dump[..]); +} + + +// 7.7.14 Command Complete + +impl EventCode for CommandComplete { + const CODE: Code = Code(0x0e, None); +} + +#[derive(Debug, Read, Write, EventToBytes)] +pub struct CommandComplete { + pub num_hci_command_packets: u8, + pub return_parameters: ReturnParameters, +} + +#[test] +fn test_command_complete() { + let dump = [0x0e, 0x04, 0x01, 0x03, 0x0c, 0x00]; + let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; + assert_eq!(e.num_hci_command_packets, 1); + assert_eq!(e.to_bytes(), &dump[..]); +} + + +// 7.7.15 Command Status + +impl EventCode for CommandStatus { + const CODE: Code = Code(0x0f, None); +} + +#[derive(Debug, Read, Write, EventToBytes)] +pub struct CommandStatus { + pub status: Status, + pub num_hci_command_packets: u8, + pub opcode: OpCode, +} + +#[test] +fn test_command_status() { + let dump = [0x0f, 0x04, 0x00, 0x01, 0x01, 0x04]; + let Ok(Event::CommandStatus(e)) = Event::from_bytes(&dump) else { panic!() }; + assert_eq!(e.status, Status::Success); + assert_eq!(e.num_hci_command_packets, 1); + assert_eq!(e.opcode, OpCode::from(0x01, 0x001)); + assert_eq!(e.to_bytes(), &dump[..]); +} + + +// 7.7.19 Number Of Completed Packets + +impl EventCode for NumberOfCompletedPackets { + const CODE: Code = Code(0x13, None); +} + +#[derive(Debug, Read, Write, EventToBytes)] +pub struct NumberOfCompletedPackets { + pub handles: Vec<NumberOfCompletedPacketsHandle>, +} + +#[derive(Debug, Copy, Clone, Read, Write)] +pub struct NumberOfCompletedPacketsHandle { + pub connection_handle: u16, + pub num_completed_packets: u16, +} + +#[test] +fn test_number_of_completed_packets() { + let dump = [0x13, 0x09, 0x02, 0x40, 0x00, 0x01, 0x00, 0x41, 0x00, 0x01, 0x00]; + let Ok(Event::NumberOfCompletedPackets(e)) = Event::from_bytes(&dump) else { panic!() }; + assert_eq!(e.handles.len(), 2); + assert_eq!(e.handles[0].connection_handle, 0x40); + assert_eq!(e.handles[0].num_completed_packets, 1); + assert_eq!(e.handles[1].connection_handle, 0x41); + assert_eq!(e.handles[1].num_completed_packets, 1); + assert_eq!(e.to_bytes(), &dump[..]); +} + + +// 7.7.65.25 LE CIS Established + +impl EventCode for LeCisEstablished { + const CODE: Code = Code(Code::LE_META, Some(0x19)); +} + +#[derive(Debug, Read, Write, EventToBytes)] +pub struct LeCisEstablished { + pub status: Status, + pub connection_handle: u16, + #[N(3)] pub cig_sync_delay: u32, + #[N(3)] pub cis_sync_delay: u32, + #[N(3)] pub transport_latency_c_to_p: u32, + #[N(3)] pub transport_latency_p_to_c: u32, + pub phy_c_to_p: u8, + pub phy_p_to_c: u8, + pub nse: u8, + pub bn_c_to_p: u8, + pub bn_p_to_c: u8, + pub ft_c_to_p: u8, + pub ft_p_to_c: u8, + pub max_pdu_c_to_p: u16, + pub max_pdu_p_to_c: u16, + pub iso_interval: u16, +} + +#[test] +fn test_le_cis_established() { + let dump = [ + 0x3e, 0x1d, 0x19, 0x00, 0x60, 0x00, 0x40, 0x2c, 0x00, 0x40, 0x2c, 0x00, 0xd0, 0x8b, 0x01, 0x60, + 0x7a, 0x00, 0x02, 0x02, 0x06, 0x02, 0x00, 0x05, 0x01, 0x78, 0x00, 0x00, 0x00, 0x10, 0x00 ]; + let Ok(Event::LeCisEstablished(e)) = Event::from_bytes(&dump) else { panic!() }; + assert_eq!(e.status, Status::Success); + assert_eq!(e.connection_handle, 0x60); + assert_eq!(e.cig_sync_delay, 11_328); + assert_eq!(e.cis_sync_delay, 11_328); + assert_eq!(e.transport_latency_c_to_p, 101_328); + assert_eq!(e.transport_latency_p_to_c, 31_328); + assert_eq!(e.phy_c_to_p, 0x02); + assert_eq!(e.phy_p_to_c, 0x02); + assert_eq!(e.nse, 6); + assert_eq!(e.bn_c_to_p, 2); + assert_eq!(e.bn_p_to_c, 0); + assert_eq!(e.ft_c_to_p, 5); + assert_eq!(e.ft_p_to_c, 1); + assert_eq!(e.max_pdu_c_to_p, 120); + assert_eq!(e.max_pdu_p_to_c, 0); + assert_eq!(e.iso_interval, 16); + assert_eq!(e.to_bytes(), &dump[..]); +} + + +// 7.7.65.27 LE Create BIG Complete + +impl EventCode for LeCreateBigComplete { + const CODE: Code = Code(Code::LE_META, Some(0x1b)); +} + +#[derive(Debug, Read, Write, EventToBytes)] +pub struct LeCreateBigComplete { + pub status: Status, + pub big_handle: u8, + #[N(3)] pub big_sync_delay: u32, + #[N(3)] pub big_transport_latency: u32, + pub phy: u8, + pub nse: u8, + pub bn: u8, + pub pto: u8, + pub irc: u8, + pub max_pdu: u16, + pub iso_interval: u16, + pub bis_handles: Vec<u16>, +} + +#[test] +fn test_le_create_big_complete() { + let dump = [ + 0x3e, 0x17, 0x1b, 0x00, 0x00, 0x46, 0x50, 0x00, 0x66, 0x9e, 0x00, 0x02, 0x0f, 0x03, 0x00, 0x05, + 0x78, 0x00, 0x18, 0x00, 0x02, 0x00, 0x04, 0x01, 0x04 + ]; + let Ok(Event::LeCreateBigComplete(e)) = Event::from_bytes(&dump) else { panic!() }; + assert_eq!(e.status, Status::Success); + assert_eq!(e.big_handle, 0x00); + assert_eq!(e.big_sync_delay, 20_550); + assert_eq!(e.big_transport_latency, 40_550); + assert_eq!(e.phy, 0x02); + assert_eq!(e.nse, 15); + assert_eq!(e.bn, 3); + assert_eq!(e.pto, 0); + assert_eq!(e.irc, 5); + assert_eq!(e.max_pdu, 120); + assert_eq!(e.iso_interval, 24); + assert_eq!(e.bis_handles.len(), 2); + assert_eq!(e.bis_handles[0], 0x400); + assert_eq!(e.bis_handles[1], 0x401); + assert_eq!(e.to_bytes(), &dump[..]); +} + + +// 7.7.65.28 LE Terminate BIG Complete + +impl EventCode for LeTerminateBigComplete { + const CODE: Code = Code(Code::LE_META, Some(0x1c)); +} + +#[derive(Debug, Read, Write, EventToBytes)] +pub struct LeTerminateBigComplete { + pub big_handle: u8, + pub reason: u8, +} + +#[test] +fn test_le_terminate_big_complete() { + let dump = [0x3e, 0x03, 0x1c, 0x00, 0x16]; + let Ok(Event::LeTerminateBigComplete(e)) = Event::from_bytes(&dump) else { panic!() }; + assert_eq!(e.big_handle, 0x00); + assert_eq!(e.reason, 0x16); + assert_eq!(e.to_bytes(), &dump[..]); +} + +} diff --git a/offload/hci/lib.rs b/offload/hci/lib.rs new file mode 100644 index 0000000000..a84e538fec --- /dev/null +++ b/offload/hci/lib.rs @@ -0,0 +1,78 @@ +// 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. + +//! HCI Proxy module implementation, along with +//! reading / writing helpers of Bluetooth HCI Commands, Events and Data encapsulations. + +use std::sync::Arc; + +/// Interface for building a module +pub trait ModuleBuilder: Send + Sync { + /// Build the module from the next module in the chain + fn build(&self, next_module: Arc<dyn Module>) -> Arc<dyn Module>; +} + +/// Interface of a an HCI proxy module +pub trait Module: Send + Sync { + /// Returns the next chained proxy module + fn next(&self) -> &dyn Module; + + /// HCI Command from Host to Controller + fn out_cmd(&self, data: &[u8]) { + self.next().out_cmd(data); + } + /// ACL Data from Host to Controller + fn out_acl(&self, data: &[u8]) { + self.next().out_acl(data); + } + /// SCO Data from Host to Controller + fn out_sco(&self, data: &[u8]) { + self.next().out_sco(data); + } + /// ISO Data from Host to Controller + fn out_iso(&self, data: &[u8]) { + self.next().out_iso(data); + } + + /// HCI Command from Controller to Host + fn in_evt(&self, data: &[u8]) { + self.next().in_evt(data); + } + /// ACL Data from Controller to Host + fn in_acl(&self, data: &[u8]) { + self.next().in_acl(data); + } + /// SCO Data from Controller to Host + fn in_sco(&self, data: &[u8]) { + self.next().in_sco(data); + } + /// ISO Data from Controller to Host + fn in_iso(&self, data: &[u8]) { + self.next().in_iso(data); + } +} + +use bluetooth_offload_hci_derive as derive; + +mod command; +mod data; +mod event; +mod reader; +mod status; +mod writer; + +pub use command::*; +pub use data::*; +pub use event::*; +pub use status::*; diff --git a/offload/hci/reader.rs b/offload/hci/reader.rs new file mode 100644 index 0000000000..98aea61735 --- /dev/null +++ b/offload/hci/reader.rs @@ -0,0 +1,99 @@ +// 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. + +pub(crate) trait Read { + fn read(r: &mut Reader) -> Option<Self> + where + Self: Sized; +} + +pub(crate) struct Reader<'a> { + data: &'a [u8], + pos: usize, +} + +impl<'a> Reader<'a> { + pub(crate) fn new(data: &'a [u8]) -> Self { + Self { data, pos: 0 } + } + + pub(crate) fn get(&mut self, n: usize) -> Option<&'a [u8]> { + if self.pos + n > self.data.len() { + return None; + } + let old_pos = self.pos; + self.pos += n; + Some(&self.data[old_pos..self.pos]) + } + + pub(crate) fn read<T: Read>(&mut self) -> Option<T> { + T::read(self) + } + + pub(crate) fn read_u8(&mut self) -> Option<u8> { + Some(self.read_u32::<1>()? as u8) + } + + pub(crate) fn read_u16(&mut self) -> Option<u16> { + Some(self.read_u32::<2>()? as u16) + } + + pub(crate) fn read_u32<const N: usize>(&mut self) -> Option<u32> { + let data_it = self.get(N)?.iter().enumerate(); + Some(data_it.fold(0u32, |v, (i, byte)| v | (*byte as u32) << (i * 8))) + } + + pub(crate) fn read_bytes<const N: usize>(&mut self) -> Option<[u8; N]> { + Some(<[u8; N]>::try_from(self.get(N)?).unwrap()) + } +} + +impl Read for Vec<u8> { + fn read(r: &mut Reader) -> Option<Self> { + let len = r.read_u8()? as usize; + Some(Vec::from(r.get(len)?)) + } +} + +impl Read for Vec<u16> { + fn read(r: &mut Reader) -> Option<Self> { + let len = r.read_u8()? as usize; + let vec: Vec<_> = (0..len).map_while(|_| r.read_u16()).collect(); + Some(vec).take_if(|v| v.len() == len) + } +} + +impl<T: Read> Read for Vec<T> { + fn read(r: &mut Reader) -> Option<Self> { + let len = r.read_u8()? as usize; + let vec: Vec<_> = (0..len).map_while(|_| r.read()).collect(); + Some(vec).take_if(|v| v.len() == len) + } +} + +macro_rules! unpack { + ($v:expr, ($( $n:expr ),*)) => { + { + let mut _x = $v; + ($({ + let y = _x & ((1 << $n) - 1); + _x >>= $n; + y + }),*) + } + }; + ($v:expr, $n:expr) => { unpack!($v, ($n)) }; +} + +pub(crate) use unpack; diff --git a/offload/hci/status.rs b/offload/hci/status.rs new file mode 100644 index 0000000000..ee92d06bad --- /dev/null +++ b/offload/hci/status.rs @@ -0,0 +1,95 @@ +// 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. + +use crate::derive::{Read, Write}; +use crate::reader::{Read, Reader}; +use crate::writer::{Write, Writer}; + +/// Status / Error codes, as defined in Part F +#[derive(Debug, PartialEq, Read, Write)] +#[allow(missing_docs)] +pub enum Status { + Success = 0x00, + UnknownHciCommand = 0x01, + UnknownConnectionIdentifier = 0x02, + HardwareFailure = 0x03, + PageTimeout = 0x04, + AuthenticationFailure = 0x05, + PinorKeyMissing = 0x06, + MemoryCapacityExceeded = 0x07, + ConnectionTimeout = 0x08, + ConnectionLimitExceeded = 0x09, + SynchronousConnectionLimitExceeded = 0x0A, + ConnectionAlreadyExists = 0x0B, + CommandDisallowed = 0x0C, + ConnectionRejectedLimitedResources = 0x0D, + ConnectionRejectedSecurityReasons = 0x0E, + ConnectionRejectedUnacceptableBdAddr = 0x0F, + ConnectionAcceptTimeoutExceeded = 0x10, + UnsupportedFeatureOrParameterValue = 0x11, + InvalidHciCommandParameters = 0x12, + RemoteUserTerminatedConnection = 0x13, + RemoteDeviceTerminatedConnectionLowResources = 0x14, + RemoteDeviceTerminatedConnectionPowerOff = 0x15, + ConnectionTerminatedByLocalHost = 0x16, + RepeatedAttempts = 0x17, + PairingNotAllowed = 0x18, + UnknownLmpPdu = 0x19, + UnsupportedRemoteFeature = 0x1A, + ScoOffsetRejected = 0x1B, + ScoIntervalRejected = 0x1C, + ScoAirModeRejected = 0x1D, + InvalidLmpParameters = 0x1E, + UnspecifiedError = 0x1F, + UnsupportedLmpParameterValue = 0x20, + RoleChangeNotAllowed = 0x21, + LmpResponseTimeout = 0x22, + LmpErrorTransactionCollision = 0x23, + LmpPduNotAllowed = 0x24, + EncryptionModeNotAcceptable = 0x25, + LinkKeyCannotBeChanged = 0x26, + RequestedQosNotSupported = 0x27, + InstantPassed = 0x28, + PairingWithUnitKeyNotSupported = 0x29, + DifferentTransactionCollision = 0x2A, + ReservedForUse2B = 0x2B, + QosUnacceptableParameter = 0x2C, + QosRejected = 0x2D, + ChannelClassificationNotSupported = 0x2E, + InsufficientSecurity = 0x2F, + ParameterOutOfMandatoryRange = 0x30, + ReservedForUse31 = 0x31, + RoleSwitchPending = 0x32, + ReservedForUse33 = 0x33, + ReservedSlotViolation = 0x34, + RoleSwitchFailed = 0x35, + ExtendedInquiryResponseTooLarge = 0x36, + SecureSimplePairingNotSupportedByHost = 0x37, + HostBusy = 0x38, + ConnectionRejectedNoSuitableChannelFound = 0x39, + ControllerBusy = 0x3A, + UnacceptableConnectionParameters = 0x3B, + AdvertisingTimeout = 0x3C, + ConnectionTerminatedMicFailure = 0x3D, + ConnectionFailedEstablished = 0x3E, + PreviouslyUsed3F = 0x3F, + CoarseClockAdjustmentRejected = 0x40, + Type0SubmapNotDefined = 0x41, + UnknownAdvertisingIdentifier = 0x42, + LimitReached = 0x43, + OperationCancelledByHost = 0x44, + PacketTooLong = 0x45, + TooLate = 0x46, + TooEarly = 0x47, +} diff --git a/offload/hci/writer.rs b/offload/hci/writer.rs new file mode 100644 index 0000000000..ca0e8e274a --- /dev/null +++ b/offload/hci/writer.rs @@ -0,0 +1,103 @@ +// 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. + +pub trait Write { + fn write(&self, w: &mut Writer) + where + Self: Sized; +} + +pub struct Writer { + vec: Vec<u8>, +} + +impl Writer { + pub(crate) fn new(vec: Vec<u8>) -> Self { + Self { vec } + } + + pub(crate) fn into_vec(self) -> Vec<u8> { + self.vec + } + + pub(crate) fn put(&mut self, slice: &[u8]) { + self.vec.extend_from_slice(slice); + } + + pub(crate) fn write<T: Write>(&mut self, v: &T) { + v.write(self) + } + + pub(crate) fn write_u8(&mut self, v: u8) { + self.write_u32::<1>(v.into()); + } + + pub(crate) fn write_u16(&mut self, v: u16) { + self.write_u32::<2>(v.into()); + } + + pub(crate) fn write_u32<const N: usize>(&mut self, mut v: u32) { + for _ in 0..N { + self.vec.push((v & 0xff) as u8); + v >>= 8; + } + } + + pub(crate) fn write_bytes<const N: usize>(&mut self, bytes: &[u8; N]) { + self.put(bytes); + } +} + +impl Write for Vec<u8> { + fn write(&self, w: &mut Writer) { + w.write_u8(self.len().try_into().unwrap()); + w.put(self); + } +} + +impl Write for Vec<u16> { + fn write(&self, w: &mut Writer) { + w.write_u8(self.len().try_into().unwrap()); + for item in self { + w.write_u16(*item); + } + } +} + +impl<T: Write> Write for Vec<T> { + fn write(&self, w: &mut Writer) { + w.write_u8(self.len().try_into().unwrap()); + for item in self { + w.write(item); + } + } +} + +macro_rules! pack { + ( $( ($x:expr, $n:expr) ),* ) => { + { + let mut y = 0; + let mut _shl = 0; + $( + assert!($x & !((1 << $n) - 1) == 0); + y |= ($x << _shl); + _shl += $n; + )* + y + } + }; + ( $x:expr, $n:expr ) => { pack!(($x, $n)) }; +} + +pub(crate) use pack; diff --git a/offload/leaudio/aidl/Android.bp b/offload/leaudio/aidl/Android.bp new file mode 100644 index 0000000000..1e904d2a64 --- /dev/null +++ b/offload/leaudio/aidl/Android.bp @@ -0,0 +1,33 @@ +// Copyright 2025, 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +aidl_interface { + name: "android.hardware.bluetooth.offload.leaudio", + vendor_available: true, + unstable: true, + srcs: ["android/hardware/bluetooth/offload/leaudio/*.aidl"], + backend: { + rust: { + enabled: true, + }, + }, + visibility: [ + "//packages/modules/Bluetooth/offload/leaudio:__subpackages__", + "//system/tools/aidl/build", + ], +} diff --git a/offload/leaudio/aidl/android/hardware/bluetooth/offload/leaudio/IHciProxy.aidl b/offload/leaudio/aidl/android/hardware/bluetooth/offload/leaudio/IHciProxy.aidl new file mode 100644 index 0000000000..a9a1289181 --- /dev/null +++ b/offload/leaudio/aidl/android/hardware/bluetooth/offload/leaudio/IHciProxy.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 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.hardware.bluetooth.offload.leaudio; + +import android.hardware.bluetooth.offload.leaudio.IHciProxyCallbacks; + +/** + * Interface of the HCI-Proxy module + */ +interface IHciProxy { + + /** + * Register callbacks for events in the other direction + */ + void registerCallbacks(in IHciProxyCallbacks callbacks); + + /** + * Send an ISO packet on a `handle` + */ + oneway void sendPacket(in int handle, in int sequence_number, in byte[] data); +} diff --git a/offload/leaudio/aidl/android/hardware/bluetooth/offload/leaudio/IHciProxyCallbacks.aidl b/offload/leaudio/aidl/android/hardware/bluetooth/offload/leaudio/IHciProxyCallbacks.aidl new file mode 100644 index 0000000000..619b22b9ea --- /dev/null +++ b/offload/leaudio/aidl/android/hardware/bluetooth/offload/leaudio/IHciProxyCallbacks.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 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.hardware.bluetooth.offload.leaudio; + +import android.hardware.bluetooth.offload.leaudio.StreamConfiguration; + +/** + * Interface from HCI-Proxy module to the audio module + */ +interface IHciProxyCallbacks { + + /** + * Start indication of a stream + */ + void startStream(in int handle, in StreamConfiguration configuration); + + /** + * Stop indication of a stream + */ + void stopStream(in int handle); +} diff --git a/offload/leaudio/aidl/android/hardware/bluetooth/offload/leaudio/StreamConfiguration.aidl b/offload/leaudio/aidl/android/hardware/bluetooth/offload/leaudio/StreamConfiguration.aidl new file mode 100644 index 0000000000..944facb2ef --- /dev/null +++ b/offload/leaudio/aidl/android/hardware/bluetooth/offload/leaudio/StreamConfiguration.aidl @@ -0,0 +1,48 @@ +/* + * Copyright (C) 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.hardware.bluetooth.offload.leaudio; + +/** + * Configuration relative to a stream + */ +parcelable StreamConfiguration { + + /** + * Constant ISO time transmission interval, in micro-seconds + */ + int isoIntervalUs; + + /** + * Constant SDU transmission interval, in micro-seconds + */ + int sduIntervalUs; + + /** + * Maximum size of a SDU + */ + int maxSduSize; + + /** + * Number of PDU's transmitted by ISO-Intervals + */ + int burstNumber; + + /** + * How many consecutive Isochronous Intervals can be used to transmit a PDU + */ + int flushTimeout; +} diff --git a/offload/leaudio/hci/Android.bp b/offload/leaudio/hci/Android.bp new file mode 100644 index 0000000000..e4f1b51881 --- /dev/null +++ b/offload/leaudio/hci/Android.bp @@ -0,0 +1,46 @@ +// Copyright 2025, 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +rust_defaults { + name: "bluetooth_offload_leaudio_hci_defaults", + crate_root: "lib.rs", + crate_name: "bluetooth_offload_leaudio_hci", + edition: "2021", + rustlibs: [ + "android.hardware.bluetooth.offload.leaudio-rust", + "libbinder_rs", + "libbluetooth_offload_hci", + "liblog_rust", + "liblogger", + ], +} + +rust_library { + name: "libbluetooth_offload_leaudio_hci", + defaults: ["bluetooth_offload_leaudio_hci_defaults"], + vendor_available: true, + visibility: [ + "//hardware/interfaces/bluetooth:__subpackages__", + "//packages/modules/Bluetooth/offload:__subpackages__", + ], +} + +rust_test { + name: "libbluetooth_offload_leaudio_hci_test", + defaults: ["bluetooth_offload_leaudio_hci_defaults"], +} diff --git a/offload/leaudio/hci/arbiter.rs b/offload/leaudio/hci/arbiter.rs new file mode 100644 index 0000000000..072295f568 --- /dev/null +++ b/offload/leaudio/hci/arbiter.rs @@ -0,0 +1,154 @@ +// 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. + +use bluetooth_offload_hci::{IsoData, Module}; +use std::collections::{HashMap, VecDeque}; +use std::sync::{Arc, Condvar, Mutex, MutexGuard}; +use std::thread::{self, JoinHandle}; + +pub struct Arbiter { + state_cvar: Arc<(Mutex<State>, Condvar)>, + thread: Option<JoinHandle<()>>, + max_buf_len: usize, +} + +#[derive(Default)] +struct State { + /// Halt indication of the sender thread + halt: bool, + + /// Software transmission queues for each `Origin`. + /// A queue is pair of connection handle, and packet raw ISO data. + queues: [VecDeque<(u16, Vec<u8>)>; 2], + + /// Count of packets sent to the controller and not yet acknowledged, + /// by connection handle stored on `u16`. + in_transit: HashMap<u16, usize>, +} + +enum Origin { + Audio, + Incoming, +} + +impl Arbiter { + pub fn new(sink: Arc<dyn Module>, max_buf_len: usize, max_buf_count: usize) -> Self { + let state_cvar = Arc::new((Mutex::<State>::new(Default::default()), Condvar::new())); + let thread = { + let state_cvar = state_cvar.clone(); + thread::spawn(move || Self::thread_loop(state_cvar.clone(), sink, max_buf_count)) + }; + + Self { state_cvar, thread: Some(thread), max_buf_len } + } + + pub fn add_connection(&self, handle: u16) { + let (state, _) = &*self.state_cvar; + if state.lock().unwrap().in_transit.insert(handle, 0).is_some() { + panic!("Connection with handle 0x{:03x} already exists", handle); + } + } + + pub fn remove_connection(&self, handle: u16) { + let (state, cvar) = &*self.state_cvar; + let mut state = state.lock().unwrap(); + for q in state.queues.iter_mut() { + while let Some(idx) = q.iter().position(|&(h, _)| h == handle) { + q.remove(idx); + } + } + if state.in_transit.remove(&handle).is_some() { + cvar.notify_one(); + } + } + + pub fn push_incoming(&self, iso_data: &IsoData) { + self.push(Origin::Incoming, iso_data); + } + + pub fn push_audio(&self, iso_data: &IsoData) { + self.push(Origin::Audio, iso_data); + } + + pub fn set_completed(&self, handle: u16, num: usize) { + let (state, cvar) = &*self.state_cvar; + if let Some(buf_usage) = state.lock().unwrap().in_transit.get_mut(&handle) { + *buf_usage -= num; + cvar.notify_one(); + } + } + + fn push(&self, origin: Origin, iso_data: &IsoData) { + let handle = iso_data.connection_handle; + let data = iso_data.to_bytes(); + assert!(data.len() <= self.max_buf_len + 4); + + let (state, cvar) = &*self.state_cvar; + let mut state = state.lock().unwrap(); + if state.in_transit.contains_key(&handle) { + state.queues[origin as usize].push_back((handle, data)); + cvar.notify_one(); + } + } + + fn thread_loop( + state_cvar: Arc<(Mutex<State>, Condvar)>, + sink: Arc<dyn Module>, + max_buf_count: usize, + ) { + let (state, cvar) = &*state_cvar; + 'main: loop { + let packet = { + let mut state = state.lock().unwrap(); + let mut packet = None; + while !state.halt && { + packet = Self::pull(&mut state, max_buf_count); + packet.is_none() + } { + state = cvar.wait(state).unwrap(); + } + if state.halt { + break 'main; + } + packet.unwrap() + }; + sink.out_iso(&packet); + } + } + + fn pull(state: &mut MutexGuard<'_, State>, max_buf_count: usize) -> Option<Vec<u8>> { + for idx in 0..state.queues.len() { + if state.queues[idx].is_empty() || max_buf_count <= state.in_transit.values().sum() { + continue; + } + let (handle, vec) = state.queues[idx].pop_front().unwrap(); + *state.in_transit.get_mut(&handle).unwrap() += 1; + return Some(vec); + } + None + } +} + +impl Drop for Arbiter { + fn drop(&mut self) { + let (state, cvar) = &*self.state_cvar; + { + let mut state = state.lock().unwrap(); + state.halt = true; + cvar.notify_one(); + } + let thread = self.thread.take().unwrap(); + thread.join().expect("End of thread loop"); + } +} diff --git a/offload/leaudio/hci/lib.rs b/offload/leaudio/hci/lib.rs new file mode 100644 index 0000000000..e06cd79356 --- /dev/null +++ b/offload/leaudio/hci/lib.rs @@ -0,0 +1,42 @@ +// 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. + +//! LE Audio HCI-Proxy module +//! +//! The module looks at HCI Commands / Events to control the mux of ISO packet +//! flows coming from the stack and the exposed "LE Audio HCI Proxy" AIDL service: +//! +//! HCI | ^ | HCI AIDL +//! Command | | | ISO Packets Interface +//! ___|_|________|__ ___________ ____________ +//! | : : proxy | | | arbiter | | service | +//! | : : | | | | | | +//! | : : `-|-------|---- ----|--------| | +//! | : : | | \ / | | | +//! | : : | Ctrl | : | | | +//! | : : ---------|------>| : | Ctrl | | +//! | : : ---------|------ | : | ------>| | +//! |___:_:________ __| |_____:_____| |____________| +//! | | | +//! | | HCI | HCI +//! v | Event V ISO Packets + +mod arbiter; +mod proxy; +mod service; + +#[cfg(test)] +mod tests; + +pub use proxy::LeAudioModuleBuilder; diff --git a/offload/leaudio/hci/proxy.rs b/offload/leaudio/hci/proxy.rs new file mode 100644 index 0000000000..413a7f84b5 --- /dev/null +++ b/offload/leaudio/hci/proxy.rs @@ -0,0 +1,381 @@ +// 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. + +use bluetooth_offload_hci as hci; + +use crate::arbiter::Arbiter; +use crate::service::{Service, StreamConfiguration}; +use hci::{Command, Event, EventToBytes, IsoData, ReturnParameters, Status}; +use hci::{Module, ModuleBuilder}; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +const DATA_PATH_ID_SOFTWARE: u8 = 0x19; // TODO + +/// LE Audio HCI-Proxy module builder +pub struct LeAudioModuleBuilder {} + +pub(crate) struct LeAudioModule { + next_module: Arc<dyn Module>, + state: Mutex<State>, + service: Service, +} + +#[derive(Default)] +struct State { + big: HashMap<u8, BigParameters>, + cig: HashMap<u8, CigParameters>, + stream: HashMap<u16, Stream>, + arbiter: Option<Arc<Arbiter>>, +} + +struct BigParameters { + bis_handles: Vec<u16>, + sdu_interval: u32, +} + +struct CigParameters { + cis_handles: Vec<u16>, + sdu_interval_c_to_p: u32, + sdu_interval_p_to_c: u32, +} + +#[derive(Debug, Clone)] +struct Stream { + state: StreamState, + iso_type: IsoType, + iso_interval_us: u32, +} + +#[derive(Debug, PartialEq, Clone)] +enum StreamState { + Disabled, + Enabling, + Enabled, +} + +#[derive(Debug, Clone)] +enum IsoType { + Cis { c_to_p: IsoInDirection, _p_to_c: IsoInDirection }, + Bis { c_to_p: IsoInDirection }, +} + +#[derive(Debug, Clone)] +struct IsoInDirection { + sdu_interval_us: u32, + max_sdu_size: u16, + burst_number: u8, + flush_timeout: u8, +} + +impl Stream { + fn new_cis(cig: &CigParameters, e: &hci::LeCisEstablished) -> Self { + let iso_interval_us = (e.iso_interval as u32) * 1250; + + assert_eq!(iso_interval_us % cig.sdu_interval_c_to_p, 0, "Framing mode not supported"); + assert_eq!(iso_interval_us % cig.sdu_interval_p_to_c, 0, "Framing mode not supported"); + + assert_eq!( + iso_interval_us / cig.sdu_interval_c_to_p, + e.bn_c_to_p.into(), + "SDU fragmentation not supported" + ); + assert_eq!( + iso_interval_us / cig.sdu_interval_p_to_c, + e.bn_p_to_c.into(), + "SDU fragmentation not supported" + ); + + Self { + state: StreamState::Disabled, + iso_interval_us, + iso_type: IsoType::Cis { + c_to_p: IsoInDirection { + sdu_interval_us: cig.sdu_interval_c_to_p, + max_sdu_size: e.max_pdu_c_to_p, + burst_number: e.bn_c_to_p, + flush_timeout: e.ft_c_to_p, + }, + _p_to_c: IsoInDirection { + sdu_interval_us: cig.sdu_interval_p_to_c, + max_sdu_size: e.max_pdu_p_to_c, + burst_number: e.bn_p_to_c, + flush_timeout: e.ft_p_to_c, + }, + }, + } + } + + fn new_bis(big: &BigParameters, e: &hci::LeCreateBigComplete) -> Self { + let iso_interval_us = (e.iso_interval as u32) * 1250; + + assert_eq!(iso_interval_us % big.sdu_interval, 0, "Framing mode not supported"); + assert_eq!( + iso_interval_us / big.sdu_interval, + e.bn.into(), + "SDU fragmentation not supported" + ); + + Self { + state: StreamState::Disabled, + iso_interval_us, + iso_type: IsoType::Bis { + c_to_p: IsoInDirection { + sdu_interval_us: big.sdu_interval, + max_sdu_size: e.max_pdu, + burst_number: e.bn, + flush_timeout: e.irc, + }, + }, + } + } +} + +impl ModuleBuilder for LeAudioModuleBuilder { + /// Build the HCI-Proxy module from the next module in the chain + fn build(&self, next_module: Arc<dyn Module>) -> Arc<dyn Module> { + Arc::new(LeAudioModule::new(next_module)) + } +} + +impl LeAudioModule { + pub(crate) fn new(next_module: Arc<dyn Module>) -> Self { + Self { next_module, state: Mutex::new(Default::default()), service: Service::new() } + } + + #[cfg(test)] + pub(crate) fn arbiter(&self) -> Option<Arc<Arbiter>> { + let state = self.state.lock().unwrap(); + state.arbiter.clone() + } +} + +impl Module for LeAudioModule { + fn next(&self) -> &dyn Module { + &*self.next_module + } + + fn out_cmd(&self, data: &[u8]) { + match Command::from_bytes(data) { + Ok(Command::LeSetCigParameters(ref c)) => { + let mut state = self.state.lock().unwrap(); + state.cig.insert( + c.cig_id, + CigParameters { + cis_handles: vec![], + sdu_interval_c_to_p: c.sdu_interval_c_to_p, + sdu_interval_p_to_c: c.sdu_interval_p_to_c, + }, + ); + } + + Ok(Command::LeCreateBig(ref c)) => { + let mut state = self.state.lock().unwrap(); + state.big.insert( + c.big_handle, + BigParameters { bis_handles: vec![], sdu_interval: c.sdu_interval }, + ); + } + + Ok(Command::LeSetupIsoDataPath(ref c)) if c.data_path_id == DATA_PATH_ID_SOFTWARE => { + assert_eq!(c.data_path_direction, hci::LeDataPathDirection::Input); + let mut state = self.state.lock().unwrap(); + let stream = state.stream.get_mut(&c.connection_handle).unwrap(); + stream.state = StreamState::Enabling; + } + + _ => (), + } + + self.next().out_cmd(data); + } + + fn in_evt(&self, data: &[u8]) { + match Event::from_bytes(data) { + Ok(Event::CommandComplete(ref e)) => match e.return_parameters { + ReturnParameters::Reset(ref ret) if ret.status == Status::Success => { + let mut state = self.state.lock().unwrap(); + *state = Default::default(); + } + + ReturnParameters::LeReadBufferSizeV2(ref ret) if ret.status == Status::Success => { + let mut state = self.state.lock().unwrap(); + state.arbiter = Some(Arc::new(Arbiter::new( + self.next_module.clone(), + ret.iso_data_packet_length.into(), + ret.total_num_iso_data_packets.into(), + ))); + self.service.reset(Arc::downgrade(state.arbiter.as_ref().unwrap())); + } + + ReturnParameters::LeSetCigParameters(ref ret) if ret.status == Status::Success => { + let mut state = self.state.lock().unwrap(); + let cig = state.cig.get_mut(&ret.cig_id).unwrap(); + cig.cis_handles = ret.connection_handles.clone(); + } + + ReturnParameters::LeRemoveCig(ref ret) if ret.status == Status::Success => { + let mut state = self.state.lock().unwrap(); + state.cig.remove(&ret.cig_id); + } + + ReturnParameters::LeSetupIsoDataPath(ref ret) => 'event: { + let mut state = self.state.lock().unwrap(); + let stream = state.stream.get_mut(&ret.connection_handle).unwrap(); + stream.state = + if stream.state == StreamState::Enabling && ret.status == Status::Success { + StreamState::Enabled + } else { + StreamState::Disabled + }; + + if stream.state != StreamState::Enabled { + break 'event; + } + + let c_to_p = match stream.iso_type { + IsoType::Cis { ref c_to_p, .. } => c_to_p, + IsoType::Bis { ref c_to_p } => c_to_p, + }; + + self.service.start_stream( + ret.connection_handle, + StreamConfiguration { + isoIntervalUs: stream.iso_interval_us as i32, + sduIntervalUs: c_to_p.sdu_interval_us as i32, + maxSduSize: c_to_p.max_sdu_size as i32, + burstNumber: c_to_p.burst_number as i32, + flushTimeout: c_to_p.flush_timeout as i32, + }, + ); + } + + ReturnParameters::LeRemoveIsoDataPath(ref ret) if ret.status == Status::Success => { + let mut state = self.state.lock().unwrap(); + let stream = state.stream.get_mut(&ret.connection_handle).unwrap(); + if stream.state == StreamState::Enabled { + self.service.stop_stream(ret.connection_handle); + } + stream.state = StreamState::Disabled; + } + + _ => (), + }, + + Ok(Event::LeCisEstablished(ref e)) if e.status == Status::Success => { + let mut state = self.state.lock().unwrap(); + let mut cig_values = state.cig.values(); + let Some(cig) = + cig_values.find(|&g| g.cis_handles.iter().any(|&h| h == e.connection_handle)) + else { + panic!("CIG not set-up for CIS 0x{:03x}", e.connection_handle); + }; + + let cis = Stream::new_cis(cig, e); + if state.stream.insert(e.connection_handle, cis).is_some() { + log::error!("CIS already established"); + } else { + let arbiter = state.arbiter.as_ref().unwrap(); + arbiter.add_connection(e.connection_handle); + } + } + + Ok(Event::DisconnectionComplete(ref e)) if e.status == Status::Success => { + let mut state = self.state.lock().unwrap(); + if state.stream.remove(&e.connection_handle).is_some() { + let arbiter = state.arbiter.as_ref().unwrap(); + arbiter.remove_connection(e.connection_handle); + } + } + + Ok(Event::LeCreateBigComplete(ref e)) if e.status == Status::Success => { + let mut state_guard = self.state.lock().unwrap(); + let state = &mut *state_guard; + + let big = state.big.get_mut(&e.big_handle).unwrap(); + big.bis_handles = e.bis_handles.clone(); + + let bis = Stream::new_bis(big, e); + for h in &big.bis_handles { + if state.stream.insert(*h, bis.clone()).is_some() { + log::error!("BIS already established"); + } else { + let arbiter = state.arbiter.as_ref().unwrap(); + arbiter.add_connection(*h); + } + } + } + + Ok(Event::LeTerminateBigComplete(ref e)) => { + let mut state = self.state.lock().unwrap(); + let big = state.big.remove(&e.big_handle).unwrap(); + for h in big.bis_handles { + state.stream.remove(&h); + + let arbiter = state.arbiter.as_ref().unwrap(); + arbiter.remove_connection(h); + } + } + + Ok(Event::NumberOfCompletedPackets(ref e)) => 'event: { + let state = self.state.lock().unwrap(); + let Some(arbiter) = state.arbiter.as_ref() else { + break 'event; + }; + + let (stack_event, _) = { + let mut stack_event = hci::NumberOfCompletedPackets { + handles: Vec::with_capacity(e.handles.len()), + }; + let mut audio_event = hci::NumberOfCompletedPackets { + handles: Vec::with_capacity(e.handles.len()), + }; + for item in &e.handles { + let handle = item.connection_handle; + arbiter.set_completed(handle, item.num_completed_packets.into()); + + if match state.stream.get(&handle) { + Some(stream) => stream.state == StreamState::Enabled, + None => false, + } { + audio_event.handles.push(*item); + } else { + stack_event.handles.push(*item); + } + } + (stack_event, audio_event) + }; + + if !stack_event.handles.is_empty() { + self.next().in_evt(&stack_event.to_bytes()); + } + return; + } + + Ok(..) => (), + + Err(code) => { + log::error!("Malformed event with code: {:?}", code); + } + } + + self.next().in_evt(data); + } + + fn out_iso(&self, data: &[u8]) { + let state = self.state.lock().unwrap(); + let arbiter = state.arbiter.as_ref().unwrap(); + arbiter.push_incoming(&IsoData::from_bytes(data).unwrap()); + } +} diff --git a/offload/leaudio/hci/service.rs b/offload/leaudio/hci/service.rs new file mode 100644 index 0000000000..85fac7168c --- /dev/null +++ b/offload/leaudio/hci/service.rs @@ -0,0 +1,126 @@ +// 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. + +use android_hardware_bluetooth_offload_leaudio::{aidl, binder}; + +use crate::arbiter::Arbiter; +use aidl::android::hardware::bluetooth::offload::leaudio::{ + IHciProxy::{BnHciProxy, BpHciProxy, IHciProxy}, + IHciProxyCallbacks::IHciProxyCallbacks, +}; +use binder::{ + BinderFeatures, DeathRecipient, ExceptionCode, Interface, Result as BinderResult, Strong, +}; +use bluetooth_offload_hci::IsoData; +use std::collections::HashMap; +use std::sync::{Arc, Mutex, Weak}; + +pub(crate) use aidl::android::hardware::bluetooth::offload::leaudio::StreamConfiguration::StreamConfiguration; + +pub(crate) struct Service { + state: Arc<Mutex<State>>, +} + +#[derive(Default)] +struct State { + arbiter: Weak<Arbiter>, + callbacks: Option<Strong<dyn IHciProxyCallbacks>>, + streams: HashMap<u16, StreamConfiguration>, +} + +impl Service { + pub(crate) fn new() -> Self { + let state = Arc::new(Mutex::new(State::default())); + HciProxy::register(state.clone()); + Self { state } + } + + pub(crate) fn reset(&self, arbiter: Weak<Arbiter>) { + let mut state = self.state.lock().unwrap(); + *state = State { arbiter, ..Default::default() } + } + + pub(crate) fn start_stream(&self, handle: u16, config: StreamConfiguration) { + let mut state = self.state.lock().unwrap(); + if let Some(callbacks) = &state.callbacks { + let _ = callbacks.startStream(handle.into(), &config); + } else { + log::warn!("Stream started without registered client"); + }; + state.streams.insert(handle, config); + } + + pub(crate) fn stop_stream(&self, handle: u16) { + let mut state = self.state.lock().unwrap(); + state.streams.remove(&handle); + if let Some(callbacks) = &state.callbacks { + let _ = callbacks.stopStream(handle.into()); + }; + } +} + +struct HciProxy { + state: Arc<Mutex<State>>, + _death_recipient: DeathRecipient, +} + +impl Interface for HciProxy {} + +impl HciProxy { + fn register(state: Arc<Mutex<State>>) { + let death_recipient = { + let state = state.clone(); + DeathRecipient::new(move || { + log::info!("Client has died"); + state.lock().unwrap().callbacks = None; + }) + }; + + binder::add_service( + &format!("{}/default", BpHciProxy::get_descriptor()), + BnHciProxy::new_binder( + Self { state, _death_recipient: death_recipient }, + BinderFeatures::default(), + ) + .as_binder(), + ) + .expect("Failed to register service"); + } +} + +impl IHciProxy for HciProxy { + fn registerCallbacks(&self, callbacks: &Strong<dyn IHciProxyCallbacks>) -> BinderResult<()> { + let mut state = self.state.lock().unwrap(); + state.callbacks = Some(callbacks.clone()); + for (handle, config) in &state.streams { + let _ = callbacks.startStream((*handle).into(), config); + } + + Ok(()) + } + + fn sendPacket(&self, handle: i32, seqnum: i32, data: &[u8]) -> BinderResult<()> { + let handle: u16 = handle.try_into().map_err(|_| ExceptionCode::ILLEGAL_ARGUMENT)?; + let seqnum: u16 = seqnum.try_into().map_err(|_| ExceptionCode::ILLEGAL_ARGUMENT)?; + + let state = self.state.lock().unwrap(); + if let Some(arbiter) = state.arbiter.upgrade() { + arbiter.push_audio(&IsoData::new(handle, seqnum, data)); + } else { + log::warn!("Trashing packet received in bad state"); + } + + Ok(()) + } +} diff --git a/offload/leaudio/hci/tests.rs b/offload/leaudio/hci/tests.rs new file mode 100644 index 0000000000..dd55a93c2c --- /dev/null +++ b/offload/leaudio/hci/tests.rs @@ -0,0 +1,914 @@ +// 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. + +use bluetooth_offload_hci as hci; + +use crate::proxy::LeAudioModule; +use hci::{CommandToBytes, EventToBytes, IsoData, Module, ReturnParameters, Status}; +use std::sync::{mpsc, Arc, Mutex}; +use std::time::Duration; + +struct ModuleSinkState { + out_cmd: Vec<Vec<u8>>, + in_evt: Vec<Vec<u8>>, + out_iso: mpsc::Receiver<Vec<u8>>, +} + +struct ModuleSink { + state: Mutex<ModuleSinkState>, + out_iso: mpsc::Sender<Vec<u8>>, +} + +impl ModuleSink { + fn new() -> Self { + let (out_iso_tx, out_iso_rx) = mpsc::channel(); + ModuleSink { + state: Mutex::new(ModuleSinkState { + out_cmd: Default::default(), + in_evt: Default::default(), + out_iso: out_iso_rx, + }), + out_iso: out_iso_tx, + } + } +} + +impl Module for ModuleSink { + fn out_cmd(&self, data: &[u8]) { + self.state.lock().unwrap().out_cmd.push(data.to_vec()); + } + fn in_evt(&self, data: &[u8]) { + self.state.lock().unwrap().in_evt.push(data.to_vec()); + } + fn out_iso(&self, data: &[u8]) { + self.out_iso.send(data.to_vec()).expect("Sending ISO packet"); + } + + fn next(&self) -> &dyn Module { + panic!(); + } +} + +#[test] +fn cig() { + let sink: Arc<ModuleSink> = Arc::new(ModuleSink::new()); + let m = LeAudioModule::new(sink.clone()); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::Reset(hci::ResetComplete { + status: Status::Success, + }), + } + .to_bytes(), + ); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::LeReadBufferSizeV2( + hci::LeReadBufferSizeV2Complete { + status: Status::Success, + le_acl_data_packet_length: 0, + total_num_le_acl_data_packets: 0, + iso_data_packet_length: 16, + total_num_iso_data_packets: 2, + }, + ), + } + .to_bytes(), + ); + + m.out_cmd( + &hci::LeSetCigParameters { + cig_id: 0x01, + sdu_interval_c_to_p: 10_000, + sdu_interval_p_to_c: 10_000, + worst_case_sca: 0, + packing: 0, + framing: 0, + max_transport_latency_c_to_p: 0, + max_transport_latency_p_to_c: 0, + cis: vec![], + } + .to_bytes(), + ); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::LeSetCigParameters( + hci::LeSetCigParametersComplete { + status: Status::Success, + cig_id: 0x01, + connection_handles: vec![0x123, 0x456], + }, + ), + } + .to_bytes(), + ); + + m.in_evt( + &hci::LeCisEstablished { + status: Status::Success, + connection_handle: 0x456, + cig_sync_delay: 0, + cis_sync_delay: 0, + transport_latency_c_to_p: 0, + transport_latency_p_to_c: 0, + phy_c_to_p: 0x02, + phy_p_to_c: 0x02, + nse: 0, + bn_c_to_p: 2, + bn_p_to_c: 2, + ft_c_to_p: 1, + ft_p_to_c: 1, + max_pdu_c_to_p: 10, + max_pdu_p_to_c: 0, + iso_interval: 20_000 / 1250, + } + .to_bytes(), + ); + + m.out_cmd( + &hci::LeSetupIsoDataPath { + connection_handle: 0x456, + data_path_direction: hci::LeDataPathDirection::Input, + data_path_id: 0, + codec_id: hci::LeCodecId { + coding_format: hci::CodingFormat::Transparent, + company_id: 0, + vendor_id: 0, + }, + controller_delay: 0, + codec_configuration: vec![], + } + .to_bytes(), + ); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::LeSetupIsoDataPath(hci::LeIsoDataPathComplete { + status: Status::Success, + connection_handle: 0x456, + }), + } + .to_bytes(), + ); + + m.out_iso(&IsoData::new(0x456, 0, &[0x00, 0x11]).to_bytes()); + m.out_iso(&IsoData::new(0x456, 1, &[]).to_bytes()); + { + let state = sink.state.lock().unwrap(); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + } + + m.in_evt( + &hci::NumberOfCompletedPackets { + handles: vec![hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x456, + num_completed_packets: 1, + }], + } + .to_bytes(), + ); + + m.out_iso(&IsoData::new(0x456, 2, &[0x22]).to_bytes()); + { + let state = sink.state.lock().unwrap(); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + } + + m.in_evt( + &hci::LeCisEstablished { + status: Status::Success, + connection_handle: 0x123, + cig_sync_delay: 0, + cis_sync_delay: 0, + transport_latency_c_to_p: 0, + transport_latency_p_to_c: 0, + phy_c_to_p: 0x02, + phy_p_to_c: 0x02, + nse: 0, + bn_c_to_p: 2, + bn_p_to_c: 2, + ft_c_to_p: 1, + ft_p_to_c: 1, + max_pdu_c_to_p: 10, + max_pdu_p_to_c: 0, + iso_interval: 20_000 / 1250, + } + .to_bytes(), + ); + + m.in_evt( + &hci::NumberOfCompletedPackets { + handles: vec![hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x456, + num_completed_packets: 1, + }], + } + .to_bytes(), + ); + + m.out_iso(&IsoData::new(0x123, 0, &[]).to_bytes()); + { + let state = sink.state.lock().unwrap(); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + } + + m.in_evt( + &hci::DisconnectionComplete { + status: Status::Success, + connection_handle: 0x456, + reason: 0, + } + .to_bytes(), + ); + + m.out_iso(&IsoData::new(0x456, 3, &[0x33]).to_bytes()); + m.out_iso(&IsoData::new(0x123, 1, &[0x11, 0x22]).to_bytes()); + { + let state = sink.state.lock().unwrap(); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + } + + { + let state = sink.state.lock().unwrap(); + assert_eq!(state.out_cmd.len(), 2); + assert_eq!(state.in_evt.len(), 9); + } +} + +#[test] +fn big() { + let sink: Arc<ModuleSink> = Arc::new(ModuleSink::new()); + let m = LeAudioModule::new(sink.clone()); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::Reset(hci::ResetComplete { + status: Status::Success, + }), + } + .to_bytes(), + ); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::LeReadBufferSizeV2( + hci::LeReadBufferSizeV2Complete { + status: Status::Success, + le_acl_data_packet_length: 0, + total_num_le_acl_data_packets: 0, + iso_data_packet_length: 16, + total_num_iso_data_packets: 2, + }, + ), + } + .to_bytes(), + ); + + m.out_cmd( + &hci::LeCreateBig { + big_handle: 0x10, + advertising_handle: 0, + num_bis: 2, + sdu_interval: 10_000, + max_sdu: 120, + max_transport_latency: 0, + rtn: 5, + phy: 0x02, + packing: 0, + framing: 0, + encryption: 0, + broadcast_code: [0u8; 16], + } + .to_bytes(), + ); + + m.in_evt( + &hci::LeCreateBigComplete { + status: Status::Success, + big_handle: 0x10, + big_sync_delay: 0, + big_transport_latency: 0, + phy: 0x02, + nse: 0, + bn: 2, + pto: 0, + irc: 0, + max_pdu: 10, + iso_interval: 20_000 / 1250, + bis_handles: vec![0x123, 0x456], + } + .to_bytes(), + ); + + m.out_cmd( + &hci::LeSetupIsoDataPath { + connection_handle: 0x123, + data_path_direction: hci::LeDataPathDirection::Input, + data_path_id: 0, + codec_id: hci::LeCodecId { + coding_format: hci::CodingFormat::Transparent, + company_id: 0, + vendor_id: 0, + }, + controller_delay: 0, + codec_configuration: vec![], + } + .to_bytes(), + ); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::LeSetupIsoDataPath(hci::LeIsoDataPathComplete { + status: Status::Success, + connection_handle: 0x123, + }), + } + .to_bytes(), + ); + + m.out_cmd( + &hci::LeSetupIsoDataPath { + connection_handle: 0x456, + data_path_direction: hci::LeDataPathDirection::Input, + data_path_id: 0, + codec_id: hci::LeCodecId { + coding_format: hci::CodingFormat::Transparent, + company_id: 0, + vendor_id: 0, + }, + controller_delay: 0, + codec_configuration: vec![], + } + .to_bytes(), + ); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::LeSetupIsoDataPath(hci::LeIsoDataPathComplete { + status: Status::Success, + connection_handle: 0x456, + }), + } + .to_bytes(), + ); + + m.out_iso(&IsoData::new(0x456, 0, &[0x00, 0x11]).to_bytes()); + m.out_iso(&IsoData::new(0x456, 1, &[]).to_bytes()); + { + let state = sink.state.lock().unwrap(); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + } + + m.in_evt( + &hci::NumberOfCompletedPackets { + handles: vec![hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x456, + num_completed_packets: 2, + }], + } + .to_bytes(), + ); + + m.out_iso(&IsoData::new(0x123, 0, &[0x22, 0x33]).to_bytes()); + m.out_iso(&IsoData::new(0x456, 2, &[]).to_bytes()); + { + let state = sink.state.lock().unwrap(); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + } + + m.in_evt( + &hci::NumberOfCompletedPackets { + handles: vec![ + hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x456, + num_completed_packets: 1, + }, + hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x123, + num_completed_packets: 1, + }, + ], + } + .to_bytes(), + ); + + m.out_iso(&IsoData::new(0x123, 1, &[0x44]).to_bytes()); + m.out_iso(&IsoData::new(0x123, 2, &[0x55, 0x66]).to_bytes()); + { + let state = sink.state.lock().unwrap(); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + } + + m.in_evt( + &hci::NumberOfCompletedPackets { + handles: vec![hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x123, + num_completed_packets: 2, + }], + } + .to_bytes(), + ); + + m.out_iso(&IsoData::new(0x123, 3, &[]).to_bytes()); + m.out_iso(&IsoData::new(0x123, 4, &[]).to_bytes()); + { + let state = sink.state.lock().unwrap(); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + } + + { + let state = sink.state.lock().unwrap(); + assert_eq!(state.out_cmd.len(), 3); + assert_eq!(state.in_evt.len(), 8); + } +} + +#[test] +fn merge() { + let sink: Arc<ModuleSink> = Arc::new(ModuleSink::new()); + let m = LeAudioModule::new(sink.clone()); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::Reset(hci::ResetComplete { + status: Status::Success, + }), + } + .to_bytes(), + ); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::LeReadBufferSizeV2( + hci::LeReadBufferSizeV2Complete { + status: Status::Success, + le_acl_data_packet_length: 0, + total_num_le_acl_data_packets: 0, + iso_data_packet_length: 16, + total_num_iso_data_packets: 2, + }, + ), + } + .to_bytes(), + ); + + m.out_cmd( + &hci::LeSetCigParameters { + cig_id: 0x01, + sdu_interval_c_to_p: 10_000, + sdu_interval_p_to_c: 10_000, + worst_case_sca: 0, + packing: 0, + framing: 0, + max_transport_latency_c_to_p: 0, + max_transport_latency_p_to_c: 0, + cis: vec![], + } + .to_bytes(), + ); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::LeSetCigParameters( + hci::LeSetCigParametersComplete { + status: Status::Success, + cig_id: 0x01, + connection_handles: vec![0x123, 0x456], + }, + ), + } + .to_bytes(), + ); + + // Establish CIS 0x123, using Software Offload path + // Establish CIS 0x456, using HCI (stack) path + + m.in_evt( + &hci::LeCisEstablished { + status: Status::Success, + connection_handle: 0x123, + cig_sync_delay: 0, + cis_sync_delay: 0, + transport_latency_c_to_p: 0, + transport_latency_p_to_c: 0, + phy_c_to_p: 0x02, + phy_p_to_c: 0x02, + nse: 0, + bn_c_to_p: 2, + bn_p_to_c: 2, + ft_c_to_p: 1, + ft_p_to_c: 1, + max_pdu_c_to_p: 10, + max_pdu_p_to_c: 0, + iso_interval: 20_000 / 1250, + } + .to_bytes(), + ); + + m.in_evt( + &hci::LeCisEstablished { + status: Status::Success, + connection_handle: 0x456, + cig_sync_delay: 0, + cis_sync_delay: 0, + transport_latency_c_to_p: 0, + transport_latency_p_to_c: 0, + phy_c_to_p: 0x02, + phy_p_to_c: 0x02, + nse: 0, + bn_c_to_p: 2, + bn_p_to_c: 2, + ft_c_to_p: 1, + ft_p_to_c: 1, + max_pdu_c_to_p: 10, + max_pdu_p_to_c: 0, + iso_interval: 20_000 / 1250, + } + .to_bytes(), + ); + + m.out_cmd( + &hci::LeSetupIsoDataPath { + connection_handle: 0x123, + data_path_direction: hci::LeDataPathDirection::Input, + data_path_id: 0x19, + codec_id: hci::LeCodecId { + coding_format: hci::CodingFormat::Transparent, + company_id: 0, + vendor_id: 0, + }, + controller_delay: 0, + codec_configuration: vec![], + } + .to_bytes(), + ); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::LeSetupIsoDataPath(hci::LeIsoDataPathComplete { + status: Status::Success, + connection_handle: 0x123, + }), + } + .to_bytes(), + ); + + m.out_cmd( + &hci::LeSetupIsoDataPath { + connection_handle: 0x456, + data_path_direction: hci::LeDataPathDirection::Input, + data_path_id: 0, + codec_id: hci::LeCodecId { + coding_format: hci::CodingFormat::Transparent, + company_id: 0, + vendor_id: 0, + }, + controller_delay: 0, + codec_configuration: vec![], + } + .to_bytes(), + ); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::LeSetupIsoDataPath(hci::LeIsoDataPathComplete { + status: Status::Success, + connection_handle: 0x456, + }), + } + .to_bytes(), + ); + + { + let mut state = sink.state.lock().unwrap(); + assert_eq!(state.out_cmd.len(), 3); + assert_eq!(state.in_evt.len(), 7); + state.out_cmd.clear(); + state.in_evt.clear(); + } + + // Send 2 Packets on 0x123 + // -> The packets are sent to the controller, and fulfill the FIFO + + m.arbiter().unwrap().push_audio(&IsoData::new(0x123, 1, &[0x44])); + m.arbiter().unwrap().push_audio(&IsoData::new(0x123, 2, &[0x55, 0x66])); + { + let state = sink.state.lock().unwrap(); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + state.out_iso.recv_timeout(Duration::from_millis(100)).expect("Receiving ISO Packet"); + } + + // Send 2 packets on 0x456 + // -> The packets are buffered, the controller FIFO is full + + m.out_iso(&IsoData::new(0x456, 1, &[0x11]).to_bytes()); + m.out_iso(&IsoData::new(0x456, 2, &[0x22, 0x33]).to_bytes()); + { + let state = sink.state.lock().unwrap(); + assert_eq!( + state.out_iso.recv_timeout(Duration::from_millis(100)), + Err(mpsc::RecvTimeoutError::Timeout), + ); + } + + // Acknowledge packet 1 on 0x123: + // -> The acknowledgment is filtered + // -> Packet 1 on 0x456 is tranmitted + + m.in_evt( + &hci::NumberOfCompletedPackets { + handles: vec![hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x123, + num_completed_packets: 1, + }], + } + .to_bytes(), + ); + + { + let mut state = sink.state.lock().unwrap(); + assert_eq!(state.in_evt.pop(), None); + assert_eq!( + state.out_iso.recv_timeout(Duration::from_millis(100)), + Ok(IsoData::new(0x456, 1, &[0x11]).to_bytes()) + ); + assert_eq!( + state.out_iso.recv_timeout(Duration::from_millis(100)), + Err(mpsc::RecvTimeoutError::Timeout), + ); + } + + // Link 0x123 disconnect (implicitly ack packet 2 on 0x123) + // -> Packet 2 on 0x456 is tranmitted + + m.in_evt( + &hci::DisconnectionComplete { + status: Status::Success, + connection_handle: 0x123, + reason: 0, + } + .to_bytes(), + ); + + { + let state = sink.state.lock().unwrap(); + assert_eq!( + state.out_iso.recv_timeout(Duration::from_millis(100)), + Ok(IsoData::new(0x456, 2, &[0x22, 0x33]).to_bytes()) + ); + } + + // Send packets and ack packets 1 and 2 on 0x456. + // -> Connection 0x123 is disconnected, ignored + // -> Packets 3, and 4 on 0x456 are sent + // -> Packet 5 on 0x456 is buffered + + m.out_iso(&IsoData::new(0x456, 3, &[0x33]).to_bytes()); + m.arbiter().unwrap().push_audio(&IsoData::new(0x123, 3, &[0x33])); + m.arbiter().unwrap().push_audio(&IsoData::new(0x123, 4, &[0x44, 0x55])); + m.out_iso(&IsoData::new(0x456, 4, &[0x44, 0x55]).to_bytes()); + m.out_iso(&IsoData::new(0x456, 5, &[0x55, 0x66]).to_bytes()); + { + let state = sink.state.lock().unwrap(); + assert_eq!( + state.out_iso.recv_timeout(Duration::from_millis(100)), + Err(mpsc::RecvTimeoutError::Timeout), + ); + } + + m.in_evt( + &hci::NumberOfCompletedPackets { + handles: vec![hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x456, + num_completed_packets: 2, + }], + } + .to_bytes(), + ); + + { + let mut state = sink.state.lock().unwrap(); + assert_eq!( + state.in_evt.pop(), + Some( + hci::NumberOfCompletedPackets { + handles: vec![hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x456, + num_completed_packets: 2, + }], + } + .to_bytes(), + ) + ); + assert_eq!(state.in_evt.len(), 1); + assert_eq!( + state.out_iso.recv_timeout(Duration::from_millis(100)), + Ok(IsoData::new(0x456, 3, &[0x33]).to_bytes()) + ); + assert_eq!( + state.out_iso.recv_timeout(Duration::from_millis(100)), + Ok(IsoData::new(0x456, 4, &[0x44, 0x55]).to_bytes()) + ); + state.in_evt.clear(); + } + + // Re-establish CIS 0x123, using Software Offload path + + m.in_evt( + &hci::LeCisEstablished { + status: Status::Success, + connection_handle: 0x123, + cig_sync_delay: 0, + cis_sync_delay: 0, + transport_latency_c_to_p: 0, + transport_latency_p_to_c: 0, + phy_c_to_p: 0x02, + phy_p_to_c: 0x02, + nse: 0, + bn_c_to_p: 2, + bn_p_to_c: 2, + ft_c_to_p: 1, + ft_p_to_c: 1, + max_pdu_c_to_p: 10, + max_pdu_p_to_c: 0, + iso_interval: 20_000 / 1250, + } + .to_bytes(), + ); + + m.out_cmd( + &hci::LeSetupIsoDataPath { + connection_handle: 0x123, + data_path_direction: hci::LeDataPathDirection::Input, + data_path_id: 0x19, + codec_id: hci::LeCodecId { + coding_format: hci::CodingFormat::Transparent, + company_id: 0, + vendor_id: 0, + }, + controller_delay: 0, + codec_configuration: vec![], + } + .to_bytes(), + ); + + m.in_evt( + &hci::CommandComplete { + num_hci_command_packets: 0, + return_parameters: ReturnParameters::LeSetupIsoDataPath(hci::LeIsoDataPathComplete { + status: Status::Success, + connection_handle: 0x123, + }), + } + .to_bytes(), + ); + + // Acknowledge packets 3 and 4 on 0x456 + // -> Packet 5 on 0x456 is sent + + m.in_evt( + &hci::NumberOfCompletedPackets { + handles: vec![hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x456, + num_completed_packets: 2, + }], + } + .to_bytes(), + ); + + { + let mut state = sink.state.lock().unwrap(); + assert_eq!( + state.in_evt.pop(), + Some( + hci::NumberOfCompletedPackets { + handles: vec![hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x456, + num_completed_packets: 2, + }], + } + .to_bytes(), + ) + ); + assert_eq!( + state.out_iso.recv_timeout(Duration::from_millis(100)), + Ok(IsoData::new(0x456, 5, &[0x55, 0x66]).to_bytes()) + ); + assert_eq!( + state.out_iso.recv_timeout(Duration::from_millis(100)), + Err(mpsc::RecvTimeoutError::Timeout), + ); + state.out_cmd.clear(); + state.in_evt.clear(); + } + + // Acknowledge packet 5 on 0x456 + // -> Controller FIFO is now empty + + m.in_evt( + &hci::NumberOfCompletedPackets { + handles: vec![hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x456, + num_completed_packets: 1, + }], + } + .to_bytes(), + ); + + { + let mut state = sink.state.lock().unwrap(); + assert_eq!( + state.in_evt.pop(), + Some( + hci::NumberOfCompletedPackets { + handles: vec![hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x456, + num_completed_packets: 1, + }], + } + .to_bytes(), + ) + ); + } + + // Send 1 packet on each CIS, and acknowledge them + // -> The CIS 0x123 is removed from "NumberOfCompletedPackets" event + + m.out_iso(&IsoData::new(0x123, 0, &[]).to_bytes()); + m.arbiter().unwrap().push_audio(&IsoData::new(0x456, 6, &[0x66, 0x77])); + + { + let state = sink.state.lock().unwrap(); + for _ in 0..2 { + let pkt = state.out_iso.recv_timeout(Duration::from_millis(100)); + assert!( + pkt == Ok(IsoData::new(0x123, 0, &[]).to_bytes()) + || pkt == Ok(IsoData::new(0x456, 6, &[0x66, 0x77]).to_bytes()) + ); + } + } + + m.in_evt( + &hci::NumberOfCompletedPackets { + handles: vec![ + hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x456, + num_completed_packets: 1, + }, + hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x123, + num_completed_packets: 1, + }, + ], + } + .to_bytes(), + ); + + { + let mut state = sink.state.lock().unwrap(); + assert_eq!( + state.in_evt.pop(), + Some( + hci::NumberOfCompletedPackets { + handles: vec![hci::NumberOfCompletedPacketsHandle { + connection_handle: 0x456, + num_completed_packets: 1, + }], + } + .to_bytes(), + ) + ); + } +} diff --git a/service/Android.bp b/service/Android.bp index 5fbf29b91c..63fd7b8faa 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -88,12 +88,11 @@ java_library { sdk_version: "system_server_current", min_sdk_version: "Tiramisu", - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], visibility: [":__subpackages__"], } // Apply jarjaring before using library in the apex -// TODO b/383863941 delete and merge with service-bluetooth-new java_library { name: "service-bluetooth", static_libs: ["service-bluetooth-pre-jarjar"], @@ -113,30 +112,6 @@ java_library { sdk_version: "system_server_current", min_sdk_version: "Tiramisu", - apex_available: ["com.android.btservices"], - visibility: ["//packages/modules/Bluetooth/apex"], -} - -// Apply jarjaring before using library in the apex -java_library { - name: "service-bluetooth-new", - static_libs: ["service-bluetooth-pre-jarjar"], - installable: true, - - jarjar_rules: ":bluetooth-jarjar-rules", - - optimize: { - enabled: true, - shrink: true, - proguard_flags_files: ["proguard.flags"], - }, - - libs: [ - "framework-bluetooth.impl", - ], - - sdk_version: "system_server_current", - min_sdk_version: "Tiramisu", apex_available: ["com.android.bt"], visibility: ["//packages/modules/Bluetooth/apex"], } @@ -153,7 +128,7 @@ java_library { sdk_version: "system_server_current", min_sdk_version: "Tiramisu", - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], } java_library { @@ -172,7 +147,7 @@ java_library { sdk_version: "system_current", min_sdk_version: "Tiramisu", - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], } android_robolectric_test { @@ -226,7 +201,6 @@ android_robolectric_test { ], sdk_version: "test_current", - upstream: true, test_suites: ["general-tests"], strict_mode: false, } diff --git a/service/aidl/Android.bp b/service/aidl/Android.bp index d7adba534e..14980c7520 100644 --- a/service/aidl/Android.bp +++ b/service/aidl/Android.bp @@ -17,6 +17,6 @@ java_library { libs: ["framework-annotations-lib"], sdk_version: "module_current", min_sdk_version: "Tiramisu", - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], visibility: ["//packages/modules/Bluetooth:__subpackages__"], } diff --git a/service/change-ids/Android.bp b/service/change-ids/Android.bp index 3544d6b1e2..b717d09e36 100644 --- a/service/change-ids/Android.bp +++ b/service/change-ids/Android.bp @@ -22,7 +22,7 @@ java_library { libs: ["app-compat-annotations"], sdk_version: "system_server_current", min_sdk_version: "Tiramisu", - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], visibility: ["//packages/modules/Bluetooth/service:__subpackages__"], } diff --git a/service/src/com/android/server/bluetooth/BtPermissionUtils.java b/service/src/com/android/server/bluetooth/BtPermissionUtils.java index b9e736b0f8..bdfbc9f304 100644 --- a/service/src/com/android/server/bluetooth/BtPermissionUtils.java +++ b/service/src/com/android/server/bluetooth/BtPermissionUtils.java @@ -96,7 +96,8 @@ class BtPermissionUtils { return true; } - final String msg = "Need " + permission + " permission for " + source + ": " + message; + final String msg = + "Need " + permission + " permission for " + currentSource + ": " + message; if (result == PERMISSION_HARD_DENIED) { throw new SecurityException(msg); } diff --git a/service/tests/AndroidTest.xml b/service/tests/AndroidTest.xml index 4e1be42419..c759326927 100644 --- a/service/tests/AndroidTest.xml +++ b/service/tests/AndroidTest.xml @@ -28,8 +28,7 @@ <option name="test-suite-tag" value="apct" /> <option name="test-tag" value="ServiceBluetoothTests" /> - <option name="config-descriptor:metadata" key="mainline-param" - value="com.google.android.btservices.apex" /> + <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.bt.apex" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.server.bluetooth.test" /> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> @@ -39,7 +38,7 @@ <!-- Only run FrameworkBluetoothTests in MTS if the Bluetooth Mainline module is installed. --> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> - <option name="mainline-module-package-name" value="com.android.btservices" /> - <option name="mainline-module-package-name" value="com.google.android.btservices" /> + <option name="mainline-module-package-name" value="com.android.bt" /> + <option name="mainline-module-package-name" value="com.google.android.bt" /> </object> </configuration> diff --git a/service/tests/src/com/android/server/bluetooth/BluetoothManagerServiceTest.java b/service/tests/src/com/android/server/bluetooth/BluetoothManagerServiceTest.java index 2cd702f233..b2042b5e79 100644 --- a/service/tests/src/com/android/server/bluetooth/BluetoothManagerServiceTest.java +++ b/service/tests/src/com/android/server/bluetooth/BluetoothManagerServiceTest.java @@ -320,7 +320,7 @@ public class BluetoothManagerServiceTest { verify(mContext) .bindServiceAsUser( any(Intent.class), captor.capture(), anyInt(), any(UserHandle.class)); - assertThat(captor.getAllValues().size()).isEqualTo(1); + assertThat(captor.getAllValues()).hasSize(1); BluetoothManagerService.BluetoothServiceConnection serviceConnection = captor.getAllValues().get(0); @@ -334,7 +334,7 @@ public class BluetoothManagerServiceTest { ArgumentCaptor<IBluetoothCallback> captor = ArgumentCaptor.forClass(IBluetoothCallback.class); verify(adapterBinder).registerCallback(captor.capture(), any()); - assertThat(captor.getAllValues().size()).isEqualTo(1); + assertThat(captor.getAllValues()).hasSize(1); return captor.getValue(); } diff --git a/sysprop/Android.bp b/sysprop/Android.bp index e9d820b2f1..241e8711f7 100644 --- a/sysprop/Android.bp +++ b/sysprop/Android.bp @@ -20,7 +20,7 @@ sysprop_library { cpp: { min_sdk_version: "Tiramisu", }, - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], } cc_library_static { @@ -30,6 +30,6 @@ cc_library_static { export_include_dirs: ["exported_include"], export_static_lib_headers: ["libcom.android.sysprop.bluetooth"], visibility: ["//packages/modules/Bluetooth/system:__subpackages__"], - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", } diff --git a/sysprop/a2dp.sysprop b/sysprop/a2dp.sysprop index 80b359c390..ae02fbbb99 100644 --- a/sysprop/a2dp.sysprop +++ b/sysprop/a2dp.sysprop @@ -9,3 +9,10 @@ prop { prop_name: "bluetooth.a2dp.src_sink_coexist.enabled"
}
+prop {
+ api_name: "avdt_accept_open_timeout_ms"
+ type: Integer
+ scope: Internal
+ access: Readonly
+ prop_name: "bluetooth.a2dp.avdt_accept_open_timeout_ms"
+}
\ No newline at end of file diff --git a/system/Android.bp b/system/Android.bp index 1ecb8b81d7..9d47e06596 100644 --- a/system/Android.bp +++ b/system/Android.bp @@ -28,7 +28,7 @@ cc_library_headers { host_supported: true, apex_available: [ "//apex_available:platform", - "com.android.btservices", + "com.android.bt", ], min_sdk_version: "30", } diff --git a/system/OWNERS b/system/OWNERS index d60842e9c9..b2acb91716 100644 --- a/system/OWNERS +++ b/system/OWNERS @@ -10,3 +10,9 @@ poahlo@google.com rongxuan@google.com rwt@google.com wescande@google.com + +# Reviewers for Channel Sounding related files +per-file /bta/ras/*=file:/OWNERS_channel_sounding +per-file /gd/hal/ranging_hal*=file:/OWNERS_channel_sounding +per-file /gd/hci/distance_measurement_*=file:/OWNERS_channel_sounding +per-file /main/shim/distance_measurement_manager*=file:/OWNERS_channel_sounding diff --git a/system/audio/Android.bp b/system/audio/Android.bp index 481182dc5f..98677c206a 100644 --- a/system/audio/Android.bp +++ b/system/audio/Android.bp @@ -41,9 +41,7 @@ cc_library_static { ], host_supported: true, min_sdk_version: "33", - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], } cc_library_host_shared { diff --git a/system/audio_hal_interface/Android.bp b/system/audio_hal_interface/Android.bp index 363627401f..cc33db9a51 100644 --- a/system/audio_hal_interface/Android.bp +++ b/system/audio_hal_interface/Android.bp @@ -79,9 +79,7 @@ cc_library_static { cflags: [ "-Wthread-safety", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", header_libs: ["libbluetooth_headers"], } diff --git a/system/audio_hal_interface/aidl/le_audio_utils.cc b/system/audio_hal_interface/aidl/le_audio_utils.cc index 1a6a4f4e4c..6cd96a423a 100644 --- a/system/audio_hal_interface/aidl/le_audio_utils.cc +++ b/system/audio_hal_interface/aidl/le_audio_utils.cc @@ -18,14 +18,23 @@ #include <com_android_bluetooth_flags.h> +#include <iomanip> #include <optional> +#include <sstream> +#include "aidl/android/hardware/bluetooth/audio/ConfigurationFlags.h" +#include "bta/le_audio/gmap_server.h" +#include "bta/le_audio/le_audio_types.h" #include "hardware/bt_le_audio.h" namespace bluetooth { namespace audio { namespace aidl { +using ::aidl::android::hardware::bluetooth::audio::CodecInfo; +using ::aidl::android::hardware::bluetooth::audio::ConfigurationFlags; +using ::aidl::android::hardware::bluetooth::audio::IBluetoothAudioProviderFactory; + ::aidl::android::hardware::bluetooth::audio::CodecId GetAidlCodecIdFromStackFormat( const ::bluetooth::le_audio::types::LeAudioCodecId& codec_id) { ::aidl::android::hardware::bluetooth::audio::CodecId codec; @@ -507,6 +516,24 @@ static ::bluetooth::le_audio::set_configurations::AseConfiguration GetStackAseCo return config; } +static std::string StackTargetLatencyToString(uint8_t target_latency) { + switch (target_latency) { + case ::bluetooth::le_audio::types::kTargetLatencyUndefined: + return "TargetLatencyUndefined"; + case ::bluetooth::le_audio::types::kTargetLatencyLower: + return "LowLatency"; + case ::bluetooth::le_audio::types::kTargetLatencyBalancedLatencyReliability: + return "BalancedReliability"; + case ::bluetooth::le_audio::types::kTargetLatencyHigherReliability: + return "HighReliability"; + default: + std::stringstream str; + str << "TargetLatencyUnknown (" << std::hex << std::setw(2) << std::setfill('0') + << target_latency << ")"; + return str.str(); + } +} + static std::string GenerateNameForConfig( const ::bluetooth::le_audio::set_configurations::AudioSetConfiguration& config) { auto namegen = [](const std::vector<::bluetooth::le_audio::set_configurations::AseConfiguration>& @@ -550,7 +577,7 @@ static std::string GenerateNameForConfig( cfg_str << "_" << current_codec.GetDataIntervalUs() << "us"; } // QoS - cfg_str << "-TargetLatency_" << +current_config->qos.target_latency; + cfg_str << "-" << StackTargetLatencyToString(current_config->qos.target_latency); if (last_equal_config == configs.end()) { break; @@ -607,6 +634,18 @@ GetStackConfigSettingFromAidl( } } + if (aidl_ase_config.flags.has_value()) { + if (aidl_ase_config.flags->bitmask & + ::aidl::android::hardware::bluetooth::audio::ConfigurationFlags:: + ALLOW_ASYMMETRIC_CONFIGURATIONS) { + log::debug("Asymmetric configuration support flag set."); + } + if (aidl_ase_config.flags->bitmask & + ::aidl::android::hardware::bluetooth::audio::ConfigurationFlags::LOW_LATENCY) { + log::debug("Low latency support flag set."); + } + } + cig_config.name = GenerateNameForConfig(cig_config); return cig_config; } @@ -625,6 +664,87 @@ GetStackUnicastConfigurationFromAidlFormat( return stack_config; } +static bool isAsymmetricConfigurationSupported( + IBluetoothAudioProviderFactory::ProviderInfo const& provider_info) { + for (auto& codec_info : provider_info.codecInfos) { + if (codec_info.transport.getTag() != CodecInfo::Transport::leAudio) { + return false; + } + + auto flags = codec_info.transport.get<CodecInfo::Transport::leAudio>().flags; + + if (!flags) { + continue; + } + + if (flags->bitmask & ConfigurationFlags::ALLOW_ASYMMETRIC_CONFIGURATIONS) { + return true; + } + } + + return false; +} + +static bool isLowLatencyConfigurationSupported( + IBluetoothAudioProviderFactory::ProviderInfo const& provider_info) { + for (auto& codec_info : provider_info.codecInfos) { + if (codec_info.transport.getTag() != CodecInfo::Transport::leAudio) { + return false; + } + + auto flags = codec_info.transport.get<CodecInfo::Transport::leAudio>().flags; + + if (!flags) { + continue; + } + + if (flags->bitmask & ConfigurationFlags::LOW_LATENCY) { + return true; + } + } + + return false; +} + +std::optional<bluetooth::le_audio::ProviderInfo> GetStackProviderInfoFromAidl( + std::optional<IBluetoothAudioProviderFactory::ProviderInfo> const& encoding_provider_info, + std::optional<IBluetoothAudioProviderFactory::ProviderInfo> const& decoding_provider_info) { + (void)encoding_provider_info; + (void)decoding_provider_info; + + if (!encoding_provider_info.has_value() && !decoding_provider_info.has_value()) { + log::error("Neither the encoding or decoding provider info are correct."); + return std::nullopt; + } + + std::optional<bluetooth::le_audio::ProviderInfo> result = bluetooth::le_audio::ProviderInfo(); + if (encoding_provider_info.has_value()) { + log::debug("Encoding: {}", encoding_provider_info->toString()); + if (isAsymmetricConfigurationSupported(encoding_provider_info.value())) { + result->allowAsymmetric = true; + } + + if (isLowLatencyConfigurationSupported(encoding_provider_info.value())) { + result->lowLatency = true; + } + } + + if (decoding_provider_info.has_value()) { + // Iterate and print for the debugging purpose + log::debug("Decoding: {}", decoding_provider_info->toString()); + if (isAsymmetricConfigurationSupported(decoding_provider_info.value())) { + result->allowAsymmetric = true; + } + + if (isLowLatencyConfigurationSupported(decoding_provider_info.value())) { + result->lowLatency = true; + } + } + + log::debug("Stack: {}", result->toString()); + return result; +} + ::aidl::android::hardware::bluetooth::audio::IBluetoothAudioProvider:: LeAudioBroadcastConfigurationRequirement GetAidlLeAudioBroadcastConfigurationRequirementFromStackFormat( @@ -664,7 +784,8 @@ GetStackUnicastConfigurationFromAidlFormat( DeviceDirectionRequirements>>& sink_reqs, const std::optional<std::vector< ::bluetooth::le_audio::CodecManager::UnicastConfigurationRequirements:: - DeviceDirectionRequirements>>& source_reqs) { + DeviceDirectionRequirements>>& source_reqs, + ::bluetooth::le_audio::CodecManager::Flags flags) { auto aidl_reqs = ::aidl::android::hardware::bluetooth::audio::IBluetoothAudioProvider:: LeAudioConfigurationRequirement(); @@ -720,7 +841,19 @@ GetStackUnicastConfigurationFromAidlFormat( aidl_reqs.audioContext.bitmask = (uint32_t)context_type; // TODO(b/341935895): Add the feature flags mechanism in the stack - // aidl_reqs.flags + if (flags != ::bluetooth::le_audio::CodecManager::Flags::NONE) { + aidl_reqs.flags = + std::make_optional<::aidl::android::hardware::bluetooth::audio::ConfigurationFlags>(); + if (flags & ::bluetooth::le_audio::CodecManager::Flags::ALLOW_ASYMMETRIC) { + aidl_reqs.flags->bitmask |= ::aidl::android::hardware::bluetooth::audio::ConfigurationFlags:: + ALLOW_ASYMMETRIC_CONFIGURATIONS; + } + + if (flags & ::bluetooth::le_audio::CodecManager::Flags::LOW_LATENCY) { + aidl_reqs.flags->bitmask |= + ::aidl::android::hardware::bluetooth::audio::ConfigurationFlags::LOW_LATENCY; + } + } return aidl_reqs; } diff --git a/system/audio_hal_interface/aidl/le_audio_utils.h b/system/audio_hal_interface/aidl/le_audio_utils.h index df9d2e044e..25efabdc28 100644 --- a/system/audio_hal_interface/aidl/le_audio_utils.h +++ b/system/audio_hal_interface/aidl/le_audio_utils.h @@ -63,7 +63,8 @@ GetAidlLeAudioDeviceCapabilitiesFromStackFormat( DeviceDirectionRequirements>>& sink_reqs, const std::optional<std::vector< ::bluetooth::le_audio::CodecManager::UnicastConfigurationRequirements:: - DeviceDirectionRequirements>>& source_reqs); + DeviceDirectionRequirements>>& source_reqs, + ::bluetooth::le_audio::CodecManager::Flags flags); ::bluetooth::le_audio::types::LeAudioLtvMap GetStackLeAudioLtvMapFromAidlFormat( const std::vector< ::aidl::android::hardware::bluetooth::audio::CodecSpecificConfigurationLtv>& @@ -94,6 +95,12 @@ GetStackUnicastConfigurationFromAidlFormat( const ::aidl::android::hardware::bluetooth::audio::IBluetoothAudioProvider:: LeAudioAseConfigurationSetting& config); +std::optional<bluetooth::le_audio::ProviderInfo> GetStackProviderInfoFromAidl( + std::optional<::aidl::android::hardware::bluetooth::audio::IBluetoothAudioProviderFactory:: + ProviderInfo> const& encoding_provider_info, + std::optional<::aidl::android::hardware::bluetooth::audio::IBluetoothAudioProviderFactory:: + ProviderInfo> const& decoding_provider_info); + } // namespace aidl } // namespace audio } // namespace bluetooth diff --git a/system/audio_hal_interface/aidl/le_audio_utils_unittest.cc b/system/audio_hal_interface/aidl/le_audio_utils_unittest.cc index 98fee01e5b..669c4fb4b0 100644 --- a/system/audio_hal_interface/aidl/le_audio_utils_unittest.cc +++ b/system/audio_hal_interface/aidl/le_audio_utils_unittest.cc @@ -919,8 +919,8 @@ TEST(BluetoothAudioClientInterfaceAidlTest, testGetStackUnicastConfigurationFrom ASSERT_EQ(stack_config->confs.source.size(), 2ul); ASSERT_EQ(*stack_config, expected_stack_config); ASSERT_EQ(stack_config->name, - "AIDL-2-1chan-SinkAse-CodecId_6_0_0-48000hz_120oct_7500us-TargetLatency_2-" - "2-1chan-SourceAse-CodecId_6_0_0-24000hz_80oct_7500us-TargetLatency_1"); + "AIDL-2-1chan-SinkAse-CodecId_6_0_0-48000hz_120oct_7500us-BalancedReliability-" + "2-1chan-SourceAse-CodecId_6_0_0-24000hz_80oct_7500us-LowLatency"); } TEST(BluetoothAudioClientInterfaceAidlTest, testGetStackUnicastConfigurationFromAidlFormatMonoLoc) { @@ -935,8 +935,8 @@ TEST(BluetoothAudioClientInterfaceAidlTest, testGetStackUnicastConfigurationFrom ASSERT_EQ(stack_config->confs.source.size(), 1ul); ASSERT_EQ(*stack_config, expected_stack_config); ASSERT_EQ(stack_config->name, - "AIDL-2-1chan-SinkAse-CodecId_6_0_0-48000hz_120oct_7500us-TargetLatency_2-" - "1-1chan-SourceAse-CodecId_6_0_0-24000hz_80oct_7500us-TargetLatency_1"); + "AIDL-2-1chan-SinkAse-CodecId_6_0_0-48000hz_120oct_7500us-BalancedReliability-" + "1-1chan-SourceAse-CodecId_6_0_0-24000hz_80oct_7500us-LowLatency"); } TEST(BluetoothAudioClientInterfaceAidlTest, testGetStackBisConfigFromAidlFormat) { @@ -1007,8 +1007,10 @@ TEST(BluetoothAudioClientInterfaceAidlTest, aidl_req_l, aidl_req_r}; reference_aidl_requirements.sourceAseRequirement = reference_aidl_requirements.sinkAseRequirement; + ::bluetooth::le_audio::CodecManager::Flags flags = + ::bluetooth::le_audio::CodecManager::Flags::NONE; auto aidl_requirements = GetAidlLeAudioUnicastConfigurationRequirementsFromStackFormat( - stack_context, stack_sink_reqs, stack_source_reqs); + stack_context, stack_sink_reqs, stack_source_reqs, flags); ASSERT_EQ(aidl_requirements.audioContext, reference_aidl_requirements.audioContext); ASSERT_EQ(aidl_requirements.flags, reference_aidl_requirements.flags); diff --git a/system/audio_hal_interface/le_audio_software.cc b/system/audio_hal_interface/le_audio_software.cc index d58483e340..4d6cd88010 100644 --- a/system/audio_hal_interface/le_audio_software.cc +++ b/system/audio_hal_interface/le_audio_software.cc @@ -25,6 +25,7 @@ #include <vector> #include "aidl/android/hardware/bluetooth/audio/AudioContext.h" +#include "aidl/client_interface_aidl.h" #include "aidl/le_audio_software_aidl.h" #include "aidl/le_audio_utils.h" #include "bta/le_audio/codec_manager.h" @@ -36,10 +37,12 @@ namespace bluetooth { namespace audio { +using aidl::BluetoothAudioClientInterface; using aidl::GetAidlLeAudioBroadcastConfigurationRequirementFromStackFormat; using aidl::GetAidlLeAudioDeviceCapabilitiesFromStackFormat; using aidl::GetAidlLeAudioUnicastConfigurationRequirementsFromStackFormat; using aidl::GetStackBroadcastConfigurationFromAidlFormat; +using aidl::GetStackProviderInfoFromAidl; using aidl::GetStackUnicastConfigurationFromAidlFormat; namespace le_audio { @@ -53,6 +56,7 @@ using ::aidl::android::hardware::bluetooth::audio::AudioContext; using ::aidl::android::hardware::bluetooth::audio::IBluetoothAudioProvider; using ::aidl::android::hardware::bluetooth::audio::LatencyMode; using ::aidl::android::hardware::bluetooth::audio::LeAudioCodecConfiguration; +using ::aidl::android::hardware::bluetooth::audio::SessionType; using ::bluetooth::le_audio::CodecManager; using ::bluetooth::le_audio::set_configurations::AudioSetConfiguration; @@ -329,7 +333,7 @@ LeAudioClientInterface::Sink::GetUnicastConfig( std::vector<IBluetoothAudioProvider::LeAudioConfigurationRequirement> reqs; reqs.push_back(GetAidlLeAudioUnicastConfigurationRequirementsFromStackFormat( requirements.audio_context_type, requirements.sink_requirements, - requirements.source_requirements)); + requirements.source_requirements, requirements.flags)); log::debug("Making an AIDL call"); auto aidl_configs = get_aidl_client_interface(is_broadcaster_) @@ -830,6 +834,26 @@ void LeAudioClientInterface::SetAllowedDsaModes(DsaModes dsa_modes) { } } +std::optional<bluetooth::le_audio::ProviderInfo> LeAudioClientInterface::GetCodecConfigProviderInfo( + void) const { + if (HalVersionManager::GetHalTransport() != BluetoothAudioHalTransport::AIDL) { + log::error("Not using an AIDL HAL transport. Provider Info is not available."); + return std::nullopt; + } + + auto encoding_provider_info = BluetoothAudioClientInterface::GetProviderInfo( + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH, nullptr); + + auto decoding_provider_info = BluetoothAudioClientInterface::GetProviderInfo( + SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH, nullptr); + + if (!encoding_provider_info.has_value() && !decoding_provider_info.has_value()) { + log::info("LE Audio offload codec extensibility is enabled, but the provider info is empty"); + return std::nullopt; + } + + return GetStackProviderInfoFromAidl(encoding_provider_info, decoding_provider_info); +} } // namespace le_audio } // namespace audio } // namespace bluetooth diff --git a/system/audio_hal_interface/le_audio_software.h b/system/audio_hal_interface/le_audio_software.h index 67d804753a..fbc6f4400f 100644 --- a/system/audio_hal_interface/le_audio_software.h +++ b/system/audio_hal_interface/le_audio_software.h @@ -178,6 +178,9 @@ public: // singleton. static LeAudioClientInterface* Get(); + // Get the Codec Configuration Provider info + std::optional<bluetooth::le_audio::ProviderInfo> GetCodecConfigProviderInfo(void) const; + private: static LeAudioClientInterface* interface; Sink* unicast_sink_ = nullptr; diff --git a/system/audio_hal_interface/le_audio_software_host.cc b/system/audio_hal_interface/le_audio_software_host.cc index 28f2592a6c..fba4c77716 100644 --- a/system/audio_hal_interface/le_audio_software_host.cc +++ b/system/audio_hal_interface/le_audio_software_host.cc @@ -570,6 +570,11 @@ LeAudioClientInterface* LeAudioClientInterface::Get() { void LeAudioClientInterface::SetAllowedDsaModes(DsaModes /*dsa_modes*/) { return; } +std::optional<bluetooth::le_audio::ProviderInfo> LeAudioClientInterface::GetCodecConfigProviderInfo( + void) const { + return std::nullopt; +} + } // namespace le_audio } // namespace audio } // namespace bluetooth diff --git a/system/audio_hal_interface/le_audio_software_unittest.cc b/system/audio_hal_interface/le_audio_software_unittest.cc index fdf03dd65c..21db531e07 100644 --- a/system/audio_hal_interface/le_audio_software_unittest.cc +++ b/system/audio_hal_interface/le_audio_software_unittest.cc @@ -130,6 +130,10 @@ public: IBluetoothAudioProvider::LeAudioDeviceCapabilities>>>&), (const ::aidl::android::hardware::bluetooth::audio::IBluetoothAudioProvider:: LeAudioBroadcastConfigurationRequirement&))); + MOCK_METHOD(std::optional<bluetooth::audio::aidl::IBluetoothAudioProviderFactory::ProviderInfo>, + GetProviderInfo, + ((bluetooth::audio::aidl::SessionType), + (std::shared_ptr<bluetooth::audio::aidl::IBluetoothAudioProviderFactory>))); static void SetInstance(MockBluetoothAudioClientInterfaceAidl* ptr) { instance_ptr = ptr; } @@ -416,6 +420,17 @@ std::vector<AudioCapabilities> BluetoothAudioClientInterface::GetAudioCapabiliti return std::vector<AudioCapabilities>(0); } +std::optional<bluetooth::audio::aidl::IBluetoothAudioProviderFactory::ProviderInfo> +BluetoothAudioClientInterface::GetProviderInfo( + bluetooth::audio::aidl::SessionType session_type, + std::shared_ptr<bluetooth::audio::aidl::IBluetoothAudioProviderFactory> provider_factory) { + auto instance = MockBluetoothAudioClientInterfaceAidl::GetInstance(); + if (instance) { + return instance->GetProviderInfo(session_type, provider_factory); + } + return std::nullopt; +} + std::vector<IBluetoothAudioProvider::LeAudioAseConfigurationSetting> BluetoothAudioClientInterface::GetLeAudioAseConfiguration( std::optional< diff --git a/system/audio_hearing_aid_hw/Android.bp b/system/audio_hearing_aid_hw/Android.bp index e8e83e86c8..afc160d599 100644 --- a/system/audio_hearing_aid_hw/Android.bp +++ b/system/audio_hearing_aid_hw/Android.bp @@ -27,9 +27,7 @@ cc_library { "src/audio_hearing_aid_hw.cc", "src/audio_hearing_aid_hw_utils.cc", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], shared_libs: [ "libbase", "liblog", diff --git a/system/bta/Android.bp b/system/bta/Android.bp index efa1d25fd1..57fd56b039 100644 --- a/system/bta/Android.bp +++ b/system/bta/Android.bp @@ -168,9 +168,7 @@ cc_library_static { ], }, }, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], host_supported: true, min_sdk_version: "Tiramisu", } @@ -225,9 +223,7 @@ cc_library_static { shared_libs: [ "libcrypto", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], host_supported: true, min_sdk_version: "Tiramisu", } @@ -816,7 +812,6 @@ cc_test { "libbluetooth_crypto_toolbox", "libbluetooth_gd", "libbluetooth_log", - "libbt-audio-hal-interface", "libbt-common", "libbt-platform-protos-lite", "libchrome", @@ -825,6 +820,7 @@ cc_test { "libflatbuffers-cpp", "libgmock", "libosi", + "libudrv-uipc", "server_configurable_flags", ], sanitize: { @@ -871,6 +867,7 @@ cc_test { ":TestMockStackL2cap", ":TestStubOsi", "gmap/gmap_client.cc", + "gmap/gmap_server.cc", "le_audio/audio_hal_client/audio_hal_client_test.cc", "le_audio/audio_hal_client/audio_sink_hal_client.cc", "le_audio/audio_hal_client/audio_source_hal_client.cc", @@ -1066,6 +1063,95 @@ cc_test { } cc_test { + name: "bluetooth_ras_test", + test_suites: ["general-tests"], + defaults: [ + "fluoride_bta_defaults", + "mts_defaults", + ], + host_supported: true, + isolated: false, + include_dirs: [ + "packages/modules/Bluetooth/system", + "packages/modules/Bluetooth/system/bta/include", + "packages/modules/Bluetooth/system/bta/test/common", + "packages/modules/Bluetooth/system/stack/include", + ], + srcs: [ + ":TestCommonMockFunctions", + ":TestMockBtaGatt", + ":TestMockMainShim", + ":TestMockMainShimEntry", + ":TestMockStackBtm", + ":TestMockStackBtmInterface", + ":TestMockStackBtmIso", + ":TestMockStackGatt", + ":TestMockStackL2cap", + ":TestStubOsi", + "gatt/database.cc", + "gatt/database_builder.cc", + "ras/ras_utils.cc", + "ras/ras_utils_test.cc", + "test/common/bta_gatt_queue_mock.cc", + "test/common/btif_storage_mock.cc", + "test/common/mock_device_groups.cc", + ], + shared_libs: [ + "libaconfig_storage_read_api_cc", + "libbase", + "libcrypto", + "libcutils", + "libhidlbase", + "liblog", + ], + static_libs: [ + "bluetooth_flags_c_lib_for_test", + "libbluetooth-types", + "libbluetooth_crypto_toolbox", + "libbluetooth_gd", + "libbluetooth_log", + "libbt-audio-hal-interface", + "libbt-common", + "libbt-platform-protos-lite", + "libchrome", + "libcom.android.sysprop.bluetooth.wrapped", + "libevent", + "libflagtest", + "libflatbuffers-cpp", + "libgmock", + "libgtest", + "liblc3", + "libosi", + "server_configurable_flags", + ], + target: { + android: { + shared_libs: [ + "libbinder_ndk", + ], + static_libs: [ + "libPlatformProperties", + ], + }, + host: { + static_libs: [ + "libbinder_ndk", + ], + }, + }, + sanitize: { + cfi: true, + scs: true, + address: true, + all_undefined: true, + integer_overflow: true, + diag: { + undefined: true, + }, + }, +} + +cc_test { name: "bluetooth_test_broadcaster_state_machine", test_suites: ["general-tests"], defaults: [ diff --git a/system/bta/ag/bta_ag_int.h b/system/bta/ag/bta_ag_int.h index f6026815a6..f10cc716cc 100644 --- a/system/bta/ag/bta_ag_int.h +++ b/system/bta/ag/bta_ag_int.h @@ -134,6 +134,24 @@ typedef enum : uint8_t { BTA_AG_SCO_SHUTTING_ST /* sco shutting down */ } tBTA_AG_SCO; +inline std::string bta_ag_sco_state_text(const tBTA_AG_SCO& state) { + switch (state) { + CASE_RETURN_TEXT(BTA_AG_SCO_SHUTDOWN_ST); + CASE_RETURN_TEXT(BTA_AG_SCO_LISTEN_ST); + CASE_RETURN_TEXT(BTA_AG_SCO_CODEC_ST); + CASE_RETURN_TEXT(BTA_AG_SCO_OPENING_ST); + CASE_RETURN_TEXT(BTA_AG_SCO_OPEN_CL_ST); + CASE_RETURN_TEXT(BTA_AG_SCO_OPEN_XFER_ST); + CASE_RETURN_TEXT(BTA_AG_SCO_OPEN_ST); + CASE_RETURN_TEXT(BTA_AG_SCO_CLOSING_ST); + CASE_RETURN_TEXT(BTA_AG_SCO_CLOSE_OP_ST); + CASE_RETURN_TEXT(BTA_AG_SCO_CLOSE_XFER_ST); + CASE_RETURN_TEXT(BTA_AG_SCO_SHUTTING_ST); + default: + return std::string("unknown_bta_ag_sco_state: ") + + std::to_string(static_cast<uint8_t>(state)); + } +} /***************************************************************************** * Data types ****************************************************************************/ @@ -245,7 +263,7 @@ struct formatter<tBTA_AG_SCO_LC3_SETTINGS> : enum_formatter<tBTA_AG_SCO_LC3_SETT template <> struct formatter<tBTA_AG_SCO_APTX_SWB_SETTINGS> : enum_formatter<tBTA_AG_SCO_APTX_SWB_SETTINGS> {}; template <> -struct formatter<tBTA_AG_SCO> : enum_formatter<tBTA_AG_SCO> {}; +struct formatter<tBTA_AG_SCO> : string_formatter<tBTA_AG_SCO, &bta_ag_sco_state_text> {}; } // namespace std /* state machine states */ diff --git a/system/bta/ag/bta_ag_sco.cc b/system/bta/ag/bta_ag_sco.cc index 1c62678576..f2c2be8aeb 100644 --- a/system/bta/ag/bta_ag_sco.cc +++ b/system/bta/ag/bta_ag_sco.cc @@ -122,24 +122,6 @@ static const char* bta_ag_sco_evt_str(uint8_t event) { } } -static const char* bta_ag_sco_state_str(uint8_t state) { - switch (state) { - CASE_RETURN_STR(BTA_AG_SCO_SHUTDOWN_ST) - CASE_RETURN_STR(BTA_AG_SCO_LISTEN_ST) - CASE_RETURN_STR(BTA_AG_SCO_CODEC_ST) - CASE_RETURN_STR(BTA_AG_SCO_OPENING_ST) - CASE_RETURN_STR(BTA_AG_SCO_OPEN_CL_ST) - CASE_RETURN_STR(BTA_AG_SCO_OPEN_XFER_ST) - CASE_RETURN_STR(BTA_AG_SCO_OPEN_ST) - CASE_RETURN_STR(BTA_AG_SCO_CLOSING_ST) - CASE_RETURN_STR(BTA_AG_SCO_CLOSE_OP_ST) - CASE_RETURN_STR(BTA_AG_SCO_CLOSE_XFER_ST) - CASE_RETURN_STR(BTA_AG_SCO_SHUTTING_ST) - default: - return "Unknown SCO State"; - } -} - static int codec_uuid_to_sample_rate(tBTA_AG_UUID_CODEC codec) { int sample_rate; switch (codec) { @@ -223,8 +205,7 @@ static void bta_ag_sco_conn_cback(uint16_t sco_idx) { static void bta_ag_sco_disc_cback(uint16_t sco_idx) { uint16_t handle = 0; - log::debug("sco_idx: 0x{:x} sco.state:{}", sco_idx, - sco_state_text(static_cast<tSCO_STATE>(bta_ag_cb.sco.state))); + log::debug("sco_idx: 0x{:x} sco.state:{}", sco_idx, bta_ag_cb.sco.state); log::debug("scb[0] in_use:{} sco_idx: 0x{:x} ag state:{}", bta_ag_cb.scb[0].in_use, bta_ag_cb.scb[0].sco_idx, bta_ag_state_str(bta_ag_cb.scb[0].state)); log::debug("scb[1] in_use:{} sco_idx:0x{:x} ag state:{}", bta_ag_cb.scb[1].in_use, @@ -752,9 +733,10 @@ void bta_ag_codec_negotiate(tBTA_AG_SCB* p_scb) { static void bta_ag_sco_event(tBTA_AG_SCB* p_scb, uint8_t event) { tBTA_AG_SCO_CB* p_sco = &bta_ag_cb.sco; - uint8_t previous_state = p_sco->state; - log::info("device:{} index:0x{:04x} state:{}[{}] event:{}[{}]", p_scb->peer_addr, p_scb->sco_idx, - bta_ag_sco_state_str(p_sco->state), p_sco->state, bta_ag_sco_evt_str(event), event); + tBTA_AG_SCO previous_state = p_sco->state; + log::info("device:{} index:0x{:04x} state:{}[0x{:02x}] event:{}[{}]", p_scb->peer_addr, + p_scb->sco_idx, p_sco->state, static_cast<uint8_t>(p_sco->state), + bta_ag_sco_evt_str(event), event); switch (p_sco->state) { case BTA_AG_SCO_SHUTDOWN_ST: @@ -1244,8 +1226,8 @@ static void bta_ag_sco_event(tBTA_AG_SCB* p_scb, uint8_t event) { log::warn( "SCO_state_change: [{}(0x{:02x})]->[{}(0x{:02x})] after event " "[{}(0x{:02x})]", - bta_ag_sco_state_str(previous_state), previous_state, - bta_ag_sco_state_str(p_sco->state), p_sco->state, bta_ag_sco_evt_str(event), event); + previous_state, static_cast<uint8_t>(previous_state), p_sco->state, + static_cast<uint8_t>(p_sco->state), bta_ag_sco_evt_str(event), event); } } diff --git a/system/bta/aics/Android.bp b/system/bta/aics/Android.bp index 33bd649d13..acb6b4945b 100644 --- a/system/bta/aics/Android.bp +++ b/system/bta/aics/Android.bp @@ -4,7 +4,7 @@ cc_library_headers { host_supported: true, vendor_available: true, // remove when https://r.android.com/3302734 is merged visibility: ["//packages/modules/Bluetooth:__subpackages__"], - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], min_sdk_version: "33", } @@ -27,6 +27,6 @@ cc_library { export_header_lib_headers: ["aics_headers"], host_supported: true, visibility: ["//packages/modules/Bluetooth:__subpackages__"], - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], min_sdk_version: "33", } diff --git a/system/bta/av/bta_av_aact.cc b/system/bta/av/bta_av_aact.cc index 8cb73a4402..769cefc4d9 100644 --- a/system/bta/av/bta_av_aact.cc +++ b/system/bta/av/bta_av_aact.cc @@ -26,6 +26,7 @@ #define LOG_TAG "bluetooth-a2dp" +#include <android_bluetooth_sysprop.h> #include <bluetooth/log.h> #include <com_android_bluetooth_flags.h> @@ -877,17 +878,6 @@ void bta_av_cleanup(tBTA_AV_SCB* p_scb, tBTA_AV_DATA* /* p_data */) { alarm_cancel(p_scb->accept_open_timer); } - /* TODO(eisenbach): RE-IMPLEMENT USING VSC OR HAL EXTENSION - vendor_get_interface()->send_command( - (vendor_opcode_t)BT_VND_OP_A2DP_OFFLOAD_STOP, (void*)&p_scb->l2c_cid); - if (p_scb->offload_start_pending) { - tBTA_AV_STATUS status = BTA_AV_FAIL_STREAM; - tBTA_AV bta_av_data; - bta_av_data.status = status; - (*bta_av_cb.p_cback)(BTA_AV_OFFLOAD_START_RSP_EVT, &bta_av_data); - } - */ - if (p_scb->deregistering) { /* remove stream */ for (int i = 0; i < BTAV_A2DP_CODEC_INDEX_MAX; i++) { @@ -1134,7 +1124,11 @@ void bta_av_setconfig_rsp(tBTA_AV_SCB* p_scb, tBTA_AV_DATA* p_data) { if (!p_scb->accept_open_timer) { p_scb->accept_open_timer = alarm_new("accept_open_timer"); } - alarm_set_on_mloop(p_scb->accept_open_timer, BTA_AV_ACCEPT_OPEN_TIMEOUT_MS, + const uint64_t accept_open_timeout = + android::sysprop::bluetooth::A2dp::avdt_accept_open_timeout_ms().value_or( + BTA_AV_ACCEPT_OPEN_TIMEOUT_MS); + log::debug("accept_open_timeout = {} ms", accept_open_timeout); + alarm_set_on_mloop(p_scb->accept_open_timer, accept_open_timeout, bta_av_accept_open_timer_cback, UINT_TO_PTR(p_scb->hdi)); } } @@ -3180,67 +3174,32 @@ void bta_av_vendor_offload_stop() { * ******************************************************************************/ void bta_av_offload_req(tBTA_AV_SCB* p_scb, tBTA_AV_DATA* /*p_data*/) { - tBTA_AV_STATUS status = BTA_AV_FAIL_RESOURCES; - + tBTA_AV bta_av_data = {}; tBT_A2DP_OFFLOAD offload_start; log::verbose("stream {}, audio channels open {}", p_scb->started ? "STARTED" : "STOPPED", bta_av_cb.audio_open_cnt); - A2dpCodecConfig* codec_config = bta_av_get_a2dp_current_codec(); - log::assert_that(codec_config != nullptr, "assert failed: codec_config != nullptr"); + if (!p_scb->started) { + log::warn("stream not started, start offload failed."); + bta_av_data.status = BTA_AV_FAIL_STREAM; + (*bta_av_cb.p_cback)(BTA_AV_OFFLOAD_START_RSP_EVT, &bta_av_data); + return; + } - /* Check if stream has already been started. */ - /* Support offload if only one audio source stream is open. */ - if (p_scb->started != true) { - status = BTA_AV_FAIL_STREAM; - } else if (bta_av_cb.offload_start_pending_hndl || bta_av_cb.offload_started_hndl) { + if (bta_av_cb.offload_start_pending_hndl || bta_av_cb.offload_started_hndl) { log::warn("offload already started, ignore request"); return; - } else if (::bluetooth::audio::a2dp::provider::supports_codec(codec_config->codecIndex())) { + } + + A2dpCodecConfig* codec_config = bta_av_get_a2dp_current_codec(); + log::assert_that(codec_config != nullptr, "assert failed: codec_config != nullptr"); + + if (::bluetooth::audio::a2dp::provider::supports_codec(codec_config->codecIndex())) { bta_av_vendor_offload_start_v2(p_scb, static_cast<A2dpCodecConfigExt*>(codec_config)); } else { bta_av_offload_codec_builder(p_scb, &offload_start); bta_av_vendor_offload_start(p_scb, &offload_start); - return; - } - if (status != BTA_AV_SUCCESS) { - tBTA_AV bta_av_data; - bta_av_data.status = status; - (*bta_av_cb.p_cback)(BTA_AV_OFFLOAD_START_RSP_EVT, &bta_av_data); - } - /* TODO(eisenbach): RE-IMPLEMENT USING VSC OR HAL EXTENSION - else if (bta_av_cb.audio_open_cnt == 1 && - p_scb->seps[p_scb->sep_idx].tsep == AVDT_TSEP_SRC && - p_scb->chnl == BTA_AV_CHNL_AUDIO) { - bt_vendor_op_a2dp_offload_t a2dp_offload_start; - - if (L2CA_GetConnectionConfig( - p_scb->l2c_cid, &a2dp_offload_start.acl_data_size, - &a2dp_offload_start.remote_cid, &a2dp_offload_start.lm_handle)) { - log::verbose("l2cmtu {} lcid 0x{:02X} rcid 0x{:02X} lm_handle 0x{:02X}", - a2dp_offload_start.acl_data_size, p_scb->l2c_cid, - a2dp_offload_start.remote_cid, a2dp_offload_start.lm_handle); - - a2dp_offload_start.bta_av_handle = p_scb->hndl; - a2dp_offload_start.xmit_quota = BTA_AV_A2DP_OFFLOAD_XMIT_QUOTA; - a2dp_offload_start.stream_mtu = p_scb->stream_mtu; - a2dp_offload_start.local_cid = p_scb->l2c_cid; - a2dp_offload_start.is_flushable = true; - a2dp_offload_start.stream_source = - ((uint32_t)(p_scb->cfg.codec_info[1] | p_scb->cfg.codec_info[2])); - - memcpy(a2dp_offload_start.codec_info, p_scb->cfg.codec_info, - sizeof(a2dp_offload_start.codec_info)); - - if (!vendor_get_interface()->send_command( - (vendor_opcode_t)BT_VND_OP_A2DP_OFFLOAD_START, - &a2dp_offload_start)) { - status = BTA_AV_SUCCESS; - p_scb->offload_start_pending = true; - } - } } - */ } /******************************************************************************* @@ -3402,4 +3361,4 @@ static void bta_av_accept_open_timer_cback(void* data) { tBTA_AV_API_OPEN* p_buf = (tBTA_AV_API_OPEN*)osi_malloc(sizeof(tBTA_AV_API_OPEN)); memcpy(p_buf, &(p_scb->open_api), sizeof(tBTA_AV_API_OPEN)); bta_sys_sendmsg(p_buf); -}
\ No newline at end of file +} diff --git a/system/bta/csis/csis_client.cc b/system/bta/csis/csis_client.cc index 916b1c0c56..1abf97356e 100644 --- a/system/bta/csis/csis_client.cc +++ b/system/bta/csis/csis_client.cc @@ -138,6 +138,7 @@ public: CsisClientImpl(bluetooth::csis::CsisClientCallbacks* callbacks, Closure initCb) : gatt_if_(0), callbacks_(callbacks) { BTA_GATTC_AppRegister( + "csis", [](tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { if (instance && p_data) { instance->GattcCallback(event, p_data); @@ -1923,7 +1924,7 @@ private: BtaGattQueue::Clean(evt.conn_id); } /* Verify bond */ - if (BTM_SecIsSecurityPending(device->addr)) { + if (BTM_SecIsLeSecurityPending(device->addr)) { /* if security collision happened, wait for encryption done * (BTA_GATTC_ENC_CMPL_CB_EVT) */ return; diff --git a/system/bta/csis/csis_client_test.cc b/system/bta/csis/csis_client_test.cc index 42ee0abb36..adc66066f2 100644 --- a/system/bta/csis/csis_client_test.cc +++ b/system/bta/csis/csis_client_test.cc @@ -437,8 +437,8 @@ protected: void TestAppRegister(void) { BtaAppRegisterCallback app_register_callback; - EXPECT_CALL(gatt_interface, AppRegister(_, _, _)) - .WillOnce(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); + EXPECT_CALL(gatt_interface, AppRegister(_, _, _, _)) + .WillOnce(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback))); CsisClient::Initialize(callbacks.get(), Bind(&btif_storage_load_bonded_csis_devices)); ASSERT_TRUE(gatt_callback); ASSERT_TRUE(app_register_callback); diff --git a/system/bta/dm/bta_dm_act.cc b/system/bta/dm/bta_dm_act.cc index 7a931a6248..eefec6d85b 100644 --- a/system/bta/dm/bta_dm_act.cc +++ b/system/bta/dm/bta_dm_act.cc @@ -25,6 +25,8 @@ #define LOG_TAG "bt_bta_dm" +#include "bta/dm/bta_dm_act.h" + #include <android_bluetooth_sysprop.h> #include <base/location.h> #include <bluetooth/log.h> @@ -39,12 +41,15 @@ #include "bta/dm/bta_dm_int.h" #include "bta/dm/bta_dm_sec_int.h" #include "bta/include/bta_api.h" +#include "bta/include/bta_dm_acl.h" +#include "bta/include/bta_dm_api.h" #include "bta/include/bta_le_audio_api.h" #include "bta/include/bta_sdp_api.h" #include "bta/include/bta_sec_api.h" #include "bta/sys/bta_sys.h" #include "btif/include/btif_dm.h" #include "btif/include/stack_manager_t.h" +#include "gd/os/rand.h" #include "hci/controller_interface.h" #include "internal_include/bt_target.h" #include "main/shim/acl_api.h" @@ -54,6 +59,7 @@ #include "osi/include/properties.h" #include "stack/connection_manager/connection_manager.h" #include "stack/include/acl_api.h" +#include "stack/include/ble_scanner.h" #include "stack/include/bt_hdr.h" #include "stack/include/bt_types.h" #include "stack/include/bt_uuid16.h" @@ -66,20 +72,16 @@ #include "types/bluetooth/uuid.h" #include "types/raw_address.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" - using bluetooth::Uuid; using namespace bluetooth; -bool ble_vnd_is_included(); -void btm_ble_scanner_init(void); +static bool ble_vnd_is_included() { + // replace build time config BLE_VND_INCLUDED with runtime + return android::sysprop::bluetooth::Ble::vnd_included().value_or(true); +} static void bta_dm_check_av(); -void BTA_dm_update_policy(tBTA_SYS_CONN_STATUS status, uint8_t id, uint8_t app_id, - const RawAddress& peer_addr); - /* Extended Inquiry Response */ static void bta_dm_set_eir(char* local_name); @@ -87,7 +89,6 @@ static void bta_dm_disable_conn_down_timer_cback(void* data); static void bta_dm_rm_cback(tBTA_SYS_CONN_STATUS status, tBTA_SYS_ID id, uint8_t app_id, const RawAddress& peer_addr); static void bta_dm_adjust_roles(bool delay_role_switch); -tBTM_CONTRL_STATE bta_dm_pm_obtain_controller_state(void); static void bta_dm_ctrl_features_rd_cmpl_cback(tHCI_STATUS result); static const char kPropertySniffOffloadEnabled[] = "persist.bluetooth.sniff_offload.enabled"; @@ -116,6 +117,14 @@ static const char kPropertySniffOffloadEnabled[] = "persist.bluetooth.sniff_offl #define BTA_DM_SWITCH_DELAY_TIMER_MS 500 #endif +/* New swich delay values behind flag extend_and_randomize_role_switch_delay (in milliseconds) */ +#ifndef BTA_DM_MAX_SWITCH_DELAY_MS +#define BTA_DM_MAX_SWITCH_DELAY_MS 1500 +#endif +#ifndef BTA_DM_MIN_SWITCH_DELAY_MS +#define BTA_DM_MIN_SWITCH_DELAY_MS 1000 +#endif + /* Sysprop path for page timeout */ #ifndef PROPERTY_PAGE_TIMEOUT #define PROPERTY_PAGE_TIMEOUT "bluetooth.core.classic.page_timeout" @@ -743,7 +752,7 @@ void BTA_dm_report_role_change(const RawAddress bd_addr, tHCI_ROLE new_role, do_in_main_thread(base::BindOnce(handle_role_change, bd_addr, new_role, hci_status)); } -void handle_remote_features_complete(const RawAddress& bd_addr) { +static void handle_remote_features_complete(const RawAddress& bd_addr) { tBTA_DM_PEER_DEVICE* p_dev = bta_dm_find_peer_device(bd_addr); if (!p_dev) { log::warn("Unable to find device peer:{}", bd_addr); @@ -1174,8 +1183,15 @@ static void bta_dm_adjust_roles(bool delay_role_switch) { break; } } else { - alarm_set_on_mloop(bta_dm_cb.switch_delay_timer, BTA_DM_SWITCH_DELAY_TIMER_MS, - bta_dm_delay_role_switch_cback, NULL); + uint64_t delay = BTA_DM_SWITCH_DELAY_TIMER_MS; + if (com::android::bluetooth::flags::extend_and_randomize_role_switch_delay()) { + delay = bluetooth::os::GenerateRandom() % + (BTA_DM_MAX_SWITCH_DELAY_MS - BTA_DM_MIN_SWITCH_DELAY_MS) + + BTA_DM_MIN_SWITCH_DELAY_MS; + } + log::debug("Set timer to delay role switch:{}", delay); + alarm_set_on_mloop(bta_dm_cb.switch_delay_timer, delay, bta_dm_delay_role_switch_cback, + NULL); } } } @@ -1902,7 +1918,6 @@ void bta_dm_acl_down(const RawAddress& bd_addr, tBT_TRANSPORT transport) { } void bta_dm_init_cb() { ::bta_dm_init_cb(); } void bta_dm_deinit_cb() { ::bta_dm_deinit_cb(); } -void BTA_dm_on_hw_on() { ::BTA_dm_on_hw_on(); } } // namespace testing } // namespace legacy diff --git a/system/bta/dm/bta_dm_act.h b/system/bta/dm/bta_dm_act.h index 7dccefbb34..6bcd7db7be 100644 --- a/system/bta/dm/bta_dm_act.h +++ b/system/bta/dm/bta_dm_act.h @@ -21,6 +21,14 @@ #include "types/raw_address.h" void bta_dm_process_remove_device_no_callback(const RawAddress& bd_addr); +void bta_dm_process_remove_device(const RawAddress& bd_addr); tBTA_DM_PEER_DEVICE* find_connected_device(const RawAddress& bd_addr, tBT_TRANSPORT /* transport */); + +namespace bluetooth::legacy::testing { +void bta_dm_init_cb(void); +void bta_dm_acl_down(const RawAddress& bd_addr, tBT_TRANSPORT transport); +void bta_dm_acl_up(const RawAddress& bd_addr, tBT_TRANSPORT transport, uint16_t acl_handle); +tBTA_DM_PEER_DEVICE* allocate_device_for(const RawAddress& bd_addr, tBT_TRANSPORT transport); +} // namespace bluetooth::legacy::testing diff --git a/system/bta/dm/bta_dm_ci.cc b/system/bta/dm/bta_dm_ci.cc index 16f6eb50c7..fb8cc0b18d 100644 --- a/system/bta/dm/bta_dm_ci.cc +++ b/system/bta/dm/bta_dm_ci.cc @@ -21,6 +21,8 @@ * This is the API implementation file for the BTA device manager. * ******************************************************************************/ +#include "bta/include/bta_dm_ci.h" + #include <base/functional/bind.h> #include <memory> @@ -29,9 +31,6 @@ #include "stack/include/main_thread.h" #include "types/raw_address.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" - /******************************************************************************* * * Function bta_dm_ci_rmt_oob diff --git a/system/bta/dm/bta_dm_device_search.cc b/system/bta/dm/bta_dm_device_search.cc index 0dded3c7db..a8a3e57a0a 100644 --- a/system/bta/dm/bta_dm_device_search.cc +++ b/system/bta/dm/bta_dm_device_search.cc @@ -29,6 +29,7 @@ #include <vector> #include "bta/dm/bta_dm_device_search_int.h" +#include "bta/dm/bta_dm_disc_int.h" #include "common/circular_buffer.h" #include "common/strings.h" #include "device/include/interop.h" @@ -45,9 +46,6 @@ #include "stack/include/rnr_interface.h" #include "types/raw_address.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" - using namespace bluetooth; namespace { @@ -888,8 +886,6 @@ static void bta_dm_search_reset() { void bta_dm_search_stop() { bta_dm_search_reset(); } -void bta_dm_disc_discover_next_device() { bta_dm_discover_next_device(); } - #define DUMPSYS_TAG "shim::legacy::bta::dm" void DumpsysBtaDmSearch(int fd) { auto copy = search_state_history_.Pull(); @@ -912,11 +908,6 @@ void bta_dm_disc_init_search_cb(tBTA_DM_SEARCH_CB& bta_dm_search_cb) { } void bta_dm_discover_next_device() { ::bta_dm_discover_next_device(); } -tBTA_DM_SEARCH_CB bta_dm_disc_get_search_cb() { - tBTA_DM_SEARCH_CB search_cb = {}; - ::bta_dm_disc_init_search_cb(search_cb); - return search_cb; -} tBTA_DM_SEARCH_CB& bta_dm_disc_search_cb() { return ::bta_dm_search_cb; } bool bta_dm_read_remote_device_name(const RawAddress& bd_addr, tBT_TRANSPORT transport) { return ::bta_dm_read_remote_device_name(bd_addr, transport); diff --git a/system/bta/dm/bta_dm_device_search.h b/system/bta/dm/bta_dm_device_search.h index 14da4d9d01..f20432a171 100644 --- a/system/bta/dm/bta_dm_device_search.h +++ b/system/bta/dm/bta_dm_device_search.h @@ -16,8 +16,11 @@ #pragma once +#include "bta/dm/bta_dm_device_search_int.h" #include "bta/include/bta_api.h" // tBTA_DM_SEARCH_CBACK +#include "stack/btm/neighbor_inquiry.h" #include "stack/include/bt_hdr.h" +#include "stack/include/rnr_interface.h" #include "types/bt_transport.h" #include "types/raw_address.h" @@ -39,3 +42,21 @@ bool bta_dm_is_search_request_queued(); // Provide data for the dumpsys procedure void DumpsysBtaDmSearch(int fd); + +namespace bluetooth::legacy::testing { + +void bta_dm_disc_init_search_cb(tBTA_DM_SEARCH_CB& bta_dm_search_cb); +bool bta_dm_read_remote_device_name(const RawAddress& bd_addr, tBT_TRANSPORT transport); +tBTA_DM_SEARCH_CB& bta_dm_disc_search_cb(); +void bta_dm_discover_next_device(); +void bta_dm_inq_cmpl(); +void bta_dm_inq_cmpl_cb(void* p_result); +void bta_dm_observe_cmpl_cb(void* p_result); +void bta_dm_observe_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir, uint16_t eir_len); +void bta_dm_opportunistic_observe_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir, + uint16_t eir_len); +void bta_dm_queue_search(tBTA_DM_API_SEARCH& search); +void bta_dm_remname_cback(const tBTM_REMOTE_DEV_NAME* p); +void bta_dm_start_scan(uint8_t duration_sec); + +} // namespace bluetooth::legacy::testing diff --git a/system/bta/dm/bta_dm_disc.cc b/system/bta/dm/bta_dm_disc.cc index 0ce2062ff8..c3977454f5 100644 --- a/system/bta/dm/bta_dm_disc.cc +++ b/system/bta/dm/bta_dm_disc.cc @@ -52,9 +52,6 @@ #include "stack/include/srvc_api.h" #endif -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" - using bluetooth::Uuid; using namespace bluetooth::legacy::stack::sdp; using namespace bluetooth; @@ -106,8 +103,8 @@ struct gatt_interface_t { void (*BTA_GATTC_Refresh)(const RawAddress& remote_bda); void (*BTA_GATTC_GetGattDb)(tCONN_ID conn_id, uint16_t start_handle, uint16_t end_handle, btgatt_db_element_t** db, int* count); - void (*BTA_GATTC_AppRegister)(tBTA_GATTC_CBACK* p_client_cb, BtaAppRegisterCallback cb, - bool eatt_support); + void (*BTA_GATTC_AppRegister)(const std::string& name, tBTA_GATTC_CBACK* p_client_cb, + BtaAppRegisterCallback cb, bool eatt_support); void (*BTA_GATTC_Close)(tCONN_ID conn_id); void (*BTA_GATTC_ServiceSearchRequest)(tCONN_ID conn_id, const bluetooth::Uuid* p_srvc_uuid); void (*BTA_GATTC_Open)(tGATT_IF client_if, const RawAddress& remote_bda, @@ -125,8 +122,9 @@ struct gatt_interface_t { BTA_GATTC_GetGattDb(conn_id, start_handle, end_handle, db, count); }, .BTA_GATTC_AppRegister = - [](tBTA_GATTC_CBACK* p_client_cb, BtaAppRegisterCallback cb, bool eatt_support) { - BTA_GATTC_AppRegister(p_client_cb, cb, eatt_support); + [](const std::string& name, tBTA_GATTC_CBACK* p_client_cb, + BtaAppRegisterCallback cb, bool eatt_support) { + BTA_GATTC_AppRegister(name, p_client_cb, cb, eatt_support); }, .BTA_GATTC_Close = [](tCONN_ID conn_id) { BTA_GATTC_Close(conn_id); }, .BTA_GATTC_ServiceSearchRequest = @@ -466,7 +464,8 @@ void bta_dm_disc_gattc_register(void) { return; } get_gatt_interface().BTA_GATTC_AppRegister( - bta_dm_gattc_callback, base::Bind([](uint8_t client_id, uint8_t status) { + "bta_dm_disc_gatt", bta_dm_gattc_callback, + base::Bind([](uint8_t client_id, uint8_t status) { tGATT_STATUS gatt_status = static_cast<tGATT_STATUS>(status); if (static_cast<tGATT_STATUS>(status) == GATT_SUCCESS) { log::info("Registered device discovery search gatt client tGATT_IF:{}", client_id); @@ -512,7 +511,7 @@ static void bta_dm_gatt_disc_complete(tCONN_ID conn_id, tGATT_STATUS status) { log::verbose("conn_id = {}, status = {}, sdp_pending = {}, le_pending = {}", conn_id, status, sdp_pending, le_pending); - if (com::android::bluetooth::flags::bta_dm_discover_both() && sdp_pending && !le_pending) { + if (sdp_pending && !le_pending) { /* LE Service discovery finished, and services were reported, but SDP is not * finished yet. gatt_close_timer closed the connection, and we received * this callback because of disconnection */ @@ -716,10 +715,6 @@ tBT_TRANSPORT bta_dm_determine_discovery_transport(const RawAddress& bd_addr) { return ::bta_dm_determine_discovery_transport(bd_addr); } -void bta_dm_sdp_result(tSDP_STATUS sdp_status, tBTA_DM_SDP_STATE* state) { - ::bta_dm_sdp_result(sdp_status, state); -} - } // namespace testing } // namespace legacy } // namespace bluetooth @@ -789,8 +784,7 @@ static void bta_dm_disc_sm_execute(tBTA_DM_DISC_EVT event, std::unique_ptr<tBTA_ "bad message type: {}", msg->index()); auto req = std::get<tBTA_DM_API_DISCOVER>(*msg); - if (com::android::bluetooth::flags::bta_dm_discover_both() && - is_same_device(req.bd_addr, bta_dm_discovery_cb.peer_bdaddr)) { + if (is_same_device(req.bd_addr, bta_dm_discovery_cb.peer_bdaddr)) { bta_dm_discover_services(std::get<tBTA_DM_API_DISCOVER>(*msg)); } else { bta_dm_queue_disc(std::get<tBTA_DM_API_DISCOVER>(*msg)); @@ -846,13 +840,3 @@ void DumpsysBtaDmDisc(int fd) { bta_dm_state_text(bta_dm_discovery_get_state()).c_str()); } #undef DUMPSYS_TAG - -namespace bluetooth { -namespace legacy { -namespace testing { - -tBTA_DM_SERVICE_DISCOVERY_CB& bta_dm_discovery_cb() { return ::bta_dm_discovery_cb; } - -} // namespace testing -} // namespace legacy -} // namespace bluetooth diff --git a/system/bta/dm/bta_dm_disc.h b/system/bta/dm/bta_dm_disc.h index a243b785d9..82b9787029 100644 --- a/system/bta/dm/bta_dm_disc.h +++ b/system/bta/dm/bta_dm_disc.h @@ -43,9 +43,6 @@ void bta_dm_ble_csis_observe(bool observe, tBTA_DM_SEARCH_CBACK* p_cback); // Checks if there is a device discovery request queued bool bta_dm_is_search_request_queued(); -// Proceed to execute service discovery on next device in queue -void bta_dm_disc_discover_next_device(); - // GATT service discovery void bta_dm_disc_gattc_register(); void bta_dm_disc_gatt_cancel_open(const RawAddress& bd_addr); diff --git a/system/bta/dm/bta_dm_disc_int.h b/system/bta/dm/bta_dm_disc_int.h index 4714af997a..adfe3b3f5c 100644 --- a/system/bta/dm/bta_dm_disc_int.h +++ b/system/bta/dm/bta_dm_disc_int.h @@ -21,6 +21,7 @@ #include <queue> #include <string> +#include "bta/dm/bta_dm_device_search_int.h" #include "bta/include/bta_api.h" #include "bta/sys/bta_sys.h" #include "macros.h" @@ -137,3 +138,10 @@ template <> struct formatter<tBTA_DM_SERVICE_DISCOVERY_STATE> : enum_formatter<tBTA_DM_SERVICE_DISCOVERY_STATE> {}; } // namespace std + +namespace bluetooth::legacy::testing { + +tBT_TRANSPORT bta_dm_determine_discovery_transport(const RawAddress& bd_addr); +void bta_dm_remote_name_cmpl(const tBTA_DM_REMOTE_NAME& remote_name_msg); + +} // namespace bluetooth::legacy::testing diff --git a/system/bta/dm/bta_dm_disc_sdp.cc b/system/bta/dm/bta_dm_disc_sdp.cc index dd467e210b..8c43a316cc 100644 --- a/system/bta/dm/bta_dm_disc_sdp.cc +++ b/system/bta/dm/bta_dm_disc_sdp.cc @@ -43,9 +43,6 @@ #include "stack/include/srvc_api.h" #endif -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" - using bluetooth::Uuid; using namespace bluetooth::legacy::stack::sdp; using namespace bluetooth; @@ -361,17 +358,3 @@ void bta_dm_sdp_find_services(tBTA_DM_SDP_STATE* sdp_state) { } sdp_state->service_index++; } - -namespace bluetooth { -namespace legacy { -namespace testing { - -void bta_dm_sdp_find_services(tBTA_DM_SDP_STATE* sdp_state) { - ::bta_dm_sdp_find_services(sdp_state); -} - -void store_avrcp_profile_feature(tSDP_DISC_REC* sdp_rec) { ::store_avrcp_profile_feature(sdp_rec); } - -} // namespace testing -} // namespace legacy -} // namespace bluetooth diff --git a/system/bta/dm/bta_dm_gatt_client.cc b/system/bta/dm/bta_dm_gatt_client.cc index a78ed4ca33..9e511db855 100644 --- a/system/bta/dm/bta_dm_gatt_client.cc +++ b/system/bta/dm/bta_dm_gatt_client.cc @@ -30,9 +30,6 @@ #include "types/bluetooth/uuid.h" #include "types/raw_address.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" - namespace { TimestampedStringCircularBuffer gatt_history_{50}; constexpr char kTimeFormatString[] = "%Y-%m-%d %H:%M:%S"; @@ -70,10 +67,11 @@ gatt_interface_t default_gatt_interface = { BTA_GATTC_GetGattDb(conn_id, start_handle, end_handle, db, count); }, .BTA_GATTC_AppRegister = - [](tBTA_GATTC_CBACK* p_client_cb, BtaAppRegisterCallback cb, bool eatt_support) { + [](const std::string& name, tBTA_GATTC_CBACK* p_client_cb, + BtaAppRegisterCallback cb, bool eatt_support) { gatt_history_.Push(std::format("{:<32s} eatt_support:{:c}", "GATTC_AppRegister", eatt_support ? 'T' : 'F')); - BTA_GATTC_AppRegister(p_client_cb, cb, eatt_support); + BTA_GATTC_AppRegister(name, p_client_cb, cb, eatt_support); }, .BTA_GATTC_Close = [](tCONN_ID conn_id) { @@ -118,18 +116,10 @@ void DumpsysBtaDmGattClient(int fd) { } #undef DUMPSYS_TAG -void bluetooth::testing::set_gatt_interface(const gatt_interface_t& interface) { - *gatt_interface = interface; -} - -namespace bluetooth { -namespace legacy { -namespace testing { +namespace bluetooth::testing { std::vector<bluetooth::common::TimestampedEntry<std::string>> PullCopyOfGattHistory() { return gatt_history_.Pull(); } -} // namespace testing -} // namespace legacy -} // namespace bluetooth +} // namespace bluetooth::testing diff --git a/system/bta/dm/bta_dm_gatt_client.h b/system/bta/dm/bta_dm_gatt_client.h index 7f5540af7d..caa1bd0800 100644 --- a/system/bta/dm/bta_dm_gatt_client.h +++ b/system/bta/dm/bta_dm_gatt_client.h @@ -17,8 +17,10 @@ #pragma once #include <cstdint> +#include <string> #include "bta/include/bta_gatt_api.h" +#include "gd/common/circular_buffer.h" #include "include/hardware/bt_common_types.h" #include "stack/include/btm_ble_api_types.h" #include "types/bluetooth/uuid.h" @@ -32,8 +34,8 @@ struct gatt_interface_t { void (*BTA_GATTC_Refresh)(const RawAddress& remote_bda); void (*BTA_GATTC_GetGattDb)(tCONN_ID conn_id, uint16_t start_handle, uint16_t end_handle, btgatt_db_element_t** db, int* count); - void (*BTA_GATTC_AppRegister)(tBTA_GATTC_CBACK* p_client_cb, BtaAppRegisterCallback cb, - bool eatt_support); + void (*BTA_GATTC_AppRegister)(const std::string& name, tBTA_GATTC_CBACK* p_client_cb, + BtaAppRegisterCallback cb, bool eatt_support); void (*BTA_GATTC_Close)(tCONN_ID conn_id); void (*BTA_GATTC_ServiceSearchRequest)(tCONN_ID conn_id, const bluetooth::Uuid* p_srvc_uuid); void (*BTA_GATTC_Open)(tGATT_IF client_if, const RawAddress& remote_bda, @@ -56,13 +58,6 @@ void gatt_history_callback(const std::string& entry); // void DumpsysBtaDmGattClient(int fd); -namespace bluetooth { -namespace testing { - -// -// TESTING: Sets a specialzed GATT client interface implementation for testing -// -void set_gatt_interface(const gatt_interface_t& interface); - -} // namespace testing -} // namespace bluetooth +namespace bluetooth::testing { +std::vector<bluetooth::common::TimestampedEntry<std::string>> PullCopyOfGattHistory(); +} // namespace bluetooth::testing diff --git a/system/bta/dm/bta_dm_int.h b/system/bta/dm/bta_dm_int.h index b5357bb06b..70deacff2f 100644 --- a/system/bta/dm/bta_dm_int.h +++ b/system/bta/dm/bta_dm_int.h @@ -21,8 +21,8 @@ * This is the private interface file for the BTA device manager. * ******************************************************************************/ -#ifndef BTA_DM_INT_H -#define BTA_DM_INT_H + +#pragma once #include <bluetooth/log.h> #include <com_android_bluetooth_flags.h> @@ -323,6 +323,9 @@ extern tBTA_DM_ACL_CB bta_dm_acl_cb; /* DI control block */ extern tBTA_DM_DI_CB bta_dm_di_cb; +void BTA_dm_on_hw_on(); +void BTA_dm_on_hw_off(); + void bta_dm_enable(tBTA_DM_SEC_CBACK*, tBTA_DM_ACL_CBACK*); void bta_dm_disable(); void bta_dm_set_dev_name(const std::vector<uint8_t>&); @@ -361,10 +364,19 @@ void bta_dm_eir_update_cust_uuid(const tBTA_CUSTOM_UUID& curr, bool adding); void bta_dm_ble_subrate_request(const RawAddress& bd_addr, uint16_t subrate_min, uint16_t subrate_max, uint16_t max_latency, uint16_t cont_num, uint16_t timeout); +tBTM_CONTRL_STATE bta_dm_pm_obtain_controller_state(void); namespace std { template <> struct formatter<tBTA_DM_CONN_STATE> : enum_formatter<tBTA_DM_CONN_STATE> {}; } // namespace std -#endif /* BTA_DM_INT_H */ +namespace bluetooth::legacy::testing { + +tBTA_DM_PEER_DEVICE* allocate_device_for(const RawAddress& bd_addr, tBT_TRANSPORT transport); +void bta_dm_acl_up(const RawAddress& bd_addr, tBT_TRANSPORT transport, uint16_t acl_handle); +void bta_dm_acl_down(const RawAddress& bd_addr, tBT_TRANSPORT transport); +void bta_dm_init_cb(); +void bta_dm_deinit_cb(); + +} // namespace bluetooth::legacy::testing diff --git a/system/bta/dm/bta_dm_pm.cc b/system/bta/dm/bta_dm_pm.cc index 9c0b869d30..53812a27d4 100644 --- a/system/bta/dm/bta_dm_pm.cc +++ b/system/bta/dm/bta_dm_pm.cc @@ -46,9 +46,6 @@ #include "stack/include/main_thread.h" #include "types/raw_address.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" - using namespace bluetooth; static void bta_dm_pm_cback(tBTA_SYS_CONN_STATUS status, const tBTA_SYS_ID id, uint8_t app_id, @@ -1210,6 +1207,6 @@ tBTM_CONTRL_STATE bta_dm_pm_obtain_controller_state(void) { tBTM_CONTRL_STATE cur_state = BTM_CONTRL_UNKNOWN; cur_state = BTM_PM_ReadControllerState(); - log::verbose("bta_dm_pm_obtain_controller_state: {}", cur_state); + log::verbose("cur_state: {}", cur_state); return cur_state; } diff --git a/system/bta/dm/bta_dm_sec.cc b/system/bta/dm/bta_dm_sec.cc index 31ce27b772..cb97af5fd3 100644 --- a/system/bta/dm/bta_dm_sec.cc +++ b/system/bta/dm/bta_dm_sec.cc @@ -37,9 +37,6 @@ #include "types/bt_transport.h" #include "types/raw_address.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" - using namespace bluetooth; static tBTM_STATUS bta_dm_sp_cback(tBTM_SP_EVT event, tBTM_SP_EVT_DATA* p_data); @@ -1016,7 +1013,6 @@ namespace testing { tBTM_STATUS bta_dm_sp_cback(tBTM_SP_EVT event, tBTM_SP_EVT_DATA* p_data) { return ::bta_dm_sp_cback(event, p_data); } - } // namespace testing } // namespace legacy } // namespace bluetooth diff --git a/system/bta/dm/bta_dm_sec_int.h b/system/bta/dm/bta_dm_sec_int.h index 540205dd99..efd9140e69 100644 --- a/system/bta/dm/bta_dm_sec_int.h +++ b/system/bta/dm/bta_dm_sec_int.h @@ -83,9 +83,15 @@ void bta_dm_ci_rmt_oob_act(std::unique_ptr<tBTA_DM_CI_RMT_OOB> msg); void bta_dm_confirm(const RawAddress& bd_addr, bool accept); void bta_dm_consolidate(const RawAddress& identity_addr, const RawAddress& rpa); void bta_dm_enable(tBTA_DM_SEC_CBACK* p_sec_cback); -void bta_dm_encrypt_cback(const RawAddress* bd_addr, tBT_TRANSPORT transport, - void* /* p_ref_data */, tBTM_STATUS result); +void bta_dm_encrypt_cback(RawAddress bd_addr, tBT_TRANSPORT transport, void* /* p_ref_data */, + tBTM_STATUS result); +void bta_dm_on_encryption_change(bt_encryption_change_evt encryption_change); void bta_dm_pin_reply(std::unique_ptr<tBTA_DM_API_PIN_REPLY> msg); void bta_dm_set_encryption(const RawAddress& bd_addr, tBT_TRANSPORT transport, tBTA_DM_ENCRYPT_CBACK* p_callback, tBTM_BLE_SEC_ACT sec_act); void btm_dm_sec_init(); +void bta_dm_remote_key_missing(const RawAddress bd_addr); + +namespace bluetooth::legacy::testing { +tBTM_STATUS bta_dm_sp_cback(tBTM_SP_EVT event, tBTM_SP_EVT_DATA* p_data); +} // namespace bluetooth::legacy::testing diff --git a/system/bta/gatt/bta_gattc_act.cc b/system/bta/gatt/bta_gattc_act.cc index 9f5ad701c7..cca68b8c0d 100644 --- a/system/bta/gatt/bta_gattc_act.cc +++ b/system/bta/gatt/bta_gattc_act.cc @@ -180,8 +180,8 @@ static void bta_gattc_start_if(uint8_t client_if) { } /** Register a GATT client application with BTA */ -void bta_gattc_register(const Uuid& app_uuid, tBTA_GATTC_CBACK* p_cback, BtaAppRegisterCallback cb, - bool eatt_support) { +void bta_gattc_register(const Uuid& app_uuid, const std::string& name, tBTA_GATTC_CBACK* p_cback, + BtaAppRegisterCallback cb, bool eatt_support) { tGATT_STATUS status = GATT_NO_RESOURCES; uint8_t client_if = 0; log::debug("state: {}, uuid={}", bta_gattc_cb.state, app_uuid.ToString()); @@ -193,7 +193,7 @@ void bta_gattc_register(const Uuid& app_uuid, tBTA_GATTC_CBACK* p_cback, BtaAppR } if (com::android::bluetooth::flags::gatt_client_dynamic_allocation()) { - client_if = GATT_Register(app_uuid, "GattClient", &bta_gattc_cl_cback, eatt_support); + client_if = GATT_Register(app_uuid, name, &bta_gattc_cl_cback, eatt_support); if (client_if == 0) { log::error("Register with GATT stack failed"); status = GATT_ERROR; diff --git a/system/bta/gatt/bta_gattc_api.cc b/system/bta/gatt/bta_gattc_api.cc index 38b3691b4a..b08988d575 100644 --- a/system/bta/gatt/bta_gattc_api.cc +++ b/system/bta/gatt/bta_gattc_api.cc @@ -76,8 +76,8 @@ void BTA_GATTC_Disable(void) { * module. |client_cb| pointer to the application callback function. * |cb| one time callback when registration is finished */ -void BTA_GATTC_AppRegister(tBTA_GATTC_CBACK* p_client_cb, BtaAppRegisterCallback cb, - bool eatt_support) { +void BTA_GATTC_AppRegister(const std::string& name, tBTA_GATTC_CBACK* p_client_cb, + BtaAppRegisterCallback cb, bool eatt_support) { log::debug("eatt_support={}", eatt_support); if (!bta_sys_is_register(BTA_ID_GATTC)) { log::debug("BTA_ID_GATTC not registered in BTA, registering it"); @@ -86,8 +86,8 @@ void BTA_GATTC_AppRegister(tBTA_GATTC_CBACK* p_client_cb, BtaAppRegisterCallback Uuid uuid = Uuid::From128BitBE(bluetooth::os::GenerateRandom<Uuid::kNumBytes128>()); - do_in_main_thread( - base::BindOnce(&bta_gattc_register, uuid, p_client_cb, std::move(cb), eatt_support)); + do_in_main_thread(base::BindOnce(&bta_gattc_register, uuid, name, p_client_cb, std::move(cb), + eatt_support)); } static void app_deregister_impl(tGATT_IF client_if) { diff --git a/system/bta/gatt/bta_gattc_int.h b/system/bta/gatt/bta_gattc_int.h index 4ceaff4062..b155260fb9 100644 --- a/system/bta/gatt/bta_gattc_int.h +++ b/system/bta/gatt/bta_gattc_int.h @@ -371,8 +371,8 @@ bool bta_gattc_sm_execute(tBTA_GATTC_CLCB* p_clcb, uint16_t event, const tBTA_GA /* function processed outside SM */ void bta_gattc_disable(); -void bta_gattc_register(const bluetooth::Uuid& app_uuid, tBTA_GATTC_CBACK* p_data, - BtaAppRegisterCallback cb, bool eatt_support); +void bta_gattc_register(const bluetooth::Uuid& app_uuid, const std::string& name, + tBTA_GATTC_CBACK* p_data, BtaAppRegisterCallback cb, bool eatt_support); void bta_gattc_process_api_open(const tBTA_GATTC_DATA* p_msg); void bta_gattc_process_api_open_cancel(const tBTA_GATTC_DATA* p_msg); void bta_gattc_deregister(tBTA_GATTC_RCB* p_clreg); diff --git a/system/bta/gmap/gmap_client.cc b/system/bta/gmap/gmap_client.cc index 0589b6a759..0a1210d413 100644 --- a/system/bta/gmap/gmap_client.cc +++ b/system/bta/gmap/gmap_client.cc @@ -36,10 +36,8 @@ using namespace bluetooth; using bluetooth::le_audio::GmapClient; bool GmapClient::is_offloader_support_gmap_ = false; -void GmapClient::AddFromStorage(const RawAddress &addr, const uint8_t role, - const uint16_t role_handle, const uint8_t UGT_feature, - const uint16_t UGT_feature_handle) { - addr_ = addr; +void GmapClient::AddFromStorage(uint8_t role, uint16_t role_handle, uint8_t UGT_feature, + uint16_t UGT_feature_handle) { role_ = role; role_handle_ = role_handle; UGT_feature_ = UGT_feature; @@ -60,8 +58,8 @@ bool GmapClient::IsGmapClientEnabled() { bool system_prop = osi_property_get_bool("bluetooth.profile.gmap.enabled", false); bool result = flag && system_prop && is_offloader_support_gmap_; - log::info("GmapClientEnabled={}, flag={}, system_prop={}, offloader_support={}", result, - system_prop, flag, GmapClient::is_offloader_support_gmap_); + log::info("GmapClientEnabled={}, flag={}, system_prop={}, offloader_support={}", result, flag, + system_prop, GmapClient::is_offloader_support_gmap_); return result; } @@ -90,14 +88,14 @@ bool GmapClient::parseAndSaveUGTFeature(uint16_t len, const uint8_t *value) { return true; } -std::bitset<8> GmapClient::getRole() { return role_; } +std::bitset<8> GmapClient::getRole() const { return role_; } -uint16_t GmapClient::getRoleHandle() { return role_handle_; } +uint16_t GmapClient::getRoleHandle() const { return role_handle_; } void GmapClient::setRoleHandle(uint16_t handle) { role_handle_ = handle; } -std::bitset<8> GmapClient::getUGTFeature() { return UGT_feature_; } +std::bitset<8> GmapClient::getUGTFeature() const { return UGT_feature_; } -uint16_t GmapClient::getUGTFeatureHandle() { return UGT_feature_handle_; } +uint16_t GmapClient::getUGTFeatureHandle() const { return UGT_feature_handle_; } void GmapClient::setUGTFeatureHandle(uint16_t handle) { UGT_feature_handle_ = handle; } diff --git a/system/bta/gmap/gmap_client_test.cc b/system/bta/gmap/gmap_client_test.cc index 68aec5caf5..bc497da0a9 100644 --- a/system/bta/gmap/gmap_client_test.cc +++ b/system/bta/gmap/gmap_client_test.cc @@ -68,7 +68,7 @@ TEST_F(GmapClientTest, test_add_from_storage) { const uint16_t role_handle = 2; const uint8_t UGT_feature = 0b0011; const uint16_t UGT_feature_handle = 4; - gmapClient.AddFromStorage(addr, role, role_handle, UGT_feature, UGT_feature_handle); + gmapClient.AddFromStorage(role, role_handle, UGT_feature, UGT_feature_handle); ASSERT_EQ(gmapClient.getRole(), role); ASSERT_EQ(gmapClient.getRoleHandle(), role_handle); ASSERT_EQ(gmapClient.getUGTFeature(), UGT_feature); diff --git a/system/bta/gmap/gmap_server.cc b/system/bta/gmap/gmap_server.cc index 407e50147b..1851355fe1 100644 --- a/system/bta/gmap/gmap_server.cc +++ b/system/bta/gmap/gmap_server.cc @@ -60,8 +60,8 @@ bool GmapServer::IsGmapServerEnabled() { bool system_prop = osi_property_get_bool("bluetooth.profile.gmap.enabled", false); bool result = flag && system_prop && is_offloader_support_gmap_; - log::info("GmapServerEnabled={}, flag={}, system_prop={}, offloader_support={}", result, - system_prop, flag, GmapServer::is_offloader_support_gmap_); + log::info("GmapServerEnabled={}, flag={}, system_prop={}, offloader_support={}", result, flag, + system_prop, GmapServer::is_offloader_support_gmap_); return result; } diff --git a/system/bta/has/has_client.cc b/system/bta/has/has_client.cc index deb79bf103..15bdfc5bfc 100644 --- a/system/bta/has/has_client.cc +++ b/system/bta/has/has_client.cc @@ -138,6 +138,7 @@ public: HasClientImpl(bluetooth::has::HasClientCallbacks* callbacks, base::Closure initCb) : gatt_if_(0), callbacks_(callbacks) { BTA_GATTC_AppRegister( + "has", [](tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { if (instance && p_data) { instance->GattcCallback(event, p_data); @@ -2014,7 +2015,7 @@ private: if (com::android::bluetooth::flags::gatt_queue_cleanup_connected()) { BtaGattQueue::Clean(evt.conn_id); } - if (BTM_SecIsSecurityPending(device->addr)) { + if (BTM_SecIsLeSecurityPending(device->addr)) { /* if security collision happened, wait for encryption done * (BTA_GATTC_ENC_CMPL_CB_EVT) */ diff --git a/system/bta/has/has_client_test.cc b/system/bta/has/has_client_test.cc index 9c98258601..ddaed634a0 100644 --- a/system/bta/has/has_client_test.cc +++ b/system/bta/has/has_client_test.cc @@ -734,8 +734,8 @@ protected: void TestAppRegister(void) { BtaAppRegisterCallback app_register_callback; - EXPECT_CALL(gatt_interface, AppRegister(_, _, _)) - .WillOnce(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); + EXPECT_CALL(gatt_interface, AppRegister(_, _, _, _)) + .WillOnce(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback))); HasClient::Initialize(callbacks.get(), base::DoNothing()); ASSERT_TRUE(gatt_callback); ASSERT_TRUE(app_register_callback); diff --git a/system/bta/hearing_aid/hearing_aid.cc b/system/bta/hearing_aid/hearing_aid.cc index 245b095b07..58a4313638 100644 --- a/system/bta/hearing_aid/hearing_aid.cc +++ b/system/bta/hearing_aid/hearing_aid.cc @@ -328,7 +328,7 @@ public: default_data_interval_ms, overwrite_min_ce_len, overwrite_max_ce_len); BTA_GATTC_AppRegister( - hearingaid_gattc_callback, + "asha", hearingaid_gattc_callback, base::Bind( [](Closure initCb, uint8_t client_id, uint8_t status) { if (status != GATT_SUCCESS) { @@ -559,7 +559,7 @@ public: log::warn("Unable to set BLE data length peer:{} size:{}", address, 167); } - if (BTM_SecIsSecurityPending(address)) { + if (BTM_SecIsLeSecurityPending(address)) { /* if security collision happened, wait for encryption done * (BTA_GATTC_ENC_CMPL_CB_EVT) */ return; diff --git a/system/bta/hh/bta_hh_headtracker.cc b/system/bta/hh/bta_hh_headtracker.cc index c8b3e20f05..0282cb59d0 100644 --- a/system/bta/hh/bta_hh_headtracker.cc +++ b/system/bta/hh/bta_hh_headtracker.cc @@ -140,7 +140,10 @@ void bta_hh_headtracker_parse_service(tBTA_HH_DEV_CB* p_dev_cb, const gatt::Serv bool bta_hh_headtracker_supported(tBTA_HH_DEV_CB* p_dev_cb) { if (p_dev_cb->hid_srvc.headtracker_support == BTA_HH_UNKNOWN) { bluetooth::Uuid remote_uuids[BT_MAX_NUM_UUIDS] = {}; - bt_property_t remote_properties = {BT_PROPERTY_UUIDS, sizeof(remote_uuids), &remote_uuids}; + bt_property_t remote_properties = {com::android::bluetooth::flags::separate_service_storage() + ? BT_PROPERTY_UUIDS_LE + : BT_PROPERTY_UUIDS, + sizeof(remote_uuids), &remote_uuids}; const RawAddress& bd_addr = p_dev_cb->link_spec.addrt.bda; p_dev_cb->hid_srvc.headtracker_support = BTA_HH_UNAVAILABLE; diff --git a/system/bta/hh/bta_hh_le.cc b/system/bta/hh/bta_hh_le.cc index ea90ecfafc..5a5c261f98 100644 --- a/system/bta/hh/bta_hh_le.cc +++ b/system/bta/hh/bta_hh_le.cc @@ -202,7 +202,8 @@ void bta_hh_le_enable(void) { bta_hh_cb.le_cb_index[xx] = BTA_HH_IDX_INVALID; } - BTA_GATTC_AppRegister(bta_hh_gattc_callback, base::Bind([](tGATT_IF client_id, uint8_t r_status) { + BTA_GATTC_AppRegister("hid", bta_hh_gattc_callback, + base::Bind([](tGATT_IF client_id, uint8_t r_status) { tBTA_HH bta_hh; bta_hh.status = BTA_HH_ERR; @@ -1113,7 +1114,7 @@ void bta_hh_start_security(tBTA_HH_DEV_CB* p_cb, const tBTA_HH_DATA* /* p_buf */ p_cb->status = BTA_HH_ERR_AUTH_FAILED; BTM_SetEncryption(p_cb->link_spec.addrt.bda, BT_TRANSPORT_LE, bta_hh_le_encrypt_cback, NULL, BTM_BLE_SEC_ENCRYPT); - } else if (BTM_SecIsSecurityPending(p_cb->link_spec.addrt.bda)) { + } else if (BTM_SecIsLeSecurityPending(p_cb->link_spec.addrt.bda)) { /* if security collision happened, wait for encryption done */ log::debug("addr:{} security collision", p_cb->link_spec.addrt.bda); p_cb->security_pending = true; @@ -1771,6 +1772,11 @@ void bta_hh_gatt_close(tBTA_HH_DEV_CB* p_cb, const tBTA_HH_DATA* p_data) { if (bta_hh_cb.cnt_num == 0 && bta_hh_cb.w4_disable) { bta_hh_disc_cmpl(); } else { + if (com::android::bluetooth::flags::hogp_reconnection()) { + // reconnection is handled in btif_hh.cc:btif_hh_disconnected + return; + } + switch (le_close->reason) { case GATT_CONN_FAILED_ESTABLISHMENT: case GATT_CONN_TERMINATE_PEER_USER: diff --git a/system/bta/include/bta_gatt_api.h b/system/bta/include/bta_gatt_api.h index 6e93821e4a..5072ae63d2 100644 --- a/system/bta/include/bta_gatt_api.h +++ b/system/bta/include/bta_gatt_api.h @@ -466,8 +466,8 @@ using BtaAppRegisterCallback = base::Callback<void(uint8_t /* app_id */, uint8_t *module. * p_client_cb - pointer to the application callback function. **/ -void BTA_GATTC_AppRegister(tBTA_GATTC_CBACK* p_client_cb, BtaAppRegisterCallback cb, - bool eatt_support); +void BTA_GATTC_AppRegister(const std::string& name, tBTA_GATTC_CBACK* p_client_cb, + BtaAppRegisterCallback cb, bool eatt_support); /******************************************************************************* * diff --git a/system/bta/include/bta_jv_api.h b/system/bta/include/bta_jv_api.h index 8978ab00c2..08709e1900 100644 --- a/system/bta/include/bta_jv_api.h +++ b/system/bta/include/bta_jv_api.h @@ -698,7 +698,7 @@ tBTA_JV_STATUS BTA_JvL2capWrite(uint32_t handle, uint32_t req_id, BT_HDR* msg, u ******************************************************************************/ tBTA_JV_STATUS BTA_JvRfcommConnect(tBTA_SEC sec_mask, uint8_t remote_scn, const RawAddress& peer_bd_addr, tBTA_JV_RFCOMM_CBACK* p_cback, - uint32_t rfcomm_slot_id, RfcommCfgInfo cfg); + uint32_t rfcomm_slot_id, RfcommCfgInfo cfg, uint32_t app_uid); /******************************************************************************* * @@ -729,7 +729,7 @@ tBTA_JV_STATUS BTA_JvRfcommClose(uint32_t handle, uint32_t rfcomm_slot_id); ******************************************************************************/ tBTA_JV_STATUS BTA_JvRfcommStartServer(tBTA_SEC sec_mask, uint8_t local_scn, uint8_t max_session, tBTA_JV_RFCOMM_CBACK* p_cback, uint32_t rfcomm_slot_id, - RfcommCfgInfo cfg); + RfcommCfgInfo cfg, uint32_t app_uid); /******************************************************************************* * diff --git a/system/bta/include/bta_le_audio_api.h b/system/bta/include/bta_le_audio_api.h index de11131bae..52a3f7d83c 100644 --- a/system/bta/include/bta_le_audio_api.h +++ b/system/bta/include/bta_le_audio_api.h @@ -86,8 +86,9 @@ public: const std::vector<uint8_t>& handles, const std::vector<uint8_t>& sink_pacs, const std::vector<uint8_t>& source_pacs, - const std::vector<uint8_t>& ases); + const std::vector<uint8_t>& ases, const std::vector<uint8_t>& gmap); static bool GetHandlesForStorage(const RawAddress& addr, std::vector<uint8_t>& out); + static bool GetGmapForStorage(const RawAddress& addr, std::vector<uint8_t>& out); static bool GetSinkPacsForStorage(const RawAddress& addr, std::vector<uint8_t>& out); static bool GetSourcePacsForStorage(const RawAddress& addr, std::vector<uint8_t>& out); static bool GetAsesForStorage(const RawAddress& addr, std::vector<uint8_t>& out); diff --git a/system/bta/include/bta_ras_api.h b/system/bta/include/bta_ras_api.h index ee6eccec4f..48113e5edf 100644 --- a/system/bta/include/bta_ras_api.h +++ b/system/bta/include/bta_ras_api.h @@ -25,6 +25,12 @@ namespace bluetooth { namespace ras { +enum class RasDisconnectReason { + GATT_DISCONNECT, + SERVER_NOT_AVAILABLE, + FATAL_ERROR, +}; + struct VendorSpecificCharacteristic { bluetooth::Uuid characteristicUuid_; std::vector<uint8_t> value_; @@ -64,7 +70,8 @@ public: const std::vector<VendorSpecificCharacteristic>& vendor_specific_characteristics, uint16_t conn_interval) = 0; virtual void OnConnIntervalUpdated(const RawAddress& address, uint16_t conn_interval) = 0; - virtual void OnDisconnected(const RawAddress& address) = 0; + virtual void OnDisconnected(const RawAddress& address, + const RasDisconnectReason& ras_disconnect_reason) = 0; virtual void OnWriteVendorSpecificReplyComplete(const RawAddress& address, bool success) = 0; virtual void OnRemoteData(const RawAddress& address, const std::vector<uint8_t>& data) = 0; virtual void OnRemoteDataTimeout(const RawAddress& address) = 0; diff --git a/system/bta/jv/bta_jv_act.cc b/system/bta/jv/bta_jv_act.cc index 7b6fa38ca8..ffe1756943 100644 --- a/system/bta/jv/bta_jv_act.cc +++ b/system/bta/jv/bta_jv_act.cc @@ -233,7 +233,7 @@ tBTA_JV_RFC_CB* bta_jv_alloc_rfc_cb(uint16_t port_handle, tBTA_JV_PCB** pp_pcb) p_cb->rfc_hdl[j] = 0; } p_cb->rfc_hdl[0] = port_handle; - log::verbose("port_handle={}, handle=0x{:x}", port_handle, p_cb->handle); + log::verbose("port_handle={}, jv_handle=0x{:x}", port_handle, p_cb->handle); p_pcb = &bta_jv_cb.port_cb[port_handle - 1]; p_pcb->handle = p_cb->handle; @@ -307,7 +307,7 @@ static tBTA_JV_STATUS bta_jv_free_rfc_cb(tBTA_JV_RFC_CB* p_cb, tBTA_JV_PCB* p_pc log::error("p_cb or p_pcb cannot be null"); return tBTA_JV_STATUS::FAILURE; } - log::verbose("max_sess={}, curr_sess={}, p_pcb={}, user={}, state={}, jv handle=0x{:x}", + log::verbose("max_sess={}, curr_sess={}, p_pcb={}, user={}, state={}, jv_handle=0x{:x}", p_cb->max_sess, p_cb->curr_sess, std::format_ptr(p_pcb), p_pcb->rfcomm_slot_id, p_pcb->state, p_pcb->handle); @@ -341,7 +341,7 @@ static tBTA_JV_STATUS bta_jv_free_rfc_cb(tBTA_JV_RFC_CB* p_cb, tBTA_JV_PCB* p_pc break; default: log::warn( - "failed, ignore port state= {}, scn={}, p_pcb= {}, jv handle=0x{:x}, " + "failed, ignore port state= {}, scn={}, p_pcb= {}, jv_handle=0x{:x}, " "port_handle={}, user_data={}", p_pcb->state, p_cb->scn, std::format_ptr(p_pcb), p_pcb->handle, p_pcb->port_handle, p_pcb->rfcomm_slot_id); @@ -359,7 +359,7 @@ static tBTA_JV_STATUS bta_jv_free_rfc_cb(tBTA_JV_RFC_CB* p_cb, tBTA_JV_PCB* p_pc if (port_status != PORT_SUCCESS) { status = tBTA_JV_STATUS::FAILURE; log::warn( - "Remove jv handle=0x{:x}, state={}, port_status={}, port_handle={}, close_pending={}", + "Remove jv_handle=0x{:x}, state={}, port_status={}, port_handle={}, close_pending={}", p_pcb->handle, p_pcb->state, port_status, p_pcb->port_handle, close_pending); } } @@ -470,8 +470,8 @@ static tBTA_JV_STATUS bta_jv_free_set_pm_profile_cb(uint32_t jv_handle) { } } - log::verbose("jv_handle=0x{:x}, idx={}app_id={}, bd_counter={}, appid_counter={}", jv_handle, - i, bta_jv_cb.pm_cb[i].app_id, bd_counter, appid_counter); + log::verbose("jv_handle=0x{:x}, idx={}, app_id={}, bd_counter={}, appid_counter={}", + jv_handle, i, bta_jv_cb.pm_cb[i].app_id, bd_counter, appid_counter); if (bd_counter > 1) { bta_jv_pm_conn_idle(&bta_jv_cb.pm_cb[i]); } @@ -559,7 +559,7 @@ static tBTA_JV_PM_CB* bta_jv_alloc_set_pm_profile_cb(uint32_t jv_handle, tBTA_JV } } } - log::verbose("handle=0x{:x}, app_id={}, idx={}, BTA_JV_PM_MAX_NUM={}, pp_cb={}", jv_handle, + log::verbose("jv_handle=0x{:x}, app_id={}, idx={}, BTA_JV_PM_MAX_NUM={}, pp_cb={}", jv_handle, app_id, i, BTA_JV_PM_MAX_NUM, std::format_ptr(pp_cb)); break; } @@ -573,7 +573,7 @@ static tBTA_JV_PM_CB* bta_jv_alloc_set_pm_profile_cb(uint32_t jv_handle, tBTA_JV bta_jv_cb.pm_cb[i].state = BTA_JV_PM_IDLE_ST; return &bta_jv_cb.pm_cb[i]; } - log::warn("handle=0x{:x}, app_id={}, return NULL", jv_handle, app_id); + log::warn("jv_handle=0x{:x}, app_id={}, return NULL", jv_handle, app_id); return NULL; } @@ -954,7 +954,7 @@ void bta_jv_delete_record(uint32_t handle) { if (handle) { /* this is a record created by btif layer*/ if (!get_legacy_stack_sdp_api()->handle.SDP_DeleteRecord(handle)) { - log::warn("Unable to delete SDP record handle:{}", handle); + log::warn("Unable to delete SDP record handle:{}", handle); } } } @@ -997,7 +997,7 @@ static void bta_jv_l2cap_client_cback(uint16_t gap_handle, uint16_t event, tGAP_ if (GAP_GetLeChannelInfo(gap_handle, &remote_mtu, &local_mps, &remote_mps, &local_credit, &remote_credit, &local_cid, &remote_cid, &acl_handle) != PORT_SUCCESS) { - log::warn("Unable to get GAP channel info handle:{}", gap_handle); + log::warn("Unable to get GAP channel info gap_handle:{}", gap_handle); } evt_data.l2c_open.tx_mtu = remote_mtu; evt_data.l2c_open.local_coc_mps = local_mps; @@ -1426,10 +1426,10 @@ static void bta_jv_port_mgmt_cl_cback(const tPORT_RESULT code, uint16_t port_han return; } - log::verbose("code={}, port_handle={}, handle={}", code, port_handle, p_cb->handle); + log::verbose("code={}, port_handle={}, rfc_handle={}", code, port_handle, p_cb->handle); if (PORT_CheckConnection(port_handle, &rem_bda, &lcid) != PORT_SUCCESS) { - log::warn("Unable to check RFCOMM connection peer:{} handle:{}", rem_bda, port_handle); + log::warn("Unable to check RFCOMM connection peer:{} port_handle:{}", rem_bda, port_handle); } if (code == PORT_SUCCESS) { @@ -1448,7 +1448,7 @@ static void bta_jv_port_mgmt_cl_cback(const tPORT_RESULT code, uint16_t port_han &evt_data.rfc_open.dlci, &evt_data.rfc_open.max_frame_size, &evt_data.rfc_open.acl_handle, &evt_data.rfc_open.mux_initiator) != PORT_SUCCESS) { - log::warn("Unable to get RFCOMM channel info peer:{} handle:{}", rem_bda, port_handle); + log::warn("Unable to get RFCOMM channel info peer:{} port_handle:{}", rem_bda, port_handle); } } p_pcb->state = BTA_JV_ST_CL_OPEN; @@ -1490,7 +1490,7 @@ static void bta_jv_port_event_cl_cback(uint32_t code, uint16_t port_handle) { return; } - log::verbose("code=0x{:x}, port_handle={}, handle={}", code, port_handle, p_cb->handle); + log::verbose("code=0x{:x}, port_handle={}, rfc_handle={}", code, port_handle, p_cb->handle); if (code & PORT_EV_RXCHAR) { evt_data.data_ind.handle = p_cb->handle; p_cb->p_cback(BTA_JV_RFCOMM_DATA_IND_EVT, &evt_data, p_pcb->rfcomm_slot_id); @@ -1512,7 +1512,7 @@ static void bta_jv_port_event_cl_cback(uint32_t code, uint16_t port_handle) { /* Client initiates an RFCOMM connection */ void bta_jv_rfcomm_connect(tBTA_SEC sec_mask, uint8_t remote_scn, const RawAddress& peer_bd_addr, tBTA_JV_RFCOMM_CBACK* p_cback, uint32_t rfcomm_slot_id, - RfcommCfgInfo cfg) { + RfcommCfgInfo cfg, uint32_t app_uid) { uint16_t handle = 0; uint32_t event_mask = BTA_JV_RFC_EV_MASK; PortSettings port_settings; @@ -1527,14 +1527,12 @@ void bta_jv_rfcomm_connect(tBTA_SEC sec_mask, uint8_t remote_scn, const RawAddre }, }; - if (com::android::bluetooth::flags::rfcomm_always_use_mitm()) { - // Update security service record for RFCOMM client so that - // secure RFCOMM connection will be authenticated with MTIM protection - // while creating the L2CAP connection. - get_btm_client_interface().security.BTM_SetSecurityLevel( - true, "RFC_MUX", BTM_SEC_SERVICE_RFC_MUX, sec_mask, BT_PSM_RFCOMM, BTM_SEC_PROTO_RFCOMM, - 0); - } + // Update security service record for RFCOMM client so that + // secure RFCOMM connection will be authenticated with MTIM protection + // while creating the L2CAP connection. + get_btm_client_interface().security.BTM_SetSecurityLevel(true, "RFC_MUX", BTM_SEC_SERVICE_RFC_MUX, + sec_mask, BT_PSM_RFCOMM, + BTM_SEC_PROTO_RFCOMM, 0); if (RFCOMM_CreateConnectionWithSecurity( UUID_SERVCLASS_SERIAL_PORT, remote_scn, false, BTA_JV_DEF_RFC_MTU, peer_bd_addr, @@ -1551,21 +1549,24 @@ void bta_jv_rfcomm_connect(tBTA_SEC sec_mask, uint8_t remote_scn, const RawAddre p_pcb->rfcomm_slot_id = rfcomm_slot_id; bta_jv.rfc_cl_init.use_co = true; + if (PORT_SetAppUid(handle, app_uid) != PORT_SUCCESS) { + log::warn("Unable to set app_uid for port_handle:{}", handle); + } if (PORT_SetEventMaskAndCallback(handle, event_mask, bta_jv_port_event_cl_cback) != PORT_SUCCESS) { - log::warn("Unable to set RFCOMM client event mask and callback handle:{}", handle); + log::warn("Unable to set RFCOMM client event mask and callback port_handle:{}", handle); } if (PORT_SetDataCOCallback(handle, bta_jv_port_data_co_cback) != PORT_SUCCESS) { - log::warn("Unable to set RFCOMM client data callback handle:{}", handle); + log::warn("Unable to set RFCOMM client data callback port_handle:{}", handle); } if (PORT_GetSettings(handle, &port_settings) != PORT_SUCCESS) { - log::warn("Unable to get RFCOMM client state handle:{}", handle); + log::warn("Unable to get RFCOMM client state port_handle:{}", handle); } port_settings.fc_type = (PORT_FC_CTS_ON_INPUT | PORT_FC_CTS_ON_OUTPUT); if (PORT_SetSettings(handle, &port_settings) != PORT_SUCCESS) { - log::warn("Unable to set RFCOMM client state handle:{}", handle); + log::warn("Unable to set RFCOMM client state port_handle:{}", handle); } bta_jv.rfc_cl_init.handle = p_cb->handle; @@ -1579,7 +1580,7 @@ void bta_jv_rfcomm_connect(tBTA_SEC sec_mask, uint8_t remote_scn, const RawAddre if (bta_jv.rfc_cl_init.status == tBTA_JV_STATUS::FAILURE) { if (handle) { if (RFCOMM_RemoveConnection(handle) != PORT_SUCCESS) { - log::warn("Unable to remove RFCOMM connection handle:{}", handle); + log::warn("Unable to remove RFCOMM connection port_handle:{}", handle); } } } @@ -1596,7 +1597,7 @@ static int find_rfc_pcb(uint32_t rfcomm_slot_id, tBTA_JV_RFC_CB** cb, tBTA_JV_PC *pcb = &bta_jv_cb.port_cb[i]; *cb = &bta_jv_cb.rfc_cb[rfc_handle - 1]; log::verbose( - "FOUND rfc_cb_handle=0x{:x}, port.jv_handle=0x{:x}, state={}, rfc_cb->handle=0x{:x}", + "FOUND rfc_handle=0x{:x}, port.jv_handle=0x{:x}, state={}, rfc_cb->handle=0x{:x}", rfc_handle, (*pcb)->handle, (*pcb)->state, (*cb)->handle); return 1; } @@ -1608,11 +1609,11 @@ static int find_rfc_pcb(uint32_t rfcomm_slot_id, tBTA_JV_RFC_CB** cb, tBTA_JV_PC /* Close an RFCOMM connection */ void bta_jv_rfcomm_close(uint32_t handle, uint32_t rfcomm_slot_id) { if (!handle) { - log::error("rfc handle is null"); + log::error("rfc_handle is null"); return; } - log::verbose("rfc handle={}", handle); + log::verbose("rfc_handle={}", handle); tBTA_JV_RFC_CB* p_cb = NULL; tBTA_JV_PCB* p_pcb = NULL; @@ -1646,7 +1647,7 @@ static void bta_jv_port_mgmt_sr_cback(const tPORT_RESULT code, uint16_t port_han return; } uint32_t rfcomm_slot_id = p_pcb->rfcomm_slot_id; - log::verbose("code={}, port_handle=0x{:x}, handle=0x{:x}, p_pcb{}, user={}", code, port_handle, + log::verbose("code={}, port_handle=0x{:x}, jv_handle=0x{:x}, p_pcb{}, user={}", code, port_handle, p_cb->handle, std::format_ptr(p_pcb), p_pcb->rfcomm_slot_id); int status = PORT_CheckConnection(port_handle, &rem_bda, &lcid); @@ -1667,7 +1668,7 @@ static void bta_jv_port_mgmt_sr_cback(const tPORT_RESULT code, uint16_t port_han &evt_data.rfc_srv_open.dlci, &evt_data.rfc_srv_open.max_frame_size, &evt_data.rfc_srv_open.acl_handle, &evt_data.rfc_srv_open.mux_initiator) != PORT_SUCCESS) { - log::warn("Unable to get RFCOMM channel info peer:{} handle:{}", rem_bda, port_handle); + log::warn("Unable to get RFCOMM channel info peer:{} port_handle:{}", rem_bda, port_handle); } } tBTA_JV_PCB* p_pcb_new_listen = bta_jv_add_rfc_port(p_cb, p_pcb); @@ -1728,7 +1729,7 @@ static void bta_jv_port_event_sr_cback(uint32_t code, uint16_t port_handle) { return; } - log::verbose("code=0x{:x}, port_handle={}, handle={}", code, port_handle, p_cb->handle); + log::verbose("code=0x{:x}, port_handle={}, rfc_handle={}", code, port_handle, p_cb->handle); uint32_t user_data = p_pcb->rfcomm_slot_id; if (code & PORT_EV_RXCHAR) { @@ -1778,7 +1779,8 @@ static tBTA_JV_PCB* bta_jv_add_rfc_port(tBTA_JV_RFC_CB* p_cb, tBTA_JV_PCB* p_pcb } else { log::error( - "open pcb not matching listen one, count={}, listen pcb handle={}, open pcb={}", + "open pcb not matching listen one, count={}, listen port_handle={}, open " + "pcb={}", listen, p_pcb->port_handle, p_pcb_open->handle); return NULL; } @@ -1808,24 +1810,25 @@ static tBTA_JV_PCB* bta_jv_add_rfc_port(tBTA_JV_RFC_CB* p_cb, tBTA_JV_PCB* p_pcb p_pcb->rfcomm_slot_id = p_pcb_open->rfcomm_slot_id; if (PORT_ClearKeepHandleFlag(p_pcb->port_handle) != PORT_SUCCESS) { - log::warn("Unable to clear RFCOMM server keep handle flag handle:{}", p_pcb->port_handle); + log::warn("Unable to clear RFCOMM server keep handle flag port_handle:{}", + p_pcb->port_handle); } if (PORT_SetEventMaskAndCallback(p_pcb->port_handle, event_mask, bta_jv_port_event_sr_cback) != PORT_SUCCESS) { - log::warn("Unable to set RFCOMM server event mask and callback handle:{}", + log::warn("Unable to set RFCOMM server event mask and callback port_handle:{}", p_pcb->port_handle); } if (PORT_SetDataCOCallback(p_pcb->port_handle, bta_jv_port_data_co_cback) != PORT_SUCCESS) { - log::warn("Unable to set RFCOMM server data callback handle:{}", p_pcb->port_handle); + log::warn("Unable to set RFCOMM server data callback port_handle:{}", p_pcb->port_handle); } if (PORT_GetSettings(p_pcb->port_handle, &port_settings) != PORT_SUCCESS) { - log::warn("Unable to get RFCOMM server state handle:{}", p_pcb->port_handle); + log::warn("Unable to get RFCOMM server state port_handle:{}", p_pcb->port_handle); } port_settings.fc_type = (PORT_FC_CTS_ON_INPUT | PORT_FC_CTS_ON_OUTPUT); if (PORT_SetSettings(p_pcb->port_handle, &port_settings) != PORT_SUCCESS) { - log::warn("Unable to set RFCOMM server state handle:{}", p_pcb->port_handle); + log::warn("Unable to set RFCOMM server state port_handle:{}", p_pcb->port_handle); } p_pcb->handle = BTA_JV_RFC_H_S_TO_HDL(p_cb->handle, si); log::verbose("p_pcb->handle=0x{:x}, curr_sess={}", p_pcb->handle, p_cb->curr_sess); @@ -1845,7 +1848,7 @@ static tBTA_JV_PCB* bta_jv_add_rfc_port(tBTA_JV_RFC_CB* p_cb, tBTA_JV_PCB* p_pcb /* waits for an RFCOMM client to connect */ void bta_jv_rfcomm_start_server(tBTA_SEC sec_mask, uint8_t local_scn, uint8_t max_session, tBTA_JV_RFCOMM_CBACK* p_cback, uint32_t rfcomm_slot_id, - RfcommCfgInfo cfg) { + RfcommCfgInfo cfg, uint32_t app_uid) { uint16_t handle = 0; uint32_t event_mask = BTA_JV_RFC_EV_MASK; PortSettings port_settings; @@ -1879,21 +1882,24 @@ void bta_jv_rfcomm_start_server(tBTA_SEC sec_mask, uint8_t local_scn, uint8_t ma evt_data.handle = p_cb->handle; evt_data.use_co = true; + if (PORT_SetAppUid(handle, app_uid) != PORT_SUCCESS) { + log::warn("Unable to set app_uid for port_handle:{}", handle); + } if (PORT_ClearKeepHandleFlag(handle) != PORT_SUCCESS) { - log::warn("Unable to clear RFCOMM server keep handle flag handle:{}", handle); + log::warn("Unable to clear RFCOMM server keep handle flag port_handle:{}", handle); } if (PORT_SetEventMaskAndCallback(handle, event_mask, bta_jv_port_event_sr_cback) != PORT_SUCCESS) { - log::warn("Unable to set RFCOMM server event mask and callback handle:{}", handle); + log::warn("Unable to set RFCOMM server event mask and callback port_handle:{}", handle); } if (PORT_GetSettings(handle, &port_settings) != PORT_SUCCESS) { - log::warn("Unable to get RFCOMM server state handle:{}", handle); + log::warn("Unable to get RFCOMM server state port_handle:{}", handle); } port_settings.fc_type = (PORT_FC_CTS_ON_INPUT | PORT_FC_CTS_ON_OUTPUT); if (PORT_SetSettings(handle, &port_settings) != PORT_SUCCESS) { - log::warn("Unable to set RFCOMM port state handle:{}", handle); + log::warn("Unable to set RFCOMM port state port_handle:{}", handle); } } while (0); @@ -1902,12 +1908,12 @@ void bta_jv_rfcomm_start_server(tBTA_SEC sec_mask, uint8_t local_scn, uint8_t ma p_cback(BTA_JV_RFCOMM_START_EVT, &bta_jv, rfcomm_slot_id); if (bta_jv.rfc_start.status == tBTA_JV_STATUS::SUCCESS) { if (PORT_SetDataCOCallback(handle, bta_jv_port_data_co_cback) != PORT_SUCCESS) { - log::error("Unable to set RFCOMM server data callback handle:{}", handle); + log::error("Unable to set RFCOMM server data callback port_handle:{}", handle); } } else { if (handle) { if (RFCOMM_RemoveConnection(handle) != PORT_SUCCESS) { - log::warn("Unable to remote RFCOMM server connection handle:{}", handle); + log::warn("Unable to remote RFCOMM server connection port_handle:{}", handle); } } } @@ -1916,7 +1922,7 @@ void bta_jv_rfcomm_start_server(tBTA_SEC sec_mask, uint8_t local_scn, uint8_t ma /* stops an RFCOMM server */ void bta_jv_rfcomm_stop_server(uint32_t handle, uint32_t rfcomm_slot_id) { if (!handle) { - log::error("jv handle is null"); + log::error("jv_handle is null"); return; } @@ -1967,23 +1973,24 @@ void bta_jv_rfcomm_write(uint32_t handle, uint32_t req_id, tBTA_JV_RFC_CB* p_cb, /* Set or free power mode profile for a JV application */ void bta_jv_set_pm_profile(uint32_t handle, tBTA_JV_PM_ID app_id, tBTA_JV_CONN_STATE init_st) { - log::verbose("handle=0x{:x}, app_id={}, init_st={}", handle, app_id, + log::verbose("jv_handle=0x{:x}, app_id={}, init_st={}", handle, app_id, bta_jv_conn_state_text(init_st)); /* clear PM control block */ if (app_id == BTA_JV_PM_ID_CLEAR) { tBTA_JV_STATUS status = bta_jv_free_set_pm_profile_cb(handle); if (status != tBTA_JV_STATUS::SUCCESS) { - log::warn("Unable to free a power mode profile handle:0x:{:x} app_id:{} state:{} status:{}", - handle, app_id, init_st, bta_jv_status_text(status)); + log::warn( + "Unable to free a power mode profile jv_handle:0x:{:x} app_id:{} state:{} status:{}", + handle, app_id, init_st, bta_jv_status_text(status)); } } else { /* set PM control block */ tBTA_JV_PM_CB* p_cb = bta_jv_alloc_set_pm_profile_cb(handle, app_id); if (p_cb) { bta_jv_pm_state_change(p_cb, init_st); } else { - log::warn("Unable to allocate a power mode profile handle:0x:{:x} app_id:{} state:{}", handle, - app_id, init_st); + log::warn("Unable to allocate a power mode profile jv_handle:0x:{:x} app_id:{} state:{}", + handle, app_id, init_st); } } } @@ -2034,7 +2041,7 @@ static void bta_jv_pm_conn_idle(tBTA_JV_PM_CB* p_cb) { * ******************************************************************************/ static void bta_jv_pm_state_change(tBTA_JV_PM_CB* p_cb, const tBTA_JV_CONN_STATE state) { - log::verbose("p_cb={}, handle=0x{:x}, busy/idle_state={}, app_id={}, conn_state={}", + log::verbose("p_cb={}, jv_handle=0x{:x}, busy/idle_state={}, app_id={}, conn_state={}", std::format_ptr(p_cb), p_cb->handle, p_cb->state, p_cb->app_id, bta_jv_conn_state_text(state)); diff --git a/system/bta/jv/bta_jv_api.cc b/system/bta/jv/bta_jv_api.cc index bf240a9834..3110869bb1 100644 --- a/system/bta/jv/bta_jv_api.cc +++ b/system/bta/jv/bta_jv_api.cc @@ -415,7 +415,7 @@ tBTA_JV_STATUS BTA_JvL2capWrite(uint32_t handle, uint32_t req_id, BT_HDR* msg, u ******************************************************************************/ tBTA_JV_STATUS BTA_JvRfcommConnect(tBTA_SEC sec_mask, uint8_t remote_scn, const RawAddress& peer_bd_addr, tBTA_JV_RFCOMM_CBACK* p_cback, - uint32_t rfcomm_slot_id, RfcommCfgInfo cfg) { + uint32_t rfcomm_slot_id, RfcommCfgInfo cfg, uint32_t app_uid) { log::verbose("remote_scn:{}, peer_bd_addr:{}, rfcomm_slot_id:{}", remote_scn, peer_bd_addr, rfcomm_slot_id); @@ -424,7 +424,7 @@ tBTA_JV_STATUS BTA_JvRfcommConnect(tBTA_SEC sec_mask, uint8_t remote_scn, } do_in_main_thread(Bind(&bta_jv_rfcomm_connect, sec_mask, remote_scn, peer_bd_addr, p_cback, - rfcomm_slot_id, cfg)); + rfcomm_slot_id, cfg, app_uid)); return tBTA_JV_STATUS::SUCCESS; } @@ -470,7 +470,7 @@ tBTA_JV_STATUS BTA_JvRfcommClose(uint32_t handle, uint32_t rfcomm_slot_id) { ******************************************************************************/ tBTA_JV_STATUS BTA_JvRfcommStartServer(tBTA_SEC sec_mask, uint8_t local_scn, uint8_t max_session, tBTA_JV_RFCOMM_CBACK* p_cback, uint32_t rfcomm_slot_id, - RfcommCfgInfo cfg) { + RfcommCfgInfo cfg, uint32_t app_uid) { log::verbose("local_scn:{}, rfcomm_slot_id:{}", local_scn, rfcomm_slot_id); if (p_cback == NULL) { @@ -486,7 +486,7 @@ tBTA_JV_STATUS BTA_JvRfcommStartServer(tBTA_SEC sec_mask, uint8_t local_scn, uin } do_in_main_thread(Bind(&bta_jv_rfcomm_start_server, sec_mask, local_scn, max_session, p_cback, - rfcomm_slot_id, cfg)); + rfcomm_slot_id, cfg, app_uid)); return tBTA_JV_STATUS::SUCCESS; } diff --git a/system/bta/jv/bta_jv_int.h b/system/bta/jv/bta_jv_int.h index 49ccd976b0..1295b88716 100644 --- a/system/bta/jv/bta_jv_int.h +++ b/system/bta/jv/bta_jv_int.h @@ -166,11 +166,11 @@ void bta_jv_l2cap_write(uint32_t handle, uint32_t req_id, BT_HDR* msg, uint32_t tBTA_JV_L2C_CB* p_cb); void bta_jv_rfcomm_connect(tBTA_SEC sec_mask, uint8_t remote_scn, const RawAddress& peer_bd_addr, tBTA_JV_RFCOMM_CBACK* p_cback, uint32_t rfcomm_slot_id, - RfcommCfgInfo cfg); + RfcommCfgInfo cfg, uint32_t app_uid); void bta_jv_rfcomm_close(uint32_t handle, uint32_t rfcomm_slot_id); void bta_jv_rfcomm_start_server(tBTA_SEC sec_mask, uint8_t local_scn, uint8_t max_session, tBTA_JV_RFCOMM_CBACK* p_cback, uint32_t rfcomm_slot_id, - RfcommCfgInfo cfg); + RfcommCfgInfo cfg, uint32_t app_uid); void bta_jv_rfcomm_stop_server(uint32_t handle, uint32_t rfcomm_slot_id); void bta_jv_rfcomm_write(uint32_t handle, uint32_t req_id, tBTA_JV_RFC_CB* p_cb, tBTA_JV_PCB* p_pcb); diff --git a/system/bta/le_audio/broadcaster/broadcaster.cc b/system/bta/le_audio/broadcaster/broadcaster.cc index da8c3ddace..e72757a129 100644 --- a/system/bta/le_audio/broadcaster/broadcaster.cc +++ b/system/bta/le_audio/broadcaster/broadcaster.cc @@ -949,6 +949,17 @@ public: log::info("Start queued broadcast."); StartAudioBroadcast(broadcast_id); } + } else { + // If audio resumes before ISO release, trigger broadcast start + if (audio_state_ == AudioState::ACTIVE) { + cancelBroadcastTimers(); + UpdateAudioActiveStateInPublicAnnouncement(); + + for (auto& broadcast_pair : broadcasts_) { + auto& broadcast = broadcast_pair.second; + broadcast->ProcessMessage(BroadcastStateMachine::Message::START, nullptr); + } + } } if (queued_create_broadcast_request_) { @@ -1394,10 +1405,19 @@ private: return; } + /* If there is ongoing ISO traffic, it might be not torn down unicast stream. Resume of + * broadcast stream would be triggered from IsoTrafficEventCb context, once ISO would be + * released. + */ + if (!IsAnyoneStreaming() && instance->is_iso_running_) { + log::debug("iso is busy, skip resume request"); + return; + } + instance->cancelBroadcastTimers(); instance->UpdateAudioActiveStateInPublicAnnouncement(); - /* In case of double call of resume when broadcast are already in streaming states */ + /* In case of double call of resume when broadcasts are already in streaming states */ if (IsAnyoneStreaming()) { log::debug("broadcasts are already streaming"); instance->le_audio_source_hal_client_->ConfirmStreamingRequest(); diff --git a/system/bta/le_audio/broadcaster/broadcaster_test.cc b/system/bta/le_audio/broadcaster/broadcaster_test.cc index c228ac68ea..8a4acba946 100644 --- a/system/bta/le_audio/broadcaster/broadcaster_test.cc +++ b/system/bta/le_audio/broadcaster/broadcaster_test.cc @@ -1454,6 +1454,78 @@ TEST_F(BroadcasterTest, AudioResumeWhileStreaming) { Mock::VerifyAndClearExpectations(mock_codec_manager_); } +TEST_F(BroadcasterTest, AudioResumeAfterSuspend) { + com::android::bluetooth::flags::provider_->leaudio_big_depends_on_audio_state(true); + + EXPECT_CALL(*mock_codec_manager_, UpdateActiveBroadcastAudioHalClient(mock_audio_source_, true)) + .Times(1); + LeAudioSourceAudioHalClient::Callbacks* audio_receiver; + EXPECT_CALL(*mock_audio_source_, Start) + .WillOnce(DoAll(SaveArg<1>(&audio_receiver), Return(true))) + .WillRepeatedly(Return(false)); + auto broadcast_id = InstantiateBroadcast(); + + ASSERT_NE(audio_receiver, nullptr); + + // OnAudioResume cause state machine go to STREAMING state so BIG creation + EXPECT_CALL(mock_broadcaster_callbacks_, + OnBroadcastStateChanged(broadcast_id, BroadcastState::STREAMING)) + .Times(1); + audio_receiver->OnAudioResume(); + Mock::VerifyAndClearExpectations(mock_audio_source_); + Mock::VerifyAndClearExpectations(mock_codec_manager_); + + // OnAudioSuspend cause starting the BIG termination timer + audio_receiver->OnAudioSuspend(); + ASSERT_EQ(2, get_func_call_count("alarm_set_on_mloop")); + ASSERT_TRUE(big_terminate_timer_->cb != nullptr); + ASSERT_TRUE(broadcast_stop_timer_->cb != nullptr); + + // BIG termination timer execution, state machine go to CONFIGURED state so BIG terminated + EXPECT_CALL(mock_broadcaster_callbacks_, + OnBroadcastStateChanged(broadcast_id, BroadcastState::CONFIGURED)) + .Times(1); + // Imitate execution of BIG termination timer + big_terminate_timer_->cb(big_terminate_timer_->data); + Mock::VerifyAndClearExpectations(&mock_broadcaster_callbacks_); + + // OnAudioResume cause state machine go to STREAMING state so BIG creation + EXPECT_CALL(mock_broadcaster_callbacks_, + OnBroadcastStateChanged(broadcast_id, BroadcastState::STREAMING)) + .Times(1); + audio_receiver->OnAudioResume(); + Mock::VerifyAndClearExpectations(&mock_broadcaster_callbacks_); + + // OnAudioSuspend cause starting the BIG termination timer + audio_receiver->OnAudioSuspend(); + ASSERT_EQ(4, get_func_call_count("alarm_set_on_mloop")); + ASSERT_TRUE(big_terminate_timer_->cb != nullptr); + ASSERT_TRUE(broadcast_stop_timer_->cb != nullptr); + + // BIG termination timer execution, state machine go to CONFIGURED state so BIG terminated + EXPECT_CALL(mock_broadcaster_callbacks_, + OnBroadcastStateChanged(broadcast_id, BroadcastState::CONFIGURED)) + .Times(1); + // Imitate execution of BIG termination timer + big_terminate_timer_->cb(big_terminate_timer_->data); + + // Imitate busy ISO + iso_active_callback(true); + + EXPECT_CALL(mock_broadcaster_callbacks_, + OnBroadcastStateChanged(broadcast_id, BroadcastState::STREAMING)) + .Times(0); + audio_receiver->OnAudioResume(); + Mock::VerifyAndClearExpectations(&mock_broadcaster_callbacks_); + + // Verify if iso de-activation start streaming + EXPECT_CALL(mock_broadcaster_callbacks_, + OnBroadcastStateChanged(broadcast_id, BroadcastState::STREAMING)) + .Times(1); + iso_active_callback(false); + Mock::VerifyAndClearExpectations(&mock_broadcaster_callbacks_); +} + // TODO: Add tests for: // ToRawPacket(BasicAudioAnnouncementData const& in, std::vector<uint8_t>& data) diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc index 7008676f9c..6dbb4ace85 100644 --- a/system/bta/le_audio/client.cc +++ b/system/bta/le_audio/client.cc @@ -249,7 +249,7 @@ public: stream_setup_time_(0) {} void Init(int group_id, LeAudioContextType context_type, int num_of_devices) { - Reset(); + Reset(bluetooth::groups::kGroupUnknown); group_id_ = group_id; context_type_ = context_type; num_of_devices_ = num_of_devices; @@ -257,7 +257,13 @@ public: ToString(context_type_), num_of_devices); } - void Reset(void) { + void Reset(int group_id) { + if (group_id != bluetooth::groups::kGroupUnknown && group_id != group_id_) { + log::verbose("StreamSpeedTracker Reset called for invalid group_id: {} != {}", group_id, + group_id_); + return; + } + log::verbose("StreamSpeedTracker group_id: {}", group_id_); is_started_ = false; group_id_ = bluetooth::groups::kGroupUnknown; @@ -300,14 +306,15 @@ public: ToString(context_type_), total_time_); } - bool IsStarted(void) { - if (is_started_) { + bool IsStarted(int group_id) { + if (is_started_ && group_id_ == group_id) { log::verbose("StreamSpeedTracker group_id: {}, {} is_started_: {} ", group_id_, ToString(context_type_), is_started_); - } else { - log::verbose("StreamSpeedTracker not started "); + return true; } - return is_started_; + log::verbose("StreamSpeedTracker not started {} or group_id does not match ({} ! = {}) ", + is_started_, group_id, group_id_); + return false; } void Dump(std::stringstream& stream) { @@ -422,7 +429,7 @@ public: leAudioHealthStatus_->RegisterCallback(base::BindRepeating(le_audio_health_status_callback)); BTA_GATTC_AppRegister( - le_audio_gattc_callback, + "le_audio", le_audio_gattc_callback, base::Bind( [](base::Closure initCb, uint8_t client_id, uint8_t status) { if (status != GATT_SUCCESS) { @@ -1551,6 +1558,21 @@ public: active_group_id_ = bluetooth::groups::kGroupUnknown; } + void ConfigureStream(LeAudioDeviceGroup* group, bool up_to_qos_configured) { + log::debug("group_id: {}", group->group_id_); + + BidirectionalPair<std::vector<uint8_t>> ccids = { + .sink = ContentControlIdKeeper::GetInstance()->GetAllCcids( + local_metadata_context_types_.sink), + .source = ContentControlIdKeeper::GetInstance()->GetAllCcids( + local_metadata_context_types_.source)}; + if (!groupStateMachine_->ConfigureStream(group, configuration_context_type_, + local_metadata_context_types_, ccids, + up_to_qos_configured)) { + log::info("Could not configure group {}", group->group_id_); + } + } + void PrepareStreamForAConversational(LeAudioDeviceGroup* group) { if (!com::android::bluetooth::flags::leaudio_improve_switch_during_phone_call()) { log::info("Flag leaudio_improve_switch_during_phone_call is not enabled"); @@ -1673,10 +1695,20 @@ public: auto previous_active_group = active_group_id_; log::info("Active group_id changed {} -> {}", previous_active_group, group_id); + bool prepare_for_a_call = IsInCall() || IsInVoipCall(); + if (previous_active_group == bluetooth::groups::kGroupUnknown) { /* Expose audio sessions if there was no previous active group */ StartAudioSession(group); active_group_id_ = group_id; + + /* For the fresh activated LeAudio device, do configuration ahead only when + * phone is in a call. + */ + if (prepare_for_a_call) { + PrepareStreamForAConversational(group); + } + } else { /* In case there was an active group. Stop the stream, but before that, set * the new group so the group change is correctly handled in OnStateMachineStatusReportCb @@ -1687,7 +1719,12 @@ public: /* Note: On purpose we are not sending INACTIVE status up to Java, because previous active * group will be provided in ACTIVE status. This is in order to have single call to audio * framework + * If group become active while phone call, let's configure it right away up to + * the QoS configured state so when audio framework resumes the stream, + * only Enable will left. + * Otherwise, if there is group switch, let's move ASEs to Configured state. */ + ConfigureStream(group, prepare_for_a_call); } /* Reset sink and source listener notified status */ @@ -1701,13 +1738,6 @@ public: callbacks_->OnGroupStatus(active_group_id_, GroupStatus::ACTIVE); SendAudioGroupSelectableCodecConfigChanged(group); } - - /* If group become active while phone call, let's configure it right away, - * so when audio framework resumes the stream, it will be almost there. - */ - if (IsInCall()) { - PrepareStreamForAConversational(group); - } } void SetEnableState(const RawAddress& address, bool enabled) override { @@ -1847,7 +1877,8 @@ public: int source_audio_location, int sink_supported_context_types, int source_supported_context_types, const std::vector<uint8_t>& handles, const std::vector<uint8_t>& sink_pacs, - const std::vector<uint8_t>& source_pacs, const std::vector<uint8_t>& ases) { + const std::vector<uint8_t>& source_pacs, const std::vector<uint8_t>& ases, + const std::vector<uint8_t>& gmap) { LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); if (leAudioDevice) { @@ -1916,6 +1947,14 @@ public: log::warn("Could not load ases"); } + if (gmap.size() != 0) { + leAudioDevice->gmap_client_ = std::make_unique<GmapClient>(leAudioDevice->address_); + if (!le_audio::DeserializeGmap(leAudioDevice->gmap_client_.get(), gmap)) { + leAudioDevice->gmap_client_.reset(); + log::warn("Invalid GMAP storage for {}", leAudioDevice->address_); + } + } + leAudioDevice->autoconnect_flag_ = autoconnect; /* When adding from storage, make sure that autoconnect is used * by all the devices in the group. @@ -1929,6 +1968,11 @@ public: return SerializeHandles(leAudioDevice, out); } + bool GetGmapForStorage(const RawAddress& addr, std::vector<uint8_t>& out) { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(addr); + return SerializeGmap(leAudioDevice->gmap_client_.get(), out); + } + bool GetSinkPacsForStorage(const RawAddress& addr, std::vector<uint8_t>& out) { LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(addr); return SerializeSinkPacs(leAudioDevice, out); @@ -2330,9 +2374,11 @@ public: } else if (leAudioDevice->gmap_client_ != nullptr && GmapClient::IsGmapClientEnabled() && hdl == leAudioDevice->gmap_client_->getRoleHandle()) { leAudioDevice->gmap_client_->parseAndSaveGmapRole(len, value); + btif_storage_leaudio_update_gmap_bin(leAudioDevice->address_); } else if (leAudioDevice->gmap_client_ != nullptr && GmapClient::IsGmapClientEnabled() && hdl == leAudioDevice->gmap_client_->getUGTFeatureHandle()) { leAudioDevice->gmap_client_->parseAndSaveUGTFeature(len, value); + btif_storage_leaudio_update_gmap_bin(leAudioDevice->address_); } else { log::error("Unknown attribute read: 0x{:x}", hdl); } @@ -2466,7 +2512,7 @@ public: /* Check if the device is in allow list and update the flag */ leAudioDevice->UpdateDeviceAllowlistFlag(); - if (BTM_SecIsSecurityPending(address)) { + if (BTM_SecIsLeSecurityPending(address)) { /* if security collision happened, wait for encryption done * (BTA_GATTC_ENC_CMPL_CB_EVT) */ return; @@ -5322,9 +5368,37 @@ public: // Do not take the obsolete metadata remote_metadata.get(remote_other_direction).clear(); } else { - remote_metadata.get(remote_other_direction).unset_all(all_bidirectional_contexts); - remote_metadata.get(remote_other_direction) - .unset_all(single_direction_only_context_types); + // The other direction was opened when already in a bidirectional scenario that was not a + // VoIP or a regular Call. We need to figure out which direction metadata is the leading + // one. + // Note: We usually remove any bidirectional or the previous direction specific context + // from the previous direction metadata and replace it with the just-resumed + // direction (but still bidirectional) context. However when recording is started + // in a GAME scenario, we don't want to reconfigure to or mix the context with LIVE. + auto remote_game_uplink_available = + group->GetAvailableContexts(le_audio::types::kLeAudioDirectionSource) + .test(LeAudioContextType::GAME); + auto local_game_uplink_active = + (audio_sender_state_ == AudioState::STARTED) && + remote_metadata.sink.test(LeAudioContextType::GAME) && + remote_metadata.source.test_any(LeAudioContextType::LIVE | + LeAudioContextType::CONVERSATIONAL); + log::debug( + "Remote {} metadata change ({}) while having remote {} context ({}) in a " + "bidirectional scenario of {}, local_game_uplink_active: {}, " + "remote_game_uplink_available: {}", + remote_direction_str, ToString(remote_metadata.get(remote_direction)), + remote_other_direction_str, ToString(remote_metadata.get(remote_other_direction)), + ToString(configuration_context_type_), local_game_uplink_active, + remote_game_uplink_available); + if (local_game_uplink_active && remote_game_uplink_available) { + remote_metadata.source.clear(); + remote_metadata.source.set(LeAudioContextType::GAME); + } else { + remote_metadata.get(remote_other_direction).unset_all(all_bidirectional_contexts); + remote_metadata.get(remote_other_direction) + .unset_all(single_direction_only_context_types); + } } remote_metadata.get(remote_other_direction) @@ -5802,9 +5876,9 @@ public: void speed_start_setup(int group_id, LeAudioContextType context_type, int num_of_connected, bool is_reconfig = false) { - log::verbose("is_started {} is_reconfig {} num_of_connected {}", speed_tracker_.IsStarted(), - is_reconfig, num_of_connected); - if (!speed_tracker_.IsStarted()) { + log::verbose("is_started {} is_reconfig {} num_of_connected {}", + speed_tracker_.IsStarted(group_id), is_reconfig, num_of_connected); + if (!speed_tracker_.IsStarted(group_id)) { speed_tracker_.Init(group_id, context_type, num_of_connected); } if (is_reconfig) { @@ -5814,26 +5888,27 @@ public: } } - void speed_stop_reconfig(void) { + void speed_stop_reconfig(int group_id) { log::verbose(""); - if (!speed_tracker_.IsStarted()) { + if (!speed_tracker_.IsStarted(group_id)) { return; } + speed_tracker_.ReconfigurationComplete(); } - void speed_stream_created() { + void speed_stream_created(int group_id) { log::verbose(""); - if (!speed_tracker_.IsStarted()) { + if (!speed_tracker_.IsStarted(group_id)) { return; } speed_tracker_.StreamCreated(); } - void speed_stop_setup() { + void speed_stop_setup(int group_id) { log::verbose(""); - if (!speed_tracker_.IsStarted()) { + if (!speed_tracker_.IsStarted(group_id)) { return; } @@ -5843,7 +5918,7 @@ public: speed_tracker_.StopStreamSetup(); stream_speed_history_.emplace_front(speed_tracker_); - speed_tracker_.Reset(); + speed_tracker_.Reset(group_id); } void notifyGroupStreamStatus(int group_id, GroupStreamStatus groupStreamStatus) { @@ -5898,26 +5973,36 @@ public: */ CancelStreamingRequest(); ReconfigurationComplete(previously_active_directions); - speed_stop_reconfig(); + speed_stop_reconfig(active_group_id_); } void OnStateMachineStatusReportCb(int group_id, GroupStreamStatus status) { - log::info("status: {}, group_id: {}, audio_sender_state {}, audio_receiver_state {}", - static_cast<int>(status), group_id, bluetooth::common::ToString(audio_sender_state_), - bluetooth::common::ToString(audio_receiver_state_)); + /* When switching stream between two group, it is important to keep track if given status is for + * active group or not in order to proper Audio HAL notifications. + * It means, we should update Audio HAL and clear common resources when group is an active group + * or active group is already cleared. + */ + bool is_active_group_operation = + (group_id == active_group_id_ || active_group_id_ == bluetooth::groups::kGroupUnknown); + + log::info( + "status: {}, group_id: {}, audio_sender_state {}, audio_receiver_state {}, " + "is_active_group_operation {}", + static_cast<int>(status), group_id, bluetooth::common::ToString(audio_sender_state_), + bluetooth::common::ToString(audio_receiver_state_), is_active_group_operation); LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); notifyGroupStreamStatus(group_id, status); switch (status) { case GroupStreamStatus::STREAMING: { - if (group_id != active_group_id_) { + if (!is_active_group_operation) { log::error("Streaming group {} is no longer active. Stop the group.", group_id); GroupStop(group_id); return; } - speed_stream_created(); + speed_stream_created(group_id); bluetooth::le_audio::MetricsCollector::Get()->OnStreamStarted(active_group_id_, configuration_context_type_); @@ -5939,7 +6024,7 @@ public: * Just stop streaming */ log::warn("Stopping stream for group {} as AF not interested.", group_id); - speed_stop_setup(); + speed_stop_setup(group_id); groupStateMachine_->StopStream(group); return; } @@ -5954,7 +6039,7 @@ public: "reconfigure to {}", ToString(group->GetConfigurationContextType()), ToString(configuration_context_type_)); - speed_stop_setup(); + speed_stop_setup(group_id); initReconfiguration(group, group->GetConfigurationContextType()); return; } @@ -5976,16 +6061,21 @@ public: StartReceivingAudio(group_id); } - speed_stop_setup(); + speed_stop_setup(group_id); break; } case GroupStreamStatus::SUSPENDED: - speed_tracker_.Reset(); - /** Stop Audio but don't release all the Audio resources */ - SuspendAudio(); + speed_tracker_.Reset(group_id); + + if (is_active_group_operation) { + /** Stop Audio but don't release all the Audio resources */ + SuspendAudio(); + } break; case GroupStreamStatus::CONFIGURED_BY_USER: - reconfigurationComplete(); + if (is_active_group_operation) { + reconfigurationComplete(); + } break; case GroupStreamStatus::CONFIGURED_AUTONOMOUS: /* This state is notified only when @@ -5994,19 +6084,21 @@ public: * it is handled same as IDLE */ case GroupStreamStatus::IDLE: { - if (sw_enc_left) { - sw_enc_left.reset(); - } - if (sw_enc_right) { - sw_enc_right.reset(); - } - if (sw_dec_left) { - sw_dec_left.reset(); - } - if (sw_dec_right) { - sw_dec_right.reset(); + if (is_active_group_operation) { + if (sw_enc_left) { + sw_enc_left.reset(); + } + if (sw_enc_right) { + sw_enc_right.reset(); + } + if (sw_dec_left) { + sw_dec_left.reset(); + } + if (sw_dec_right) { + sw_dec_right.reset(); + } + CleanCachedMicrophoneData(); } - CleanCachedMicrophoneData(); if (group) { handleAsymmetricPhyForUnicast(group); @@ -6042,8 +6134,10 @@ public: } log::info("Clear pending configuration flag for group {}", group->group_id_); group->ClearPendingConfiguration(); - reconfigurationComplete(); - } else { + if (is_active_group_operation) { + reconfigurationComplete(); + } + } else if (is_active_group_operation) { if (sink_monitor_mode_) { notifyAudioLocalSink(UnicastMonitorModeStatus::STREAMING_SUSPENDED); } @@ -6054,8 +6148,10 @@ public: } } - speed_tracker_.Reset(); - CancelStreamingRequest(); + speed_tracker_.Reset(group_id); + if (is_active_group_operation) { + CancelStreamingRequest(); + } if (group) { NotifyUpperLayerGroupTurnedIdleDuringCall(group->group_id_); @@ -6064,6 +6160,20 @@ public: } break; } + case GroupStreamStatus::RELEASING_AUTONOMOUS: + /* Remote device releases all the ASEs autonomusly. This should not happen and not sure what + * is the remote device intention. If remote wants stop the stream then MCS shall be used to + * stop the stream in a proper way. For a phone call, GTBS shall be used. For now we assume + * this device has does not want to be used for streaming and mark it as Inactive. + */ + log::warn("Group {} is doing autonomous release, make it inactive", group_id); + if (group) { + group->PrintDebugState(); + groupSetAndNotifyInactive(); + } + audio_sender_state_ = AudioState::IDLE; + audio_receiver_state_ = AudioState::IDLE; + break; case GroupStreamStatus::RELEASING: case GroupStreamStatus::SUSPENDING: if (active_group_id_ != bluetooth::groups::kGroupUnknown && @@ -6076,23 +6186,29 @@ public: * it means that it is some internal state machine error. This is very unlikely and * for now just Inactivate the group. */ - log::error("Internal state machine error"); + log::error("Internal state machine error for group {}", group_id); group->PrintDebugState(); groupSetAndNotifyInactive(); + audio_sender_state_ = AudioState::IDLE; + audio_receiver_state_ = AudioState::IDLE; + return; } - if (audio_sender_state_ != AudioState::IDLE) { - audio_sender_state_ = AudioState::RELEASING; - } + if (is_active_group_operation) { + if (audio_sender_state_ != AudioState::IDLE) { + audio_sender_state_ = AudioState::RELEASING; + } - if (audio_receiver_state_ != AudioState::IDLE) { - audio_receiver_state_ = AudioState::RELEASING; - } + if (audio_receiver_state_ != AudioState::IDLE) { + audio_receiver_state_ = AudioState::RELEASING; + } - if (group && group->IsPendingConfiguration()) { - log::info("Releasing for reconfiguration, don't send anything on CISes"); - SuspendedForReconfiguration(); + if (group && group->IsPendingConfiguration()) { + log::info("Releasing for reconfiguration, don't send anything on CISes"); + SuspendedForReconfiguration(); + } } + break; default: break; @@ -6238,14 +6354,14 @@ private: * the session callbacks special action from this Module would be * required e.g. to Unicast handover. */ - if (!com::android::bluetooth::flags::leaudio_use_audio_recording_listener()) { - if (!sink_monitor_mode_) { - local_metadata_context_types_.sink.clear(); - le_audio_sink_hal_client_->Stop(); - le_audio_sink_hal_client_.reset(); - } + if (com::android::bluetooth::flags::leaudio_use_audio_recording_listener() || + !sink_monitor_mode_) { + local_metadata_context_types_.sink.clear(); + le_audio_sink_hal_client_->Stop(); + le_audio_sink_hal_client_.reset(); } } + local_metadata_context_types_.source.clear(); configuration_context_type_ = LeAudioContextType::UNINITIALIZED; @@ -6544,14 +6660,12 @@ DeviceGroupsCallbacksImpl deviceGroupsCallbacksImpl; } // namespace -void LeAudioClient::AddFromStorage(const RawAddress& addr, bool autoconnect, - int sink_audio_location, int source_audio_location, - int sink_supported_context_types, - int source_supported_context_types, - const std::vector<uint8_t>& handles, - const std::vector<uint8_t>& sink_pacs, - const std::vector<uint8_t>& source_pacs, - const std::vector<uint8_t>& ases) { +void LeAudioClient::AddFromStorage( + const RawAddress& addr, bool autoconnect, int sink_audio_location, + int source_audio_location, int sink_supported_context_types, + int source_supported_context_types, const std::vector<uint8_t>& handles, + const std::vector<uint8_t>& sink_pacs, const std::vector<uint8_t>& source_pacs, + const std::vector<uint8_t>& ases, const std::vector<uint8_t>& gmap) { if (!instance) { log::error("Not initialized yet"); return; @@ -6559,7 +6673,7 @@ void LeAudioClient::AddFromStorage(const RawAddress& addr, bool autoconnect, instance->AddFromStorage(addr, autoconnect, sink_audio_location, source_audio_location, sink_supported_context_types, source_supported_context_types, handles, - sink_pacs, source_pacs, ases); + sink_pacs, source_pacs, ases, gmap); } bool LeAudioClient::GetHandlesForStorage(const RawAddress& addr, std::vector<uint8_t>& out) { @@ -6598,6 +6712,15 @@ bool LeAudioClient::GetAsesForStorage(const RawAddress& addr, std::vector<uint8_ return instance->GetAsesForStorage(addr, out); } +bool LeAudioClient::GetGmapForStorage(const RawAddress& addr, std::vector<uint8_t>& out) { + if (!instance) { + log::error("Not initialized yet"); + return false; + } + + return instance->GetGmapForStorage(addr, out); +} + bool LeAudioClient::IsLeAudioClientRunning(void) { return instance != nullptr; } bool LeAudioClient::IsLeAudioClientInStreaming(void) { @@ -6669,6 +6792,7 @@ void LeAudioClient::Initialize( void LeAudioClient::DebugDump(int fd) { std::scoped_lock<std::mutex> lock(instance_mutex); DeviceGroups::DebugDump(fd); + GmapServer::DebugDump(fd); dprintf(fd, "LeAudio Manager: \n"); if (instance) { diff --git a/system/bta/le_audio/client_linux.cc b/system/bta/le_audio/client_linux.cc index ff56a555b8..9908ef0a61 100644 --- a/system/bta/le_audio/client_linux.cc +++ b/system/bta/le_audio/client_linux.cc @@ -48,14 +48,12 @@ void LeAudioClient::Initialize( void LeAudioClient::Cleanup(void) {} LeAudioClient* LeAudioClient::Get(void) { return nullptr; } void LeAudioClient::DebugDump(int fd) {} -void LeAudioClient::AddFromStorage(const RawAddress& addr, bool autoconnect, - int sink_audio_location, int source_audio_location, - int sink_supported_context_types, - int source_supported_context_types, - const std::vector<uint8_t>& handles, - const std::vector<uint8_t>& sink_pacs, - const std::vector<uint8_t>& source_pacs, - const std::vector<uint8_t>& ases) {} +void LeAudioClient::AddFromStorage( + const RawAddress& addr, bool autoconnect, int sink_audio_location, + int source_audio_location, int sink_supported_context_types, + int source_supported_context_types, const std::vector<uint8_t>& handles, + const std::vector<uint8_t>& sink_pacs, const std::vector<uint8_t>& source_pacs, + const std::vector<uint8_t>& ases, const std::vector<uint8_t>& gmap) {} bool LeAudioClient::GetHandlesForStorage(const RawAddress& addr, std::vector<uint8_t>& out) { return false; } @@ -68,5 +66,8 @@ bool LeAudioClient::GetSourcePacsForStorage(const RawAddress& addr, std::vector< bool LeAudioClient::GetAsesForStorage(const RawAddress& addr, std::vector<uint8_t>& out) { return false; } +bool LeAudioClient::GetGmapForStorage(const RawAddress& addr, std::vector<uint8_t>& out) { + return false; +} bool LeAudioClient::IsLeAudioClientRunning() { return false; } bool LeAudioClient::IsLeAudioClientInStreaming() { return false; } diff --git a/system/bta/le_audio/codec_manager.cc b/system/bta/le_audio/codec_manager.cc index 9977fec885..b956adec0e 100644 --- a/system/bta/le_audio/codec_manager.cc +++ b/system/bta/le_audio/codec_manager.cc @@ -25,6 +25,7 @@ #include <cstdint> #include <functional> #include <memory> +#include <optional> #include <ostream> #include <sstream> #include <string> @@ -39,6 +40,8 @@ #include "broadcaster/broadcaster_types.h" #include "bta_le_audio_api.h" #include "btm_iso_api_types.h" +#include "gmap_client.h" +#include "gmap_server.h" #include "hardware/bt_le_audio.h" #include "hci/controller_interface.h" #include "hci/hci_packets.h" @@ -137,6 +140,19 @@ public: osi_property_get_bool("bluetooth.leaudio.dual_bidirection_swb.supported", false); bluetooth::le_audio::AudioSetConfigurationProvider::Initialize(GetCodecLocation()); UpdateOffloadCapability(offloading_preference); + + if (IsUsingCodecExtensibility()) { + codec_provider_info_ = + audio::le_audio::LeAudioClientInterface::Get()->GetCodecConfigProviderInfo(); + if (codec_provider_info_.has_value() && codec_provider_info_->allowAsymmetric && + codec_provider_info_->lowLatency) { + GmapClient::UpdateGmapOffloaderSupport(true); + GmapServer::UpdateGmapOffloaderSupport(true); + log::debug("Asymmetric configuration supported. Enabling offloader GMAP support."); + } else { + log::debug("Asymmetric configurations not supported. Not enabling offloader GMAP support."); + } + } } ~codec_manager_impl() { if (GetCodecLocation() != CodecLocation::HOST) { @@ -149,6 +165,10 @@ public: } CodecLocation GetCodecLocation(void) const { return codec_location_; } + std::optional<ProviderInfo> GetCodecConfigProviderInfo(void) const { + return codec_provider_info_; + } + bool IsDualBiDirSwbSupported(void) const { if (GetCodecLocation() == CodecLocation::ADSP) { // Whether dual bidirection swb is supported by property and for offload @@ -1094,6 +1114,8 @@ private: std::unordered_map<btle_audio_codec_index_t, uint8_t> btle_audio_codec_type_map_ = { {::bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3, types::kLeAudioCodingFormatLC3}}; + std::optional<ProviderInfo> codec_provider_info_; + std::vector<btle_audio_codec_config_t> codec_input_capa = {}; std::vector<btle_audio_codec_config_t> codec_output_capa = {}; int broadcast_target_config = -1; @@ -1138,26 +1160,24 @@ std::ostream& operator<<(std::ostream& os, if (req.sink_requirements.has_value()) { for (auto const& sink_req : req.sink_requirements.value()) { - os << "sink_req: {"; + os << ", sink_req: {"; os << ", target_latency: " << +sink_req.target_latency; os << ", target_Phy: " << +sink_req.target_Phy; - // os << sink_req.params.GetAsCoreCodecCapabilities(); os << "}"; } } else { - os << "sink_req: None"; + os << ", sink_req: None"; } if (req.source_requirements.has_value()) { for (auto const& source_req : req.source_requirements.value()) { - os << "source_req: {"; + os << ", source_req: {"; os << ", target_latency: " << +source_req.target_latency; os << ", target_Phy: " << +source_req.target_Phy; - // os << source_req.params.GetAsCoreCodecCapabilities(); os << "}"; } } else { - os << "source_req: None"; + os << ", source_req: None"; } os << "}"; @@ -1207,6 +1227,14 @@ types::CodecLocation CodecManager::GetCodecLocation(void) const { return pimpl_->codec_manager_impl_->GetCodecLocation(); } +std::optional<ProviderInfo> CodecManager::GetCodecConfigProviderInfo(void) const { + if (!pimpl_->IsRunning()) { + return std::nullopt; + } + + return pimpl_->codec_manager_impl_->GetCodecConfigProviderInfo(); +} + bool CodecManager::IsDualBiDirSwbSupported(void) const { if (!pimpl_->IsRunning()) { return false; diff --git a/system/bta/le_audio/codec_manager.h b/system/bta/le_audio/codec_manager.h index 1a86a9d4b0..27c2534216 100644 --- a/system/bta/le_audio/codec_manager.h +++ b/system/bta/le_audio/codec_manager.h @@ -60,8 +60,28 @@ struct broadcast_offload_config { uint16_t max_transport_latency; }; +struct ProviderInfo { + bool allowAsymmetric = false; + bool lowLatency = false; + + inline std::string toString() const { + std::ostringstream _aidl_os; + _aidl_os << "ProviderInfo{"; + _aidl_os << "allowAsymmetric: " << allowAsymmetric; + _aidl_os << ", lowLatency: " << lowLatency; + _aidl_os << "}"; + return _aidl_os.str(); + } +}; + class CodecManager { public: + enum Flags { + NONE = 0x00, + LOW_LATENCY, + ALLOW_ASYMMETRIC, + }; + struct UnicastConfigurationRequirements { ::bluetooth::le_audio::types::LeAudioContextType audio_context_type; std::optional<std::vector<types::acs_ac_record>> sink_pacs; @@ -75,6 +95,8 @@ public: std::optional<std::vector<DeviceDirectionRequirements>> sink_requirements; std::optional<std::vector<DeviceDirectionRequirements>> source_requirements; + + Flags flags; }; /* The provider function checks each possible configuration (from the set of @@ -103,6 +125,7 @@ public: const std::vector<bluetooth::le_audio::btle_audio_codec_config_t>& offloading_preference); void Stop(void); virtual types::CodecLocation GetCodecLocation(void) const; + virtual std::optional<ProviderInfo> GetCodecConfigProviderInfo(void) const; virtual bool IsDualBiDirSwbSupported(void) const; virtual bool UpdateCisConfiguration(const std::vector<struct types::cis>& cises, const stream_parameters& stream_params, uint8_t direction); diff --git a/system/bta/le_audio/codec_manager_test.cc b/system/bta/le_audio/codec_manager_test.cc index 5770fdbea4..fe6a332ab8 100644 --- a/system/bta/le_audio/codec_manager_test.cc +++ b/system/bta/le_audio/codec_manager_test.cc @@ -25,6 +25,8 @@ #include "hci/controller_interface_mock.h" #include "hci/hci_packets.h" #include "internal_include/stack_config.h" +#include "le_audio/gmap_client.h" +#include "le_audio/gmap_server.h" #include "le_audio/le_audio_types.h" #include "le_audio_set_configuration_provider.h" #include "test/mock/mock_legacy_hci_interface.h" @@ -49,11 +51,6 @@ using bluetooth::le_audio::types::kLeAudioDirectionSource; void osi_property_set_bool(const char* key, bool value); -template <typename T> -T& bluetooth::le_audio::types::BidirectionalPair<T>::get(uint8_t direction) { - return (direction == bluetooth::le_audio::types::kLeAudioDirectionSink) ? sink : source; -} - static const std::vector<AudioSetConfiguration> offload_capabilities_none(0); const std::vector<AudioSetConfiguration>* offload_capabilities = &offload_capabilities_none; @@ -95,18 +92,22 @@ stack_config_t mock_stack_config{ const stack_config_t* stack_config_get_interface(void) { return &mock_stack_config; } -namespace bluetooth { -namespace audio { -namespace le_audio { +namespace bluetooth::audio::le_audio { OffloadCapabilities get_offload_capabilities() { return {*offload_capabilities, *offload_capabilities}; } -} // namespace le_audio -} // namespace audio -} // namespace bluetooth +std::optional<bluetooth::le_audio::ProviderInfo> LeAudioClientInterface::GetCodecConfigProviderInfo( + void) const { + return std::nullopt; +} +LeAudioClientInterface* LeAudioClientInterface::Get() { return nullptr; } +} // namespace bluetooth::audio::le_audio namespace bluetooth::le_audio { +void GmapClient::UpdateGmapOffloaderSupport(bool) {} +void GmapServer::UpdateGmapOffloaderSupport(bool) {} + class MockLeAudioSourceHalClient; MockLeAudioSourceHalClient* mock_le_audio_source_hal_client_; std::unique_ptr<LeAudioSourceAudioHalClient> owned_mock_le_audio_source_hal_client_; @@ -681,12 +682,16 @@ TEST_F(CodecManagerTestAdsp, test_capabilities_none) { TEST_F(CodecManagerTestAdsp, test_capabilities) { for (auto test_context : ::bluetooth::le_audio::types::kLeAudioContextAllTypesArray) { // Build the offloader capabilities vector using the configuration provider - // in HOST mode to get all the .json filce configuration entries. + // in HOST mode to get all the .json file configuration entries. std::vector<AudioSetConfiguration> offload_capabilities; AudioSetConfigurationProvider::Initialize(bluetooth::le_audio::types::CodecLocation::HOST); - for (auto& cap : *AudioSetConfigurationProvider::Get()->GetConfigurations(test_context)) { + auto all_local_configs = AudioSetConfigurationProvider::Get()->GetConfigurations(test_context); + ASSERT_NE(0lu, all_local_configs->size()); + + for (auto& cap : *all_local_configs) { offload_capabilities.push_back(*cap); } + ASSERT_NE(0u, offload_capabilities.size()); set_mock_offload_capabilities(offload_capabilities); // Clean up before the codec manager starts it in ADSP mode. diff --git a/system/bta/le_audio/device_groups.cc b/system/bta/le_audio/device_groups.cc index 85c230c0b0..c4000c16d7 100644 --- a/system/bta/le_audio/device_groups.cc +++ b/system/bta/le_audio/device_groups.cc @@ -34,6 +34,7 @@ #include "audio_hal_client/audio_hal_client.h" #include "bta/include/bta_gatt_api.h" +#include "bta/le_audio/gmap_server.h" #include "bta_csis_api.h" #include "bta_groups.h" #include "btif/include/btif_profile_storage.h" @@ -785,8 +786,8 @@ bool LeAudioDeviceGroup::GetPresentationDelay(uint32_t* delay, uint8_t direction } while ((ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase))); } while ((leAudioDevice = GetNextActiveDevice(leAudioDevice))); - if (preferred_delay_min <= preferred_delay_max && preferred_delay_min > delay_min && - preferred_delay_min < delay_max) { + if (preferred_delay_min <= preferred_delay_max && preferred_delay_min >= delay_min && + preferred_delay_min <= delay_max) { *delay = preferred_delay_min; } else { *delay = delay_min; @@ -825,8 +826,11 @@ CodecManager::UnicastConfigurationRequirements LeAudioDeviceGroup::GetAudioSetConfigurationRequirements(types::LeAudioContextType ctx_type) const { auto new_req = CodecManager::UnicastConfigurationRequirements{ .audio_context_type = ctx_type, + .flags = CodecManager::Flags::NONE, }; + bool remote_has_gmap = false; + // Define a requirement for each location. Knowing codec specific // capabilities (i.e. multiplexing capability) the config provider can // determine the number of ASEs to activate. @@ -933,6 +937,25 @@ LeAudioDeviceGroup::GetAudioSetConfigurationRequirements(types::LeAudioContextTy } } } + + if (device->gmap_client_) { + remote_has_gmap = true; + } + } + + if ((ctx_type == ::bluetooth::le_audio::types::LeAudioContextType::GAME) && + GmapClient::IsGmapClientEnabled() && GmapServer::IsGmapServerEnabled() && remote_has_gmap) { + // Allow asymmetric configurations for the low latency GAME scenarios + new_req.flags = CodecManager::Flags(CodecManager::Flags::ALLOW_ASYMMETRIC | + CodecManager::Flags::LOW_LATENCY); + log::debug( + "GMAP is enabled. Set asymmetric flag for the GAME audio context configuration " + "requests."); + } else { + log::debug( + "GMAP is disabled, remote_has_gmap: {}, gmap_client_enabled: {}, gmap_server_enabled: " + "{}", + remote_has_gmap, GmapClient::IsGmapClientEnabled(), GmapServer::IsGmapServerEnabled()); } return new_req; diff --git a/system/bta/le_audio/gmap_client.h b/system/bta/le_audio/gmap_client.h index ec3d431afe..4b98abb868 100644 --- a/system/bta/le_audio/gmap_client.h +++ b/system/bta/le_audio/gmap_client.h @@ -26,8 +26,8 @@ namespace bluetooth::le_audio { class GmapClient { public: - void AddFromStorage(const RawAddress& addr, const uint8_t role, const uint16_t role_handle, - const uint8_t UGT_feature, const uint16_t UGT_feature_handle); + void AddFromStorage(uint8_t role, uint16_t role_handle, uint8_t UGT_feature, + uint16_t UGT_feature_handle); void DebugDump(std::stringstream& stream); @@ -41,15 +41,15 @@ public: bool parseAndSaveUGTFeature(uint16_t len, const uint8_t* value); - std::bitset<8> getRole(); + std::bitset<8> getRole() const; - uint16_t getRoleHandle(); + uint16_t getRoleHandle() const; void setRoleHandle(uint16_t handle); - std::bitset<8> getUGTFeature(); + std::bitset<8> getUGTFeature() const; - uint16_t getUGTFeatureHandle(); + uint16_t getUGTFeatureHandle() const; void setUGTFeatureHandle(uint16_t handle); diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc index 2891a9a8d4..693494f73c 100644 --- a/system/bta/le_audio/le_audio_client_test.cc +++ b/system/bta/le_audio/le_audio_client_test.cc @@ -1956,7 +1956,9 @@ protected: unicast_source_hal_cb_)); SyncOnMainLoop(); - Mock::VerifyAndClearExpectations(&*mock_le_audio_source_hal_client_); + if (expected_confirmation || expected_cancel) { + Mock::VerifyAndClearExpectations(&*mock_le_audio_source_hal_client_); + } } void LocalAudioSinkSuspend(void) { @@ -2693,6 +2695,7 @@ protected: LeAudioSinkAudioHalClient::Callbacks* unicast_sink_hal_cb_ = nullptr; uint8_t default_channel_cnt = 0x03; + uint8_t default_src_channel_cnt = default_channel_cnt; uint8_t default_ase_cnt = 1; NiceMock<MockCsisClient> mock_csis_client_module_; @@ -2774,8 +2777,8 @@ protected: std::vector<::bluetooth::le_audio::btle_audio_codec_config_t> framework_encode_preference; BtaAppRegisterCallback app_register_callback; - EXPECT_CALL(mock_gatt_interface_, AppRegister(_, _, _)) - .WillOnce(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); + EXPECT_CALL(mock_gatt_interface_, AppRegister(_, _, _, _)) + .WillOnce(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback))); LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, base::Bind([](MockFunction<void()>* foo) { foo->Call(); }, &mock_storage_load), @@ -2969,8 +2972,8 @@ TEST_F(UnicastTestNoInit, InitializeNoHal_2_1) { ON_CALL(mock_hal_2_1_verifier, Call()).WillByDefault([]() -> bool { return false; }); BtaAppRegisterCallback app_register_callback; - ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) - .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); + ON_CALL(mock_gatt_interface_, AppRegister(_, _, _, _)) + .WillByDefault(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback))); std::vector<::bluetooth::le_audio::btle_audio_codec_config_t> framework_encode_preference; EXPECT_DEATH( @@ -2989,7 +2992,7 @@ TEST_F(UnicastTest, CleanupWhenUserConnecting) { uint16_t conn_id = 1; SetSampleDatabaseEarbudsValid(1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, - default_channel_cnt, 0x0004, + default_src_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ @@ -3728,17 +3731,20 @@ TEST_F(UnicastTestNoInit, ConnectFailedDueToInvalidParameters) { std::vector<uint8_t> snk_pacs; LeAudioClient::GetSinkPacsForStorage(test_address0, snk_pacs); + std::vector<uint8_t> gmap_data; + LeAudioClient::GetGmapForStorage(test_address0, gmap_data); + EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() { do_in_main_thread(base::Bind(&LeAudioClient::AddFromStorage, test_address0, autoconnect, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), - std::move(ases))); + std::move(ases), std::move(gmap_data))); do_in_main_thread(base::Bind(&LeAudioClient::AddFromStorage, test_address1, autoconnect, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), - std::move(ases))); + std::move(ases), std::move(gmap_data))); }); // Expect stored device0 to connect automatically (first directed connection ) @@ -3763,8 +3769,8 @@ TEST_F(UnicastTestNoInit, ConnectFailedDueToInvalidParameters) { // Initialize BtaAppRegisterCallback app_register_callback; - ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) - .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); + ON_CALL(mock_gatt_interface_, AppRegister(_, _, _, _)) + .WillByDefault(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback))); LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, base::Bind([](MockFunction<void()>* foo) { foo->Call(); }, &mock_storage_load), @@ -3835,16 +3841,17 @@ TEST_F(UnicastTestNoInit, LoadStoredEarbudsBroakenStorage) { std::vector<uint8_t> empty_buf; EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() { - do_in_main_thread(base::BindOnce(&LeAudioClient::AddFromStorage, test_address0, autoconnect, - codec_spec_conf::kLeAudioLocationFrontLeft, - codec_spec_conf::kLeAudioLocationFrontLeft, 0xff, 0xff, - std::move(empty_buf), std::move(empty_buf), - std::move(empty_buf), std::move(empty_buf))); + do_in_main_thread(base::BindOnce( + &LeAudioClient::AddFromStorage, test_address0, autoconnect, + codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, + 0xff, 0xff, std::move(empty_buf), std::move(empty_buf), std::move(empty_buf), + std::move(empty_buf), std::move(empty_buf))); do_in_main_thread(base::BindOnce(&LeAudioClient::AddFromStorage, test_address1, autoconnect, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, 0xff, 0xff, std::move(empty_buf), std::move(empty_buf), - std::move(empty_buf), std::move(empty_buf))); + std::move(empty_buf), std::move(empty_buf), + std::move(empty_buf))); SyncOnMainLoop(); }); @@ -3870,8 +3877,8 @@ TEST_F(UnicastTestNoInit, LoadStoredEarbudsBroakenStorage) { // Initialize BtaAppRegisterCallback app_register_callback; - ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) - .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); + ON_CALL(mock_gatt_interface_, AppRegister(_, _, _, _)) + .WillByDefault(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback))); LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, base::Bind([](MockFunction<void()>* foo) { foo->Call(); }, &mock_storage_load), @@ -3975,17 +3982,20 @@ TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGrouped) { std::vector<uint8_t> snk_pacs; LeAudioClient::GetSinkPacsForStorage(test_address0, snk_pacs); + std::vector<uint8_t> gmap_data; + LeAudioClient::GetGmapForStorage(test_address0, gmap_data); + EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() { do_in_main_thread(base::BindOnce(&LeAudioClient::AddFromStorage, test_address0, autoconnect, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), - std::move(ases))); + std::move(ases), std::move(gmap_data))); do_in_main_thread(base::BindOnce(&LeAudioClient::AddFromStorage, test_address1, autoconnect, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), - std::move(ases))); + std::move(ases), std::move(gmap_data))); SyncOnMainLoop(); }); @@ -4011,8 +4021,8 @@ TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGrouped) { // Initialize BtaAppRegisterCallback app_register_callback; - ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) - .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); + ON_CALL(mock_gatt_interface_, AppRegister(_, _, _, _)) + .WillByDefault(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback))); LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, base::Bind([](MockFunction<void()>* foo) { foo->Call(); }, &mock_storage_load), @@ -4098,6 +4108,9 @@ TEST_F(UnicastTest, LoadStoredBandedHeadphones) { std::vector<uint8_t> snk_pacs; LeAudioClient::GetSinkPacsForStorage(test_address0, snk_pacs); + std::vector<uint8_t> gmap_data; + LeAudioClient::GetGmapForStorage(test_address0, gmap_data); + /* Disconnect & Cleanup */ DisconnectLeAudioWithAclClose(test_address0, conn_id); if (LeAudioClient::IsLeAudioClientRunning()) { @@ -4116,7 +4129,7 @@ TEST_F(UnicastTest, LoadStoredBandedHeadphones) { codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationMonoAudio, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), - std::move(ases))); + std::move(ases), std::move(gmap_data))); SyncOnMainLoop(); }); @@ -4127,8 +4140,8 @@ TEST_F(UnicastTest, LoadStoredBandedHeadphones) { // Re-Initialize & load from storage BtaAppRegisterCallback app_register_callback; - ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) - .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); + ON_CALL(mock_gatt_interface_, AppRegister(_, _, _, _)) + .WillByDefault(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback))); std::vector<::bluetooth::le_audio::btle_audio_codec_config_t> framework_encode_preference; LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, @@ -4221,17 +4234,20 @@ TEST_F(UnicastTestNoInit, ServiceChangedBeforeServiceIsConnected) { std::vector<uint8_t> snk_pacs; LeAudioClient::GetSinkPacsForStorage(test_address0, snk_pacs); + std::vector<uint8_t> gmap_data; + LeAudioClient::GetGmapForStorage(test_address0, gmap_data); + EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() { do_in_main_thread(base::BindOnce(&LeAudioClient::AddFromStorage, test_address0, autoconnect, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), - std::move(ases))); + std::move(ases), std::move(gmap_data))); do_in_main_thread(base::BindOnce(&LeAudioClient::AddFromStorage, test_address1, autoconnect, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), - std::move(ases))); + std::move(ases), std::move(gmap_data))); SyncOnMainLoop(); }); @@ -4257,8 +4273,8 @@ TEST_F(UnicastTestNoInit, ServiceChangedBeforeServiceIsConnected) { // Initialize BtaAppRegisterCallback app_register_callback; - ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) - .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); + ON_CALL(mock_gatt_interface_, AppRegister(_, _, _, _)) + .WillByDefault(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback))); LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, base::Bind([](MockFunction<void()>* foo) { foo->Call(); }, &mock_storage_load), @@ -4347,18 +4363,21 @@ TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGroupedDifferently) { std::vector<uint8_t> snk_pacs; LeAudioClient::GetSinkPacsForStorage(test_address0, snk_pacs); + std::vector<uint8_t> gmap_data; + LeAudioClient::GetGmapForStorage(test_address0, gmap_data); + // Load devices from the storage when storage API is called EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() { do_in_main_thread(base::BindOnce(&LeAudioClient::AddFromStorage, test_address0, autoconnect0, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), - std::move(ases))); + std::move(ases), std::move(gmap_data))); do_in_main_thread(base::BindOnce(&LeAudioClient::AddFromStorage, test_address1, autoconnect1, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), - std::move(ases))); + std::move(ases), std::move(gmap_data))); }); // Expect stored device0 to connect automatically @@ -4388,8 +4407,8 @@ TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGroupedDifferently) { // Initialize BtaAppRegisterCallback app_register_callback; - ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) - .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); + ON_CALL(mock_gatt_interface_, AppRegister(_, _, _, _)) + .WillByDefault(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback))); std::vector<::bluetooth::le_audio::btle_audio_codec_config_t> framework_encode_preference; LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, @@ -4988,6 +5007,155 @@ TEST_F(UnicastTest, GroupSetActive_and_InactiveDuringStreamConfiguration) { Mock::VerifyAndClearExpectations(&mock_state_machine_); } +TEST_F(UnicastTest, AnotherGroupSetActive_DuringMediaStream) { + com::android::bluetooth::flags::provider_->leaudio_improve_switch_during_phone_call(true); + const RawAddress test_address0 = GetTestAddress(0); + const RawAddress test_address1 = GetTestAddress(1); + int group_id_1 = 1; + int group_id_2 = 2; + int group_size = 1; + + default_channel_cnt = 2; + default_src_channel_cnt = 1; + + /** + * Scenario: + * 1. Voip is started + * 2. Group 1 is set active - no fast configuration is expected + * 3. Audio HAL resumse the stream and Group 1 is streaming + * 3. Group 2 is set to active - it is expected that stream to Group 1 is stopped and Group 2 + * configuration goes to QoS configured + */ + // Report working CSIS + ON_CALL(mock_csis_client_module_, IsCsisClientRunning()).WillByDefault(Return(true)); + ON_CALL(mock_csis_client_module_, GetDesiredSize(_)).WillByDefault(Return(group_size)); + + log::info("Connecting first Group with device {}", test_address0); + ConnectCsisDevice( + test_address0, 1 /* conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft | codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id_1, 1 /* rank*/); + + SyncOnMainLoop(); + + log::info("Connecting second Group with device {}", test_address1); + ConnectCsisDevice( + test_address1, 2 /* conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft | codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id_2, 1 /* rank*/); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + + EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(0); + EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _, _)).Times(0); + + log::info("Group is getting Active"); + LeAudioClient::Get()->GroupSetActive(group_id_1); + + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_state_machine_); + + log::info("Audio HAL opens for Media"); + + constexpr int gmcs_ccid = 1; + LeAudioClient::Get()->SetCcidInformation(gmcs_ccid, static_cast<int>(LeAudioContextType::MEDIA)); + + types::BidirectionalPair<std::vector<uint8_t>> ccids = {.sink = {gmcs_ccid}, .source = {}}; + EXPECT_CALL(mock_state_machine_, StartStream(_, types::LeAudioContextType::MEDIA, _, ccids)) + .Times(AtLeast(1)); + + StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id_1); + + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); + + log::info("Set group 2 as active one "); + EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(1); + EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(0); + EXPECT_CALL(mock_state_machine_, + ConfigureStream(_, types::LeAudioContextType::MEDIA, _, _, false)) + .Times(1); + + LeAudioClient::Get()->GroupSetActive(group_id_2); + + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_state_machine_); +} + +TEST_F(UnicastTest, AnotherGroupSetActive_DuringVoip) { + com::android::bluetooth::flags::provider_->leaudio_improve_switch_during_phone_call(true); + const RawAddress test_address0 = GetTestAddress(0); + const RawAddress test_address1 = GetTestAddress(1); + int group_id_1 = 1; + int group_id_2 = 2; + int group_size = 1; + + default_channel_cnt = 2; + default_src_channel_cnt = 1; + + /** + * Scenario: + * 1. Voip is started + * 2. Group 1 is set active - no fast configuration is expected + * 3. Audio HAL resumse the stream and Group 1 is streaming + * 3. Group 2 is set to active - it is expected that stream to Group 1 is stopped and Group 2 + * configuration goes to QoS configured + */ + // Report working CSIS + ON_CALL(mock_csis_client_module_, IsCsisClientRunning()).WillByDefault(Return(true)); + ON_CALL(mock_csis_client_module_, GetDesiredSize(_)).WillByDefault(Return(group_size)); + + log::info("Connecting first Group with device {}", test_address0); + ConnectCsisDevice( + test_address0, 1 /* conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft | codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id_1, 1 /* rank*/); + + SyncOnMainLoop(); + + log::info("Connecting second Group with device {}", test_address1); + ConnectCsisDevice( + test_address1, 2 /* conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft | codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id_2, 1 /* rank*/); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + + EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(0); + EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _, _)).Times(0); + + log::info("Group is getting Active"); + LeAudioClient::Get()->GroupSetActive(group_id_1); + + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_state_machine_); + + log::info("Audio HAL opens for Converstational"); + + // VOIP not using Telecom API has no ccids. + types::BidirectionalPair<std::vector<uint8_t>> ccids = {.sink = {}, .source = {}}; + EXPECT_CALL(mock_state_machine_, + StartStream(_, types::LeAudioContextType::CONVERSATIONAL, _, ccids)) + .Times(AtLeast(1)); + + StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id_1); + + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); + + log::info("Set group 2 as active one "); + EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(1); + EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(0); + EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _, true)).Times(1); + + LeAudioClient::Get()->GroupSetActive(group_id_2); + + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_state_machine_); +} + TEST_F(UnicastTest, GroupSetActive_and_GroupSetInactive_DuringPhoneCall) { com::android::bluetooth::flags::provider_->leaudio_improve_switch_during_phone_call(true); const RawAddress test_address0 = GetTestAddress(0); @@ -6649,11 +6817,18 @@ TEST_F(UnicastTest, SpeakerStreamingAutonomousRelease) { Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); + Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, 1 /* cis_count_out */, 0 /* cis_count_in */, 1920); + EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStatus(group_id, GroupStatus::INACTIVE)) + .Times(1); + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) + .Times(1); + // Inject the IDLE state as if an autonomous release happened ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); @@ -6667,9 +6842,14 @@ TEST_F(UnicastTest, SpeakerStreamingAutonomousRelease) { InjectCisDisconnected(group_id, ase.cis_conn_hdl); } } - // Verify no Data transfer after the autonomous release TestAudioDataTransfer(group_id, 0 /* cis_count_out */, 0 /* cis_count_in */, 1920); + + // Inject Releasing + state_machine_callbacks_->StatusReportCb(group->group_id_, + GroupStreamStatus::RELEASING_AUTONOMOUS); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTest, TwoEarbudsStreaming) { @@ -11953,6 +12133,16 @@ TEST_F(UnicastTest, GroupStreamStatus) { EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) .Times(1); + state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::RELEASING_AUTONOMOUS); + + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnGroupStreamStatus(group_id, GroupStreamStatus::STREAMING)) + .Times(1); + state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::STREAMING); + + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) + .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::SUSPENDING); EXPECT_CALL(mock_audio_hal_client_callbacks_, @@ -12062,7 +12252,7 @@ TEST_F(UnicastTest, GroupStreamStatusManyGroups) { .Times(1); state_machine_callbacks_->StatusReportCb(group_id_2, GroupStreamStatus::IDLE); - // Group 1 active and start streaming + log::info("Group 1 active and start streaming"); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id_1, GroupStreamStatus::STREAMING)) .Times(1); @@ -12072,7 +12262,7 @@ TEST_F(UnicastTest, GroupStreamStatusManyGroups) { SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); - // Group 2 active + log::info("Group 2 is getting active"); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id_1, GroupStreamStatus::IDLE)) .Times(1); @@ -12080,7 +12270,7 @@ TEST_F(UnicastTest, GroupStreamStatusManyGroups) { SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); - // Group 2 start streaming + log::info("Group 2 is starts streaming"); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id_2, GroupStreamStatus::STREAMING)) .Times(1); @@ -12089,6 +12279,106 @@ TEST_F(UnicastTest, GroupStreamStatusManyGroups) { Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } +TEST_F(UnicastTest, GroupStreamStatusManyGroups_GettingConfigWhileOtherGroupIsStreaming) { + uint8_t group_size = 2; + int group_id_1 = 1; + int group_id_2 = 2; + + // Report working CSIS + ON_CALL(mock_csis_client_module_, IsCsisClientRunning()).WillByDefault(Return(true)); + + ON_CALL(mock_csis_client_module_, GetDesiredSize(_)).WillByDefault(Return(group_size)); + + // First group - First earbud + const RawAddress test_address0 = GetTestAddress(0); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)).Times(1); + ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id_1, + 1 /* rank*/); + + // First group - Second earbud + const RawAddress test_address1 = GetTestAddress(1); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)).Times(1); + ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id_1, + 2 /* rank*/, true /*connect_through_csis*/); + + // Second group - First earbud + const RawAddress test_address2 = GetTestAddress(2); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address2, true)).Times(1); + ConnectCsisDevice(test_address2, 3 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id_2, + 1 /* rank*/); + + // Second group - Second earbud + const RawAddress test_address3 = GetTestAddress(3); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address3, true)).Times(1); + ConnectCsisDevice(test_address3, 4 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id_2, + 2 /* rank*/, true /*connect_through_csis*/); + + InSequence s; + + // Group 1 IDLE + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnGroupStreamStatus(group_id_1, GroupStreamStatus::IDLE)) + .Times(1); + state_machine_callbacks_->StatusReportCb(group_id_1, GroupStreamStatus::IDLE); + + // Group 2 IDLE + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnGroupStreamStatus(group_id_2, GroupStreamStatus::IDLE)) + .Times(1); + state_machine_callbacks_->StatusReportCb(group_id_2, GroupStreamStatus::IDLE); + + log::info("Group 1 active and start streaming"); + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnGroupStreamStatus(group_id_1, GroupStreamStatus::STREAMING)) + .Times(1); + LeAudioClient::Get()->GroupSetActive(group_id_1); + SyncOnMainLoop(); + StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id_1); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + + log::info("Group 2 is getting active"); + stay_at_releasing_stop_stream = true; + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnGroupStreamStatus(group_id_1, GroupStreamStatus::IDLE)) + .Times(1); + LeAudioClient::Get()->GroupSetActive(group_id_2); + SyncOnMainLoop(); + + log::info("Group 2 is starts streaming"); + stay_at_qos_config_in_start_stream = true; + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnGroupStreamStatus(group_id_2, GroupStreamStatus::STREAMING)) + .Times(1); + EXPECT_CALL(*mock_le_audio_source_hal_client_, ConfirmStreamingRequest()).Times(1); + + StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id_2, AUDIO_SOURCE_INVALID, + false, false); + + log::info("Group 1 going to IDLE"); + do_in_main_thread(base::BindOnce( + [](int group_id, + bluetooth::le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks) { + state_machine_callbacks->StatusReportCb(group_id, GroupStreamStatus::IDLE); + }, + group_id_1, base::Unretained(this->state_machine_callbacks_))); + SyncOnMainLoop(); + log::info("Group 2 going to STREAMING"); + do_in_main_thread(base::BindOnce( + [](int group_id, + bluetooth::le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks) { + state_machine_callbacks->StatusReportCb(group_id, GroupStreamStatus::STREAMING); + }, + group_id_2, base::Unretained(this->state_machine_callbacks_))); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); +} + TEST_F(UnicastTest, GroupStreamStatusResendAfterRemove) { uint8_t group_size = 2; int group_id = 1; @@ -12229,86 +12519,92 @@ TEST_F(UnicastTestHandoverMode, SetSinkMonitorModeWhileUnicastIsActive) { // Stop StopStreaming(group_id, true); - // Check if cache configuration is still present - ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) - ->confs.get(le_audio::types::kLeAudioDirectionSink) - .size()); - ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) - ->confs.get(le_audio::types::kLeAudioDirectionSource) - .size()); - - // Release, Sink HAL client should remain in monitor mode - EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); - EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); - EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(0); - EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(0); - LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); - SyncOnMainLoop(); + if (com::android::bluetooth::flags::leaudio_use_audio_recording_listener()) { + // simulate suspend timeout passed, alarm executing + fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data); + SyncOnMainLoop(); + } else { + // Check if cache configuration is still present + ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) + ->confs.get(le_audio::types::kLeAudioDirectionSink) + .size()); + ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) + ->confs.get(le_audio::types::kLeAudioDirectionSource) + .size()); + + // Release, Sink HAL client should remain in monitor mode + EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); + EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); + EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(0); + EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(0); + LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); + SyncOnMainLoop(); - Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); - Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); - Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); + Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); - // Re-initialize mock for destroyed hal client - RegisterSourceHalClientMock(); + // Re-initialize mock for destroyed hal client + RegisterSourceHalClientMock(); - // Setting group inactive, shall not change cached configuration - ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) - ->confs.get(le_audio::types::kLeAudioDirectionSink) - .size()); - ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) - ->confs.get(le_audio::types::kLeAudioDirectionSource) - .size()); + // Setting group inactive, shall not change cached configuration + ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) + ->confs.get(le_audio::types::kLeAudioDirectionSink) + .size()); + ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) + ->confs.get(le_audio::types::kLeAudioDirectionSource) + .size()); - EXPECT_CALL(mock_audio_hal_client_callbacks_, - OnUnicastMonitorModeStatus(bluetooth::le_audio::types::kLeAudioDirectionSink, - UnicastMonitorModeStatus::STREAMING_REQUESTED)) - .Times(1); + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnUnicastMonitorModeStatus(bluetooth::le_audio::types::kLeAudioDirectionSink, + UnicastMonitorModeStatus::STREAMING_REQUESTED)) + .Times(1); - // Start streaming to trigger next group going to IDLE state - LocalAudioSinkResume(); + // Start streaming to trigger next group going to IDLE state + LocalAudioSinkResume(); - EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); - EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); - LeAudioClient::Get()->GroupSetActive(group_id); - Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); - SyncOnMainLoop(); + EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); + EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); + LeAudioClient::Get()->GroupSetActive(group_id); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + SyncOnMainLoop(); - Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); + Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); - StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); - SyncOnMainLoop(); - Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); + StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); - // Stop streaming and expect Service to be informed about straming suspension - EXPECT_CALL(mock_audio_hal_client_callbacks_, - OnUnicastMonitorModeStatus(bluetooth::le_audio::types::kLeAudioDirectionSink, - UnicastMonitorModeStatus::STREAMING_SUSPENDED)) - .Times(1); + // Stop streaming and expect Service to be informed about straming suspension + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnUnicastMonitorModeStatus(bluetooth::le_audio::types::kLeAudioDirectionSink, + UnicastMonitorModeStatus::STREAMING_SUSPENDED)) + .Times(1); - // Stop - StopStreaming(group_id, true); + // Stop + StopStreaming(group_id, true); - // Release, Sink HAL client should remain in monitor mode - EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); - EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); - EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(0); - EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(0); - LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); - SyncOnMainLoop(); + // Release, Sink HAL client should remain in monitor mode + EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); + EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); + EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(0); + EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(0); + LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); + SyncOnMainLoop(); - Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); - Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); - Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); + Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); - // De-activate monitoring mode - EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(1); - EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); - do_in_main_thread(base::BindOnce( - &LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), - bluetooth::le_audio::types::kLeAudioDirectionSink, false /* enable */)); - SyncOnMainLoop(); - Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); + // De-activate monitoring mode + EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(1); + EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); + do_in_main_thread(base::BindOnce( + &LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), + bluetooth::le_audio::types::kLeAudioDirectionSink, false /* enable */)); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); + } } TEST_F(UnicastTestHandoverMode, SetSinkMonitorModeWhileUnicastIsInactive) { @@ -12376,33 +12672,39 @@ TEST_F(UnicastTestHandoverMode, SetSinkMonitorModeWhileUnicastIsInactive) { // Stop StopStreaming(group_id, true); - // Check if cache configuration is still present - ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) - ->confs.get(le_audio::types::kLeAudioDirectionSink) - .size()); - ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) - ->confs.get(le_audio::types::kLeAudioDirectionSource) - .size()); - - // Release, Sink HAL client should remain in monitor mode - EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); - EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); - EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(0); - EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(0); - LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); - SyncOnMainLoop(); + if (com::android::bluetooth::flags::leaudio_use_audio_recording_listener()) { + // simulate suspend timeout passed, alarm executing + fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data); + SyncOnMainLoop(); + } else { + // Check if cache configuration is still present + ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) + ->confs.get(le_audio::types::kLeAudioDirectionSink) + .size()); + ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) + ->confs.get(le_audio::types::kLeAudioDirectionSource) + .size()); + + // Release, Sink HAL client should remain in monitor mode + EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); + EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); + EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(0); + EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(0); + LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); + SyncOnMainLoop(); - Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); - Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); - Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); + Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); - // Setting group inactive, shall not change cached configuration - ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) - ->confs.get(le_audio::types::kLeAudioDirectionSink) - .size()); - ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) - ->confs.get(le_audio::types::kLeAudioDirectionSource) - .size()); + // Setting group inactive, shall not change cached configuration + ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) + ->confs.get(le_audio::types::kLeAudioDirectionSink) + .size()); + ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) + ->confs.get(le_audio::types::kLeAudioDirectionSource) + .size()); + } } TEST_F(UnicastTestHandoverMode, ClearSinkMonitorModeWhileUnicastIsActive) { diff --git a/system/bta/le_audio/le_audio_set_configuration_provider_json.cc b/system/bta/le_audio/le_audio_set_configuration_provider_json.cc index 8e22ddb3ff..a76a58bb59 100644 --- a/system/bta/le_audio/le_audio_set_configuration_provider_json.cc +++ b/system/bta/le_audio/le_audio_set_configuration_provider_json.cc @@ -51,14 +51,14 @@ namespace bluetooth::le_audio { #ifdef __ANDROID__ static const std::vector<std::pair<const char* /*schema*/, const char* /*content*/>> - kLeAudioSetConfigs = {{"/apex/com.android.btservices/etc/bluetooth/le_audio/" + kLeAudioSetConfigs = {{"/apex/com.android.bt/etc/bluetooth/le_audio/" "audio_set_configurations.bfbs", - "/apex/com.android.btservices/etc/bluetooth/le_audio/" + "/apex/com.android.bt/etc/bluetooth/le_audio/" "audio_set_configurations.json"}}; static const std::vector<std::pair<const char* /*schema*/, const char* /*content*/>> - kLeAudioSetScenarios = {{"/apex/com.android.btservices/etc/bluetooth/" + kLeAudioSetScenarios = {{"/apex/com.android.bt/etc/bluetooth/" "le_audio/audio_set_scenarios.bfbs", - "/apex/com.android.btservices/etc/bluetooth/" + "/apex/com.android.bt/etc/bluetooth/" "le_audio/audio_set_scenarios.json"}}; #elif defined(TARGET_FLOSS) static const std::vector<std::pair<const char* /*schema*/, const char* /*content*/>> diff --git a/system/bta/le_audio/le_audio_types.cc b/system/bta/le_audio/le_audio_types.cc index b164649c50..55db925be4 100644 --- a/system/bta/le_audio/le_audio_types.cc +++ b/system/bta/le_audio/le_audio_types.cc @@ -773,22 +773,6 @@ std::ostream& operator<<(std::ostream& os, const AudioContexts& contexts) { return os; } -template <typename T> -const T& BidirectionalPair<T>::get(uint8_t direction) const { - log::assert_that(direction < types::kLeAudioDirectionBoth, - "Unsupported complex direction. Consider using " - "get_bidirectional<>() instead."); - return (direction == types::kLeAudioDirectionSink) ? sink : source; -} - -template <typename T> -T& BidirectionalPair<T>::get(uint8_t direction) { - log::assert_that(direction < types::kLeAudioDirectionBoth, - "Unsupported complex direction. Reference to a single " - "complex direction value is not supported."); - return (direction == types::kLeAudioDirectionSink) ? sink : source; -} - /* Bidirectional getter trait for AudioContexts bidirectional pair */ template <> AudioContexts get_bidirectional(BidirectionalPair<AudioContexts> p) { @@ -874,23 +858,5 @@ std::ostream& operator<<(std::ostream& os, const LeAudioMetadata& config) { return os; } -template struct BidirectionalPair<AudioContexts>; -template struct BidirectionalPair<AudioLocations>; -template struct BidirectionalPair<CisType>; -template struct BidirectionalPair<LeAudioConfigurationStrategy>; -template struct BidirectionalPair<ase*>; -template struct BidirectionalPair<std::string>; -template struct BidirectionalPair<std::vector<uint8_t>>; -template struct BidirectionalPair<stream_configuration>; -template struct BidirectionalPair<stream_parameters>; -template struct BidirectionalPair<uint16_t>; -template struct BidirectionalPair<uint8_t>; -template struct BidirectionalPair<bool>; -template struct BidirectionalPair<int>; -template struct BidirectionalPair<std::vector<set_configurations::AseConfiguration>>; -template struct BidirectionalPair<set_configurations::QosConfigSetting>; -template struct BidirectionalPair< - std::unique_ptr<const bluetooth::le_audio::btle_audio_codec_config_t>>; - } // namespace types } // namespace bluetooth::le_audio diff --git a/system/bta/le_audio/le_audio_types.h b/system/bta/le_audio/le_audio_types.h index a0c5f364c7..906996a7c7 100644 --- a/system/bta/le_audio/le_audio_types.h +++ b/system/bta/le_audio/le_audio_types.h @@ -503,8 +503,19 @@ struct BidirectionalPair { T sink; T source; - const T& get(uint8_t direction) const; - T& get(uint8_t direction); + const T& get(uint8_t direction) const { + log::assert_that(direction < types::kLeAudioDirectionBoth, + "Unsupported complex direction. Consider using " + "get_bidirectional<>() instead."); + return (direction == types::kLeAudioDirectionSink) ? sink : source; + } + + T& get(uint8_t direction) { + log::assert_that(direction < types::kLeAudioDirectionBoth, + "Unsupported complex direction. Reference to a single " + "complex direction value is not supported."); + return (direction == types::kLeAudioDirectionSink) ? sink : source; + } }; template <typename T> diff --git a/system/bta/le_audio/le_audio_types_test.cc b/system/bta/le_audio/le_audio_types_test.cc index 70c29426ef..992a35d4c1 100644 --- a/system/bta/le_audio/le_audio_types_test.cc +++ b/system/bta/le_audio/le_audio_types_test.cc @@ -840,5 +840,45 @@ TEST(CodecConfigTest, test_invalid_codec_bits_per_sample) { LE_AUDIO_BITS_PER_SAMPLE_INDEX_NONE); } +TEST(CodecConfigTest, test_tmap_and_gmap_target_latency) { + /* TMAP: Media -> Higher reliability */ + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::MEDIA), + types::kTargetLatencyHigherReliability); + + /* TMAP: Live performance -> Low Latency */ + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::LIVE), + types::kTargetLatencyLower); + + /* TMAP: Call -> Balanced reliability */ + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::RINGTONE), + types::kTargetLatencyBalancedLatencyReliability); + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::CONVERSATIONAL), + types::kTargetLatencyBalancedLatencyReliability); + + /* GMAP: Game -> Low Latency */ + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::GAME), + types::kTargetLatencyLower); + + /* Undefined for the rest of contexts */ + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::UNINITIALIZED), + types::kTargetLatencyUndefined); + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::UNSPECIFIED), + types::kTargetLatencyUndefined); + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::INSTRUCTIONAL), + types::kTargetLatencyUndefined); + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::VOICEASSISTANTS), + types::kTargetLatencyUndefined); + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::SOUNDEFFECTS), + types::kTargetLatencyUndefined); + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::NOTIFICATIONS), + types::kTargetLatencyUndefined); + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::ALERTS), + types::kTargetLatencyUndefined); + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::EMERGENCYALARM), + types::kTargetLatencyUndefined); + ASSERT_EQ(utils::GetTargetLatencyForAudioContext(LeAudioContextType::RFU), + types::kTargetLatencyUndefined); +} + } // namespace types } // namespace bluetooth::le_audio diff --git a/system/bta/le_audio/le_audio_utils.h b/system/bta/le_audio/le_audio_utils.h index f7406a41a5..8feed29473 100644 --- a/system/bta/le_audio/le_audio_utils.h +++ b/system/bta/le_audio/le_audio_utils.h @@ -40,17 +40,19 @@ types::AudioContexts GetAudioContextsFromSinkMetadata( const std::vector<struct record_track_metadata_v7>& sink_metadata); inline uint8_t GetTargetLatencyForAudioContext(types::LeAudioContextType ctx) { switch (ctx) { - case types::LeAudioContextType::GAME: - FALLTHROUGH_INTENDED; - case types::LeAudioContextType::VOICEASSISTANTS: - FALLTHROUGH_INTENDED; + case types::LeAudioContextType::MEDIA: + return types::kTargetLatencyHigherReliability; + case types::LeAudioContextType::LIVE: FALLTHROUGH_INTENDED; - case types::LeAudioContextType::CONVERSATIONAL: - FALLTHROUGH_INTENDED; - case types::LeAudioContextType::RINGTONE: + case types::LeAudioContextType::GAME: return types::kTargetLatencyLower; + case types::LeAudioContextType::RINGTONE: + FALLTHROUGH_INTENDED; + case types::LeAudioContextType::CONVERSATIONAL: + return types::kTargetLatencyBalancedLatencyReliability; + default: return types::kTargetLatencyUndefined; } diff --git a/system/bta/le_audio/mock_codec_manager.cc b/system/bta/le_audio/mock_codec_manager.cc index addf45ba85..f1621e02c0 100644 --- a/system/bta/le_audio/mock_codec_manager.cc +++ b/system/bta/le_audio/mock_codec_manager.cc @@ -41,6 +41,13 @@ types::CodecLocation CodecManager::GetCodecLocation() const { return pimpl_->GetCodecLocation(); } +std::optional<ProviderInfo> CodecManager::GetCodecConfigProviderInfo(void) const { + if (!pimpl_) { + return std::nullopt; + } + return pimpl_->GetCodecConfigProviderInfo(); +} + bool CodecManager::IsDualBiDirSwbSupported(void) const { if (!pimpl_) { return false; diff --git a/system/bta/le_audio/mock_codec_manager.h b/system/bta/le_audio/mock_codec_manager.h index 3203f33257..27db5c399a 100644 --- a/system/bta/le_audio/mock_codec_manager.h +++ b/system/bta/le_audio/mock_codec_manager.h @@ -38,6 +38,8 @@ public: virtual ~MockCodecManager() = default; MOCK_METHOD((bluetooth::le_audio::types::CodecLocation), GetCodecLocation, (), (const)); + MOCK_METHOD(std::optional<bluetooth::le_audio::ProviderInfo>, GetCodecConfigProviderInfo, (), + (const)); MOCK_METHOD((bool), IsDualBiDirSwbSupported, (), (const)); MOCK_METHOD((bool), UpdateActiveUnicastAudioHalClient, diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc index 2fb2963149..cc0a79690c 100644 --- a/system/bta/le_audio/state_machine.cc +++ b/system/bta/le_audio/state_machine.cc @@ -1084,7 +1084,7 @@ public: /* Note, that this type is actually LONG WRITE. * Meaning all the Prepare Writes plus Execute is handled in the stack */ - write_type = GATT_WRITE_PREPARE; + write_type = GATT_WRITE; } BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, @@ -1744,7 +1744,7 @@ private: } void SetAseState(LeAudioDevice* leAudioDevice, struct ase* ase, AseState state) { - log::info("{}, ase_id: {}, {} -> {}", leAudioDevice->address_, ase->id, ToString(ase->state), + log::info("{} ({}), ase_id: {}, {} -> {}", leAudioDevice->address_, leAudioDevice->group_id_, ase->id, ToString(ase->state), ToString(state)); log_history_->AddLogHistory(kLogStateMachineTag, leAudioDevice->group_id_, @@ -3045,7 +3045,7 @@ private: log::info("Group {} is doing autonomous release", group->group_id_); SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); state_machine_callbacks_->StatusReportCb(group->group_id_, - GroupStreamStatus::RELEASING); + GroupStreamStatus::RELEASING_AUTONOMOUS); } } diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc index 7fc2985b28..682bb9c722 100644 --- a/system/bta/le_audio/state_machine_test.cc +++ b/system/bta/le_audio/state_machine_test.cc @@ -4147,9 +4147,13 @@ TEST_F(StateMachineTest, testAutonomousReleaseMultiple) { // Validate GroupStreamStatus EXPECT_CALL(mock_callbacks_, - StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING)) + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::RELEASING_AUTONOMOUS)) .Times(1); EXPECT_CALL(mock_callbacks_, + StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING)) + .Times(0); + EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::IDLE)) .Times(1); EXPECT_CALL(mock_callbacks_, diff --git a/system/bta/le_audio/storage_helper.cc b/system/bta/le_audio/storage_helper.cc index 159580d456..d48ec25363 100644 --- a/system/bta/le_audio/storage_helper.cc +++ b/system/bta/le_audio/storage_helper.cc @@ -437,4 +437,94 @@ bool DeserializeHandles(LeAudioDevice* leAudioDevice, const std::vector<uint8_t> leAudioDevice->known_service_handles_ = true; return true; } + +static constexpr uint8_t LEAUDIO_GMAP_STORAGE_V1_LAYOUT_MAGIC = 0x01; +static constexpr uint8_t LEAUDIO_GMAP_STORAGE_V1_LAYOUT_SZ = 7; +static constexpr uint8_t LEAUDIO_GMAP_STORAGE_CURRENT_LAYOUT_MAGIC = + LEAUDIO_GMAP_STORAGE_V1_LAYOUT_MAGIC; + +static bool SerializeGmapV1(const GmapClient* gmapClient, std::vector<uint8_t>& out) { + if (gmapClient == nullptr) { + log::warn("GMAP client not available"); + return false; + } + + /* The total size */ + out.resize(LEAUDIO_GMAP_STORAGE_V1_LAYOUT_SZ); + auto* ptr = out.data(); + + /* header */ + UINT8_TO_STREAM(ptr, LEAUDIO_GMAP_STORAGE_V1_LAYOUT_MAGIC); + + /* handles */ + UINT16_TO_STREAM(ptr, gmapClient->getRoleHandle()); + UINT16_TO_STREAM(ptr, gmapClient->getUGTFeatureHandle()); + + /* role & features */ + UINT8_TO_STREAM(ptr, gmapClient->getRole().to_ulong()); + UINT8_TO_STREAM(ptr, gmapClient->getUGTFeature().to_ulong()); + + return true; +} + +bool SerializeGmap(const GmapClient* gmapClient, std::vector<uint8_t>& out) { + if (gmapClient == nullptr) { + log::warn("GMAP client not available"); + return false; + } + + if (LEAUDIO_GMAP_STORAGE_CURRENT_LAYOUT_MAGIC == LEAUDIO_GMAP_STORAGE_V1_LAYOUT_MAGIC) { + return SerializeGmapV1(gmapClient, out); + } + + log::warn("Invalid GMAP storage magic number {}", +LEAUDIO_GMAP_STORAGE_CURRENT_LAYOUT_MAGIC); +} + +bool DeserializeGmapV1(GmapClient* gmapClient, const std::vector<uint8_t>& in) { + if (in.size() != LEAUDIO_GMAP_STORAGE_V1_LAYOUT_SZ) { + log::warn("Invalid storage size for GMAP data. Got {}, expected {}", in.size(), + +LEAUDIO_GMAP_STORAGE_V1_LAYOUT_SZ); + return false; + } + + // Skip the magic number + auto* ptr = in.data() + 1; + + /* handles */ + uint16_t role_handle, ugt_feature_handle; + STREAM_TO_UINT16(role_handle, ptr); + STREAM_TO_UINT16(ugt_feature_handle, ptr); + + uint8_t role, ugt_feature; + STREAM_TO_UINT8(role, ptr); + STREAM_TO_UINT8(ugt_feature, ptr); + + gmapClient->AddFromStorage(role, role_handle, ugt_feature, ugt_feature_handle); + return true; +} + +bool DeserializeGmap(GmapClient* gmapClient, const std::vector<uint8_t>& in) { + if (gmapClient == nullptr) { + log::warn("GMAP client not available"); + return false; + } + + if (in.size() < 1) { + log::warn("GMAP storage is not available"); + return false; + } + + auto* ptr = in.data(); + uint8_t magic; + STREAM_TO_UINT8(magic, ptr); + + if (magic == LEAUDIO_GMAP_STORAGE_V1_LAYOUT_MAGIC) { + return DeserializeGmapV1(gmapClient, in); + } + + log::warn("Invalid GMAP storage magic number. Got {}, current {}", +magic, + +LEAUDIO_GMAP_STORAGE_CURRENT_LAYOUT_MAGIC); + return false; +} + } // namespace bluetooth::le_audio diff --git a/system/bta/le_audio/storage_helper.h b/system/bta/le_audio/storage_helper.h index 85418457c9..78868fa744 100644 --- a/system/bta/le_audio/storage_helper.h +++ b/system/bta/le_audio/storage_helper.h @@ -21,6 +21,7 @@ #include <vector> #include "devices.h" +#include "le_audio/gmap_client.h" namespace bluetooth::le_audio { bool SerializeSinkPacs(const LeAudioDevice* leAudioDevice, std::vector<uint8_t>& out); @@ -31,4 +32,6 @@ bool SerializeAses(const LeAudioDevice* leAudioDevice, std::vector<uint8_t>& out bool DeserializeAses(LeAudioDevice* leAudioDevice, const std::vector<uint8_t>& in); bool SerializeHandles(const LeAudioDevice* leAudioDevice, std::vector<uint8_t>& out); bool DeserializeHandles(LeAudioDevice* leAudioDevice, const std::vector<uint8_t>& in); +bool SerializeGmap(const GmapClient* gmap_server, std::vector<uint8_t>& out); +bool DeserializeGmap(GmapClient* gmap_server, const std::vector<uint8_t>& in); } // namespace bluetooth::le_audio diff --git a/system/bta/le_audio/storage_helper_test.cc b/system/bta/le_audio/storage_helper_test.cc index ef0a9ed172..9effb2b47f 100644 --- a/system/bta/le_audio/storage_helper_test.cc +++ b/system/bta/le_audio/storage_helper_test.cc @@ -309,4 +309,41 @@ TEST(StorageHelperTest, DeserializeHandles) { ASSERT_FALSE(DeserializeHandles(&leAudioDevice, invalidHandlesMagic)); ASSERT_FALSE(DeserializeHandles(&leAudioDevice, invalidHandles)); } + +TEST(StorageHelperTest, DeserializeGmapV1) { + // clang-format off + const std::vector<uint8_t> validHandles { + 0x01, // V1 Layout Magic + 0x0e, 0x11, // Role Handle + 0x0f, 0x11, // Feature Handle + 0x05, // Role value + 0x06, // Feature value + }; + const std::vector<uint8_t> invalidHandlesMagic { + 0x00, // Unknown Layout Magic + 0x0e, 0x11, // Role Handle + 0x0f, 0x11, // Feature Handle + 0x05, // Role value + 0x06, // Feature value + }; + const std::vector<uint8_t> invalidHandles { + 0x01, // V1 Layout Magic + 0x0e, 0x11, // Role Handle + 0x0f, 0x11, // Feature Handle + 0x05, // Role value + 0x06, // Feature value + 0x06, // corrupted + }; + + // clang-format on + RawAddress test_address0 = GetTestAddress(0); + GmapClient gmap(test_address0); + ASSERT_TRUE(DeserializeGmap(&gmap, validHandles)); + std::vector<uint8_t> serialize; + ASSERT_TRUE(SerializeGmap(&gmap, serialize)); + ASSERT_TRUE(serialize == validHandles); + + ASSERT_FALSE(DeserializeGmap(&gmap, invalidHandlesMagic)); + ASSERT_FALSE(DeserializeGmap(&gmap, invalidHandles)); +} } // namespace bluetooth::le_audio diff --git a/system/bta/ras/ras_client.cc b/system/bta/ras/ras_client.cc index 6dcf00c377..b052717d14 100644 --- a/system/bta/ras/ras_client.cc +++ b/system/bta/ras/ras_client.cc @@ -51,6 +51,7 @@ using namespace bluetooth; using namespace ::ras; using namespace ::ras::feature; using namespace ::ras::uuid; +using bluetooth::ras::RasDisconnectReason; using bluetooth::ras::VendorSpecificCharacteristic; namespace { @@ -155,6 +156,7 @@ public: return; } BTA_GATTC_AppRegister( + "ranging_service", [](tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { if (instance && p_data) { instance->GattcCallback(event, p_data); @@ -286,6 +288,8 @@ public: if (evt.status != GATT_SUCCESS) { log::error("Failed to connect to server device {}", evt.remote_bda); + callbacks_->OnDisconnected(tracker->address_for_cs_, + RasDisconnectReason::SERVER_NOT_AVAILABLE); return; } tracker->conn_id_ = evt.conn_id; @@ -307,7 +311,7 @@ public: BTA_GATTC_Close(evt.conn_id); return; } - callbacks_->OnDisconnected(tracker->address_for_cs_); + callbacks_->OnDisconnected(tracker->address_for_cs_, RasDisconnectReason::GATT_DISCONNECT); trackers_.remove(tracker); } @@ -337,6 +341,8 @@ public: return; } else if (!service_found) { log::error("Can't find Ranging Service in the services list"); + callbacks_->OnDisconnected(tracker->address_for_cs_, + RasDisconnectReason::SERVER_NOT_AVAILABLE); return; } else { log::info("Found Ranging Service"); @@ -346,7 +352,10 @@ public: if (UseCachedData(tracker)) { log::info("Use cached data for Ras features and vendor specific characteristic"); - SubscribeCharacteristic(tracker, kRasControlPointCharacteristic); + if (!SubscribeCharacteristic(tracker, kRasControlPointCharacteristic)) { + callbacks_->OnDisconnected(tracker->address_for_cs_, + RasDisconnectReason::SERVER_NOT_AVAILABLE); + } AllCharacteristicsReadComplete(tracker); } else { // Read Vendor Specific Uuid @@ -369,6 +378,8 @@ public: auto characteristic = tracker->FindCharacteristicByUuid(kRasFeaturesCharacteristic); if (characteristic == nullptr) { log::error("Can not find Characteristic for Ras Features"); + callbacks_->OnDisconnected(tracker->address_for_cs_, + RasDisconnectReason::SERVER_NOT_AVAILABLE); return; } BTA_GATTC_ReadCharacteristic( @@ -379,7 +390,9 @@ public: }, &gatt_read_callback_data_); - SubscribeCharacteristic(tracker, kRasControlPointCharacteristic); + if (!SubscribeCharacteristic(tracker, kRasControlPointCharacteristic)) { + callbacks_->OnDisconnected(tracker->address_for_cs_, RasDisconnectReason::FATAL_ERROR); + } } } @@ -627,23 +640,23 @@ public: } } - void SubscribeCharacteristic(std::shared_ptr<RasTracker> tracker, const Uuid uuid) { + bool SubscribeCharacteristic(std::shared_ptr<RasTracker> tracker, const Uuid uuid) { auto characteristic = tracker->FindCharacteristicByUuid(uuid); if (characteristic == nullptr) { log::warn("Can't find characteristic 0x{:04x}", uuid.As16Bit()); - return; + return false; } uint16_t ccc_handle = FindCccHandle(characteristic); if (ccc_handle == GAP_INVALID_HANDLE) { log::warn("Can't find Client Characteristic Configuration descriptor"); - return; + return false; } tGATT_STATUS register_status = BTA_GATTC_RegisterForNotifications(gatt_if_, tracker->address_, characteristic->value_handle); if (register_status != GATT_SUCCESS) { log::error("Fail to register, {}", gatt_status_text(register_status)); - return; + return false; } std::vector<uint8_t> value(2); @@ -663,6 +676,7 @@ public: } }, nullptr); + return true; } void UnsubscribeCharacteristic(std::shared_ptr<RasTracker> tracker, const Uuid uuid) { @@ -789,14 +803,22 @@ public: if (tracker->remote_supported_features_ & feature::kRealTimeRangingData) { log::info("Subscribe Real-time Ranging Data"); tracker->ranging_type_ = REAL_TIME; - SubscribeCharacteristic(tracker, kRasRealTimeRangingDataCharacteristic); + if (!SubscribeCharacteristic(tracker, kRasRealTimeRangingDataCharacteristic)) { + callbacks_->OnDisconnected(tracker->address_for_cs_, + RasDisconnectReason::SERVER_NOT_AVAILABLE); + return; + } SetTimeOutAlarm(tracker, kFirstSegmentRangingDataTimeoutMs, TimeoutType::FIRST_SEGMENT); } else { log::info("Subscribe On-demand Ranging Data"); tracker->ranging_type_ = ON_DEMAND; - SubscribeCharacteristic(tracker, kRasOnDemandDataCharacteristic); - SubscribeCharacteristic(tracker, kRasRangingDataReadyCharacteristic); - SubscribeCharacteristic(tracker, kRasRangingDataOverWrittenCharacteristic); + if (!SubscribeCharacteristic(tracker, kRasOnDemandDataCharacteristic) || + !SubscribeCharacteristic(tracker, kRasRangingDataReadyCharacteristic) || + !SubscribeCharacteristic(tracker, kRasRangingDataOverWrittenCharacteristic)) { + callbacks_->OnDisconnected(tracker->address_for_cs_, + RasDisconnectReason::SERVER_NOT_AVAILABLE); + return; + } SetTimeOutAlarm(tracker, kRangingDataReadyTimeoutMs, TimeoutType::RANGING_DATA_READY); } auto characteristic = tracker->FindCharacteristicByUuid(kRasRealTimeRangingDataCharacteristic); diff --git a/system/bta/ras/ras_utils_test.cc b/system/bta/ras/ras_utils_test.cc new file mode 100644 index 0000000000..7acc658265 --- /dev/null +++ b/system/bta/ras/ras_utils_test.cc @@ -0,0 +1,179 @@ +/* + * Copyright 2025 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 <gtest/gtest.h> + +#include "bta/include/bta_ras_api.h" +#include "bta/ras/ras_types.h" + +class RasUtilsTest : public ::testing::Test {}; + +TEST(RasUtilsTest, GetUuidName) { + // Test known UUIDs + EXPECT_EQ(ras::uuid::getUuidName(bluetooth::Uuid::From16Bit(ras::uuid::kRangingService16Bit)), + "Ranging Service"); + EXPECT_EQ(ras::uuid::getUuidName( + bluetooth::Uuid::From16Bit(ras::uuid::kRasFeaturesCharacteristic16bit)), + "RAS Features"); + EXPECT_EQ(ras::uuid::getUuidName(bluetooth::Uuid::From16Bit( + ras::uuid::kRasRealTimeRangingDataCharacteristic16bit)), + "Real-time Ranging Data"); + EXPECT_EQ(ras::uuid::getUuidName( + bluetooth::Uuid::From16Bit(ras::uuid::kRasOnDemandDataCharacteristic16bit)), + "On-demand Ranging Data"); + EXPECT_EQ(ras::uuid::getUuidName( + bluetooth::Uuid::From16Bit(ras::uuid::kRasControlPointCharacteristic16bit)), + "RAS Control Point (RAS-CP)"); + EXPECT_EQ(ras::uuid::getUuidName( + bluetooth::Uuid::From16Bit(ras::uuid::kRasRangingDataReadyCharacteristic16bit)), + "Ranging Data Ready"); + EXPECT_EQ(ras::uuid::getUuidName(bluetooth::Uuid::From16Bit( + ras::uuid::kRasRangingDataOverWrittenCharacteristic16bit)), + "Ranging Data Overwritten"); + EXPECT_EQ(ras::uuid::getUuidName( + bluetooth::Uuid::From16Bit(ras::uuid::kClientCharacteristicConfiguration16bit)), + "Client Characteristic Configuration"); + + // Test unknown UUID + EXPECT_EQ(ras::uuid::getUuidName( + bluetooth::Uuid::FromString("00001101-0000-1000-8000-00805F9B34FB")), + "Unknown UUID"); +} + +TEST(RasUtilsTest, ParseControlPointCommand) { + // Test successful parsing of valid commands + uint8_t valid_data_get_ranging_data[] = {0x00, 0x01, 0x02}; + ras::ControlPointCommand command_get_ranging_data; + ASSERT_TRUE(ras::ParseControlPointCommand(&command_get_ranging_data, valid_data_get_ranging_data, + sizeof(valid_data_get_ranging_data))); + ASSERT_EQ(command_get_ranging_data.opcode_, ras::Opcode::GET_RANGING_DATA); + ASSERT_EQ(command_get_ranging_data.parameter_[0], 0x01); + ASSERT_EQ(command_get_ranging_data.parameter_[1], 0x02); + + uint8_t valid_data_ack_ranging_data[] = {0x01, 0x03, 0x04}; + ras::ControlPointCommand command_ack_ranging_data; + ASSERT_TRUE(ras::ParseControlPointCommand(&command_ack_ranging_data, valid_data_ack_ranging_data, + sizeof(valid_data_ack_ranging_data))); + ASSERT_EQ(command_ack_ranging_data.opcode_, ras::Opcode::ACK_RANGING_DATA); + ASSERT_EQ(command_ack_ranging_data.parameter_[0], 0x03); + ASSERT_EQ(command_ack_ranging_data.parameter_[1], 0x04); + + uint8_t valid_data_retrieve_lost_ranging_data_segments[] = {0x02, 0x05, 0x06, 0x07, 0x08}; + ras::ControlPointCommand command_retrieve_lost_ranging_data_segments; + ASSERT_TRUE( + ras::ParseControlPointCommand(&command_retrieve_lost_ranging_data_segments, + valid_data_retrieve_lost_ranging_data_segments, + sizeof(valid_data_retrieve_lost_ranging_data_segments))); + ASSERT_EQ(command_retrieve_lost_ranging_data_segments.opcode_, + ras::Opcode::RETRIEVE_LOST_RANGING_DATA_SEGMENTS); + ASSERT_EQ(command_retrieve_lost_ranging_data_segments.parameter_[0], 0x05); + ASSERT_EQ(command_retrieve_lost_ranging_data_segments.parameter_[1], 0x06); + ASSERT_EQ(command_retrieve_lost_ranging_data_segments.parameter_[2], 0x07); + ASSERT_EQ(command_retrieve_lost_ranging_data_segments.parameter_[3], 0x08); + + uint8_t valid_data_abort_operation[] = {0x03}; + ras::ControlPointCommand command_abort_operation; + ASSERT_TRUE(ras::ParseControlPointCommand(&command_abort_operation, valid_data_abort_operation, + sizeof(valid_data_abort_operation))); + ASSERT_EQ(command_abort_operation.opcode_, ras::Opcode::ABORT_OPERATION); + + uint8_t valid_data_filter[] = {0x04, 0x09, 0x0A}; + ras::ControlPointCommand command_filter; + ASSERT_TRUE(ras::ParseControlPointCommand(&command_filter, valid_data_filter, + sizeof(valid_data_filter))); + ASSERT_EQ(command_filter.opcode_, ras::Opcode::FILTER); + ASSERT_EQ(command_filter.parameter_[0], 0x09); + ASSERT_EQ(command_filter.parameter_[1], 0x0A); + + // Test failed parsing of invalid commands + uint8_t invalid_data_short_get_ranging_data[] = {0x00, 0x01}; + ras::ControlPointCommand command_invalid_short_get_ranging_data; + ASSERT_FALSE(ras::ParseControlPointCommand(&command_invalid_short_get_ranging_data, + invalid_data_short_get_ranging_data, + sizeof(invalid_data_short_get_ranging_data))); + + uint8_t invalid_data_long_get_ranging_data[] = {0x00, 0x01, 0x02, 0x03}; + ras::ControlPointCommand command_invalid_long_get_ranging_data; + ASSERT_FALSE(ras::ParseControlPointCommand(&command_invalid_long_get_ranging_data, + invalid_data_long_get_ranging_data, + sizeof(invalid_data_long_get_ranging_data))); + + uint8_t invalid_data_unknown_opcode[] = {0x05, 0x01, 0x02}; + ras::ControlPointCommand command_invalid_unknown_opcode; + ASSERT_FALSE(ras::ParseControlPointCommand(&command_invalid_unknown_opcode, + invalid_data_unknown_opcode, + sizeof(invalid_data_unknown_opcode))); +} + +TEST(RasUtilsTest, GetOpcodeText) { + // Test known opcodes + EXPECT_EQ(ras::GetOpcodeText(ras::Opcode::GET_RANGING_DATA), "GET_RANGING_DATA"); + EXPECT_EQ(ras::GetOpcodeText(ras::Opcode::ACK_RANGING_DATA), "ACK_RANGING_DATA"); + EXPECT_EQ(ras::GetOpcodeText(ras::Opcode::RETRIEVE_LOST_RANGING_DATA_SEGMENTS), + "RETRIEVE_LOST_RANGING_DATA_SEGMENTS"); + EXPECT_EQ(ras::GetOpcodeText(ras::Opcode::ABORT_OPERATION), "ABORT_OPERATION"); + EXPECT_EQ(ras::GetOpcodeText(ras::Opcode::FILTER), "FILTER"); + + // Test unknown opcode (casting an invalid value to Opcode) + EXPECT_EQ(ras::GetOpcodeText(static_cast<ras::Opcode>(0x05)), "Unknown Opcode"); +} + +TEST(RasUtilsTest, GetResponseOpcodeValueText) { + // Test known response code values + EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::RESERVED_FOR_FUTURE_USE), + "RESERVED_FOR_FUTURE_USE"); + EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::SUCCESS), "SUCCESS"); + EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::OP_CODE_NOT_SUPPORTED), + "OP_CODE_NOT_SUPPORTED"); + EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::INVALID_PARAMETER), + "INVALID_PARAMETER"); + EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::PERSISTED), "PERSISTED"); + EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::ABORT_UNSUCCESSFUL), + "ABORT_UNSUCCESSFUL"); + EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::PROCEDURE_NOT_COMPLETED), + "PROCEDURE_NOT_COMPLETED"); + EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::SERVER_BUSY), "SERVER_BUSY"); + EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::NO_RECORDS_FOUND), + "NO_RECORDS_FOUND"); + + // Test unknown response code value (casting an invalid value to ResponseCodeValue) + EXPECT_EQ(ras::GetResponseOpcodeValueText(static_cast<ras::ResponseCodeValue>(0x09)), + "Reserved for Future Use"); +} + +TEST(RasUtilsTest, IsRangingServiceCharacteristic) { + // Test true cases for Ranging Service characteristics + EXPECT_TRUE(ras::IsRangingServiceCharacteristic( + bluetooth::Uuid::From16Bit(ras::uuid::kRangingService16Bit))); + EXPECT_TRUE(ras::IsRangingServiceCharacteristic( + bluetooth::Uuid::From16Bit(ras::uuid::kRasFeaturesCharacteristic16bit))); + EXPECT_TRUE(ras::IsRangingServiceCharacteristic( + bluetooth::Uuid::From16Bit(ras::uuid::kRasRealTimeRangingDataCharacteristic16bit))); + EXPECT_TRUE(ras::IsRangingServiceCharacteristic( + bluetooth::Uuid::From16Bit(ras::uuid::kRasOnDemandDataCharacteristic16bit))); + EXPECT_TRUE(ras::IsRangingServiceCharacteristic( + bluetooth::Uuid::From16Bit(ras::uuid::kRasControlPointCharacteristic16bit))); + EXPECT_TRUE(ras::IsRangingServiceCharacteristic( + bluetooth::Uuid::From16Bit(ras::uuid::kRasRangingDataReadyCharacteristic16bit))); + EXPECT_TRUE(ras::IsRangingServiceCharacteristic( + bluetooth::Uuid::From16Bit(ras::uuid::kRasRangingDataOverWrittenCharacteristic16bit))); + + // Test false cases for non-Ranging Service characteristics + EXPECT_FALSE(ras::IsRangingServiceCharacteristic( + bluetooth::Uuid::From16Bit(ras::uuid::kClientCharacteristicConfiguration16bit))); + EXPECT_FALSE(ras::IsRangingServiceCharacteristic( + bluetooth::Uuid::FromString("00001101-0000-1000-8000-00805F9B34FB"))); // Random UUID +} diff --git a/system/bta/test/bta_disc_test.cc b/system/bta/test/bta_disc_test.cc index 72e446c122..3a1dbc9882 100644 --- a/system/bta/test/bta_disc_test.cc +++ b/system/bta/test/bta_disc_test.cc @@ -44,28 +44,6 @@ namespace { const RawAddress kRawAddress({0x11, 0x22, 0x33, 0x44, 0x55, 0x66}); } -// Test hooks -namespace bluetooth { -namespace legacy { -namespace testing { - -void bta_dm_disc_init_search_cb(tBTA_DM_SEARCH_CB& bta_dm_search_cb); -bool bta_dm_read_remote_device_name(const RawAddress& bd_addr, tBT_TRANSPORT transport); -tBTA_DM_SEARCH_CB& bta_dm_disc_search_cb(); -void bta_dm_discover_next_device(); -void bta_dm_sdp_find_services(tBTA_DM_SDP_STATE* state); -void bta_dm_inq_cmpl(); -void bta_dm_inq_cmpl_cb(void* p_result); -void bta_dm_observe_cmpl_cb(void* p_result); -void bta_dm_observe_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir, uint16_t eir_len); -void bta_dm_opportunistic_observe_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir, - uint16_t eir_len); -void bta_dm_queue_search(tBTA_DM_API_SEARCH& search); -void bta_dm_start_scan(uint8_t duration_sec); -} // namespace testing -} // namespace legacy -} // namespace bluetooth - class BtaInitializedTest : public BtaWithContextTest { protected: void SetUp() override { @@ -101,7 +79,9 @@ TEST_F(BtaInitializedTest, bta_dm_ble_scan) { bta_dm_ble_scan(kStopLeScan, duration_in_seconds); } -TEST_F(BtaInitializedTest, bta_dm_disc_discover_next_device) { bta_dm_disc_discover_next_device(); } +TEST_F(BtaInitializedTest, bta_dm_disc_discover_next_device) { + bluetooth::legacy::testing::bta_dm_discover_next_device(); +} TEST_F(BtaInitializedTest, bta_dm_disc_remove_device) { bta_dm_disc_remove_device(kRawAddress); } @@ -116,7 +96,7 @@ TEST_F(BtaInitializedTest, bta_dm_sdp_find_services) { .services_found = 0, .service_index = 0, }); - bluetooth::legacy::testing::bta_dm_sdp_find_services(state.get()); + bta_dm_sdp_find_services(state.get()); } TEST_F(BtaInitializedTest, bta_dm_inq_cmpl) { bluetooth::legacy::testing::bta_dm_inq_cmpl(); } @@ -239,61 +219,7 @@ int gatt_service_cb_both_call_cnt = 0; /* This test exercises the usual service discovery flow when bonding to * dual-mode, CTKD capable device on LE transport. */ -TEST_F_WITH_FLAGS(BtaInitializedTest, bta_dm_disc_both_transports_flag_disabled, - REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(TEST_BT, bta_dm_discover_both))) { - bta_dm_disc_start(true); - - std::promise<void> gatt_triggered; - int gatt_call_cnt = 0; - base::RepeatingCallback<void(const RawAddress&)> gatt_performer = - base::BindLambdaForTesting([&](const RawAddress& /*bd_addr*/) { - gatt_call_cnt++; - gatt_triggered.set_value(); - }); - bta_dm_disc_override_gatt_performer_for_testing(gatt_performer); - - int sdp_call_cnt = 0; - base::RepeatingCallback<void(tBTA_DM_SDP_STATE*)> sdp_performer = - base::BindLambdaForTesting([&](tBTA_DM_SDP_STATE* /*sdp_state*/) { sdp_call_cnt++; }); - bta_dm_disc_override_sdp_performer_for_testing(sdp_performer); - - gatt_service_cb_both_call_cnt = 0; - service_cb_both_call_cnt = 0; - - bta_dm_disc_start_service_discovery( - {[](RawAddress, std::vector<bluetooth::Uuid>&, bool) {}, nullptr, - [](RawAddress /*addr*/, const std::vector<bluetooth::Uuid>&, tBTA_STATUS) { - service_cb_both_call_cnt++; - }}, - kRawAddress, BT_TRANSPORT_BR_EDR); - EXPECT_EQ(sdp_call_cnt, 1); - - bta_dm_disc_start_service_discovery( - {[](RawAddress, std::vector<bluetooth::Uuid>&, bool) { gatt_service_cb_both_call_cnt++; }, - nullptr, [](RawAddress /*addr*/, const std::vector<bluetooth::Uuid>&, tBTA_STATUS) {}}, - kRawAddress, BT_TRANSPORT_LE); - - // GATT discovery is queued, until SDP finishes - EXPECT_EQ(gatt_call_cnt, 0); - - bta_dm_sdp_finished(kRawAddress, BTA_SUCCESS, {}, {}); - EXPECT_EQ(service_cb_both_call_cnt, 1); - - // SDP finished, wait until GATT is triggered. - EXPECT_EQ(std::future_status::ready, - gatt_triggered.get_future().wait_for(std::chrono::seconds(1))); - bta_dm_gatt_finished(kRawAddress, BTA_SUCCESS); - EXPECT_EQ(gatt_service_cb_both_call_cnt, 1); - - bta_dm_disc_override_sdp_performer_for_testing({}); - bta_dm_disc_override_gatt_performer_for_testing({}); -} - -/* This test exercises the usual service discovery flow when bonding to - * dual-mode, CTKD capable device on LE transport. - */ -TEST_F_WITH_FLAGS(BtaInitializedTest, bta_dm_disc_both_transports_flag_enabled, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, bta_dm_discover_both))) { +TEST_F(BtaInitializedTest, bta_dm_disc_both_transports) { bta_dm_disc_start(true); int gatt_call_cnt = 0; diff --git a/system/bta/test/bta_dm_test.cc b/system/bta/test/bta_dm_test.cc index 91a8390172..90d41d5e6f 100644 --- a/system/bta/test/bta_dm_test.cc +++ b/system/bta/test/bta_dm_test.cc @@ -25,8 +25,10 @@ #include <format> #include <string> +#include "bta/dm/bta_dm_device_search.h" #include "bta/dm/bta_dm_device_search_int.h" #include "bta/dm/bta_dm_disc.h" +#include "bta/dm/bta_dm_disc_int.h" #include "bta/dm/bta_dm_int.h" #include "bta/dm/bta_dm_pm.cc" #include "bta/dm/bta_dm_sec_int.h" @@ -59,15 +61,6 @@ constexpr char kRemoteName[] = "TheRemoteName"; } // namespace -namespace bluetooth::legacy::testing { - -tBTA_DM_SEARCH_CB& bta_dm_disc_search_cb(); -void bta_dm_deinit_cb(); -void bta_dm_init_cb(); -void bta_dm_remote_name_cmpl(const tBTA_DM_REMOTE_NAME& remote_name_msg); - -} // namespace bluetooth::legacy::testing - class BtaDmTest : public BtaWithContextTest { protected: void SetUp() override { @@ -193,23 +186,6 @@ void BTA_DM_ENCRYPT_CBACK(const RawAddress& bd_addr, tBT_TRANSPORT transport, tB } // namespace -namespace bluetooth { -namespace legacy { -namespace testing { -tBTA_DM_PEER_DEVICE* allocate_device_for(const RawAddress& bd_addr, tBT_TRANSPORT transport); - -void bta_dm_remname_cback(const tBTM_REMOTE_DEV_NAME* p); - -tBT_TRANSPORT bta_dm_determine_discovery_transport(const RawAddress& remote_bd_addr); - -tBTM_STATUS bta_dm_sp_cback(tBTM_SP_EVT event, tBTM_SP_EVT_DATA* p_data); - -void BTA_dm_on_hw_on(); - -} // namespace testing -} // namespace legacy -} // namespace bluetooth - TEST_F(BtaDmTest, bta_dm_set_encryption) { const tBT_TRANSPORT transport{BT_TRANSPORT_LE}; const tBTM_BLE_SEC_ACT sec_act{BTM_BLE_SEC_NONE}; @@ -268,9 +244,6 @@ TEST_F(BtaDmTest, bta_dm_set_encryption) { BTA_DM_ENCRYPT_CBACK_queue = {}; } -void bta_dm_encrypt_cback(RawAddress bd_addr, tBT_TRANSPORT transport, void* /* p_ref_data */, - tBTM_STATUS result); - TEST_F(BtaDmTest, bta_dm_encrypt_cback) { const tBT_TRANSPORT transport{BT_TRANSPORT_LE}; @@ -481,13 +454,13 @@ TEST_F(BtaDmCustomAlarmTest, sniff_offload_feature__test_sysprop) { // Expect not to trigger bta_dm_init_pm due to sysprop enabled // and reset the value of .srvc_id. is_property_enabled = true; - bluetooth::legacy::testing::BTA_dm_on_hw_on(); + BTA_dm_on_hw_on(); ASSERT_EQ(0, bta_dm_cb.pm_timer[0].srvc_id[0]); // Expect to trigger bta_dm_init_pm and init the value of .srvc_id to // BTA_ID_MAX due to sysprop disabled. is_property_enabled = false; - bluetooth::legacy::testing::BTA_dm_on_hw_on(); + BTA_dm_on_hw_on(); ASSERT_EQ((uint8_t)BTA_ID_MAX, bta_dm_cb.pm_timer[0].srvc_id[0]); // Shouldn't crash even there's no active timer when calling diff --git a/system/bta/test/bta_gatt_client_test.cc b/system/bta/test/bta_gatt_client_test.cc index 3c19614768..c48dc02cf5 100644 --- a/system/bta/test/bta_gatt_client_test.cc +++ b/system/bta/test/bta_gatt_client_test.cc @@ -27,15 +27,6 @@ using namespace bluetooth::common; // Test hooks -namespace bluetooth { -namespace legacy { -namespace testing { - -std::vector<TimestampedEntry<std::string>> PullCopyOfGattHistory(); - -} // namespace testing -} // namespace legacy -} // namespace bluetooth class BtaDiscTest : public testing::Test { protected: @@ -61,7 +52,7 @@ TEST_F(BtaDiscTest, gatt_history_callback) { gatt_history_callback(std::format("{}", a[2].c_str())); std::vector<bluetooth::common::TimestampedEntry<std::string>> history = - bluetooth::legacy::testing::PullCopyOfGattHistory(); + bluetooth::testing::PullCopyOfGattHistory(); ASSERT_EQ(3UL, history.size()); ASSERT_STREQ(a[0].c_str(), history[0].entry.c_str()); ASSERT_STREQ(a[1].c_str(), history[1].entry.c_str()); diff --git a/system/bta/test/bta_sdp_test.cc b/system/bta/test/bta_sdp_test.cc index 94d035d9a7..06e8ff86aa 100644 --- a/system/bta/test/bta_sdp_test.cc +++ b/system/bta/test/bta_sdp_test.cc @@ -22,24 +22,10 @@ #include "hci/controller_interface_mock.h" #include "test/mock/mock_main_shim_entry.h" -void BTA_dm_on_hw_on(); -void BTA_dm_on_hw_off(); - namespace { const char kName[] = "Hello"; } -namespace bluetooth { -namespace legacy { -namespace testing { - -tBTA_DM_SERVICE_DISCOVERY_CB& bta_dm_discovery_cb(); -void bta_dm_sdp_result(tSDP_STATUS sdp_status, tBTA_DM_SDP_STATE* state); - -} // namespace testing -} // namespace legacy -} // namespace bluetooth - class BtaSdpTest : public BtaWithHwOnTest { protected: void SetUp() override { @@ -69,5 +55,5 @@ TEST_F(BtaSdpTest, nop) {} TEST_F(BtaSdpRegisteredTest, bta_dm_sdp_result_SDP_SUCCESS) { std::unique_ptr<tBTA_DM_SDP_STATE> state = std::make_unique<tBTA_DM_SDP_STATE>( tBTA_DM_SDP_STATE{.service_index = BTA_MAX_SERVICE_ID}); - bluetooth::legacy::testing::bta_dm_sdp_result(tSDP_STATUS::SDP_SUCCESS, state.get()); + bta_dm_sdp_result(tSDP_STATUS::SDP_SUCCESS, state.get()); } diff --git a/system/bta/test/bta_sec_test.cc b/system/bta/test/bta_sec_test.cc index 20b82cc480..57952ece3f 100644 --- a/system/bta/test/bta_sec_test.cc +++ b/system/bta/test/bta_sec_test.cc @@ -38,17 +38,6 @@ constexpr char kRemoteName[] = "TheRemoteName"; } // namespace -// Test hooks -namespace bluetooth { -namespace legacy { -namespace testing { - -tBTM_STATUS bta_dm_sp_cback(tBTM_SP_EVT event, tBTM_SP_EVT_DATA* p_data); - -} // namespace testing -} // namespace legacy -} // namespace bluetooth - class BtaSecTest : public BtaWithHwOnTest { protected: void SetUp() override { diff --git a/system/bta/test/bta_test_fixtures.h b/system/bta/test/bta_test_fixtures.h index 373b846122..c259d0b916 100644 --- a/system/bta/test/bta_test_fixtures.h +++ b/system/bta/test/bta_test_fixtures.h @@ -36,9 +36,6 @@ constexpr tGATT_IF kGattRegisteredIf = 5; -void BTA_dm_on_hw_on(); -void BTA_dm_on_hw_off(); - extern tBTA_DM_CB bta_dm_cb; // Set up base mocks and fakes diff --git a/system/bta/test/common/bta_gatt_api_mock.cc b/system/bta/test/common/bta_gatt_api_mock.cc index e5dee293fb..9494f96c48 100644 --- a/system/bta/test/common/bta_gatt_api_mock.cc +++ b/system/bta/test/common/bta_gatt_api_mock.cc @@ -31,10 +31,10 @@ void gatt::SetMockBtaGattInterface(MockBtaGattInterface* mock_bta_gatt_interface gatt_interface = mock_bta_gatt_interface; } -void BTA_GATTC_AppRegister(tBTA_GATTC_CBACK* p_client_cb, BtaAppRegisterCallback cb, - bool eatt_support) { +void BTA_GATTC_AppRegister(const std::string& name, tBTA_GATTC_CBACK* p_client_cb, + BtaAppRegisterCallback cb, bool eatt_support) { log::assert_that(gatt_interface != nullptr, "Mock GATT interface not set!"); - gatt_interface->AppRegister(p_client_cb, cb, eatt_support); + gatt_interface->AppRegister(name, p_client_cb, cb, eatt_support); } void BTA_GATTC_AppDeregister(tGATT_IF client_if) { diff --git a/system/bta/test/common/bta_gatt_api_mock.h b/system/bta/test/common/bta_gatt_api_mock.h index 6b404526ed..dafec74eb7 100644 --- a/system/bta/test/common/bta_gatt_api_mock.h +++ b/system/bta/test/common/bta_gatt_api_mock.h @@ -27,8 +27,8 @@ namespace gatt { class BtaGattInterface { public: - virtual void AppRegister(tBTA_GATTC_CBACK* p_client_cb, BtaAppRegisterCallback cb, - bool eatt_support) = 0; + virtual void AppRegister(const std::string& name, tBTA_GATTC_CBACK* p_client_cb, + BtaAppRegisterCallback cb, bool eatt_support) = 0; virtual void AppDeregister(tGATT_IF client_if) = 0; virtual void Open(tGATT_IF client_if, const RawAddress& remote_bda, tBTM_BLE_CONN_TYPE connection_type, tBT_TRANSPORT transport, bool opportunistic, @@ -52,7 +52,8 @@ public: class MockBtaGattInterface : public BtaGattInterface { public: MOCK_METHOD((void), AppRegister, - (tBTA_GATTC_CBACK * p_client_cb, BtaAppRegisterCallback cb, bool eatt_support), + (const std::string& name, tBTA_GATTC_CBACK* p_client_cb, BtaAppRegisterCallback cb, + bool eatt_support), (override)); MOCK_METHOD((void), AppDeregister, (tGATT_IF client_if), (override)); MOCK_METHOD((void), Open, diff --git a/system/bta/test/common/btif_storage_mock.cc b/system/bta/test/common/btif_storage_mock.cc index b74b7678ea..3b7ee7d06e 100644 --- a/system/bta/test/common/btif_storage_mock.cc +++ b/system/bta/test/common/btif_storage_mock.cc @@ -41,6 +41,12 @@ void btif_storage_leaudio_update_pacs_bin(const RawAddress& addr) { btif_storage_interface->LeAudioUpdatePacs(addr); } +/** Store GMAP information */ +void btif_storage_leaudio_update_gmap_bin(const RawAddress& addr) { + log::assert_that(btif_storage_interface != nullptr, "Mock storage module not set!"); + btif_storage_interface->LeAudioUpdateGmap(addr); +} + void btif_storage_leaudio_update_ase_bin(const RawAddress& addr) { log::assert_that(btif_storage_interface != nullptr, "Mock storage module not set!"); btif_storage_interface->LeAudioUpdateAses(addr); diff --git a/system/bta/test/common/btif_storage_mock.h b/system/bta/test/common/btif_storage_mock.h index a715b43b70..c756336dab 100644 --- a/system/bta/test/common/btif_storage_mock.h +++ b/system/bta/test/common/btif_storage_mock.h @@ -28,6 +28,7 @@ class BtifStorageInterface { public: virtual void AddLeaudioAutoconnect(RawAddress const& addr, bool autoconnect) = 0; virtual void LeAudioUpdatePacs(RawAddress const& addr) = 0; + virtual void LeAudioUpdateGmap(RawAddress const& addr) = 0; virtual void LeAudioUpdateAses(RawAddress const& addr) = 0; virtual void LeAudioUpdateHandles(RawAddress const& addr) = 0; virtual void SetLeAudioLocations(RawAddress const& addr, uint32_t sink_location, @@ -57,6 +58,7 @@ public: MOCK_METHOD((void), AddLeaudioAutoconnect, (RawAddress const& addr, bool autoconnect), (override)); MOCK_METHOD((void), LeAudioUpdatePacs, (RawAddress const& addr), (override)); + MOCK_METHOD((void), LeAudioUpdateGmap, (RawAddress const& addr), (override)); MOCK_METHOD((void), LeAudioUpdateAses, (RawAddress const& addr), (override)); MOCK_METHOD((void), LeAudioUpdateHandles, (RawAddress const& addr), (override)); MOCK_METHOD((void), SetLeAudioLocations, diff --git a/system/bta/test/common/btm_api_mock.cc b/system/bta/test/common/btm_api_mock.cc index b7208803a3..10540aea65 100644 --- a/system/bta/test/common/btm_api_mock.cc +++ b/system/bta/test/common/btm_api_mock.cc @@ -76,7 +76,7 @@ tBTM_STATUS BTM_SetEncryption(const RawAddress& bd_addr, tBT_TRANSPORT transport return btm_interface->SetEncryption(bd_addr, transport, p_callback, p_ref_data, sec_act); } -bool BTM_SecIsSecurityPending(const RawAddress& bd_addr) { +bool BTM_SecIsLeSecurityPending(const RawAddress& bd_addr) { log::assert_that(btm_interface != nullptr, "Mock btm interface not set!"); return btm_interface->SecIsSecurityPending(bd_addr); } diff --git a/system/bta/vc/vc.cc b/system/bta/vc/vc.cc index c381f1210a..2acf1ae01d 100644 --- a/system/bta/vc/vc.cc +++ b/system/bta/vc/vc.cc @@ -20,6 +20,7 @@ #include <base/strings/string_number_conversions.h> #include <base/strings/string_util.h> #include <bluetooth/log.h> +#include <com_android_bluetooth_flags.h> #include <hardware/bt_gatt_types.h> #include <hardware/bt_vc.h> #include <stdio.h> @@ -114,7 +115,7 @@ public: VolumeControlImpl(bluetooth::vc::VolumeControlCallbacks* callbacks, const base::Closure& initCb) : gatt_if_(0), callbacks_(callbacks), latest_operation_id_(0) { BTA_GATTC_AppRegister( - gattc_callback_static, + "volume_control", gattc_callback_static, base::Bind( [](const base::Closure& initCb, uint8_t client_id, uint8_t status) { if (status != GATT_SUCCESS) { @@ -421,7 +422,7 @@ public: is_volume_change, is_mute_change); if (!is_volume_change && !is_mute_change) { - bluetooth::log::error("Autonomous change but volume and mute did not changed."); + bluetooth::log::warn("Autonomous change but volume and mute did not changed."); return; } @@ -919,7 +920,7 @@ public: [operation_id](auto& operation) { return operation.operation_id_ == operation_id; }); if (op == ongoing_operations_.end()) { - bluetooth::log::error("Could not find operation id: {}", operation_id); + bluetooth::log::warn("Could not find operation id: {}", operation_id); return; } @@ -934,6 +935,27 @@ public: } } + bool isPendingVolumeControlOperation(const RawAddress& addr) { + if (!com::android::bluetooth::flags::vcp_allow_set_same_volume_if_pending()) { + return false; + } + + if (std::find_if(ongoing_operations_.begin(), ongoing_operations_.end(), + [&addr](const VolumeOperation& op) { + auto it = find(op.devices_.begin(), op.devices_.end(), addr); + if (it != op.devices_.end()) { + bluetooth::log::debug( + "There is a pending volume operation {} for device {}", + op.operation_id_, addr); + return true; + } + return false; + }) != ongoing_operations_.end()) { + return true; + } + return false; + } + void RemovePendingVolumeControlOperations(const std::vector<RawAddress>& devices, int group_id) { bluetooth::log::debug(""); for (auto op = ongoing_operations_.begin(); op != ongoing_operations_.end();) { @@ -1156,7 +1178,8 @@ public: volume_control_devices_.FindByAddress(std::get<RawAddress>(addr_or_group_id)); if (dev != nullptr) { bluetooth::log::debug("Address: {}: isReady: {}", dev->address, dev->IsReady()); - if (dev->IsReady() && (dev->volume != volume)) { + if (dev->IsReady() && + ((dev->volume != volume) || isPendingVolumeControlOperation(dev->address))) { std::vector<RawAddress> devices = {dev->address}; RemovePendingVolumeControlOperations(devices, bluetooth::groups::kGroupUnknown); PrepareVolumeControlOperation(devices, bluetooth::groups::kGroupUnknown, false, opcode, @@ -1189,7 +1212,7 @@ public: continue; } - if (!dev->IsReady() || (dev->volume == volume)) { + if (!dev->IsReady() || ((dev->volume == volume) && !isPendingVolumeControlOperation(*it))) { it = devices.erase(it); volumeNotChanged = volumeNotChanged ? volumeNotChanged : (dev->volume == volume); deviceNotReady = deviceNotReady ? deviceNotReady : !dev->IsReady(); diff --git a/system/bta/vc/vc_test.cc b/system/bta/vc/vc_test.cc index c9c6adc425..cd084fc75e 100644 --- a/system/bta/vc/vc_test.cc +++ b/system/bta/vc/vc_test.cc @@ -555,8 +555,8 @@ protected: void TestAppRegister(void) { BtaAppRegisterCallback app_register_callback; - EXPECT_CALL(gatt_interface, AppRegister(_, _, _)) - .WillOnce(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); + EXPECT_CALL(gatt_interface, AppRegister(_, _, _, _)) + .WillOnce(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback))); VolumeControl::Initialize(&callbacks, base::DoNothing()); ASSERT_TRUE(gatt_callback); ASSERT_TRUE(app_register_callback); @@ -765,8 +765,8 @@ TEST_F(VolumeControlTest, test_get_uninitialized) { ASSERT_DEATH(VolumeControl:: TEST_F(VolumeControlTest, test_initialize) { bool init_cb_called = false; BtaAppRegisterCallback app_register_callback; - EXPECT_CALL(gatt_interface, AppRegister(_, _, _)) - .WillOnce(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); + EXPECT_CALL(gatt_interface, AppRegister(_, _, _, _)) + .WillOnce(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback))); VolumeControl::Initialize( &callbacks, base::Bind([](bool* init_cb_called) { *init_cb_called = true; }, &init_cb_called)); @@ -1703,6 +1703,47 @@ TEST_F(VolumeControlValueSetTest, test_set_volume) { VolumeControl::Get()->SetVolume(test_address, 0x20); } +TEST_F(VolumeControlValueSetTest, test_set_volume_to_previous_during_pending) { + com::android::bluetooth::flags::provider_->vcp_allow_set_same_volume_if_pending(true); + // In this test we simulate notification coming later and operations will be queued + ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _)) + .WillByDefault([](uint16_t conn_id, uint16_t handle, std::vector<uint8_t> value, + tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb, void* cb_data) { + uint8_t write_rsp; + + switch (value[0]) { + case 0x04: // set abs. volume + break; + default: + break; + } + cb(conn_id, GATT_SUCCESS, handle, 0, &write_rsp, cb_data); + }); + + const std::vector<uint8_t> vol_x10({0x04, /*change_cnt*/ 0, 0x10}); + std::vector<uint8_t> ntf_value_x10({0x10, 0, 1}); + const std::vector<uint8_t> vol_x11({0x04, /*change_cnt*/ 1, 0x11}); + std::vector<uint8_t> ntf_value_x11({0x11, 0, 2}); + const std::vector<uint8_t> vol_x10_2({0x04, /*change_cnt*/ 2, 0x10}); + std::vector<uint8_t> ntf_value_x10_2({0x10, 0, 3}); + + EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1); + + VolumeControl::Get()->SetVolume(test_address, 0x10); + GetNotificationEvent(0x0021, ntf_value_x10); + + EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x11, GATT_WRITE, _, _)).Times(1); + EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10_2, GATT_WRITE, _, _)) + .Times(1); + + VolumeControl::Get()->SetVolume(test_address, 0x11); + VolumeControl::Get()->SetVolume(test_address, 0x10); + GetNotificationEvent(0x0021, ntf_value_x11); + GetNotificationEvent(0x0021, ntf_value_x10_2); + + Mock::VerifyAndClearExpectations(&gatt_queue); +} + TEST_F(VolumeControlValueSetTest, test_set_volume_stress) { uint8_t n = 100; uint8_t change_cnt = 0; diff --git a/system/btcore/Android.bp b/system/btcore/Android.bp index dc6df095ad..17d5c01905 100644 --- a/system/btcore/Android.bp +++ b/system/btcore/Android.bp @@ -24,9 +24,7 @@ cc_defaults { ], header_libs: ["libbluetooth_headers"], host_supported: true, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], target: { host_linux: { cflags: ["-D_GNU_SOURCE"], @@ -47,9 +45,7 @@ cc_library_static { ], header_libs: ["libbluetooth_headers"], host_supported: true, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], target: { host_linux: { cflags: ["-D_GNU_SOURCE"], @@ -80,7 +76,7 @@ cc_library_headers { host_supported: true, apex_available: [ "//apex_available:platform", - "com.android.btservices", + "com.android.bt", ], min_sdk_version: "30", } diff --git a/system/btif/Android.bp b/system/btif/Android.bp index 75494590fd..498d0c1048 100644 --- a/system/btif/Android.bp +++ b/system/btif/Android.bp @@ -24,9 +24,7 @@ cc_library { generated_sources: ["statslog_bt.cpp"], generated_headers: ["statslog_bt.h"], export_generated_headers: ["statslog_bt.h"], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "30", shared_libs: [ "libstatssocket", @@ -64,9 +62,7 @@ cc_library_static { shared_libs: [ "libchrome", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", } @@ -164,9 +160,7 @@ cc_library_static { shared_libs: [ "libbinder", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], host_supported: true, min_sdk_version: "Tiramisu", } @@ -253,9 +247,7 @@ cc_library_static { /* we export all classes, so change default visibility, instead of having EXPORT_SYMBOL on each class*/ "-fvisibility=default", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], host_supported: true, min_sdk_version: "Tiramisu", header_libs: ["libbluetooth_headers"], diff --git a/system/btif/include/btif_dm.h b/system/btif/include/btif_dm.h index 15a5bfa1ae..9069c97b76 100644 --- a/system/btif/include/btif_dm.h +++ b/system/btif/include/btif_dm.h @@ -104,6 +104,7 @@ void btif_dm_clear_event_filter(); void btif_dm_clear_event_mask(); void btif_dm_clear_filter_accept_list(); void btif_dm_disconnect_all_acls(); +void btif_dm_disconnect_acl(const RawAddress& bd_addr, tBT_TRANSPORT transport); void btif_dm_le_rand(bluetooth::hci::LeRandCallback callback); void btif_dm_set_event_filter_connection_setup_all_devices(); @@ -150,6 +151,7 @@ void btif_dm_get_ble_local_keys(tBTA_DM_BLE_LOCAL_KEY_MASK* p_key_mask, Octet16* tBTA_BLE_LOCAL_ID_KEYS* p_id_keys); void btif_update_remote_properties(const RawAddress& bd_addr, BD_NAME bd_name, DEV_CLASS dev_class, tBT_DEVICE_TYPE dev_type); +bool btif_is_interesting_le_service(const bluetooth::Uuid& uuid); bool check_cod_hid(const RawAddress& bd_addr); bool check_cod_hid_major(const RawAddress& bd_addr, uint32_t cod); diff --git a/system/btif/include/btif_profile_storage.h b/system/btif/include/btif_profile_storage.h index 6459c45b99..d6a3688741 100644 --- a/system/btif/include/btif_profile_storage.h +++ b/system/btif/include/btif_profile_storage.h @@ -102,6 +102,9 @@ void btif_storage_set_leaudio_autoconnect(const RawAddress& addr, bool autoconne /** Store PACs information */ void btif_storage_leaudio_update_pacs_bin(const RawAddress& addr); +/** Store GMAP information */ +void btif_storage_leaudio_update_gmap_bin(const RawAddress& addr); + /** Store ASEs information */ void btif_storage_leaudio_update_ase_bin(const RawAddress& addr); diff --git a/system/btif/include/btif_storage.h b/system/btif/include/btif_storage.h index 0c1043faae..01432bade6 100644 --- a/system/btif/include/btif_storage.h +++ b/system/btif/include/btif_storage.h @@ -446,6 +446,7 @@ bt_status_t btif_storage_set_hid_connection_policy(const tAclLinkSpec& link_spec bt_status_t btif_storage_get_hid_connection_policy(const tAclLinkSpec& link_spec, bool* reconnect_allowed); +void btif_storage_migrate_services(); /****************************************************************************** * Exported for unit tests *****************************************************************************/ diff --git a/system/btif/src/bluetooth.cc b/system/btif/src/bluetooth.cc index 25a74ea47a..7bdf50781b 100644 --- a/system/btif/src/bluetooth.cc +++ b/system/btif/src/bluetooth.cc @@ -814,6 +814,16 @@ static int disconnect_all_acls() { return BT_STATUS_SUCCESS; } +static int disconnect_acl(const RawAddress& bd_addr, int transport) { + log::verbose("{}", bd_addr); + if (!interface_ready()) { + return BT_STATUS_NOT_READY; + } + + do_in_main_thread(base::BindOnce(btif_dm_disconnect_acl, bd_addr, to_bt_transport(transport))); + return BT_STATUS_SUCCESS; +} + static void le_rand_btif_cb(uint64_t random_number) { log::verbose(""); do_in_jni_thread(base::BindOnce( @@ -1286,6 +1296,7 @@ EXPORT_SYMBOL bt_interface_t bluetoothInterface = { .clear_event_mask = clear_event_mask, .clear_filter_accept_list = clear_filter_accept_list, .disconnect_all_acls = disconnect_all_acls, + .disconnect_acl = disconnect_acl, .le_rand = le_rand, .set_event_filter_inquiry_result_all_devices = set_event_filter_inquiry_result_all_devices, .set_default_event_mask_except = set_default_event_mask_except, diff --git a/system/btif/src/btif_a2dp_sink.cc b/system/btif/src/btif_a2dp_sink.cc index 02b92621ed..0328ba9cbc 100644 --- a/system/btif/src/btif_a2dp_sink.cc +++ b/system/btif/src/btif_a2dp_sink.cc @@ -731,6 +731,7 @@ uint8_t btif_a2dp_sink_enqueue_buf(BT_HDR* p_pkt) { } log::verbose("+"); + /* Allocate and queue this buffer */ BT_HDR* p_msg = reinterpret_cast<BT_HDR*>(osi_malloc(sizeof(*p_msg) + p_pkt->len)); memcpy(p_msg, p_pkt, sizeof(*p_msg)); @@ -738,17 +739,18 @@ uint8_t btif_a2dp_sink_enqueue_buf(BT_HDR* p_pkt) { memcpy(p_msg->data, p_pkt->data + p_pkt->offset, p_pkt->len); fixed_queue_enqueue(btif_a2dp_sink_cb.rx_audio_queue, p_msg); + /* If the queue is full, pop the front off to make room for the new data */ if (fixed_queue_length(btif_a2dp_sink_cb.rx_audio_queue) == MAX_INPUT_A2DP_FRAME_QUEUE_SZ) { + log::verbose("Audio data buffer has reached max size. Dropping front packet"); osi_free(fixed_queue_try_dequeue(btif_a2dp_sink_cb.rx_audio_queue)); - uint8_t ret = fixed_queue_length(btif_a2dp_sink_cb.rx_audio_queue); - return ret; } - // Avoid other checks if alarm has already been initialized. + /* Check to see if we need to start decoding */ if (btif_a2dp_sink_cb.decode_alarm == nullptr && fixed_queue_length(btif_a2dp_sink_cb.rx_audio_queue) >= MAX_A2DP_DELAYED_START_FRAME_COUNT) { - log::verbose("Initiate decoding. Current focus state:{}", btif_a2dp_sink_cb.rx_focus_state); + log::verbose("Can initiate decoding, focus_state={}", btif_a2dp_sink_cb.rx_focus_state); if (btif_a2dp_sink_cb.rx_focus_state == BTIF_A2DP_SINK_FOCUS_GRANTED) { + log::info("Request to begin decoding"); btif_a2dp_sink_audio_handle_start_decoding(); } } diff --git a/system/btif/src/btif_a2dp_source.cc b/system/btif/src/btif_a2dp_source.cc index d58e3de696..70634b9984 100644 --- a/system/btif/src/btif_a2dp_source.cc +++ b/system/btif/src/btif_a2dp_source.cc @@ -531,9 +531,15 @@ bool btif_a2dp_source_restart_session(const RawAddress& old_peer_address, bool btif_a2dp_source_end_session(const RawAddress& peer_address) { log::info("peer_address={} state={}", peer_address, btif_a2dp_source_cb.StateStr()); - local_thread()->DoInThread(FROM_HERE, - base::BindOnce(&btif_a2dp_source_end_session_delayed, peer_address)); - btif_a2dp_source_cleanup_codec(); + if (com::android::bluetooth::flags::a2dp_source_threading_fix()) { + btif_a2dp_source_cleanup_codec(); + btif_a2dp_source_end_session_delayed(peer_address); + } else { + + local_thread()->DoInThread(FROM_HERE, + base::BindOnce(&btif_a2dp_source_end_session_delayed, peer_address)); + btif_a2dp_source_cleanup_codec(); + } return true; } diff --git a/system/btif/src/btif_bqr.cc b/system/btif/src/btif_bqr.cc index 37ce06a935..eaaef220e6 100644 --- a/system/btif/src/btif_bqr.cc +++ b/system/btif/src/btif_bqr.cc @@ -725,30 +725,34 @@ static void CategorizeBqrEvent(uint8_t length, const uint8_t* p_bqr_event) { break; case QUALITY_REPORT_ID_ENERGY_MONITOR: - if (length < kEnergyMonitorParamTotalLen) { - log::fatal( - "Parameter total length: {} is abnormal. It shall be not shorter " - "than: {}", - length, kEnergyMonitorParamTotalLen); - return; - } - if (com::android::bluetooth::flags::support_bluetooth_quality_report_v6()) { - AddEnergyMonitorEventToQueue(length, p_bqr_event); + if (vendor_cap_supported_version >= kBqrVersion6_0) { + if (length < kEnergyMonitorParamTotalLen) { + log::fatal( + "Event {} Parameter total length: {} is abnormal. It shall be not shorter " + "than: {}", + quality_report_id, length, kEnergyMonitorParamTotalLen); + return; + } + + AddEnergyMonitorEventToQueue(length, p_bqr_event); + } } break; case QUALITY_REPORT_ID_RF_STATS: - if (length < kRFStatsParamTotalLen) { - log::fatal( - "Parameter total length: {} is abnormal. It shall be not shorter " - "than: {}", - length, kRFStatsParamTotalLen); - return; - } - if (com::android::bluetooth::flags::support_bluetooth_quality_report_v6()) { - AddRFStatsEventToQueue(length, p_bqr_event); + if (vendor_cap_supported_version >= kBqrVersion6_0) { + if (length < kRFStatsParamTotalLen) { + log::fatal( + "Event {} Parameter total length: {} is abnormal. It shall be not shorter " + "than: {}", + quality_report_id, length, kEnergyMonitorParamTotalLen); + return; + } + + AddRFStatsEventToQueue(length, p_bqr_event); + } } break; diff --git a/system/btif/src/btif_core.cc b/system/btif/src/btif_core.cc index 9164e53ef7..8e481daed0 100644 --- a/system/btif/src/btif_core.cc +++ b/system/btif/src/btif_core.cc @@ -80,7 +80,7 @@ using namespace bluetooth; #if defined(TARGET_FLOSS) #define BTE_DID_CONF_FILE "/var/lib/bluetooth/bt_did.conf" #elif defined(__ANDROID__) -#define BTE_DID_CONF_FILE "/apex/com.android.btservices/etc/bluetooth/bt_did.conf" +#define BTE_DID_CONF_FILE "/apex/com.android.bt/etc/bluetooth/bt_did.conf" #else // !defined(__ANDROID__) #define BTE_DID_CONF_FILE "bt_did.conf" #endif // defined(__ANDROID__) @@ -134,7 +134,12 @@ int btif_is_enabled(void) { return (!btif_is_dut_mode()) && (stack_manager_get_interface()->get_stack_is_running()); } -void btif_init_ok() { btif_dm_load_ble_local_keys(); } +void btif_init_ok() { + btif_dm_load_ble_local_keys(); + if (com::android::bluetooth::flags::separate_service_storage()) { + btif_storage_migrate_services(); + } +} /******************************************************************************* * @@ -290,7 +295,7 @@ void btif_dut_mode_send(uint16_t opcode, uint8_t* buf, uint8_t len) { ****************************************************************************/ static bt_status_t btif_in_get_adapter_properties(void) { - const static uint32_t NUM_ADAPTER_PROPERTIES = 5; + static const uint32_t NUM_ADAPTER_PROPERTIES = 5; bt_property_t properties[NUM_ADAPTER_PROPERTIES]; uint32_t num_props = 0; @@ -340,12 +345,13 @@ static bt_status_t btif_in_get_adapter_properties(void) { } static bt_status_t btif_in_get_remote_device_properties(RawAddress* bd_addr) { - bt_property_t remote_properties[8]; + bt_property_t remote_properties[9]; uint32_t num_props = 0; bt_bdname_t name, alias; uint32_t cod, devtype; Uuid remote_uuids[BT_MAX_NUM_UUIDS]; + Uuid remote_uuids_le[BT_MAX_NUM_UUIDS]; memset(remote_properties, 0, sizeof(remote_properties)); BTIF_STORAGE_FILL_PROPERTY(&remote_properties[num_props], BT_PROPERTY_BDNAME, sizeof(name), @@ -369,10 +375,17 @@ static bt_status_t btif_in_get_remote_device_properties(RawAddress* bd_addr) { num_props++; BTIF_STORAGE_FILL_PROPERTY(&remote_properties[num_props], BT_PROPERTY_UUIDS, sizeof(remote_uuids), - remote_uuids); + &remote_uuids); btif_storage_get_remote_device_property(bd_addr, &remote_properties[num_props]); num_props++; + if (com::android::bluetooth::flags::separate_service_storage()) { + BTIF_STORAGE_FILL_PROPERTY(&remote_properties[num_props], BT_PROPERTY_UUIDS_LE, + sizeof(remote_uuids_le), &remote_uuids_le); + btif_storage_get_remote_device_property(bd_addr, &remote_properties[num_props]); + num_props++; + } + GetInterfaceToProfiles()->events->invoke_remote_device_properties_cb( BT_STATUS_SUCCESS, *bd_addr, num_props, remote_properties); diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc index 5848ebba1c..6f83aee781 100644 --- a/system/btif/src/btif_dm.cc +++ b/system/btif/src/btif_dm.cc @@ -1517,15 +1517,16 @@ static void btif_dm_search_devices_evt(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH* } /* Returns true if |uuid| should be passed as device property */ -static bool btif_is_interesting_le_service(bluetooth::Uuid uuid) { +bool btif_is_interesting_le_service(const bluetooth::Uuid& uuid) { return uuid.As16Bit() == UUID_SERVCLASS_LE_HID || uuid == UUID_HEARING_AID || uuid == UUID_VC || uuid == UUID_CSIS || uuid == UUID_LE_AUDIO || uuid == UUID_LE_MIDI || uuid == UUID_HAS || uuid == UUID_BASS || uuid == UUID_BATTERY || uuid == ANDROID_HEADTRACKER_SERVICE_UUID; } -static bt_status_t btif_get_existing_uuids(RawAddress* bd_addr, Uuid* existing_uuids) { +static bt_status_t btif_get_existing_uuids(RawAddress* bd_addr, Uuid* existing_uuids, + bt_property_type_t property_type = BT_PROPERTY_UUIDS) { bt_property_t tmp_prop; - BTIF_STORAGE_FILL_PROPERTY(&tmp_prop, BT_PROPERTY_UUIDS, sizeof(*existing_uuids), existing_uuids); + BTIF_STORAGE_FILL_PROPERTY(&tmp_prop, property_type, sizeof(*existing_uuids), existing_uuids); return btif_storage_get_remote_device_property(bd_addr, &tmp_prop); } @@ -1537,9 +1538,10 @@ static bool btif_is_gatt_service_discovery_post_pairing(const RawAddress bd_addr (pairing_cb.gatt_over_le == btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED); } -static void btif_merge_existing_uuids(RawAddress& addr, std::set<Uuid>* uuids) { +static void btif_merge_existing_uuids(RawAddress& addr, std::set<Uuid>* uuids, + bt_property_type_t property_type = BT_PROPERTY_UUIDS) { Uuid existing_uuids[BT_MAX_NUM_UUIDS] = {}; - bt_status_t lookup_result = btif_get_existing_uuids(&addr, existing_uuids); + bt_status_t lookup_result = btif_get_existing_uuids(&addr, existing_uuids, property_type); if (lookup_result == BT_STATUS_FAIL) { return; @@ -1550,18 +1552,14 @@ static void btif_merge_existing_uuids(RawAddress& addr, std::set<Uuid>* uuids) { if (btif_should_ignore_uuid(uuid)) { continue; } - if (btif_is_interesting_le_service(uuid)) { - log::info("interesting le service {} insert", uuid.ToString()); - uuids->insert(uuid); - } + + uuids->insert(uuid); } } static void btif_on_service_discovery_results(RawAddress bd_addr, const std::vector<bluetooth::Uuid>& uuids_param, tBTA_STATUS result) { - bt_property_t prop; - std::vector<uint8_t> property_value; std::set<Uuid> uuids; bool a2dp_sink_capable = false; @@ -1589,8 +1587,12 @@ static void btif_on_service_discovery_results(RawAddress bd_addr, pairing_cb.sdp_over_classic = btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED; } - prop.type = BT_PROPERTY_UUIDS; - prop.len = 0; + std::vector<uint8_t> bredr_property_value; + std::vector<uint8_t> le_property_value; + bt_property_t uuid_props[2] = {}; + bt_property_t& bredr_prop = uuid_props[0]; + bt_property_t& le_prop = uuid_props[1]; + if ((result == BTA_SUCCESS) && !uuids_param.empty()) { log::info("New UUIDs for {}:", bd_addr); for (const auto& uuid : uuids_param) { @@ -1610,13 +1612,35 @@ static void btif_on_service_discovery_results(RawAddress bd_addr, for (auto& uuid : uuids) { auto uuid_128bit = uuid.To128BitBE(); - property_value.insert(property_value.end(), uuid_128bit.begin(), uuid_128bit.end()); + bredr_property_value.insert(bredr_property_value.end(), uuid_128bit.begin(), + uuid_128bit.end()); if (uuid == UUID_A2DP_SINK) { a2dp_sink_capable = true; } } - prop.val = (void*)property_value.data(); - prop.len = Uuid::kNumBytes128 * uuids.size(); + + bredr_prop = {BT_PROPERTY_UUIDS, static_cast<int>(Uuid::kNumBytes128 * uuids.size()), + (void*)bredr_property_value.data()}; + + if (com::android::bluetooth::flags::separate_service_storage()) { + bt_status_t ret = btif_storage_set_remote_device_property(&bd_addr, &bredr_prop); + ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote classic services failed", ret); + + std::set<Uuid> le_uuids; + if (results_for_bonding_device) { + btif_merge_existing_uuids(pairing_cb.static_bdaddr, &le_uuids, BT_PROPERTY_UUIDS_LE); + btif_merge_existing_uuids(pairing_cb.bd_addr, &le_uuids, BT_PROPERTY_UUIDS_LE); + } else { + btif_merge_existing_uuids(bd_addr, &le_uuids, BT_PROPERTY_UUIDS_LE); + } + + for (auto& uuid : le_uuids) { + auto uuid_128bit = uuid.To128BitBE(); + le_property_value.insert(le_property_value.end(), uuid_128bit.begin(), uuid_128bit.end()); + } + le_prop = {BT_PROPERTY_UUIDS_LE, static_cast<int>(Uuid::kNumBytes128 * le_uuids.size()), + (void*)le_property_value.data()}; + } } bool skip_reporting_wait_for_le = false; @@ -1649,17 +1673,18 @@ static void btif_on_service_discovery_results(RawAddress bd_addr, log::info("SDP failed, send {} EIR UUIDs to unblock bonding {}", num_eir_uuids, bd_addr); for (auto eir_uuid : uuids_iter->second) { auto uuid_128bit = eir_uuid.To128BitBE(); - property_value.insert(property_value.end(), uuid_128bit.begin(), uuid_128bit.end()); + bredr_property_value.insert(bredr_property_value.end(), uuid_128bit.begin(), + uuid_128bit.end()); } eir_uuids_cache.erase(uuids_iter); } if (num_eir_uuids > 0) { - prop.val = (void*)property_value.data(); - prop.len = num_eir_uuids * Uuid::kNumBytes128; + bredr_prop.val = (void*)bredr_property_value.data(); + bredr_prop.len = num_eir_uuids * Uuid::kNumBytes128; } else { log::warn("SDP failed and we have no EIR UUIDs to report either"); - prop.val = &uuid; - prop.len = Uuid::kNumBytes128; + bredr_prop.val = &uuid; + bredr_prop.len = Uuid::kNumBytes128; } } @@ -1677,9 +1702,10 @@ static void btif_on_service_discovery_results(RawAddress bd_addr, uuids_param.size(), num_eir_uuids)); if (!uuids_param.empty() || num_eir_uuids != 0) { - /* Also write this to the NVRAM */ - const bt_status_t ret = btif_storage_set_remote_device_property(&bd_addr, &prop); - ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", ret); + if (!com::android::bluetooth::flags::separate_service_storage()) { + const bt_status_t ret = btif_storage_set_remote_device_property(&bd_addr, &bredr_prop); + ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", ret); + } if (skip_reporting_wait_for_le) { log::info( @@ -1694,16 +1720,13 @@ static void btif_on_service_discovery_results(RawAddress bd_addr, } /* Send the event to the BTIF */ - GetInterfaceToProfiles()->events->invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr, - 1, &prop); + GetInterfaceToProfiles()->events->invoke_remote_device_properties_cb( + BT_STATUS_SUCCESS, bd_addr, ARRAY_SIZE(uuid_props), uuid_props); } } static void btif_on_gatt_results(RawAddress bd_addr, std::vector<bluetooth::Uuid>& services, bool is_transport_le) { - std::vector<bt_property_t> prop; - std::vector<uint8_t> property_value; - std::set<Uuid> uuids; RawAddress static_addr_copy = pairing_cb.static_bdaddr; bool lea_supported = is_le_audio_capable_during_service_discovery(bd_addr); @@ -1739,6 +1762,7 @@ static void btif_on_gatt_results(RawAddress bd_addr, std::vector<bluetooth::Uuid BTM_LogHistory(kBtmLogTag, bd_addr, "Discovered GATT services using SDP transport"); } + std::set<Uuid> uuids; for (Uuid uuid : services) { if (btif_is_interesting_le_service(uuid)) { if (btif_should_ignore_uuid(uuid)) { @@ -1767,46 +1791,28 @@ static void btif_on_gatt_results(RawAddress bd_addr, std::vector<bluetooth::Uuid log::info("Will return Classic SDP results, if done, to unblock bonding"); } - Uuid existing_uuids[BT_MAX_NUM_UUIDS] = {}; - - // Look up UUIDs using pseudo address (either RPA or static address) - bt_status_t existing_lookup_result = btif_get_existing_uuids(&bd_addr, existing_uuids); - - if (existing_lookup_result != BT_STATUS_FAIL) { - log::info("Got some existing UUIDs by address {}", bd_addr); - - for (int i = 0; i < BT_MAX_NUM_UUIDS; i++) { - Uuid uuid = existing_uuids[i]; - if (uuid.IsEmpty()) { - continue; - } - uuids.insert(uuid); + if (!com::android::bluetooth::flags::separate_service_storage()) { + // Look up UUIDs using pseudo address (either RPA or static address) + btif_merge_existing_uuids(bd_addr, &uuids); + if (bd_addr != static_addr_copy) { + // Look up UUID using static address, if different than sudo address + btif_merge_existing_uuids(static_addr_copy, &uuids); } } - if (bd_addr != static_addr_copy) { - // Look up UUID using static address, if different than sudo address - existing_lookup_result = btif_get_existing_uuids(&static_addr_copy, existing_uuids); - if (existing_lookup_result != BT_STATUS_FAIL) { - log::info("Got some existing UUIDs by static address {}", static_addr_copy); - for (int i = 0; i < BT_MAX_NUM_UUIDS; i++) { - Uuid uuid = existing_uuids[i]; - if (uuid.IsEmpty()) { - continue; - } - uuids.insert(uuid); - } - } - } + std::vector<bt_property_t> prop; + std::vector<uint8_t> property_value; for (auto& uuid : uuids) { auto uuid_128bit = uuid.To128BitBE(); property_value.insert(property_value.end(), uuid_128bit.begin(), uuid_128bit.end()); } - prop.push_back(bt_property_t{BT_PROPERTY_UUIDS, - static_cast<int>(Uuid::kNumBytes128 * uuids.size()), - (void*)property_value.data()}); + prop.push_back(bt_property_t{ + (com::android::bluetooth::flags::separate_service_storage() && is_transport_le) + ? BT_PROPERTY_UUIDS_LE + : BT_PROPERTY_UUIDS, + static_cast<int>(Uuid::kNumBytes128 * uuids.size()), (void*)property_value.data()}); /* Also write this to the NVRAM */ bt_status_t ret = btif_storage_set_remote_device_property(&bd_addr, &prop[0]); @@ -1817,8 +1823,7 @@ static void btif_on_gatt_results(RawAddress bd_addr, std::vector<bluetooth::Uuid * send them with rest of SDP results in on_service_discovery_results */ return; } else { - if (pairing_cb.sdp_over_classic == btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED && - com::android::bluetooth::flags::bta_dm_discover_both()) { + if (pairing_cb.sdp_over_classic == btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED) { /* Don't report services yet, they will be reported together once SDP * finishes. */ log::info("will report services later, with SDP results {}", bd_addr); @@ -1826,6 +1831,32 @@ static void btif_on_gatt_results(RawAddress bd_addr, std::vector<bluetooth::Uuid } } + if (!com::android::bluetooth::flags::separate_service_storage()) { + /* Send the event to the BTIF */ + GetInterfaceToProfiles()->events->invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr, + prop.size(), prop.data()); + return; + } + + std::set<Uuid> bredr_uuids; + // Look up UUIDs using pseudo address (either RPA or static address) + btif_merge_existing_uuids(bd_addr, &bredr_uuids); + if (bd_addr != static_addr_copy) { + // Look up UUID using static address, if different than sudo address + btif_merge_existing_uuids(static_addr_copy, &bredr_uuids); + } + + std::vector<uint8_t> bredr_property_value; + + for (auto& uuid : bredr_uuids) { + auto uuid_128bit = uuid.To128BitBE(); + bredr_property_value.insert(bredr_property_value.end(), uuid_128bit.begin(), uuid_128bit.end()); + } + + prop.push_back(bt_property_t{BT_PROPERTY_UUIDS, + static_cast<int>(Uuid::kNumBytes128 * bredr_uuids.size()), + (void*)bredr_property_value.data()}); + /* Send the event to the BTIF */ GetInterfaceToProfiles()->events->invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr, prop.size(), prop.data()); @@ -2517,6 +2548,9 @@ void btif_dm_cancel_bond(const RawAddress bd_addr) { ** 2. special handling for HID devices */ if (is_bonding_or_sdp()) { + // clear sdp_attempts + pairing_cb.sdp_attempts = 0; + if (com::android::bluetooth::flags::ignore_unrelated_cancel_bond() && (pairing_cb.bd_addr != bd_addr)) { log::warn("Ignoring bond cancel for unrelated device: {} pairing: {}", bd_addr, @@ -3896,6 +3930,30 @@ void btif_dm_clear_filter_accept_list() { BTA_DmClearFilterAcceptList(); } void btif_dm_disconnect_all_acls() { BTA_DmDisconnectAllAcls(); } +void btif_dm_disconnect_acl(const RawAddress& bd_addr, tBT_TRANSPORT transport) { + log::debug(" {}, transport {}", bd_addr, transport); + + if (transport == BT_TRANSPORT_LE || transport == BT_TRANSPORT_AUTO) { + uint16_t acl_handle = + get_btm_client_interface().peer.BTM_GetHCIConnHandle(bd_addr, BT_TRANSPORT_LE); + + log::debug("{}, le_acl_handle: {:#x}", bd_addr, acl_handle); + if (acl_handle != HCI_INVALID_HANDLE) { + acl_disconnect_from_handle(acl_handle, HCI_ERR_PEER_USER, "bt_btif_dm disconnect"); + } + } + + if (transport == BT_TRANSPORT_BR_EDR || transport == BT_TRANSPORT_AUTO) { + uint16_t acl_handle = + get_btm_client_interface().peer.BTM_GetHCIConnHandle(bd_addr, BT_TRANSPORT_BR_EDR); + + log::debug("{}, bredr_acl_handle: {:#x}", bd_addr, acl_handle); + if (acl_handle != HCI_INVALID_HANDLE) { + acl_disconnect_from_handle(acl_handle, HCI_ERR_PEER_USER, "bt_btif_dm disconnect"); + } + } +} + void btif_dm_le_rand(bluetooth::hci::LeRandCallback callback) { BTA_DmLeRand(std::move(callback)); } void btif_dm_set_event_filter_connection_setup_all_devices() { diff --git a/system/btif/src/btif_gatt_client.cc b/system/btif/src/btif_gatt_client.cc index 075ed1b20d..c77af0e13d 100644 --- a/system/btif/src/btif_gatt_client.cc +++ b/system/btif/src/btif_gatt_client.cc @@ -266,13 +266,13 @@ void btm_read_rssi_cb(void* p_void) { * Client API Functions ******************************************************************************/ -static bt_status_t btif_gattc_register_app(const Uuid& uuid, bool eatt_support) { +static bt_status_t btif_gattc_register_app(const Uuid& uuid, const char* name, bool eatt_support) { CHECK_BTGATT_INIT(); return do_in_jni_thread(Bind( - [](const Uuid& uuid, bool eatt_support) { + [](const Uuid& uuid, const std::string& name, bool eatt_support) { BTA_GATTC_AppRegister( - bta_gattc_cback, + name, bta_gattc_cback, base::Bind( [](const Uuid& uuid, uint8_t client_id, uint8_t status) { do_in_jni_thread(Bind( @@ -286,7 +286,7 @@ static bt_status_t btif_gattc_register_app(const Uuid& uuid, bool eatt_support) uuid), eatt_support); }, - uuid, eatt_support)); + uuid, std::string(name), eatt_support)); } static void btif_gattc_unregister_app_impl(int client_if) { BTA_GATTC_AppDeregister(client_if); } diff --git a/system/btif/src/btif_hci_vs.cc b/system/btif/src/btif_hci_vs.cc index 35b8b9cdfb..90af5cb05d 100644 --- a/system/btif/src/btif_hci_vs.cc +++ b/system/btif/src/btif_hci_vs.cc @@ -56,7 +56,7 @@ static void CommandStatusOrCompleteCallback(BluetoothHciVendorSpecificCallbacks* static void EventCallback(BluetoothHciVendorSpecificCallbacks* callbacks, VendorSpecificEventView view) { - const uint8_t aosp_reserved_codes_range[] = {0x50, 0x60}; + const uint8_t aosp_reserved_codes_range[] = {0x52, 0x60}; auto code = static_cast<uint8_t>(view.GetSubeventCode()); if (code >= aosp_reserved_codes_range[0] && code < aosp_reserved_codes_range[1]) { return; diff --git a/system/btif/src/btif_hf.cc b/system/btif/src/btif_hf.cc index 7e6fa5fef3..a8caa39452 100644 --- a/system/btif/src/btif_hf.cc +++ b/system/btif/src/btif_hf.cc @@ -67,6 +67,7 @@ #include "stack/include/bt_uuid16.h" #include "stack/include/btm_client_interface.h" #include "stack/include/btm_log_history.h" +#include "stack/include/btm_sec_api.h" #include "types/raw_address.h" #define PRIVATE_CELL(number) \ @@ -471,7 +472,9 @@ static void btif_hf_upstreams_evt(uint16_t event, char* p_param) { log_counter_metrics_btif( android::bluetooth::CodePathCounterKeyEnum::HFP_SELF_INITIATED_AG_FAILED, 1); btif_queue_advance(); - DEVICE_IOT_CONFIG_ADDR_INT_ADD_ONE(connected_bda, IOT_CONF_KEY_HFP_SLC_CONN_FAIL_COUNT); + if (btm_sec_is_a_bonded_dev(connected_bda)) { + DEVICE_IOT_CONFIG_ADDR_INT_ADD_ONE(connected_bda, IOT_CONF_KEY_HFP_SLC_CONN_FAIL_COUNT); + } } break; case BTA_AG_CLOSE_EVT: { diff --git a/system/btif/src/btif_hh.cc b/system/btif/src/btif_hh.cc index 5e4d2077cb..8fc0beed06 100644 --- a/system/btif/src/btif_hh.cc +++ b/system/btif/src/btif_hh.cc @@ -937,11 +937,10 @@ void btif_hh_disconnected(const RawAddress& addr, tBT_TRANSPORT transport) { if (p_dev == nullptr) { return; } - if (com::android::bluetooth::flags::allow_switching_hid_and_hogp()) { - btif_hh_added_device_t* added_dev = btif_hh_find_added_dev(link_spec); - if (added_dev == nullptr || !added_dev->reconnect_allowed) { - return; - } + + btif_hh_added_device_t* added_dev = btif_hh_find_added_dev(link_spec); + if (added_dev == nullptr || !added_dev->reconnect_allowed) { + return; } log::debug("Rearm HoGP reconnection for {}", addr); diff --git a/system/btif/src/btif_profile_storage.cc b/system/btif/src/btif_profile_storage.cc index e6d671a369..fba4aaf5cc 100644 --- a/system/btif/src/btif_profile_storage.cc +++ b/system/btif/src/btif_profile_storage.cc @@ -646,6 +646,21 @@ void btif_storage_leaudio_update_handles_bin(const RawAddress& addr) { } } +/** Store GMAP information */ +void btif_storage_leaudio_update_gmap_bin(const RawAddress& addr) { + std::vector<uint8_t> gmap; + + if (LeAudioClient::GetGmapForStorage(addr, gmap)) { + do_in_jni_thread(Bind( + [](const RawAddress& bd_addr, std::vector<uint8_t> gmap) { + auto bdstr = bd_addr.ToString(); + btif_config_set_bin(bdstr, BTIF_STORAGE_KEY_LEAUDIO_GMAP_BIN, gmap.data(), + gmap.size()); + }, + addr, std::move(gmap))); + } +} + /** Store PACs information */ void btif_storage_leaudio_update_pacs_bin(const RawAddress& addr) { std::vector<uint8_t> sink_pacs; @@ -794,10 +809,16 @@ void btif_storage_load_bonded_leaudio() { btif_config_get_bin(name, BTIF_STORAGE_KEY_LEAUDIO_ASES_BIN, ases.data(), &buffer_size); } + buffer_size = btif_config_get_bin_length(name, BTIF_STORAGE_KEY_LEAUDIO_GMAP_BIN); + std::vector<uint8_t> gmap(buffer_size); + if (buffer_size > 0) { + btif_config_get_bin(name, BTIF_STORAGE_KEY_LEAUDIO_GMAP_BIN, gmap.data(), &buffer_size); + } + do_in_main_thread(Bind(&LeAudioClient::AddFromStorage, bd_addr, autoconnect, sink_audio_location, source_audio_location, sink_supported_context_type, source_supported_context_type, std::move(handles), std::move(sink_pacs), - std::move(source_pacs), std::move(ases))); + std::move(source_pacs), std::move(ases), std::move(gmap))); } } @@ -806,6 +827,7 @@ void btif_storage_leaudio_clear_service_data(const RawAddress& address) { btif_config_remove(bdstr, BTIF_STORAGE_KEY_LEAUDIO_HANDLES_BIN); btif_config_remove(bdstr, BTIF_STORAGE_KEY_LEAUDIO_SINK_PACS_BIN); btif_config_remove(bdstr, BTIF_STORAGE_KEY_LEAUDIO_ASES_BIN); + btif_config_remove(bdstr, BTIF_STORAGE_KEY_LEAUDIO_GMAP_BIN); } /** Remove the Le Audio device from storage */ diff --git a/system/btif/src/btif_sock_rfc.cc b/system/btif/src/btif_sock_rfc.cc index de2188c5d8..d23f012249 100644 --- a/system/btif/src/btif_sock_rfc.cc +++ b/system/btif/src/btif_sock_rfc.cc @@ -196,7 +196,7 @@ static rfc_slot_t* find_rfc_slot_by_pending_sdp(void) { static bool is_requesting_sdp(void) { for (size_t i = 0; i < ARRAY_SIZE(rfc_slots); ++i) { if (rfc_slots[i].id && rfc_slots[i].f.doing_sdp_request) { - log::info("slot id {} is doing sdp request", rfc_slots[i].id); + log::info("slot_id {} is doing sdp request", rfc_slots[i].id); return true; } } @@ -458,7 +458,7 @@ bt_status_t btsock_rfc_connect(const RawAddress* bd_addr, const Uuid* service_uu if (!service_uuid || service_uuid->IsEmpty()) { tBTA_JV_STATUS ret = BTA_JvRfcommConnect(slot->security, slot->scn, slot->addr, rfcomm_cback, - slot->id, RfcommCfgInfo{}); + slot->id, RfcommCfgInfo{}, slot->app_uid); if (ret != tBTA_JV_STATUS::SUCCESS) { log::error("unable to initiate RFCOMM connection. status:{}, scn:{}, bd_addr:{}", bta_jv_status_text(ret), slot->scn, slot->addr); @@ -467,7 +467,7 @@ bt_status_t btsock_rfc_connect(const RawAddress* bd_addr, const Uuid* service_uu } if (!send_app_scn(slot)) { - log::error("send_app_scn() failed, closing slot->id:{}", slot->id); + log::error("send_app_scn() failed, closing slot_id:{}", slot->id); cleanup_rfc_slot(slot); return BT_STATUS_SOCKET_ERROR; } @@ -536,7 +536,7 @@ static void cleanup_rfc_slot(rfc_slot_t* slot) { close(slot->fd); log::info( "disconnected from RFCOMM socket connections for device: {}, scn: {}, " - "app_uid: {}, id: {}, socket_id: {}", + "app_uid: {}, slot_id: {}, socket_id: {}", slot->addr, slot->scn, slot->app_uid, slot->id, slot->socket_id); btif_sock_connection_logger( slot->addr, slot->id, BTSOCK_RFCOMM, SOCKET_CONNECTION_STATE_DISCONNECTED, @@ -586,7 +586,7 @@ static bool send_app_scn(rfc_slot_t* slot) { // already sent, just return success. return true; } - log::debug("Sending scn for slot {}. bd_addr:{}", slot->id, slot->addr); + log::debug("Sending scn for slot_id {}. bd_addr:{}", slot->id, slot->addr); slot->scn_notified = true; return sock_send_all(slot->fd, (const uint8_t*)&slot->scn, sizeof(slot->scn)) == sizeof(slot->scn); @@ -615,10 +615,10 @@ static void on_cl_rfc_init(tBTA_JV_RFCOMM_CL_INIT* p_init, uint32_t id) { std::unique_lock<std::recursive_mutex> lock(slot_lock); rfc_slot_t* slot = find_rfc_slot_by_id(id); if (!slot) { - log::error("RFCOMM slot with id {} not found. p_init->status={}", id, + log::error("RFCOMM slot_id {} not found. p_init->status={}", id, bta_jv_status_text(p_init->status)); } else if (p_init->status != tBTA_JV_STATUS::SUCCESS) { - log::warn("INIT unsuccessful, status {}. Cleaning up slot with id {}", + log::warn("INIT unsuccessful, status {}. Cleaning up slot_id {}", bta_jv_status_text(p_init->status), slot->id); cleanup_rfc_slot(slot); } else { @@ -630,10 +630,10 @@ static void on_srv_rfc_listen_started(tBTA_JV_RFCOMM_START* p_start, uint32_t id std::unique_lock<std::recursive_mutex> lock(slot_lock); rfc_slot_t* slot = find_rfc_slot_by_id(id); if (!slot) { - log::error("RFCOMM slot with id {} not found", id); + log::error("RFCOMM slot_id {} not found", id); return; } else if (p_start->status != tBTA_JV_STATUS::SUCCESS) { - log::warn("START unsuccessful, status {}. Cleaning up slot with id {}", + log::warn("START unsuccessful, status {}. Cleaning up slot_id {}", bta_jv_status_text(p_start->status), slot->id); cleanup_rfc_slot(slot); return; @@ -702,7 +702,7 @@ static uint32_t on_srv_rfc_connect(tBTA_JV_RFCOMM_SRV_OPEN* p_open, uint32_t id) rfc_slot_t* accept_rs; rfc_slot_t* srv_rs = find_rfc_slot_by_id(id); if (!srv_rs) { - log::error("RFCOMM slot with id {} not found.", id); + log::error("RFCOMM slot_id {} not found.", id); return 0; } @@ -719,7 +719,7 @@ static uint32_t on_srv_rfc_connect(tBTA_JV_RFCOMM_SRV_OPEN* p_open, uint32_t id) log::info( "connected to RFCOMM socket connections for device: {}, scn: {}, " - "app_uid: {}, id: {}, socket_id: {}", + "app_uid: {}, slot_id: {}, socket_id: {}", accept_rs->addr, accept_rs->scn, accept_rs->app_uid, id, accept_rs->socket_id); btif_sock_connection_logger(accept_rs->addr, accept_rs->id, BTSOCK_RFCOMM, SOCKET_CONNECTION_STATE_CONNECTED, @@ -779,12 +779,12 @@ static void on_cli_rfc_connect(tBTA_JV_RFCOMM_OPEN* p_open, uint32_t id) { std::unique_lock<std::recursive_mutex> lock(slot_lock); rfc_slot_t* slot = find_rfc_slot_by_id(id); if (!slot) { - log::error("RFCOMM slot with id {} not found.", id); + log::error("RFCOMM slot_id {} not found.", id); return; } if (p_open->status != tBTA_JV_STATUS::SUCCESS) { - log::warn("CONNECT unsuccessful, status {}. Cleaning up slot with id {}", + log::warn("CONNECT unsuccessful, status {}. Cleaning up slot_id {}", bta_jv_status_text(p_open->status), slot->id); cleanup_rfc_slot(slot); return; @@ -906,7 +906,7 @@ static void on_rfc_close(tBTA_JV_RFCOMM_CLOSE* /* p_close */, uint32_t id) { static void on_rfc_write_done(tBTA_JV_RFCOMM_WRITE* p, uint32_t id) { if (p->status != tBTA_JV_STATUS::SUCCESS) { - log::error("error writing to RFCOMM socket with slot {}.", p->req_id); + log::error("error writing to RFCOMM socket, req_id:{}.", p->req_id); return; } @@ -915,7 +915,7 @@ static void on_rfc_write_done(tBTA_JV_RFCOMM_WRITE* p, uint32_t id) { rfc_slot_t* slot = find_rfc_slot_by_id(id); if (!slot) { - log::error("RFCOMM slot with id {} not found.", id); + log::error("RFCOMM slot_id {} not found.", id); return; } app_uid = slot->app_uid; @@ -931,7 +931,7 @@ static void on_rfc_outgoing_congest(tBTA_JV_RFCOMM_CONG* p, uint32_t id) { rfc_slot_t* slot = find_rfc_slot_by_id(id); if (!slot) { - log::error("RFCOMM slot with id {} not found.", id); + log::error("RFCOMM slot_id {} not found.", id); return; } @@ -946,39 +946,39 @@ static uint32_t rfcomm_cback(tBTA_JV_EVT event, tBTA_JV* p_data, uint32_t rfcomm switch (event) { case BTA_JV_RFCOMM_START_EVT: - log::info("handling {}, rfcomm_slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); + log::info("handling {}, slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); on_srv_rfc_listen_started(&p_data->rfc_start, rfcomm_slot_id); break; case BTA_JV_RFCOMM_CL_INIT_EVT: - log::info("handling {}, rfcomm_slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); + log::info("handling {}, slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); on_cl_rfc_init(&p_data->rfc_cl_init, rfcomm_slot_id); break; case BTA_JV_RFCOMM_OPEN_EVT: - log::info("handling {}, rfcomm_slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); + log::info("handling {}, slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); BTA_JvSetPmProfile(p_data->rfc_open.handle, BTA_JV_PM_ID_1, BTA_JV_CONN_OPEN); on_cli_rfc_connect(&p_data->rfc_open, rfcomm_slot_id); break; case BTA_JV_RFCOMM_SRV_OPEN_EVT: - log::info("handling {}, rfcomm_slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); + log::info("handling {}, slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); BTA_JvSetPmProfile(p_data->rfc_srv_open.handle, BTA_JV_PM_ALL, BTA_JV_CONN_OPEN); id = on_srv_rfc_connect(&p_data->rfc_srv_open, rfcomm_slot_id); break; case BTA_JV_RFCOMM_CLOSE_EVT: - log::info("handling {}, rfcomm_slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); + log::info("handling {}, slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); on_rfc_close(&p_data->rfc_close, rfcomm_slot_id); break; case BTA_JV_RFCOMM_WRITE_EVT: - log::verbose("handling {}, rfcomm_slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); + log::verbose("handling {}, slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); on_rfc_write_done(&p_data->rfc_write, rfcomm_slot_id); break; case BTA_JV_RFCOMM_CONG_EVT: - log::verbose("handling {}, rfcomm_slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); + log::verbose("handling {}, slot_id:{}", bta_jv_event_text(event), rfcomm_slot_id); on_rfc_outgoing_congest(&p_data->rfc_cong, rfcomm_slot_id); break; @@ -987,20 +987,20 @@ static uint32_t rfcomm_cback(tBTA_JV_EVT event, tBTA_JV* p_data, uint32_t rfcomm break; default: - log::warn("unhandled event {}, slot id: {}", bta_jv_event_text(event), rfcomm_slot_id); + log::warn("unhandled event {}, slot_id: {}", bta_jv_event_text(event), rfcomm_slot_id); break; } return id; } static void jv_dm_cback(tBTA_JV_EVT event, tBTA_JV* p_data, uint32_t id) { - log::info("handling event:{}, id:{}", bta_jv_event_text(event), id); + log::info("handling event:{}, slot_id:{}", bta_jv_event_text(event), id); switch (event) { case BTA_JV_GET_SCN_EVT: { std::unique_lock<std::recursive_mutex> lock(slot_lock); rfc_slot_t* rs = find_rfc_slot_by_id(id); if (!rs) { - log::error("RFCOMM slot with id {} not found. event:{}", id, bta_jv_event_text(event)); + log::error("RFCOMM slot with slot_id {} not found. event:{}", id, bta_jv_event_text(event)); break; } if (p_data->scn == 0) { @@ -1044,7 +1044,8 @@ static void jv_dm_cback(tBTA_JV_EVT event, tBTA_JV* p_data, uint32_t id) { } } // now start the rfcomm server after sdp & channel # assigned - BTA_JvRfcommStartServer(rs->security, rs->scn, MAX_RFC_SESSION, rfcomm_cback, rs->id, cfg); + BTA_JvRfcommStartServer(rs->security, rs->scn, MAX_RFC_SESSION, rfcomm_cback, rs->id, cfg, + rs->app_uid); } break; } @@ -1060,7 +1061,7 @@ static void jv_dm_cback(tBTA_JV_EVT event, tBTA_JV* p_data, uint32_t id) { rfc_slot_t* slot = find_rfc_slot_by_id(id); if (!slot) { - log::error("RFCOMM slot with id {} not found. event:{}", id, bta_jv_event_text(event)); + log::error("RFCOMM slot_id {} not found. event:{}", id, bta_jv_event_text(event)); break; } @@ -1085,7 +1086,7 @@ static void jv_dm_cback(tBTA_JV_EVT event, tBTA_JV* p_data, uint32_t id) { } // Start the rfcomm server after sdp & channel # assigned. BTA_JvRfcommStartServer(slot->security, slot->scn, MAX_RFC_SESSION, rfcomm_cback, slot->id, - cfg); + cfg, slot->app_uid); break; } @@ -1103,7 +1104,7 @@ static void jv_dm_cback(tBTA_JV_EVT event, tBTA_JV* p_data, uint32_t id) { } default: - log::debug("unhandled event:{}, slot id:{}", bta_jv_event_text(event), id); + log::debug("unhandled event:{}, slot_id:{}", bta_jv_event_text(event), id); break; } } @@ -1111,18 +1112,18 @@ static void jv_dm_cback(tBTA_JV_EVT event, tBTA_JV* p_data, uint32_t id) { static void handle_discovery_comp(tBTA_JV_STATUS status, int scn, uint32_t id) { rfc_slot_t* slot = find_rfc_slot_by_id(id); if (!slot) { - log::error("RFCOMM slot with id {} not found. event: BTA_JV_DISCOVERY_COMP_EVT", id); + log::error("RFCOMM slot_id {} not found. event: BTA_JV_DISCOVERY_COMP_EVT", id); return; } if (!slot->f.doing_sdp_request) { - log::error("SDP response returned but RFCOMM slot {} did not request SDP record.", id); + log::error("SDP response returned but RFCOMM slot_id {} did not request SDP record.", id); return; } if (status != tBTA_JV_STATUS::SUCCESS || !scn) { log::error( - "SDP service discovery completed for slot id: {} with the result " + "SDP service discovery completed for slot_id: {} with the result " "status: {}, scn: {}", id, bta_jv_status_text(status), scn); cleanup_rfc_slot(slot); @@ -1143,12 +1144,9 @@ static void handle_discovery_comp(tBTA_JV_STATUS status, int scn, uint32_t id) { } } - if (BTA_JvRfcommConnect(slot->security, scn, slot->addr, rfcomm_cback, slot->id, cfg) != - tBTA_JV_STATUS::SUCCESS) { - log::warn( - "BTA_JvRfcommConnect() returned BTA_JV_FAILURE for RFCOMM slot with " - "id: {}", - id); + if (BTA_JvRfcommConnect(slot->security, scn, slot->addr, rfcomm_cback, slot->id, cfg, + slot->app_uid) != tBTA_JV_STATUS::SUCCESS) { + log::warn("BTA_JvRfcommConnect() returned BTA_JV_FAILURE for RFCOMM slot_id:{}", id); cleanup_rfc_slot(slot); return; } @@ -1157,7 +1155,7 @@ static void handle_discovery_comp(tBTA_JV_STATUS status, int scn, uint32_t id) { slot->f.doing_sdp_request = false; if (!send_app_scn(slot)) { - log::warn("send_app_scn() failed, closing slot->id {}", slot->id); + log::warn("send_app_scn() failed, closing slot_id {}", slot->id); cleanup_rfc_slot(slot); return; } @@ -1232,7 +1230,7 @@ static bool flush_incoming_que_on_wr_signal(rfc_slot_t* slot) { static bool btsock_rfc_read_signaled_on_connected_socket(int /* fd */, int flags, uint32_t /* id */, rfc_slot_t* slot) { if (!slot->f.connected) { - log::error("socket signaled for read while disconnected, slot: {}, channel: {}", slot->id, + log::error("socket signaled for read while disconnected, slot_id: {}, channel: {}", slot->id, slot->scn); return false; } @@ -1259,7 +1257,7 @@ static bool btsock_rfc_read_signaled_on_listen_socket(int fd, int /* flags */, u return false; } slot->is_accepting = accept_signal.is_accepting; - log::info("Server socket: {}, is_accepting: {}", slot->id, slot->is_accepting); + log::info("Server socket slot_id: {}, is_accepting: {}", slot->id, slot->is_accepting); } return true; } @@ -1269,7 +1267,7 @@ static void btsock_rfc_signaled_flagged(int fd, int flags, uint32_t id) { std::unique_lock<std::recursive_mutex> lock(slot_lock); rfc_slot_t* slot = find_rfc_slot_by_id(id); if (!slot) { - log::warn("RFCOMM slot with id {} not found.", id); + log::warn("RFCOMM slot_id {} not found.", id); return; } @@ -1293,7 +1291,7 @@ static void btsock_rfc_signaled_flagged(int fd, int flags, uint32_t id) { if (!slot->f.connected || !flush_incoming_que_on_wr_signal(slot)) { log::error( "socket signaled for write while disconnected (or write failure), " - "slot: {}, channel: {}", + "slot_id: {}, channel: {}", slot->id, slot->scn); need_close = true; } @@ -1334,7 +1332,7 @@ void btsock_rfc_signaled(int fd, int flags, uint32_t id) { BTA_JvRfcommWrite(slot->rfc_handle, slot->id); } } else { - log::error("socket signaled for read while disconnected, slot: {}, channel: {}", slot->id, + log::error("socket signaled for read while disconnected, slot_id: {}, channel: {}", slot->id, slot->scn); need_close = true; } @@ -1345,7 +1343,7 @@ void btsock_rfc_signaled(int fd, int flags, uint32_t id) { if (!slot->f.connected || !flush_incoming_que_on_wr_signal(slot)) { log::error( "socket signaled for write while disconnected (or write failure), " - "slot: {}, channel: {}", + "slot_id: {}, channel: {}", slot->id, slot->scn); need_close = true; } @@ -1371,7 +1369,7 @@ int bta_co_rfc_data_incoming(uint32_t id, BT_HDR* p_buf) { std::unique_lock<std::recursive_mutex> lock(slot_lock); rfc_slot_t* slot = find_rfc_slot_by_id(id); if (!slot) { - log::error("RFCOMM slot with id {} not found.", id); + log::error("RFCOMM slot_id {} not found.", id); return 0; } @@ -1411,7 +1409,7 @@ int bta_co_rfc_data_outgoing_size(uint32_t id, int* size) { std::unique_lock<std::recursive_mutex> lock(slot_lock); rfc_slot_t* slot = find_rfc_slot_by_id(id); if (!slot) { - log::error("RFCOMM slot with id {} not found.", id); + log::error("RFCOMM slot_id {} not found.", id); return false; } @@ -1429,7 +1427,7 @@ int bta_co_rfc_data_outgoing(uint32_t id, uint8_t* buf, uint16_t size) { std::unique_lock<std::recursive_mutex> lock(slot_lock); rfc_slot_t* slot = find_rfc_slot_by_id(id); if (!slot) { - log::error("RFCOMM slot with id {} not found.", id); + log::error("RFCOMM slot_id {} not found.", id); return false; } diff --git a/system/btif/src/btif_storage.cc b/system/btif/src/btif_storage.cc index e51756483c..4704362f2c 100644 --- a/system/btif/src/btif_storage.cc +++ b/system/btif/src/btif_storage.cc @@ -47,6 +47,7 @@ #include "btif/include/btif_api.h" #include "btif/include/btif_config.h" +#include "btif/include/btif_dm.h" #include "btif/include/btif_util.h" #include "btif/include/core_callbacks.h" #include "btif/include/stack_manager_t.h" @@ -179,15 +180,18 @@ static bool prop2cfg(const RawAddress* remote_bd_addr, bt_property_t* prop) { case BT_PROPERTY_TYPE_OF_DEVICE: btif_config_set_int(bdstr, BTIF_STORAGE_KEY_DEV_TYPE, *reinterpret_cast<int*>(prop->val)); break; - case BT_PROPERTY_UUIDS: { + case BT_PROPERTY_UUIDS: + case BT_PROPERTY_UUIDS_LE: { std::string val; size_t cnt = (prop->len) / sizeof(Uuid); for (size_t i = 0; i < cnt; i++) { val += (reinterpret_cast<Uuid*>(prop->val) + i)->ToString() + " "; } - btif_config_set_str(bdstr, BTIF_STORAGE_KEY_REMOTE_SERVICE, val); - break; - } + std::string key = (prop->type == BT_PROPERTY_UUIDS_LE) ? BTIF_STORAGE_KEY_REMOTE_SERVICE_LE + : BTIF_STORAGE_KEY_REMOTE_SERVICE; + btif_config_set_str(bdstr, key, val); + } break; + case BT_PROPERTY_REMOTE_VERSION_INFO: { bt_remote_version_t* info = reinterpret_cast<bt_remote_version_t*>(prop->val); @@ -300,10 +304,15 @@ static bool cfg2prop(const RawAddress* remote_bd_addr, bt_property_t* prop) { reinterpret_cast<int*>(prop->val)); } break; - case BT_PROPERTY_UUIDS: { + case BT_PROPERTY_UUIDS: + case BT_PROPERTY_UUIDS_LE: { char value[1280]; int size = sizeof(value); - if (btif_config_get_str(bdstr, BTIF_STORAGE_KEY_REMOTE_SERVICE, value, &size)) { + + std::string key = (prop->type == BT_PROPERTY_UUIDS_LE) ? BTIF_STORAGE_KEY_REMOTE_SERVICE_LE + : BTIF_STORAGE_KEY_REMOTE_SERVICE; + + if (btif_config_get_str(bdstr, key, value, &size)) { Uuid* p_uuid = reinterpret_cast<Uuid*>(prop->val); size_t num_uuids = btif_split_uuids_string(value, p_uuid, BT_MAX_NUM_UUIDS); prop->len = num_uuids * sizeof(Uuid); @@ -938,13 +947,14 @@ bt_status_t btif_storage_load_bonded_devices(void) { uint32_t i = 0; bt_property_t adapter_props[6]; uint32_t num_props = 0; - bt_property_t remote_properties[10]; + bt_property_t remote_properties[11]; RawAddress addr; bt_bdname_t name, alias, model_name; bt_scan_mode_t mode; uint32_t disc_timeout; Uuid local_uuids[BT_MAX_NUM_UUIDS]; Uuid remote_uuids[BT_MAX_NUM_UUIDS]; + Uuid remote_uuids_le[BT_MAX_NUM_UUIDS]; bt_status_t status; remove_devices_with_sample_ltk(); @@ -1026,10 +1036,16 @@ bt_status_t btif_storage_load_bonded_devices(void) { sizeof(devtype), &remote_properties[num_props]); num_props++; - btif_storage_get_remote_prop(p_remote_addr, BT_PROPERTY_UUIDS, remote_uuids, + btif_storage_get_remote_prop(p_remote_addr, BT_PROPERTY_UUIDS, &remote_uuids, sizeof(remote_uuids), &remote_properties[num_props]); num_props++; + if (com::android::bluetooth::flags::separate_service_storage()) { + btif_storage_get_remote_prop(p_remote_addr, BT_PROPERTY_UUIDS_LE, &remote_uuids_le, + sizeof(remote_uuids_le), &remote_properties[num_props]); + num_props++; + } + // Floss needs appearance for metrics purposes uint16_t appearance = 0; if (btif_storage_get_remote_prop(p_remote_addr, BT_PROPERTY_APPEARANCE, &appearance, @@ -1438,6 +1454,48 @@ void btif_storage_remove_gatt_cl_db_hash(const RawAddress& bd_addr) { bd_addr)); } +// TODO(b/369381361) Remove this function after all devices are migrated +void btif_storage_migrate_services() { + for (const auto& mac_address : btif_config_get_paired_devices()) { + auto addr_str = mac_address.ToString(); + + int device_type = BT_DEVICE_TYPE_UNKNOWN; + btif_config_get_int(addr_str, BTIF_STORAGE_KEY_DEV_TYPE, &device_type); + + if ((device_type == BT_DEVICE_TYPE_BREDR) || + btif_config_exist(addr_str, BTIF_STORAGE_KEY_REMOTE_SERVICE_LE)) { + /* Classic only, or already migrated entries don't need migration */ + continue; + } + + bt_property_t remote_uuids_prop; + Uuid remote_uuids[BT_MAX_NUM_UUIDS]; + BTIF_STORAGE_FILL_PROPERTY(&remote_uuids_prop, BT_PROPERTY_UUIDS, sizeof(remote_uuids), + remote_uuids); + btif_storage_get_remote_device_property(&mac_address, &remote_uuids_prop); + + log::info("Will migrate Services => ServicesLe for {}", mac_address.ToStringForLogging()); + + std::vector<uint8_t> property_value; + for (auto& uuid : remote_uuids) { + if (!btif_is_interesting_le_service(uuid)) { + continue; + } + + log::info("interesting LE service: {}", uuid); + auto uuid_128bit = uuid.To128BitBE(); + property_value.insert(property_value.end(), uuid_128bit.begin(), uuid_128bit.end()); + } + + bt_property_t le_uuids_prop{BT_PROPERTY_UUIDS_LE, static_cast<int>(property_value.size()), + (void*)property_value.data()}; + + /* Write LE services to storage */ + btif_storage_set_remote_device_property(&mac_address, &le_uuids_prop); + log::info("Migration finished for {}", mac_address.ToStringForLogging()); + } +} + void btif_debug_linkkey_type_dump(int fd) { dprintf(fd, "\nLink Key Types:\n"); for (const auto& bd_addr : btif_config_get_paired_devices()) { diff --git a/system/btif/src/btif_util.cc b/system/btif/src/btif_util.cc index 85c0069eaa..c6cf544d4a 100644 --- a/system/btif/src/btif_util.cc +++ b/system/btif/src/btif_util.cc @@ -129,6 +129,7 @@ std::string dump_property_type(bt_property_type_t type) { CASE_RETURN_STRING(BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT); CASE_RETURN_STRING(BT_PROPERTY_ADAPTER_BONDED_DEVICES); CASE_RETURN_STRING(BT_PROPERTY_REMOTE_FRIENDLY_NAME); + CASE_RETURN_STRING(BT_PROPERTY_UUIDS_LE); default: RETURN_UNKNOWN_TYPE_STRING(bt_property_type_t, type); } diff --git a/system/btif/src/stack_manager.cc b/system/btif/src/stack_manager.cc index 288cbfcdf3..4ba403586e 100644 --- a/system/btif/src/stack_manager.cc +++ b/system/btif/src/stack_manager.cc @@ -102,9 +102,6 @@ static_assert(BTA_HH_INCLUDED, // TODO(b/369381361) Enfore -Wmissing-prototypes #pragma GCC diagnostic ignored "-Wmissing-prototypes" -void BTA_dm_on_hw_on(); -void BTA_dm_on_hw_off(); - using bluetooth::common::MessageLoopThread; using bluetooth::log::error; using bluetooth::log::fatal; diff --git a/system/btif/test/btif_core_test.cc b/system/btif/test/btif_core_test.cc index 66881a92d8..1bffc9ce3a 100644 --- a/system/btif/test/btif_core_test.cc +++ b/system/btif/test/btif_core_test.cc @@ -320,6 +320,7 @@ TEST_F(BtifUtilsTest, dump_property_type) { "BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT"), std::make_pair(BT_PROPERTY_ADAPTER_BONDED_DEVICES, "BT_PROPERTY_ADAPTER_BONDED_DEVICES"), std::make_pair(BT_PROPERTY_REMOTE_FRIENDLY_NAME, "BT_PROPERTY_REMOTE_FRIENDLY_NAME"), + std::make_pair(BT_PROPERTY_UUIDS_LE, "BT_PROPERTY_UUIDS_LE"), }; for (const auto& type : types) { EXPECT_TRUE(dump_property_type(type.first).starts_with(type.second)); diff --git a/system/btif/test/btif_dm_test.cc b/system/btif/test/btif_dm_test.cc index 5d7595b1c5..068740472f 100644 --- a/system/btif/test/btif_dm_test.cc +++ b/system/btif/test/btif_dm_test.cc @@ -23,8 +23,9 @@ #include <memory> #include "bta/include/bta_api_data_types.h" -#include "btif/include/btif_dm.h" #include "btif/include/mock_core_callbacks.h" +#include "main/shim/entry.h" +#include "main/shim/shim.h" #include "main/shim/stack.h" #include "module.h" #include "stack/include/bt_dev_class.h" @@ -116,21 +117,47 @@ TEST_F(BtifDmWithUidTest, bta_energy_info_cb__with_uid) { ASSERT_TRUE(invoke_energy_info_cb_entered); } +// Mock implementation for GetStorage() +static bluetooth::storage::StorageModule* s_StorageModule = nullptr; +bluetooth::storage::StorageModule* bluetooth::shim::GetStorage() { return s_StorageModule; } + +bluetooth::os::Handler* bluetooth::shim::GetGdShimHandler() { return nullptr; } +bluetooth::hci::LeAdvertisingManager* bluetooth::shim::GetAdvertising() { return nullptr; } +bluetooth::hci::ControllerInterface* bluetooth::shim::GetController() { return nullptr; } +bluetooth::hci::HciInterface* bluetooth::shim::GetHciLayer() { return nullptr; } +bluetooth::hci::RemoteNameRequestModule* bluetooth::shim::GetRemoteNameRequest() { return nullptr; } +bluetooth::hci::LeScanningManager* bluetooth::shim::GetScanning() { return nullptr; } +bluetooth::hci::DistanceMeasurementManager* bluetooth::shim::GetDistanceMeasurementManager() { + return nullptr; +} +bluetooth::hal::SnoopLogger* bluetooth::shim::GetSnoopLogger() { return nullptr; } +bluetooth::lpp::LppOffloadInterface* bluetooth::shim::GetLppOffloadManager() { return nullptr; } +bluetooth::hci::AclManager* bluetooth::shim::GetAclManager() { return nullptr; } +bluetooth::metrics::CounterMetrics* bluetooth::shim::GetCounterMetrics() { return nullptr; } +bluetooth::hci::MsftExtensionManager* bluetooth::shim::GetMsftExtensionManager() { return nullptr; } + +bool bluetooth::shim::is_gd_stack_started_up() { return s_StorageModule != nullptr; } + class BtifDmWithStackTest : public BtifDmTest { protected: void SetUp() override { BtifDmTest::SetUp(); - modules_.add<bluetooth::storage::StorageModule>(); - bluetooth::shim::Stack::GetInstance()->StartModuleStack( - &modules_, - new bluetooth::os::Thread("gd_stack_thread", bluetooth::os::Thread::Priority::NORMAL)); + thread_ = new bluetooth::os::Thread("gd_stack_thread", bluetooth::os::Thread::Priority::NORMAL); + storage_module_ = new bluetooth::storage::StorageModule(new bluetooth::os::Handler(thread_)); + storage_module_->Start(); + s_StorageModule = storage_module_; } void TearDown() override { - bluetooth::shim::Stack::GetInstance()->Stop(); + storage_module_->Stop(); + s_StorageModule = nullptr; + delete storage_module_; + delete thread_; BtifDmTest::TearDown(); } - bluetooth::ModuleList modules_; + + bluetooth::os::Thread* thread_; + bluetooth::storage::StorageModule* storage_module_; }; TEST_F_WITH_FLAGS(BtifDmWithStackTest, btif_dm_search_services_evt__BTA_DM_NAME_READ_EVT) { diff --git a/system/common/Android.bp b/system/common/Android.bp index f3f2a619ca..6fe25539d9 100644 --- a/system/common/Android.bp +++ b/system/common/Android.bp @@ -49,9 +49,7 @@ cc_library_static { "libcrypto", "libcutils", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", header_libs: ["libbluetooth_headers"], static_libs: [ diff --git a/system/device/Android.bp b/system/device/Android.bp index eb71e3f414..d5176640c0 100644 --- a/system/device/Android.bp +++ b/system/device/Android.bp @@ -26,9 +26,7 @@ cc_library_static { "src/esco_parameters.cc", "src/interop.cc", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", header_libs: ["libbluetooth_headers"], static_libs: [ @@ -97,17 +95,14 @@ cc_test { "liblog", ], static_libs: [ - "bluetooth_flags_c_lib", "libbluetooth-types", "libbluetooth_log", "libbt-platform-protos-lite", "libbtcore", "libbtdevice", "libchrome", - "libflagtest", "libgmock", "libosi", - "server_configurable_flags", ], header_libs: ["libbluetooth_headers"], } diff --git a/system/device/src/device_iot_config.cc b/system/device/src/device_iot_config.cc index 4513e6840a..f247eff0e6 100644 --- a/system/device/src/device_iot_config.cc +++ b/system/device/src/device_iot_config.cc @@ -21,7 +21,6 @@ #include "device/include/device_iot_config.h" #include <bluetooth/log.h> -#include <com_android_bluetooth_flags.h> #include <ctype.h> #include <string.h> #include <time.h> @@ -49,10 +48,6 @@ alarm_t* config_timer; using namespace bluetooth; bool device_iot_config_has_section(const std::string& section) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); std::unique_lock<std::mutex> lock(config_lock); @@ -60,10 +55,6 @@ bool device_iot_config_has_section(const std::string& section) { } bool device_iot_config_exist(const std::string& section, const std::string& key) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); std::unique_lock<std::mutex> lock(config_lock); @@ -71,10 +62,6 @@ bool device_iot_config_exist(const std::string& section, const std::string& key) } bool device_iot_config_get_int(const std::string& section, const std::string& key, int& value) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); std::unique_lock<std::mutex> lock(config_lock); @@ -87,10 +74,6 @@ bool device_iot_config_get_int(const std::string& section, const std::string& ke } bool device_iot_config_set_int(const std::string& section, const std::string& key, int value) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); std::unique_lock<std::mutex> lock(config_lock); @@ -107,10 +90,6 @@ bool device_iot_config_set_int(const std::string& section, const std::string& ke } bool device_iot_config_int_add_one(const std::string& section, const std::string& key) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); int result = 0; @@ -128,10 +107,6 @@ bool device_iot_config_int_add_one(const std::string& section, const std::string } bool device_iot_config_get_hex(const std::string& section, const std::string& key, int& value) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); std::unique_lock<std::mutex> lock(config_lock); @@ -159,10 +134,6 @@ bool device_iot_config_get_hex(const std::string& section, const std::string& ke bool device_iot_config_set_hex(const std::string& section, const std::string& key, int value, int byte_num) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); char value_str[32] = {0}; @@ -189,10 +160,6 @@ bool device_iot_config_set_hex(const std::string& section, const std::string& ke bool device_iot_config_set_hex_if_greater(const std::string& section, const std::string& key, int value, int byte_num) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - int stored_value = 0; bool ret = device_iot_config_get_hex(section, key, stored_value); if (ret && stored_value >= value) { @@ -204,10 +171,6 @@ bool device_iot_config_set_hex_if_greater(const std::string& section, const std: bool device_iot_config_get_str(const std::string& section, const std::string& key, char* value, int* size_bytes) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); log::assert_that(value != NULL, "assert failed: value != NULL"); log::assert_that(size_bytes != NULL, "assert failed: size_bytes != NULL"); @@ -227,10 +190,6 @@ bool device_iot_config_get_str(const std::string& section, const std::string& ke bool device_iot_config_set_str(const std::string& section, const std::string& key, const std::string& value) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); std::unique_lock<std::mutex> lock(config_lock); @@ -246,10 +205,6 @@ bool device_iot_config_set_str(const std::string& section, const std::string& ke bool device_iot_config_get_bin(const std::string& section, const std::string& key, uint8_t* value, size_t* length) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); log::assert_that(value != NULL, "assert failed: value != NULL"); log::assert_that(length != NULL, "assert failed: length != NULL"); @@ -293,10 +248,6 @@ bool device_iot_config_get_bin(const std::string& section, const std::string& ke } size_t device_iot_config_get_bin_length(const std::string& section, const std::string& key) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return 0; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); std::unique_lock<std::mutex> lock(config_lock); @@ -312,10 +263,6 @@ size_t device_iot_config_get_bin_length(const std::string& section, const std::s bool device_iot_config_set_bin(const std::string& section, const std::string& key, const uint8_t* value, size_t length) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - const char* lookup = "0123456789abcdef"; log::assert_that(config != NULL, "assert failed: config != NULL"); @@ -350,10 +297,6 @@ bool device_iot_config_set_bin(const std::string& section, const std::string& ke } bool device_iot_config_remove(const std::string& section, const std::string& key) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return false; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); std::unique_lock<std::mutex> lock(config_lock); @@ -361,10 +304,6 @@ bool device_iot_config_remove(const std::string& section, const std::string& key } void device_iot_config_flush(void) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); log::assert_that(config_timer != NULL, "assert failed: config_timer != NULL"); @@ -376,10 +315,6 @@ void device_iot_config_flush(void) { } bool device_iot_config_clear(void) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return true; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); log::assert_that(config_timer != NULL, "assert failed: config_timer != NULL"); @@ -400,10 +335,6 @@ bool device_iot_config_clear(void) { } void device_debug_iot_config_dump(int fd) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return; - } - dprintf(fd, "\nBluetooth Iot Config:\n"); dprintf(fd, " Config Source: "); diff --git a/system/device/src/device_iot_config_int.cc b/system/device/src/device_iot_config_int.cc index 33b5f8106d..fa65e13096 100644 --- a/system/device/src/device_iot_config_int.cc +++ b/system/device/src/device_iot_config_int.cc @@ -21,7 +21,6 @@ #include "device_iot_config_int.h" #include <bluetooth/log.h> -#include <com_android_bluetooth_flags.h> #include <string.h> #include <time.h> #include <unistd.h> @@ -184,10 +183,6 @@ EXPORT_SYMBOL module_t device_iot_config_module = {.name = DEVICE_IOT_CONFIG_MOD .clean_up = device_iot_config_module_clean_up}; void device_iot_config_write(uint16_t event, UNUSED_ATTR char* p_param) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); log::assert_that(config_timer != NULL, "assert failed: config_timer != NULL"); @@ -223,10 +218,6 @@ bool device_iot_config_has_key_value(const std::string& section, const std::stri } void device_iot_config_save_async(void) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return; - } - log::assert_that(config != NULL, "assert failed: config != NULL"); log::assert_that(config_timer != NULL, "assert failed: config_timer != NULL"); @@ -235,10 +226,6 @@ void device_iot_config_save_async(void) { } int device_iot_config_get_device_num(const config_t& conf) { - if (!com::android::bluetooth::flags::device_iot_config_logging()) { - return 0; - } - int devices = 0; for (const auto& entry : conf.sections) { diff --git a/system/device/src/interop.cc b/system/device/src/interop.cc index 99c7941aa5..354a9e1a65 100644 --- a/system/device/src/interop.cc +++ b/system/device/src/interop.cc @@ -55,7 +55,7 @@ using namespace bluetooth; #ifdef __ANDROID__ static const char* INTEROP_DYNAMIC_FILE_PATH = "/data/misc/bluedroid/interop_database_dynamic.conf"; static const char* INTEROP_STATIC_FILE_PATH = - "/apex/com.android.btservices/etc/bluetooth/interop_database.conf"; + "/apex/com.android.bt/etc/bluetooth/interop_database.conf"; #elif TARGET_FLOSS #include <base/files/file_util.h> diff --git a/system/device/test/device_iot_config_test.cc b/system/device/test/device_iot_config_test.cc index 4693c741d5..958a76bd7a 100644 --- a/system/device/test/device_iot_config_test.cc +++ b/system/device/test/device_iot_config_test.cc @@ -18,8 +18,6 @@ #include "device/include/device_iot_config.h" -#include <com_android_bluetooth_flags.h> -#include <flag_macros.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <sys/mman.h> @@ -34,8 +32,6 @@ #include "test/mock/mock_osi_future.h" #include "test/mock/mock_osi_properties.h" -#define TEST_BT com::android::bluetooth::flags - using namespace bluetooth; using namespace testing; @@ -132,8 +128,7 @@ protected: } }; -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_is_factory_reset, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, test_device_iot_config_module_init_is_factory_reset) { bool is_factory_reset = false; config_t* config_new_return_value = NULL; config_t* config_new_empty_return_value = NULL; @@ -192,8 +187,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_ test::mock::osi_config::config_new_empty.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_no_config, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, test_device_iot_config_module_init_no_config) { test::mock::osi_config::config_new.body = [&](const char* /*filename*/) { return std::unique_ptr<config_t>(nullptr); }; @@ -217,8 +211,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_ test::mock::osi_config::config_new_empty.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_original, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, test_device_iot_config_module_init_original) { std::string enable_logging_property_get_value; std::string factory_reset_property_get_value; config_t* config_new_return_value = NULL; @@ -268,8 +261,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_ test::mock::osi_config::config_new_empty.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_backup, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, test_device_iot_config_module_init_backup) { std::string enable_logging_property_get_value; std::string factory_reset_property_get_value; config_t* config_new_return_value = NULL; @@ -322,8 +314,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_ test::mock::osi_config::config_new_empty.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_new_file, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, test_device_iot_config_module_init_new_file) { std::string enable_logging_property_get_value; std::string factory_reset_property_get_value; config_t* config_new_return_value = NULL; @@ -367,8 +358,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_ test::mock::osi_config::config_new_empty.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_version_invalid, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, test_device_iot_config_module_init_version_invalid) { std::string enable_logging_property_get_value; std::string factory_reset_property_get_value; config_t* config_new_return_value = NULL; @@ -418,9 +408,8 @@ TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_ test::mock::osi_config::config_new_empty.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, - test_device_iot_config_module_init_version_new_config_new_empty_success, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, + test_device_iot_config_module_init_version_new_config_new_empty_success) { std::string enable_logging_property_get_value; std::string factory_reset_property_get_value; config_t* config_new_return_value = NULL; @@ -495,9 +484,8 @@ TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test::mock::osi_config::config_new_empty.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, - test_device_iot_config_module_init_version_new_config_new_empty_fail, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, + test_device_iot_config_module_init_version_new_config_new_empty_fail) { std::string enable_logging_property_get_value; std::string factory_reset_property_get_value; config_t* config_new_return_value = NULL; @@ -572,9 +560,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test::mock::osi_config::config_new_empty.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, - test_device_iot_config_module_init_original_timestamp_null, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, test_device_iot_config_module_init_original_timestamp_null) { std::string enable_logging_property_get_value; std::string factory_reset_property_get_value; config_t* config_new_return_value = NULL; @@ -622,8 +608,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test::mock::osi_config::config_new_empty.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_alarm_new_fail, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, test_device_iot_config_module_init_alarm_new_fail) { std::string enable_logging_property_get_value; std::string factory_reset_property_get_value; config_t* config_new_return_value = NULL; @@ -675,8 +660,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_init_ test::mock::osi_config::config_new_empty.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_start_up, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, test_device_iot_config_module_start_up) { std::string enable_logging_property_get_value; std::string factory_reset_property_get_value; @@ -699,8 +683,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_start test::mock::osi_config::config_new_empty.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_shutdown, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, test_device_iot_config_module_shutdown) { bool return_value; std::string enable_logging_property_get_value; std::string factory_reset_property_get_value; @@ -745,8 +728,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_shutd test::mock::osi_alarm::alarm_is_scheduled.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigModuleTest, test_device_iot_config_module_clean_up, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigModuleTest, test_device_iot_config_module_clean_up) { bool return_value; std::string enable_logging_property_get_value; std::string factory_reset_property_get_value; @@ -867,8 +849,7 @@ protected: } }; -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_sections_sort_by_entry_key, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_sections_sort_by_entry_key) { { config_t conf; device_iot_config_sections_sort_by_entry_key(conf, NULL); @@ -925,8 +906,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_sections_sort_by_e } } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_has_section, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_has_section) { std::string actual_section, expected_section = "abc"; bool return_value = false; @@ -958,8 +938,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_has_section, test::mock::osi_config::config_has_section.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_exist, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_exist) { std::string actual_section, actual_key, expected_section = "abc", expected_key = "def"; bool return_value = false; @@ -993,8 +972,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_exist, test::mock::osi_config::config_has_key.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_has_key_value, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_has_key_value) { std::string actual_section, actual_key, expected_section = "abc", expected_key = "def"; std::string expected_value_str = "xyz", actual_value_str; const std::string* actual_def_value = NULL; @@ -1080,8 +1058,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_has_key_value, test::mock::osi_config::config_get_string.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_get_int, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_get_int) { std::string actual_section, actual_key, expected_section = "abc", expected_key = "def"; bool return_value = false; int int_value = 0, new_value = 0xff; @@ -1126,8 +1103,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_get_int, test::mock::osi_config::config_get_int.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_get_int, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_addr_get_int) { const RawAddress peer_addr{}; std::string actual_section, actual_key, expected_section = "00:00:00:00:00:00", expected_key = "def"; @@ -1174,8 +1150,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_get_int, test::mock::osi_config::config_get_int.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_set_int, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_set_int) { std::string actual_section, actual_key, expected_section = "abc", expected_key = "def"; std::string string_return_value = "123456789"; std::string old_string_value = string_return_value; @@ -1229,8 +1204,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_set_int, test::mock::osi_alarm::alarm_set.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_set_int, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_addr_set_int) { const RawAddress peer_addr{}; std::string actual_key, expected_key = "def"; std::string actual_section, expected_section = "00:00:00:00:00:00"; @@ -1286,8 +1260,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_set_int, test::mock::osi_alarm::alarm_set.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_int_add_one, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_int_add_one) { std::string actual_section, actual_key, expected_section = "abc", expected_key = "def"; int int_value = 0, get_default_value, set_value; @@ -1393,8 +1366,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_int_add_one, test::mock::osi_alarm::alarm_set.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_int_add_one, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_addr_int_add_one) { const RawAddress peer_addr{}; std::string actual_section, actual_key, expected_section = "00:00:00:00:00:00", expected_key = "def"; @@ -1501,8 +1473,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_int_add_one, test::mock::osi_alarm::alarm_set.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_get_hex, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_get_hex) { std::string actual_section, actual_key, expected_section = "00:00:00:00:00:00", expected_key = "def"; int int_value = 0; @@ -1661,8 +1632,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_get_hex, test::mock::osi_config::config_get_string.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_get_hex, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_addr_get_hex) { const RawAddress peer_addr{}; std::string actual_section, actual_key, expected_section = "00:00:00:00:00:00", expected_key = "def"; @@ -1818,8 +1788,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_get_hex, test::mock::osi_config::config_get_string.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_set_hex, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_set_hex) { std::string actual_key, expected_key = "def"; std::string actual_section, expected_section = "00:00:00:00:00:00"; std::string string_return_value; @@ -1966,8 +1935,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_set_hex, test::mock::osi_alarm::alarm_set.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_set_hex, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_addr_set_hex) { const RawAddress peer_addr{}; std::string actual_key, expected_key = "def"; std::string actual_section, expected_section = "00:00:00:00:00:00"; @@ -2119,8 +2087,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_set_hex, test::mock::osi_alarm::alarm_set.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_set_hex_if_greater, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_addr_set_hex_if_greater) { const RawAddress peer_addr{}; std::string actual_key, expected_key = "def"; std::string actual_section, expected_section = "00:00:00:00:00:00"; @@ -2205,8 +2172,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_set_hex_if_gr test::mock::osi_alarm::alarm_set.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_get_str, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_get_str) { std::string actual_section, actual_key, expected_section = "abc", expected_key = "def"; std::string actual_value_str; const std::string* actual_def_value = NULL; @@ -2260,8 +2226,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_get_str, test::mock::osi_config::config_get_string.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_set_str, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_set_str) { std::string actual_key, expected_key = "def"; std::string actual_section, expected_section = "00:00:00:00:00:00"; std::string input_value; @@ -2324,8 +2289,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_set_str, test::mock::osi_alarm::alarm_set.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_set_str, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_addr_set_str) { const RawAddress peer_addr{}; std::string actual_key, expected_key = "def"; std::string actual_section, expected_section = "00:00:00:00:00:00"; @@ -2389,8 +2353,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_set_str, test::mock::osi_alarm::alarm_set.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_get_bin, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_get_bin) { std::string actual_section, actual_key, expected_section = "abc", expected_key = "def"; std::string actual_value_str; const std::string* actual_def_value = NULL; @@ -2496,8 +2459,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_get_bin, test::mock::osi_config::config_get_string.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_get_bin_length, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_get_bin_length) { std::string actual_section, actual_key, expected_section = "abc", expected_key = "def"; std::string actual_value_str; const std::string* actual_def_value = NULL; @@ -2573,8 +2535,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_get_bin_length, test::mock::osi_config::config_get_string.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_set_bin, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_set_bin) { std::string actual_key, expected_key = "def"; std::string actual_section, expected_section = "00:00:00:00:00:00"; std::string string_return_value; @@ -2680,8 +2641,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_set_bin, test::mock::osi_alarm::alarm_set.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_set_bin, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_addr_set_bin) { const RawAddress peer_addr{}; std::string actual_key, expected_key = "def"; std::string actual_section, expected_section = "00:00:00:00:00:00"; @@ -2788,8 +2748,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_addr_set_bin, test::mock::osi_alarm::alarm_set.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_remove, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_remove) { std::string actual_key, expected_key = "def"; std::string actual_section, expected_section = "00:00:00:00:00:00"; bool return_value; @@ -2828,8 +2787,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_remove, test::mock::osi_config::config_remove_key.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_save_async, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_save_async) { { reset_mock_function_count_map(); @@ -2839,8 +2797,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_save_async, } } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_flush, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_flush) { bool return_value; test::mock::osi_alarm::alarm_is_scheduled.body = [&](const alarm_t* /*alarm*/) -> bool { @@ -2876,8 +2833,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_flush, test::mock::osi_alarm::alarm_is_scheduled.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_clear, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_clear) { config_t* config_new_empty_return_value; bool config_save_return_value; @@ -2935,8 +2891,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_clear, test::mock::osi_config::config_save.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_timer_save_cb, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_timer_save_cb) { { reset_mock_function_count_map(); @@ -2946,8 +2901,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_timer_save_cb, } } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_set_modified_time, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_set_modified_time) { { reset_mock_function_count_map(); @@ -2957,8 +2911,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_set_modified_time, } } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_get_device_num, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_get_device_num) { { config_t config; auto num = device_iot_config_get_device_num(config); @@ -2980,8 +2933,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_get_device_num, } } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_restrict_device_num, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_restrict_device_num) { section_t section = {.name = "00:01:02:03:04:05"}; { @@ -3032,8 +2984,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_restrict_device_nu } } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_compare_key, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_compare_key) { { entry_t first = { @@ -3095,8 +3046,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_compare_key, } } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_write, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_write) { test::mock::osi_config::config_save.body = [&](const config_t& /*config*/, const std::string& /*filename*/) -> bool { return true; }; @@ -3122,8 +3072,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_write, test::mock::osi_config::config_save.body = {}; } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_debug_iot_config_dump, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_debug_iot_config_dump) { { errno = 0; int fd = -1; @@ -3152,8 +3101,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_debug_iot_config_dump, } } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_is_factory_reset, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_iot_config_is_factory_reset) { bool return_value; test::mock::osi_properties::osi_property_get_bool.body = [&](const char* /*key*/, bool /*default_value*/) -> bool { return return_value; }; @@ -3169,8 +3117,7 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_iot_config_is_factory_reset, } } -TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_debug_iot_config_delete_files, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { +TEST_F(DeviceIotConfigTest, test_device_debug_iot_config_delete_files) { { errno = 0; int file_fd = -1; @@ -3199,248 +3146,3 @@ TEST_F_WITH_FLAGS(DeviceIotConfigTest, test_device_debug_iot_config_delete_files EXPECT_EQ(errno, ENOENT); } } -class DeviceIotConfigDisabledTest : public testing::Test { -protected: - void SetUp() override { - test::mock::osi_alarm::alarm_new.body = [&](const char* /*name*/) -> alarm_t* { - return &placeholder_alarm; - }; - - test::mock::osi_alarm::alarm_set.body = [&](alarm_t* /*alarm*/, uint64_t /*interval_ms*/, - alarm_callback_t /*cb*/, - void* /*data*/) { return; }; - - test::mock::osi_alarm::alarm_free.body = [](alarm_t* /*alarm*/) {}; - - test::mock::osi_alarm::alarm_is_scheduled.body = [&](const alarm_t* /*alarm*/) -> bool { - return false; - }; - - test::mock::osi_future::future_new_immediate.body = [&](void* /*value*/) -> future_t* { - return &placeholder_future; - }; - - test::mock::osi_config::config_new_empty.body = [&]() -> std::unique_ptr<config_t> { - return std::make_unique<config_t>(); - }; - - test::mock::osi_config::config_new.body = - [&](const char* /*filename*/) -> std::unique_ptr<config_t> { - return std::make_unique<config_t>(); - }; - - test::mock::osi_config::config_get_int.body = - [&](const config_t& /*config*/, const std::string& /*section*/, - const std::string& /*key*/, int def_value) { return def_value; }; - - test::mock::osi_config::config_set_int.body = - [&](config_t* /*config*/, const std::string& /*section*/, const std::string& /*key*/, - int /*value*/) { return; }; - - test::mock::osi_config::config_get_string.body = - [&](const config_t& /*config*/, const std::string& /*section*/, - const std::string& /*key*/, const std::string* def_value) { return def_value; }; - - test::mock::osi_config::config_set_string.body = - [&](config_t* /*config*/, const std::string& /*section*/, const std::string& /*key*/, - const std::string& /*value*/) { return; }; - - test::mock::osi_allocator::osi_free.body = [&](void* /*ptr*/) {}; - - device_iot_config_module_init(); - device_iot_config_module_start_up(); - - reset_mock_function_count_map(); - } - - void TearDown() override { - test::mock::osi_alarm::alarm_new = {}; - test::mock::osi_alarm::alarm_set = {}; - test::mock::osi_alarm::alarm_free = {}; - test::mock::osi_alarm::alarm_is_scheduled = {}; - test::mock::osi_future::future_new_immediate = {}; - test::mock::osi_properties::osi_property_get = {}; - test::mock::osi_config::config_new_empty = {}; - test::mock::osi_config::config_new = {}; - test::mock::osi_config::config_get_int = {}; - test::mock::osi_config::config_set_int = {}; - test::mock::osi_config::config_get_string = {}; - test::mock::osi_config::config_set_string = {}; - test::mock::osi_allocator::osi_free = {}; - } -}; - -TEST_F_WITH_FLAGS(DeviceIotConfigDisabledTest, test_device_iot_config_disabled, - REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(TEST_BT, device_iot_config_logging))) { - const RawAddress peer_addr{}; - std::string section, key, value_str; - int value_int{}; - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(device_iot_config_has_section(section)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(device_iot_config_exist(section, key)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(device_iot_config_get_int(section, key, value_int)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(DEVICE_IOT_CONFIG_ADDR_GET_INT(peer_addr, key, value_int)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(device_iot_config_set_int(section, key, 0)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(DEVICE_IOT_CONFIG_ADDR_SET_INT(peer_addr, key, 0)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(device_iot_config_int_add_one(section, key)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(DEVICE_IOT_CONFIG_ADDR_INT_ADD_ONE(peer_addr, key)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(device_iot_config_get_hex(section, key, value_int)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(DEVICE_IOT_CONFIG_ADDR_GET_HEX(peer_addr, key, value_int)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(device_iot_config_set_hex(section, key, 0, 0)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(DEVICE_IOT_CONFIG_ADDR_SET_HEX(peer_addr, key, 0, 0)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(DEVICE_IOT_CONFIG_ADDR_SET_HEX_IF_GREATER(peer_addr, key, 0, 0)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(device_iot_config_get_str(section, key, NULL, NULL)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(device_iot_config_set_str(section, key, value_str)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(DEVICE_IOT_CONFIG_ADDR_SET_STR(peer_addr, key, value_str)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(device_iot_config_get_bin(section, key, NULL, NULL)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(device_iot_config_set_bin(section, key, NULL, 0)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(DEVICE_IOT_CONFIG_ADDR_SET_BIN(peer_addr, key, NULL, 0)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_FALSE(device_iot_config_remove(section, key)); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_EQ(device_iot_config_get_bin_length(section, key), 0u); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - device_iot_config_flush(); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - EXPECT_TRUE(device_iot_config_clear()); - EXPECT_EQ(get_func_call_size(), 0); - } - - { - reset_mock_function_count_map(); - - device_debug_iot_config_dump(0); - EXPECT_EQ(get_func_call_size(), 0); - } -} diff --git a/system/embdrv/encoder_for_aptx/Android.bp b/system/embdrv/encoder_for_aptx/Android.bp index 406f8e302f..1b421089d7 100644 --- a/system/embdrv/encoder_for_aptx/Android.bp +++ b/system/embdrv/encoder_for_aptx/Android.bp @@ -42,9 +42,7 @@ cc_library_static { tidy_checks: tidy_errors, tidy_checks_as_errors: tidy_errors, min_sdk_version: "Tiramisu", - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], visibility: [ "//packages/modules/Bluetooth:__subpackages__", ], diff --git a/system/embdrv/encoder_for_aptxhd/Android.bp b/system/embdrv/encoder_for_aptxhd/Android.bp index 553d87547c..04dd120c40 100644 --- a/system/embdrv/encoder_for_aptxhd/Android.bp +++ b/system/embdrv/encoder_for_aptxhd/Android.bp @@ -42,9 +42,7 @@ cc_library_static { tidy_checks: tidy_errors, tidy_checks_as_errors: tidy_errors, min_sdk_version: "Tiramisu", - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], visibility: [ "//packages/modules/Bluetooth:__subpackages__", ], diff --git a/system/embdrv/g722/Android.bp b/system/embdrv/g722/Android.bp index 63af8f58ea..1eb2c59a1b 100644 --- a/system/embdrv/g722/Android.bp +++ b/system/embdrv/g722/Android.bp @@ -21,8 +21,6 @@ cc_library_static { "g722_encode.cc", ], host_supported: true, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", } diff --git a/system/embdrv/sbc/decoder/Android.bp b/system/embdrv/sbc/decoder/Android.bp index 84184fece7..2bce45cf98 100644 --- a/system/embdrv/sbc/decoder/Android.bp +++ b/system/embdrv/sbc/decoder/Android.bp @@ -35,9 +35,7 @@ cc_library_static { "srce", ], host_supported: true, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", } diff --git a/system/embdrv/sbc/encoder/Android.bp b/system/embdrv/sbc/encoder/Android.bp index 74fe1ec965..c583277f41 100644 --- a/system/embdrv/sbc/encoder/Android.bp +++ b/system/embdrv/sbc/encoder/Android.bp @@ -32,8 +32,6 @@ cc_library_static { "packages/modules/Bluetooth/system/stack/include", ], host_supported: true, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", } diff --git a/system/gd/Android.bp b/system/gd/Android.bp index aae5f1deb2..fd01e82eb2 100644 --- a/system/gd/Android.bp +++ b/system/gd/Android.bp @@ -177,9 +177,7 @@ cc_library_static { include_dirs: [ "packages/modules/Bluetooth/system", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "31", static_libs: [ "bluetooth_flags_c_lib", @@ -263,11 +261,11 @@ cc_test { ], static_libs: [ "bluetooth_flags_c_lib_for_test", + "libaconfig_storage_read_api_cc", "libbluetooth_log", "libchrome", "libgmock", "server_configurable_flags", - "libaconfig_storage_read_api_cc", ], shared_libs: [ "libbase", diff --git a/system/gd/AndroidTestTemplate.xml b/system/gd/AndroidTestTemplate.xml index 8332422b34..4083baac14 100644 --- a/system/gd/AndroidTestTemplate.xml +++ b/system/gd/AndroidTestTemplate.xml @@ -39,7 +39,7 @@ <!-- Only run tests in MTS if the Bluetooth Mainline module is installed. --> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> - <option name="mainline-module-package-name" value="com.android.btservices" /> - <option name="mainline-module-package-name" value="com.google.android.btservices" /> + <option name="mainline-module-package-name" value="com.android.bt" /> + <option name="mainline-module-package-name" value="com.google.android.bt" /> </object> </configuration> diff --git a/system/gd/crypto_toolbox/Android.bp b/system/gd/crypto_toolbox/Android.bp index 05e44b7bc2..2bac8bc985 100644 --- a/system/gd/crypto_toolbox/Android.bp +++ b/system/gd/crypto_toolbox/Android.bp @@ -19,7 +19,7 @@ cc_library { name: "libbluetooth_crypto_toolbox", defaults: ["fluoride_defaults"], host_supported: true, - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], min_sdk_version: "29", include_dirs: [ "packages/modules/Bluetooth/system/gd", diff --git a/system/gd/hal/hci_hal_host.cc b/system/gd/hal/hci_hal_host.cc index e00554c923..65c1671c8d 100644 --- a/system/gd/hal/hci_hal_host.cc +++ b/system/gd/hal/hci_hal_host.cc @@ -328,7 +328,6 @@ public: protected: void ListDependencies(ModuleList* list) const { list->add<LinkClocker>(); - list->add<metrics::CounterMetrics>(); list->add<SnoopLogger>(); } diff --git a/system/gd/hal/hci_hal_host_rootcanal.cc b/system/gd/hal/hci_hal_host_rootcanal.cc index 05d898228d..f4866a5115 100644 --- a/system/gd/hal/hci_hal_host_rootcanal.cc +++ b/system/gd/hal/hci_hal_host_rootcanal.cc @@ -163,10 +163,7 @@ public: } protected: - void ListDependencies(ModuleList* list) const { - list->add<metrics::CounterMetrics>(); - list->add<SnoopLogger>(); - } + void ListDependencies(ModuleList* list) const { list->add<SnoopLogger>(); } void Start() override { std::lock_guard<std::mutex> lock(api_mutex_); diff --git a/system/gd/hal/ranging_hal.h b/system/gd/hal/ranging_hal.h index 5a37900bc5..b22378970e 100644 --- a/system/gd/hal/ranging_hal.h +++ b/system/gd/hal/ranging_hal.h @@ -281,9 +281,15 @@ struct ProcedureDataV2 { struct RangingResult { double result_meters_; + double error_meters_; + // A normalized value from 0 (low confidence) to 100 (high confidence) representing the confidence // of estimated distance. The value is -1 when unavailable. int8_t confidence_level_; + double delay_spread_meters_; + uint8_t detected_attack_level_; + double velocity_meters_per_second_; + int64_t elapsed_timestamp_nanos_; }; class RangingHalCallback { diff --git a/system/gd/hal/ranging_hal_android.cc b/system/gd/hal/ranging_hal_android.cc index d093685863..1a82eb2062 100644 --- a/system/gd/hal/ranging_hal_android.cc +++ b/system/gd/hal/ranging_hal_android.cc @@ -73,10 +73,11 @@ class BluetoothChannelSoundingSessionTracker : public BnBluetoothChannelSounding public: BluetoothChannelSoundingSessionTracker(uint16_t connection_handle, RangingHalCallback* ranging_hal_callback, - bool for_vendor_specific_reply) + bool for_vendor_specific_reply, RangingHalVersion hal_ver) : connection_handle_(connection_handle), ranging_hal_callback_(ranging_hal_callback), - for_vendor_specific_reply_(for_vendor_specific_reply) {} + for_vendor_specific_reply_(for_vendor_specific_reply), + hal_ver_(hal_ver) {} ::ndk::ScopedAStatus onOpened(::aidl::android::hardware::bluetooth::ranging::Reason in_reason) { log::info("connection_handle 0x{:04x}, reason {}", connection_handle_, (uint16_t)in_reason); @@ -103,8 +104,15 @@ public: log::verbose("resultMeters {}", in_result.resultMeters); hal::RangingResult ranging_result = { .result_meters_ = in_result.resultMeters, + .error_meters_ = in_result.errorMeters, .confidence_level_ = in_result.confidenceLevel, + .delay_spread_meters_ = in_result.delaySpreadMeters, + .detected_attack_level_ = static_cast<uint8_t>(in_result.detectedAttackLevel), + .velocity_meters_per_second_ = in_result.velocityMetersPerSecond, }; + if (hal_ver_ == V_2) { + ranging_result.elapsed_timestamp_nanos_ = in_result.timestampNanos; + } ranging_hal_callback_->OnResult(connection_handle_, ranging_result); return ::ndk::ScopedAStatus::ok(); } @@ -129,6 +137,7 @@ private: uint16_t connection_handle_; RangingHalCallback* ranging_hal_callback_; bool for_vendor_specific_reply_; + RangingHalVersion hal_ver_; }; class RangingHalAndroid : public RangingHal { @@ -167,7 +176,7 @@ public: connection_handle, att_handle, vendor_specific_data.size()); session_trackers_[connection_handle] = ndk::SharedRefBase::make<BluetoothChannelSoundingSessionTracker>( - connection_handle, ranging_hal_callback_, false); + connection_handle, ranging_hal_callback_, false, hal_ver_); BluetoothChannelSoundingParameters parameters; parameters.aclHandle = connection_handle; parameters.role = aidl::android::hardware::bluetooth::ranging::Role::INITIATOR; @@ -200,7 +209,7 @@ public: log::info("connection_handle 0x{:04x}", connection_handle); session_trackers_[connection_handle] = ndk::SharedRefBase::make<BluetoothChannelSoundingSessionTracker>( - connection_handle, ranging_hal_callback_, true); + connection_handle, ranging_hal_callback_, true, hal_ver_); BluetoothChannelSoundingParameters parameters; parameters.aclHandle = connection_handle; parameters.role = aidl::android::hardware::bluetooth::ranging::Role::REFLECTOR; diff --git a/system/gd/hal/snoop_logger.cc b/system/gd/hal/snoop_logger.cc index 2e587cbfd6..326ec3e82e 100644 --- a/system/gd/hal/snoop_logger.cc +++ b/system/gd/hal/snoop_logger.cc @@ -29,6 +29,7 @@ #include <algorithm> #include <bitset> #include <chrono> +#include <filesystem> #include <sstream> #include "common/circular_buffer.h" @@ -322,6 +323,21 @@ std::string get_btsnoop_log_path(std::string log_dir, bool filtered) { std::string get_last_log_path(std::string log_file_path) { return log_file_path.append(".last"); } +#ifdef __ANDROID__ +static bool create_log_directories() { + std::filesystem::path default_path = os::ParameterProvider::SnoopLogFilePath(); + std::filesystem::path default_dir_path = default_path.parent_path(); + + if (std::filesystem::exists(default_dir_path)) { + log::info("Directory {} already exists", default_dir_path.string()); + return true; + } + + log::info("Creating directory: {}", default_dir_path.string()); + return std::filesystem::create_directories(default_dir_path); +} +#endif // __ANDROID__ + void delete_btsnoop_files(const std::string& log_path) { log::info("Deleting logs if they exist"); if (os::FileExists(log_path)) { @@ -531,6 +547,13 @@ void SnoopLogger::OpenNextSnoopLogFile() { auto last_file_path = get_last_log_path(snoop_log_path_); +#ifdef __ANDROID__ + if (com::android::bluetooth::flags::snoop_logger_recreate_logs_directory() && + !create_log_directories()) { + log::error("Could not recreate log directory"); + } +#endif // __ANDROID__ + if (os::FileExists(snoop_log_path_)) { if (!os::RenameFile(snoop_log_path_, last_file_path)) { log::error("Unabled to rename existing snoop log from \"{}\" to \"{}\"", snoop_log_path_, @@ -547,21 +570,17 @@ void SnoopLogger::OpenNextSnoopLogFile() { file_creation_time = fake_timerfd_get_clock(); #endif if (!btsnoop_ostream_.good()) { - log::error("Unable to open snoop log at \"{}\", error: \"{}\"", snoop_log_path_, + log::fatal("Unable to open snoop log at \"{}\", error: \"{}\"", snoop_log_path_, strerror(errno)); - return; } umask(prevmask); if (!btsnoop_ostream_.write(reinterpret_cast<const char*>(&SnoopLoggerCommon::kBtSnoopFileHeader), sizeof(SnoopLoggerCommon::FileHeaderType))) { - log::error("Unable to write file header to \"{}\", error: \"{}\"", snoop_log_path_, + log::fatal("Unable to write file header to \"{}\", error: \"{}\"", snoop_log_path_, strerror(errno)); - btsnoop_ostream_.close(); - return; } if (!btsnoop_ostream_.flush()) { log::error("Failed to flush, error: \"{}\"", strerror(errno)); - return; } } @@ -725,21 +744,30 @@ uint32_t SnoopLogger::PayloadStrip(profile_type_t current_profile, uint8_t* pack uint32_t SnoopLogger::FilterProfilesHandleHfp(uint8_t* packet, uint32_t length, uint32_t totlen, uint32_t offset) { - if ((totlen - offset) > cpbr_pat_len) { - if (memcmp(&packet[offset], cpbr_pattern, cpbr_pat_len) == 0) { - length = offset + cpbr_pat_len + 1; - packet[L2CAP_PDU_LENGTH_OFFSET] = offset + cpbr_pat_len - BASIC_L2CAP_HEADER_LENGTH; - packet[L2CAP_PDU_LENGTH_OFFSET] = - offset + cpbr_pat_len - (ACL_HEADER_LENGTH + BASIC_L2CAP_HEADER_LENGTH); - return length; - } + // CPBR packet + if ((totlen - offset) > cpbr_pat_len && + memcmp(&packet[offset], cpbr_pattern, cpbr_pat_len) == 0) { + length = offset + cpbr_pat_len + 1; + packet[ACL_LENGTH_OFFSET] = offset + cpbr_pat_len - BASIC_L2CAP_HEADER_LENGTH; + packet[ACL_LENGTH_OFFSET + 1] = (offset + cpbr_pat_len - BASIC_L2CAP_HEADER_LENGTH) >> 8; - if (memcmp(&packet[offset], clcc_pattern, clcc_pat_len) == 0) { - length = offset + cpbr_pat_len + 1; - packet[L2CAP_PDU_LENGTH_OFFSET] = offset + clcc_pat_len - BASIC_L2CAP_HEADER_LENGTH; - packet[L2CAP_PDU_LENGTH_OFFSET] = - offset + clcc_pat_len - (ACL_HEADER_LENGTH + BASIC_L2CAP_HEADER_LENGTH); - } + packet[L2CAP_PDU_LENGTH_OFFSET] = + offset + cpbr_pat_len - (ACL_HEADER_LENGTH + BASIC_L2CAP_HEADER_LENGTH); + packet[L2CAP_PDU_LENGTH_OFFSET + 1] = + (offset + cpbr_pat_len - (ACL_HEADER_LENGTH + BASIC_L2CAP_HEADER_LENGTH)) >> 8; + return length; + } + // CLCC packet + if ((totlen - offset) > clcc_pat_len && + memcmp(&packet[offset], clcc_pattern, clcc_pat_len) == 0) { + length = offset + cpbr_pat_len + 1; + packet[ACL_LENGTH_OFFSET] = offset + clcc_pat_len - BASIC_L2CAP_HEADER_LENGTH; + packet[ACL_LENGTH_OFFSET + 1] = (offset + clcc_pat_len - BASIC_L2CAP_HEADER_LENGTH) >> 8; + + packet[L2CAP_PDU_LENGTH_OFFSET] = + offset + clcc_pat_len - (ACL_HEADER_LENGTH + BASIC_L2CAP_HEADER_LENGTH); + packet[L2CAP_PDU_LENGTH_OFFSET + 1] = + (offset + clcc_pat_len - (ACL_HEADER_LENGTH + BASIC_L2CAP_HEADER_LENGTH)) >> 8; } return length; @@ -1194,9 +1222,6 @@ void SnoopLogger::Capture(const HciPacket& immutable_packet, Direction direction if (packet_counter_ > max_packets_per_file_) { OpenNextSnoopLogFile(); } - if (!btsnoop_ostream_.is_open() || !btsnoop_ostream_.good()) { - return; - } if (!btsnoop_ostream_.write(reinterpret_cast<const char*>(&header), sizeof(PacketHeaderType))) { log::error("Failed to write packet header for btsnoop, error: \"{}\"", strerror(errno)); } diff --git a/system/gd/hal/snoop_logger_test.cc b/system/gd/hal/snoop_logger_test.cc index bfe81f3194..a64db3cff5 100644 --- a/system/gd/hal/snoop_logger_test.cc +++ b/system/gd/hal/snoop_logger_test.cc @@ -17,6 +17,7 @@ #include "hal/snoop_logger.h" #include <bluetooth/log.h> +#include <com_android_bluetooth_flags.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <netinet/in.h> @@ -28,6 +29,7 @@ #include "hal/snoop_logger_common.h" #include "hal/syscall_wrapper_impl.h" #include "os/fake_timer/fake_timerfd.h" +#include "os/parameter_provider.h" #include "os/system_properties.h" #include "os/utils.h" @@ -161,6 +163,7 @@ protected: } void TearDown() override { + com::android::bluetooth::flags::provider_->reset_flags(); DeleteSnoopLogFiles(); fake_timerfd_reset(); test_registry->StopAll(); @@ -389,7 +392,10 @@ TEST_F(SnoopLoggerModuleTest, delete_old_snooz_log_files) { handler->Post(bluetooth::common::BindOnce(fake_timerfd_advance, 15)); sync_handler(handler); handler->Post(bluetooth::common::BindOnce( - [](std::filesystem::path path) { ASSERT_FALSE(std::filesystem::exists(path)); }, + [](std::filesystem::path path) { + log::info("path: {}", path.string()); + ASSERT_FALSE(std::filesystem::exists(path)); + }, temp_snooz_log_)); sync_handler(handler); @@ -1392,7 +1398,7 @@ TEST_F(SnoopLoggerModuleTest, custom_socket_profiles_filtered_hfp_hf_test) { uint16_t conn_handle = 0x000b; uint16_t local_cid = 0x0043; uint16_t remote_cid = 0x3040; - uint8_t dlci = 0x06; + uint8_t dlci = 0x02; uint16_t psm = 0x0003; uint16_t profile_uuid_hfp_hf = 0x111f; bool flow = true; @@ -1400,18 +1406,22 @@ TEST_F(SnoopLoggerModuleTest, custom_socket_profiles_filtered_hfp_hf_test) { const uint16_t HEADER_SIZE = 12; size_t expected_data_size = HEADER_SIZE + strlen(clcc_pattern.c_str()); std::vector<uint8_t> kPhoneNumber = { - 0x0b, 0x00, 0x30, 0x00, 0x2c, 0x00, 0x40, 0x30, 0x19, 0xff, 0x4f, 0x01, 0x0d, - 0x0a, 0x2b, 0x43, 0x4c, 0x43, 0x43, 0x3a, 0x20, 0x31, 0x2c, 0x31, 0x2c, 0x34, - 0x2c, 0x30, 0x2c, 0x30, 0x2c, 0x22, 0x2b, 0x39, 0x39, 0x31, 0x32, 0x33, 0x34, - 0x35, 0x36, 0x37, 0x38, 0x39, 0x22, 0x2c, 0x31, 0x34, 0x35, 0x0d, 0x0a, 0x49, + 0x0b, 0x00, 0x30, 0x00, // ACL Header (Handle: 0x000b, PB flag: 0x00, Length: 48) + 0x2c, 0x00, 0x40, 0x30, // L2CAP Header (Length: 44, CID: 0x3040) + 0x0b, 0xff, 0x4f, 0x01, // RFCOMM + // "\r\n+CLCC: 1,1,4,0,0,"+99123456789",145\r\n" + 0x0d, 0x0a, 0x2b, 0x43, 0x4c, 0x43, 0x43, 0x3a, 0x20, 0x31, 0x2c, 0x31, 0x2c, 0x34, 0x2c, + 0x30, 0x2c, 0x30, 0x2c, 0x22, 0x2b, 0x39, 0x39, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x22, 0x2c, 0x31, 0x34, 0x35, 0x0d, 0x0a, + 0x86 // RFCOMM }; std::vector<uint8_t> kExpectedPhoneNumber = { - 0x0b, 0x00, 0x30, 0x00, 0x0c, 0x00, 0x40, 0x30, 0x19, 0xff, 0x4f, 0x01, 0x0d, - 0x0a, 0x2b, 0x43, 0x4c, 0x43, 0x43, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; + 0x0b, 0x00, 0x10, 0x00, // ACL Header (Handle: 0x000b, PB flag: 0x00, Length: 16) + 0x0c, 0x00, 0x40, 0x30, // L2CAP Header (Length: 12, CID: 0x3040) + 0x0b, 0xff, 0x4f, 0x01, // RFCOMM + // "\r\n+CLCC:" + 0x0d, 0x0a, 0x2b, 0x43, 0x4c, 0x43, 0x43, 0x3a}; // Set pbap and map filtering modes ASSERT_TRUE( @@ -1498,4 +1508,86 @@ TEST_F(SnoopLoggerModuleTest, custom_socket_profiles_filtered_hfp_hf_test) { test_registry->StopAll(); close(socket_fd); } + +#ifdef __ANDROID__ +TEST_F(SnoopLoggerModuleTest, recreate_log_directory_when_enabled_test) { + com::android::bluetooth::flags::provider_->snoop_logger_recreate_logs_directory(true); + // Actual test + const testing::TestInfo* const test_info = testing::UnitTest::GetInstance()->current_test_info(); + const std::filesystem::path os_btsnoop_file_path_ = os::ParameterProvider::SnoopLogFilePath(); + std::filesystem::path temp_dir_path_ = os_btsnoop_file_path_.parent_path(); + + const std::filesystem::path temp_log_btsnoop_file_ = + temp_dir_path_ / (std::string(test_info->name()) + "_btsnoop_hci.log"); + const std::filesystem::path temp_log_btsnooz_file_ = + temp_dir_path_ / (std::string(test_info->name()) + "_btsnooz_hci.log"); + + if (std::filesystem::exists(temp_dir_path_)) { + std::filesystem::remove_all(temp_dir_path_); + } + + ASSERT_FALSE(std::filesystem::exists(temp_dir_path_)); + + auto* snoop_logger = new TestSnoopLoggerModule(temp_log_btsnoop_file_.string(), + temp_log_btsnooz_file_.string(), 10, + SnoopLogger::kBtSnoopLogModeFull, false, false); + test_registry->InjectTestModule(&SnoopLogger::Factory, snoop_logger); + + ASSERT_TRUE(std::filesystem::exists(temp_dir_path_)); + + test_registry->StopAll(); + + // btsnoop file should exist + ASSERT_TRUE(std::filesystem::exists(temp_log_btsnoop_file_)); + // btsnooz file should be removed as snoop_log_persists is false + ASSERT_FALSE(std::filesystem::exists(temp_log_btsnooz_file_)); + // remove after test + if (std::filesystem::exists(temp_dir_path_)) { + std::filesystem::remove_all(temp_dir_path_); + } +} + +TEST_F(SnoopLoggerModuleTest, recreate_log_directory_when_filtered_test) { + com::android::bluetooth::flags::provider_->snoop_logger_recreate_logs_directory(true); + // Actual test + const testing::TestInfo* const test_info = testing::UnitTest::GetInstance()->current_test_info(); + const std::filesystem::path os_btsnoop_file_path_ = os::ParameterProvider::SnoopLogFilePath(); + std::filesystem::path temp_dir_path_ = os_btsnoop_file_path_.parent_path(); + + const std::filesystem::path temp_log_btsnoop_file_ = + temp_dir_path_ / (std::string(test_info->name()) + "_btsnoop_hci.log"); + const std::filesystem::path temp_log_btsnooz_file_ = + temp_dir_path_ / (std::string(test_info->name()) + "_btsnooz_hci.log"); + + if (std::filesystem::exists(temp_dir_path_)) { + std::filesystem::remove_all(temp_dir_path_); + } + + ASSERT_FALSE(std::filesystem::exists(temp_dir_path_)); + + auto* snoop_logger = new TestSnoopLoggerModule( + temp_log_btsnoop_file_.string(), temp_log_btsnooz_file_.string(), 10, + SnoopLogger::kBtSnoopLogModeFiltered, false, false); + test_registry->InjectTestModule(&SnoopLogger::Factory, snoop_logger); + + ASSERT_TRUE(std::filesystem::exists(temp_dir_path_)); + + test_registry->StopAll(); + + const std::filesystem::path temp_log_btsnoop_filtered_file_ = + temp_dir_path_ / (std::string(test_info->name()) + "_btsnoop_hci.log.filtered"); + const std::filesystem::path temp_log_btsnooz_filtered_file_ = + temp_dir_path_ / (std::string(test_info->name()) + "_btsnooz_hci.log.filtered"); + + // btsnoop file should exist + ASSERT_TRUE(std::filesystem::exists(temp_log_btsnoop_filtered_file_)); + // btsnooz file should be removed as snoop_log_persists is false + ASSERT_FALSE(std::filesystem::exists(temp_log_btsnooz_filtered_file_)); + // remove after test + if (std::filesystem::exists(temp_dir_path_)) { + std::filesystem::remove_all(temp_dir_path_); + } +} +#endif // __ANDROID__ + } // namespace testing diff --git a/system/gd/hci/distance_measurement_manager.cc b/system/gd/hci/distance_measurement_manager.cc index 04c3955204..696f33e621 100644 --- a/system/gd/hci/distance_measurement_manager.cc +++ b/system/gd/hci/distance_measurement_manager.cc @@ -45,6 +45,13 @@ namespace bluetooth { namespace hci { const ModuleFactory DistanceMeasurementManager::Factory = ModuleFactory([]() { return new DistanceMeasurementManager(); }); +// valid azimuth angle degree value is from 0 to 360. +static constexpr int kInvalidAzimuthAngleDegree = -1; +// valid altitude angle degree value is from -90 to 90 +static constexpr int kInvalidAltitudeAngleDegree = -91; +static constexpr double kInvalidDelayedSpreadMeters = -1.0; +static constexpr int8_t kInvalidConfidenceLevel = -1; +static constexpr double kInvalidVelocityMetersPerSecond = -1.0; static constexpr uint16_t kIllegalConnectionHandle = 0xffff; static constexpr uint8_t kTxPowerNotAvailable = 0xfe; static constexpr int8_t kRSSIDropOffAt1M = 41; @@ -283,10 +290,18 @@ struct DistanceMeasurementManager::impl : bluetooth::hal::RangingHalCallback { using namespace std::chrono; uint64_t elapsedRealtimeNanos = duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count(); + if (is_hal_v2()) { + elapsedRealtimeNanos = ranging_result.elapsed_timestamp_nanos_; + } distance_measurement_callbacks_->OnDistanceMeasurementResult( cs_requester_trackers_[connection_handle].address, ranging_result.result_meters_ * 100, - 0.0, -1, -1, -1, -1, elapsedRealtimeNanos, ranging_result.confidence_level_, - DistanceMeasurementMethod::METHOD_CS); + ranging_result.error_meters_ * 100, kInvalidAzimuthAngleDegree, + kInvalidAzimuthAngleDegree, kInvalidAltitudeAngleDegree, kInvalidAltitudeAngleDegree, + elapsedRealtimeNanos, ranging_result.confidence_level_, + ranging_result.delay_spread_meters_, + static_cast<DistanceMeasurementDetectedAttackLevel>( + ranging_result.detected_attack_level_), + ranging_result.velocity_meters_per_second_, DistanceMeasurementMethod::METHOD_CS); } ~impl() {} @@ -527,7 +542,8 @@ struct DistanceMeasurementManager::impl : bluetooth::hal::RangingHalCallback { } } - void handle_ras_client_disconnected_event(const Address address) { + void handle_ras_client_disconnected_event(const Address address, + const ras::RasDisconnectReason& ras_disconnect_reason) { log::info("address:{}", address); for (auto it = cs_requester_trackers_.begin(); it != cs_requester_trackers_.end();) { if (it->second.address == address) { @@ -535,8 +551,13 @@ struct DistanceMeasurementManager::impl : bluetooth::hal::RangingHalCallback { it->second.repeating_alarm->Cancel(); it->second.repeating_alarm.reset(); } - distance_measurement_callbacks_->OnDistanceMeasurementStopped( - address, REASON_NO_LE_CONNECTION, METHOD_CS); + DistanceMeasurementErrorCode reason = REASON_NO_LE_CONNECTION; + if (ras_disconnect_reason == ras::RasDisconnectReason::SERVER_NOT_AVAILABLE) { + reason = REASON_FEATURE_NOT_SUPPORTED_REMOTE; + } else if (ras_disconnect_reason == ras::RasDisconnectReason::FATAL_ERROR) { + reason = REASON_INTERNAL_ERROR; + } + distance_measurement_callbacks_->OnDistanceMeasurementStopped(address, reason, METHOD_CS); gatt_mtus_.erase(it->first); it = cs_requester_trackers_.erase(it); // erase and get the next iterator } else { @@ -915,9 +936,6 @@ struct DistanceMeasurementManager::impl : bluetooth::hal::RangingHalCallback { req_it->second.remote_support_phase_based_ranging = event_view.GetOptionalSubfeaturesSupported().phase_based_ranging_ == 0x01; req_it->second.remote_num_antennas_supported_ = event_view.GetNumAntennasSupported(); - req_it->second.setup_complete = true; - log::info("Setup phase complete, connection_handle: {}, address: {}", connection_handle, - req_it->second.address); req_it->second.retry_counter_for_create_config = 0; req_it->second.remote_supported_sw_time_ = event_view.GetTSwTimeSupported(); send_le_cs_create_config(connection_handle, req_it->second.requesting_config_id); @@ -1016,6 +1034,10 @@ struct DistanceMeasurementManager::impl : bluetooth::hal::RangingHalCallback { if (!live_tracker->local_start) { // reset the responder state, as no other event to set the state. live_tracker->state = CsTrackerState::WAIT_FOR_CONFIG_COMPLETE; + } else { + live_tracker->setup_complete = true; + log::info("connection_handle: {}, address: {}, config_id: {}", connection_handle, + live_tracker->address, config_id); } live_tracker->used_config_id = config_id; @@ -2477,8 +2499,11 @@ struct DistanceMeasurementManager::impl : bluetooth::hal::RangingHalCallback { uint64_t elapsedRealtimeNanos = duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count(); distance_measurement_callbacks_->OnDistanceMeasurementResult( - address, distance * 100, distance * 100, -1, -1, -1, -1, elapsedRealtimeNanos, -1, - DistanceMeasurementMethod::METHOD_RSSI); + address, distance * 100, distance * 100, kInvalidAzimuthAngleDegree, + kInvalidAzimuthAngleDegree, kInvalidAltitudeAngleDegree, kInvalidAltitudeAngleDegree, + elapsedRealtimeNanos, kInvalidConfidenceLevel, kInvalidDelayedSpreadMeters, + DistanceMeasurementDetectedAttackLevel::NADM_ATTACK_UNKNOWN, + kInvalidVelocityMetersPerSecond, DistanceMeasurementMethod::METHOD_RSSI); } std::vector<uint8_t> builder_to_bytes(std::unique_ptr<PacketBuilder<true>> builder) { @@ -2578,8 +2603,9 @@ void DistanceMeasurementManager::HandleConnIntervalUpdated(const Address& addres conn_interval); } -void DistanceMeasurementManager::HandleRasClientDisconnectedEvent(const Address& address) { - CallOn(pimpl_.get(), &impl::handle_ras_client_disconnected_event, address); +void DistanceMeasurementManager::HandleRasClientDisconnectedEvent( + const Address& address, const ras::RasDisconnectReason& ras_disconnect_reason) { + CallOn(pimpl_.get(), &impl::handle_ras_client_disconnected_event, address, ras_disconnect_reason); } void DistanceMeasurementManager::HandleVendorSpecificReply( diff --git a/system/gd/hci/distance_measurement_manager.h b/system/gd/hci/distance_measurement_manager.h index da26425c49..d8c3d49aea 100644 --- a/system/gd/hci/distance_measurement_manager.h +++ b/system/gd/hci/distance_measurement_manager.h @@ -18,6 +18,7 @@ #include <bluetooth/log.h> #include "address.h" +#include "bta/include/bta_ras_api.h" #include "hal/ranging_hal.h" #include "hci/hci_packets.h" #include "module.h" @@ -42,11 +43,15 @@ enum DistanceMeasurementErrorCode { REASON_INTERNAL_ERROR, }; -struct DistanceMeasurementResult { - Address address; - uint32_t centimeter; - uint32_t error_centimeter; - DistanceMeasurementMethod method; +enum DistanceMeasurementDetectedAttackLevel { + NADM_ATTACK_IS_EXTREMELY_UNLIKELY = 0, + NADM_ATTACK_IS_VERY_UNLIKELY = 1, + NADM_ATTACK_IS_UNLIKELY = 2, + NADM_ATTACK_IS_POSSIBLE = 3, + NADM_ATTACK_IS_LIKELY = 4, + NADM_ATTACK_IS_VERY_LIKELY = 5, + NADM_ATTACK_IS_EXTREMELY_LIKELY = 6, + NADM_ATTACK_UNKNOWN = 0xFF, }; class DistanceMeasurementCallbacks { @@ -55,12 +60,12 @@ public: virtual void OnDistanceMeasurementStarted(Address address, DistanceMeasurementMethod method) = 0; virtual void OnDistanceMeasurementStopped(Address address, DistanceMeasurementErrorCode reason, DistanceMeasurementMethod method) = 0; - virtual void OnDistanceMeasurementResult(Address address, uint32_t centimeter, - uint32_t error_centimeter, int azimuth_angle, - int error_azimuth_angle, int altitude_angle, - int error_altitude_angle, uint64_t elapsedRealtimeNanos, - int8_t confidence_level, - DistanceMeasurementMethod method) = 0; + virtual void OnDistanceMeasurementResult( + Address address, uint32_t centimeter, uint32_t error_centimeter, int azimuth_angle, + int error_azimuth_angle, int altitude_angle, int error_altitude_angle, + uint64_t elapsed_realtime_nanos, int8_t confidence_level, double delayed_spread_meters, + DistanceMeasurementDetectedAttackLevel detected_attack_level, + double velocity_meters_per_second, DistanceMeasurementMethod method) = 0; virtual void OnRasFragmentReady(Address address, uint16_t procedure_counter, bool is_last, std::vector<uint8_t> raw_data) = 0; virtual void OnVendorSpecificCharacteristics( @@ -88,7 +93,8 @@ public: const Address& address, uint16_t connection_handle, uint16_t att_handle, const std::vector<hal::VendorSpecificCharacteristic>& vendor_specific_data, uint16_t conn_interval); - void HandleRasClientDisconnectedEvent(const Address& address); + void HandleRasClientDisconnectedEvent(const Address& address, + const ras::RasDisconnectReason& ras_disconnect_reason); void HandleVendorSpecificReply( const Address& address, uint16_t connection_handle, const std::vector<hal::VendorSpecificCharacteristic>& vendor_specific_reply); diff --git a/system/gd/hci/distance_measurement_manager_mock.h b/system/gd/hci/distance_measurement_manager_mock.h index 0aef34d2af..827c096670 100644 --- a/system/gd/hci/distance_measurement_manager_mock.h +++ b/system/gd/hci/distance_measurement_manager_mock.h @@ -35,8 +35,8 @@ class MockDistanceMeasurementCallbacks : public DistanceMeasurementCallbacks { MOCK_METHOD(void, OnDistanceMeasurementStopped, (Address, DistanceMeasurementErrorCode, DistanceMeasurementMethod)); MOCK_METHOD(void, OnDistanceMeasurementResult, - (Address, uint32_t, uint32_t, int, int, int, int, uint64_t, int8_t, - DistanceMeasurementMethod)); + (Address, uint32_t, uint32_t, int, int, int, int, uint64_t, int8_t, double, + DistanceMeasurementDetectedAttackLevel, double, DistanceMeasurementMethod)); }; class MockDistanceMeasurementManager : public DistanceMeasurementManager { diff --git a/system/gd/hci/le_advertising_manager.cc b/system/gd/hci/le_advertising_manager.cc index 5213569243..acf3da1188 100644 --- a/system/gd/hci/le_advertising_manager.cc +++ b/system/gd/hci/le_advertising_manager.cc @@ -250,6 +250,13 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb auto advertiser_id = view.GetAdvertisingInstance(); + if (com::android::bluetooth::flags::fix_unusable_adv_slot_due_to_map_access()) { + if (!advertising_sets_.contains(advertiser_id)) { + log::warn("Unknown advertiser id {}", advertiser_id); + return; + } + } + log::info("Instance: 0x{:x} StateChangeReason: {} Handle: 0x{:x} Address: {}", advertiser_id, VseStateChangeReasonText(view.GetStateChangeReason()), view.GetConnectionHandle(), advertising_sets_[view.GetAdvertisingInstance()].current_address.ToString()); @@ -312,6 +319,13 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb uint8_t advertiser_id = event_view.GetAdvertisingHandle(); + if (com::android::bluetooth::flags::fix_unusable_adv_slot_due_to_map_access()) { + if (!advertising_sets_.contains(advertiser_id)) { + log::warn("Unknown advertiser id {}", advertiser_id); + return; + } + } + bool was_rotating_address = false; if (advertising_sets_[advertiser_id].address_rotation_wake_alarm_ != nullptr) { was_rotating_address = true; @@ -450,7 +464,8 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb } } - /// Generates an address for the advertiser + // Generates an address for the advertiser + // Before calling this method, ensure the id exists in advertising_sets_. AddressWithType new_advertiser_address(AdvertiserId id) { switch (advertising_sets_[id].address_type) { case AdvertiserAddressType::PUBLIC: @@ -771,6 +786,13 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb } void rotate_advertiser_address(AdvertiserId advertiser_id) { + if (com::android::bluetooth::flags::fix_unusable_adv_slot_due_to_map_access()) { + if (!advertising_sets_.contains(advertiser_id)) { + log::warn("Unknown advertiser id {}", advertiser_id); + return; + } + } + if (advertising_api_type_ == AdvertisingApiType::EXTENDED) { AddressWithType address_with_type = new_advertiser_address(advertiser_id); le_advertising_interface_->EnqueueCommand( @@ -784,6 +806,13 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb } void set_advertising_set_random_address_on_timer(AdvertiserId advertiser_id) { + if (com::android::bluetooth::flags::fix_unusable_adv_slot_due_to_map_access()) { + if (!advertising_sets_.contains(advertiser_id)) { + log::warn("Unknown advertiser id {}", advertiser_id); + return; + } + } + // This function should only be trigger by enabled advertising set or IRK rotation if (enabled_sets_[advertiser_id].advertising_handle_ == kInvalidHandle) { if (advertising_sets_[advertiser_id].address_rotation_wake_alarm_ != nullptr) { @@ -892,6 +921,13 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb } void set_parameters(AdvertiserId advertiser_id, AdvertisingConfig config) { + if (com::android::bluetooth::flags::fix_unusable_adv_slot_due_to_map_access()) { + if (!advertising_sets_.contains(advertiser_id)) { + log::warn("Unknown advertiser id {}", advertiser_id); + return; + } + } + config.tx_power = get_tx_power_after_calibration(static_cast<int8_t>(config.tx_power)); advertising_sets_[advertiser_id].is_legacy = config.legacy_pdus; advertising_sets_[advertiser_id].connectable = config.connectable; @@ -1054,6 +1090,13 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb } void set_data(AdvertiserId advertiser_id, bool set_scan_rsp, std::vector<GapData> data) { + if (com::android::bluetooth::flags::fix_unusable_adv_slot_due_to_map_access()) { + if (!advertising_sets_.contains(advertiser_id)) { + log::warn("Unknown advertiser id {}", advertiser_id); + return; + } + } + // The Flags data type shall be included when any of the Flag bits are non-zero and the // advertising packet is connectable and discoverable. if (!set_scan_rsp && advertising_sets_[advertiser_id].connectable && @@ -1578,12 +1621,19 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb return; } for (EnabledSet enabled_set : enabled_sets) { - bool started = advertising_sets_[enabled_set.advertising_handle_].started; uint8_t id = enabled_set.advertising_handle_; if (id == kInvalidHandle) { continue; } + if (com::android::bluetooth::flags::fix_unusable_adv_slot_due_to_map_access()) { + if (!advertising_sets_.contains(id)) { + log::warn("Unknown advertiser id {}", id); + continue; + } + } + + bool started = advertising_sets_[id].started; int reg_id = id_map_[id]; if (reg_id == kIdLocal) { if (!advertising_sets_[enabled_set.advertising_handle_].status_callback.is_null()) { @@ -1626,13 +1676,21 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb } for (EnabledSet enabled_set : enabled_sets) { - int8_t tx_power = advertising_sets_[enabled_set.advertising_handle_].tx_power; - bool started = advertising_sets_[enabled_set.advertising_handle_].started; uint8_t id = enabled_set.advertising_handle_; if (id == kInvalidHandle) { continue; } + if (com::android::bluetooth::flags::fix_unusable_adv_slot_due_to_map_access()) { + if (!advertising_sets_.contains(id)) { + log::warn("Unknown advertiser id {}", id); + continue; + } + } + + int8_t tx_power = advertising_sets_[enabled_set.advertising_handle_].tx_power; + bool started = advertising_sets_[enabled_set.advertising_handle_].started; + int reg_id = id_map_[id]; if (reg_id == kIdLocal) { if (!advertising_sets_[enabled_set.advertising_handle_].status_callback.is_null()) { @@ -1665,6 +1723,14 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb log::info("Got a command complete with status {}", ErrorCodeText(complete_view.GetStatus())); advertising_status = AdvertisingCallback::AdvertisingStatus::INTERNAL_ERROR; } + + if (com::android::bluetooth::flags::fix_unusable_adv_slot_due_to_map_access()) { + if (!advertising_sets_.contains(id)) { + log::warn("Unknown advertiser id {}", id); + return; + } + } + advertising_sets_[id].tx_power = complete_view.GetSelectedTxPower(); if (advertising_sets_[id].started && id_map_[id] != kIdLocal) { @@ -1686,6 +1752,13 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb advertising_status = AdvertisingCallback::AdvertisingStatus::INTERNAL_ERROR; } + if (com::android::bluetooth::flags::fix_unusable_adv_slot_due_to_map_access()) { + if (!advertising_sets_.contains(id)) { + log::warn("Unknown advertiser id {}", id); + return; + } + } + if (advertising_callbacks_ == nullptr || !advertising_sets_[id].started || id_map_[id] == kIdLocal) { return; @@ -1706,6 +1779,13 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb } else { log::info("update random address for advertising set {} : {}", advertiser_id, address_with_type.GetAddress()); + + if (com::android::bluetooth::flags::fix_unusable_adv_slot_due_to_map_access()) { + if (!advertising_sets_.contains(advertiser_id)) { + log::warn("Unknown advertiser id {}", advertiser_id); + return; + } + } advertising_sets_[advertiser_id].current_address = address_with_type; } } @@ -1726,6 +1806,13 @@ struct LeAdvertisingManager::impl : public bluetooth::hci::LeAddressManagerCallb advertising_status = AdvertisingCallback::AdvertisingStatus::INTERNAL_ERROR; } + if (com::android::bluetooth::flags::fix_unusable_adv_slot_due_to_map_access()) { + if (!advertising_sets_.contains(id)) { + log::warn("Unknown advertiser id {}", id); + return; + } + } + // Do not trigger callback if the advertiser not stated yet, or the advertiser is not register // from Java layer if (advertising_callbacks_ == nullptr || !advertising_sets_[id].started || diff --git a/system/gd/hci/le_advertising_manager.h b/system/gd/hci/le_advertising_manager.h index 55245a5cfa..fe614861e2 100644 --- a/system/gd/hci/le_advertising_manager.h +++ b/system/gd/hci/le_advertising_manager.h @@ -47,8 +47,8 @@ class AdvertisingConfig { public: std::vector<GapData> advertisement; std::vector<GapData> scan_response; - uint16_t interval_min; - uint16_t interval_max; + uint32_t interval_min; + uint32_t interval_max; AdvertisingType advertising_type; AdvertiserAddressType requested_advertiser_address_type; PeerAddressType peer_address_type; diff --git a/system/gd/metrics/counter_metrics.cc b/system/gd/metrics/counter_metrics.cc index 1315a87df1..99ba652bf9 100644 --- a/system/gd/metrics/counter_metrics.cc +++ b/system/gd/metrics/counter_metrics.cc @@ -27,8 +27,6 @@ namespace metrics { const int COUNTER_METRICS_PERDIOD_MINUTES = 360; // Drain counters every 6 hours -const ModuleFactory CounterMetrics::Factory = ModuleFactory([]() { return new CounterMetrics(); }); - void CounterMetrics::ListDependencies(ModuleList* /* list */) const {} void CounterMetrics::Start() { diff --git a/system/gd/metrics/counter_metrics.h b/system/gd/metrics/counter_metrics.h index a677041d16..e5a63f6394 100644 --- a/system/gd/metrics/counter_metrics.h +++ b/system/gd/metrics/counter_metrics.h @@ -25,14 +25,16 @@ namespace metrics { class CounterMetrics : public bluetooth::Module { public: + CounterMetrics(os::Handler* handler) : Module(handler) {} + bool CacheCount(int32_t key, int64_t value); virtual bool Count(int32_t key, int64_t count); void Stop() override; - static const ModuleFactory Factory; + void Start() override; protected: + CounterMetrics() = default; void ListDependencies(ModuleList* list) const override; - void Start() override; std::string ToString() const override { return std::string("BluetoothCounterMetrics"); } void DrainBufferedCounters(); virtual bool IsInitialized() { return initialized_; } diff --git a/system/gd/module.h b/system/gd/module.h index e708035a77..0e703ba817 100644 --- a/system/gd/module.h +++ b/system/gd/module.h @@ -84,6 +84,9 @@ public: virtual ~Module() = default; protected: + Module() = default; + Module(os::Handler* handler) : handler_(handler) {} + // Populate the provided list with modules that must start before yours virtual void ListDependencies(ModuleList* list) const = 0; @@ -120,7 +123,7 @@ private: ::bluetooth::os::Handler* handler_ = nullptr; ModuleList dependencies_; - const ModuleRegistry* registry_; + const ModuleRegistry* registry_ = nullptr; }; class ModuleRegistry { diff --git a/system/gd/proto/Android.bp b/system/gd/proto/Android.bp index 6136cf84f6..1f7e72687f 100644 --- a/system/gd/proto/Android.bp +++ b/system/gd/proto/Android.bp @@ -16,9 +16,7 @@ java_library_static { srcs: [ "bluetooth/metrics/bluetooth.proto", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "30", sdk_version: "current", } @@ -41,8 +39,6 @@ cc_library_static { srcs: [ "bluetooth/metrics/bluetooth.proto", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "30", } diff --git a/system/gd/rust/common/Android.bp b/system/gd/rust/common/Android.bp index a1e8c4a977..26b724c900 100644 --- a/system/gd/rust/common/Android.bp +++ b/system/gd/rust/common/Android.bp @@ -35,9 +35,7 @@ rust_library { ], }, }, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", } diff --git a/system/gd/rust/topshim/le_audio/le_audio_shim.cc b/system/gd/rust/topshim/le_audio/le_audio_shim.cc index 77d7a5c60d..53a0f7d98f 100644 --- a/system/gd/rust/topshim/le_audio/le_audio_shim.cc +++ b/system/gd/rust/topshim/le_audio/le_audio_shim.cc @@ -162,6 +162,8 @@ static BtLeAudioGroupStreamStatus to_rust_btle_audio_group_stream_status( return BtLeAudioGroupStreamStatus::Streaming; case le_audio::GroupStreamStatus::RELEASING: return BtLeAudioGroupStreamStatus::Releasing; + case le_audio::GroupStreamStatus::RELEASING_AUTONOMOUS: + return BtLeAudioGroupStreamStatus::ReleasingAutonomous; case le_audio::GroupStreamStatus::SUSPENDING: return BtLeAudioGroupStreamStatus::Suspending; case le_audio::GroupStreamStatus::SUSPENDED: diff --git a/system/gd/rust/topshim/src/profiles/gatt.rs b/system/gd/rust/topshim/src/profiles/gatt.rs index 7bed7bdf78..6bb9714852 100644 --- a/system/gd/rust/topshim/src/profiles/gatt.rs +++ b/system/gd/rust/topshim/src/profiles/gatt.rs @@ -15,6 +15,8 @@ use num_traits::cast::{FromPrimitive, ToPrimitive}; use std::fmt::{Display, Formatter, Result}; use std::sync::{Arc, Mutex}; +use std::ffi::CString; + use topshim_macros::{cb_variant, gen_cxx_extern_trivial}; pub type BtGattNotifyParams = bindings::btgatt_notify_params_t; @@ -1165,7 +1167,8 @@ pub struct GattClient { impl GattClient { pub fn register_client(&self, uuid: &Uuid, eatt_support: bool) -> BtStatus { - BtStatus::from(ccall!(self, register_client, uuid, eatt_support)) + let cname = CString::new("rust_client").expect("CString::new failed"); + BtStatus::from(ccall!(self, register_client, uuid, cname.as_ptr(), eatt_support)) } pub fn unregister_client(&self, client_if: i32) -> BtStatus { diff --git a/system/gd/rust/topshim/src/profiles/le_audio.rs b/system/gd/rust/topshim/src/profiles/le_audio.rs index 266f24f7fb..651ad82ecd 100644 --- a/system/gd/rust/topshim/src/profiles/le_audio.rs +++ b/system/gd/rust/topshim/src/profiles/le_audio.rs @@ -109,6 +109,7 @@ pub mod ffi { Idle = 0, Streaming, Releasing, + ReleasingAutonomous, Suspending, Suspended, ConfiguredAutonomous, @@ -413,11 +414,12 @@ impl From<BtLeAudioGroupStreamStatus> for i32 { BtLeAudioGroupStreamStatus::Idle => 0, BtLeAudioGroupStreamStatus::Streaming => 1, BtLeAudioGroupStreamStatus::Releasing => 2, - BtLeAudioGroupStreamStatus::Suspending => 3, - BtLeAudioGroupStreamStatus::Suspended => 4, - BtLeAudioGroupStreamStatus::ConfiguredAutonomous => 5, - BtLeAudioGroupStreamStatus::ConfiguredByUser => 6, - BtLeAudioGroupStreamStatus::Destroyed => 7, + BtLeAudioGroupStreamStatus::ReleasingAutonomous => 3, + BtLeAudioGroupStreamStatus::Suspending => 4, + BtLeAudioGroupStreamStatus::Suspended => 5, + BtLeAudioGroupStreamStatus::ConfiguredAutonomous => 6, + BtLeAudioGroupStreamStatus::ConfiguredByUser => 7, + BtLeAudioGroupStreamStatus::Destroyed => 8, _ => panic!("Invalid value {:?} to BtLeAudioGroupStreamStatus", value), } } @@ -429,11 +431,12 @@ impl From<i32> for BtLeAudioGroupStreamStatus { 0 => BtLeAudioGroupStreamStatus::Idle, 1 => BtLeAudioGroupStreamStatus::Streaming, 2 => BtLeAudioGroupStreamStatus::Releasing, - 3 => BtLeAudioGroupStreamStatus::Suspending, - 4 => BtLeAudioGroupStreamStatus::Suspended, - 5 => BtLeAudioGroupStreamStatus::ConfiguredAutonomous, - 6 => BtLeAudioGroupStreamStatus::ConfiguredByUser, - 7 => BtLeAudioGroupStreamStatus::Destroyed, + 3 => BtLeAudioGroupStreamStatus::ReleasingAutonomous, + 4 => BtLeAudioGroupStreamStatus::Suspending, + 5 => BtLeAudioGroupStreamStatus::Suspended, + 6 => BtLeAudioGroupStreamStatus::ConfiguredAutonomous, + 7 => BtLeAudioGroupStreamStatus::ConfiguredByUser, + 8 => BtLeAudioGroupStreamStatus::Destroyed, _ => panic!("Invalid value {} to BtLeAudioGroupStreamStatus", value), } } diff --git a/system/gd/storage/config_keys.h b/system/gd/storage/config_keys.h index c3e6573c78..d3659b6a96 100644 --- a/system/gd/storage/config_keys.h +++ b/system/gd/storage/config_keys.h @@ -99,6 +99,7 @@ #define BTIF_STORAGE_KEY_LEAUDIO_SOURCE_AUDIOLOCATION "SourceAudioLocation" #define BTIF_STORAGE_KEY_LEAUDIO_SOURCE_PACS_BIN "SourcePacsBin" #define BTIF_STORAGE_KEY_LEAUDIO_SOURCE_SUPPORTED_CONTEXT_TYPE "SourceSupportedContextType" +#define BTIF_STORAGE_KEY_LEAUDIO_GMAP_BIN "LeAudioGmap" #define BTIF_STORAGE_KEY_LINK_KEY "LinkKey" #define BTIF_STORAGE_KEY_LINK_KEY_TYPE "LinkKeyType" #define BTIF_STORAGE_KEY_LOCAL_IO_CAPS "LocalIOCaps" @@ -110,6 +111,7 @@ #define BTIF_STORAGE_KEY_PIN_LENGTH "PinLength" #define BTIF_STORAGE_KEY_PRODUCT_ID "ProductId" #define BTIF_STORAGE_KEY_REMOTE_SERVICE "Service" +#define BTIF_STORAGE_KEY_REMOTE_SERVICE_LE "ServiceLe" #define BTIF_STORAGE_KEY_REMOTE_VER_MFCT "Manufacturer" #define BTIF_STORAGE_KEY_REMOTE_VER_SUBVER "LmpSubVer" #define BTIF_STORAGE_KEY_REMOTE_VER_VER "LmpVer" diff --git a/system/gd/storage/storage_module.cc b/system/gd/storage/storage_module.cc index ce5a89c6f7..0a7c9a7ef1 100644 --- a/system/gd/storage/storage_module.cc +++ b/system/gd/storage/storage_module.cc @@ -63,11 +63,20 @@ const std::string StorageModule::kTimeCreatedFormat = "%Y-%m-%d %H:%M:%S"; const std::string StorageModule::kAdapterSection = BTIF_STORAGE_SECTION_ADAPTER; -StorageModule::StorageModule(std::string config_file_path, +StorageModule::StorageModule() + : StorageModule(nullptr, os::ParameterProvider::ConfigFilePath(), kDefaultConfigSaveDelay, + kDefaultTempDeviceCapacity, false, false) {} + +StorageModule::StorageModule(os::Handler* handler) + : StorageModule(handler, os::ParameterProvider::ConfigFilePath(), kDefaultConfigSaveDelay, + kDefaultTempDeviceCapacity, false, false) {} + +StorageModule::StorageModule(os::Handler* handler, std::string config_file_path, std::chrono::milliseconds config_save_delay, size_t temp_devices_capacity, bool is_restricted_mode, bool is_single_user_mode) - : config_file_path_(std::move(config_file_path)), + : Module(handler), + config_file_path_(std::move(config_file_path)), config_save_delay_(config_save_delay), temp_devices_capacity_(temp_devices_capacity), is_restricted_mode_(is_restricted_mode), @@ -84,10 +93,7 @@ StorageModule::~StorageModule() { pimpl_.reset(); } -const ModuleFactory StorageModule::Factory = ModuleFactory([]() { - return new StorageModule(os::ParameterProvider::ConfigFilePath(), kDefaultConfigSaveDelay, - kDefaultTempDeviceCapacity, false, false); -}); +const ModuleFactory StorageModule::Factory = ModuleFactory([]() { return new StorageModule(); }); struct StorageModule::impl { explicit impl(Handler* handler, ConfigCache cache, size_t in_memory_cache_size_limit) @@ -144,9 +150,7 @@ void StorageModule::Clear() { pimpl_->cache_.Clear(); } -void StorageModule::ListDependencies(ModuleList* list) const { - list->add<metrics::CounterMetrics>(); -} +void StorageModule::ListDependencies(ModuleList* /*list*/) const {} void StorageModule::Start() { std::lock_guard<std::recursive_mutex> lock(mutex_); diff --git a/system/gd/storage/storage_module.h b/system/gd/storage/storage_module.h index 12f5750196..b94251a0dc 100644 --- a/system/gd/storage/storage_module.h +++ b/system/gd/storage/storage_module.h @@ -55,12 +55,17 @@ public: static const std::string kAdapterSection; + StorageModule(); + StorageModule(os::Handler*); StorageModule(const StorageModule&) = delete; StorageModule& operator=(const StorageModule&) = delete; ~StorageModule(); static const ModuleFactory Factory; + void Start() override; + void Stop() override; + // Methods to access the storage layer via Device abstraction // - Devices will be lazily created when methods below are called. Hence, no std::optional<> nor // nullptr is used in @@ -121,8 +126,6 @@ public: protected: void ListDependencies(ModuleList* list) const override; - void Start() override; - void Stop() override; std::string ToString() const override; friend shim::BtifConfigInterface; @@ -147,8 +150,9 @@ protected: // - temp_devices_capacity is the number of temporary, typically unpaired devices to hold in a // memory based LRU // - is_restricted_mode and is_single_user_mode are flags from upper layer - StorageModule(std::string config_file_path, std::chrono::milliseconds config_save_delay, - size_t temp_devices_capacity, bool is_restricted_mode, bool is_single_user_mode); + StorageModule(os::Handler* handler, std::string config_file_path, + std::chrono::milliseconds config_save_delay, size_t temp_devices_capacity, + bool is_restricted_mode, bool is_single_user_mode); bool HasSection(const std::string& section) const; bool HasProperty(const std::string& section, const std::string& property) const; diff --git a/system/gd/storage/storage_module_test.cc b/system/gd/storage/storage_module_test.cc index 2f600944ae..e58c210609 100644 --- a/system/gd/storage/storage_module_test.cc +++ b/system/gd/storage/storage_module_test.cc @@ -53,8 +53,8 @@ class TestStorageModule : public StorageModule { public: TestStorageModule(std::string config_file_path, std::chrono::milliseconds config_save_delay, bool is_restricted_mode, bool is_single_user_mode) - : StorageModule(std::move(config_file_path), config_save_delay, kTestTempDevicesCapacity, - is_restricted_mode, is_single_user_mode) {} + : StorageModule(nullptr, std::move(config_file_path), config_save_delay, + kTestTempDevicesCapacity, is_restricted_mode, is_single_user_mode) {} ConfigCache* GetMemoryOnlyConfigCachePublic() { return StorageModule::GetMemoryOnlyConfigCache(); diff --git a/system/hci/Android.bp b/system/hci/Android.bp index e4e0106606..1f0a51435a 100644 --- a/system/hci/Android.bp +++ b/system/hci/Android.bp @@ -26,9 +26,7 @@ cc_library_static { "packages/modules/Bluetooth/system/stack/include", "system/libhwbinder/include", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], host_supported: true, min_sdk_version: "Tiramisu", header_libs: ["libbluetooth_headers"], diff --git a/system/include/Android.bp b/system/include/Android.bp index 2482effbf3..c7cec2e726 100644 --- a/system/include/Android.bp +++ b/system/include/Android.bp @@ -22,7 +22,7 @@ cc_library_headers { host_supported: true, apex_available: [ "//apex_available:platform", - "com.android.btservices", + "com.android.bt", ], min_sdk_version: "30", } @@ -49,7 +49,7 @@ cc_library_headers { host_supported: true, apex_available: [ "//apex_available:platform", - "com.android.btservices", + "com.android.bt", ], min_sdk_version: "30", } diff --git a/system/include/hardware/bluetooth.h b/system/include/hardware/bluetooth.h index a8a169a58d..fa15353d9f 100644 --- a/system/include/hardware/bluetooth.h +++ b/system/include/hardware/bluetooth.h @@ -299,7 +299,7 @@ typedef enum { */ BT_PROPERTY_TYPE_OF_DEVICE, /** - * Description - Bluetooth Service Record + * Description - Bluetooth Service Record, UUIDs on BREDR transport * Access mode - Only GET. * Data type - bt_service_record_t */ @@ -427,6 +427,14 @@ typedef enum { */ BT_PROPERTY_LPP_OFFLOAD_FEATURES, + /** + * Description - Bluetooth Service 128-bit UUIDs on LE transport + * Access mode - Only GET. + * Data type - Array of bluetooth::Uuid (Array size inferred from property + * length). + */ + BT_PROPERTY_UUIDS_LE, + BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP = 0xFF, } bt_property_type_t; @@ -915,6 +923,11 @@ typedef struct { int (*disconnect_all_acls)(); /** + * Call to disconnect ACL connection to device + */ + int (*disconnect_acl)(const RawAddress& bd_addr, int transport); + + /** * Call to retrieve a generated random */ int (*le_rand)(); diff --git a/system/include/hardware/bt_gatt_client.h b/system/include/hardware/bt_gatt_client.h index 5fbbd5f7c7..94bb4c7c24 100644 --- a/system/include/hardware/bt_gatt_client.h +++ b/system/include/hardware/bt_gatt_client.h @@ -203,7 +203,7 @@ typedef struct { typedef struct { /** Registers a GATT client application with the stack */ - bt_status_t (*register_client)(const bluetooth::Uuid& uuid, bool eatt_support); + bt_status_t (*register_client)(const bluetooth::Uuid& uuid, const char* name, bool eatt_support); /** Unregister a client application from the stack */ bt_status_t (*unregister_client)(int client_if); diff --git a/system/include/hardware/bt_le_audio.h b/system/include/hardware/bt_le_audio.h index 91ce17c388..94c3762596 100644 --- a/system/include/hardware/bt_le_audio.h +++ b/system/include/hardware/bt_le_audio.h @@ -70,6 +70,7 @@ enum class GroupStreamStatus { IDLE = 0, STREAMING, RELEASING, + RELEASING_AUTONOMOUS, SUSPENDING, SUSPENDED, CONFIGURED_AUTONOMOUS, diff --git a/system/include/hardware/distance_measurement_interface.h b/system/include/hardware/distance_measurement_interface.h index e5de171c39..65532034d9 100644 --- a/system/include/hardware/distance_measurement_interface.h +++ b/system/include/hardware/distance_measurement_interface.h @@ -34,7 +34,9 @@ public: uint32_t error_centimeter, int azimuth_angle, int error_azimuth_angle, int altitude_angle, int error_altitude_angle, uint64_t elapsedRealtimeNanos, - int8_t confidence_level, uint8_t method) = 0; + int8_t confidence_level, double delayedSpreadCentimeters, + uint8_t detectedAttackLevel, + double velocityCentimetersPerSecond, uint8_t method) = 0; }; class DistanceMeasurementInterface { diff --git a/system/internal_include/Android.bp b/system/internal_include/Android.bp index 7d66847ebf..06a56b06fa 100644 --- a/system/internal_include/Android.bp +++ b/system/internal_include/Android.bp @@ -14,7 +14,7 @@ cc_library_headers { host_supported: true, apex_available: [ "//apex_available:platform", - "com.android.btservices", + "com.android.bt", ], min_sdk_version: "30", } diff --git a/system/log/Android.bp b/system/log/Android.bp index ebfd81d1fb..649a039aff 100644 --- a/system/log/Android.bp +++ b/system/log/Android.bp @@ -2,9 +2,7 @@ cc_library { name: "libbluetooth_log", host_supported: true, min_sdk_version: "33", - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], export_include_dirs: [ "include", ], diff --git a/system/main/Android.bp b/system/main/Android.bp index 5272905831..15b1351bc3 100644 --- a/system/main/Android.bp +++ b/system/main/Android.bp @@ -41,9 +41,7 @@ cc_library_static { "packages/modules/Bluetooth/system/udrv/include", "system/security/keystore/include", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], host_supported: true, min_sdk_version: "Tiramisu", static_libs: [ @@ -102,9 +100,7 @@ cc_library { sanitize: { scs: true, }, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], host_supported: true, min_sdk_version: "30", } diff --git a/system/main/shim/distance_measurement_manager.cc b/system/main/shim/distance_measurement_manager.cc index 61150f3b07..fd7eb3591d 100644 --- a/system/main/shim/distance_measurement_manager.cc +++ b/system/main/shim/distance_measurement_manager.cc @@ -25,6 +25,7 @@ #include "stack/include/acl_api.h" #include "stack/include/main_thread.h" +using bluetooth::hci::DistanceMeasurementDetectedAttackLevel; using bluetooth::hci::DistanceMeasurementErrorCode; using bluetooth::hci::DistanceMeasurementMethod; using namespace bluetooth; @@ -117,15 +118,18 @@ public: void OnDistanceMeasurementResult(bluetooth::hci::Address address, uint32_t centimeter, uint32_t error_centimeter, int azimuth_angle, int error_azimuth_angle, int altitude_angle, - int error_altitude_angle, uint64_t elapsedRealtimeNanos, - int8_t confidence_level, + int error_altitude_angle, uint64_t elapsed_realtime_nanos, + int8_t confidence_level, double delay_spread_meters, + DistanceMeasurementDetectedAttackLevel detected_attack_level, + double velocity_meters_per_second, DistanceMeasurementMethod method) override { - do_in_jni_thread(base::BindOnce(&::DistanceMeasurementCallbacks::OnDistanceMeasurementResult, - base::Unretained(distance_measurement_callbacks_), - bluetooth::ToRawAddress(address), centimeter, error_centimeter, - azimuth_angle, error_azimuth_angle, altitude_angle, - error_altitude_angle, elapsedRealtimeNanos, confidence_level, - static_cast<uint8_t>(method))); + do_in_jni_thread(base::BindOnce( + &::DistanceMeasurementCallbacks::OnDistanceMeasurementResult, + base::Unretained(distance_measurement_callbacks_), bluetooth::ToRawAddress(address), + centimeter, error_centimeter, azimuth_angle, error_azimuth_angle, altitude_angle, + error_altitude_angle, elapsed_realtime_nanos, confidence_level, delay_spread_meters, + static_cast<uint8_t>(detected_attack_level), velocity_meters_per_second, + static_cast<uint8_t>(method))); } void OnRasFragmentReady(bluetooth::hci::Address address, uint16_t procedure_counter, bool is_last, @@ -248,9 +252,10 @@ public: bluetooth::ToGdAddress(address), GetConnectionHandleAndRole(address), conn_interval); } - void OnDisconnected(const RawAddress& address) { + void OnDisconnected(const RawAddress& address, + const ras::RasDisconnectReason& ras_disconnect_reason) { bluetooth::shim::GetDistanceMeasurementManager()->HandleRasClientDisconnectedEvent( - bluetooth::ToGdAddress(address)); + bluetooth::ToGdAddress(address), ras_disconnect_reason); } // Must be called from main_thread diff --git a/system/main/shim/entry.cc b/system/main/shim/entry.cc index ff0014b481..c1014a8a09 100644 --- a/system/main/shim/entry.cc +++ b/system/main/shim/entry.cc @@ -27,6 +27,7 @@ #include "hci/msft.h" #include "hci/remote_name_request.h" #include "lpp/lpp_offload_manager.h" +#include "main/shim/shim.h" #include "main/shim/stack.h" #include "metrics/counter_metrics.h" #include "os/handler.h" @@ -71,13 +72,13 @@ storage::StorageModule* GetStorage() { hci::AclManager* GetAclManager() { return Stack::GetInstance()->GetInstance<hci::AclManager>(); } -metrics::CounterMetrics* GetCounterMetrics() { - return Stack::GetInstance()->GetInstance<metrics::CounterMetrics>(); -} +metrics::CounterMetrics* GetCounterMetrics() { return Stack::GetInstance()->GetCounterMetrics(); } hci::MsftExtensionManager* GetMsftExtensionManager() { return Stack::GetInstance()->GetInstance<hci::MsftExtensionManager>(); } +bool is_gd_stack_started_up() { return Stack::GetInstance()->IsRunning(); } + } // namespace shim } // namespace bluetooth diff --git a/system/main/shim/shim.cc b/system/main/shim/shim.cc index 69c70cd9ba..81bd0b6bf3 100644 --- a/system/main/shim/shim.cc +++ b/system/main/shim/shim.cc @@ -54,7 +54,3 @@ EXPORT_SYMBOL extern const module_t gd_shim_module = {.name = GD_SHIM_MODULE, .shut_down = GeneralShutDown, .clean_up = kUnusedModuleApi, .dependencies = {kUnusedModuleDependencies}}; - -bool bluetooth::shim::is_gd_stack_started_up() { - return bluetooth::shim::Stack::GetInstance()->IsRunning(); -} diff --git a/system/main/shim/stack.cc b/system/main/shim/stack.cc index 6da9117ae4..d45d65d7cd 100644 --- a/system/main/shim/stack.cc +++ b/system/main/shim/stack.cc @@ -67,6 +67,7 @@ namespace shim { struct Stack::impl { Acl* acl_ = nullptr; + metrics::CounterMetrics* counter_metrics_ = nullptr; }; Stack::Stack() { pimpl_ = std::make_shared<Stack::impl>(); } @@ -82,6 +83,11 @@ void Stack::StartEverything() { log::info("Starting Gd stack"); ModuleList modules; + stack_thread_ = new os::Thread("gd_stack_thread", os::Thread::Priority::REAL_TIME); + stack_handler_ = new os::Handler(stack_thread_); + + pimpl_->counter_metrics_ = new metrics::CounterMetrics(new Handler(stack_thread_)); + #if TARGET_FLOSS modules.add<sysprops::SyspropsModule>(); #else @@ -89,7 +95,6 @@ void Stack::StartEverything() { modules.add<lpp::LppOffloadManager>(); } #endif - modules.add<metrics::CounterMetrics>(); modules.add<hal::HciHal>(); modules.add<hci::HciLayer>(); modules.add<storage::StorageModule>(); @@ -103,14 +108,30 @@ void Stack::StartEverything() { modules.add<hci::LeScanningManager>(); modules.add<hci::DistanceMeasurementManager>(); - stack_thread_ = new os::Thread("gd_stack_thread", os::Thread::Priority::REAL_TIME); - StartUp(&modules, stack_thread_); + management_thread_ = new Thread("management_thread", Thread::Priority::NORMAL); + management_handler_ = new Handler(management_thread_); - stack_handler_ = new os::Handler(stack_thread_); + WakelockManager::Get().Acquire(); + + std::promise<void> promise; + auto future = promise.get_future(); + management_handler_->Post(common::BindOnce(&Stack::handle_start_up, common::Unretained(this), + &modules, std::move(promise))); + + auto init_status = future.wait_for( + std::chrono::milliseconds(get_gd_stack_timeout_ms(/* is_start = */ true))); + + WakelockManager::Get().Release(); + + log::info("init_status == {}", int(init_status)); + + log::assert_that(init_status == std::future_status::ready, "Can't start stack, last instance: {}", + registry_.last_instance_); log::info("Successfully toggled Gd stack"); is_running_ = true; + // Make sure the leaf modules are started log::assert_that(GetInstance<storage::StorageModule>() != nullptr, "assert failed: GetInstance<storage::StorageModule>() != nullptr"); @@ -127,19 +148,6 @@ void Stack::StartEverything() { bluetooth::shim::init_distance_measurement_manager(); } -void Stack::StartModuleStack(const ModuleList* modules, const os::Thread* thread) { - std::lock_guard<std::recursive_mutex> lock(mutex_); - log::assert_that(!is_running_, "Gd stack already running"); - stack_thread_ = const_cast<os::Thread*>(thread); - log::info("Starting Gd stack"); - - StartUp(const_cast<ModuleList*>(modules), stack_thread_); - stack_handler_ = new os::Handler(stack_thread_); - - num_modules_ = modules->NumModules(); - is_running_ = true; -} - void Stack::Stop() { std::lock_guard<std::recursive_mutex> lock(mutex_); bluetooth::shim::hci_on_shutting_down(); @@ -192,13 +200,19 @@ bool Stack::IsRunning() { return is_running_; } -Acl* Stack::GetAcl() { +Acl* Stack::GetAcl() const { std::lock_guard<std::recursive_mutex> lock(mutex_); log::assert_that(is_running_, "assert failed: is_running_"); log::assert_that(pimpl_->acl_ != nullptr, "Acl shim layer has not been created"); return pimpl_->acl_; } +metrics::CounterMetrics* Stack::GetCounterMetrics() const { + std::lock_guard<std::recursive_mutex> lock(mutex_); + log::assert_that(is_running_, "assert failed: is_running_"); + return pimpl_->counter_metrics_; +} + os::Handler* Stack::GetHandler() { std::lock_guard<std::recursive_mutex> lock(mutex_); log::assert_that(is_running_, "assert failed: is_running_"); @@ -222,32 +236,9 @@ void Stack::Dump(int fd, std::promise<void> promise) const { } } -void Stack::StartUp(ModuleList* modules, Thread* stack_thread) { - management_thread_ = new Thread("management_thread", Thread::Priority::NORMAL); - management_handler_ = new Handler(management_thread_); - - WakelockManager::Get().Acquire(); - - std::promise<void> promise; - auto future = promise.get_future(); - management_handler_->Post(common::BindOnce(&Stack::handle_start_up, common::Unretained(this), - modules, stack_thread, std::move(promise))); - - auto init_status = future.wait_for( - std::chrono::milliseconds(get_gd_stack_timeout_ms(/* is_start = */ true))); - - WakelockManager::Get().Release(); - - log::info("init_status == {}", int(init_status)); - - log::assert_that(init_status == std::future_status::ready, "Can't start stack, last instance: {}", - registry_.last_instance_); - - log::info("init complete"); -} - -void Stack::handle_start_up(ModuleList* modules, Thread* stack_thread, std::promise<void> promise) { - registry_.Start(modules, stack_thread); +void Stack::handle_start_up(ModuleList* modules, std::promise<void> promise) { + pimpl_->counter_metrics_->Start(); + registry_.Start(modules, stack_thread_); promise.set_value(); } diff --git a/system/main/shim/stack.h b/system/main/shim/stack.h index 0dfd3fe3ab..e65a0ef528 100644 --- a/system/main/shim/stack.h +++ b/system/main/shim/stack.h @@ -25,10 +25,13 @@ // The shim layer implementation on the Gd stack side. namespace bluetooth { +namespace metrics { +class CounterMetrics; +} + namespace shim { class Acl; -class Btm; // GD shim stack, having modes corresponding to legacy stack class Stack { @@ -39,7 +42,7 @@ public: Stack(const Stack&) = delete; Stack& operator=(const Stack&) = delete; - ~Stack() = default; + virtual ~Stack() = default; // Running mode, everything is up void StartEverything(); @@ -57,17 +60,13 @@ public: return registry_.IsStarted(&T::Factory); } - Acl* GetAcl(); + virtual Acl* GetAcl() const; + virtual metrics::CounterMetrics* GetCounterMetrics() const; os::Handler* GetHandler(); void Dump(int fd, std::promise<void> promise) const; - // Start the list of modules with the given stack manager thread - void StartModuleStack(const ModuleList* modules, const os::Thread* thread); - - size_t NumModules() const { return num_modules_; } - private: struct impl; std::shared_ptr<impl> pimpl_; @@ -76,15 +75,12 @@ private: bool is_running_ = false; os::Thread* stack_thread_ = nullptr; os::Handler* stack_handler_ = nullptr; - size_t num_modules_{0}; - - void StartUp(ModuleList* modules, os::Thread* stack_thread); os::Thread* management_thread_ = nullptr; os::Handler* management_handler_ = nullptr; ModuleRegistry registry_; - void handle_start_up(ModuleList* modules, os::Thread* stack_thread, std::promise<void> promise); + void handle_start_up(ModuleList* modules, std::promise<void> promise); void handle_shut_down(std::promise<void> promise); static std::chrono::milliseconds get_gd_stack_timeout_ms(bool is_start); }; diff --git a/system/main/stack_config.cc b/system/main/stack_config.cc index 9b04e7f16c..20db9be13d 100644 --- a/system/main/stack_config.cc +++ b/system/main/stack_config.cc @@ -60,7 +60,7 @@ static future_t* init() { #if defined(TARGET_FLOSS) const char* path = "/etc/bluetooth/bt_stack.conf"; #elif defined(__ANDROID__) - const char* path = "/apex/com.android.btservices/etc/bluetooth/bt_stack.conf"; + const char* path = "/apex/com.android.bt/etc/bluetooth/bt_stack.conf"; #else // !defined(__ANDROID__) const char* path = "bt_stack.conf"; #endif // defined(__ANDROID__) diff --git a/system/osi/Android.bp b/system/osi/Android.bp index 4806d61985..23055416f2 100644 --- a/system/osi/Android.bp +++ b/system/osi/Android.bp @@ -83,9 +83,7 @@ cc_library_static { "-DLIB_OSI_INTERNAL", ], min_sdk_version: "Tiramisu", - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], header_libs: ["libbluetooth_headers"], static_libs: [ "bluetooth_flags_c_lib", diff --git a/system/packet/Android.bp b/system/packet/Android.bp index d3ba6dfd71..05c2c8cc5b 100644 --- a/system/packet/Android.bp +++ b/system/packet/Android.bp @@ -20,9 +20,7 @@ cc_library_static { "lib-bt-packets-base", "libbluetooth_log", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "30", } diff --git a/system/packet/avrcp/Android.bp b/system/packet/avrcp/Android.bp index 1808112072..ee51ed340a 100644 --- a/system/packet/avrcp/Android.bp +++ b/system/packet/avrcp/Android.bp @@ -48,8 +48,6 @@ cc_library_static { "libbluetooth_log", "libchrome", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "30", } diff --git a/system/packet/base/Android.bp b/system/packet/base/Android.bp index 732d597c34..f81a511374 100644 --- a/system/packet/base/Android.bp +++ b/system/packet/base/Android.bp @@ -18,9 +18,7 @@ cc_library_static { "packet.cc", "packet_builder.cc", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "30", shared_libs: [ "libbase", diff --git a/system/pdl/hci/Android.bp b/system/pdl/hci/Android.bp index 354d500300..832598aaff 100644 --- a/system/pdl/hci/Android.bp +++ b/system/pdl/hci/Android.bp @@ -19,9 +19,7 @@ cc_library_headers { "BluetoothGeneratedPacketsHci_h", ], host_supported: true, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "33", } @@ -48,9 +46,7 @@ cc_library_static { "//hardware/interfaces/bluetooth/aidl/vts", "//packages/modules/Bluetooth/system:__subpackages__", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "33", } diff --git a/system/pdl/l2cap/Android.bp b/system/pdl/l2cap/Android.bp index d55935995c..bbd499612e 100644 --- a/system/pdl/l2cap/Android.bp +++ b/system/pdl/l2cap/Android.bp @@ -15,9 +15,7 @@ cc_library_headers { "BluetoothGeneratedPacketsL2cap_h", ], host_supported: true, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "33", } @@ -30,8 +28,6 @@ cc_library_static { "libbluetooth_l2cap_pdl_header", ], host_supported: true, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "33", } diff --git a/system/pdl/ras/Android.bp b/system/pdl/ras/Android.bp index d54d753b77..84e516c64f 100644 --- a/system/pdl/ras/Android.bp +++ b/system/pdl/ras/Android.bp @@ -15,9 +15,7 @@ cc_library_headers { "BluetoothGeneratedPacketsRas_h", ], host_supported: true, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "33", } @@ -30,8 +28,6 @@ cc_library_static { "libbluetooth_ras_pdl_header", ], host_supported: true, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "33", } diff --git a/system/pdl/security/Android.bp b/system/pdl/security/Android.bp index ebd5a3d8db..fbbc2b7339 100644 --- a/system/pdl/security/Android.bp +++ b/system/pdl/security/Android.bp @@ -15,9 +15,7 @@ cc_library_headers { "BluetoothGeneratedPacketsSmp_h", ], host_supported: true, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "33", } @@ -30,9 +28,7 @@ cc_library_static { "libbluetooth_smp_pdl_header", ], host_supported: true, - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "33", } diff --git a/system/profile/avrcp/Android.bp b/system/profile/avrcp/Android.bp index 8db8f11b2a..6ea0ea1abb 100644 --- a/system/profile/avrcp/Android.bp +++ b/system/profile/avrcp/Android.bp @@ -38,9 +38,7 @@ cc_library_static { shared_libs: [ "liblog", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", header_libs: ["libbluetooth_headers"], } diff --git a/system/profile/avrcp/device.cc b/system/profile/avrcp/device.cc index ef4fcda929..d094108d8a 100644 --- a/system/profile/avrcp/device.cc +++ b/system/profile/avrcp/device.cc @@ -766,6 +766,7 @@ void Device::GetElementAttributesResponse(uint8_t label, SongInfo info) { auto get_element_attributes_pkt = pkt; auto attributes_requested = get_element_attributes_pkt->GetAttributesRequested(); + bool all_attributes_flag = com::android::bluetooth::flags::get_all_element_attributes_empty(); auto response = GetElementAttributesResponseBuilder::MakeBuilder(ctrl_mtu_); @@ -780,10 +781,12 @@ void Device::GetElementAttributesResponse(uint8_t label, for (const auto& attribute : attributes_requested) { if (info.attributes.find(attribute) != info.attributes.end()) { response->AddAttributeEntry(*info.attributes.find(attribute)); + } else if (all_attributes_flag) { + response->AddAttributeEntry(attribute, std::string()); } } } else { // zero attributes requested which means all attributes requested - if (!com::android::bluetooth::flags::get_all_element_attributes_empty()) { + if (!all_attributes_flag) { for (const auto& attribute : info.attributes) { response->AddAttributeEntry(attribute); } diff --git a/system/rust/Android.bp b/system/rust/Android.bp index 592a686216..59ef358330 100644 --- a/system/rust/Android.bp +++ b/system/rust/Android.bp @@ -58,7 +58,7 @@ rust_defaults { ], }, }, - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], } rust_ffi_static { @@ -123,7 +123,7 @@ cc_library_static { ], host_supported: true, generated_sources: ["libbluetooth_core_rs_bridge_codegen"], - apex_available: ["com.android.btservices"], + apex_available: ["com.android.bt"], min_sdk_version: "Tiramisu", // Bug: 286537287 this library gets linked with Rust objects but cross-language LTO // isn't supported yet. diff --git a/system/rust/src/core/shared_box.rs b/system/rust/src/core/shared_box.rs index 5dd1118185..accd1ae6ec 100644 --- a/system/rust/src/core/shared_box.rs +++ b/system/rust/src/core/shared_box.rs @@ -79,7 +79,7 @@ impl<T: ?Sized> Clone for WeakBox<T> { /// A strong reference to the contents within a SharedBox<>. pub struct WeakBoxRef<'a, T: ?Sized>(&'a T, Weak<T>); -impl<'a, T: ?Sized> WeakBoxRef<'a, T> { +impl<T: ?Sized> WeakBoxRef<'_, T> { /// Downgrade to a weak reference (with static lifetime) to the contents /// within the underlying SharedBox<> pub fn downgrade(&self) -> WeakBox<T> { @@ -87,7 +87,7 @@ impl<'a, T: ?Sized> WeakBoxRef<'a, T> { } } -impl<'a, T: ?Sized> Deref for WeakBoxRef<'a, T> { +impl<T: ?Sized> Deref for WeakBoxRef<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -95,7 +95,7 @@ impl<'a, T: ?Sized> Deref for WeakBoxRef<'a, T> { } } -impl<'a, T: ?Sized> Clone for WeakBoxRef<'a, T> { +impl<T: ?Sized> Clone for WeakBoxRef<'_, T> { fn clone(&self) -> Self { Self(self.0, self.1.clone()) } diff --git a/system/stack/Android.bp b/system/stack/Android.bp index 2d690133f3..841e7a93ef 100644 --- a/system/stack/Android.bp +++ b/system/stack/Android.bp @@ -24,9 +24,7 @@ cc_library_static { shared_libs: [ "libchrome", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], host_supported: true, min_sdk_version: "Tiramisu", } @@ -150,9 +148,7 @@ cc_library_static { "libldacBT_abr", "libldacBT_enc", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], host_supported: true, min_sdk_version: "Tiramisu", } @@ -342,9 +338,7 @@ cc_library_static { "libPlatformProperties", "libcrypto", ], - apex_available: [ - "com.android.btservices", - ], + apex_available: ["com.android.bt"], host_supported: true, min_sdk_version: "Tiramisu", } @@ -355,6 +349,10 @@ cc_defaults { defaults: [ "bluetooth_cflags", ], + cflags: [ + "-Wno-missing-prototypes", + "-Wno-unused-parameter", + ], include_dirs: [ "packages/modules/Bluetooth/system", "packages/modules/Bluetooth/system/gd", @@ -1105,6 +1103,7 @@ cc_test { ":TestMockRustFfi", ":TestMockStackArbiter", ":TestMockStackBtm", + ":TestMockStackConnMgr", ":TestMockStackSdp", "gatt/gatt_utils.cc", "test/common/mock_eatt.cc", @@ -1639,6 +1638,7 @@ cc_test { ], static_libs: [ "bluetooth_flags_c_lib_for_test", + "libaconfig_storage_read_api_cc", "libbase", "libbluetooth-types", "libbluetooth_gd", @@ -1655,7 +1655,6 @@ cc_test { "libprotobuf-cpp-lite", "libstatslog_bt", "server_configurable_flags", - "libaconfig_storage_read_api_cc", ], target: { android: { @@ -1887,6 +1886,7 @@ cc_test { "test/hid/stack_hid_test.cc", ], static_libs: [ + "bluetooth_flags_c_lib_for_test", "libbase", "libbluetooth-types", "libbluetooth_crypto_toolbox", @@ -1905,6 +1905,7 @@ cc_test { "libprotobuf-cpp-lite", ], shared_libs: [ + "libaconfig_storage_read_api_cc", "libcrypto", ], target: { @@ -1962,6 +1963,7 @@ cc_test { ], static_libs: [ "bluetooth_flags_c_lib_for_test", + "libaconfig_storage_read_api_cc", "libbase", "libbluetooth-types", "libbluetooth_gd", @@ -1977,7 +1979,6 @@ cc_test { "liblog", "libosi", "server_configurable_flags", - "libaconfig_storage_read_api_cc", ], aidl: { libs: ["bluetooth_constants"], diff --git a/system/stack/acl/ble_acl.cc b/system/stack/acl/ble_acl.cc index 683e4801cd..0aeb9ceb91 100644 --- a/system/stack/acl/ble_acl.cc +++ b/system/stack/acl/ble_acl.cc @@ -50,9 +50,6 @@ static bool acl_ble_common_connection(const tBLE_BD_ADDR& address_with_type, uin btm_ble_clear_topology_mask(BTM_BLE_STATE_INIT_BIT); } - // Inform any applications that a connection has completed. - connection_manager::on_connection_complete(address_with_type.bda); - // Allocate or update the security device record for this device btm_ble_connected(address_with_type.bda, handle, HCI_ENCRYPT_MODE_DISABLED, role, address_with_type.type, is_in_security_db, @@ -108,8 +105,6 @@ void acl_ble_enhanced_connection_complete_from_shim( uint16_t conn_interval, uint16_t conn_latency, uint16_t conn_timeout, const RawAddress& local_rpa, const RawAddress& peer_rpa, tBLE_ADDR_TYPE peer_addr_type, bool can_read_discoverable_characteristics) { - connection_manager::on_connection_complete(address_with_type.bda); - tBLE_BD_ADDR resolved_address_with_type; const bool is_in_security_db = maybe_resolve_received_address(address_with_type, &resolved_address_with_type); diff --git a/system/stack/btm/btm_ble_gap.cc b/system/stack/btm/btm_ble_gap.cc index 4b9cc79e32..6e16d43c17 100644 --- a/system/stack/btm/btm_ble_gap.cc +++ b/system/stack/btm/btm_ble_gap.cc @@ -1511,9 +1511,8 @@ static void btm_ble_scan_filt_param_cfg_evt(uint8_t /* avbl_space */, * If the duration is zero, the periodic inquiry mode is * cancelled. * - * Parameters: duration - Duration of inquiry in seconds. With flag - * le_inquiry_duration duration is a multiplier for - * 1.28 seconds. + * Parameters: duration - Duration of inquiry as a multiplier for 1.28 + * seconds. * * Returns tBTM_STATUS::BTM_CMD_STARTED if successfully started * tBTM_STATUS::BTM_BUSY - if an inquiry is already active @@ -1559,10 +1558,6 @@ tBTM_STATUS btm_ble_start_inquiry(uint8_t duration) { } else if ((btm_cb.ble_ctr_cb.inq_var.scan_interval != scan_interval) || (btm_cb.ble_ctr_cb.inq_var.scan_window != scan_window)) { log::verbose("restart LE scan with low latency scan params"); - if (!com::android::bluetooth::flags::le_inquiry_duration()) { - btm_cb.ble_ctr_cb.inq_var.scan_interval = scan_interval; - btm_cb.ble_ctr_cb.inq_var.scan_window = scan_window; - } btm_send_hci_scan_enable(BTM_BLE_SCAN_DISABLE, BTM_BLE_DUPLICATE_ENABLE); btm_send_hci_set_scan_params(BTM_BLE_SCAN_MODE_ACTI, scan_interval, scan_window, scan_phy, btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type, SP_ADV_ALL); @@ -1576,8 +1571,7 @@ tBTM_STATUS btm_ble_start_inquiry(uint8_t duration) { if (duration != 0) { /* start inquiry timer */ - uint64_t duration_ms = - duration * (com::android::bluetooth::flags::le_inquiry_duration() ? 1280 : 1000); + uint64_t duration_ms = duration * 1280; alarm_set_on_mloop(btm_cb.ble_ctr_cb.inq_var.inquiry_timer, duration_ms, btm_ble_inquiry_timer_timeout, NULL); } @@ -1924,7 +1918,7 @@ static DEV_CLASS btm_ble_appearance_to_cod(uint16_t appearance) { dev_class[1] = BTM_COD_MAJOR_PERIPHERAL; dev_class[2] = BTM_COD_MINOR_DIGITAL_PAN; break; - case BTM_BLE_APPEARANCE_UKNOWN: + case BTM_BLE_APPEARANCE_UNKNOWN: case BTM_BLE_APPEARANCE_GENERIC_CLOCK: case BTM_BLE_APPEARANCE_GENERIC_TAG: case BTM_BLE_APPEARANCE_GENERIC_KEYRING: diff --git a/system/stack/btm/btm_ble_sec.cc b/system/stack/btm/btm_ble_sec.cc index 2ab87a0573..e05091f656 100644 --- a/system/stack/btm/btm_ble_sec.cc +++ b/system/stack/btm/btm_ble_sec.cc @@ -1595,7 +1595,7 @@ void btm_ble_connection_established(const RawAddress& bda) { } // Encrypt the link if device is bonded - if (com::android::bluetooth::flags::le_enc_on_reconnection() && + if (com::android::bluetooth::flags::le_enc_on_reconnect() && p_dev_rec->sec_rec.is_le_link_key_known()) { btm_ble_set_encryption(bda, BTM_BLE_SEC_ENCRYPT, p_dev_rec->role_central ? HCI_ROLE_CENTRAL : HCI_ROLE_PERIPHERAL); diff --git a/system/stack/btm/btm_sec.cc b/system/stack/btm/btm_sec.cc index 47dda744d7..63be75bffd 100644 --- a/system/stack/btm/btm_sec.cc +++ b/system/stack/btm/btm_sec.cc @@ -35,6 +35,8 @@ #include <cstdint> #include <string> +#include "bta/dm/bta_dm_act.h" +#include "bta/dm/bta_dm_sec_int.h" #include "btif/include/btif_storage.h" #include "common/metrics.h" #include "common/time_util.h" @@ -103,9 +105,6 @@ extern tBTM_CB btm_cb; bool btm_ble_init_pseudo_addr(tBTM_SEC_DEV_REC* p_dev_rec, const RawAddress& new_pseudo_addr); void bta_dm_remove_device(const RawAddress& bd_addr); -void bta_dm_on_encryption_change(bt_encryption_change_evt encryption_change); -void bta_dm_remote_key_missing(const RawAddress bd_addr); -void bta_dm_process_remove_device(const RawAddress& bd_addr); static tBTM_STATUS btm_sec_execute_procedure(tBTM_SEC_DEV_REC* p_dev_rec); static bool btm_sec_start_get_name(tBTM_SEC_DEV_REC* p_dev_rec); @@ -1029,7 +1028,7 @@ tBTM_STATUS BTM_SetEncryption(const RawAddress& bd_addr, tBT_TRANSPORT transport : p_dev_rec->sec_rec.classic_link; /* Enqueue security request if security is active */ - if (!com::android::bluetooth::flags::le_enc_on_reconnection()) { + if (!com::android::bluetooth::flags::le_enc_on_reconnect()) { if (p_dev_rec->sec_rec.p_callback || (p_dev_rec->sec_rec.le_link != tSECURITY_STATE::IDLE && p_dev_rec->sec_rec.classic_link != tSECURITY_STATE::IDLE)) { @@ -1099,9 +1098,9 @@ tBTM_STATUS BTM_SetEncryption(const RawAddress& bd_addr, tBT_TRANSPORT transport return rc; } -bool BTM_SecIsSecurityPending(const RawAddress& bd_addr) { +bool BTM_SecIsLeSecurityPending(const RawAddress& bd_addr) { tBTM_SEC_DEV_REC* p_dev_rec = btm_find_dev(bd_addr); - return p_dev_rec && (p_dev_rec->sec_rec.is_security_state_encrypting() || + return p_dev_rec && (p_dev_rec->sec_rec.is_security_state_le_encrypting() || p_dev_rec->sec_rec.le_link == tSECURITY_STATE::AUTHENTICATING); } @@ -4974,7 +4973,7 @@ static void btm_sec_check_pending_enc_req(tBTM_SEC_DEV_REC* p_dev_rec, tBT_TRANS node = list_next(node); log::debug("btm_sec_check_pending_enc_req : sec_act=0x{:x}", p_e->sec_act); if (p_e->bd_addr == p_dev_rec->bd_addr && p_e->psm == 0 && p_e->transport == transport) { - if (!com::android::bluetooth::flags::le_enc_on_reconnection()) { + if (!com::android::bluetooth::flags::le_enc_on_reconnect()) { if (encr_enable == 0 || transport == BT_TRANSPORT_BR_EDR || p_e->sec_act == BTM_BLE_SEC_ENCRYPT || p_e->sec_act == BTM_BLE_SEC_ENCRYPT_NO_MITM || (p_e->sec_act == BTM_BLE_SEC_ENCRYPT_MITM && diff --git a/system/stack/btm/btm_sec.h b/system/stack/btm/btm_sec.h index 9646ada250..117edc0c3c 100644 --- a/system/stack/btm/btm_sec.h +++ b/system/stack/btm/btm_sec.h @@ -243,7 +243,7 @@ tBTM_STATUS BTM_SetEncryption(const RawAddress& bd_addr, tBT_TRANSPORT transport tBTM_SEC_CALLBACK* p_callback, void* p_ref_data, tBTM_BLE_SEC_ACT sec_act); -bool BTM_SecIsSecurityPending(const RawAddress& bd_addr); +bool BTM_SecIsLeSecurityPending(const RawAddress& bd_addr); /******************************************************************************* * diff --git a/system/stack/btm/btm_security_client_interface.cc b/system/stack/btm/btm_security_client_interface.cc index f2d397a9a7..3adda4d0f8 100644 --- a/system/stack/btm/btm_security_client_interface.cc +++ b/system/stack/btm/btm_security_client_interface.cc @@ -54,7 +54,7 @@ static SecurityClientInterface security = { .BTM_SecClearSecurityFlags = BTM_SecClearSecurityFlags, .BTM_SetEncryption = BTM_SetEncryption, .BTM_IsEncrypted = BTM_IsEncrypted, - .BTM_SecIsSecurityPending = BTM_SecIsSecurityPending, + .BTM_SecIsLeSecurityPending = BTM_SecIsLeSecurityPending, .BTM_IsLinkKeyKnown = BTM_IsLinkKeyKnown, .BTM_SetSecurityLevel = BTM_SetSecurityLevel, diff --git a/system/stack/connection_manager/connection_manager.cc b/system/stack/connection_manager/connection_manager.cc index 25c9ee57ac..ed223b92a8 100644 --- a/system/stack/connection_manager/connection_manager.cc +++ b/system/stack/connection_manager/connection_manager.cc @@ -52,6 +52,8 @@ struct closure_data { base::Location posted_from; }; +extern std::string get_client_name(uint8_t gatt_if); + static void alarm_closure_cb(void* p) { closure_data* data = (closure_data*)p; log::verbose("executing timer scheduled at {}", data->posted_from.ToString()); @@ -367,7 +369,7 @@ bool background_connect_remove(uint8_t app_id, const RawAddress& address) { } if (is_anyone_connecting(it)) { - log::debug("some device is still connecting, app_id={}, address={}", static_cast<int>(app_id), + log::debug("some app is still connecting, app_id={}, address={}", static_cast<int>(app_id), address); /* Check which method should be used now.*/ if (!accept_list_enabled) { @@ -627,14 +629,14 @@ void dump(int fd) { if (!entry.second.doing_direct_conn.empty()) { dprintf(fd, "\n\t\tapps doing direct connect: "); for (const auto& id : entry.second.doing_direct_conn) { - dprintf(fd, "%d, ", id.first); + dprintf(fd, "%s (%d), ", get_client_name(id.first).c_str(), id.first); } } if (!entry.second.doing_bg_conn.empty()) { dprintf(fd, "\n\t\tapps doing background connect: "); for (const auto& id : entry.second.doing_bg_conn) { - dprintf(fd, "%d, ", id); + dprintf(fd, "%s (%d), ", get_client_name(id).c_str(), id); } } } diff --git a/system/stack/connection_manager/connection_manager.h b/system/stack/connection_manager/connection_manager.h index 0f029fbe60..5328b4e7cb 100644 --- a/system/stack/connection_manager/connection_manager.h +++ b/system/stack/connection_manager/connection_manager.h @@ -23,6 +23,9 @@ #include "types/ble_address_with_type.h" #include "types/raw_address.h" +/* Must be provided by stack to connection manager, so it can dump nice client names in dumpsys */ +std::string get_client_name(uint8_t gatt_if); + /* connection_manager takes care of all the low-level details of LE connection * initiation. It accept requests from multiple subsystems to connect to * devices, and multiplex them into acceptlist add/remove, and scan parameter diff --git a/system/stack/fuzzers/avrc_fuzzer.cc b/system/stack/fuzzers/avrc_fuzzer.cc index be0c80f32c..d2c4acf4f8 100644 --- a/system/stack/fuzzers/avrc_fuzzer.cc +++ b/system/stack/fuzzers/avrc_fuzzer.cc @@ -33,10 +33,6 @@ #include "test/mock/mock_stack_l2cap_api.h" #include "types/bluetooth/uuid.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" -#pragma GCC diagnostic ignored "-Wunused-parameter" - using bluetooth::Uuid; using namespace bluetooth; diff --git a/system/stack/fuzzers/bnep_fuzzer.cc b/system/stack/fuzzers/bnep_fuzzer.cc index 79a70556e4..6976ccd602 100644 --- a/system/stack/fuzzers/bnep_fuzzer.cc +++ b/system/stack/fuzzers/bnep_fuzzer.cc @@ -31,10 +31,6 @@ #include "test/mock/mock_stack_l2cap_ble.h" #include "types/bluetooth/uuid.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" -#pragma GCC diagnostic ignored "-Wunused-parameter" - using bluetooth::Uuid; namespace { diff --git a/system/stack/fuzzers/gatt_fuzzer.cc b/system/stack/fuzzers/gatt_fuzzer.cc index 3a9fb81276..064a203a81 100644 --- a/system/stack/fuzzers/gatt_fuzzer.cc +++ b/system/stack/fuzzers/gatt_fuzzer.cc @@ -33,11 +33,8 @@ #include "test/mock/mock_stack_l2cap_api.h" #include "test/mock/mock_stack_l2cap_ble.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" -#pragma GCC diagnostic ignored "-Wunused-parameter" - using bluetooth::Uuid; + bt_status_t do_in_main_thread(base::OnceCallback<void()>) { // this is not properly mocked, so we use abort to catch if this is used in // any test cases diff --git a/system/stack/fuzzers/l2cap_fuzzer.cc b/system/stack/fuzzers/l2cap_fuzzer.cc index 004e5b0920..6cb4d5170f 100644 --- a/system/stack/fuzzers/l2cap_fuzzer.cc +++ b/system/stack/fuzzers/l2cap_fuzzer.cc @@ -42,9 +42,6 @@ #include "test/mock/mock_stack_acl.h" #include "test/mock/mock_stack_btm_devctl.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" - using bluetooth::Uuid; using testing::Return; using namespace bluetooth; diff --git a/system/stack/fuzzers/rfcomm_fuzzer.cc b/system/stack/fuzzers/rfcomm_fuzzer.cc index 1445156aec..3418f3a6ed 100644 --- a/system/stack/fuzzers/rfcomm_fuzzer.cc +++ b/system/stack/fuzzers/rfcomm_fuzzer.cc @@ -39,10 +39,6 @@ #include "test/mock/mock_stack_l2cap_interface.h" #include "test/rfcomm/stack_rfcomm_test_utils.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" -#pragma GCC diagnostic ignored "-Wunused-parameter" - using ::testing::_; using ::testing::NiceMock; using ::testing::Return; diff --git a/system/stack/fuzzers/sdp_fuzzer.cc b/system/stack/fuzzers/sdp_fuzzer.cc index bd5b6f77c2..de994d415e 100644 --- a/system/stack/fuzzers/sdp_fuzzer.cc +++ b/system/stack/fuzzers/sdp_fuzzer.cc @@ -31,10 +31,6 @@ #include "test/mock/mock_stack_l2cap_api.h" #include "types/bluetooth/uuid.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" -#pragma GCC diagnostic ignored "-Wunused-parameter" - namespace { #define SDP_DB_SIZE 0x10000 diff --git a/system/stack/fuzzers/smp_fuzzer.cc b/system/stack/fuzzers/smp_fuzzer.cc index a99010254a..b4dad542ea 100644 --- a/system/stack/fuzzers/smp_fuzzer.cc +++ b/system/stack/fuzzers/smp_fuzzer.cc @@ -33,13 +33,10 @@ #include "test/mock/mock_stack_l2cap_api.h" #include "test/mock/mock_stack_l2cap_ble.h" -// TODO(b/369381361) Enfore -Wmissing-prototypes -#pragma GCC diagnostic ignored "-Wmissing-prototypes" -#pragma GCC diagnostic ignored "-Wunused-parameter" - bluetooth::common::MessageLoopThread* main_thread_ptr = nullptr; bluetooth::common::MessageLoopThread* get_main_thread() { return main_thread_ptr; } + namespace { #define SDP_DB_SIZE 0x10000 diff --git a/system/stack/gatt/gatt_api.cc b/system/stack/gatt/gatt_api.cc index 19ea3a3090..3e10a8b660 100644 --- a/system/stack/gatt/gatt_api.cc +++ b/system/stack/gatt/gatt_api.cc @@ -1486,14 +1486,7 @@ bool GATT_Connect(tGATT_IF gatt_if, const RawAddress& bd_addr, tBLE_ADDR_TYPE ad /* Consider to remove gatt_act_connect at all */ ret = gatt_act_connect(p_reg, bd_addr, addr_type, transport, initiating_phys); } else { - log::verbose("Connecting without tcb address: {}", bd_addr); - - if (p_reg->direct_connect_request.count(bd_addr) == 0) { - p_reg->direct_connect_request.insert(bd_addr); - } else { - log::warn("{} already added to gatt_if {} direct conn list", bd_addr, gatt_if); - } - + log::verbose("Connecting without tcb to: {}", bd_addr); ret = connection_manager::direct_connect_add(gatt_if, bd_addr, addr_type); } diff --git a/system/stack/gatt/gatt_int.h b/system/stack/gatt/gatt_int.h index 70ff37546f..4ba6d7d127 100644 --- a/system/stack/gatt/gatt_int.h +++ b/system/stack/gatt/gatt_int.h @@ -193,7 +193,6 @@ typedef struct { uint8_t listening{0}; /* if adv for all has been enabled */ bool eatt_support{false}; std::string name; - std::set<RawAddress> direct_connect_request; std::map<RawAddress, uint16_t> mtu_prefs; } tGATT_REG; @@ -490,7 +489,6 @@ extern bluetooth::common::TimestampedCircularBuffer<tTCB_STATE_HISTORY> tcb_stat /* from gatt_main.cc */ bool gatt_disconnect(tGATT_TCB* p_tcb); -void gatt_cancel_connect(const RawAddress& bd_addr, tBT_TRANSPORT transport); bool gatt_act_connect(tGATT_REG* p_reg, const RawAddress& bd_addr, tBT_TRANSPORT transport, int8_t initiating_phys); bool gatt_act_connect(tGATT_REG* p_reg, const RawAddress& bd_addr, tBLE_ADDR_TYPE addr_type, @@ -613,7 +611,7 @@ bool gatt_is_pending_mtu_exchange(tGATT_TCB* p_tcb); void gatt_set_conn_id_waiting_for_mtu_exchange(tGATT_TCB* p_tcb, tCONN_ID conn_id); void gatt_sr_copy_prep_cnt_to_cback_cnt(tGATT_TCB& p_tcb); -bool gatt_sr_is_cback_cnt_zero(tGATT_TCB& p_tcb); +bool gatt_sr_is_cback_cnt_zero(tGATT_TCB& p_tcb, uint16_t cid); bool gatt_sr_is_prep_cnt_zero(tGATT_TCB& p_tcb); void gatt_sr_reset_cback_cnt(tGATT_TCB& p_tcb, uint16_t cid); void gatt_sr_reset_prep_cnt(tGATT_TCB& tcb); diff --git a/system/stack/gatt/gatt_main.cc b/system/stack/gatt/gatt_main.cc index 0d8d51d846..2688cfa582 100644 --- a/system/stack/gatt/gatt_main.cc +++ b/system/stack/gatt/gatt_main.cc @@ -240,32 +240,6 @@ static bool gatt_connect(const RawAddress& rem_bda, tBLE_ADDR_TYPE addr_type, tG /******************************************************************************* * - * Function gatt_cancel_connect - * - * Description This will remove device from allow list and cancel connection - * - * Parameter bd_addr: peer device address. - * transport: transport - * - * - ******************************************************************************/ -void gatt_cancel_connect(const RawAddress& bd_addr, tBT_TRANSPORT transport) { - /* This shall be call only when device is not connected */ - log::debug("{}, transport {}", bd_addr, transport); - - if (!connection_manager::direct_connect_remove(CONN_MGR_ID_L2CAP, bd_addr)) { - bluetooth::shim::ACL_IgnoreLeConnectionFrom(BTM_Sec_GetAddressWithType(bd_addr)); - log::info( - "GATT connection manager has no record but removed filter " - "acceptlist gatt_if:{} peer:{}", - static_cast<uint8_t>(CONN_MGR_ID_L2CAP), bd_addr); - } - - gatt_cleanup_upon_disc(bd_addr, GATT_CONN_TERMINATE_LOCAL_HOST, transport); -} - -/******************************************************************************* - * * Function gatt_disconnect * * Description This function is called to disconnect to an ATT device. @@ -290,29 +264,42 @@ bool gatt_disconnect(tGATT_TCB* p_tcb) { return true; } - if (p_tcb->att_lcid == L2CAP_ATT_CID) { - if (ch_state == GATT_CH_OPEN) { - if (com::android::bluetooth::flags::gatt_disconnect_fix() && p_tcb->eatt) { - /* ATT is fixed channel and it is expected to drop ACL. - * Make sure all EATT channels are disconnected before doing that. - */ - EattExtension::GetInstance()->Disconnect(p_tcb->peer_bda); - } - if (!stack::l2cap::get_interface().L2CA_RemoveFixedChnl(L2CAP_ATT_CID, p_tcb->peer_bda)) { - log::warn("Unable to remove L2CAP ATT fixed channel peer:{}", p_tcb->peer_bda); - } - gatt_set_ch_state(p_tcb, GATT_CH_CLOSING); - } else { - gatt_cancel_connect(p_tcb->peer_bda, p_tcb->transport); - } - } else { + if (p_tcb->att_lcid != L2CAP_ATT_CID) { if ((ch_state == GATT_CH_OPEN) || (ch_state == GATT_CH_CFG)) { gatt_l2cif_disconnect(p_tcb->att_lcid); } else { log::verbose("gatt_disconnect channel not opened"); } + return true; + } + + /* att_lcid == L2CAP_ATT_CID */ + + if (ch_state != GATT_CH_OPEN) { + if (!connection_manager::direct_connect_remove(CONN_MGR_ID_L2CAP, p_tcb->peer_bda)) { + bluetooth::shim::ACL_IgnoreLeConnectionFrom(BTM_Sec_GetAddressWithType(p_tcb->peer_bda)); + log::info( + "GATT connection manager has no record but removed filter " + "acceptlist gatt_if:{} peer:{}", + static_cast<uint8_t>(CONN_MGR_ID_L2CAP), p_tcb->peer_bda); + } + + gatt_cleanup_upon_disc(p_tcb->peer_bda, GATT_CONN_TERMINATE_LOCAL_HOST, p_tcb->transport); + return true; + } + + if (com::android::bluetooth::flags::gatt_disconnect_fix() && p_tcb->eatt) { + /* ATT is fixed channel and it is expected to drop ACL. + * Make sure all EATT channels are disconnected before doing that. + */ + EattExtension::GetInstance()->Disconnect(p_tcb->peer_bda); + } + + if (!stack::l2cap::get_interface().L2CA_RemoveFixedChnl(L2CAP_ATT_CID, p_tcb->peer_bda)) { + log::warn("Unable to remove L2CAP ATT fixed channel peer:{}", p_tcb->peer_bda); } + gatt_set_ch_state(p_tcb, GATT_CH_CLOSING); return true; } @@ -1004,13 +991,6 @@ static void gatt_send_conn_cback(tGATT_TCB* p_tcb) { gatt_update_app_use_link_flag(p_reg->gatt_if, p_tcb, true, true); } - if (p_reg->direct_connect_request.count(p_tcb->peer_bda) > 0) { - gatt_update_app_use_link_flag(p_reg->gatt_if, p_tcb, true, true); - log::info("Removing device {} from the direct connect list of gatt_if {}", p_tcb->peer_bda, - p_reg->gatt_if); - p_reg->direct_connect_request.erase(p_tcb->peer_bda); - } - if (p_reg->app_cb.p_conn_cb) { conn_id = gatt_create_conn_id(p_tcb->tcb_idx, p_reg->gatt_if); (*p_reg->app_cb.p_conn_cb)(p_reg->gatt_if, p_tcb->peer_bda, conn_id, kGattConnected, @@ -1027,13 +1007,6 @@ static void gatt_send_conn_cback(tGATT_TCB* p_tcb) { gatt_update_app_use_link_flag(p_reg->gatt_if, p_tcb, true, true); } - if (p_reg->direct_connect_request.count(p_tcb->peer_bda) > 0) { - gatt_update_app_use_link_flag(p_reg->gatt_if, p_tcb, true, true); - log::info("Removing device {} from the direct connect list of gatt_if {}", p_tcb->peer_bda, - p_reg->gatt_if); - p_reg->direct_connect_request.erase(p_tcb->peer_bda); - } - if (p_reg->app_cb.p_conn_cb) { conn_id = gatt_create_conn_id(p_tcb->tcb_idx, p_reg->gatt_if); (*p_reg->app_cb.p_conn_cb)(p_reg->gatt_if, p_tcb->peer_bda, conn_id, kGattConnected, diff --git a/system/stack/gatt/gatt_sr.cc b/system/stack/gatt/gatt_sr.cc index 49c1ae452d..133080c1db 100644 --- a/system/stack/gatt/gatt_sr.cc +++ b/system/stack/gatt/gatt_sr.cc @@ -338,7 +338,7 @@ tGATT_STATUS gatt_sr_process_app_rsp(tGATT_TCB& tcb, tGATT_IF gatt_if, uint32_t sr_res_p->status = status; - if (gatt_sr_is_cback_cnt_zero(tcb) && status == GATT_SUCCESS) { + if (gatt_sr_is_cback_cnt_zero(tcb, sr_res_p->cid) && status == GATT_SUCCESS) { if (sr_res_p->p_rsp_msg == NULL) { sr_res_p->p_rsp_msg = attp_build_sr_msg(tcb, (uint8_t)(op_code + 1), (tGATT_SR_MSG*)p_msg, payload_size); @@ -347,7 +347,7 @@ tGATT_STATUS gatt_sr_process_app_rsp(tGATT_TCB& tcb, tGATT_IF gatt_if, uint32_t } } } - if (gatt_sr_is_cback_cnt_zero(tcb)) { + if (gatt_sr_is_cback_cnt_zero(tcb, sr_res_p->cid)) { if ((sr_res_p->status == GATT_SUCCESS) && (sr_res_p->p_rsp_msg)) { ret_code = attp_send_sr_msg(tcb, sr_res_p->cid, sr_res_p->p_rsp_msg); sr_res_p->p_rsp_msg = NULL; diff --git a/system/stack/gatt/gatt_utils.cc b/system/stack/gatt/gatt_utils.cc index 88ecfe2d53..b0d18f892e 100644 --- a/system/stack/gatt/gatt_utils.cc +++ b/system/stack/gatt/gatt_utils.cc @@ -430,16 +430,26 @@ tGATT_TCB* gatt_find_tcb_by_addr(const RawAddress& bda, tBT_TRANSPORT transport) return p_tcb; } +/* This is for connection manager */ +std::string get_client_name(uint8_t gatt_if) { + if (gatt_if == CONN_MGR_ID_L2CAP) { + return "L2CAP"; + } + + tGATT_REG* reg = gatt_get_regcb(gatt_if); + return (reg == nullptr) ? "" : reg->name; +} + std::string gatt_tcb_get_holders_info_string(const tGATT_TCB* p_tcb) { std::stringstream stream; if (p_tcb->app_hold_link.size() == 0) { stream << "No ACL holders"; } else { - stream << "ACL holders gatt_if:"; + stream << "ACL holders gatt_if: "; for (auto gatt_if : p_tcb->app_hold_link) { - stream << static_cast<int>(gatt_if) << ","; + stream << get_client_name(gatt_if) << " (" << +gatt_if << "), "; } } return stream.str(); @@ -1362,17 +1372,30 @@ tGATT_SR_CMD* gatt_sr_get_cmd_by_trans_id(tGATT_TCB* p_tcb, uint32_t trans_id) { * Returns True if thetotal application callback count is zero * ******************************************************************************/ -bool gatt_sr_is_cback_cnt_zero(tGATT_TCB& tcb) { - if (com::android::bluetooth::flags::gatt_client_dynamic_allocation()) { - return tcb.sr_cmd.cback_cnt_map.empty(); +bool gatt_sr_is_cback_cnt_zero(tGATT_TCB& tcb, uint16_t cid) { + tGATT_SR_CMD* sr_cmd_p; + if (cid == tcb.att_lcid) { + sr_cmd_p = &tcb.sr_cmd; } else { - for (uint8_t i = 0; i < GATT_MAX_APPS; i++) { - if (tcb.sr_cmd.cback_cnt[i]) { - return false; - } + EattChannel* channel = EattExtension::GetInstance()->FindEattChannelByCid(tcb.peer_bda, cid); + if (channel == nullptr) { + log::warn("{}, cid 0x{:02x} already disconnected", tcb.peer_bda, cid); + return true; } - return true; + sr_cmd_p = &channel->server_outstanding_cmd_; + } + + if (com::android::bluetooth::flags::gatt_client_dynamic_allocation()) { + return sr_cmd_p->cback_cnt_map.empty(); } + + for (uint8_t i = 0; i < GATT_MAX_APPS; i++) { + if (sr_cmd_p->cback_cnt[i] != 0) { + return false; + } + } + + return true; } /******************************************************************************* @@ -1581,46 +1604,25 @@ void gatt_sr_update_prep_cnt(tGATT_TCB& tcb, tGATT_IF gatt_if, bool is_inc, bool } } -static bool gatt_is_anybody_interested_in_connection(const RawAddress& bda) { - if (connection_manager::is_background_connection(bda)) { - log::debug("{} is in background connection", bda); - return true; +/** Cancel LE Create Connection request */ +bool gatt_cancel_open(tGATT_IF gatt_if, const RawAddress& bda) { + if (connection_manager::direct_connect_remove(gatt_if, bda)) { + log::info("{} was doing direct connect to {}, canceled", gatt_if, bda); } - - for (size_t i = 1; i <= GATT_MAX_APPS; i++) { - tGATT_REG* p_reg = &gatt_cb.cl_rcb[i - 1]; - if (p_reg->in_use && p_reg->direct_connect_request.count(bda) > 0) { - log::debug("gatt_if {} interested in connection to {}", i, bda); - return true; - } + if (connection_manager::background_connect_remove(gatt_if, bda)) { + log::info("{} was doing background connect to {}, canceled", gatt_if, bda); } - return false; -} -/** Cancel LE Create Connection request */ -bool gatt_cancel_open(tGATT_IF gatt_if, const RawAddress& bda) { tGATT_TCB* p_tcb = gatt_find_tcb_by_addr(bda, BT_TRANSPORT_LE); if (!p_tcb) { - /* TCB is not allocated when trying to connect under this flag. - * but device address is storred in the tGATT_REG. Make sure to remove - * the address from the list when cancel is called. - */ - - tGATT_REG* p_reg = gatt_get_regcb(gatt_if); - if (!p_reg) { - log::error("Unable to find registered app gatt_if={}", gatt_if); - } else { - log::info("Removing {} from direct list", bda); - p_reg->direct_connect_request.erase(bda); - } - if (!gatt_is_anybody_interested_in_connection(bda)) { - gatt_cancel_connect(bda, static_cast<tBT_TRANSPORT>(BT_TRANSPORT_LE)); + if (connection_manager::get_apps_connecting_to(bda).empty()) { + gatt_cleanup_upon_disc(bda, GATT_CONN_TERMINATE_LOCAL_HOST, BT_TRANSPORT_LE); } return true; } if (gatt_get_ch_state(p_tcb) == GATT_CH_OPEN) { - log::error("link connected Too late to cancel"); + log::error("link connected too late to cancel {}", bda); return false; } @@ -1632,24 +1634,6 @@ bool gatt_cancel_open(tGATT_IF gatt_if, const RawAddress& bda) { gatt_disconnect(p_tcb); } - if (!connection_manager::direct_connect_remove(gatt_if, bda)) { - if (!connection_manager::is_background_connection(bda)) { - if (!com::android::bluetooth::flags::gatt_fix_multiple_direct_connect() || - p_tcb->app_hold_link.empty()) { - bluetooth::shim::ACL_IgnoreLeConnectionFrom(BTM_Sec_GetAddressWithType(bda)); - } - log::info( - "Gatt connection manager has no background record but removed " - "filter acceptlist gatt_if:{} peer:{}", - gatt_if, bda); - } else { - log::info( - "Gatt connection manager maintains a background record preserving " - "filter acceptlist gatt_if:{} peer:{}", - gatt_if, bda); - } - } - return true; } @@ -1830,12 +1814,6 @@ static void gatt_disconnect_complete_notify_user(const RawAddress& bda, tGATT_DI (*p_reg->app_cb.p_conn_cb)(p_reg->gatt_if, bda, conn_id, kGattDisconnected, reason, transport); } - - if (p_reg->direct_connect_request.count(bda) > 0) { - log::info("Removing device {} from the direct connect list of gatt_if {}", bda, - p_reg->gatt_if); - p_reg->direct_connect_request.erase(bda); - } } } else { for (uint8_t i = 0; i < GATT_MAX_APPS; i++) { @@ -1846,12 +1824,6 @@ static void gatt_disconnect_complete_notify_user(const RawAddress& bda, tGATT_DI (*p_reg->app_cb.p_conn_cb)(p_reg->gatt_if, bda, conn_id, kGattDisconnected, reason, transport); } - - if (p_reg->direct_connect_request.count(bda) > 0) { - log::info("Removing device {} from the direct connect list of gatt_if {}", bda, - p_reg->gatt_if); - p_reg->direct_connect_request.erase(bda); - } } } } @@ -1906,6 +1878,9 @@ void gatt_cleanup_upon_disc(const RawAddress& bda, tGATT_DISCONN_REASON reason, gatt_free_pending_ind(p_tcb); fixed_queue_free(p_tcb->sr_cmd.multi_rsp_q, NULL); p_tcb->sr_cmd.multi_rsp_q = NULL; + if (p_tcb->sr_cmd.p_rsp_msg) { + osi_free_and_reset((void**)&p_tcb->sr_cmd.p_rsp_msg); + } gatt_disconnect_complete_notify_user(bda, reason, transport); diff --git a/system/stack/hid/hidh_conn.cc b/system/stack/hid/hidh_conn.cc index 83bb039173..d658c1ab29 100644 --- a/system/stack/hid/hidh_conn.cc +++ b/system/stack/hid/hidh_conn.cc @@ -24,6 +24,7 @@ #include <base/functional/callback.h> #include <bluetooth/log.h> +#include <com_android_bluetooth_flags.h> #include <frameworks/proto_logging/stats/enums/bluetooth/enums.pb.h> #include <string.h> @@ -70,6 +71,8 @@ static void hidh_l2cif_connect_cfm(uint16_t l2cap_cid, tL2CAP_CONN result); static void hidh_l2cif_config_ind(uint16_t l2cap_cid, tL2CAP_CFG_INFO* p_cfg); static void hidh_l2cif_config_cfm(uint16_t l2cap_cid, uint16_t result, tL2CAP_CFG_INFO* p_cfg); static void hidh_l2cif_disconnect_ind(uint16_t l2cap_cid, bool ack_needed); +static void hidh_l2cif_disconnect_cfm(uint16_t l2cap_cid, uint16_t result); +static void hidh_l2cif_disconnect_cfm_actual(uint16_t l2cap_cid, uint16_t result); static void hidh_l2cif_data_ind(uint16_t l2cap_cid, BT_HDR* p_msg); static void hidh_l2cif_disconnect(uint16_t l2cap_cid); static void hidh_l2cif_cong_ind(uint16_t l2cap_cid, bool congested); @@ -81,6 +84,7 @@ static const tL2CAP_APPL_INFO hst_reg_info = { .pL2CA_ConfigInd_Cb = hidh_l2cif_config_ind, .pL2CA_ConfigCfm_Cb = hidh_l2cif_config_cfm, .pL2CA_DisconnectInd_Cb = hidh_l2cif_disconnect_ind, + .pL2CA_DisconnectCfm_Cb = hidh_l2cif_disconnect_cfm, .pL2CA_DataInd_Cb = hidh_l2cif_data_ind, .pL2CA_CongestionStatus_Cb = hidh_l2cif_cong_ind, .pL2CA_TxComplete_Cb = nullptr, @@ -160,7 +164,7 @@ tHID_STATUS hidh_conn_disconnect(uint8_t dhandle) { BT_TRANSPORT_BR_EDR)) { log::warn("Unable to set L2CAP idle timeout peer:{}", hh_cb.devices[dhandle].addr); } - /* Disconnect both interrupt and control channels */ + /* Disconnect channels one by one */ if (p_hcon->intr_cid) { hidh_l2cif_disconnect(p_hcon->intr_cid); } else if (p_hcon->ctrl_cid) { @@ -553,11 +557,38 @@ static void hidh_l2cif_disconnect_ind(uint16_t l2cap_cid, bool ack_needed) { } } +// TODO: after disconnect_hid_channels_serially aflags is the default, +// remove this function and call L2CA_DisconnectReq directly. static void hidh_l2cif_disconnect(uint16_t l2cap_cid) { if (!stack::l2cap::get_interface().L2CA_DisconnectReq(l2cap_cid)) { log::warn("Unable to send L2CAP disconnect request cid:{}", l2cap_cid); } + if (!com::android::bluetooth::flags::disconnect_hid_channels_serially()) { + hidh_l2cif_disconnect_cfm_actual(l2cap_cid, 0); + } +} + +/******************************************************************************* + * + * Function hidh_l2cif_disconnect_cfm + * + * Description This function handles a disconnect confirm from L2CAP. This + * means our disconnection request has been acknowledged, so we + * can disconnect the other channel, if required. + * + * Returns void + * + ******************************************************************************/ +static void hidh_l2cif_disconnect_cfm(uint16_t l2cap_cid, uint16_t result) { + if (com::android::bluetooth::flags::disconnect_hid_channels_serially()) { + hidh_l2cif_disconnect_cfm_actual(l2cap_cid, result); + } +} + +// TODO: after disconnect_hid_channels_serially aflags is the default, +// copy the body to hidh_l2cif_disconnect_cfm and remove this. +static void hidh_l2cif_disconnect_cfm_actual(uint16_t l2cap_cid, uint16_t /* result */) { /* Find CCB based on CID */ const uint8_t dhandle = find_conn_by_cid(l2cap_cid); if (dhandle == kHID_HOST_MAX_DEVICES) { diff --git a/system/stack/include/ble_appearance.h b/system/stack/include/ble_appearance.h new file mode 100644 index 0000000000..6dbb32996c --- /dev/null +++ b/system/stack/include/ble_appearance.h @@ -0,0 +1,446 @@ +/****************************************************************************** + * + * Copyright (C) 2025 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 BLE_APPEARANCE_H +#define BLE_APPEARANCE_H + +/* BLE appearance values as per BT spec assigned numbers */ +/* Category[15:6] 0x000 */ +#define BLE_APPEARANCE_UNKNOWN 0x0000 + +/* Category[15:6] 0x001 */ +#define BLE_APPEARANCE_GENERIC_PHONE 0x0040 + +/* Category[15:6] 0x002 */ +#define BLE_APPEARANCE_GENERIC_COMPUTER 0x0080 + +#define BLE_APPEARANCE_DESKTOP_WORKSTATION 0x81 +#define BLE_APPEARANCE_SERVER_CLASS_COMPUTER 0x82 +#define BLE_APPEARANCE_LAPTOP 0x83 +#define BLE_APPEARANCE_HANDHELD_PC_PDA 0x84 +#define BLE_APPEARANCE_PALM_SIZE_PC_PDA 0x85 +#define BLE_APPEARANCE_WEARABLE_COMPUTER_WATCH_SIZE 0x86 +#define BLE_APPEARANCE_TABLET 0x87 +#define BLE_APPEARANCE_DOCKING_STATION 0x88 +#define BLE_APPEARANCE_ALL_IN_ONE 0x89 +#define BLE_APPEARANCE_BLADE_SERVER 0x8A +#define BLE_APPEARANCE_CONVERTIBLE 0x8B +#define BLE_APPEARANCE_DETACHABLE 0x8C +#define BLE_APPEARANCE_IOT_GATEWAY 0x8D +#define BLE_APPEARANCE_MINI_PC 0x8E +#define BLE_APPEARANCE_STICK_PC 0x8F + +/* Category[15:6] 0x003 */ +#define BLE_APPEARANCE_GENERIC_WATCH 0x00C0 +#define BLE_APPEARANCE_SPORTS_WATCH 0x00C1 +#define BLE_APPEARANCE_SMART_WATCH 0x00C2 + +/* Category[15:6] 0x004 */ +#define BLE_APPEARANCE_GENERIC_CLOCK 0x0100 + +/* Category[15:6] 0x005 */ +#define BLE_APPEARANCE_GENERIC_DISPLAY 0x0140 + +/* Category[15:6] 0x006 */ +#define BLE_APPEARANCE_GENERIC_REMOTE 0x0180 + +/* Category[15:6] 0x007 */ +#define BLE_APPEARANCE_GENERIC_EYEGLASSES 0x01C0 + +/* Category[15:6] 0x008 */ +#define BLE_APPEARANCE_GENERIC_TAG 0x0200 + +/* Category[15:6] 0x009 */ +#define BLE_APPEARANCE_GENERIC_KEYRING 0x0240 + +/* Category[15:6] 0x00A */ +#define BLE_APPEARANCE_GENERIC_MEDIA_PLAYER 0x0280 + +/* Category[15:6] 0x00B */ +#define BLE_APPEARANCE_GENERIC_BARCODE_SCANNER 0x02C0 + +/* Category[15:6] 0x00C */ +#define BLE_APPEARANCE_GENERIC_THERMOMETER 0x0300 +#define BLE_APPEARANCE_THERMOMETER_EAR 0x0301 + +/* Category[15:6] 0x00D */ +#define BLE_APPEARANCE_GENERIC_HEART_RATE 0x0340 +#define BLE_APPEARANCE_HEART_RATE_BELT 0x0341 + +/* Category[15:6] 0x00E */ +#define BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE 0x0380 +#define BLE_APPEARANCE_BLOOD_PRESSURE_ARM 0x0381 +#define BLE_APPEARANCE_BLOOD_PRESSURE_WRIST 0x0382 + +/* Category[15:6] 0x00F */ +#define BLE_APPEARANCE_GENERIC_HID 0x03C0 +#define BLE_APPEARANCE_HID_KEYBOARD 0x03C1 +#define BLE_APPEARANCE_HID_MOUSE 0x03C2 +#define BLE_APPEARANCE_HID_JOYSTICK 0x03C3 +#define BLE_APPEARANCE_HID_GAMEPAD 0x03C4 +#define BLE_APPEARANCE_HID_DIGITIZER_TABLET 0x03C5 +#define BLE_APPEARANCE_HID_CARD_READER 0x03C6 +#define BLE_APPEARANCE_HID_DIGITAL_PEN 0x03C7 +#define BLE_APPEARANCE_HID_BARCODE_SCANNER 0x03C8 +#define BLE_APPEARANCE_HID_TOUCHPAD 0x03C9 +#define BLE_APPEARANCE_HID_PRESENTATION_REMOTE 0x03CA + +/* Category[15:6] 0x010 */ +#define BLE_APPEARANCE_GENERIC_GLUCOSE 0x0400 + +/* Category[15:6] 0x011 */ +#define BLE_APPEARANCE_GENERIC_WALKING 0x0440 +#define BLE_APPEARANCE_WALKING_IN_SHOE 0x0441 +#define BLE_APPEARANCE_WALKING_ON_SHOE 0x0442 +#define BLE_APPEARANCE_WALKING_ON_HIP 0x0443 + +/* Category[15:6] 0x012 */ +#define BLE_APPEARANCE_GENERIC_CYCLING 0x0480 +#define BLE_APPEARANCE_CYCLING_COMPUTER 0x0481 +#define BLE_APPEARANCE_CYCLING_SPEED 0x0482 +#define BLE_APPEARANCE_CYCLING_CADENCE 0x0483 +#define BLE_APPEARANCE_CYCLING_POWER 0x0484 +#define BLE_APPEARANCE_CYCLING_SPEED_CADENCE 0x0485 + +/* Category[15:6] 0x013 */ +#define BLE_APPEARANCE_GENERIC_CONTROL_DEVICE 0x04C0 +#define BLE_APPEARANCE_SWITCH 0x04C1 +#define BLE_APPEARANCE_MULTI_SWITCH 0x04C2 +#define BLE_APPEARANCE_SWITCH_BUTTON 0x04C3 +#define BLE_APPEARANCE_SWITCH_SLIDER 0x04C4 +#define BLE_APPEARANCE_ROTARY_SWITCH 0x04C5 +#define BLE_APPEARANCE_TOUCH_PANEL 0x04C6 +#define BLE_APPEARANCE_SINGLE_SWITCH 0x04C7 +#define BLE_APPEARANCE_DOUBLE_SWITCH 0x04C8 +#define BLE_APPEARANCE_TRIPLE_SWITCH 0x04C9 +#define BLE_APPEARANCE_BATTERY_SWITCH 0x04CA +#define BLE_APPEARANCE_ENERGY_HARVESTING_SWITCH 0x04CB +#define BLE_APPEARANCE_SWITCH_PUSH_BUTTON 0x04CC +#define BLE_APPEARANCE_SWITCH_DIAL 0x04CD + +/* Category[15:6] 0x014 */ +#define BLE_APPEARANCE_GENERIC_NETWORK_DEVICE 0x0500 +#define BLE_APPEARANCE_NETWORK_DEVICE_ACCESS_POINT 0x0501 +#define BLE_APPEARANCE_NETWORK_DEVICE_MESH_DEVICE 0x0502 +#define BLE_APPEARANCE_NETWORK_DEVICE_MESH_NETWORK_PROXY 0x0503 + +/* Category[15:6] 0x015 */ +#define BLE_APPEARANCE_GENERIC_SENSOR 0x0540 +#define BLE_APPEARANCE_MOTION_SENSOR 0x0541 +#define BLE_APPEARANCE_AIR_QUALITY_SENSOR 0x0542 +#define BLE_APPEARANCE_TEMPERATURE_SENSOR 0x0543 +#define BLE_APPEARANCE_HUMIDITY_SENSOR 0x0544 +#define BLE_APPEARANCE_LEAK_SENSOR 0x05 +#define BLE_APPEARANCE_SMOKE_SENSOR 0x0546 +#define BLE_APPEARANCE_OCCUPANCY_SENSOR 0x0547 +#define BLE_APPEARANCE_CONTACT_SENSOR 0x0548 +#define BLE_APPEARANCE_CARBON_MONOXIDE_SENSOR 0x0549 +#define BLE_APPEARANCE_CARBON_DIOXIDE_SENSOR 0x054A +#define BLE_APPEARANCE_AMBIENT_LIGHT_SENSOR 0x054B +#define BLE_APPEARANCE_ENERGY_SENSOR 0x054C +#define BLE_APPEARANCE_COLOR_LIGHT_SENSOR 0x054D +#define BLE_APPEARANCE_RAIN_SENSOR 0x054E +#define BLE_APPEARANCE_FIRE_SENSOR 0x054F +#define BLE_APPEARANCE_WIND_SENSOR 0x0550 +#define BLE_APPEARANCE_PROXIMITY_SENSOR 0x0551 +#define BLE_APPEARANCE_MULTI_SENSOR 0x0552 +#define BLE_APPEARANCE_FLUSH_MOUNTED_SENSOR 0x0553 +#define BLE_APPEARANCE_CEILING_MOUNTED_SENSOR 0x0554 +#define BLE_APPEARANCE_WALL_MOUNTED_SENSOR 0x0555 +#define BLE_APPEARANCE_MULTISENSOR 0x0556 +#define BLE_APPEARANCE_SENSOR_ENERGY_METER 0x0557 +#define BLE_APPEARANCE_SENSOR_FLAME_DETECTOR 0x0558 +#define BLE_APPEARANCE_VEHICLE_TIRE_PRESSURE_SENSOR 0x0559 + +/* Category[15:6] 0x016 */ +#define BLE_APPEARANCE_GENERIC_LIGHT_FIXTURE 0x0580 +#define BLE_APPEARANCE_WALL_LIGHT 0x0581 +#define BLE_APPEARANCE_CEILING_LIGHT 0x0582 +#define BLE_APPEARANCE_FLOOR_LIGHT 0x0583 +#define BLE_APPEARANCE_CABINET_LIGHT 0x0584 +#define BLE_APPEARANCE_DESK_LIGHT 0x0585 +#define BLE_APPEARANCE_TROFFER_LIGHT 0x0586 +#define BLE_APPEARANCE_PENDANT_LIGHT 0x0587 +#define BLE_APPEARANCE_IN_GROUND_LIGHT 0x0588 +#define BLE_APPEARANCE_FLOOD_LIGHT 0x0589 +#define BLE_APPEARANCE_UNDERWATER_LIGHT 0x058A +#define BLE_APPEARANCE_BOLLARD_WITH_LIGHT 0x058B +#define BLE_APPEARANCE_PATHWAY_LIGHT 0x058C +#define BLE_APPEARANCE_GARDEN_LIGHT 0x058D +#define BLE_APPEARANCE_POLE_TOP_LIGHT 0x058E +#define BLE_APPEARANCE_SPOTLIGHT 0x058F +#define BLE_APPEARANCE_LINEAR_LIGHT 0x0590 +#define BLE_APPEARANCE_STREET_LIGHT 0x0591 +#define BLE_APPEARANCE_SHELVES_LIGHT 0x0592 +#define BLE_APPEARANCE_BAY_LIGHT 0x0593 +#define BLE_APPEARANCE_EMERGENCY_EXIT_LIGHT 0x0594 +#define BLE_APPEARANCE_LIGHT_CONTROLLER 0x0595 +#define BLE_APPEARANCE_LIGHT_DRIVER 0x0596 +#define BLE_APPEARANCE_BULB 0x0597 +#define BLE_APPEARANCE_LOW_BAY_LIGHT 0x0598 +#define BLE_APPEARANCE_HIGH_BAY_LIGHT 0x0599 + +/* Category[15:6] 0x017 */ +#define BLE_APPEARANCE_GENERIC_FAN 0x05C0 +#define BLE_APPEARANCE_CEILING_FAN 0x05C1 +#define BLE_APPEARANCE_AXIAL_FAN 0x05C2 +#define BLE_APPEARANCE_EXHAUST_FAN 0x05C3 +#define BLE_APPEARANCE_PEDESTAL_FAN 0x05C4 +#define BLE_APPEARANCE_DESK_FAN 0x05C5 +#define BLE_APPEARANCE_WALL_FAN 0x05C6 + +/* Category[15:6] 0x018 */ +#define BLE_APPEARANCE_GENERIC_HVAC 0x0600 +#define BLE_APPEARANCE_HVAC_THERMOSTAT 0x0601 +#define BLE_APPEARANCE_HVAC_HUMIDIFIER 0x0602 +#define BLE_APPEARANCE_HVAC_DEHUMIDIFIER 0x0603 +#define BLE_APPEARANCE_HVAC_HEATER 0x0604 +#define BLE_APPEARANCE_HVAC_RADIATOR 0x0605 +#define BLE_APPEARANCE_HVAC_BOILER 0x0606 +#define BLE_APPEARANCE_HVAC_HEAT_PUMP 0x0607 +#define BLE_APPEARANCE_HVAC_INFRARED_HEATER 0x0608 +#define BLE_APPEARANCE_HVAC_RADIANT_PANEL_HEATER 0x0609 +#define BLE_APPEARANCE_HVAC_FAN_HEATER 0x060A +#define BLE_APPEARANCE_HVAC_AIR_CURTAIN 0x060B + +/* Category[15:6] 0x019 */ +#define BLE_APPEARANCE_GENERIC_AIR_CONDITIONING 0x0640 + +/* Category[15:6] 0x01A */ +#define BLE_APPEARANCE_GENERIC_HUMIDIFIER 0x0680 + +/* Category[15:6] 0x01B */ +#define BLE_APPEARANCE_GENERIC_HEATING 0x06C0 +#define BLE_APPEARANCE_HEATING_RADIATOR 0x06C1 +#define BLE_APPEARANCE_HEATING_BOILER 0x06C2 +#define BLE_APPEARANCE_HEATING_HEAT_PUMP 0x06C3 +#define BLE_APPEARANCE_HEATING_INFRARED_HEATER 0x06C4 +#define BLE_APPEARANCE_HEATING_RADIANT_PANEL_HEATER 0x06C5 +#define BLE_APPEARANCE_HEATING_FAN_HEATER 0x06C6 +#define BLE_APPEARANCE_HEATING_AIR_CURTAIN 0x06C7 + +/* Category[15:6] 0x01C */ +#define BLE_APPEARANCE_GENERIC_ACCESS_CONTROL 0x0700 +#define BLE_APPEARANCE_ACCESS_DOOR 0x0701 +#define BLE_APPEARANCE_ACCESS_CONTROL_GARAGE_DOOR 0x0702 +#define BLE_APPEARANCE_ACCESS_CONTROL_EMERGENCY_EXIT_DOOR 0x0703 +#define BLE_APPEARANCE_ACCESS_CONTROL_ACCESS_LOCK 0x0704 +#define BLE_APPEARANCE_ACCESS_CONTROL_ELEVATOR 0x0705 +#define BLE_APPEARANCE_ACCESS_CONTROL_WINDOW 0x0706 +#define BLE_APPEARANCE_ACCESS_CONTROL_ENTRANCE_GATE 0x0707 +#define BLE_APPEARANCE_ACCESS_CONTROL_DOOR_LOCK 0x0708 +#define BLE_APPEARANCE_ACCESS_CONTROL_LOCKER 0x0709 + +/* Category[15:6] 0x01D */ +#define BLE_APPEARANCE_GENERIC_MOTORIZED_DEVICE 0x0740 +#define BLE_APPEARANCE_MOTORIZED_GATE 0x0741 +#define BLE_APPEARANCE_MOTORIZED_AWNING 0x0742 +#define BLE_APPEARANCE_MOTORIZED_BLINDS_OR_SHADES 0x0743 +#define BLE_APPEARANCE_MOTORIZED_CURTAINS 0x0744 +#define BLE_APPEARANCE_MOTORIZED_SCREEN 0x0745 + +/* Category[15:6] 0x01E */ +#define BLE_APPEARANCE_GENERIC_POWER_DEVICE 0x0780 +#define BLE_APPEARANCE_POWER_OUTLET 0x0781 +#define BLE_APPEARANCE_POWER_STRIP 0x0782 +#define BLE_APPEARANCE_POWER_PLUG 0x0783 +#define BLE_APPEARANCE_POWER_SUPPLY 0x0784 + +/* Category[15:6] 0x01F */ +#define BLE_APPEARANCE_GENERIC_LIGHT_SOURCE 0x07C0 +#define BLE_APPEARANCE_LIGHT_SOURCE_INCANDESCENT_LIGHT_BULB 0x07C1 +#define BLE_APPEARANCE_LIGHT_SOURCE_LED_LAMP 0x07C2 +#define BLE_APPEARANCE_LIGHT_SOURCE_HID_LAMP 0x07C3 +#define BLE_APPEARANCE_LIGHT_SOURCE_FLUORESCENT_LAMP 0x07C4 +#define BLE_APPEARANCE_LIGHT_SOURCE_LED_ARRAY 0x07C5 +#define BLE_APPEARANCE_LIGHT_SOURCE_MULTI_COLOR_LED_ARRAY 0x07C6 +#define BLE_APPEARANCE_LIGHT_SOURCE_LOW_VOLTAGE_HALOGEN 0x07C7 +#define BLE_APPEARANCE_LIGHT_SOURCE_ORGANIC_LIGHT_EMITTING_DIODE_OLED 0x07C8 + +/* Category[15:6] 0x020 */ +#define BLE_APPEARANCE_GENERIC_WINDOW_COVERING 0x0800 +#define BLE_APPEARANCE_WINDOW_COVERING_WINDOW_SHADES 0x0801 +#define BLE_APPEARANCE_WINDOW_COVERING_WINDOW_BLINDS 0x0802 +#define BLE_APPEARANCE_WINDOW_COVERING_WINDOW_AWNING 0x0803 +#define BLE_APPEARANCE_WINDOW_COVERING_WINDOW_CURTAIN 0x0804 +#define BLE_APPEARANCE_WINDOW_COVERING_EXTERIOR_SHUTTER 0x0805 +#define BLE_APPEARANCE_WINDOW_COVERING_EXTERIOR_SCREEN 0x0806 + +/* Category[15:6] 0x021 */ +#define BLE_APPEARANCE_GENERIC_AUDIO_SINK 0x0840 +#define BLE_APPEARANCE_AUDIO_SINK_STANDALONE_SPEAKER 0x0841 +#define BLE_APPEARANCE_AUDIO_SINK_SOUNDBAR 0x0842 +#define BLE_APPEARANCE_AUDIO_SINK_BOOKSHELF_SPEAKER 0x0843 +#define BLE_APPEARANCE_AUDIO_SINK_STANDMOUNTED_SPEAKER 0x0844 +#define BLE_APPEARANCE_AUDIO_SINK_SPEAKERPHONE 0x0845 + +/* Category[15:6] 0x022 */ +#define BLE_APPEARANCE_GENERIC_AUDIO_SOURCE 0x0880 +#define BLE_APPEARANCE_AUDIO_SOURCE_MICROPHONE 0x0881 +#define BLE_APPEARANCE_AUDIO_SOURCE_ALARM 0x0882 +#define BLE_APPEARANCE_AUDIO_SOURCE_BELL 0x0883 +#define BLE_APPEARANCE_AUDIO_SOURCE_HORN 0x0884 +#define BLE_APPEARANCE_AUDIO_SOURCE_BROADCASTING_DEVICE 0x0885 +#define BLE_APPEARANCE_AUDIO_SOURCE_SERVICE_DESK 0x0886 +#define BLE_APPEARANCE_AUDIO_SOURCE_KIOSK 0x0887 +#define BLE_APPEARANCE_AUDIO_SOURCE_BROADCASTING_ROOM 0x0888 +#define BLE_APPEARANCE_AUDIO_SOURCE_AUDITORIUM 0x0889 + +/* Category[15:6] 0x023 */ +#define BLE_APPEARANCE_GENERIC_MOTORIZED_VEHICLE 0x08C0 +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_CAR 0x08C1 +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_LARGE_GOODS 0x08C2 +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_2_WHEELED 0x08C3 +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_MOTORBIKE 0x08C4 +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_SCOOTER 0x08C5 +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_MOPED 0x08C6 +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_3_WHEELED 0x08C7 +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_LIGHT_VEHICLE 0x08C8 +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_QUAD_BIKE 0x08C9 +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_MINIBUS 0x08CA +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_BUS 0x08CB +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_TROLLEY 0x08CC +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_AGRICULTURAL_VEHICLE 0x08CD +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_CAMPER_CARAVAN 0x08CE +#define BLE_APPEARANCE_MOTORIZED_VEHICLE_RECREATIONAL_VEHICLE_MOTOR_HOME 0x08CF + +/* Category[15:6] 0x024 */ +#define BLE_APPEARANCE_GENERIC_DOMESTIC_APPLIANCE 0x0900 +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_REFRIGERATOR 0x0901 +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_FREEZER 0x0902 +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_OVEN 0x0903 +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_MICROWAVE 0x0904 +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_TOASTER 0x0905 +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_WASHING_MACHINE 0x0906 +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_DRYER 0x0907 +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_COFFEE_MAKER 0x0908 +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_CLOTHES_IRON 0x0909 +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_CURLING_IRON 0x090A +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_HAIR_DRYER 0x090B +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_VACUUM_CLEANER 0x090C +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_ROBOTIC_VACUUM_CLEANER 0x090D +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_RICE_COOKER 0x090E +#define BLE_APPEARANCE_DOMESTIC_APPLIANCE_CLOTHES_STEAMER 0x090F + +/* Category[15:6] 0x025 */ +#define BLE_APPEARANCE_GENERIC_WEARABLE_AUDIO_DEVICE 0x0940 +#define BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_EARBUD 0x0941 +#define BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_HEADSET 0x0942 +#define BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_HEADPHONES 0x0943 +#define BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_NECK_BAND 0x0944 + +/* Category[15:6] 0x026 */ +#define BLE_APPEARANCE_GENERIC_AIRCRAFT 0x0980 +#define BLE_APPEARANCE_AIRCRAFT_LIGHT 0x0981 +#define BLE_APPEARANCE_AIRCRAFT_MICROLIGHT 0x0982 +#define BLE_APPEARANCE_AIRCRAFT_PARAGLIDER 0x0983 +#define BLE_APPEARANCE_AIRCRAFT_LARGE_PASSENGER 0x0984 + +/* Category[15:6] 0x027 */ +#define BLE_APPEARANCE_GENERIC_AV_EQUIPMENT 0x09C0 +#define BLE_APPEARANCE_AV_EQUIPMENT_AMPLIFIER 0x09C1 +#define BLE_APPEARANCE_AV_EQUIPMENT_RECEIVER 0x09C2 +#define BLE_APPEARANCE_AV_EQUIPMENT_RADIO 0x09C3 +#define BLE_APPEARANCE_AV_EQUIPMENT_TUNER 0x09C4 +#define BLE_APPEARANCE_AV_EQUIPMENT_TURNTABLE 0x09C5 +#define BLE_APPEARANCE_AV_EQUIPMENT_CD_PLAYER 0x09C6 +#define BLE_APPEARANCE_AV_EQUIPMENT_DVD_PLAYER 0x09C7 +#define BLE_APPEARANCE_AV_EQUIPMENT_BLU_RAY_PLAYER 0x09C8 +#define BLE_APPEARANCE_AV_EQUIPMENT_OPTICAL_DISC_PLAYER 0x09C9 +#define BLE_APPEARANCE_AV_EQUIPMENT_SET_TOP_BOX 0x09CA + +/* Category[15:6] 0x028 */ +#define BLE_APPEARANCE_GENERIC_DISPLAY_EQUIPMENT 0x0A00 +#define BLE_APPEARANCE_DISPLAY_EQUIPMENT_TELEVISION 0x0A01 +#define BLE_APPEARANCE_DISPLAY_EQUIPMENT_MONITOR 0x0A02 +#define BLE_APPEARANCE_DISPLAY_EQUIPMENT_PROJECTOR 0x0A03 + +/* Category[15:6] 0x029 */ +#define BLE_APPEARANCE_GENERIC_HEARING_AID 0x0A40 +#define BLE_APPEARANCE_HEARING_AID_IN_EAR 0x0A41 +#define BLE_APPEARANCE_HEARING_AID_BEHIND_EAR 0x0A42 +#define BLE_APPEARANCE_HEARING_AID_COCHLLEAR_IMPLANT 0x0A43 + +/* Category[15:6] 0x02A */ +#define BLE_APPEARANCE_GENERIC_GAMING 0x0A80 +#define BLE_APPEARANCE_GAMING_HOME_VIDEO_GAME_CONSOLE 0x0A81 +#define BLE_APPEARANCE_GAMING_PORTABLE_HANDHELD_CONSOLE 0x0A82 + +/* Category[15:6] 0x02B */ +#define BLE_APPEARANCE_GENERIC_SIGNAGE 0x0AC0 +#define BLE_APPEARANCE_SIGNAGE_DIGITAL 0x0AC1 +#define BLE_APPEARANCE_SIGNAGE_ELECTRONIC_LABEL 0x0AC2 + +/* Category[15:6] 0x031 */ +#define BLE_APPEARANCE_GENERIC_PULSE_OXIMETER 0x0C40 +#define BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP 0x0C41 +#define BLE_APPEARANCE_PULSE_OXIMETER_WRIST 0x0C42 + +/* Category[15:6] 0x032 */ +#define BLE_APPEARANCE_GENERIC_WEIGHT 0x0C80 + +/* Category[15:6] 0x033 */ +#define BLE_APPEARANCE_GENERIC_PERSONAL_MOBILITY_DEVICE 0x0CC0 +#define BLE_APPEARANCE_PERSONAL_MOBILITY_DEVICE_POWERED_WHEELCHAIR 0x0CC1 +#define BLE_APPEARANCE_PERSONAL_MOBILITY_DEVICE_MOBILITY_SCOOTER 0x0CC2 + +/* Category[15:6] 0x034 */ +#define BLE_APPEARANCE_GENERIC_CONTINUOUS_GLUCOSE_MONITOR 0x0D00 + +/* Category[15:6] 0x035 */ +#define BLE_APPEARANCE_GENERIC_INSULIN_PUMP 0x0D40 +#define BLE_APPEARANCE_INSULIN_PUMP_DURABLE 0x0D41 +#define BLE_APPEARANCE_INSULIN_PUMP_PATCH 0x0D44 +#define BLE_APPEARANCE_INSULIN_PUMP_PEN 0x0D48 + +/* Category[15:6] 0x036 */ +#define BLE_APPEARANCE_GENERIC_MEDICATION_DELIVERY 0x0D80 + +/* Category[15:6] 0x037 */ +#define BLE_APPEARANCE_GENERIC_SPIROMETER 0x0DC0 +#define BLE_APPEARANCE_SPIROMETER_HANDHELD 0x0DC1 + +/* Category[15:6] 0x051 */ +#define BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS 0x1440 +#define BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION 0x1441 +#define BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION_AND_NAV 0x1442 +#define BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION_POD 0x1443 +#define BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION_POD_AND_NAV 0x1444 + +/* Category[15:6] 0x052 */ +#define BLE_APPEARANCE_GENERIC_INDUSTRIAL_MEASUREMENT_DEVICE 0x1480 +#define BLE_APPEARANCE_INDUSTRIAL_MEASUREMENT_DEVICE_TORQUE_TESTING 0x1481 +#define BLE_APPEARANCE_INDUSTRIAL_MEASUREMENT_DEVICE_CALIPER 0x1482 +#define BLE_APPEARANCE_INDUSTRIAL_MEASUREMENT_DEVICE_DIAL_INDICATOR 0x1483 +#define BLE_APPEARANCE_INDUSTRIAL_MEASUREMENT_DEVICE_MICROMETER 0x1484 +#define BLE_APPEARANCE_INDUSTRIAL_MEASUREMENT_DEVICE_HEIGHT_GAUGE 0x1485 +#define BLE_APPEARANCE_INDUSTRIAL_MEASUREMENT_DEVICE_FORCE_GAUGE 0x1486 + +/* Category[15:6] 0x053 */ +#define BLE_APPEARANCE_GENERIC_INDUSTRIAL_TOOLS 0x14C0 +#define BLE_APPEARANCE_INDUSTRIAL_TOOLS_MACHINE_TOOL_HOLDER 0x14C1 +#define BLE_APPEARANCE_INDUSTRIAL_TOOLS_GENERIC_CLAMPING_DEVICE 0x14C2 +#define BLE_APPEARANCE_INDUSTRIAL_TOOLS_CLAMPING_JAWS_JAWS_CHUCK 0x14C3 +#define BLE_APPEARANCE_INDUSTRIAL_TOOLS_CLAMPING_COLLET_CHUCK 0x14C4 +#define BLE_APPEARANCE_INDUSTRIAL_TOOLS_CLAMPING_MANDREL 0x14C5 +#define BLE_APPEARANCE_INDUSTRIAL_TOOLS_VISE 0x14C6 +#define BLE_APPEARANCE_INDUSTRIAL_TOOLS_ZERO_POINT_CLAMPING_SYSTEM 0x14C7 +#define BLE_APPEARANCE_INDUSTRIAL_TOOLS_TORQUE_WRENCH 0x14C8 +#define BLE_APPEARANCE_INDUSTRIAL_TOOLS_TORQUE_SCREWDRIVER 0x14C9 + +#endif // BLE_APPEARANCE_H
\ No newline at end of file diff --git a/system/stack/include/bt_dev_class.h b/system/stack/include/bt_dev_class.h index 1455f66fd2..ca951532ce 100644 --- a/system/stack/include/bt_dev_class.h +++ b/system/stack/include/bt_dev_class.h @@ -26,72 +26,222 @@ typedef std::array<uint8_t, kDevClassLength> DEV_CLASS; /* Device class */ inline constexpr DEV_CLASS kDevClassEmpty = {}; +/*************************** + * major device class field + * Note: All values are deduced by basing BIT_X to BIT_8, values as per + * BT-spec assigned-numbers. + ***************************/ +#define COD_MAJOR_MISC 0x00 +#define COD_MAJOR_COMPUTER 0x01 // BIT8 +#define COD_MAJOR_PHONE 0x02 // BIT9 +#define COD_MAJOR_LAN_NAP 0x03 // BIT8 | BIT9 +#define COD_MAJOR_AUDIO 0x04 // BIT10 +#define COD_MAJOR_PERIPHERAL 0x05 // BIT8 | BIT10 +#define COD_MAJOR_IMAGING 0x06 // BIT9 | BIT10 +#define COD_MAJOR_WEARABLE 0x07 // BIT8 | BIT9 | BIT10 +#define COD_MAJOR_TOY 0x08 // BIT11 +#define COD_MAJOR_HEALTH 0x09 // BIT8 | BIT11 +#define COD_MAJOR_UNCLASSIFIED 0x1F // BIT8 | BIT9 | BIT10 | BIT11 | BIT12 + +/*************************** + * service class fields + * Note: All values are deduced by basing BIT_X to BIT_8, values as per + * BT-spec assigned-numbers. + ***************************/ +#define COD_SERVICE_LMTD_DISCOVER 0x0020 // BIT13 (eg. 13-8 = BIT5 = 0x0020) +#define COD_SERVICE_LE_AUDIO 0x0040 // BIT14 +#define COD_SERVICE_POSITIONING 0x0100 // BIT16 +#define COD_SERVICE_NETWORKING 0x0200 // BIT17 +#define COD_SERVICE_RENDERING 0x0400 // BIT18 +#define COD_SERVICE_CAPTURING 0x0800 // BIT19 +#define COD_SERVICE_OBJ_TRANSFER 0x1000 // BIT20 +#define COD_SERVICE_AUDIO 0x2000 // BIT21 +#define COD_SERVICE_TELEPHONY 0x4000 // BIT22 +#define COD_SERVICE_INFORMATION 0x8000 // BIT23 + +/*************************** + * minor device class field + * Note: LSB[1:0] (2 bits) is don't care for minor device class. + ***************************/ +/* Minor Device class field - Computer Major Class (COD_MAJOR_COMPUTER) */ +#define COD_MAJOR_COMPUTER_MINOR_UNCATEGORIZED 0x00 +#define COD_MAJOR_COMPUTER_MINOR_DESKTOP_WORKSTATION 0x04 // BIT2 +#define COD_MAJOR_COMPUTER_MINOR_SERVER_CLASS_COMPUTER 0x08 // BIT3 +#define COD_MAJOR_COMPUTER_MINOR_LAPTOP 0x0C // BIT2 | BIT3 +#define COD_MAJOR_COMPUTER_MINOR_HANDHELD_PC_PDA 0x10 // BIT4 +#define COD_MAJOR_COMPUTER_MINOR_PALM_SIZE_PC_PDA 0x14 // BIT2 | BIT4 +#define COD_MAJOR_COMPUTER_MINOR_WEARABLE_COMPUTER_WATCH_SIZE 0x18 // BIT3 | BIT4 +#define COD_MAJOR_COMPUTER_MINOR_TABLET 0x1C // BIT2 | BIT3 | BIT4 + +/* Minor Device class field - Phone Major Class (COD_MAJOR_PHONE) */ +#define COD_MAJOR_PHONE_MINOR_UNCATEGORIZED 0x00 +#define COD_MAJOR_PHONE_MINOR_CELLULAR 0x04 // BIT2 +#define COD_MAJOR_PHONE_MINOR_CORDLESS 0x08 // BIT3 +#define COD_MAJOR_PHONE_MINOR_SMARTPHONE 0x0C // BIT2 | BIT3 +#define COD_MAJOR_PHONE_MINOR_WIRED_MODEM_OR_VOICE_GATEWAY 0x10 // BIT4 +#define COD_MAJOR_PHONE_MINOR_COMMON_ISDN_ACCESS 0x14 // BIT2 | BIT4 + +/* + * Minor Device class field - + * LAN/Network Access Point Major Class (COD_MAJOR_LAN_NAP) + */ +#define COD_MAJOR_LAN_NAP_MINOR_FULLY_AVAILABLE 0x00 +#define COD_MAJOR_LAN_NAP_MINOR_1_TO_17_PER_UTILIZED 0x20 // BIT5 +#define COD_MAJOR_LAN_NAP_MINOR_17_TO_33_PER_UTILIZED 0x40 // BIT6 +#define COD_MAJOR_LAN_NAP_MINOR_33_TO_50_PER_UTILIZED 0x60 // BIT5 | BIT6 +#define COD_MAJOR_LAN_NAP_MINOR_50_TO_67_PER_UTILIZED 0x80 // BIT7 +#define COD_MAJOR_LAN_NAP_MINOR_67_TO_83_PER_UTILIZED 0xA0 // BIT5 | BIT7 +#define COD_MAJOR_LAN_NAP_MINOR_83_TO_99_PER_UTILIZED 0xC0 // BIT6 | BIT7 +#define COD_MAJOR_LAN_NAP_MINOR_NO_SERVICE_AVAILABLE 0xE0 // BIT5 | BIT6 | BIT7 + +/* Minor Device class field - Audio/Video Major Class (COD_MAJOR_AUDIO) */ +/* 0x00 is used as unclassified for all minor device classes */ +#define COD_MINOR_UNCATEGORIZED 0x00 +#define COD_MAJOR_AUDIO_MINOR_WEARABLE_HEADSET 0x04 // BIT2 +#define COD_MAJOR_AUDIO_MINOR_CONFM_HANDSFREE 0x08 // BIT3 +#define COD_MAJOR_AUDIO_MINOR_MICROPHONE 0x10 // BIT4 +#define COD_MAJOR_AUDIO_MINOR_LOUDSPEAKER 0x14 // BIT2 | BIT4 +#define COD_MAJOR_AUDIO_MINOR_HEADPHONES 0x18 // BIT3 | BIT4 +#define COD_MAJOR_AUDIO_MINOR_PORTABLE_AUDIO 0x1C // BIT2 | BIT3 | BIT4 +#define COD_MAJOR_AUDIO_MINOR_CAR_AUDIO 0x20 // BIT5 +#define COD_MAJOR_AUDIO_MINOR_SET_TOP_BOX 0x24 // BIT2 | BIT5 +#define COD_MAJOR_AUDIO_MINOR_HIFI_AUDIO 0x28 // BIT3 | BIT5 +#define COD_MAJOR_AUDIO_MINOR_VCR 0x2C // BIT2 | BIT3 | BIT5 +#define COD_MAJOR_AUDIO_MINOR_VIDEO_CAMERA 0x30 // BIT4 | BIT5 +#define COD_MAJOR_AUDIO_MINOR_CAMCORDER 0x34 // BIT2 | BIT4 | BIT5 +#define COD_MAJOR_AUDIO_MINOR_VIDEO_MONITOR 0x38 // BIT3 | BIT4 | BIT5 +#define COD_MAJOR_AUDIO_MINOR_VIDEO_DISPLAY_AND_LOUDSPEAKER 0x3C // BIT2 | + // BIT3 | BIT4 | BIT5 +#define COD_MAJOR_AUDIO_MINOR_VIDEO_CONFERENCING 0x40 // BIT6 +#define COD_MAJOR_AUDIO_MINOR_GAMING_OR_TOY 0x48 // BIT3 | BIT6 + +/* Minor Device class field - Peripheral Major Class (COD_MAJOR_PERIPHERAL) */ +/* Bits 6-7 independently specify mouse, keyboard, or combo mouse/keyboard */ +#define COD_MAJOR_PERIPH_MINOR_KEYBOARD 0x40 // BIT6 +#define COD_MAJOR_PERIPH_MINOR_POINTING 0x80 // BIT7 +#define COD_MAJOR_PERIPH_MINOR_KEYBOARD_AND_POINTING_DEVICE 0xC0 // BIT6 | BIT7 + +/* Bits 2-5 OR'd with selection from bits 6-7 */ +#define COD_MAJOR_PERIPH_MINOR_JOYSTICK 0x04 // BIT2 +#define COD_MAJOR_PERIPH_MINOR_GAMEPAD 0x08 // BIT3 +#define COD_MAJOR_PERIPH_MINOR_REMOTE_CONTROL 0x0C // BIT2 | BIT3 +#define COD_MAJOR_PERIPH_MINOR_SENSING_DEVICE 0x10 // BIT4 +#define COD_MAJOR_PERIPH_MINOR_DIGITIZING_TABLET 0x14 // BIT2 | BIT4 +#define COD_MAJOR_PERIPH_MINOR_CARD_READER 0x18 /* e.g. SIM card reader, BIT3 | BIT4 */ +#define COD_MAJOR_PERIPH_MINOR_DIGITAL_PEN 0x1C // Pen, BIT2 | BIT3 | BIT4 +#define COD_MAJOR_PERIPH_MINOR_HANDHELD_SCANNER 0x20 // e.g. Barcode, RFID, BIT5 +#define COD_MAJOR_PERIPH_MINOR_HANDHELD_GESTURAL_INP_DEVICE 0x24 + // e.g. "wand" form factor, BIT2 | BIT5 + +/* Minor Device class field - Imaging Major Class (COD_MAJOR_IMAGING) + * + * Bits 5-7 independently specify display, camera, scanner, or printer + * Note: Apart from the set bit, all other bits are don't care. + */ +#define COD_MAJOR_IMAGING_MINOR_DISPLAY 0x10 // BIT4 +#define COD_MAJOR_IMAGING_MINOR_CAMERA 0x20 // BIT5 +#define COD_MAJOR_IMAGING_MINOR_SCANNER 0x40 // BIT6 +#define COD_MAJOR_IMAGING_MINOR_PRINTER 0x80 // BIT7 + +/* Minor Device class field - Wearable Major Class (COD_MAJOR_WEARABLE) */ +#define COD_MAJOR_WEARABLE_MINOR_WRIST_WATCH 0x04 // BIT2 +#define COD_MAJOR_WEARABLE_MINOR_PAGER 0x08 // BIT3 +#define COD_MJAOR_WEARABLE_MINOR_JACKET 0x0C // BIT2 | BIT3 +#define COD_MAJOR_WEARABLE_MINOR_HELMET 0x10 // BIT4 +#define COD_MAJOR_WEARABLE_MINOR_GLASSES 0x14 // BIT2 | BIT4 +#define COD_MAJOR_WEARABLE_MINOR_PIN 0x18 + // e.g. Label pin, broach, badge BIT3 | BIT4 + +/* Minor Device class field - Toy Major Class (COD_MAJOR_TOY) */ +#define COD_MAJOR_TOY_MINOR_ROBOT 0x04 // BIT2 +#define COD_MAJOR_TOY_MINOR_VEHICLE 0x08 // BIT3 +#define COD_MAJOR_TOY_MINOR_DOLL_OR_ACTION_FIGURE 0x0C // BIT2 | BIT3 +#define COD_MAJOR_TOY_MINOR_CONTROLLER 0x10 // BIT4 +#define COD_MAJOR_TOY_MINOR_GAME 0x14 // BIT2 | BIT4 + +/* Minor Device class field - Health Major Class (COD_MAJOR_HEALTH) */ +#define COD_MAJOR_HEALTH_MINOR_BLOOD_MONITOR 0x04 // Blood pressure monitor, BIT2 +#define COD_MAJOR_HEALTH_MINOR_THERMOMETER 0x08 // BIT3 +#define COD_MAJOR_HEALTH_MINOR_WEIGHING_SCALE 0x0C // BIT2 | BIT3 +#define COD_MAJOR_HEALTH_MINOR_GLUCOSE_METER 0x10 // BIT4 +#define COD_MAJOR_HEALTH_MINOR_PULSE_OXIMETER 0x14 // BIT2 | BIT4 +#define COD_MAJOR_HEALTH_MINOR_HEART_PULSE_MONITOR 0x18 // BIT3 | BIT4 +#define COD_MAJOR_HEALTH_MINOR_HEALTH_DATA_DISPLAY 0x1C // BIT2 | BIT3 | BIT4 +#define COD_MAJO_HEALTH_MINOR_STEP_COUNTER 0x20 // BIT5 +#define COD_MAJOR_HEALTH_MINOR_BODY_COMPOSITION_ANALYZER 0x24 // BIT2 | BIT5 +#define COD_MAJOR_HEALTH_MINOR_PEAK_FLOW_MONITOR 0x28 // BIT3 | BIT5 +#define COD_MAJOR_HEALTH_MINOR_MEDICATION_MONITOR 0x2C // BIT2 | BIT3 | BIT5 +#define COD_MAJOR_HEALTH_MINOR_KNEE_PROSTHESIS 0x30 // BIT4 | BIT5 +#define COD_MAJOR_HEALTH_MINOR_ANKLE_PROSTHESIS 0x34 // BIT3 | BIT4 | BIT5 +#define COD_MAJOR_HEALTH_MINOR_GENERIC_HEALTH_MANAGER 0x38 // BIT2 | BIT3 | BIT4 | BIT5 +#define COD_MAJOR_HEALTH_MINOR_PERSONAL_MOBILITY_DEVICE 0x3C // BIT4 | BIT5 + /* 0x00 is used as unclassified for all minor device classes */ -#define BTM_COD_MINOR_UNCLASSIFIED 0x00 -#define BTM_COD_MINOR_WEARABLE_HEADSET 0x04 -#define BTM_COD_MINOR_CONFM_HANDSFREE 0x08 -#define BTM_COD_MINOR_CAR_AUDIO 0x20 -#define BTM_COD_MINOR_SET_TOP_BOX 0x24 +#define BTM_COD_MINOR_UNCLASSIFIED COD_MINOR_UNCATEGORIZED +#define BTM_COD_MINOR_WEARABLE_HEADSET COD_MAJOR_AUDIO_MINOR_WEARABLE_HEADSET +#define BTM_COD_MINOR_CONFM_HANDSFREE COD_MAJOR_AUDIO_MINOR_CONFM_HANDSFREE +#define BTM_COD_MINOR_CAR_AUDIO COD_MAJOR_AUDIO_MINOR_CAR_AUDIO +#define BTM_COD_MINOR_SET_TOP_BOX COD_MAJOR_AUDIO_MINOR_SET_TOP_BOX /* minor device class field for Peripheral Major Class */ /* Bits 6-7 independently specify mouse, keyboard, or combo mouse/keyboard */ -#define BTM_COD_MINOR_KEYBOARD 0x40 -#define BTM_COD_MINOR_POINTING 0x80 +#define BTM_COD_MINOR_KEYBOARD COD_MAJOR_PERIPH_MINOR_KEYBOARD +#define BTM_COD_MINOR_POINTING COD_MAJOR_PERIPH_MINOR_POINTING /* Bits 2-5 OR'd with selection from bits 6-7 */ /* #define BTM_COD_MINOR_UNCLASSIFIED 0x00 */ -#define BTM_COD_MINOR_JOYSTICK 0x04 -#define BTM_COD_MINOR_GAMEPAD 0x08 -#define BTM_COD_MINOR_REMOTE_CONTROL 0x0C -#define BTM_COD_MINOR_DIGITIZING_TABLET 0x14 -#define BTM_COD_MINOR_CARD_READER 0x18 /* e.g. SIM card reader */ -#define BTM_COD_MINOR_DIGITAL_PAN 0x1C +#define BTM_COD_MINOR_JOYSTICK COD_MAJOR_PERIPH_MINOR_JOYSTICK +#define BTM_COD_MINOR_GAMEPAD COD_MAJOR_PERIPH_MINOR_GAMEPAD +#define BTM_COD_MINOR_REMOTE_CONTROL COD_MAJOR_PERIPH_MINOR_REMOTE_CONTROL +#define BTM_COD_MINOR_DIGITIZING_TABLET COD_MAJOR_PERIPH_MINOR_DIGITIZING_TABLET +#define BTM_COD_MINOR_CARD_READER COD_MAJOR_PERIPH_MINOR_CARD_READER +#define BTM_COD_MINOR_DIGITAL_PAN COD_MAJOR_PERIPH_MINOR_DIGITAL_PEN /* minor device class field for Imaging Major Class */ /* Bits 5-7 independently specify display, camera, scanner, or printer */ -#define BTM_COD_MINOR_DISPLAY 0x10 +#define BTM_COD_MINOR_DISPLAY COD_MAJOR_IMAGING_MINOR_DISPLAY /* Bits 2-3 Reserved */ /* #define BTM_COD_MINOR_UNCLASSIFIED 0x00 */ /* minor device class field for Wearable Major Class */ /* Bits 2-7 meaningful */ -#define BTM_COD_MINOR_WRIST_WATCH 0x04 -#define BTM_COD_MINOR_GLASSES 0x14 +#define BTM_COD_MINOR_WRIST_WATCH COD_MAJOR_WEARABLE_MINOR_WRIST_WATCH +#define BTM_COD_MINOR_GLASSES COD_MAJOR_WEARABLE_MINOR_GLASSES /* minor device class field for Health Major Class */ /* Bits 2-7 meaningful */ -#define BTM_COD_MINOR_BLOOD_MONITOR 0x04 -#define BTM_COD_MINOR_THERMOMETER 0x08 -#define BTM_COD_MINOR_WEIGHING_SCALE 0x0C -#define BTM_COD_MINOR_GLUCOSE_METER 0x10 -#define BTM_COD_MINOR_PULSE_OXIMETER 0x14 -#define BTM_COD_MINOR_HEART_PULSE_MONITOR 0x18 -#define BTM_COD_MINOR_STEP_COUNTER 0x20 +#define BTM_COD_MINOR_BLOOD_MONITOR COD_MAJOR_HEALTH_MINOR_BLOOD_MONITOR +#define BTM_COD_MINOR_THERMOMETER COD_MAJOR_HEALTH_MINOR_THERMOMETER +#define BTM_COD_MINOR_WEIGHING_SCALE COD_MAJOR_HEALTH_MINOR_WEIGHING_SCALE +#define BTM_COD_MINOR_GLUCOSE_METER COD_MAJOR_HEALTH_MINOR_GLUCOSE_METER +#define BTM_COD_MINOR_PULSE_OXIMETER COD_MAJOR_HEALTH_MINOR_PULSE_OXIMETER +#define BTM_COD_MINOR_HEART_PULSE_MONITOR COD_MAJOR_HEALTH_MINOR_HEART_PULSE_MONITOR +#define BTM_COD_MINOR_STEP_COUNTER COD_MAJO_HEALTH_MINOR_STEP_COUNTER /*************************** * major device class field ***************************/ -#define BTM_COD_MAJOR_COMPUTER 0x01 -#define BTM_COD_MAJOR_PHONE 0x02 -#define BTM_COD_MAJOR_AUDIO 0x04 -#define BTM_COD_MAJOR_PERIPHERAL 0x05 -#define BTM_COD_MAJOR_IMAGING 0x06 -#define BTM_COD_MAJOR_WEARABLE 0x07 -#define BTM_COD_MAJOR_HEALTH 0x09 -#define BTM_COD_MAJOR_UNCLASSIFIED 0x1F +#define BTM_COD_MAJOR_COMPUTER COD_MAJOR_COMPUTER +#define BTM_COD_MAJOR_PHONE COD_MAJOR_PHONE +#define BTM_COD_MAJOR_AUDIO COD_MAJOR_AUDIO +#define BTM_COD_MAJOR_PERIPHERAL COD_MAJOR_PERIPHERAL +#define BTM_COD_MAJOR_IMAGING COD_MAJOR_IMAGING +#define BTM_COD_MAJOR_WEARABLE COD_MAJOR_WEARABLE +#define BTM_COD_MAJOR_HEALTH COD_MAJOR_HEALTH +#define BTM_COD_MAJOR_UNCLASSIFIED COD_MAJOR_UNCLASSIFIED /*************************** * service class fields ***************************/ -#define BTM_COD_SERVICE_LMTD_DISCOVER 0x0020 -#define BTM_COD_SERVICE_LE_AUDIO 0x0040 -#define BTM_COD_SERVICE_POSITIONING 0x0100 -#define BTM_COD_SERVICE_NETWORKING 0x0200 -#define BTM_COD_SERVICE_RENDERING 0x0400 -#define BTM_COD_SERVICE_CAPTURING 0x0800 -#define BTM_COD_SERVICE_OBJ_TRANSFER 0x1000 -#define BTM_COD_SERVICE_AUDIO 0x2000 -#define BTM_COD_SERVICE_TELEPHONY 0x4000 -#define BTM_COD_SERVICE_INFORMATION 0x8000 +#define BTM_COD_SERVICE_LMTD_DISCOVER COD_SERVICE_LMTD_DISCOVER +#define BTM_COD_SERVICE_LE_AUDIO COD_SERVICE_LE_AUDIO +#define BTM_COD_SERVICE_POSITIONING COD_SERVICE_POSITIONING +#define BTM_COD_SERVICE_NETWORKING COD_SERVICE_NETWORKING +#define BTM_COD_SERVICE_RENDERING COD_SERVICE_RENDERING +#define BTM_COD_SERVICE_CAPTURING COD_SERVICE_CAPTURING +#define BTM_COD_SERVICE_OBJ_TRANSFER COD_SERVICE_OBJ_TRANSFER +#define BTM_COD_SERVICE_AUDIO COD_SERVICE_AUDIO +#define BTM_COD_SERVICE_TELEPHONY COD_SERVICE_TELEPHONY +#define BTM_COD_SERVICE_INFORMATION COD_SERVICE_INFORMATION /* the COD masks */ #define BTM_COD_MINOR_CLASS_MASK 0xFC diff --git a/system/stack/include/btm_ble_api_types.h b/system/stack/include/btm_ble_api_types.h index 9a1679768b..d89028d27f 100644 --- a/system/stack/include/btm_ble_api_types.h +++ b/system/stack/include/btm_ble_api_types.h @@ -29,6 +29,7 @@ #include "stack/include/bt_octets.h" #include "stack/include/btm_status.h" #include "stack/include/hci_error_code.h" +#include "stack/include/ble_appearance.h" #include "types/ble_address_with_type.h" #include "types/raw_address.h" @@ -216,60 +217,70 @@ typedef uint8_t BLE_SIGNATURE[BTM_BLE_AUTH_SIGN_LEN]; /* Device address */ #endif /* Appearance Values Reported with BTM_BLE_AD_TYPE_APPEARANCE */ -#define BTM_BLE_APPEARANCE_UKNOWN 0x0000 -#define BTM_BLE_APPEARANCE_GENERIC_PHONE 0x0040 -#define BTM_BLE_APPEARANCE_GENERIC_COMPUTER 0x0080 -#define BTM_BLE_APPEARANCE_GENERIC_WATCH 0x00C0 -#define BTM_BLE_APPEARANCE_SPORTS_WATCH 0x00C1 -#define BTM_BLE_APPEARANCE_GENERIC_CLOCK 0x0100 -#define BTM_BLE_APPEARANCE_GENERIC_DISPLAY 0x0140 -#define BTM_BLE_APPEARANCE_GENERIC_REMOTE 0x0180 -#define BTM_BLE_APPEARANCE_GENERIC_EYEGLASSES 0x01C0 -#define BTM_BLE_APPEARANCE_GENERIC_TAG 0x0200 -#define BTM_BLE_APPEARANCE_GENERIC_KEYRING 0x0240 -#define BTM_BLE_APPEARANCE_GENERIC_MEDIA_PLAYER 0x0280 -#define BTM_BLE_APPEARANCE_GENERIC_BARCODE_SCANNER 0x02C0 -#define BTM_BLE_APPEARANCE_GENERIC_THERMOMETER 0x0300 -#define BTM_BLE_APPEARANCE_THERMOMETER_EAR 0x0301 -#define BTM_BLE_APPEARANCE_GENERIC_HEART_RATE 0x0340 -#define BTM_BLE_APPEARANCE_HEART_RATE_BELT 0x0341 -#define BTM_BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE 0x0380 -#define BTM_BLE_APPEARANCE_BLOOD_PRESSURE_ARM 0x0381 -#define BTM_BLE_APPEARANCE_BLOOD_PRESSURE_WRIST 0x0382 -#define BTM_BLE_APPEARANCE_GENERIC_HID 0x03C0 -#define BTM_BLE_APPEARANCE_HID_KEYBOARD 0x03C1 -#define BTM_BLE_APPEARANCE_HID_MOUSE 0x03C2 -#define BTM_BLE_APPEARANCE_HID_JOYSTICK 0x03C3 -#define BTM_BLE_APPEARANCE_HID_GAMEPAD 0x03C4 -#define BTM_BLE_APPEARANCE_HID_DIGITIZER_TABLET 0x03C5 -#define BTM_BLE_APPEARANCE_HID_CARD_READER 0x03C6 -#define BTM_BLE_APPEARANCE_HID_DIGITAL_PEN 0x03C7 -#define BTM_BLE_APPEARANCE_HID_BARCODE_SCANNER 0x03C8 -#define BTM_BLE_APPEARANCE_GENERIC_GLUCOSE 0x0400 -#define BTM_BLE_APPEARANCE_GENERIC_WALKING 0x0440 -#define BTM_BLE_APPEARANCE_WALKING_IN_SHOE 0x0441 -#define BTM_BLE_APPEARANCE_WALKING_ON_SHOE 0x0442 -#define BTM_BLE_APPEARANCE_WALKING_ON_HIP 0x0443 -#define BTM_BLE_APPEARANCE_GENERIC_CYCLING 0x0480 -#define BTM_BLE_APPEARANCE_CYCLING_COMPUTER 0x0481 -#define BTM_BLE_APPEARANCE_CYCLING_SPEED 0x0482 -#define BTM_BLE_APPEARANCE_CYCLING_CADENCE 0x0483 -#define BTM_BLE_APPEARANCE_CYCLING_POWER 0x0484 -#define BTM_BLE_APPEARANCE_CYCLING_SPEED_CADENCE 0x0485 -#define BTM_BLE_APPEARANCE_GENERIC_WEARABLE_AUDIO_DEVICE 0x0940 -#define BTM_BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_EARBUD 0x0941 -#define BTM_BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_HEADSET 0x0942 -#define BTM_BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_HEADPHONES 0x0943 -#define BTM_BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_NECK_BAND 0x0944 -#define BTM_BLE_APPEARANCE_GENERIC_PULSE_OXIMETER 0x0C40 -#define BTM_BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP 0x0C41 -#define BTM_BLE_APPEARANCE_PULSE_OXIMETER_WRIST 0x0C42 -#define BTM_BLE_APPEARANCE_GENERIC_WEIGHT 0x0C80 -#define BTM_BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS 0x1440 -#define BTM_BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION 0x1441 -#define BTM_BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION_AND_NAV 0x1442 -#define BTM_BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION_POD 0x1443 -#define BTM_BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION_POD_AND_NAV 0x1444 +#define BTM_BLE_APPEARANCE_UNKNOWN BLE_APPEARANCE_UNKNOWN +#define BTM_BLE_APPEARANCE_GENERIC_PHONE BLE_APPEARANCE_GENERIC_PHONE +#define BTM_BLE_APPEARANCE_GENERIC_COMPUTER BLE_APPEARANCE_GENERIC_COMPUTER +#define BTM_BLE_APPEARANCE_GENERIC_WATCH BLE_APPEARANCE_GENERIC_WATCH +#define BTM_BLE_APPEARANCE_SPORTS_WATCH BLE_APPEARANCE_SPORTS_WATCH +#define BTM_BLE_APPEARANCE_GENERIC_CLOCK BLE_APPEARANCE_GENERIC_CLOCK +#define BTM_BLE_APPEARANCE_GENERIC_DISPLAY BLE_APPEARANCE_GENERIC_DISPLAY +#define BTM_BLE_APPEARANCE_GENERIC_REMOTE BLE_APPEARANCE_GENERIC_REMOTE +#define BTM_BLE_APPEARANCE_GENERIC_EYEGLASSES BLE_APPEARANCE_GENERIC_EYEGLASSES +#define BTM_BLE_APPEARANCE_GENERIC_TAG BLE_APPEARANCE_GENERIC_TAG +#define BTM_BLE_APPEARANCE_GENERIC_KEYRING BLE_APPEARANCE_GENERIC_KEYRING +#define BTM_BLE_APPEARANCE_GENERIC_MEDIA_PLAYER BLE_APPEARANCE_GENERIC_MEDIA_PLAYER +#define BTM_BLE_APPEARANCE_GENERIC_BARCODE_SCANNER BLE_APPEARANCE_GENERIC_BARCODE_SCANNER +#define BTM_BLE_APPEARANCE_GENERIC_THERMOMETER BLE_APPEARANCE_GENERIC_THERMOMETER +#define BTM_BLE_APPEARANCE_THERMOMETER_EAR BLE_APPEARANCE_THERMOMETER_EAR +#define BTM_BLE_APPEARANCE_GENERIC_HEART_RATE BLE_APPEARANCE_GENERIC_HEART_RATE +#define BTM_BLE_APPEARANCE_HEART_RATE_BELT BLE_APPEARANCE_HEART_RATE_BELT +#define BTM_BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE +#define BTM_BLE_APPEARANCE_BLOOD_PRESSURE_ARM BLE_APPEARANCE_BLOOD_PRESSURE_ARM +#define BTM_BLE_APPEARANCE_BLOOD_PRESSURE_WRIST BLE_APPEARANCE_BLOOD_PRESSURE_WRIST +#define BTM_BLE_APPEARANCE_GENERIC_HID BLE_APPEARANCE_GENERIC_HID +#define BTM_BLE_APPEARANCE_HID_KEYBOARD BLE_APPEARANCE_HID_KEYBOARD +#define BTM_BLE_APPEARANCE_HID_MOUSE BLE_APPEARANCE_HID_MOUSE +#define BTM_BLE_APPEARANCE_HID_JOYSTICK BLE_APPEARANCE_HID_JOYSTICK +#define BTM_BLE_APPEARANCE_HID_GAMEPAD BLE_APPEARANCE_HID_GAMEPAD +#define BTM_BLE_APPEARANCE_HID_DIGITIZER_TABLET BLE_APPEARANCE_HID_DIGITIZER_TABLET +#define BTM_BLE_APPEARANCE_HID_CARD_READER BLE_APPEARANCE_HID_CARD_READER +#define BTM_BLE_APPEARANCE_HID_DIGITAL_PEN BLE_APPEARANCE_HID_DIGITAL_PEN +#define BTM_BLE_APPEARANCE_HID_BARCODE_SCANNER BLE_APPEARANCE_HID_BARCODE_SCANNER +#define BTM_BLE_APPEARANCE_GENERIC_GLUCOSE BLE_APPEARANCE_GENERIC_GLUCOSE +#define BTM_BLE_APPEARANCE_GENERIC_WALKING BLE_APPEARANCE_GENERIC_WALKING +#define BTM_BLE_APPEARANCE_WALKING_IN_SHOE BLE_APPEARANCE_WALKING_IN_SHOE +#define BTM_BLE_APPEARANCE_WALKING_ON_SHOE BLE_APPEARANCE_WALKING_ON_SHOE +#define BTM_BLE_APPEARANCE_WALKING_ON_HIP BLE_APPEARANCE_WALKING_ON_HIP +#define BTM_BLE_APPEARANCE_GENERIC_CYCLING BLE_APPEARANCE_GENERIC_CYCLING +#define BTM_BLE_APPEARANCE_CYCLING_COMPUTER BLE_APPEARANCE_CYCLING_COMPUTER +#define BTM_BLE_APPEARANCE_CYCLING_SPEED BLE_APPEARANCE_CYCLING_SPEED +#define BTM_BLE_APPEARANCE_CYCLING_CADENCE BLE_APPEARANCE_CYCLING_CADENCE +#define BTM_BLE_APPEARANCE_CYCLING_POWER BLE_APPEARANCE_CYCLING_POWER +#define BTM_BLE_APPEARANCE_CYCLING_SPEED_CADENCE BLE_APPEARANCE_CYCLING_SPEED_CADENCE +#define BTM_BLE_APPEARANCE_GENERIC_WEARABLE_AUDIO_DEVICE \ + BLE_APPEARANCE_GENERIC_WEARABLE_AUDIO_DEVICE +#define BTM_BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_EARBUD \ + BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_EARBUD +#define BTM_BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_HEADSET \ + BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_HEADSET +#define BTM_BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_HEADPHONES \ + BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_HEADPHONES +#define BTM_BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_NECK_BAND \ + BLE_APPEARANCE_WEARABLE_AUDIO_DEVICE_NECK_BAND +#define BTM_BLE_APPEARANCE_GENERIC_PULSE_OXIMETER BLE_APPEARANCE_GENERIC_PULSE_OXIMETER +#define BTM_BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP BLE_APPEARANCE_PULSE_OXIMETER_FINGERTIP +#define BTM_BLE_APPEARANCE_PULSE_OXIMETER_WRIST BLE_APPEARANCE_PULSE_OXIMETER_WRIST +#define BTM_BLE_APPEARANCE_GENERIC_WEIGHT BLE_APPEARANCE_GENERIC_WEIGHT +#define BTM_BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS \ + BLE_APPEARANCE_GENERIC_OUTDOOR_SPORTS +#define BTM_BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION \ + BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION +#define BTM_BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION_AND_NAV \ + BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION_AND_NAV +#define BTM_BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION_POD \ + BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION_POD +#define BTM_BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION_POD_AND_NAV \ + BLE_APPEARANCE_OUTDOOR_SPORTS_LOCATION_POD_AND_NAV /* Structure returned with Rand/Encrypt complete callback */ typedef struct { diff --git a/system/stack/include/port_api.h b/system/stack/include/port_api.h index 3a9fd7e4b3..d515813b81 100644 --- a/system/stack/include/port_api.h +++ b/system/stack/include/port_api.h @@ -360,6 +360,19 @@ typedef void(tPORT_MGMT_CALLBACK)(const tPORT_RESULT code, uint16_t port_handle) /******************************************************************************* * + * Function PORT_SetAppUid + * + * Description This function configures connection according to the + * specifications in the tPORT_STATE structure. + * + * Parameters: handle - Handle returned in the RFCOMM_CreateConnection + * app_uid - Uid of app that requested the socket + * + ******************************************************************************/ +[[nodiscard]] int PORT_SetAppUid(uint16_t handle, uint32_t app_uid); + +/******************************************************************************* + * * Function PORT_SetState * * Description This function configures connection according to the diff --git a/system/stack/include/security_client_callbacks.h b/system/stack/include/security_client_callbacks.h index da1bd4e425..d9d19f33f6 100644 --- a/system/stack/include/security_client_callbacks.h +++ b/system/stack/include/security_client_callbacks.h @@ -110,7 +110,7 @@ typedef struct { tBTM_SEC_CALLBACK* p_callback, void* p_ref_data, tBTM_BLE_SEC_ACT sec_act); bool (*BTM_IsEncrypted)(const RawAddress& bd_addr, tBT_TRANSPORT transport); - bool (*BTM_SecIsSecurityPending)(const RawAddress& bd_addr); + bool (*BTM_SecIsLeSecurityPending)(const RawAddress& bd_addr); bool (*BTM_IsLinkKeyKnown)(const RawAddress& bd_addr, tBT_TRANSPORT transport); // Secure service management diff --git a/system/stack/include/smp_status.h b/system/stack/include/smp_status.h index 86670a5161..1d222ab419 100644 --- a/system/stack/include/smp_status.h +++ b/system/stack/include/smp_status.h @@ -41,25 +41,27 @@ typedef enum : uint8_t { SMP_NUMERIC_COMPAR_FAIL = 0x0C, SMP_BR_PARING_IN_PROGR = 0x0D, SMP_XTRANS_DERIVE_NOT_ALLOW = 0x0E, - SMP_MAX_FAIL_RSN_PER_SPEC = SMP_XTRANS_DERIVE_NOT_ALLOW, + SMP_KEY_REJECTED = 0x0F, + SMP_BUSY = 0x10, /*device is not ready to perform a pairing procedure*/ + SMP_MAX_FAIL_RSN_PER_SPEC = SMP_BUSY, /* self defined error code */ - SMP_PAIR_INTERNAL_ERR = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x01), /* 0x0F */ + SMP_PAIR_INTERNAL_ERR = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x01), /* 0x11 */ /* Unknown IO capability, unable to decide association model */ - SMP_UNKNOWN_IO_CAP = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x02), /* 0x10 */ + SMP_UNKNOWN_IO_CAP = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x02), /* 0x12 */ - SMP_BUSY = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x05), /* 0x13 */ - SMP_ENC_FAIL = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x06), /* 0x14 */ - SMP_STARTED = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x07), /* 0x15 */ - SMP_RSP_TIMEOUT = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x08), /* 0x16 */ + SMP_IMPL_BUSY = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x05), /* 0x15 */ + SMP_ENC_FAIL = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x06), /* 0x16 */ + SMP_STARTED = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x07), /* 0x17 */ + SMP_RSP_TIMEOUT = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x08), /* 0x18 */ /* Unspecified failure reason */ - SMP_FAIL = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x0A), /* 0x18 */ + SMP_FAIL = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x0A), /* 0x1A */ - SMP_CONN_TOUT = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x0B), /* 0x19 */ - SMP_SIRK_DEVICE_INVALID = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x0C), /* 0x1a */ - SMP_USER_CANCELLED = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x0D), /* 0x1b */ + SMP_CONN_TOUT = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x0B), /* 0x1B */ + SMP_SIRK_DEVICE_INVALID = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x0C), /* 0x1C */ + SMP_USER_CANCELLED = (SMP_MAX_FAIL_RSN_PER_SPEC + 0x0D), /* 0x1D */ } tSMP_STATUS; inline std::string smp_status_text(const tSMP_STATUS& status) { @@ -79,9 +81,11 @@ inline std::string smp_status_text(const tSMP_STATUS& status) { CASE_RETURN_TEXT(SMP_NUMERIC_COMPAR_FAIL); CASE_RETURN_TEXT(SMP_BR_PARING_IN_PROGR); CASE_RETURN_TEXT(SMP_XTRANS_DERIVE_NOT_ALLOW); + CASE_RETURN_TEXT(SMP_KEY_REJECTED); + CASE_RETURN_TEXT(SMP_BUSY); CASE_RETURN_TEXT(SMP_PAIR_INTERNAL_ERR); CASE_RETURN_TEXT(SMP_UNKNOWN_IO_CAP); - CASE_RETURN_TEXT(SMP_BUSY); + CASE_RETURN_TEXT(SMP_IMPL_BUSY); CASE_RETURN_TEXT(SMP_ENC_FAIL); CASE_RETURN_TEXT(SMP_STARTED); CASE_RETURN_TEXT(SMP_RSP_TIMEOUT); diff --git a/system/stack/rfcomm/port_api.cc b/system/stack/rfcomm/port_api.cc index d74d4d1835..fea9367fbc 100644 --- a/system/stack/rfcomm/port_api.cc +++ b/system/stack/rfcomm/port_api.cc @@ -142,7 +142,7 @@ int RFCOMM_CreateConnectionWithSecurity(uint16_t uuid, uint8_t scn, bool is_serv log::error( "already at opened state {}, RFC_state={}, MCB_state={}, " "bd_addr={}, scn={}, is_server={}, mtu={}, uuid=0x{:x}, dlci={}, p_mcb={}, port={}", - static_cast<int>(p_port->state), static_cast<int>(p_port->rfc.state), + static_cast<int>(p_port->state), static_cast<int>(p_port->rfc.sm_cb.state), p_port->rfc.p_mcb ? p_port->rfc.p_mcb->state : 0, bd_addr, scn, is_server, mtu, uuid, dlci, std::format_ptr(p_mcb), p_port->handle); *p_handle = p_port->handle; @@ -455,14 +455,14 @@ int PORT_CheckConnection(uint16_t handle, RawAddress* bd_addr, uint16_t* p_lcid) } log::verbose("handle={}, in_use={}, port_state={}, p_mcb={}, peer_ready={}, rfc_state={}", handle, p_port->in_use, p_port->state, std::format_ptr(p_port->rfc.p_mcb), - p_port->rfc.p_mcb ? p_port->rfc.p_mcb->peer_ready : -1, p_port->rfc.state); + p_port->rfc.p_mcb ? p_port->rfc.p_mcb->peer_ready : -1, p_port->rfc.sm_cb.state); if (!p_port->in_use || (p_port->state == PORT_CONNECTION_STATE_CLOSED)) { return PORT_NOT_OPENED; } if (!p_port->rfc.p_mcb || !p_port->rfc.p_mcb->peer_ready || - (p_port->rfc.state != RFC_STATE_OPENED)) { + (p_port->rfc.sm_cb.state != RFC_STATE_OPENED)) { return PORT_LINE_ERR; } @@ -509,8 +509,8 @@ bool PORT_IsOpening(RawAddress* bd_addr) { if (multiplexer_cb.state == RFC_MX_STATE_CONNECTED) { const tPORT* p_port = get_port_from_mcb(&multiplexer_cb); log::info("RFC_MX_STATE_CONNECTED, found_port={}, tRFC_PORT_STATE={}", - (p_port != nullptr) ? "T" : "F", (p_port != nullptr) ? p_port->rfc.state : 0); - if ((p_port == nullptr) || (p_port->rfc.state < RFC_STATE_OPENED)) { + (p_port != nullptr) ? "T" : "F", (p_port != nullptr) ? p_port->rfc.sm_cb.state : 0); + if ((p_port == nullptr) || (p_port->rfc.sm_cb.state < RFC_STATE_OPENED)) { /* Port is not established yet. */ *bd_addr = multiplexer_cb.bd_addr; log::info("In RFC_MX_STATE_CONNECTED but port is not established yet, returning true"); @@ -554,8 +554,8 @@ bool PORT_IsCollisionDetected(RawAddress bd_addr) { if (multiplexer_cb.state == RFC_MX_STATE_CONNECTED) { const tPORT* p_port = get_port_from_mcb(&multiplexer_cb); log::info("RFC_MX_STATE_CONNECTED, found_port={}, tRFC_PORT_STATE={}", - (p_port != nullptr) ? "T" : "F", (p_port != nullptr) ? p_port->rfc.state : 0); - if ((p_port == nullptr) || (p_port->rfc.state < RFC_STATE_OPENED)) { + (p_port != nullptr) ? "T" : "F", (p_port != nullptr) ? p_port->rfc.sm_cb.state : 0); + if ((p_port == nullptr) || (p_port->rfc.sm_cb.state < RFC_STATE_OPENED)) { // Port is not established yet log::info( "In RFC_MX_STATE_CONNECTED but port is not established yet, " @@ -570,6 +570,30 @@ bool PORT_IsCollisionDetected(RawAddress bd_addr) { /******************************************************************************* * + * Function PORT_SetAppUid + * + * Description This function configures connection according to the + * specifications in the tPORT_STATE structure. + * + * Parameters: handle - Handle returned in the RFCOMM_CreateConnection + * app_uid - Uid of app that requested the socket + * + ******************************************************************************/ +int PORT_SetAppUid(uint16_t handle, uint32_t app_uid) { + tPORT* p_port = get_port_from_handle(handle); + + if (p_port == nullptr) { + log::error("Unable to get RFCOMM port control block bad handle:{}", handle); + return PORT_BAD_HANDLE; + } + + p_port->app_uid = app_uid; + + return PORT_SUCCESS; +} + +/******************************************************************************* + * * Function PORT_SetSettings * * Description This function configures connection according to the @@ -828,7 +852,7 @@ int PORT_ReadData(uint16_t handle, char* p_data, uint16_t max_len, uint16_t* p_l static int port_write(tPORT* p_port, BT_HDR* p_buf) { /* We should not allow to write data in to server port when connection is not * opened */ - if (p_port->is_server && (p_port->rfc.state != RFC_STATE_OPENED)) { + if (p_port->is_server && (p_port->rfc.sm_cb.state != RFC_STATE_OPENED)) { osi_free(p_buf); return PORT_CLOSED; } @@ -837,7 +861,7 @@ static int port_write(tPORT* p_port, BT_HDR* p_buf) { /* Peer is not ready or Port is not yet opened or initial port control */ /* command has not been sent */ if (p_port->tx.peer_fc || !p_port->rfc.p_mcb || !p_port->rfc.p_mcb->peer_ready || - (p_port->rfc.state != RFC_STATE_OPENED) || + (p_port->rfc.sm_cb.state != RFC_STATE_OPENED) || ((p_port->port_ctrl & (PORT_CTRL_REQ_SENT | PORT_CTRL_IND_RECEIVED)) != (PORT_CTRL_REQ_SENT | PORT_CTRL_IND_RECEIVED))) { if ((p_port->tx.queue_size > PORT_TX_CRITICAL_WM) || @@ -857,7 +881,7 @@ static int port_write(tPORT* p_port, BT_HDR* p_buf) { "Data is enqueued. flow disabled {} peer_ready {} state {} ctrl_state " "{:x}", p_port->tx.peer_fc, p_port->rfc.p_mcb && p_port->rfc.p_mcb->peer_ready, - p_port->rfc.state, p_port->port_ctrl); + p_port->rfc.sm_cb.state, p_port->port_ctrl); fixed_queue_enqueue(p_port->tx.queue, p_buf); p_port->tx.queue_size += p_buf->len; diff --git a/system/stack/rfcomm/port_int.h b/system/stack/rfcomm/port_int.h index 94d9f4cc8b..6854ba9524 100644 --- a/system/stack/rfcomm/port_int.h +++ b/system/stack/rfcomm/port_int.h @@ -34,6 +34,7 @@ #include "stack/include/l2cap_types.h" #include "stack/include/port_api.h" #include "stack/include/rfcdefs.h" +#include "stack/rfcomm/rfc_event.h" #include "stack/rfcomm/rfc_state.h" #include "types/raw_address.h" @@ -45,7 +46,7 @@ #define PORT_FC_CREDIT 2 /* use RFCOMM credit based flow control */ /* - * Define Port Data Transfere control block + * Define Port Data Transfer control block */ typedef struct { fixed_queue_t* queue; /* Queue of buffers waiting to be sent */ @@ -104,10 +105,22 @@ typedef struct { } tRFC_MCB; /* + * RFCOMM Port State Machine Control Block + */ +struct RfcommPortSm { + tRFC_PORT_STATE state; + tRFC_PORT_STATE state_prior; + tRFC_PORT_EVENT last_event; + tPORT_RESULT close_reason; + uint64_t open_timestamp; + uint64_t close_timestamp; +}; + +/* * RFCOMM Port Connection Control Block */ typedef struct { - tRFC_PORT_STATE state; /* Current state of the connection */ + RfcommPortSm sm_cb; // State machine control block #define RFC_RSP_PN 0x01 #define RFC_RSP_RPN_REPLY 0x02 @@ -155,8 +168,9 @@ typedef struct { tPORT_CONNECTION_STATE state; /* State of the application */ - uint8_t scn; /* Service channel number */ - uint16_t uuid; /* Service UUID */ + uint8_t scn; /* Service channel number */ + uint16_t uuid; /* Service UUID */ + uint32_t app_uid; /* UID of the app for which this socket was created */ RawAddress bd_addr; /* BD ADDR of the device for the multiplexer channel */ bool is_server; /* true if the server application */ diff --git a/system/stack/rfcomm/port_rfc.cc b/system/stack/rfcomm/port_rfc.cc index a4cf8d5adf..14a9cb5bf0 100644 --- a/system/stack/rfcomm/port_rfc.cc +++ b/system/stack/rfcomm/port_rfc.cc @@ -180,7 +180,7 @@ void port_start_close(tPORT* p_port) { } /* Check if RFCOMM side has been closed while the message was queued */ - if ((p_mcb == NULL) || (p_port->rfc.state == RFC_STATE_CLOSED)) { + if ((p_mcb == NULL) || (p_port->rfc.sm_cb.state == RFC_STATE_CLOSED)) { /* Call management callback function before calling port_release_port() to * clear tPort */ if (p_port->p_mgmt_callback) { @@ -899,7 +899,7 @@ void PORT_FlowInd(tRFC_MCB* p_mcb, uint8_t dlci, bool enable_data) { if (dlci == 0) { p_port = &rfc_cb.port.port[i]; if (!p_port->in_use || (p_port->rfc.p_mcb != p_mcb) || - (p_port->rfc.state != RFC_STATE_OPENED)) { + (p_port->rfc.sm_cb.state != RFC_STATE_OPENED)) { continue; } } @@ -991,7 +991,7 @@ void port_rfc_closed(tPORT* p_port, uint8_t res) { log::warn("port_rfc_closed in OPENING state ignored"); rfc_port_timer_stop(p_port); - p_port->rfc.state = RFC_STATE_CLOSED; + p_port->rfc.sm_cb.state = RFC_STATE_CLOSED; if (p_mcb) { p_mcb->port_handles[p_port->dlci] = 0; @@ -1049,10 +1049,10 @@ void port_rfc_closed(tPORT* p_port, uint8_t res) { p_port->p_mgmt_callback(static_cast<tPORT_RESULT>(res2), p_port->handle); } - p_port->rfc.state = RFC_STATE_CLOSED; + p_port->rfc.sm_cb.state = RFC_STATE_CLOSED; log::info( - "RFCOMM connection closed, index={}, state={}, reason={}[{}], " + "RFCOMM connection closed, port_handle={}, state={}, reason={}[{}], " "UUID=0x{:x}, bd_addr={}, is_server={}", p_port->handle, p_port->state, PORT_GetResultString(res), res, p_port->uuid, p_port->bd_addr, p_port->is_server); diff --git a/system/stack/rfcomm/port_utils.cc b/system/stack/rfcomm/port_utils.cc index 25e1da9f4c..dc59e58987 100644 --- a/system/stack/rfcomm/port_utils.cc +++ b/system/stack/rfcomm/port_utils.cc @@ -199,8 +199,8 @@ void port_select_mtu(tPORT* p_port) { * ******************************************************************************/ void port_release_port(tPORT* p_port) { - log::verbose("p_port: {} state: {} keep_handle: {}", std::format_ptr(p_port), p_port->rfc.state, - p_port->keep_port_handle); + log::verbose("p_port: {} state: {} keep_handle: {}", std::format_ptr(p_port), + p_port->rfc.sm_cb.state, p_port->keep_port_handle); mutex_global_lock(); BT_HDR* p_buf; @@ -219,7 +219,7 @@ void port_release_port(tPORT* p_port) { p_port->state = PORT_CONNECTION_STATE_CLOSED; - if (p_port->rfc.state == RFC_STATE_CLOSED) { + if (p_port->rfc.sm_cb.state == RFC_STATE_CLOSED) { if (p_port->rfc.p_mcb) { p_port->rfc.p_mcb->port_handles[p_port->dlci] = 0; @@ -228,6 +228,7 @@ void port_release_port(tPORT* p_port) { } rfc_port_timer_stop(p_port); + p_port->rfc.sm_cb = {}; mutex_global_lock(); fixed_queue_free(p_port->tx.queue, nullptr); diff --git a/system/stack/rfcomm/rfc_port_fsm.cc b/system/stack/rfcomm/rfc_port_fsm.cc index 0f3ec064ff..be47e22617 100644 --- a/system/stack/rfcomm/rfc_port_fsm.cc +++ b/system/stack/rfcomm/rfc_port_fsm.cc @@ -77,11 +77,11 @@ void rfc_port_sm_execute(tPORT* p_port, tRFC_PORT_EVENT event, void* p_data) { log::assert_that(p_port != nullptr, "NULL port event {}", event); // logs for state RFC_STATE_OPENED handled in rfc_port_sm_opened() - if (p_port->rfc.state != RFC_STATE_OPENED) { + if (p_port->rfc.sm_cb.state != RFC_STATE_OPENED) { log::info("bd_addr:{}, handle:{}, state:{}, event:{}", p_port->bd_addr, p_port->handle, - rfcomm_port_state_text(p_port->rfc.state), rfcomm_port_event_text(event)); + rfcomm_port_state_text(p_port->rfc.sm_cb.state), rfcomm_port_event_text(event)); } - switch (p_port->rfc.state) { + switch (p_port->rfc.sm_cb.state) { case RFC_STATE_CLOSED: rfc_port_sm_state_closed(p_port, event, p_data); break; @@ -122,7 +122,7 @@ void rfc_port_sm_execute(tPORT* p_port, tRFC_PORT_EVENT event, void* p_data) { void rfc_port_sm_state_closed(tPORT* p_port, tRFC_PORT_EVENT event, void* p_data) { switch (event) { case RFC_PORT_EVENT_OPEN: - p_port->rfc.state = RFC_STATE_ORIG_WAIT_SEC_CHECK; + p_port->rfc.sm_cb.state = RFC_STATE_ORIG_WAIT_SEC_CHECK; btm_sec_mx_access_request(p_port->rfc.p_mcb->bd_addr, true, p_port->sec_mask, &rfc_sec_check_complete, p_port); return; @@ -143,7 +143,7 @@ void rfc_port_sm_state_closed(tPORT* p_port, tRFC_PORT_EVENT event, void* p_data rfc_timer_stop(p_port->rfc.p_mcb); /* Open will be continued after security checks are passed */ - p_port->rfc.state = RFC_STATE_TERM_WAIT_SEC_CHECK; + p_port->rfc.sm_cb.state = RFC_STATE_TERM_WAIT_SEC_CHECK; btm_sec_mx_access_request(p_port->rfc.p_mcb->bd_addr, false, p_port->sec_mask, &rfc_sec_check_complete, p_port); return; @@ -167,11 +167,11 @@ void rfc_port_sm_state_closed(tPORT* p_port, tRFC_PORT_EVENT event, void* p_data case RFC_PORT_EVENT_TIMEOUT: PORT_TimeOutCloseMux(p_port->rfc.p_mcb); - log::error("Port error state {} event {}", p_port->rfc.state, event); + log::error("Port error state {} event {}", p_port->rfc.sm_cb.state, event); return; default: log::error("Received unexpected event:{} in state:{}", rfcomm_port_event_text(event), - rfcomm_port_state_text(p_port->rfc.state)); + rfcomm_port_state_text(p_port->rfc.sm_cb.state)); } log::warn("Event ignored {}", rfcomm_port_event_text(event)); @@ -199,7 +199,7 @@ void rfc_port_sm_sabme_wait_ua(tPORT* p_port, tRFC_PORT_EVENT event, void* p_dat rfc_port_timer_start(p_port, RFC_DISC_TIMEOUT); rfc_send_disc(p_port->rfc.p_mcb, p_port->dlci); p_port->rfc.expected_rsp = 0; - p_port->rfc.state = RFC_STATE_DISC_WAIT_UA; + p_port->rfc.sm_cb.state = RFC_STATE_DISC_WAIT_UA; return; case RFC_PORT_EVENT_CLEAR: @@ -213,7 +213,7 @@ void rfc_port_sm_sabme_wait_ua(tPORT* p_port, tRFC_PORT_EVENT event, void* p_dat case RFC_PORT_EVENT_UA: rfc_port_timer_stop(p_port); - p_port->rfc.state = RFC_STATE_OPENED; + p_port->rfc.sm_cb.state = RFC_STATE_OPENED; if (uuid_logging_acceptlist.find(p_port->uuid) != uuid_logging_acceptlist.end()) { // Find Channel Control Block by Channel ID @@ -267,13 +267,13 @@ void rfc_port_sm_sabme_wait_ua(tPORT* p_port, tRFC_PORT_EVENT event, void* p_dat return; case RFC_PORT_EVENT_TIMEOUT: - p_port->rfc.state = RFC_STATE_CLOSED; + p_port->rfc.sm_cb.state = RFC_STATE_CLOSED; PORT_DlcEstablishCnf(p_port->rfc.p_mcb, p_port->dlci, p_port->rfc.p_mcb->peer_l2cap_mtu, RFCOMM_ERROR); return; default: log::error("Received unexpected event:{} in state:{}", rfcomm_port_event_text(event), - rfcomm_port_state_text(static_cast<tRFC_PORT_STATE>(p_port->rfc.state))); + rfcomm_port_state_text(static_cast<tRFC_PORT_STATE>(p_port->rfc.sm_cb.state))); } log::warn("Event ignored {}", rfcomm_port_event_text(event)); } @@ -296,7 +296,7 @@ void rfc_port_sm_term_wait_sec_check(tPORT* p_port, tRFC_PORT_EVENT event, void* if (*((tBTM_STATUS*)p_data) != tBTM_STATUS::BTM_SUCCESS) { log::error("Security check failed result:{} state:{} port_handle:{}", btm_status_text(*((tBTM_STATUS*)p_data)), - rfcomm_port_state_text(p_port->rfc.state), p_port->handle); + rfcomm_port_state_text(p_port->rfc.sm_cb.state), p_port->handle); /* Authentication/authorization failed. If link is still */ /* up send DM and check if we need to start inactive timer */ if (p_port->rfc.p_mcb) { @@ -306,7 +306,7 @@ void rfc_port_sm_term_wait_sec_check(tPORT* p_port, tRFC_PORT_EVENT event, void* } } else { log::debug("Security check succeeded state:{} port_handle:{}", - rfcomm_port_state_text(static_cast<tRFC_PORT_STATE>(p_port->rfc.state)), + rfcomm_port_state_text(static_cast<tRFC_PORT_STATE>(p_port->rfc.sm_cb.state)), p_port->handle); PORT_DlcEstablishInd(p_port->rfc.p_mcb, p_port->dlci, p_port->rfc.p_mcb->peer_l2cap_mtu); } @@ -334,7 +334,7 @@ void rfc_port_sm_term_wait_sec_check(tPORT* p_port, tRFC_PORT_EVENT event, void* case RFC_PORT_EVENT_DISC: btm_sec_abort_access_req(p_port->rfc.p_mcb->bd_addr); - p_port->rfc.state = RFC_STATE_CLOSED; + p_port->rfc.sm_cb.state = RFC_STATE_CLOSED; rfc_send_ua(p_port->rfc.p_mcb, p_port->dlci); PORT_DlcReleaseInd(p_port->rfc.p_mcb, p_port->dlci); @@ -351,7 +351,7 @@ void rfc_port_sm_term_wait_sec_check(tPORT* p_port, tRFC_PORT_EVENT event, void* } } else { rfc_send_ua(p_port->rfc.p_mcb, p_port->dlci); - p_port->rfc.state = RFC_STATE_OPENED; + p_port->rfc.sm_cb.state = RFC_STATE_OPENED; if (uuid_logging_acceptlist.find(p_port->uuid) != uuid_logging_acceptlist.end()) { // Find Channel Control Block by Channel ID @@ -378,7 +378,7 @@ void rfc_port_sm_term_wait_sec_check(tPORT* p_port, tRFC_PORT_EVENT event, void* return; default: log::error("Received unexpected event:{} in state:{}", rfcomm_port_event_text(event), - rfcomm_port_state_text(p_port->rfc.state)); + rfcomm_port_state_text(p_port->rfc.sm_cb.state)); } log::warn("Event ignored {}", event); } @@ -400,16 +400,16 @@ void rfc_port_sm_orig_wait_sec_check(tPORT* p_port, tRFC_PORT_EVENT event, void* if (*((tBTM_STATUS*)p_data) != tBTM_STATUS::BTM_SUCCESS) { log::error("Security check failed result:{} state:{} handle:{}", btm_status_text(*((tBTM_STATUS*)p_data)), - rfcomm_port_state_text(p_port->rfc.state), p_port->handle); + rfcomm_port_state_text(p_port->rfc.sm_cb.state), p_port->handle); p_port->rfc.p_mcb->is_disc_initiator = true; PORT_DlcEstablishCnf(p_port->rfc.p_mcb, p_port->dlci, 0, RFCOMM_SECURITY_ERR); rfc_port_closed(p_port); } else { log::debug("Security check succeeded state:{} handle:{}", - rfcomm_port_state_text(p_port->rfc.state), p_port->handle); + rfcomm_port_state_text(p_port->rfc.sm_cb.state), p_port->handle); rfc_send_sabme(p_port->rfc.p_mcb, p_port->dlci); rfc_port_timer_start(p_port, RFC_PORT_T1_TIMEOUT); - p_port->rfc.state = RFC_STATE_SABME_WAIT_UA; + p_port->rfc.sm_cb.state = RFC_STATE_SABME_WAIT_UA; } return; @@ -434,7 +434,7 @@ void rfc_port_sm_orig_wait_sec_check(tPORT* p_port, tRFC_PORT_EVENT event, void* return; default: log::error("Received unexpected event:{} in state:{}", rfcomm_port_event_text(event), - rfcomm_port_state_text(p_port->rfc.state)); + rfcomm_port_state_text(p_port->rfc.sm_cb.state)); } log::warn("Event ignored {}", rfcomm_port_event_text(event)); } @@ -452,21 +452,21 @@ void rfc_port_sm_orig_wait_sec_check(tPORT* p_port, tRFC_PORT_EVENT event, void* void rfc_port_sm_opened(tPORT* p_port, tRFC_PORT_EVENT event, void* p_data) { switch (event) { case RFC_PORT_EVENT_OPEN: - log::error("RFC_PORT_EVENT_OPEN bd_addr:{} handle:{} dlci:{} scn:{}", p_port->bd_addr, + log::error("RFC_PORT_EVENT_OPEN bd_addr:{} port_handle:{} dlci:{} scn:{}", p_port->bd_addr, p_port->handle, p_port->dlci, p_port->scn); return; case RFC_PORT_EVENT_CLOSE: - log::info("RFC_PORT_EVENT_CLOSE bd_addr:{}, handle:{} dlci:{} scn:{}", p_port->bd_addr, + log::info("RFC_PORT_EVENT_CLOSE bd_addr:{}, port_handle:{} dlci:{} scn:{}", p_port->bd_addr, p_port->handle, p_port->dlci, p_port->scn); rfc_port_timer_start(p_port, RFC_DISC_TIMEOUT); rfc_send_disc(p_port->rfc.p_mcb, p_port->dlci); p_port->rfc.expected_rsp = 0; - p_port->rfc.state = RFC_STATE_DISC_WAIT_UA; + p_port->rfc.sm_cb.state = RFC_STATE_DISC_WAIT_UA; return; case RFC_PORT_EVENT_CLEAR: - log::warn("RFC_PORT_EVENT_CLEAR bd_addr:{} handle:{} dlci:{} scn:{}", p_port->bd_addr, + log::warn("RFC_PORT_EVENT_CLEAR bd_addr:{} port_handle:{} dlci:{} scn:{}", p_port->bd_addr, p_port->handle, p_port->dlci, p_port->scn); rfc_port_closed(p_port); return; @@ -475,7 +475,7 @@ void rfc_port_sm_opened(tPORT* p_port, tRFC_PORT_EVENT event, void* p_data) { // Send credits in the frame. Pass them in the layer specific member of the hdr. // There might be an initial case when we reduced rx_max and credit_rx is still bigger. // Make sure that we do not send 255 - log::verbose("RFC_PORT_EVENT_DATA bd_addr:{} handle:{} dlci:{} scn:{}", p_port->bd_addr, + log::verbose("RFC_PORT_EVENT_DATA bd_addr:{} port_handle:{} dlci:{} scn:{}", p_port->bd_addr, p_port->handle, p_port->dlci, p_port->scn); if ((p_port->rfc.p_mcb->flow == PORT_FC_CREDIT) && (((BT_HDR*)p_data)->len < p_port->peer_mtu) && (!p_port->rx.user_fc) && @@ -490,27 +490,27 @@ void rfc_port_sm_opened(tPORT* p_port, tRFC_PORT_EVENT event, void* p_data) { return; case RFC_PORT_EVENT_UA: - log::verbose("RFC_PORT_EVENT_UA bd_addr:{} handle:{} dlci:{} scn:{}", p_port->bd_addr, + log::verbose("RFC_PORT_EVENT_UA bd_addr:{} port_handle:{} dlci:{} scn:{}", p_port->bd_addr, p_port->handle, p_port->dlci, p_port->scn); return; case RFC_PORT_EVENT_SABME: - log::verbose("RFC_PORT_EVENT_SABME bd_addr:{} handle:{} dlci:{} scn:{}", p_port->bd_addr, + log::verbose("RFC_PORT_EVENT_SABME bd_addr:{} port_handle:{} dlci:{} scn:{}", p_port->bd_addr, p_port->handle, p_port->dlci, p_port->scn); rfc_send_ua(p_port->rfc.p_mcb, p_port->dlci); return; case RFC_PORT_EVENT_DM: - log::info("RFC_EVENT_DM bd_addr:{} handle:{} dlci:{} scn:{}", p_port->bd_addr, p_port->handle, - p_port->dlci, p_port->scn); + log::info("RFC_EVENT_DM bd_addr:{} port_handle:{} dlci:{} scn:{}", p_port->bd_addr, + p_port->handle, p_port->dlci, p_port->scn); PORT_DlcReleaseInd(p_port->rfc.p_mcb, p_port->dlci); rfc_port_closed(p_port); return; case RFC_PORT_EVENT_DISC: - log::info("RFC_PORT_EVENT_DISC bd_addr:{} handle:{} dlci:{} scn:{}", p_port->bd_addr, + log::info("RFC_PORT_EVENT_DISC bd_addr:{} port_handle:{} dlci:{} scn:{}", p_port->bd_addr, p_port->handle, p_port->dlci, p_port->scn); - p_port->rfc.state = RFC_STATE_CLOSED; + p_port->rfc.sm_cb.state = RFC_STATE_CLOSED; rfc_send_ua(p_port->rfc.p_mcb, p_port->dlci); if (!fixed_queue_is_empty(p_port->rx.queue)) { /* give a chance to upper stack to close port properly */ @@ -522,19 +522,19 @@ void rfc_port_sm_opened(tPORT* p_port, tRFC_PORT_EVENT event, void* p_data) { return; case RFC_PORT_EVENT_UIH: - log::verbose("RFC_PORT_EVENT_UIH bd_addr:{}, handle:{} dlci:{} scn:{}", p_port->bd_addr, + log::verbose("RFC_PORT_EVENT_UIH bd_addr:{}, port_handle:{} dlci:{} scn:{}", p_port->bd_addr, p_port->handle, p_port->dlci, p_port->scn); rfc_port_uplink_data(p_port, (BT_HDR*)p_data); return; case RFC_PORT_EVENT_TIMEOUT: PORT_TimeOutCloseMux(p_port->rfc.p_mcb); - log::error("RFC_PORT_EVENT_TIMEOUT bd_addr:{} handle:{} dlci:{} scn:{}", p_port->bd_addr, + log::error("RFC_PORT_EVENT_TIMEOUT bd_addr:{} port_handle:{} dlci:{} scn:{}", p_port->bd_addr, p_port->handle, p_port->dlci, p_port->scn); return; default: - log::error("Received unexpected event:{} bd_addr:{} handle:{} dlci:{} scn:{}", + log::error("Received unexpected event:{} bd_addr:{} port_handle:{} dlci:{} scn:{}", rfcomm_port_event_text(event), p_port->bd_addr, p_port->handle, p_port->dlci, p_port->scn); break; @@ -560,7 +560,7 @@ void rfc_port_sm_disc_wait_ua(tPORT* p_port, tRFC_PORT_EVENT event, void* p_data return; case RFC_PORT_EVENT_CLEAR: - log::warn("RFC_PORT_EVENT_CLEAR, handle:{}", p_port->handle); + log::warn("RFC_PORT_EVENT_CLEAR, port_handle:{}", p_port->handle); rfc_port_closed(p_port); return; @@ -573,7 +573,7 @@ void rfc_port_sm_disc_wait_ua(tPORT* p_port, tRFC_PORT_EVENT event, void* p_data FALLTHROUGH_INTENDED; /* FALLTHROUGH */ case RFC_PORT_EVENT_DM: - log::warn("RFC_EVENT_DM|RFC_EVENT_UA[{}], handle:{}", event, p_port->handle); + log::warn("RFC_EVENT_DM|RFC_EVENT_UA[{}], port_handle:{}", event, p_port->handle); if (com::android::bluetooth::flags::rfcomm_always_disc_initiator_in_disc_wait_ua()) { // If we got a DM in RFC_STATE_DISC_WAIT_UA, it's likely that both ends // attempt to DISC at the same time and both get a DM. @@ -602,12 +602,12 @@ void rfc_port_sm_disc_wait_ua(tPORT* p_port, tRFC_PORT_EVENT event, void* p_data return; case RFC_PORT_EVENT_TIMEOUT: - log::error("RFC_EVENT_TIMEOUT, handle:{}", p_port->handle); + log::error("RFC_EVENT_TIMEOUT, port_handle:{}", p_port->handle); rfc_port_closed(p_port); return; default: log::error("Received unexpected event:{} in state:{}", rfcomm_port_event_text(event), - rfcomm_port_state_text(p_port->rfc.state)); + rfcomm_port_state_text(p_port->rfc.sm_cb.state)); } log::warn("Event ignored {}", rfcomm_port_event_text(event)); diff --git a/system/stack/rfcomm/rfc_port_if.cc b/system/stack/rfcomm/rfc_port_if.cc index 0973f0370a..af6906fbb5 100644 --- a/system/stack/rfcomm/rfc_port_if.cc +++ b/system/stack/rfcomm/rfc_port_if.cc @@ -257,7 +257,8 @@ void RFCOMM_ControlReq(tRFC_MCB* p_mcb, uint8_t dlci, tPORT_CTRL* p_pars) { return; } - if ((p_port->state != PORT_CONNECTION_STATE_OPENED) || (p_port->rfc.state != RFC_STATE_OPENED)) { + if ((p_port->state != PORT_CONNECTION_STATE_OPENED) || + (p_port->rfc.sm_cb.state != RFC_STATE_OPENED)) { return; } @@ -285,7 +286,8 @@ void RFCOMM_FlowReq(tRFC_MCB* p_mcb, uint8_t dlci, bool enable) { return; } - if ((p_port->state != PORT_CONNECTION_STATE_OPENED) || (p_port->rfc.state != RFC_STATE_OPENED)) { + if ((p_port->state != PORT_CONNECTION_STATE_OPENED) || + (p_port->rfc.sm_cb.state != RFC_STATE_OPENED)) { return; } @@ -312,7 +314,8 @@ void RFCOMM_LineStatusReq(tRFC_MCB* p_mcb, uint8_t dlci, uint8_t status) { return; } - if ((p_port->state != PORT_CONNECTION_STATE_OPENED) || (p_port->rfc.state != RFC_STATE_OPENED)) { + if ((p_port->state != PORT_CONNECTION_STATE_OPENED) || + (p_port->rfc.sm_cb.state != RFC_STATE_OPENED)) { return; } diff --git a/system/stack/rfcomm/rfc_utils.cc b/system/stack/rfcomm/rfc_utils.cc index f57214644d..87beaaf566 100644 --- a/system/stack/rfcomm/rfc_utils.cc +++ b/system/stack/rfcomm/rfc_utils.cc @@ -319,8 +319,8 @@ void rfc_sec_check_complete(RawAddress /* bd_addr */, tBT_TRANSPORT /* transport tPORT* p_port = (tPORT*)p_ref_data; /* Verify that PORT is still waiting for Security to complete */ - if (!p_port->in_use || ((p_port->rfc.state != RFC_STATE_ORIG_WAIT_SEC_CHECK) && - (p_port->rfc.state != RFC_STATE_TERM_WAIT_SEC_CHECK))) { + if (!p_port->in_use || ((p_port->rfc.sm_cb.state != RFC_STATE_ORIG_WAIT_SEC_CHECK) && + (p_port->rfc.sm_cb.state != RFC_STATE_TERM_WAIT_SEC_CHECK))) { return; } @@ -341,7 +341,7 @@ void rfc_sec_check_complete(RawAddress /* bd_addr */, tBT_TRANSPORT /* transport void rfc_port_closed(tPORT* p_port) { tRFC_MCB* p_mcb = p_port->rfc.p_mcb; rfc_port_timer_stop(p_port); - p_port->rfc.state = RFC_STATE_CLOSED; + p_port->rfc.sm_cb.state = RFC_STATE_CLOSED; /* If multiplexer channel was up mark it as down */ if (p_mcb) { diff --git a/system/stack/smp/smp_api.cc b/system/stack/smp/smp_api.cc index 0d66b0a064..a46f113aec 100644 --- a/system/stack/smp/smp_api.cc +++ b/system/stack/smp/smp_api.cc @@ -89,7 +89,7 @@ tSMP_STATUS SMP_Pair(const RawAddress& bd_addr, tBLE_ADDR_TYPE addr_type) { if (p_cb->state != SMP_STATE_IDLE || p_cb->flags & SMP_PAIR_FLAGS_WE_STARTED_DD || p_cb->smp_over_br) { /* pending security on going, reject this one */ - return SMP_BUSY; + return SMP_IMPL_BUSY; } else { p_cb->flags = SMP_PAIR_FLAGS_WE_STARTED_DD; p_cb->pairing_bda = bd_addr; @@ -135,7 +135,7 @@ tSMP_STATUS SMP_BR_PairWith(const RawAddress& bd_addr) { if (p_cb->state != SMP_STATE_IDLE || p_cb->smp_over_br || p_cb->flags & SMP_PAIR_FLAGS_WE_STARTED_DD) { /* pending security on going, reject this one */ - return SMP_BUSY; + return SMP_IMPL_BUSY; } p_cb->role = HCI_ROLE_CENTRAL; diff --git a/system/stack/smp/smp_utils.cc b/system/stack/smp/smp_utils.cc index 81b005a78c..e5aefe3c6a 100644 --- a/system/stack/smp/smp_utils.cc +++ b/system/stack/smp/smp_utils.cc @@ -1231,7 +1231,7 @@ void smp_reject_unexpected_pairing_command(const RawAddress& bd_addr) { p = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET; UINT8_TO_STREAM(p, SMP_OPCODE_PAIRING_FAILED); - UINT8_TO_STREAM(p, SMP_PAIR_NOT_SUPPORT); + UINT8_TO_STREAM(p, SMP_BUSY); p_buf->offset = L2CAP_MIN_OFFSET; p_buf->len = SMP_PAIR_FAIL_SIZE; diff --git a/system/stack/test/a2dp/AndroidTest.xml b/system/stack/test/a2dp/AndroidTest.xml index 71fc46cdb9..ace94ad9b8 100644 --- a/system/stack/test/a2dp/AndroidTest.xml +++ b/system/stack/test/a2dp/AndroidTest.xml @@ -34,7 +34,7 @@ <!-- Only run tests in MTS if the Bluetooth Mainline module is installed. --> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> - <option name="mainline-module-package-name" value="com.android.btservices" /> - <option name="mainline-module-package-name" value="com.google.android.btservices" /> + <option name="mainline-module-package-name" value="com.android.bt" /> + <option name="mainline-module-package-name" value="com.google.android.bt" /> </object> </configuration> diff --git a/system/stack/test/a2dp/AndroidTestForce32.xml b/system/stack/test/a2dp/AndroidTestForce32.xml index c2fa5e44dc..afb13d3266 100644 --- a/system/stack/test/a2dp/AndroidTestForce32.xml +++ b/system/stack/test/a2dp/AndroidTestForce32.xml @@ -33,7 +33,7 @@ <!-- Only run tests in MTS if the Bluetooth Mainline module is installed. --> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> - <option name="mainline-module-package-name" value="com.android.btservices" /> - <option name="mainline-module-package-name" value="com.google.android.btservices" /> + <option name="mainline-module-package-name" value="com.android.bt" /> + <option name="mainline-module-package-name" value="com.google.android.bt" /> </object> </configuration> diff --git a/system/stack/test/connection_manager_test.cc b/system/stack/test/connection_manager_test.cc index 8c99963279..7e59e85f08 100644 --- a/system/stack/test/connection_manager_test.cc +++ b/system/stack/test/connection_manager_test.cc @@ -57,6 +57,8 @@ constexpr tAPP_ID CLIENT2 = 2; constexpr tAPP_ID CLIENT3 = 3; constexpr tAPP_ID CLIENT10 = 10; +std::string get_client_name(uint8_t /* gatt_if */) { return ""; } + const tBLE_BD_ADDR BTM_Sec_GetAddressWithType(const RawAddress& bd_addr) { return tBLE_BD_ADDR{.type = BLE_ADDR_PUBLIC, .bda = bd_addr}; } diff --git a/system/stack/test/gatt/gatt_sr_test.cc b/system/stack/test/gatt/gatt_sr_test.cc index 285aa23ea8..9c5eb868d0 100644 --- a/system/stack/test/gatt/gatt_sr_test.cc +++ b/system/stack/test/gatt/gatt_sr_test.cc @@ -53,16 +53,6 @@ struct TestMutables { TestMutables test_state_; } // namespace -namespace connection_manager { -bool background_connect_remove(uint8_t /*app_id*/, const RawAddress& /*address*/) { return false; } -bool direct_connect_remove(uint8_t /*app_id*/, const RawAddress& /*address*/, - bool /*connection_timeout*/) { - return false; -} -bool is_background_connection(const RawAddress& /*address*/) { return false; } - -} // namespace connection_manager - BT_HDR* attp_build_sr_msg(tGATT_TCB& /*tcb*/, uint8_t op_code, tGATT_SR_MSG* /*p_msg*/, uint16_t /*payload_size*/) { test_state_.attp_build_sr_msg.op_code_ = op_code; @@ -81,7 +71,6 @@ tGATT_STATUS attp_send_sr_msg(tGATT_TCB& /*tcb*/, uint16_t /*cid*/, BT_HDR* /*p_ void gatt_act_discovery(tGATT_CLCB* /*p_clcb*/) {} bool gatt_disconnect(tGATT_TCB* /*p_tcb*/) { return false; } -void gatt_cancel_connect(const RawAddress& /*bd_addr*/, tBT_TRANSPORT /*transport*/) {} tGATT_CH_STATE gatt_get_ch_state(tGATT_TCB* /*p_tcb*/) { return GATT_CH_CLOSE; } tGATT_STATUS gatts_db_read_attr_value_by_type(tGATT_TCB& /*tcb*/, uint16_t /*cid*/, tGATT_SVC_DB* /*p_db*/, uint8_t /*op_code*/, diff --git a/system/stack/test/gatt/mock_gatt_utils_ref.cc b/system/stack/test/gatt/mock_gatt_utils_ref.cc index a7b9082824..f4df22397b 100644 --- a/system/stack/test/gatt/mock_gatt_utils_ref.cc +++ b/system/stack/test/gatt/mock_gatt_utils_ref.cc @@ -46,7 +46,6 @@ void gatt_update_app_use_link_flag(tGATT_IF /*gatt_if*/, tGATT_TCB* /*p_tcb*/, b bool /*check_acl_link*/) {} void gatts_proc_srv_chg_ind_ack(tGATT_TCB) {} bool gatt_disconnect(tGATT_TCB* /*p_tcb*/) { return false; } -void gatt_cancel_connect(const RawAddress& /*bd_addr*/, tBT_TRANSPORT /*transport*/) {} tGATT_CH_STATE gatt_get_ch_state(tGATT_TCB* /*p_tcb*/) { return GATT_CH_CLOSE; } void gatt_set_ch_state(tGATT_TCB* /*p_tcb*/, tGATT_CH_STATE /*ch_state*/) {} diff --git a/system/stack/test/gatt/stack_gatt_test.cc b/system/stack/test/gatt/stack_gatt_test.cc index 71928440a4..9f0cacf48a 100644 --- a/system/stack/test/gatt/stack_gatt_test.cc +++ b/system/stack/test/gatt/stack_gatt_test.cc @@ -124,7 +124,6 @@ TEST_F(StackGattTest, lifecycle_tGATT_REG) { memset(reg0.get(), 0, sizeof(tGATT_REG)); // Restore the complex structure after memset memset(®1.name, 0, sizeof(std::string)); - memset(®1.direct_connect_request, 0, sizeof(std::set<RawAddress>)); memset(®1.mtu_prefs, 0, sizeof(std::map<RawAddress, uint16_t>)); reg1 = {}; ASSERT_EQ(0, memcmp(reg0.get(), ®1, actual_sizeof_tGATT_REG())); diff --git a/system/stack/test/rfcomm/stack_rfcomm_port_test.cc b/system/stack/test/rfcomm/stack_rfcomm_port_test.cc index 54ca9943b6..084e109c72 100644 --- a/system/stack/test/rfcomm/stack_rfcomm_port_test.cc +++ b/system/stack/test/rfcomm/stack_rfcomm_port_test.cc @@ -48,9 +48,9 @@ TEST_F(StackRfcommPortTest, PORT_IsOpening__basic) { ASSERT_TRUE(PORT_IsOpening(&bd_addr)); rfc_cb.port.rfc_mcb[0].state = RFC_MX_STATE_CONNECTED; rfc_cb.port.port[0].rfc.p_mcb = &rfc_cb.port.rfc_mcb[0]; - rfc_cb.port.port[0].rfc.state = RFC_STATE_OPENED; + rfc_cb.port.port[0].rfc.sm_cb.state = RFC_STATE_OPENED; ASSERT_FALSE(PORT_IsOpening(&bd_addr)); - rfc_cb.port.port[0].rfc.state = RFC_STATE_TERM_WAIT_SEC_CHECK; + rfc_cb.port.port[0].rfc.sm_cb.state = RFC_STATE_TERM_WAIT_SEC_CHECK; ASSERT_TRUE(PORT_IsOpening(&bd_addr)); rfc_cb.port.rfc_mcb[0].state = RFC_MX_STATE_DISC_WAIT_UA; ASSERT_FALSE(PORT_IsOpening(&bd_addr)); @@ -90,9 +90,9 @@ TEST_F(StackRfcommPortTest, PORT_IsCollisionDetected__basic) { rfc_cb.port.rfc_mcb[0].state = RFC_MX_STATE_CONNECTED; rfc_cb.port.port[0].rfc.p_mcb = &rfc_cb.port.rfc_mcb[0]; - rfc_cb.port.port[0].rfc.state = RFC_STATE_OPENED; + rfc_cb.port.port[0].rfc.sm_cb.state = RFC_STATE_OPENED; ASSERT_FALSE(PORT_IsCollisionDetected(test_bd_addr)); - rfc_cb.port.port[0].rfc.state = RFC_STATE_TERM_WAIT_SEC_CHECK; + rfc_cb.port.port[0].rfc.sm_cb.state = RFC_STATE_TERM_WAIT_SEC_CHECK; ASSERT_TRUE(PORT_IsCollisionDetected(test_bd_addr)); rfc_cb.port.rfc_mcb[0].state = RFC_MX_STATE_DISC_WAIT_UA; ASSERT_FALSE(PORT_IsCollisionDetected(test_bd_addr)); diff --git a/system/stack/test/stack_smp_test.cc b/system/stack/test/stack_smp_test.cc index 41d61e738b..8f020ae1c5 100644 --- a/system/stack/test/stack_smp_test.cc +++ b/system/stack/test/stack_smp_test.cc @@ -375,11 +375,13 @@ TEST(SmpStatusText, smp_status_text) { std::make_pair(SMP_NUMERIC_COMPAR_FAIL, "SMP_NUMERIC_COMPAR_FAIL"), std::make_pair(SMP_BR_PARING_IN_PROGR, "SMP_BR_PARING_IN_PROGR"), std::make_pair(SMP_XTRANS_DERIVE_NOT_ALLOW, "SMP_XTRANS_DERIVE_NOT_ALLOW"), + std::make_pair(SMP_KEY_REJECTED, "SMP_KEY_REJECTED"), + std::make_pair(SMP_BUSY, "SMP_BUSY"), std::make_pair(SMP_MAX_FAIL_RSN_PER_SPEC, - "SMP_XTRANS_DERIVE_NOT_ALLOW"), // NOTE: Dup + "SMP_BUSY"), // NOTE: Dup std::make_pair(SMP_PAIR_INTERNAL_ERR, "SMP_PAIR_INTERNAL_ERR"), std::make_pair(SMP_UNKNOWN_IO_CAP, "SMP_UNKNOWN_IO_CAP"), - std::make_pair(SMP_BUSY, "SMP_BUSY"), + std::make_pair(SMP_IMPL_BUSY, "SMP_IMPL_BUSY"), std::make_pair(SMP_ENC_FAIL, "SMP_ENC_FAIL"), std::make_pair(SMP_STARTED, "SMP_STARTED"), std::make_pair(SMP_RSP_TIMEOUT, "SMP_RSP_TIMEOUT"), diff --git a/system/test/Android.bp b/system/test/Android.bp index c5a3b0c2d6..595f148754 100644 --- a/system/test/Android.bp +++ b/system/test/Android.bp @@ -205,7 +205,6 @@ filegroup { filegroup { name: "TestMockMainShim", srcs: [ - "mock/mock_main_shim.cc", "mock/mock_main_shim_BtifConfigInterface.cc", "mock/mock_main_shim_acl.cc", "mock/mock_main_shim_acl_api.cc", @@ -363,19 +362,11 @@ filegroup { filegroup { name: "TestMockMainShimLeScanning", srcs: [ - "mock/mock_main_shim.cc", "mock/mock_main_shim_le_scanning_manager.cc", ], } filegroup { - name: "TestMockMainShimFlags", - srcs: [ - "mock/mock_main_shim.cc", - ], -} - -filegroup { name: "TestMockBtif", srcs: [ ":TestCommonCoreInterface", diff --git a/system/test/headless/property.cc b/system/test/headless/property.cc index fe6e21f1ed..2b5df50ff2 100644 --- a/system/test/headless/property.cc +++ b/system/test/headless/property.cc @@ -99,6 +99,10 @@ std::map<::bt_property_type_t, return new headless::property::void_t(data, len, BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP); }}, + {BT_PROPERTY_UUIDS_LE, + [](const uint8_t* data, const size_t len) -> headless::bt_property_t* { + return new headless::property::uuid_t(data, len); + }}, }; } // namespace diff --git a/system/test/headless/property.h b/system/test/headless/property.h index 6af17a244f..1113495f24 100644 --- a/system/test/headless/property.h +++ b/system/test/headless/property.h @@ -52,6 +52,7 @@ inline std::string bt_property_type_text(const ::bt_property_type_t type) { CASE_RETURN_TEXT(BT_PROPERTY_REMOTE_MODEL_NUM); CASE_RETURN_TEXT(BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP); CASE_RETURN_TEXT(BT_PROPERTY_REMOTE_ADDR_TYPE); + CASE_RETURN_TEXT(BT_PROPERTY_UUIDS_LE); CASE_RETURN_TEXT(BT_PROPERTY_RESERVED_0x14); default: RETURN_UNKNOWN_TYPE_STRING(::bt_property_type_t, type); diff --git a/system/test/mock/mock_bta_dm_act.cc b/system/test/mock/mock_bta_dm_act.cc index 9fbf408f13..9f2686d4fd 100644 --- a/system/test/mock/mock_bta_dm_act.cc +++ b/system/test/mock/mock_bta_dm_act.cc @@ -77,7 +77,6 @@ struct bta_dm_on_encryption_change bta_dm_on_encryption_change; struct bta_dm_rm_cback bta_dm_rm_cback; struct bta_dm_set_dev_name bta_dm_set_dev_name; struct bta_dm_set_encryption bta_dm_set_encryption; -struct handle_remote_features_complete handle_remote_features_complete; } // namespace bta_dm_act } // namespace mock @@ -250,9 +249,5 @@ void bta_dm_set_encryption(const RawAddress& bd_addr, tBT_TRANSPORT transport, inc_func_call_count(__func__); test::mock::bta_dm_act::bta_dm_set_encryption(bd_addr, transport, p_callback, sec_act); } -void handle_remote_features_complete(const RawAddress& bd_addr) { - inc_func_call_count(__func__); - test::mock::bta_dm_act::handle_remote_features_complete(bd_addr); -} // END mockcify generation diff --git a/system/test/mock/mock_bta_dm_act.h b/system/test/mock/mock_bta_dm_act.h index 09fd1bbbc1..df329a53af 100644 --- a/system/test/mock/mock_bta_dm_act.h +++ b/system/test/mock/mock_bta_dm_act.h @@ -498,15 +498,6 @@ struct bta_dm_set_encryption { }; extern struct bta_dm_set_encryption bta_dm_set_encryption; -// Name: handle_remote_features_complete -// Params: const RawAddress& bd_addr -// Return: void -struct handle_remote_features_complete { - std::function<void(const RawAddress& bd_addr)> body{[](const RawAddress& /* bd_addr */) {}}; - void operator()(const RawAddress& bd_addr) { body(bd_addr); } -}; -extern struct handle_remote_features_complete handle_remote_features_complete; - } // namespace bta_dm_act } // namespace mock } // namespace test diff --git a/system/test/mock/mock_bta_gattc_api.cc b/system/test/mock/mock_bta_gattc_api.cc index 7bea0245ce..6932102e84 100644 --- a/system/test/mock/mock_bta_gattc_api.cc +++ b/system/test/mock/mock_bta_gattc_api.cc @@ -69,8 +69,8 @@ tGATT_STATUS BTA_GATTC_RegisterForNotifications(tGATT_IF /* client_if */, return GATT_SUCCESS; } void BTA_GATTC_AppDeregister(tGATT_IF /* client_if */) { inc_func_call_count(__func__); } -void BTA_GATTC_AppRegister(tBTA_GATTC_CBACK* /* p_client_cb */, BtaAppRegisterCallback /* cb */, - bool /* eatt_support */) { +void BTA_GATTC_AppRegister(const std::string& /* name */, tBTA_GATTC_CBACK* /* p_client_cb */, + BtaAppRegisterCallback /* cb */, bool /* eatt_support */) { inc_func_call_count(__func__); } void BTA_GATTC_CancelOpen(tGATT_IF /* client_if */, const RawAddress& /* remote_bda */, diff --git a/system/test/mock/mock_bta_jv_api.cc b/system/test/mock/mock_bta_jv_api.cc index ec913b5b01..52f8ffcabe 100644 --- a/system/test/mock/mock_bta_jv_api.cc +++ b/system/test/mock/mock_bta_jv_api.cc @@ -76,14 +76,16 @@ tBTA_JV_STATUS BTA_JvRfcommClose(uint32_t /* handle */, uint32_t /* rfcomm_slot_ tBTA_JV_STATUS BTA_JvRfcommConnect(tBTA_SEC /* sec_mask */, uint8_t /* remote_scn */, const RawAddress& /* peer_bd_addr */, tBTA_JV_RFCOMM_CBACK* /* p_cback */, - uint32_t /* rfcomm_slot_id */, RfcommCfgInfo /* cfg */) { + uint32_t /* rfcomm_slot_id */, RfcommCfgInfo /* cfg */, + uint32_t /* app_uid */) { inc_func_call_count(__func__); return tBTA_JV_STATUS::SUCCESS; } tBTA_JV_STATUS BTA_JvRfcommStartServer(tBTA_SEC /* sec_mask */, uint8_t /* local_scn */, uint8_t /* max_session */, tBTA_JV_RFCOMM_CBACK* /* p_cback */, - uint32_t /* rfcomm_slot_id */, RfcommCfgInfo /* cfg */) { + uint32_t /* rfcomm_slot_id */, RfcommCfgInfo /* cfg */, + uint32_t /* app_uid */) { inc_func_call_count(__func__); return tBTA_JV_STATUS::SUCCESS; } diff --git a/system/test/mock/mock_bta_leaudio.cc b/system/test/mock/mock_bta_leaudio.cc index 2b5aeb8ff9..24824032a3 100644 --- a/system/test/mock/mock_bta_leaudio.cc +++ b/system/test/mock/mock_bta_leaudio.cc @@ -41,14 +41,12 @@ class HalVersionManager { } // namespace audio } // namespace bluetooth -void LeAudioClient::AddFromStorage(const RawAddress& /* addr */, bool /* autoconnect */, - int /* sink_audio_location */, int /* source_audio_location */, - int /* sink_supported_context_types */, - int /* source_supported_context_types */, - const std::vector<uint8_t>& /* handles */, - const std::vector<uint8_t>& /* sink_pacs */, - const std::vector<uint8_t>& /* source_pacs */, - const std::vector<uint8_t>& /* ases */) { +void LeAudioClient::AddFromStorage( + const RawAddress& /* addr */, bool /* autoconnect */, int /* sink_audio_location */, + int /* source_audio_location */, int /* sink_supported_context_types */, + int /* source_supported_context_types */, const std::vector<uint8_t>& /* handles */, + const std::vector<uint8_t>& /* sink_pacs */, const std::vector<uint8_t>& /* source_pacs */, + const std::vector<uint8_t>& /* ases */, const std::vector<uint8_t>& /* gmap */) { inc_func_call_count(__func__); } @@ -76,6 +74,12 @@ bool LeAudioClient::GetAsesForStorage(const RawAddress& /* addr */, return false; } +bool LeAudioClient::GetGmapForStorage(const RawAddress& /* addr */, + std::vector<uint8_t>& /* out */) { + inc_func_call_count(__func__); + return false; +} + void LeAudioClient::Cleanup(void) { inc_func_call_count(__func__); } LeAudioClient* LeAudioClient::Get(void) { diff --git a/system/test/mock/mock_btif_profile_storage.cc b/system/test/mock/mock_btif_profile_storage.cc index a89422a7ee..a65103fa8e 100644 --- a/system/test/mock/mock_btif_profile_storage.cc +++ b/system/test/mock/mock_btif_profile_storage.cc @@ -55,6 +55,7 @@ struct btif_storage_leaudio_clear_service_data btif_storage_leaudio_clear_servic struct btif_storage_leaudio_update_ase_bin btif_storage_leaudio_update_ase_bin; struct btif_storage_leaudio_update_handles_bin btif_storage_leaudio_update_handles_bin; struct btif_storage_leaudio_update_pacs_bin btif_storage_leaudio_update_pacs_bin; +struct btif_storage_leaudio_update_gmap_bin btif_storage_leaudio_update_gmap_bin; struct btif_storage_load_bonded_csis_devices btif_storage_load_bonded_csis_devices; struct btif_storage_load_bonded_groups btif_storage_load_bonded_groups; struct btif_storage_load_bonded_hearing_aids btif_storage_load_bonded_hearing_aids; @@ -183,6 +184,10 @@ void btif_storage_leaudio_update_pacs_bin(const RawAddress& addr) { inc_func_call_count(__func__); test::mock::btif_profile_storage::btif_storage_leaudio_update_pacs_bin(addr); } +void btif_storage_leaudio_update_gmap_bin(const RawAddress& addr) { + inc_func_call_count(__func__); + test::mock::btif_profile_storage::btif_storage_leaudio_update_gmap_bin(addr); +} void btif_storage_load_bonded_csis_devices(void) { inc_func_call_count(__func__); test::mock::btif_profile_storage::btif_storage_load_bonded_csis_devices(); diff --git a/system/test/mock/mock_btif_profile_storage.h b/system/test/mock/mock_btif_profile_storage.h index 7d322e543d..1d0bbd7c96 100644 --- a/system/test/mock/mock_btif_profile_storage.h +++ b/system/test/mock/mock_btif_profile_storage.h @@ -226,6 +226,15 @@ struct btif_storage_leaudio_update_pacs_bin { }; extern struct btif_storage_leaudio_update_pacs_bin btif_storage_leaudio_update_pacs_bin; +// Name: btif_storage_leaudio_update_gmap_bin +// Params: void +// Return: void +struct btif_storage_leaudio_update_gmap_bin { + std::function<void(const RawAddress& addr)> body{[](const RawAddress& /* addr */) {}}; + void operator()(const RawAddress& addr) { body(addr); } +}; +extern struct btif_storage_leaudio_update_gmap_bin btif_storage_leaudio_update_gmap_bin; + // Name: btif_storage_load_bonded_csis_devices // Params: void // Return: void diff --git a/system/test/mock/mock_main_shim.cc b/system/test/mock/mock_main_shim.cc deleted file mode 100644 index 8e26aaeecd..0000000000 --- a/system/test/mock/mock_main_shim.cc +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2021 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. - */ - -/* - * Generated mock file from original source file - * Functions generated:14 - */ - -#define LOG_TAG "bt_shim" - -#include "main/shim/shim.h" -#include "test/common/mock_functions.h" - -namespace test { -namespace mock { -bool bluetooth_shim_is_gd_stack_started_up = false; -} -} // namespace test -bool bluetooth::shim::is_gd_stack_started_up() { - inc_func_call_count(__func__); - return test::mock::bluetooth_shim_is_gd_stack_started_up; -} diff --git a/system/test/mock/mock_main_shim_entry.cc b/system/test/mock/mock_main_shim_entry.cc index 291cb0c0fd..a78ff5bdf5 100644 --- a/system/test/mock/mock_main_shim_entry.cc +++ b/system/test/mock/mock_main_shim_entry.cc @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "test/mock/mock_main_shim_entry.h" + #include "hci/acl_manager_mock.h" #include "hci/controller_interface_mock.h" #include "hci/distance_measurement_manager_mock.h" @@ -22,9 +24,16 @@ #include "hci/le_scanning_manager_mock.h" #include "lpp/lpp_offload_interface_mock.h" #include "main/shim/entry.h" +#include "main/shim/shim.h" #include "os/handler.h" #include "storage/storage_module.h" +namespace test { +namespace mock { +bool bluetooth_shim_is_gd_stack_started_up = false; +} // namespace mock +} // namespace test + namespace bluetooth { namespace hci { namespace testing { @@ -65,6 +74,7 @@ hci::RemoteNameRequestModule* GetRemoteNameRequest() { return nullptr; } lpp::LppOffloadInterface* GetLppOffloadManager() { return lpp::testing::mock_lpp_offload_interface_; } +bool is_gd_stack_started_up() { return test::mock::bluetooth_shim_is_gd_stack_started_up; } } // namespace shim } // namespace bluetooth diff --git a/system/test/mock/mock_stack_btm_interface.cc b/system/test/mock/mock_stack_btm_interface.cc index 116d2008af..2361ce1307 100644 --- a/system/test/mock/mock_stack_btm_interface.cc +++ b/system/test/mock/mock_stack_btm_interface.cc @@ -137,7 +137,7 @@ struct btm_client_interface_t default_btm_client_interface = { }, .BTM_IsEncrypted = [](const RawAddress& /* bd_addr */, tBT_TRANSPORT /* transport */) -> bool { return false; }, - .BTM_SecIsSecurityPending = [](const RawAddress& /* bd_addr */) -> bool { + .BTM_SecIsLeSecurityPending = [](const RawAddress& /* bd_addr */) -> bool { return false; }, .BTM_IsLinkKeyKnown = [](const RawAddress& /* bd_addr */, diff --git a/system/test/mock/mock_stack_btm_sec.cc b/system/test/mock/mock_stack_btm_sec.cc index 2bfd288187..cdf4b97df6 100644 --- a/system/test/mock/mock_stack_btm_sec.cc +++ b/system/test/mock/mock_stack_btm_sec.cc @@ -58,7 +58,7 @@ struct BTM_SecBondCancel BTM_SecBondCancel; struct BTM_SecClrService BTM_SecClrService; struct BTM_SecClrServiceByPsm BTM_SecClrServiceByPsm; struct BTM_SecGetDeviceLinkKeyType BTM_SecGetDeviceLinkKeyType; -struct BTM_SecIsSecurityPending BTM_SecIsSecurityPending; +struct BTM_SecIsLeSecurityPending BTM_SecIsLeSecurityPending; struct BTM_SecRegister BTM_SecRegister; struct BTM_SetEncryption BTM_SetEncryption; struct BTM_SetPinType BTM_SetPinType; @@ -121,7 +121,7 @@ tBTM_STATUS BTM_SecBondCancel::return_value = tBTM_STATUS::BTM_SUCCESS; uint8_t BTM_SecClrService::return_value = 0; uint8_t BTM_SecClrServiceByPsm::return_value = 0; tBTM_LINK_KEY_TYPE BTM_SecGetDeviceLinkKeyType::return_value = 0; -bool BTM_SecIsSecurityPending::return_value = false; +bool BTM_SecIsLeSecurityPending::return_value = false; bool BTM_SecRegister::return_value = false; tBTM_STATUS BTM_SetEncryption::return_value = tBTM_STATUS::BTM_SUCCESS; bool BTM_SetSecurityLevel::return_value = false; @@ -204,9 +204,9 @@ tBTM_LINK_KEY_TYPE BTM_SecGetDeviceLinkKeyType(const RawAddress& bd_addr) { inc_func_call_count(__func__); return test::mock::stack_btm_sec::BTM_SecGetDeviceLinkKeyType(bd_addr); } -bool BTM_SecIsSecurityPending(const RawAddress& bd_addr) { +bool BTM_SecIsLeSecurityPending(const RawAddress& bd_addr) { inc_func_call_count(__func__); - return test::mock::stack_btm_sec::BTM_SecIsSecurityPending(bd_addr); + return test::mock::stack_btm_sec::BTM_SecIsLeSecurityPending(bd_addr); } bool BTM_SecRegister(const tBTM_APPL_INFO* p_cb_info) { inc_func_call_count(__func__); diff --git a/system/test/mock/mock_stack_btm_sec.h b/system/test/mock/mock_stack_btm_sec.h index 8f716f443b..c40e57c593 100644 --- a/system/test/mock/mock_stack_btm_sec.h +++ b/system/test/mock/mock_stack_btm_sec.h @@ -246,16 +246,16 @@ struct BTM_SecGetDeviceLinkKeyType { }; extern struct BTM_SecGetDeviceLinkKeyType BTM_SecGetDeviceLinkKeyType; -// Name: BTM_SecIsSecurityPending +// Name: BTM_SecIsLeSecurityPending // Params: const RawAddress& bd_addr // Return: bool -struct BTM_SecIsSecurityPending { +struct BTM_SecIsLeSecurityPending { static bool return_value; std::function<bool(const RawAddress& bd_addr)> body{ [](const RawAddress& /* bd_addr */) { return return_value; }}; bool operator()(const RawAddress& bd_addr) { return body(bd_addr); } }; -extern struct BTM_SecIsSecurityPending BTM_SecIsSecurityPending; +extern struct BTM_SecIsLeSecurityPending BTM_SecIsLeSecurityPending; // Name: BTM_SecRegister // Params: const tBTM_APPL_INFO* p_cb_info diff --git a/system/test/mock/mock_stack_gatt_main.cc b/system/test/mock/mock_stack_gatt_main.cc index 2c09b4a768..ead90a71d1 100644 --- a/system/test/mock/mock_stack_gatt_main.cc +++ b/system/test/mock/mock_stack_gatt_main.cc @@ -33,9 +33,6 @@ bool gatt_act_connect(tGATT_REG* /* p_reg */, const RawAddress& /* bd_addr */, inc_func_call_count(__func__); return false; } -void gatt_cancel_connect(const RawAddress& /* bd_addr */, tBT_TRANSPORT /* transport*/) { - inc_func_call_count(__func__); -} bool gatt_disconnect(tGATT_TCB* /* p_tcb */) { inc_func_call_count(__func__); return false; diff --git a/system/test/mock/mock_stack_rfcomm_port_api.cc b/system/test/mock/mock_stack_rfcomm_port_api.cc index b87a752689..388f0cbdfc 100644 --- a/system/test/mock/mock_stack_rfcomm_port_api.cc +++ b/system/test/mock/mock_stack_rfcomm_port_api.cc @@ -115,3 +115,7 @@ bool PORT_IsCollisionDetected(RawAddress /* bd_addr */) { inc_func_call_count(__func__); return false; } +int PORT_SetAppUid(uint16_t /* handle */, uint32_t /* app_uid */) { + inc_func_call_count(__func__); + return 0; +} diff --git a/system/test/mock/mock_stack_security_client_interface.h b/system/test/mock/mock_stack_security_client_interface.h index 2e61c7b56b..224d880905 100644 --- a/system/test/mock/mock_stack_security_client_interface.h +++ b/system/test/mock/mock_stack_security_client_interface.h @@ -43,7 +43,7 @@ struct MockSecurityClientInterface : public SecurityClientInterface { tBTM_BLE_SEC_ACT /* sec_act */)); MOCK_METHOD((bool), BTM_IsEncrypted, (const RawAddress& /* bd_addr */, tBT_TRANSPORT /* transport */)); - MOCK_METHOD((bool), BTM_SecIsSecurityPending, (const RawAddress& /* bd_addr */)); + MOCK_METHOD((bool), BTM_SecIsLeSecurityPending, (const RawAddress& /* bd_addr */)); MOCK_METHOD((bool), BTM_IsLinkKeyKnown, (const RawAddress& /* bd_addr */, tBT_TRANSPORT /* transport */)); MOCK_METHOD((bool), BTM_SetSecurityLevel, diff --git a/system/test/suite/gatt/gatt_unittest.cc b/system/test/suite/gatt/gatt_unittest.cc index aed80c1f10..62631e8730 100644 --- a/system/test/suite/gatt/gatt_unittest.cc +++ b/system/test/suite/gatt/gatt_unittest.cc @@ -30,7 +30,7 @@ TEST_F(GattTest, GattClientRegister) { // Registers gatt client. bluetooth::Uuid gatt_client_uuid = bluetooth::Uuid::From128BitBE( bluetooth::os::GenerateRandom<bluetooth::Uuid::kNumBytes128>()); - gatt_client_interface()->register_client(gatt_client_uuid, false); + gatt_client_interface()->register_client(gatt_client_uuid, "test", false); semaphore_wait(register_client_callback_sem_); EXPECT_TRUE(status() == BT_STATUS_SUCCESS) << "Error registering GATT client app callback."; diff --git a/system/types/Android.bp b/system/types/Android.bp index 00a0de546d..e00941d8ec 100644 --- a/system/types/Android.bp +++ b/system/types/Android.bp @@ -15,7 +15,7 @@ cc_library_headers { host_supported: true, apex_available: [ "//apex_available:platform", - "com.android.btservices", + "com.android.bt", "com.android.media", "com.android.media.swcodec", ], @@ -45,7 +45,7 @@ cc_library_static { export_header_lib_headers: ["libbluetooth-types-header"], apex_available: [ "//apex_available:platform", - "com.android.btservices", + "com.android.bt", ], min_sdk_version: "29", } diff --git a/system/udrv/Android.bp b/system/udrv/Android.bp index 5a69d8e886..fb77f0f0f1 100644 --- a/system/udrv/Android.bp +++ b/system/udrv/Android.bp @@ -24,7 +24,7 @@ cc_library_static { host_supported: true, apex_available: [ "//apex_available:platform", - "com.android.btservices", + "com.android.bt", ], min_sdk_version: "Tiramisu", header_libs: ["libbluetooth_headers"], diff --git a/tools/rootcanal/hal/bluetooth_hci.cc b/tools/rootcanal/hal/bluetooth_hci.cc index b08d59995b..b36c18687d 100644 --- a/tools/rootcanal/hal/bluetooth_hci.cc +++ b/tools/rootcanal/hal/bluetooth_hci.cc @@ -53,24 +53,6 @@ bool BtTestConsoleEnabled() { } // namespace -class BluetoothDeathRecipient : public hidl_death_recipient { -public: - BluetoothDeathRecipient(const sp<IBluetoothHci> hci) : mHci(hci) {} - - void serviceDied(uint64_t /* cookie */, - const wp<::android::hidl::base::V1_0::IBase>& /* who */) override { - ALOGE("BluetoothDeathRecipient::serviceDied - Bluetooth service died"); - has_died_ = true; - mHci->close(); - } - sp<IBluetoothHci> mHci; - bool getHasDied() const { return has_died_; } - void setHasDied(bool has_died) { has_died_ = has_died; } - -private: - bool has_died_{false}; -}; - BluetoothHci::BluetoothHci() : death_recipient_(new BluetoothDeathRecipient(this)) {} Return<void> BluetoothHci::initialize(const sp<V1_0::IBluetoothHciCallbacks>& cb) { diff --git a/tools/rootcanal/hal/bluetooth_hci.h b/tools/rootcanal/hal/bluetooth_hci.h index 95bda38957..95e3e45334 100644 --- a/tools/rootcanal/hal/bluetooth_hci.h +++ b/tools/rootcanal/hal/bluetooth_hci.h @@ -18,6 +18,7 @@ #include <android/hardware/bluetooth/1.1/IBluetoothHci.h> #include <hidl/MQDescriptor.h> +#include <log/log.h> #include "hci_packetizer.h" #include "model/controller/dual_mode_controller.h" @@ -44,6 +45,25 @@ using android::net::ConnectCallback; using rootcanal::Device; using rootcanal::Phy; +class BluetoothDeathRecipient : public hidl_death_recipient { +public: + BluetoothDeathRecipient(const sp<IBluetoothHci> hci) : mHci(hci) {} + + void serviceDied(uint64_t /* cookie */, + const wp<::android::hidl::base::V1_0::IBase>& /* who */) override { + ALOGE("BluetoothDeathRecipient::serviceDied - Bluetooth service died"); + has_died_ = true; + mHci->close(); + } + + sp<IBluetoothHci> mHci; + bool getHasDied() const { return has_died_; } + void setHasDied(bool has_died) { has_died_ = has_died; } + +private: + bool has_died_{false}; +}; + class BluetoothHci : public IBluetoothHci { public: BluetoothHci(); diff --git a/tools/rootcanal/rust/src/lmp/procedure/mod.rs b/tools/rootcanal/rust/src/lmp/procedure/mod.rs index 5f93e62da9..d73c5d39e9 100644 --- a/tools/rootcanal/rust/src/lmp/procedure/mod.rs +++ b/tools/rootcanal/rust/src/lmp/procedure/mod.rs @@ -66,7 +66,7 @@ pub trait Context { /// Future for Context::receive_hci_command and Context::receive_lmp_packet pub struct ReceiveFuture<'a, C: ?Sized, P>(fn(&'a C) -> Poll<P>, &'a C); -impl<'a, C, O> Future for ReceiveFuture<'a, C, O> +impl<C, O> Future for ReceiveFuture<'_, C, O> where C: Context, { @@ -80,7 +80,7 @@ where /// Future for Context::receive_hci_command and Context::receive_lmp_packet pub struct SendAcceptedLmpPacketFuture<'a, C: ?Sized>(&'a C, lmp::Opcode); -impl<'a, C> Future for SendAcceptedLmpPacketFuture<'a, C> +impl<C> Future for SendAcceptedLmpPacketFuture<'_, C> where C: Context, { |