1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bluetooth.a2dp;
18 
19 import android.bluetooth.BluetoothCodecConfig;
20 import android.bluetooth.BluetoothCodecConfig.CodecPriority;
21 import android.bluetooth.BluetoothCodecStatus;
22 import android.bluetooth.BluetoothDevice;
23 import android.content.Context;
24 import android.content.res.Resources;
25 import android.content.res.Resources.NotFoundException;
26 import android.media.AudioManager;
27 import android.util.Log;
28 
29 import com.android.bluetooth.R;
30 
31 import java.util.Arrays;
32 import java.util.Objects;
33 /*
34  * A2DP Codec Configuration setup.
35  */
36 class A2dpCodecConfig {
37     private static final boolean DBG = true;
38     private static final String TAG = "A2dpCodecConfig";
39 
40     private Context mContext;
41     private A2dpNativeInterface mA2dpNativeInterface;
42 
43     private BluetoothCodecConfig[] mCodecConfigPriorities;
44     private @CodecPriority int mA2dpSourceCodecPrioritySbc =
45             BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
46     private @CodecPriority int mA2dpSourceCodecPriorityAac =
47             BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
48     private @CodecPriority int mA2dpSourceCodecPriorityAptx =
49             BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
50     private @CodecPriority int mA2dpSourceCodecPriorityAptxHd =
51             BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
52     private @CodecPriority int mA2dpSourceCodecPriorityLdac =
53             BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
54 
55     private BluetoothCodecConfig[] mCodecConfigOffloading = new BluetoothCodecConfig[0];
56 
A2dpCodecConfig(Context context, A2dpNativeInterface a2dpNativeInterface)57     A2dpCodecConfig(Context context, A2dpNativeInterface a2dpNativeInterface) {
58         mContext = context;
59         mA2dpNativeInterface = a2dpNativeInterface;
60         mCodecConfigPriorities = assignCodecConfigPriorities();
61 
62         AudioManager audioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
63         if (audioManager == null) {
64           Log.w(TAG, "Can't obtain the codec offloading prefernece from null AudioManager");
65           return;
66         }
67         mCodecConfigOffloading = audioManager.getHwOffloadEncodingFormatsSupportedForA2DP()
68                                              .toArray(mCodecConfigOffloading);
69     }
70 
codecConfigPriorities()71     BluetoothCodecConfig[] codecConfigPriorities() {
72         return mCodecConfigPriorities;
73     }
74 
codecConfigOffloading()75     BluetoothCodecConfig[] codecConfigOffloading() {
76         return mCodecConfigOffloading;
77     }
78 
setCodecConfigPreference(BluetoothDevice device, BluetoothCodecStatus codecStatus, BluetoothCodecConfig newCodecConfig)79     void setCodecConfigPreference(BluetoothDevice device,
80                                   BluetoothCodecStatus codecStatus,
81                                   BluetoothCodecConfig newCodecConfig) {
82         Objects.requireNonNull(codecStatus);
83 
84         // Check whether the codecConfig is selectable for this Bluetooth device.
85         BluetoothCodecConfig[] selectableCodecs = codecStatus.getCodecsSelectableCapabilities();
86         if (!Arrays.asList(selectableCodecs).stream().anyMatch(codec ->
87                 codec.isMandatoryCodec())) {
88             // Do not set codec preference to native if the selectableCodecs not contain mandatory
89             // codec. The reason could be remote codec negotiation is not completed yet.
90             Log.w(TAG, "setCodecConfigPreference: must have mandatory codec before changing.");
91             return;
92         }
93         if (!codecStatus.isCodecConfigSelectable(newCodecConfig)) {
94             Log.w(TAG, "setCodecConfigPreference: invalid codec "
95                     + Objects.toString(newCodecConfig));
96             return;
97         }
98 
99         // Check whether the codecConfig would change current codec config.
100         int prioritizedCodecType = getPrioitizedCodecType(newCodecConfig, selectableCodecs);
101         BluetoothCodecConfig currentCodecConfig = codecStatus.getCodecConfig();
102         if (prioritizedCodecType == currentCodecConfig.getCodecType()
103                 && (prioritizedCodecType != newCodecConfig.getCodecType()
104                 || (currentCodecConfig.similarCodecFeedingParameters(newCodecConfig)
105                 && currentCodecConfig.sameCodecSpecificParameters(newCodecConfig)))) {
106             // Same codec with same parameters, no need to send this request to native.
107             Log.w(TAG, "setCodecConfigPreference: codec not changed.");
108             return;
109         }
110 
111         BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
112         codecConfigArray[0] = newCodecConfig;
113         mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
114     }
115 
enableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig)116     void enableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
117         if (currentCodecConfig != null && !currentCodecConfig.isMandatoryCodec()) {
118             Log.i(TAG, "enableOptionalCodecs: already using optional codec "
119                     + currentCodecConfig.getCodecName());
120             return;
121         }
122 
123         BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
124         if (codecConfigArray == null) {
125             return;
126         }
127 
128         // Set the mandatory codec's priority to default, and remove the rest
129         for (int i = 0; i < codecConfigArray.length; i++) {
130             BluetoothCodecConfig codecConfig = codecConfigArray[i];
131             if (!codecConfig.isMandatoryCodec()) {
132                 codecConfigArray[i] = null;
133             }
134         }
135 
136         mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
137     }
138 
disableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig)139     void disableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
140         if (currentCodecConfig != null && currentCodecConfig.isMandatoryCodec()) {
141             Log.i(TAG, "disableOptionalCodecs: already using mandatory codec.");
142             return;
143         }
144 
145         BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
146         if (codecConfigArray == null) {
147             return;
148         }
149         // Set the mandatory codec's priority to highest, and remove the rest
150         for (int i = 0; i < codecConfigArray.length; i++) {
151             BluetoothCodecConfig codecConfig = codecConfigArray[i];
152             if (codecConfig.isMandatoryCodec()) {
153                 codecConfig.setCodecPriority(BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST);
154             } else {
155                 codecConfigArray[i] = null;
156             }
157         }
158         mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
159     }
160 
161     // Get the codec type of the highest priority of selectableCodecs and codecConfig.
getPrioitizedCodecType(BluetoothCodecConfig codecConfig, BluetoothCodecConfig[] selectableCodecs)162     private int getPrioitizedCodecType(BluetoothCodecConfig codecConfig,
163             BluetoothCodecConfig[] selectableCodecs) {
164         BluetoothCodecConfig prioritizedCodecConfig = codecConfig;
165         for (BluetoothCodecConfig config : selectableCodecs) {
166             if (prioritizedCodecConfig == null) {
167                 prioritizedCodecConfig = config;
168             }
169             if (config.getCodecPriority() > prioritizedCodecConfig.getCodecPriority()) {
170                 prioritizedCodecConfig = config;
171             }
172         }
173         return prioritizedCodecConfig.getCodecType();
174     }
175 
176     // Assign the A2DP Source codec config priorities
assignCodecConfigPriorities()177     private BluetoothCodecConfig[] assignCodecConfigPriorities() {
178         Resources resources = mContext.getResources();
179         if (resources == null) {
180             return null;
181         }
182 
183         int value;
184         try {
185             value = resources.getInteger(R.integer.a2dp_source_codec_priority_sbc);
186         } catch (NotFoundException e) {
187             value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
188         }
189         if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
190                 < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
191             mA2dpSourceCodecPrioritySbc = value;
192         }
193 
194         try {
195             value = resources.getInteger(R.integer.a2dp_source_codec_priority_aac);
196         } catch (NotFoundException e) {
197             value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
198         }
199         if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
200                 < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
201             mA2dpSourceCodecPriorityAac = value;
202         }
203 
204         try {
205             value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx);
206         } catch (NotFoundException e) {
207             value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
208         }
209         if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
210                 < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
211             mA2dpSourceCodecPriorityAptx = value;
212         }
213 
214         try {
215             value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd);
216         } catch (NotFoundException e) {
217             value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
218         }
219         if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
220                 < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
221             mA2dpSourceCodecPriorityAptxHd = value;
222         }
223 
224         try {
225             value = resources.getInteger(R.integer.a2dp_source_codec_priority_ldac);
226         } catch (NotFoundException e) {
227             value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
228         }
229         if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
230                 < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
231             mA2dpSourceCodecPriorityLdac = value;
232         }
233 
234         BluetoothCodecConfig codecConfig;
235         BluetoothCodecConfig[] codecConfigArray =
236                 new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
237         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
238                 mA2dpSourceCodecPrioritySbc, BluetoothCodecConfig.SAMPLE_RATE_NONE,
239                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
240                 .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
241                 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
242         codecConfigArray[0] = codecConfig;
243         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
244                 mA2dpSourceCodecPriorityAac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
245                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
246                 .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
247                 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
248         codecConfigArray[1] = codecConfig;
249         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
250                 mA2dpSourceCodecPriorityAptx, BluetoothCodecConfig.SAMPLE_RATE_NONE,
251                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
252                 .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
253                 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
254         codecConfigArray[2] = codecConfig;
255         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
256                 mA2dpSourceCodecPriorityAptxHd, BluetoothCodecConfig.SAMPLE_RATE_NONE,
257                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
258                 .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
259                 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
260         codecConfigArray[3] = codecConfig;
261         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
262                 mA2dpSourceCodecPriorityLdac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
263                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
264                 .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
265                 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
266         codecConfigArray[4] = codecConfig;
267 
268         return codecConfigArray;
269     }
270 }
271 
272