• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.BluetoothA2dp;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothCodecConfig;
22 import android.bluetooth.BluetoothCodecStatus;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothProfile;
25 import android.bluetooth.BluetoothUuid;
26 import android.bluetooth.IBluetoothA2dp;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.media.AudioManager;
32 import android.os.HandlerThread;
33 import android.provider.Settings;
34 import android.support.annotation.GuardedBy;
35 import android.support.annotation.VisibleForTesting;
36 import android.util.Log;
37 
38 import com.android.bluetooth.BluetoothMetricsProto;
39 import com.android.bluetooth.Utils;
40 import com.android.bluetooth.avrcp.Avrcp;
41 import com.android.bluetooth.avrcp.AvrcpTargetService;
42 import com.android.bluetooth.btservice.AdapterService;
43 import com.android.bluetooth.btservice.MetricsLogger;
44 import com.android.bluetooth.btservice.ProfileService;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Objects;
49 import java.util.Set;
50 import java.util.concurrent.ConcurrentHashMap;
51 import java.util.concurrent.ConcurrentMap;
52 
53 /**
54  * Provides Bluetooth A2DP profile, as a service in the Bluetooth application.
55  * @hide
56  */
57 public class A2dpService extends ProfileService {
58     private static final boolean DBG = true;
59     private static final String TAG = "A2dpService";
60 
61     private static A2dpService sA2dpService;
62 
63     private BluetoothAdapter mAdapter;
64     private AdapterService mAdapterService;
65     private HandlerThread mStateMachinesThread;
66     private Avrcp mAvrcp;
67 
68     @VisibleForTesting
69     A2dpNativeInterface mA2dpNativeInterface;
70     private AudioManager mAudioManager;
71     private A2dpCodecConfig mA2dpCodecConfig;
72 
73     @GuardedBy("mStateMachines")
74     private BluetoothDevice mActiveDevice;
75     private final ConcurrentMap<BluetoothDevice, A2dpStateMachine> mStateMachines =
76             new ConcurrentHashMap<>();
77 
78     // Upper limit of all A2DP devices: Bonded or Connected
79     private static final int MAX_A2DP_STATE_MACHINES = 50;
80     // Upper limit of all A2DP devices that are Connected or Connecting
81     private int mMaxConnectedAudioDevices = 1;
82     // A2DP Offload Enabled in platform
83     boolean mA2dpOffloadEnabled = false;
84 
85     private BroadcastReceiver mBondStateChangedReceiver;
86     private BroadcastReceiver mConnectionStateChangedReceiver;
87 
88     @Override
initBinder()89     protected IProfileServiceBinder initBinder() {
90         return new BluetoothA2dpBinder(this);
91     }
92 
93     @Override
create()94     protected void create() {
95         Log.i(TAG, "create()");
96     }
97 
98     @Override
start()99     protected boolean start() {
100         Log.i(TAG, "start()");
101         if (sA2dpService != null) {
102             throw new IllegalStateException("start() called twice");
103         }
104 
105         // Step 1: Get BluetoothAdapter, AdapterService, A2dpNativeInterface, AudioManager.
106         // None of them can be null.
107         mAdapter = Objects.requireNonNull(BluetoothAdapter.getDefaultAdapter(),
108                 "BluetoothAdapter cannot be null when A2dpService starts");
109         mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
110                 "AdapterService cannot be null when A2dpService starts");
111         mA2dpNativeInterface = Objects.requireNonNull(A2dpNativeInterface.getInstance(),
112                 "A2dpNativeInterface cannot be null when A2dpService starts");
113         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
114         Objects.requireNonNull(mAudioManager,
115                                "AudioManager cannot be null when A2dpService starts");
116 
117         // Step 2: Get maximum number of connected audio devices
118         mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices();
119         Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices);
120 
121         // Step 3: Setup AVRCP
122         mAvrcp = Avrcp.make(this);
123 
124         // Step 4: Start handler thread for state machines
125         mStateMachines.clear();
126         mStateMachinesThread = new HandlerThread("A2dpService.StateMachines");
127         mStateMachinesThread.start();
128 
129         // Step 5: Setup codec config
130         mA2dpCodecConfig = new A2dpCodecConfig(this, mA2dpNativeInterface);
131 
132         // Step 6: Initialize native interface
133         mA2dpNativeInterface.init(mMaxConnectedAudioDevices,
134                                   mA2dpCodecConfig.codecConfigPriorities());
135 
136         // Step 7: Check if A2DP is in offload mode
137         mA2dpOffloadEnabled = mAdapterService.isA2dpOffloadEnabled();
138         if (DBG) {
139             Log.d(TAG, "A2DP offload flag set to " + mA2dpOffloadEnabled);
140         }
141 
142         // Step 8: Setup broadcast receivers
143         IntentFilter filter = new IntentFilter();
144         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
145         mBondStateChangedReceiver = new BondStateChangedReceiver();
146         registerReceiver(mBondStateChangedReceiver, filter);
147         filter = new IntentFilter();
148         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
149         mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
150         registerReceiver(mConnectionStateChangedReceiver, filter);
151 
152         // Step 9: Mark service as started
153         setA2dpService(this);
154 
155         // Step 10: Clear active device
156         setActiveDevice(null);
157 
158         return true;
159     }
160 
161     @Override
stop()162     protected boolean stop() {
163         Log.i(TAG, "stop()");
164         if (sA2dpService == null) {
165             Log.w(TAG, "stop() called before start()");
166             return true;
167         }
168 
169         // Step 10: Store volume if there is an active device
170         if (mActiveDevice != null && AvrcpTargetService.get() != null) {
171             AvrcpTargetService.get().storeVolumeForDevice(mActiveDevice);
172         }
173 
174         // Step 9: Clear active device and stop playing audio
175         removeActiveDevice(true);
176 
177         // Step 8: Mark service as stopped
178         setA2dpService(null);
179 
180         // Step 7: Unregister broadcast receivers
181         unregisterReceiver(mConnectionStateChangedReceiver);
182         mConnectionStateChangedReceiver = null;
183         unregisterReceiver(mBondStateChangedReceiver);
184         mBondStateChangedReceiver = null;
185 
186         // Step 6: Cleanup native interface
187         mA2dpNativeInterface.cleanup();
188         mA2dpNativeInterface = null;
189 
190         // Step 5: Clear codec config
191         mA2dpCodecConfig = null;
192 
193         // Step 4: Destroy state machines and stop handler thread
194         synchronized (mStateMachines) {
195             for (A2dpStateMachine sm : mStateMachines.values()) {
196                 sm.doQuit();
197                 sm.cleanup();
198             }
199             mStateMachines.clear();
200         }
201         mStateMachinesThread.quitSafely();
202         mStateMachinesThread = null;
203 
204         // Step 3: Cleanup AVRCP
205         mAvrcp.doQuit();
206         mAvrcp.cleanup();
207         mAvrcp = null;
208 
209         // Step 2: Reset maximum number of connected audio devices
210         mMaxConnectedAudioDevices = 1;
211 
212         // Step 1: Clear BluetoothAdapter, AdapterService, A2dpNativeInterface, AudioManager
213         mAudioManager = null;
214         mA2dpNativeInterface = null;
215         mAdapterService = null;
216         mAdapter = null;
217 
218         return true;
219     }
220 
221     @Override
cleanup()222     protected void cleanup() {
223         Log.i(TAG, "cleanup()");
224     }
225 
getA2dpService()226     public static synchronized A2dpService getA2dpService() {
227         if (sA2dpService == null) {
228             Log.w(TAG, "getA2dpService(): service is null");
229             return null;
230         }
231         if (!sA2dpService.isAvailable()) {
232             Log.w(TAG, "getA2dpService(): service is not available");
233             return null;
234         }
235         return sA2dpService;
236     }
237 
setA2dpService(A2dpService instance)238     private static synchronized void setA2dpService(A2dpService instance) {
239         if (DBG) {
240             Log.d(TAG, "setA2dpService(): set to: " + instance);
241         }
242         sA2dpService = instance;
243     }
244 
connect(BluetoothDevice device)245     public boolean connect(BluetoothDevice device) {
246         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
247         if (DBG) {
248             Log.d(TAG, "connect(): " + device);
249         }
250 
251         if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
252             Log.e(TAG, "Cannot connect to " + device + " : PRIORITY_OFF");
253             return false;
254         }
255         if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
256                                          BluetoothUuid.AudioSink)) {
257             Log.e(TAG, "Cannot connect to " + device + " : Remote does not have A2DP Sink UUID");
258             return false;
259         }
260 
261         synchronized (mStateMachines) {
262             if (!connectionAllowedCheckMaxDevices(device)) {
263                 Log.e(TAG, "Cannot connect to " + device + " : too many connected devices");
264                 return false;
265             }
266             A2dpStateMachine smConnect = getOrCreateStateMachine(device);
267             if (smConnect == null) {
268                 Log.e(TAG, "Cannot connect to " + device + " : no state machine");
269                 return false;
270             }
271             smConnect.sendMessage(A2dpStateMachine.CONNECT);
272             return true;
273         }
274     }
275 
disconnect(BluetoothDevice device)276     boolean disconnect(BluetoothDevice device) {
277         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
278         if (DBG) {
279             Log.d(TAG, "disconnect(): " + device);
280         }
281 
282         synchronized (mStateMachines) {
283             A2dpStateMachine sm = mStateMachines.get(device);
284             if (sm == null) {
285                 Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine");
286                 return false;
287             }
288             sm.sendMessage(A2dpStateMachine.DISCONNECT);
289             return true;
290         }
291     }
292 
getConnectedDevices()293     public List<BluetoothDevice> getConnectedDevices() {
294         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
295         synchronized (mStateMachines) {
296             List<BluetoothDevice> devices = new ArrayList<>();
297             for (A2dpStateMachine sm : mStateMachines.values()) {
298                 if (sm.isConnected()) {
299                     devices.add(sm.getDevice());
300                 }
301             }
302             return devices;
303         }
304     }
305 
306     /**
307      * Check whether can connect to a peer device.
308      * The check considers the maximum number of connected peers.
309      *
310      * @param device the peer device to connect to
311      * @return true if connection is allowed, otherwise false
312      */
connectionAllowedCheckMaxDevices(BluetoothDevice device)313     private boolean connectionAllowedCheckMaxDevices(BluetoothDevice device) {
314         int connected = 0;
315         // Count devices that are in the process of connecting or already connected
316         synchronized (mStateMachines) {
317             for (A2dpStateMachine sm : mStateMachines.values()) {
318                 switch (sm.getConnectionState()) {
319                     case BluetoothProfile.STATE_CONNECTING:
320                     case BluetoothProfile.STATE_CONNECTED:
321                         if (Objects.equals(device, sm.getDevice())) {
322                             return true;    // Already connected or accounted for
323                         }
324                         connected++;
325                         break;
326                     default:
327                         break;
328                 }
329             }
330         }
331         return (connected < mMaxConnectedAudioDevices);
332     }
333 
334     /**
335      * Check whether can connect to a peer device.
336      * The check considers a number of factors during the evaluation.
337      *
338      * @param device the peer device to connect to
339      * @param isOutgoingRequest if true, the check is for outgoing connection
340      * request, otherwise is for incoming connection request
341      * @return true if connection is allowed, otherwise false
342      */
343     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
okToConnect(BluetoothDevice device, boolean isOutgoingRequest)344     public boolean okToConnect(BluetoothDevice device, boolean isOutgoingRequest) {
345         Log.i(TAG, "okToConnect: device " + device + " isOutgoingRequest: " + isOutgoingRequest);
346         // Check if this is an incoming connection in Quiet mode.
347         if (mAdapterService.isQuietModeEnabled() && !isOutgoingRequest) {
348             Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
349             return false;
350         }
351         // Check if too many devices
352         if (!connectionAllowedCheckMaxDevices(device)) {
353             Log.e(TAG, "okToConnect: cannot connect to " + device
354                     + " : too many connected devices");
355             return false;
356         }
357         // Check priority and accept or reject the connection.
358         int priority = getPriority(device);
359         int bondState = mAdapterService.getBondState(device);
360         // Allow this connection only if the device is bonded. Any attempt to connect while
361         // bonding would potentially lead to an unauthorized connection.
362         if (bondState != BluetoothDevice.BOND_BONDED) {
363             Log.w(TAG, "okToConnect: return false, bondState=" + bondState);
364             return false;
365         } else if (priority != BluetoothProfile.PRIORITY_UNDEFINED
366                 && priority != BluetoothProfile.PRIORITY_ON
367                 && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
368             // Otherwise, reject the connection if priority is not valid.
369             Log.w(TAG, "okToConnect: return false, priority=" + priority);
370             return false;
371         }
372         return true;
373     }
374 
getDevicesMatchingConnectionStates(int[] states)375     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
376         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
377         List<BluetoothDevice> devices = new ArrayList<>();
378         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
379         synchronized (mStateMachines) {
380             for (BluetoothDevice device : bondedDevices) {
381                 if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
382                                                  BluetoothUuid.AudioSink)) {
383                     continue;
384                 }
385                 int connectionState = BluetoothProfile.STATE_DISCONNECTED;
386                 A2dpStateMachine sm = mStateMachines.get(device);
387                 if (sm != null) {
388                     connectionState = sm.getConnectionState();
389                 }
390                 for (int i = 0; i < states.length; i++) {
391                     if (connectionState == states[i]) {
392                         devices.add(device);
393                     }
394                 }
395             }
396             return devices;
397         }
398     }
399 
400     /**
401      * Get the list of devices that have state machines.
402      *
403      * @return the list of devices that have state machines
404      */
405     @VisibleForTesting
getDevices()406     List<BluetoothDevice> getDevices() {
407         List<BluetoothDevice> devices = new ArrayList<>();
408         synchronized (mStateMachines) {
409             for (A2dpStateMachine sm : mStateMachines.values()) {
410                 devices.add(sm.getDevice());
411             }
412             return devices;
413         }
414     }
415 
getConnectionState(BluetoothDevice device)416     public int getConnectionState(BluetoothDevice device) {
417         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
418         synchronized (mStateMachines) {
419             A2dpStateMachine sm = mStateMachines.get(device);
420             if (sm == null) {
421                 return BluetoothProfile.STATE_DISCONNECTED;
422             }
423             return sm.getConnectionState();
424         }
425     }
426 
removeActiveDevice(boolean forceStopPlayingAudio)427     private void removeActiveDevice(boolean forceStopPlayingAudio) {
428         BluetoothDevice previousActiveDevice = mActiveDevice;
429         synchronized (mStateMachines) {
430             // Clear the active device
431             mActiveDevice = null;
432             // This needs to happen before we inform the audio manager that the device
433             // disconnected. Please see comment in broadcastActiveDevice() for why.
434             broadcastActiveDevice(null);
435 
436             if (previousActiveDevice == null) {
437                 return;
438             }
439 
440             // Make sure the Audio Manager knows the previous Active device is disconnected.
441             // However, if A2DP is still connected and not forcing stop audio for that remote
442             // device, the user has explicitly switched the output to the local device and music
443             // should continue playing. Otherwise, the remote device has been indeed disconnected
444             // and audio should be suspended before switching the output to the local device.
445             boolean suppressNoisyIntent = !forceStopPlayingAudio
446                     && (getConnectionState(previousActiveDevice)
447                     == BluetoothProfile.STATE_CONNECTED);
448             Log.i(TAG, "removeActiveDevice: suppressNoisyIntent=" + suppressNoisyIntent);
449             mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
450                     previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
451                     BluetoothProfile.A2DP, suppressNoisyIntent, -1);
452             // Make sure the Active device in native layer is set to null and audio is off
453             if (!mA2dpNativeInterface.setActiveDevice(null)) {
454                 Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native "
455                         + "layer");
456             }
457         }
458     }
459 
460     /**
461      * Set the active device.
462      *
463      * @param device the active device
464      * @return true on success, otherwise false
465      */
setActiveDevice(BluetoothDevice device)466     public boolean setActiveDevice(BluetoothDevice device) {
467         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
468         synchronized (mStateMachines) {
469             BluetoothDevice previousActiveDevice = mActiveDevice;
470             if (DBG) {
471                 Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
472             }
473 
474             if (previousActiveDevice != null && AvrcpTargetService.get() != null) {
475                 AvrcpTargetService.get().storeVolumeForDevice(previousActiveDevice);
476             }
477 
478             if (device == null) {
479                 // Remove active device and continue playing audio only if necessary.
480                 removeActiveDevice(false);
481                 return true;
482             }
483 
484             BluetoothCodecStatus codecStatus = null;
485             A2dpStateMachine sm = mStateMachines.get(device);
486             if (sm == null) {
487                 Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
488                           + "no state machine");
489                 return false;
490             }
491             if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
492                 Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
493                           + "device is not connected");
494                 return false;
495             }
496             if (!mA2dpNativeInterface.setActiveDevice(device)) {
497                 Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer");
498                 return false;
499             }
500             codecStatus = sm.getCodecStatus();
501 
502             boolean deviceChanged = !Objects.equals(device, mActiveDevice);
503             mActiveDevice = device;
504             // This needs to happen before we inform the audio manager that the device
505             // disconnected. Please see comment in broadcastActiveDevice() for why.
506             broadcastActiveDevice(mActiveDevice);
507             if (deviceChanged) {
508                 // Send an intent with the active device codec config
509                 if (codecStatus != null) {
510                     broadcastCodecConfig(mActiveDevice, codecStatus);
511                 }
512                 // Make sure the Audio Manager knows the previous Active device is disconnected,
513                 // and the new Active device is connected.
514                 if (previousActiveDevice != null) {
515                     mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
516                             previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
517                             BluetoothProfile.A2DP, true, -1);
518                 }
519 
520                 int rememberedVolume = -1;
521                 if (AvrcpTargetService.get() != null) {
522                     AvrcpTargetService.get().volumeDeviceSwitched(mActiveDevice);
523 
524                     rememberedVolume = AvrcpTargetService.get()
525                             .getRememberedVolumeForDevice(mActiveDevice);
526                 }
527 
528                 mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
529                         mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
530                         true, rememberedVolume);
531 
532                 // Inform the Audio Service about the codec configuration
533                 // change, so the Audio Service can reset accordingly the audio
534                 // feeding parameters in the Audio HAL to the Bluetooth stack.
535                 mAudioManager.handleBluetoothA2dpDeviceConfigChange(mActiveDevice);
536             }
537         }
538         return true;
539     }
540 
541     /**
542      * Get the active device.
543      *
544      * @return the active device or null if no device is active
545      */
getActiveDevice()546     public BluetoothDevice getActiveDevice() {
547         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
548         synchronized (mStateMachines) {
549             return mActiveDevice;
550         }
551     }
552 
isActiveDevice(BluetoothDevice device)553     private boolean isActiveDevice(BluetoothDevice device) {
554         synchronized (mStateMachines) {
555             return (device != null) && Objects.equals(device, mActiveDevice);
556         }
557     }
558 
setPriority(BluetoothDevice device, int priority)559     public boolean setPriority(BluetoothDevice device, int priority) {
560         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
561         Settings.Global.putInt(getContentResolver(),
562                 Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
563         if (DBG) {
564             Log.d(TAG, "Saved priority " + device + " = " + priority);
565         }
566         return true;
567     }
568 
getPriority(BluetoothDevice device)569     public int getPriority(BluetoothDevice device) {
570         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
571         int priority = Settings.Global.getInt(getContentResolver(),
572                 Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
573                 BluetoothProfile.PRIORITY_UNDEFINED);
574         return priority;
575     }
576 
577     /* Absolute volume implementation */
isAvrcpAbsoluteVolumeSupported()578     public boolean isAvrcpAbsoluteVolumeSupported() {
579         return mAvrcp.isAbsoluteVolumeSupported();
580     }
581 
setAvrcpAbsoluteVolume(int volume)582     public void setAvrcpAbsoluteVolume(int volume) {
583         // TODO (apanicke): Instead of using A2DP as a middleman for volume changes, add a binder
584         // service to the new AVRCP Profile and have the audio manager use that instead.
585         if (AvrcpTargetService.get() != null) {
586             AvrcpTargetService.get().sendVolumeChanged(volume);
587             return;
588         }
589 
590         mAvrcp.setAbsoluteVolume(volume);
591     }
592 
setAvrcpAudioState(int state)593     public void setAvrcpAudioState(int state) {
594         mAvrcp.setA2dpAudioState(state);
595     }
596 
resetAvrcpBlacklist(BluetoothDevice device)597     public void resetAvrcpBlacklist(BluetoothDevice device) {
598         if (mAvrcp != null) {
599             mAvrcp.resetBlackList(device.getAddress());
600         }
601     }
602 
isA2dpPlaying(BluetoothDevice device)603     boolean isA2dpPlaying(BluetoothDevice device) {
604         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
605         if (DBG) {
606             Log.d(TAG, "isA2dpPlaying(" + device + ")");
607         }
608         synchronized (mStateMachines) {
609             A2dpStateMachine sm = mStateMachines.get(device);
610             if (sm == null) {
611                 return false;
612             }
613             return sm.isPlaying();
614         }
615     }
616 
617     /**
618      * Gets the current codec status (configuration and capability).
619      *
620      * @param device the remote Bluetooth device. If null, use the currect
621      * active A2DP Bluetooth device.
622      * @return the current codec status
623      * @hide
624      */
getCodecStatus(BluetoothDevice device)625     public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
626         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
627         if (DBG) {
628             Log.d(TAG, "getCodecStatus(" + device + ")");
629         }
630         synchronized (mStateMachines) {
631             if (device == null) {
632                 device = mActiveDevice;
633             }
634             if (device == null) {
635                 return null;
636             }
637             A2dpStateMachine sm = mStateMachines.get(device);
638             if (sm != null) {
639                 return sm.getCodecStatus();
640             }
641             return null;
642         }
643     }
644 
645     /**
646      * Sets the codec configuration preference.
647      *
648      * @param device the remote Bluetooth device. If null, use the currect
649      * active A2DP Bluetooth device.
650      * @param codecConfig the codec configuration preference
651      * @hide
652      */
setCodecConfigPreference(BluetoothDevice device, BluetoothCodecConfig codecConfig)653     public void setCodecConfigPreference(BluetoothDevice device,
654                                          BluetoothCodecConfig codecConfig) {
655         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
656         if (DBG) {
657             Log.d(TAG, "setCodecConfigPreference(" + device + "): "
658                     + Objects.toString(codecConfig));
659         }
660         if (device == null) {
661             device = mActiveDevice;
662         }
663         if (device == null) {
664             Log.e(TAG, "Cannot set codec config preference: no active A2DP device");
665             return;
666         }
667         mA2dpCodecConfig.setCodecConfigPreference(device, codecConfig);
668     }
669 
670     /**
671      * Enables the optional codecs.
672      *
673      * @param device the remote Bluetooth device. If null, use the currect
674      * active A2DP Bluetooth device.
675      * @hide
676      */
enableOptionalCodecs(BluetoothDevice device)677     public void enableOptionalCodecs(BluetoothDevice device) {
678         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
679         if (DBG) {
680             Log.d(TAG, "enableOptionalCodecs(" + device + ")");
681         }
682         if (device == null) {
683             device = mActiveDevice;
684         }
685         if (device == null) {
686             Log.e(TAG, "Cannot enable optional codecs: no active A2DP device");
687             return;
688         }
689         mA2dpCodecConfig.enableOptionalCodecs(device);
690     }
691 
692     /**
693      * Disables the optional codecs.
694      *
695      * @param device the remote Bluetooth device. If null, use the currect
696      * active A2DP Bluetooth device.
697      * @hide
698      */
disableOptionalCodecs(BluetoothDevice device)699     public void disableOptionalCodecs(BluetoothDevice device) {
700         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
701         if (DBG) {
702             Log.d(TAG, "disableOptionalCodecs(" + device + ")");
703         }
704         if (device == null) {
705             device = mActiveDevice;
706         }
707         if (device == null) {
708             Log.e(TAG, "Cannot disable optional codecs: no active A2DP device");
709             return;
710         }
711         mA2dpCodecConfig.disableOptionalCodecs(device);
712     }
713 
getSupportsOptionalCodecs(BluetoothDevice device)714     public int getSupportsOptionalCodecs(BluetoothDevice device) {
715         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
716         int support = Settings.Global.getInt(getContentResolver(),
717                 Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
718                 BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
719         return support;
720     }
721 
setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport)722     public void setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport) {
723         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
724         int value = doesSupport ? BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED
725                 : BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
726         Settings.Global.putInt(getContentResolver(),
727                 Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
728                 value);
729     }
730 
getOptionalCodecsEnabled(BluetoothDevice device)731     public int getOptionalCodecsEnabled(BluetoothDevice device) {
732         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
733         return Settings.Global.getInt(getContentResolver(),
734                 Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
735                 BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
736     }
737 
setOptionalCodecsEnabled(BluetoothDevice device, int value)738     public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
739         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
740         if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
741                 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
742                 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
743             Log.w(TAG, "Unexpected value passed to setOptionalCodecsEnabled:" + value);
744             return;
745         }
746         Settings.Global.putInt(getContentResolver(),
747                 Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
748                 value);
749     }
750 
751     // Handle messages from native (JNI) to Java
messageFromNative(A2dpStackEvent stackEvent)752     void messageFromNative(A2dpStackEvent stackEvent) {
753         Objects.requireNonNull(stackEvent.device,
754                                "Device should never be null, event: " + stackEvent);
755         synchronized (mStateMachines) {
756             BluetoothDevice device = stackEvent.device;
757             A2dpStateMachine sm = mStateMachines.get(device);
758             if (sm == null) {
759                 if (stackEvent.type == A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
760                     switch (stackEvent.valueInt) {
761                         case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
762                         case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
763                             // Create a new state machine only when connecting to a device
764                             if (!connectionAllowedCheckMaxDevices(device)) {
765                                 Log.e(TAG, "Cannot connect to " + device
766                                         + " : too many connected devices");
767                                 return;
768                             }
769                             sm = getOrCreateStateMachine(device);
770                             break;
771                         default:
772                             break;
773                     }
774                 }
775             }
776             if (sm == null) {
777                 Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
778                 return;
779             }
780             sm.sendMessage(A2dpStateMachine.STACK_EVENT, stackEvent);
781         }
782     }
783 
784     /**
785      * The codec configuration for a device has been updated.
786      *
787      * @param device the remote device
788      * @param codecStatus the new codec status
789      * @param sameAudioFeedingParameters if true the audio feeding parameters
790      * haven't been changed
791      */
codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus, boolean sameAudioFeedingParameters)792     void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
793                             boolean sameAudioFeedingParameters) {
794         broadcastCodecConfig(device, codecStatus);
795 
796         // Inform the Audio Service about the codec configuration change,
797         // so the Audio Service can reset accordingly the audio feeding
798         // parameters in the Audio HAL to the Bluetooth stack.
799         if (isActiveDevice(device) && !sameAudioFeedingParameters) {
800             mAudioManager.handleBluetoothA2dpDeviceConfigChange(device);
801         }
802     }
803 
getOrCreateStateMachine(BluetoothDevice device)804     private A2dpStateMachine getOrCreateStateMachine(BluetoothDevice device) {
805         if (device == null) {
806             Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
807             return null;
808         }
809         synchronized (mStateMachines) {
810             A2dpStateMachine sm = mStateMachines.get(device);
811             if (sm != null) {
812                 return sm;
813             }
814             // Limit the maximum number of state machines to avoid DoS attack
815             if (mStateMachines.size() >= MAX_A2DP_STATE_MACHINES) {
816                 Log.e(TAG, "Maximum number of A2DP state machines reached: "
817                         + MAX_A2DP_STATE_MACHINES);
818                 return null;
819             }
820             if (DBG) {
821                 Log.d(TAG, "Creating a new state machine for " + device);
822             }
823             sm = A2dpStateMachine.make(device, this, mA2dpNativeInterface,
824                                        mStateMachinesThread.getLooper());
825             mStateMachines.put(device, sm);
826             return sm;
827         }
828     }
829 
broadcastActiveDevice(BluetoothDevice device)830     private void broadcastActiveDevice(BluetoothDevice device) {
831         if (DBG) {
832             Log.d(TAG, "broadcastActiveDevice(" + device + ")");
833         }
834 
835         Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
836         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
837         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
838                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
839         sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
840     }
841 
broadcastCodecConfig(BluetoothDevice device, BluetoothCodecStatus codecStatus)842     private void broadcastCodecConfig(BluetoothDevice device, BluetoothCodecStatus codecStatus) {
843         if (DBG) {
844             Log.d(TAG, "broadcastCodecConfig(" + device + "): " + codecStatus);
845         }
846         Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
847         intent.putExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS, codecStatus);
848         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
849         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
850                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
851         sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
852     }
853 
854     private class BondStateChangedReceiver extends BroadcastReceiver {
855         @Override
onReceive(Context context, Intent intent)856         public void onReceive(Context context, Intent intent) {
857             if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
858                 return;
859             }
860             int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
861                                            BluetoothDevice.ERROR);
862             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
863             Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
864             bondStateChanged(device, state);
865         }
866     }
867 
868     /**
869      * Process a change in the bonding state for a device.
870      *
871      * @param device the device whose bonding state has changed
872      * @param bondState the new bond state for the device. Possible values are:
873      * {@link BluetoothDevice#BOND_NONE},
874      * {@link BluetoothDevice#BOND_BONDING},
875      * {@link BluetoothDevice#BOND_BONDED}.
876      */
877     @VisibleForTesting
bondStateChanged(BluetoothDevice device, int bondState)878     void bondStateChanged(BluetoothDevice device, int bondState) {
879         if (DBG) {
880             Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState);
881         }
882         // Remove state machine if the bonding for a device is removed
883         if (bondState != BluetoothDevice.BOND_NONE) {
884             return;
885         }
886         synchronized (mStateMachines) {
887             A2dpStateMachine sm = mStateMachines.get(device);
888             if (sm == null) {
889                 return;
890             }
891             if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
892                 return;
893             }
894             removeStateMachine(device);
895         }
896     }
897 
removeStateMachine(BluetoothDevice device)898     private void removeStateMachine(BluetoothDevice device) {
899         synchronized (mStateMachines) {
900             A2dpStateMachine sm = mStateMachines.get(device);
901             if (sm == null) {
902                 Log.w(TAG, "removeStateMachine: device " + device
903                         + " does not have a state machine");
904                 return;
905             }
906             Log.i(TAG, "removeStateMachine: removing state machine for device: " + device);
907             sm.doQuit();
908             sm.cleanup();
909             mStateMachines.remove(device);
910         }
911     }
912 
updateOptionalCodecsSupport(BluetoothDevice device)913     private void updateOptionalCodecsSupport(BluetoothDevice device) {
914         int previousSupport = getSupportsOptionalCodecs(device);
915         boolean supportsOptional = false;
916 
917         synchronized (mStateMachines) {
918             A2dpStateMachine sm = mStateMachines.get(device);
919             if (sm == null) {
920                 return;
921             }
922             BluetoothCodecStatus codecStatus = sm.getCodecStatus();
923             if (codecStatus != null) {
924                 for (BluetoothCodecConfig config : codecStatus.getCodecsSelectableCapabilities()) {
925                     if (!config.isMandatoryCodec()) {
926                         supportsOptional = true;
927                         break;
928                     }
929                 }
930             }
931         }
932         if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
933                 || supportsOptional != (previousSupport
934                                     == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
935             setSupportsOptionalCodecs(device, supportsOptional);
936         }
937         if (supportsOptional) {
938             int enabled = getOptionalCodecsEnabled(device);
939             if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
940                 enableOptionalCodecs(device);
941             } else if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED) {
942                 disableOptionalCodecs(device);
943             }
944         }
945     }
946 
connectionStateChanged(BluetoothDevice device, int fromState, int toState)947     private void connectionStateChanged(BluetoothDevice device, int fromState, int toState) {
948         if ((device == null) || (fromState == toState)) {
949             return;
950         }
951         synchronized (mStateMachines) {
952             if (toState == BluetoothProfile.STATE_CONNECTED) {
953                 // Each time a device connects, we want to re-check if it supports optional
954                 // codecs (perhaps it's had a firmware update, etc.) and save that state if
955                 // it differs from what we had saved before.
956                 updateOptionalCodecsSupport(device);
957                 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
958             }
959             // Set the active device if only one connected device is supported and it was connected
960             if (toState == BluetoothProfile.STATE_CONNECTED && (mMaxConnectedAudioDevices == 1)) {
961                 setActiveDevice(device);
962             }
963             // Check if the active device is not connected anymore
964             if (isActiveDevice(device) && (fromState == BluetoothProfile.STATE_CONNECTED)) {
965                 setActiveDevice(null);
966             }
967             // Check if the device is disconnected - if unbond, remove the state machine
968             if (toState == BluetoothProfile.STATE_DISCONNECTED) {
969                 int bondState = mAdapterService.getBondState(device);
970                 if (bondState == BluetoothDevice.BOND_NONE) {
971                     removeStateMachine(device);
972                 }
973             }
974         }
975     }
976 
977     /**
978      * Receiver for processing device connection state changes.
979      *
980      * <ul>
981      * <li> Update codec support per device when device is (re)connected
982      * <li> Delete the state machine instance if the device is disconnected and unbond
983      * </ul>
984      */
985     private class ConnectionStateChangedReceiver extends BroadcastReceiver {
986         @Override
onReceive(Context context, Intent intent)987         public void onReceive(Context context, Intent intent) {
988             if (!BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
989                 return;
990             }
991             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
992             int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
993             int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
994             connectionStateChanged(device, fromState, toState);
995         }
996     }
997 
998     /**
999      * Binder object: must be a static class or memory leak may occur.
1000      */
1001     @VisibleForTesting
1002     static class BluetoothA2dpBinder extends IBluetoothA2dp.Stub
1003             implements IProfileServiceBinder {
1004         private A2dpService mService;
1005 
getService()1006         private A2dpService getService() {
1007             if (!Utils.checkCaller()) {
1008                 Log.w(TAG, "A2DP call not allowed for non-active user");
1009                 return null;
1010             }
1011 
1012             if (mService != null && mService.isAvailable()) {
1013                 return mService;
1014             }
1015             return null;
1016         }
1017 
BluetoothA2dpBinder(A2dpService svc)1018         BluetoothA2dpBinder(A2dpService svc) {
1019             mService = svc;
1020         }
1021 
1022         @Override
cleanup()1023         public void cleanup() {
1024             mService = null;
1025         }
1026 
1027         @Override
connect(BluetoothDevice device)1028         public boolean connect(BluetoothDevice device) {
1029             A2dpService service = getService();
1030             if (service == null) {
1031                 return false;
1032             }
1033             return service.connect(device);
1034         }
1035 
1036         @Override
disconnect(BluetoothDevice device)1037         public boolean disconnect(BluetoothDevice device) {
1038             A2dpService service = getService();
1039             if (service == null) {
1040                 return false;
1041             }
1042             return service.disconnect(device);
1043         }
1044 
1045         @Override
getConnectedDevices()1046         public List<BluetoothDevice> getConnectedDevices() {
1047             A2dpService service = getService();
1048             if (service == null) {
1049                 return new ArrayList<>(0);
1050             }
1051             return service.getConnectedDevices();
1052         }
1053 
1054         @Override
getDevicesMatchingConnectionStates(int[] states)1055         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
1056             A2dpService service = getService();
1057             if (service == null) {
1058                 return new ArrayList<>(0);
1059             }
1060             return service.getDevicesMatchingConnectionStates(states);
1061         }
1062 
1063         @Override
getConnectionState(BluetoothDevice device)1064         public int getConnectionState(BluetoothDevice device) {
1065             A2dpService service = getService();
1066             if (service == null) {
1067                 return BluetoothProfile.STATE_DISCONNECTED;
1068             }
1069             return service.getConnectionState(device);
1070         }
1071 
1072         @Override
setActiveDevice(BluetoothDevice device)1073         public boolean setActiveDevice(BluetoothDevice device) {
1074             A2dpService service = getService();
1075             if (service == null) {
1076                 return false;
1077             }
1078             return service.setActiveDevice(device);
1079         }
1080 
1081         @Override
getActiveDevice()1082         public BluetoothDevice getActiveDevice() {
1083             A2dpService service = getService();
1084             if (service == null) {
1085                 return null;
1086             }
1087             return service.getActiveDevice();
1088         }
1089 
1090         @Override
setPriority(BluetoothDevice device, int priority)1091         public boolean setPriority(BluetoothDevice device, int priority) {
1092             A2dpService service = getService();
1093             if (service == null) {
1094                 return false;
1095             }
1096             return service.setPriority(device, priority);
1097         }
1098 
1099         @Override
getPriority(BluetoothDevice device)1100         public int getPriority(BluetoothDevice device) {
1101             A2dpService service = getService();
1102             if (service == null) {
1103                 return BluetoothProfile.PRIORITY_UNDEFINED;
1104             }
1105             return service.getPriority(device);
1106         }
1107 
1108         @Override
isAvrcpAbsoluteVolumeSupported()1109         public boolean isAvrcpAbsoluteVolumeSupported() {
1110             A2dpService service = getService();
1111             if (service == null) {
1112                 return false;
1113             }
1114             return service.isAvrcpAbsoluteVolumeSupported();
1115         }
1116 
1117         @Override
setAvrcpAbsoluteVolume(int volume)1118         public void setAvrcpAbsoluteVolume(int volume) {
1119             A2dpService service = getService();
1120             if (service == null) {
1121                 return;
1122             }
1123             service.setAvrcpAbsoluteVolume(volume);
1124         }
1125 
1126         @Override
isA2dpPlaying(BluetoothDevice device)1127         public boolean isA2dpPlaying(BluetoothDevice device) {
1128             A2dpService service = getService();
1129             if (service == null) {
1130                 return false;
1131             }
1132             return service.isA2dpPlaying(device);
1133         }
1134 
1135         @Override
getCodecStatus(BluetoothDevice device)1136         public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
1137             A2dpService service = getService();
1138             if (service == null) {
1139                 return null;
1140             }
1141             return service.getCodecStatus(device);
1142         }
1143 
1144         @Override
setCodecConfigPreference(BluetoothDevice device, BluetoothCodecConfig codecConfig)1145         public void setCodecConfigPreference(BluetoothDevice device,
1146                                              BluetoothCodecConfig codecConfig) {
1147             A2dpService service = getService();
1148             if (service == null) {
1149                 return;
1150             }
1151             service.setCodecConfigPreference(device, codecConfig);
1152         }
1153 
1154         @Override
enableOptionalCodecs(BluetoothDevice device)1155         public void enableOptionalCodecs(BluetoothDevice device) {
1156             A2dpService service = getService();
1157             if (service == null) {
1158                 return;
1159             }
1160             service.enableOptionalCodecs(device);
1161         }
1162 
1163         @Override
disableOptionalCodecs(BluetoothDevice device)1164         public void disableOptionalCodecs(BluetoothDevice device) {
1165             A2dpService service = getService();
1166             if (service == null) {
1167                 return;
1168             }
1169             service.disableOptionalCodecs(device);
1170         }
1171 
supportsOptionalCodecs(BluetoothDevice device)1172         public int supportsOptionalCodecs(BluetoothDevice device) {
1173             A2dpService service = getService();
1174             if (service == null) {
1175                 return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
1176             }
1177             return service.getSupportsOptionalCodecs(device);
1178         }
1179 
getOptionalCodecsEnabled(BluetoothDevice device)1180         public int getOptionalCodecsEnabled(BluetoothDevice device) {
1181             A2dpService service = getService();
1182             if (service == null) {
1183                 return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
1184             }
1185             return service.getOptionalCodecsEnabled(device);
1186         }
1187 
setOptionalCodecsEnabled(BluetoothDevice device, int value)1188         public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
1189             A2dpService service = getService();
1190             if (service == null) {
1191                 return;
1192             }
1193             service.setOptionalCodecsEnabled(device, value);
1194         }
1195     }
1196 
1197     @Override
dump(StringBuilder sb)1198     public void dump(StringBuilder sb) {
1199         super.dump(sb);
1200         ProfileService.println(sb, "mActiveDevice: " + mActiveDevice);
1201         for (A2dpStateMachine sm : mStateMachines.values()) {
1202             sm.dump(sb);
1203         }
1204         if (mAvrcp != null) {
1205             mAvrcp.dump(sb);
1206         }
1207     }
1208 }
1209