diff --git a/core/api/current.txt b/core/api/current.txt index 8c313a25ccec..32adeefec2b9 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9520,6 +9520,20 @@ package android.bluetooth { field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED"; } + public final class BluetoothLeAudioCodecConfig { + method @NonNull public String getCodecName(); + method public int getCodecType(); + method public static int getMaxCodecType(); + field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240 + field public static final int SOURCE_CODEC_TYPE_LC3 = 0; // 0x0 + } + + public static final class BluetoothLeAudioCodecConfig.Builder { + ctor public BluetoothLeAudioCodecConfig.Builder(); + method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig build(); + method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setCodecType(int); + } + public final class BluetoothManager { method public android.bluetooth.BluetoothAdapter getAdapter(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List getConnectedDevices(int); diff --git a/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java b/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java new file mode 100644 index 000000000000..dcaf4b682f44 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 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. + */ + +package android.bluetooth; + +import android.annotation.IntDef; +import android.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Represents the codec configuration for a Bluetooth LE Audio source device. + *

Contains the source codec type. + *

The source codec type values are the same as those supported by the + * device hardware. + * + * {@see BluetoothLeAudioCodecConfig} + */ +public final class BluetoothLeAudioCodecConfig { + // Add an entry for each source codec here. + + /** @hide */ + @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = { + SOURCE_CODEC_TYPE_LC3, + SOURCE_CODEC_TYPE_INVALID + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SourceCodecType {}; + + public static final int SOURCE_CODEC_TYPE_LC3 = 0; + public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000; + + /** + * Represents the count of valid source codec types. Can be accessed via + * {@link #getMaxCodecType}. + */ + private static final int SOURCE_CODEC_TYPE_MAX = 1; + + private final @SourceCodecType int mCodecType; + + /** + * Creates a new BluetoothLeAudioCodecConfig. + * + * @param codecType the source codec type + */ + private BluetoothLeAudioCodecConfig(@SourceCodecType int codecType) { + mCodecType = codecType; + } + + @Override + public String toString() { + return "{codecName:" + getCodecName() + "}"; + } + + /** + * Gets the codec type. + * + * @return the codec type + */ + public @SourceCodecType int getCodecType() { + return mCodecType; + } + + /** + * Returns the valid codec types count. + */ + public static int getMaxCodecType() { + return SOURCE_CODEC_TYPE_MAX; + } + + /** + * Gets the codec name. + * + * @return the codec name + */ + public @NonNull String getCodecName() { + switch (mCodecType) { + case SOURCE_CODEC_TYPE_LC3: + return "LC3"; + case SOURCE_CODEC_TYPE_INVALID: + return "INVALID CODEC"; + default: + break; + } + return "UNKNOWN CODEC(" + mCodecType + ")"; + } + + /** + * Builder for {@link BluetoothLeAudioCodecConfig}. + *

By default, the codec type will be set to + * {@link BluetoothLeAudioCodecConfig#SOURCE_CODEC_TYPE_INVALID} + */ + public static final class Builder { + private int mCodecType = BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID; + + /** + * Set codec type for Bluetooth codec config. + * + * @param codecType of this codec + * @return the same Builder instance + */ + public @NonNull Builder setCodecType(@SourceCodecType int codecType) { + mCodecType = codecType; + return this; + } + + /** + * Build {@link BluetoothLeAudioCodecConfig}. + * @return new BluetoothLeAudioCodecConfig built + */ + public @NonNull BluetoothLeAudioCodecConfig build() { + return new BluetoothLeAudioCodecConfig(mCodecType); + } + } +} diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 4b93363b5b90..c847e4d7654f 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2273,10 +2273,8 @@ android_media_AudioSystem_getMicrophones(JNIEnv *env, jobject thiz, jobject jMic return jStatus; } -static jint -android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP( - JNIEnv *env, jobject thiz, jobject jEncodingFormatList) -{ +static jint android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia( + JNIEnv *env, jobject thiz, jint deviceType, jobject jEncodingFormatList) { ALOGV("%s", __FUNCTION__); jint jStatus = AUDIO_JAVA_SUCCESS; if (!env->IsInstanceOf(jEncodingFormatList, gArrayListClass)) { @@ -2284,8 +2282,10 @@ android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP( return (jint)AUDIO_JAVA_BAD_VALUE; } std::vector encodingFormats; - status_t status = AudioSystem::getHwOffloadEncodingFormatsSupportedForA2DP( - &encodingFormats); + status_t status = + AudioSystem::getHwOffloadFormatsSupportedForBluetoothMedia(static_cast( + deviceType), + &encodingFormats); if (status != NO_ERROR) { ALOGE("%s: error %d", __FUNCTION__, status); jStatus = nativeToJavaStatus(status); @@ -2810,8 +2810,8 @@ static const JNINativeMethod gMethods[] = {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids}, {"isHapticPlaybackSupported", "()Z", (void *)android_media_AudioSystem_isHapticPlaybackSupported}, - {"getHwOffloadEncodingFormatsSupportedForA2DP", "(Ljava/util/ArrayList;)I", - (void *)android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP}, + {"getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I", + (void *)android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia}, {"setSupportedSystemUsages", "([I)I", (void *)android_media_AudioSystem_setSupportedSystemUsages}, {"setAllowedCapturePolicy", "(II)I", diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java new file mode 100644 index 000000000000..6471492c9663 --- /dev/null +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 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. + */ + +package android.bluetooth; + +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.TestCase; + +/** + * Unit test cases for {@link BluetoothLeAudioCodecConfig}. + */ +public class BluetoothLeAudioCodecConfigTest extends TestCase { + private int[] mCodecTypeArray = new int[] { + BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3, + BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID, + }; + + @SmallTest + public void testBluetoothLeAudioCodecConfig_valid_get_methods() { + + for (int codecIdx = 0; codecIdx < mCodecTypeArray.length; codecIdx++) { + int codecType = mCodecTypeArray[codecIdx]; + + BluetoothLeAudioCodecConfig leAudioCodecConfig = + buildBluetoothLeAudioCodecConfig(codecType); + + if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) { + assertEquals("LC3", leAudioCodecConfig.getCodecName()); + } + if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) { + assertEquals("INVALID CODEC", leAudioCodecConfig.getCodecName()); + } + + assertEquals(1, leAudioCodecConfig.getMaxCodecType()); + assertEquals(codecType, leAudioCodecConfig.getCodecType()); + } + } + + private BluetoothLeAudioCodecConfig buildBluetoothLeAudioCodecConfig(int sourceCodecType) { + return new BluetoothLeAudioCodecConfig.Builder() + .setCodecType(sourceCodecType) + .build(); + + } +} diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index c7f56961e498..60f4a5a226db 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -32,6 +32,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothCodecConfig; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeAudioCodecConfig; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -6790,30 +6791,56 @@ public class AudioManager { /** * Returns a list of audio formats that corresponds to encoding formats - * supported on offload path for A2DP playback. + * supported on offload path for A2DP and LE audio playback. * + * @param deviceType Indicates the target device type {@link AudioSystem.DeviceType} * @return a list of {@link BluetoothCodecConfig} objects containing encoding formats - * supported for offload A2DP playback + * supported for offload A2DP playback or a list of {@link BluetoothLeAudioCodecConfig} + * objects containing encoding formats supported for offload LE Audio playback * @hide */ - public List getHwOffloadEncodingFormatsSupportedForA2DP() { + public List getHwOffloadFormatsSupportedForBluetoothMedia( + @AudioSystem.DeviceType int deviceType) { ArrayList formatsList = new ArrayList(); - ArrayList codecConfigList = new ArrayList(); + ArrayList a2dpCodecConfigList = new ArrayList(); + ArrayList leAudioCodecConfigList = + new ArrayList(); - int status = AudioSystem.getHwOffloadEncodingFormatsSupportedForA2DP(formatsList); + if (deviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP + && deviceType != AudioSystem.DEVICE_OUT_BLE_HEADSET) { + throw new IllegalArgumentException( + "Illegal devicetype for the getHwOffloadFormatsSupportedForBluetoothMedia"); + } + + int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia(deviceType, + formatsList); if (status != AudioManager.SUCCESS) { - Log.e(TAG, "getHwOffloadEncodingFormatsSupportedForA2DP failed:" + status); - return codecConfigList; + Log.e(TAG, "getHwOffloadFormatsSupportedForBluetoothMedia for deviceType " + + deviceType + " failed:" + status); + return a2dpCodecConfigList; } - for (Integer format : formatsList) { - int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format); - if (btSourceCodec - != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) { - codecConfigList.add(new BluetoothCodecConfig(btSourceCodec)); + if (deviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { + for (Integer format : formatsList) { + int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format); + if (btSourceCodec != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) { + a2dpCodecConfigList.add(new BluetoothCodecConfig(btSourceCodec)); + } } + return a2dpCodecConfigList; + } else if (deviceType == AudioSystem.DEVICE_OUT_BLE_HEADSET) { + for (Integer format : formatsList) { + int btLeAudioCodec = AudioSystem.audioFormatToBluetoothLeAudioSourceCodec(format); + if (btLeAudioCodec != BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) { + leAudioCodecConfigList.add(new BluetoothLeAudioCodecConfig.Builder() + .setCodecType(btLeAudioCodec) + .build()); + } + } + return leAudioCodecConfigList; } - return codecConfigList; + Log.e(TAG, "Input deviceType " + deviceType + " doesn't support."); + return a2dpCodecConfigList; } // Since we need to calculate the changes since THE LAST NOTIFICATION, and not since the diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 69d1889d5762..f0e42c0550ff 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothLeAudioCodecConfig; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; @@ -229,6 +230,9 @@ public class AudioSystem public static final int AUDIO_FORMAT_APTX_HD = 0x21000000; /** @hide */ public static final int AUDIO_FORMAT_LDAC = 0x23000000; + /** @hide */ + public static final int AUDIO_FORMAT_LC3 = 0x2B000000; + /** @hide */ @IntDef(flag = false, prefix = "AUDIO_FORMAT_", value = { @@ -238,11 +242,26 @@ public class AudioSystem AUDIO_FORMAT_SBC, AUDIO_FORMAT_APTX, AUDIO_FORMAT_APTX_HD, - AUDIO_FORMAT_LDAC } + AUDIO_FORMAT_LDAC} ) @Retention(RetentionPolicy.SOURCE) public @interface AudioFormatNativeEnumForBtCodec {} + /** @hide */ + @IntDef(flag = false, prefix = "AUDIO_FORMAT_", value = { + AUDIO_FORMAT_LC3} + ) + @Retention(RetentionPolicy.SOURCE) + public @interface AudioFormatNativeEnumForBtLeAudioCodec {} + + /** @hide */ + @IntDef(flag = false, prefix = "DEVICE_", value = { + DEVICE_OUT_BLUETOOTH_A2DP, + DEVICE_OUT_BLE_HEADSET} + ) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceType {} + /** * @hide * Convert audio format enum values to Bluetooth codec values @@ -262,6 +281,21 @@ public class AudioSystem } } + /** + * @hide + * Convert audio format enum values to Bluetooth LE audio codec values + */ + public static int audioFormatToBluetoothLeAudioSourceCodec( + @AudioFormatNativeEnumForBtLeAudioCodec int audioFormat) { + switch (audioFormat) { + case AUDIO_FORMAT_LC3: return BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3; + default: + Log.e(TAG, "Unknown audio format 0x" + Integer.toHexString(audioFormat) + + " for conversion to BT LE audio codec"); + return BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID; + } + } + /** * @hide * Convert a Bluetooth codec to an audio format enum @@ -1754,10 +1788,10 @@ public class AudioSystem /** * @hide - * Returns a list of audio formats (codec) supported on the A2DP offload path. + * Returns a list of audio formats (codec) supported on the A2DP and LE audio offload path. */ - public static native int getHwOffloadEncodingFormatsSupportedForA2DP( - ArrayList formatList); + public static native int getHwOffloadFormatsSupportedForBluetoothMedia( + @DeviceType int deviceType, ArrayList formatList); /** @hide */ public static native int setSurroundFormatEnabled(int audioFormat, boolean enabled);