blob: bed3030788058914f97d799b4c17e2dcf09b9c39 [file] [log] [blame]
Jason Monk7ce96b92015-02-02 11:27:58 -05001/*
2 * Copyright (C) 2011 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
17package com.android.settingslib.bluetooth;
18
19import android.bluetooth.BluetoothA2dp;
20import android.bluetooth.BluetoothAdapter;
21import android.bluetooth.BluetoothClass;
Antony Sargent374d2592017-04-20 11:23:34 -070022import android.bluetooth.BluetoothCodecConfig;
Jason Monk7ce96b92015-02-02 11:27:58 -050023import android.bluetooth.BluetoothDevice;
24import android.bluetooth.BluetoothProfile;
25import android.bluetooth.BluetoothUuid;
26import android.content.Context;
27import android.os.ParcelUuid;
28import android.util.Log;
29
30import com.android.settingslib.R;
31
32import java.util.ArrayList;
Antony Sargent374d2592017-04-20 11:23:34 -070033import java.util.Arrays;
Jason Monk7ce96b92015-02-02 11:27:58 -050034import java.util.List;
35
Antony Sargent374d2592017-04-20 11:23:34 -070036public class A2dpProfile implements LocalBluetoothProfile {
Jason Monk7ce96b92015-02-02 11:27:58 -050037 private static final String TAG = "A2dpProfile";
Jason Monk7ce96b92015-02-02 11:27:58 -050038
Antony Sargent374d2592017-04-20 11:23:34 -070039 private Context mContext;
40
Jason Monk7ce96b92015-02-02 11:27:58 -050041 private BluetoothA2dp mService;
42 private boolean mIsProfileReady;
43
Jason Monk7ce96b92015-02-02 11:27:58 -050044 private final CachedBluetoothDeviceManager mDeviceManager;
45
46 static final ParcelUuid[] SINK_UUIDS = {
47 BluetoothUuid.AudioSink,
48 BluetoothUuid.AdvAudioDist,
49 };
50
51 static final String NAME = "A2DP";
52 private final LocalBluetoothProfileManager mProfileManager;
53
54 // Order of this profile in device profiles list
55 private static final int ORDINAL = 1;
56
57 // These callbacks run on the main thread.
58 private final class A2dpServiceListener
59 implements BluetoothProfile.ServiceListener {
60
61 public void onServiceConnected(int profile, BluetoothProfile proxy) {
Jason Monk7ce96b92015-02-02 11:27:58 -050062 mService = (BluetoothA2dp) proxy;
63 // We just bound to the service, so refresh the UI for any connected A2DP devices.
64 List<BluetoothDevice> deviceList = mService.getConnectedDevices();
65 while (!deviceList.isEmpty()) {
66 BluetoothDevice nextDevice = deviceList.remove(0);
67 CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
68 // we may add a new device here, but generally this should not happen
69 if (device == null) {
70 Log.w(TAG, "A2dpProfile found new device: " + nextDevice);
timhypengf64f6902018-07-31 15:40:15 +080071 device = mDeviceManager.addDevice(nextDevice);
Jason Monk7ce96b92015-02-02 11:27:58 -050072 }
73 device.onProfileStateChanged(A2dpProfile.this, BluetoothProfile.STATE_CONNECTED);
74 device.refresh();
75 }
76 mIsProfileReady=true;
hughchenbd0dd492019-01-08 14:34:10 +080077 mProfileManager.callServiceConnectedListeners();
Jason Monk7ce96b92015-02-02 11:27:58 -050078 }
79
80 public void onServiceDisconnected(int profile) {
Jason Monk7ce96b92015-02-02 11:27:58 -050081 mIsProfileReady=false;
82 }
83 }
84
85 public boolean isProfileReady() {
86 return mIsProfileReady;
87 }
88
ryanywlin44de3a02018-05-02 15:15:37 +080089 @Override
90 public int getProfileId() {
91 return BluetoothProfile.A2DP;
92 }
93
timhypengf64f6902018-07-31 15:40:15 +080094 A2dpProfile(Context context, CachedBluetoothDeviceManager deviceManager,
Jason Monk7ce96b92015-02-02 11:27:58 -050095 LocalBluetoothProfileManager profileManager) {
Antony Sargent374d2592017-04-20 11:23:34 -070096 mContext = context;
Jason Monk7ce96b92015-02-02 11:27:58 -050097 mDeviceManager = deviceManager;
98 mProfileManager = profileManager;
timhypengf64f6902018-07-31 15:40:15 +080099 BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new A2dpServiceListener(),
Jason Monk7ce96b92015-02-02 11:27:58 -0500100 BluetoothProfile.A2DP);
Antony Sargent374d2592017-04-20 11:23:34 -0700101 }
102
Leon Liao3b5e6bd2018-09-18 12:45:45 +0800103 public boolean accessProfileEnabled() {
Jason Monk7ce96b92015-02-02 11:27:58 -0500104 return true;
105 }
106
107 public boolean isAutoConnectable() {
108 return true;
109 }
110
111 public List<BluetoothDevice> getConnectedDevices() {
112 if (mService == null) return new ArrayList<BluetoothDevice>(0);
113 return mService.getDevicesMatchingConnectionStates(
114 new int[] {BluetoothProfile.STATE_CONNECTED,
115 BluetoothProfile.STATE_CONNECTING,
116 BluetoothProfile.STATE_DISCONNECTING});
117 }
118
119 public boolean connect(BluetoothDevice device) {
120 if (mService == null) return false;
Jason Monk7ce96b92015-02-02 11:27:58 -0500121 return mService.connect(device);
122 }
123
124 public boolean disconnect(BluetoothDevice device) {
125 if (mService == null) return false;
126 // Downgrade priority as user is disconnecting the headset.
127 if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){
128 mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
129 }
130 return mService.disconnect(device);
131 }
132
133 public int getConnectionStatus(BluetoothDevice device) {
134 if (mService == null) {
135 return BluetoothProfile.STATE_DISCONNECTED;
136 }
137 return mService.getConnectionState(device);
138 }
139
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -0800140 public boolean setActiveDevice(BluetoothDevice device) {
141 if (mService == null) return false;
142 return mService.setActiveDevice(device);
143 }
144
145 public BluetoothDevice getActiveDevice() {
146 if (mService == null) return null;
147 return mService.getActiveDevice();
148 }
149
Jason Monk7ce96b92015-02-02 11:27:58 -0500150 public boolean isPreferred(BluetoothDevice device) {
151 if (mService == null) return false;
152 return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
153 }
154
155 public int getPreferred(BluetoothDevice device) {
156 if (mService == null) return BluetoothProfile.PRIORITY_OFF;
157 return mService.getPriority(device);
158 }
159
160 public void setPreferred(BluetoothDevice device, boolean preferred) {
161 if (mService == null) return;
162 if (preferred) {
163 if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
164 mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
165 }
166 } else {
167 mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
168 }
169 }
170 boolean isA2dpPlaying() {
171 if (mService == null) return false;
172 List<BluetoothDevice> sinks = mService.getConnectedDevices();
Pavlin Radoslavov1af33a12018-01-21 02:59:15 -0800173 for (BluetoothDevice device : sinks) {
174 if (mService.isA2dpPlaying(device)) {
Jason Monk7ce96b92015-02-02 11:27:58 -0500175 return true;
176 }
177 }
178 return false;
179 }
180
Antony Sargent374d2592017-04-20 11:23:34 -0700181 public boolean supportsHighQualityAudio(BluetoothDevice device) {
hjchangliaobd8c8fb2018-04-26 15:12:11 +0800182 int support = mService.supportsOptionalCodecs(device);
Antony Sargent374d2592017-04-20 11:23:34 -0700183 return support == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED;
184 }
185
186 public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
hjchangliaobd8c8fb2018-04-26 15:12:11 +0800187 int enabled = mService.getOptionalCodecsEnabled(device);
Antony Sargent374d2592017-04-20 11:23:34 -0700188 if (enabled != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN) {
189 return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED;
190 } else if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED &&
191 supportsHighQualityAudio(device)) {
192 // Since we don't have a stored preference and the device isn't connected, just return
193 // true since the default behavior when the device gets connected in the future would be
194 // to have optional codecs enabled.
195 return true;
196 }
197 BluetoothCodecConfig codecConfig = null;
hjchangliaobd8c8fb2018-04-26 15:12:11 +0800198 if (mService.getCodecStatus(device) != null) {
199 codecConfig = mService.getCodecStatus(device).getCodecConfig();
Antony Sargent374d2592017-04-20 11:23:34 -0700200 }
201 if (codecConfig != null) {
202 return !codecConfig.isMandatoryCodec();
203 } else {
204 return false;
205 }
206 }
207
208 public void setHighQualityAudioEnabled(BluetoothDevice device, boolean enabled) {
209 int prefValue = enabled
210 ? BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED
211 : BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED;
hjchangliaobd8c8fb2018-04-26 15:12:11 +0800212 mService.setOptionalCodecsEnabled(device, prefValue);
Antony Sargent374d2592017-04-20 11:23:34 -0700213 if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) {
214 return;
215 }
216 if (enabled) {
Pavlin Radoslavov502af212018-01-03 19:38:39 -0800217 mService.enableOptionalCodecs(device);
Antony Sargent374d2592017-04-20 11:23:34 -0700218 } else {
Pavlin Radoslavov502af212018-01-03 19:38:39 -0800219 mService.disableOptionalCodecs(device);
Antony Sargent374d2592017-04-20 11:23:34 -0700220 }
221 }
222
223 public String getHighQualityAudioOptionLabel(BluetoothDevice device) {
224 int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec;
Justin Klaassencb605752017-08-28 15:39:45 -0700225 if (!supportsHighQualityAudio(device)
226 || getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) {
Antony Sargent374d2592017-04-20 11:23:34 -0700227 return mContext.getString(unknownCodecId);
228 }
229 // We want to get the highest priority codec, since that's the one that will be used with
230 // this device, and see if it is high-quality (ie non-mandatory).
231 BluetoothCodecConfig[] selectable = null;
hjchangliaobd8c8fb2018-04-26 15:12:11 +0800232 if (mService.getCodecStatus(device) != null) {
233 selectable = mService.getCodecStatus(device).getCodecsSelectableCapabilities();
Antony Sargent374d2592017-04-20 11:23:34 -0700234 // To get the highest priority, we sort in reverse.
235 Arrays.sort(selectable,
236 (a, b) -> {
237 return b.getCodecPriority() - a.getCodecPriority();
238 });
239 }
Justin Klaassencb605752017-08-28 15:39:45 -0700240
241 final BluetoothCodecConfig codecConfig = (selectable == null || selectable.length < 1)
242 ? null : selectable[0];
243 final int codecType = (codecConfig == null || codecConfig.isMandatoryCodec())
244 ? BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID : codecConfig.getCodecType();
245
246 int index = -1;
247 switch (codecType) {
248 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
249 index = 1;
250 break;
251 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
252 index = 2;
253 break;
254 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
255 index = 3;
256 break;
257 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
258 index = 4;
259 break;
260 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
261 index = 5;
262 break;
263 }
264
265 if (index < 0) {
Antony Sargent374d2592017-04-20 11:23:34 -0700266 return mContext.getString(unknownCodecId);
267 }
268 return mContext.getString(R.string.bluetooth_profile_a2dp_high_quality,
Justin Klaassencb605752017-08-28 15:39:45 -0700269 mContext.getResources().getStringArray(R.array.bluetooth_a2dp_codec_titles)[index]);
Antony Sargent374d2592017-04-20 11:23:34 -0700270 }
271
Jason Monk7ce96b92015-02-02 11:27:58 -0500272 public String toString() {
273 return NAME;
274 }
275
276 public int getOrdinal() {
277 return ORDINAL;
278 }
279
280 public int getNameResource(BluetoothDevice device) {
281 return R.string.bluetooth_profile_a2dp;
282 }
283
284 public int getSummaryResourceForDevice(BluetoothDevice device) {
285 int state = getConnectionStatus(device);
286 switch (state) {
287 case BluetoothProfile.STATE_DISCONNECTED:
288 return R.string.bluetooth_a2dp_profile_summary_use_for;
289
290 case BluetoothProfile.STATE_CONNECTED:
291 return R.string.bluetooth_a2dp_profile_summary_connected;
292
293 default:
Kunhung Lie7b7cb82018-05-17 11:04:54 +0800294 return BluetoothUtils.getConnectionStateSummary(state);
Jason Monk7ce96b92015-02-02 11:27:58 -0500295 }
296 }
297
298 public int getDrawableResource(BluetoothClass btClass) {
Amin Shaikh10f363b2019-01-24 17:59:49 -0500299 return com.android.internal.R.drawable.ic_bt_headphones_a2dp;
Jason Monk7ce96b92015-02-02 11:27:58 -0500300 }
301
302 protected void finalize() {
Chienyuan97e4b202019-01-03 20:02:27 +0800303 Log.d(TAG, "finalize()");
Jason Monk7ce96b92015-02-02 11:27:58 -0500304 if (mService != null) {
305 try {
306 BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP,
307 mService);
308 mService = null;
309 }catch (Throwable t) {
310 Log.w(TAG, "Error cleaning up A2DP proxy", t);
311 }
312 }
313 }
314}