1 /* 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 17 package com.android.settingslib.bluetooth; 18 19 import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO; 20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; 21 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 22 23 import android.bluetooth.BluetoothA2dp; 24 import android.bluetooth.BluetoothAdapter; 25 import android.bluetooth.BluetoothClass; 26 import android.bluetooth.BluetoothCodecConfig; 27 import android.bluetooth.BluetoothDevice; 28 import android.bluetooth.BluetoothProfile; 29 import android.bluetooth.BluetoothUuid; 30 import android.content.Context; 31 import android.os.ParcelUuid; 32 import android.util.Log; 33 34 import com.android.settingslib.R; 35 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.List; 39 40 public class A2dpProfile implements LocalBluetoothProfile { 41 private static final String TAG = "A2dpProfile"; 42 43 private Context mContext; 44 45 private BluetoothA2dp mService; 46 private boolean mIsProfileReady; 47 48 private final CachedBluetoothDeviceManager mDeviceManager; 49 private final BluetoothAdapter mBluetoothAdapter; 50 51 static final ParcelUuid[] SINK_UUIDS = { 52 BluetoothUuid.A2DP_SINK, 53 BluetoothUuid.ADV_AUDIO_DIST, 54 }; 55 56 static final String NAME = "A2DP"; 57 private final LocalBluetoothProfileManager mProfileManager; 58 59 // Order of this profile in device profiles list 60 private static final int ORDINAL = 1; 61 62 // These callbacks run on the main thread. 63 private final class A2dpServiceListener 64 implements BluetoothProfile.ServiceListener { 65 onServiceConnected(int profile, BluetoothProfile proxy)66 public void onServiceConnected(int profile, BluetoothProfile proxy) { 67 mService = (BluetoothA2dp) proxy; 68 // We just bound to the service, so refresh the UI for any connected A2DP devices. 69 List<BluetoothDevice> deviceList = mService.getConnectedDevices(); 70 while (!deviceList.isEmpty()) { 71 BluetoothDevice nextDevice = deviceList.remove(0); 72 CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); 73 // we may add a new device here, but generally this should not happen 74 if (device == null) { 75 Log.w(TAG, "A2dpProfile found new device: " + nextDevice); 76 device = mDeviceManager.addDevice(nextDevice); 77 } 78 device.onProfileStateChanged(A2dpProfile.this, BluetoothProfile.STATE_CONNECTED); 79 device.refresh(); 80 } 81 mIsProfileReady=true; 82 mProfileManager.callServiceConnectedListeners(); 83 } 84 onServiceDisconnected(int profile)85 public void onServiceDisconnected(int profile) { 86 mIsProfileReady=false; 87 } 88 } 89 isProfileReady()90 public boolean isProfileReady() { 91 return mIsProfileReady; 92 } 93 94 @Override getProfileId()95 public int getProfileId() { 96 return BluetoothProfile.A2DP; 97 } 98 A2dpProfile(Context context, CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager)99 A2dpProfile(Context context, CachedBluetoothDeviceManager deviceManager, 100 LocalBluetoothProfileManager profileManager) { 101 mContext = context; 102 mDeviceManager = deviceManager; 103 mProfileManager = profileManager; 104 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 105 mBluetoothAdapter.getProfileProxy(context, new A2dpServiceListener(), 106 BluetoothProfile.A2DP); 107 } 108 accessProfileEnabled()109 public boolean accessProfileEnabled() { 110 return true; 111 } 112 isAutoConnectable()113 public boolean isAutoConnectable() { 114 return true; 115 } 116 117 /** 118 * Get A2dp devices matching connection states{ 119 * @code BluetoothProfile.STATE_CONNECTED, 120 * @code BluetoothProfile.STATE_CONNECTING, 121 * @code BluetoothProfile.STATE_DISCONNECTING} 122 * 123 * @return Matching device list 124 */ getConnectedDevices()125 public List<BluetoothDevice> getConnectedDevices() { 126 return getDevicesByStates(new int[] { 127 BluetoothProfile.STATE_CONNECTED, 128 BluetoothProfile.STATE_CONNECTING, 129 BluetoothProfile.STATE_DISCONNECTING}); 130 } 131 132 /** 133 * Get A2dp devices matching connection states{ 134 * @code BluetoothProfile.STATE_DISCONNECTED, 135 * @code BluetoothProfile.STATE_CONNECTED, 136 * @code BluetoothProfile.STATE_CONNECTING, 137 * @code BluetoothProfile.STATE_DISCONNECTING} 138 * 139 * @return Matching device list 140 */ getConnectableDevices()141 public List<BluetoothDevice> getConnectableDevices() { 142 return getDevicesByStates(new int[] { 143 BluetoothProfile.STATE_DISCONNECTED, 144 BluetoothProfile.STATE_CONNECTED, 145 BluetoothProfile.STATE_CONNECTING, 146 BluetoothProfile.STATE_DISCONNECTING}); 147 } 148 getDevicesByStates(int[] states)149 private List<BluetoothDevice> getDevicesByStates(int[] states) { 150 if (mService == null) { 151 return new ArrayList<BluetoothDevice>(0); 152 } 153 return mService.getDevicesMatchingConnectionStates(states); 154 } 155 getConnectionStatus(BluetoothDevice device)156 public int getConnectionStatus(BluetoothDevice device) { 157 if (mService == null) { 158 return BluetoothProfile.STATE_DISCONNECTED; 159 } 160 return mService.getConnectionState(device); 161 } 162 setActiveDevice(BluetoothDevice device)163 public boolean setActiveDevice(BluetoothDevice device) { 164 if (mBluetoothAdapter == null) { 165 return false; 166 } 167 return device == null 168 ? mBluetoothAdapter.removeActiveDevice(ACTIVE_DEVICE_AUDIO) 169 : mBluetoothAdapter.setActiveDevice(device, ACTIVE_DEVICE_AUDIO); 170 } 171 getActiveDevice()172 public BluetoothDevice getActiveDevice() { 173 if (mService == null) return null; 174 return mService.getActiveDevice(); 175 } 176 177 @Override isEnabled(BluetoothDevice device)178 public boolean isEnabled(BluetoothDevice device) { 179 if (mService == null) { 180 return false; 181 } 182 return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; 183 } 184 185 @Override getConnectionPolicy(BluetoothDevice device)186 public int getConnectionPolicy(BluetoothDevice device) { 187 if (mService == null) { 188 return CONNECTION_POLICY_FORBIDDEN; 189 } 190 return mService.getConnectionPolicy(device); 191 } 192 193 @Override setEnabled(BluetoothDevice device, boolean enabled)194 public boolean setEnabled(BluetoothDevice device, boolean enabled) { 195 boolean isEnabled = false; 196 if (mService == null) { 197 return false; 198 } 199 if (enabled) { 200 if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { 201 isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); 202 } 203 } else { 204 isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); 205 } 206 207 return isEnabled; 208 } isA2dpPlaying()209 boolean isA2dpPlaying() { 210 if (mService == null) return false; 211 List<BluetoothDevice> sinks = mService.getConnectedDevices(); 212 for (BluetoothDevice device : sinks) { 213 if (mService.isA2dpPlaying(device)) { 214 return true; 215 } 216 } 217 return false; 218 } 219 supportsHighQualityAudio(BluetoothDevice device)220 public boolean supportsHighQualityAudio(BluetoothDevice device) { 221 BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice(); 222 if (bluetoothDevice == null) { 223 return false; 224 } 225 int support = mService.isOptionalCodecsSupported(bluetoothDevice); 226 return support == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED; 227 } 228 isHighQualityAudioEnabled(BluetoothDevice device)229 public boolean isHighQualityAudioEnabled(BluetoothDevice device) { 230 BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice(); 231 if (bluetoothDevice == null) { 232 return false; 233 } 234 int enabled = mService.isOptionalCodecsEnabled(bluetoothDevice); 235 if (enabled != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN) { 236 return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED; 237 } else if (getConnectionStatus(bluetoothDevice) != BluetoothProfile.STATE_CONNECTED 238 && supportsHighQualityAudio(bluetoothDevice)) { 239 // Since we don't have a stored preference and the device isn't connected, just return 240 // true since the default behavior when the device gets connected in the future would be 241 // to have optional codecs enabled. 242 return true; 243 } 244 BluetoothCodecConfig codecConfig = null; 245 if (mService.getCodecStatus(bluetoothDevice) != null) { 246 codecConfig = mService.getCodecStatus(bluetoothDevice).getCodecConfig(); 247 } 248 if (codecConfig != null) { 249 return !codecConfig.isMandatoryCodec(); 250 } else { 251 return false; 252 } 253 } 254 setHighQualityAudioEnabled(BluetoothDevice device, boolean enabled)255 public void setHighQualityAudioEnabled(BluetoothDevice device, boolean enabled) { 256 BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice(); 257 if (bluetoothDevice == null) { 258 return; 259 } 260 int prefValue = enabled 261 ? BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED 262 : BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED; 263 mService.setOptionalCodecsEnabled(bluetoothDevice, prefValue); 264 if (getConnectionStatus(bluetoothDevice) != BluetoothProfile.STATE_CONNECTED) { 265 return; 266 } 267 if (enabled) { 268 mService.enableOptionalCodecs(bluetoothDevice); 269 } else { 270 mService.disableOptionalCodecs(bluetoothDevice); 271 } 272 } 273 getHighQualityAudioOptionLabel(BluetoothDevice device)274 public String getHighQualityAudioOptionLabel(BluetoothDevice device) { 275 BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice(); 276 int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec; 277 if (bluetoothDevice == null || !supportsHighQualityAudio(device) 278 || getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) { 279 return mContext.getString(unknownCodecId); 280 } 281 // We want to get the highest priority codec, since that's the one that will be used with 282 // this device, and see if it is high-quality (ie non-mandatory). 283 BluetoothCodecConfig[] selectable = null; 284 if (mService.getCodecStatus(device) != null) { 285 selectable = mService.getCodecStatus(device).getCodecsSelectableCapabilities(); 286 // To get the highest priority, we sort in reverse. 287 Arrays.sort(selectable, 288 (a, b) -> { 289 return b.getCodecPriority() - a.getCodecPriority(); 290 }); 291 } 292 293 final BluetoothCodecConfig codecConfig = (selectable == null || selectable.length < 1) 294 ? null : selectable[0]; 295 final int codecType = (codecConfig == null || codecConfig.isMandatoryCodec()) 296 ? BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID : codecConfig.getCodecType(); 297 298 int index = -1; 299 switch (codecType) { 300 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC: 301 index = 1; 302 break; 303 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC: 304 index = 2; 305 break; 306 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX: 307 index = 3; 308 break; 309 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD: 310 index = 4; 311 break; 312 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: 313 index = 5; 314 break; 315 } 316 317 if (index < 0) { 318 return mContext.getString(unknownCodecId); 319 } 320 return mContext.getString(R.string.bluetooth_profile_a2dp_high_quality, 321 mContext.getResources().getStringArray(R.array.bluetooth_a2dp_codec_titles)[index]); 322 } 323 toString()324 public String toString() { 325 return NAME; 326 } 327 getOrdinal()328 public int getOrdinal() { 329 return ORDINAL; 330 } 331 getNameResource(BluetoothDevice device)332 public int getNameResource(BluetoothDevice device) { 333 return R.string.bluetooth_profile_a2dp; 334 } 335 getSummaryResourceForDevice(BluetoothDevice device)336 public int getSummaryResourceForDevice(BluetoothDevice device) { 337 int state = getConnectionStatus(device); 338 switch (state) { 339 case BluetoothProfile.STATE_DISCONNECTED: 340 return R.string.bluetooth_a2dp_profile_summary_use_for; 341 342 case BluetoothProfile.STATE_CONNECTED: 343 return R.string.bluetooth_a2dp_profile_summary_connected; 344 345 default: 346 return BluetoothUtils.getConnectionStateSummary(state); 347 } 348 } 349 getDrawableResource(BluetoothClass btClass)350 public int getDrawableResource(BluetoothClass btClass) { 351 return com.android.internal.R.drawable.ic_bt_headphones_a2dp; 352 } 353 finalize()354 protected void finalize() { 355 Log.d(TAG, "finalize()"); 356 if (mService != null) { 357 try { 358 BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP, 359 mService); 360 mService = null; 361 }catch (Throwable t) { 362 Log.w(TAG, "Error cleaning up A2DP proxy", t); 363 } 364 } 365 } 366 } 367