/* * Copyright 2018 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.a2dp; import android.bluetooth.BluetoothCodecConfig; import android.bluetooth.BluetoothCodecConfig.CodecPriority; import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.media.AudioManager; import android.util.Log; import com.android.bluetooth.R; import java.util.Arrays; import java.util.Objects; /* * A2DP Codec Configuration setup. */ class A2dpCodecConfig { private static final boolean DBG = true; private static final String TAG = "A2dpCodecConfig"; private Context mContext; private A2dpNativeInterface mA2dpNativeInterface; private BluetoothCodecConfig[] mCodecConfigPriorities; private @CodecPriority int mA2dpSourceCodecPrioritySbc = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; private @CodecPriority int mA2dpSourceCodecPriorityAac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; private @CodecPriority int mA2dpSourceCodecPriorityAptx = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; private @CodecPriority int mA2dpSourceCodecPriorityAptxHd = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; private @CodecPriority int mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; private BluetoothCodecConfig[] mCodecConfigOffloading = new BluetoothCodecConfig[0]; A2dpCodecConfig(Context context, A2dpNativeInterface a2dpNativeInterface) { mContext = context; mA2dpNativeInterface = a2dpNativeInterface; mCodecConfigPriorities = assignCodecConfigPriorities(); AudioManager audioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE); if (audioManager == null) { Log.w(TAG, "Can't obtain the codec offloading prefernece from null AudioManager"); return; } mCodecConfigOffloading = audioManager.getHwOffloadEncodingFormatsSupportedForA2DP() .toArray(mCodecConfigOffloading); } BluetoothCodecConfig[] codecConfigPriorities() { return mCodecConfigPriorities; } BluetoothCodecConfig[] codecConfigOffloading() { return mCodecConfigOffloading; } void setCodecConfigPreference(BluetoothDevice device, BluetoothCodecStatus codecStatus, BluetoothCodecConfig newCodecConfig) { Objects.requireNonNull(codecStatus); // Check whether the codecConfig is selectable for this Bluetooth device. BluetoothCodecConfig[] selectableCodecs = codecStatus.getCodecsSelectableCapabilities(); if (!Arrays.asList(selectableCodecs).stream().anyMatch(codec -> codec.isMandatoryCodec())) { // Do not set codec preference to native if the selectableCodecs not contain mandatory // codec. The reason could be remote codec negotiation is not completed yet. Log.w(TAG, "setCodecConfigPreference: must have mandatory codec before changing."); return; } if (!codecStatus.isCodecConfigSelectable(newCodecConfig)) { Log.w(TAG, "setCodecConfigPreference: invalid codec " + Objects.toString(newCodecConfig)); return; } // Check whether the codecConfig would change current codec config. int prioritizedCodecType = getPrioitizedCodecType(newCodecConfig, selectableCodecs); BluetoothCodecConfig currentCodecConfig = codecStatus.getCodecConfig(); if (prioritizedCodecType == currentCodecConfig.getCodecType() && (prioritizedCodecType != newCodecConfig.getCodecType() || (currentCodecConfig.similarCodecFeedingParameters(newCodecConfig) && currentCodecConfig.sameCodecSpecificParameters(newCodecConfig)))) { // Same codec with same parameters, no need to send this request to native. Log.w(TAG, "setCodecConfigPreference: codec not changed."); return; } BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1]; codecConfigArray[0] = newCodecConfig; mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray); } void enableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) { if (currentCodecConfig != null && !currentCodecConfig.isMandatoryCodec()) { Log.i(TAG, "enableOptionalCodecs: already using optional codec " + currentCodecConfig.getCodecName()); return; } BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities(); if (codecConfigArray == null) { return; } // Set the mandatory codec's priority to default, and remove the rest for (int i = 0; i < codecConfigArray.length; i++) { BluetoothCodecConfig codecConfig = codecConfigArray[i]; if (!codecConfig.isMandatoryCodec()) { codecConfigArray[i] = null; } } mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray); } void disableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) { if (currentCodecConfig != null && currentCodecConfig.isMandatoryCodec()) { Log.i(TAG, "disableOptionalCodecs: already using mandatory codec."); return; } BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities(); if (codecConfigArray == null) { return; } // Set the mandatory codec's priority to highest, and remove the rest for (int i = 0; i < codecConfigArray.length; i++) { BluetoothCodecConfig codecConfig = codecConfigArray[i]; if (codecConfig.isMandatoryCodec()) { codecConfig.setCodecPriority(BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST); } else { codecConfigArray[i] = null; } } mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray); } // Get the codec type of the highest priority of selectableCodecs and codecConfig. private int getPrioitizedCodecType(BluetoothCodecConfig codecConfig, BluetoothCodecConfig[] selectableCodecs) { BluetoothCodecConfig prioritizedCodecConfig = codecConfig; for (BluetoothCodecConfig config : selectableCodecs) { if (prioritizedCodecConfig == null) { prioritizedCodecConfig = config; } if (config.getCodecPriority() > prioritizedCodecConfig.getCodecPriority()) { prioritizedCodecConfig = config; } } return prioritizedCodecConfig.getCodecType(); } // Assign the A2DP Source codec config priorities private BluetoothCodecConfig[] assignCodecConfigPriorities() { Resources resources = mContext.getResources(); if (resources == null) { return null; } int value; try { value = resources.getInteger(R.integer.a2dp_source_codec_priority_sbc); } catch (NotFoundException e) { value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; } if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) { mA2dpSourceCodecPrioritySbc = value; } try { value = resources.getInteger(R.integer.a2dp_source_codec_priority_aac); } catch (NotFoundException e) { value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; } if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) { mA2dpSourceCodecPriorityAac = value; } try { value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx); } catch (NotFoundException e) { value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; } if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) { mA2dpSourceCodecPriorityAptx = value; } try { value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd); } catch (NotFoundException e) { value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; } if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) { mA2dpSourceCodecPriorityAptxHd = value; } try { value = resources.getInteger(R.integer.a2dp_source_codec_priority_ldac); } catch (NotFoundException e) { value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; } if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) { mA2dpSourceCodecPriorityLdac = value; } BluetoothCodecConfig codecConfig; BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX]; codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, mA2dpSourceCodecPrioritySbc, BluetoothCodecConfig.SAMPLE_RATE_NONE, BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */); codecConfigArray[0] = codecConfig; codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, mA2dpSourceCodecPriorityAac, BluetoothCodecConfig.SAMPLE_RATE_NONE, BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */); codecConfigArray[1] = codecConfig; codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, mA2dpSourceCodecPriorityAptx, BluetoothCodecConfig.SAMPLE_RATE_NONE, BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */); codecConfigArray[2] = codecConfig; codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, mA2dpSourceCodecPriorityAptxHd, BluetoothCodecConfig.SAMPLE_RATE_NONE, BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */); codecConfigArray[3] = codecConfig; codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, mA2dpSourceCodecPriorityLdac, BluetoothCodecConfig.SAMPLE_RATE_NONE, BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */); codecConfigArray[4] = codecConfig; return codecConfigArray; } }