Home | History | Annotate | Download | only in a2dp
      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
     89     protected IProfileServiceBinder initBinder() {
     90         return new BluetoothA2dpBinder(this);
     91     }
     92 
     93     @Override
     94     protected void create() {
     95         Log.i(TAG, "create()");
     96     }
     97 
     98     @Override
     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
    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
    222     protected void cleanup() {
    223         Log.i(TAG, "cleanup()");
    224     }
    225 
    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 
    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 
    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 
    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 
    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      */
    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)
    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         // Note: Logic can be simplified, but keeping it this way for readability
    359         int priority = getPriority(device);
    360         int bondState = mAdapterService.getBondState(device);
    361         // If priority is undefined, it is likely that service discovery has not completed and peer
    362         // initiated the connection. Allow this connection only if the device is bonded or bonding
    363         boolean serviceDiscoveryPending = (priority == BluetoothProfile.PRIORITY_UNDEFINED)
    364                 && (bondState == BluetoothDevice.BOND_BONDING
    365                 || bondState == BluetoothDevice.BOND_BONDED);
    366         // Also allow connection when device is bonded/bonding and priority is ON/AUTO_CONNECT.
    367         boolean isEnabled = (priority == BluetoothProfile.PRIORITY_ON
    368                 || priority == BluetoothProfile.PRIORITY_AUTO_CONNECT)
    369                 && (bondState == BluetoothDevice.BOND_BONDED
    370                 || bondState == BluetoothDevice.BOND_BONDING);
    371         if (!serviceDiscoveryPending && !isEnabled) {
    372             // Otherwise, reject the connection if no service discovery is pending and priority is
    373             // neither PRIORITY_ON nor PRIORITY_AUTO_CONNECT
    374             Log.w(TAG, "okToConnect: return false, priority=" + priority + ", bondState="
    375                     + bondState);
    376             return false;
    377         }
    378         return true;
    379     }
    380 
    381     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    382         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    383         List<BluetoothDevice> devices = new ArrayList<>();
    384         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
    385         synchronized (mStateMachines) {
    386             for (BluetoothDevice device : bondedDevices) {
    387                 if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
    388                                                  BluetoothUuid.AudioSink)) {
    389                     continue;
    390                 }
    391                 int connectionState = BluetoothProfile.STATE_DISCONNECTED;
    392                 A2dpStateMachine sm = mStateMachines.get(device);
    393                 if (sm != null) {
    394                     connectionState = sm.getConnectionState();
    395                 }
    396                 for (int i = 0; i < states.length; i++) {
    397                     if (connectionState == states[i]) {
    398                         devices.add(device);
    399                     }
    400                 }
    401             }
    402             return devices;
    403         }
    404     }
    405 
    406     /**
    407      * Get the list of devices that have state machines.
    408      *
    409      * @return the list of devices that have state machines
    410      */
    411     @VisibleForTesting
    412     List<BluetoothDevice> getDevices() {
    413         List<BluetoothDevice> devices = new ArrayList<>();
    414         synchronized (mStateMachines) {
    415             for (A2dpStateMachine sm : mStateMachines.values()) {
    416                 devices.add(sm.getDevice());
    417             }
    418             return devices;
    419         }
    420     }
    421 
    422     public int getConnectionState(BluetoothDevice device) {
    423         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    424         synchronized (mStateMachines) {
    425             A2dpStateMachine sm = mStateMachines.get(device);
    426             if (sm == null) {
    427                 return BluetoothProfile.STATE_DISCONNECTED;
    428             }
    429             return sm.getConnectionState();
    430         }
    431     }
    432 
    433     private void removeActiveDevice(boolean forceStopPlayingAudio) {
    434         BluetoothDevice previousActiveDevice = mActiveDevice;
    435         synchronized (mStateMachines) {
    436             // Clear the active device
    437             mActiveDevice = null;
    438             // This needs to happen before we inform the audio manager that the device
    439             // disconnected. Please see comment in broadcastActiveDevice() for why.
    440             broadcastActiveDevice(null);
    441 
    442             if (previousActiveDevice == null) {
    443                 return;
    444             }
    445 
    446             // Make sure the Audio Manager knows the previous Active device is disconnected.
    447             // However, if A2DP is still connected and not forcing stop audio for that remote
    448             // device, the user has explicitly switched the output to the local device and music
    449             // should continue playing. Otherwise, the remote device has been indeed disconnected
    450             // and audio should be suspended before switching the output to the local device.
    451             boolean suppressNoisyIntent = !forceStopPlayingAudio
    452                     && (getConnectionState(previousActiveDevice)
    453                     == BluetoothProfile.STATE_CONNECTED);
    454             Log.i(TAG, "removeActiveDevice: suppressNoisyIntent=" + suppressNoisyIntent);
    455             mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
    456                     previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
    457                     BluetoothProfile.A2DP, suppressNoisyIntent, -1);
    458             // Make sure the Active device in native layer is set to null and audio is off
    459             if (!mA2dpNativeInterface.setActiveDevice(null)) {
    460                 Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native "
    461                         + "layer");
    462             }
    463         }
    464     }
    465 
    466     /**
    467      * Set the active device.
    468      *
    469      * @param device the active device
    470      * @return true on success, otherwise false
    471      */
    472     public boolean setActiveDevice(BluetoothDevice device) {
    473         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
    474         synchronized (mStateMachines) {
    475             BluetoothDevice previousActiveDevice = mActiveDevice;
    476             if (DBG) {
    477                 Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
    478             }
    479 
    480             if (previousActiveDevice != null && AvrcpTargetService.get() != null) {
    481                 AvrcpTargetService.get().storeVolumeForDevice(previousActiveDevice);
    482             }
    483 
    484             if (device == null) {
    485                 // Remove active device and continue playing audio only if necessary.
    486                 removeActiveDevice(false);
    487                 return true;
    488             }
    489 
    490             BluetoothCodecStatus codecStatus = null;
    491             A2dpStateMachine sm = mStateMachines.get(device);
    492             if (sm == null) {
    493                 Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
    494                           + "no state machine");
    495                 return false;
    496             }
    497             if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
    498                 Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
    499                           + "device is not connected");
    500                 return false;
    501             }
    502             if (!mA2dpNativeInterface.setActiveDevice(device)) {
    503                 Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer");
    504                 return false;
    505             }
    506             codecStatus = sm.getCodecStatus();
    507 
    508             boolean deviceChanged = !Objects.equals(device, mActiveDevice);
    509             mActiveDevice = device;
    510             // This needs to happen before we inform the audio manager that the device
    511             // disconnected. Please see comment in broadcastActiveDevice() for why.
    512             broadcastActiveDevice(mActiveDevice);
    513             if (deviceChanged) {
    514                 // Send an intent with the active device codec config
    515                 if (codecStatus != null) {
    516                     broadcastCodecConfig(mActiveDevice, codecStatus);
    517                 }
    518                 // Make sure the Audio Manager knows the previous Active device is disconnected,
    519                 // and the new Active device is connected.
    520                 if (previousActiveDevice != null) {
    521                     mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
    522                             previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
    523                             BluetoothProfile.A2DP, true, -1);
    524                 }
    525 
    526                 int rememberedVolume = -1;
    527                 if (AvrcpTargetService.get() != null) {
    528                     AvrcpTargetService.get().volumeDeviceSwitched(mActiveDevice);
    529 
    530                     rememberedVolume = AvrcpTargetService.get()
    531                             .getRememberedVolumeForDevice(mActiveDevice);
    532                 }
    533 
    534                 mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
    535                         mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
    536                         true, rememberedVolume);
    537 
    538                 // Inform the Audio Service about the codec configuration
    539                 // change, so the Audio Service can reset accordingly the audio
    540                 // feeding parameters in the Audio HAL to the Bluetooth stack.
    541                 mAudioManager.handleBluetoothA2dpDeviceConfigChange(mActiveDevice);
    542             }
    543         }
    544         return true;
    545     }
    546 
    547     /**
    548      * Get the active device.
    549      *
    550      * @return the active device or null if no device is active
    551      */
    552     public BluetoothDevice getActiveDevice() {
    553         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    554         synchronized (mStateMachines) {
    555             return mActiveDevice;
    556         }
    557     }
    558 
    559     private boolean isActiveDevice(BluetoothDevice device) {
    560         synchronized (mStateMachines) {
    561             return (device != null) && Objects.equals(device, mActiveDevice);
    562         }
    563     }
    564 
    565     public boolean setPriority(BluetoothDevice device, int priority) {
    566         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
    567         Settings.Global.putInt(getContentResolver(),
    568                 Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
    569         if (DBG) {
    570             Log.d(TAG, "Saved priority " + device + " = " + priority);
    571         }
    572         return true;
    573     }
    574 
    575     public int getPriority(BluetoothDevice device) {
    576         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
    577         int priority = Settings.Global.getInt(getContentResolver(),
    578                 Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
    579                 BluetoothProfile.PRIORITY_UNDEFINED);
    580         return priority;
    581     }
    582 
    583     /* Absolute volume implementation */
    584     public boolean isAvrcpAbsoluteVolumeSupported() {
    585         return mAvrcp.isAbsoluteVolumeSupported();
    586     }
    587 
    588     public void setAvrcpAbsoluteVolume(int volume) {
    589         // TODO (apanicke): Instead of using A2DP as a middleman for volume changes, add a binder
    590         // service to the new AVRCP Profile and have the audio manager use that instead.
    591         if (AvrcpTargetService.get() != null) {
    592             AvrcpTargetService.get().sendVolumeChanged(volume);
    593             return;
    594         }
    595 
    596         mAvrcp.setAbsoluteVolume(volume);
    597     }
    598 
    599     public void setAvrcpAudioState(int state) {
    600         mAvrcp.setA2dpAudioState(state);
    601     }
    602 
    603     public void resetAvrcpBlacklist(BluetoothDevice device) {
    604         if (mAvrcp != null) {
    605             mAvrcp.resetBlackList(device.getAddress());
    606         }
    607     }
    608 
    609     boolean isA2dpPlaying(BluetoothDevice device) {
    610         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    611         if (DBG) {
    612             Log.d(TAG, "isA2dpPlaying(" + device + ")");
    613         }
    614         synchronized (mStateMachines) {
    615             A2dpStateMachine sm = mStateMachines.get(device);
    616             if (sm == null) {
    617                 return false;
    618             }
    619             return sm.isPlaying();
    620         }
    621     }
    622 
    623     /**
    624      * Gets the current codec status (configuration and capability).
    625      *
    626      * @param device the remote Bluetooth device. If null, use the currect
    627      * active A2DP Bluetooth device.
    628      * @return the current codec status
    629      * @hide
    630      */
    631     public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
    632         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    633         if (DBG) {
    634             Log.d(TAG, "getCodecStatus(" + device + ")");
    635         }
    636         synchronized (mStateMachines) {
    637             if (device == null) {
    638                 device = mActiveDevice;
    639             }
    640             if (device == null) {
    641                 return null;
    642             }
    643             A2dpStateMachine sm = mStateMachines.get(device);
    644             if (sm != null) {
    645                 return sm.getCodecStatus();
    646             }
    647             return null;
    648         }
    649     }
    650 
    651     /**
    652      * Sets the codec configuration preference.
    653      *
    654      * @param device the remote Bluetooth device. If null, use the currect
    655      * active A2DP Bluetooth device.
    656      * @param codecConfig the codec configuration preference
    657      * @hide
    658      */
    659     public void setCodecConfigPreference(BluetoothDevice device,
    660                                          BluetoothCodecConfig codecConfig) {
    661         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    662         if (DBG) {
    663             Log.d(TAG, "setCodecConfigPreference(" + device + "): "
    664                     + Objects.toString(codecConfig));
    665         }
    666         if (device == null) {
    667             device = mActiveDevice;
    668         }
    669         if (device == null) {
    670             Log.e(TAG, "Cannot set codec config preference: no active A2DP device");
    671             return;
    672         }
    673         mA2dpCodecConfig.setCodecConfigPreference(device, codecConfig);
    674     }
    675 
    676     /**
    677      * Enables the optional codecs.
    678      *
    679      * @param device the remote Bluetooth device. If null, use the currect
    680      * active A2DP Bluetooth device.
    681      * @hide
    682      */
    683     public void enableOptionalCodecs(BluetoothDevice device) {
    684         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    685         if (DBG) {
    686             Log.d(TAG, "enableOptionalCodecs(" + device + ")");
    687         }
    688         if (device == null) {
    689             device = mActiveDevice;
    690         }
    691         if (device == null) {
    692             Log.e(TAG, "Cannot enable optional codecs: no active A2DP device");
    693             return;
    694         }
    695         mA2dpCodecConfig.enableOptionalCodecs(device);
    696     }
    697 
    698     /**
    699      * Disables the optional codecs.
    700      *
    701      * @param device the remote Bluetooth device. If null, use the currect
    702      * active A2DP Bluetooth device.
    703      * @hide
    704      */
    705     public void disableOptionalCodecs(BluetoothDevice device) {
    706         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    707         if (DBG) {
    708             Log.d(TAG, "disableOptionalCodecs(" + device + ")");
    709         }
    710         if (device == null) {
    711             device = mActiveDevice;
    712         }
    713         if (device == null) {
    714             Log.e(TAG, "Cannot disable optional codecs: no active A2DP device");
    715             return;
    716         }
    717         mA2dpCodecConfig.disableOptionalCodecs(device);
    718     }
    719 
    720     public int getSupportsOptionalCodecs(BluetoothDevice device) {
    721         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
    722         int support = Settings.Global.getInt(getContentResolver(),
    723                 Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
    724                 BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
    725         return support;
    726     }
    727 
    728     public void setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport) {
    729         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
    730         int value = doesSupport ? BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED
    731                 : BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
    732         Settings.Global.putInt(getContentResolver(),
    733                 Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
    734                 value);
    735     }
    736 
    737     public int getOptionalCodecsEnabled(BluetoothDevice device) {
    738         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
    739         return Settings.Global.getInt(getContentResolver(),
    740                 Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
    741                 BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
    742     }
    743 
    744     public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
    745         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
    746         if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
    747                 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
    748                 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
    749             Log.w(TAG, "Unexpected value passed to setOptionalCodecsEnabled:" + value);
    750             return;
    751         }
    752         Settings.Global.putInt(getContentResolver(),
    753                 Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
    754                 value);
    755     }
    756 
    757     // Handle messages from native (JNI) to Java
    758     void messageFromNative(A2dpStackEvent stackEvent) {
    759         Objects.requireNonNull(stackEvent.device,
    760                                "Device should never be null, event: " + stackEvent);
    761         synchronized (mStateMachines) {
    762             BluetoothDevice device = stackEvent.device;
    763             A2dpStateMachine sm = mStateMachines.get(device);
    764             if (sm == null) {
    765                 if (stackEvent.type == A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
    766                     switch (stackEvent.valueInt) {
    767                         case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
    768                         case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
    769                             // Create a new state machine only when connecting to a device
    770                             if (!connectionAllowedCheckMaxDevices(device)) {
    771                                 Log.e(TAG, "Cannot connect to " + device
    772                                         + " : too many connected devices");
    773                                 return;
    774                             }
    775                             sm = getOrCreateStateMachine(device);
    776                             break;
    777                         default:
    778                             break;
    779                     }
    780                 }
    781             }
    782             if (sm == null) {
    783                 Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
    784                 return;
    785             }
    786             sm.sendMessage(A2dpStateMachine.STACK_EVENT, stackEvent);
    787         }
    788     }
    789 
    790     /**
    791      * The codec configuration for a device has been updated.
    792      *
    793      * @param device the remote device
    794      * @param codecStatus the new codec status
    795      * @param sameAudioFeedingParameters if true the audio feeding parameters
    796      * haven't been changed
    797      */
    798     void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
    799                             boolean sameAudioFeedingParameters) {
    800         broadcastCodecConfig(device, codecStatus);
    801 
    802         // Inform the Audio Service about the codec configuration change,
    803         // so the Audio Service can reset accordingly the audio feeding
    804         // parameters in the Audio HAL to the Bluetooth stack.
    805         if (isActiveDevice(device) && !sameAudioFeedingParameters) {
    806             mAudioManager.handleBluetoothA2dpDeviceConfigChange(device);
    807         }
    808     }
    809 
    810     private A2dpStateMachine getOrCreateStateMachine(BluetoothDevice device) {
    811         if (device == null) {
    812             Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
    813             return null;
    814         }
    815         synchronized (mStateMachines) {
    816             A2dpStateMachine sm = mStateMachines.get(device);
    817             if (sm != null) {
    818                 return sm;
    819             }
    820             // Limit the maximum number of state machines to avoid DoS attack
    821             if (mStateMachines.size() >= MAX_A2DP_STATE_MACHINES) {
    822                 Log.e(TAG, "Maximum number of A2DP state machines reached: "
    823                         + MAX_A2DP_STATE_MACHINES);
    824                 return null;
    825             }
    826             if (DBG) {
    827                 Log.d(TAG, "Creating a new state machine for " + device);
    828             }
    829             sm = A2dpStateMachine.make(device, this, mA2dpNativeInterface,
    830                                        mStateMachinesThread.getLooper());
    831             mStateMachines.put(device, sm);
    832             return sm;
    833         }
    834     }
    835 
    836     private void broadcastActiveDevice(BluetoothDevice device) {
    837         if (DBG) {
    838             Log.d(TAG, "broadcastActiveDevice(" + device + ")");
    839         }
    840 
    841         Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
    842         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    843         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
    844                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
    845         sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
    846     }
    847 
    848     private void broadcastCodecConfig(BluetoothDevice device, BluetoothCodecStatus codecStatus) {
    849         if (DBG) {
    850             Log.d(TAG, "broadcastCodecConfig(" + device + "): " + codecStatus);
    851         }
    852         Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
    853         intent.putExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS, codecStatus);
    854         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    855         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
    856                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
    857         sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
    858     }
    859 
    860     private class BondStateChangedReceiver extends BroadcastReceiver {
    861         @Override
    862         public void onReceive(Context context, Intent intent) {
    863             if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
    864                 return;
    865             }
    866             int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
    867                                            BluetoothDevice.ERROR);
    868             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    869             Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
    870             bondStateChanged(device, state);
    871         }
    872     }
    873 
    874     /**
    875      * Process a change in the bonding state for a device.
    876      *
    877      * @param device the device whose bonding state has changed
    878      * @param bondState the new bond state for the device. Possible values are:
    879      * {@link BluetoothDevice#BOND_NONE},
    880      * {@link BluetoothDevice#BOND_BONDING},
    881      * {@link BluetoothDevice#BOND_BONDED}.
    882      */
    883     @VisibleForTesting
    884     void bondStateChanged(BluetoothDevice device, int bondState) {
    885         if (DBG) {
    886             Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState);
    887         }
    888         // Remove state machine if the bonding for a device is removed
    889         if (bondState != BluetoothDevice.BOND_NONE) {
    890             return;
    891         }
    892         synchronized (mStateMachines) {
    893             A2dpStateMachine sm = mStateMachines.get(device);
    894             if (sm == null) {
    895                 return;
    896             }
    897             if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
    898                 return;
    899             }
    900             removeStateMachine(device);
    901         }
    902     }
    903 
    904     private void removeStateMachine(BluetoothDevice device) {
    905         synchronized (mStateMachines) {
    906             A2dpStateMachine sm = mStateMachines.get(device);
    907             if (sm == null) {
    908                 Log.w(TAG, "removeStateMachine: device " + device
    909                         + " does not have a state machine");
    910                 return;
    911             }
    912             Log.i(TAG, "removeStateMachine: removing state machine for device: " + device);
    913             sm.doQuit();
    914             sm.cleanup();
    915             mStateMachines.remove(device);
    916         }
    917     }
    918 
    919     private void updateOptionalCodecsSupport(BluetoothDevice device) {
    920         int previousSupport = getSupportsOptionalCodecs(device);
    921         boolean supportsOptional = false;
    922 
    923         synchronized (mStateMachines) {
    924             A2dpStateMachine sm = mStateMachines.get(device);
    925             if (sm == null) {
    926                 return;
    927             }
    928             BluetoothCodecStatus codecStatus = sm.getCodecStatus();
    929             if (codecStatus != null) {
    930                 for (BluetoothCodecConfig config : codecStatus.getCodecsSelectableCapabilities()) {
    931                     if (!config.isMandatoryCodec()) {
    932                         supportsOptional = true;
    933                         break;
    934                     }
    935                 }
    936             }
    937         }
    938         if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
    939                 || supportsOptional != (previousSupport
    940                                     == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
    941             setSupportsOptionalCodecs(device, supportsOptional);
    942         }
    943         if (supportsOptional) {
    944             int enabled = getOptionalCodecsEnabled(device);
    945             if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
    946                 enableOptionalCodecs(device);
    947             } else if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED) {
    948                 disableOptionalCodecs(device);
    949             }
    950         }
    951     }
    952 
    953     private void connectionStateChanged(BluetoothDevice device, int fromState, int toState) {
    954         if ((device == null) || (fromState == toState)) {
    955             return;
    956         }
    957         synchronized (mStateMachines) {
    958             if (toState == BluetoothProfile.STATE_CONNECTED) {
    959                 // Each time a device connects, we want to re-check if it supports optional
    960                 // codecs (perhaps it's had a firmware update, etc.) and save that state if
    961                 // it differs from what we had saved before.
    962                 updateOptionalCodecsSupport(device);
    963                 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
    964             }
    965             // Set the active device if only one connected device is supported and it was connected
    966             if (toState == BluetoothProfile.STATE_CONNECTED && (mMaxConnectedAudioDevices == 1)) {
    967                 setActiveDevice(device);
    968             }
    969             // Check if the active device is not connected anymore
    970             if (isActiveDevice(device) && (fromState == BluetoothProfile.STATE_CONNECTED)) {
    971                 setActiveDevice(null);
    972             }
    973             // Check if the device is disconnected - if unbond, remove the state machine
    974             if (toState == BluetoothProfile.STATE_DISCONNECTED) {
    975                 int bondState = mAdapterService.getBondState(device);
    976                 if (bondState == BluetoothDevice.BOND_NONE) {
    977                     removeStateMachine(device);
    978                 }
    979             }
    980         }
    981     }
    982 
    983     /**
    984      * Receiver for processing device connection state changes.
    985      *
    986      * <ul>
    987      * <li> Update codec support per device when device is (re)connected
    988      * <li> Delete the state machine instance if the device is disconnected and unbond
    989      * </ul>
    990      */
    991     private class ConnectionStateChangedReceiver extends BroadcastReceiver {
    992         @Override
    993         public void onReceive(Context context, Intent intent) {
    994             if (!BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
    995                 return;
    996             }
    997             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    998             int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
    999             int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
   1000             connectionStateChanged(device, fromState, toState);
   1001         }
   1002     }
   1003 
   1004     /**
   1005      * Binder object: must be a static class or memory leak may occur.
   1006      */
   1007     @VisibleForTesting
   1008     static class BluetoothA2dpBinder extends IBluetoothA2dp.Stub
   1009             implements IProfileServiceBinder {
   1010         private A2dpService mService;
   1011 
   1012         private A2dpService getService() {
   1013             if (!Utils.checkCaller()) {
   1014                 Log.w(TAG, "A2DP call not allowed for non-active user");
   1015                 return null;
   1016             }
   1017 
   1018             if (mService != null && mService.isAvailable()) {
   1019                 return mService;
   1020             }
   1021             return null;
   1022         }
   1023 
   1024         BluetoothA2dpBinder(A2dpService svc) {
   1025             mService = svc;
   1026         }
   1027 
   1028         @Override
   1029         public void cleanup() {
   1030             mService = null;
   1031         }
   1032 
   1033         @Override
   1034         public boolean connect(BluetoothDevice device) {
   1035             A2dpService service = getService();
   1036             if (service == null) {
   1037                 return false;
   1038             }
   1039             return service.connect(device);
   1040         }
   1041 
   1042         @Override
   1043         public boolean disconnect(BluetoothDevice device) {
   1044             A2dpService service = getService();
   1045             if (service == null) {
   1046                 return false;
   1047             }
   1048             return service.disconnect(device);
   1049         }
   1050 
   1051         @Override
   1052         public List<BluetoothDevice> getConnectedDevices() {
   1053             A2dpService service = getService();
   1054             if (service == null) {
   1055                 return new ArrayList<>(0);
   1056             }
   1057             return service.getConnectedDevices();
   1058         }
   1059 
   1060         @Override
   1061         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
   1062             A2dpService service = getService();
   1063             if (service == null) {
   1064                 return new ArrayList<>(0);
   1065             }
   1066             return service.getDevicesMatchingConnectionStates(states);
   1067         }
   1068 
   1069         @Override
   1070         public int getConnectionState(BluetoothDevice device) {
   1071             A2dpService service = getService();
   1072             if (service == null) {
   1073                 return BluetoothProfile.STATE_DISCONNECTED;
   1074             }
   1075             return service.getConnectionState(device);
   1076         }
   1077 
   1078         @Override
   1079         public boolean setActiveDevice(BluetoothDevice device) {
   1080             A2dpService service = getService();
   1081             if (service == null) {
   1082                 return false;
   1083             }
   1084             return service.setActiveDevice(device);
   1085         }
   1086 
   1087         @Override
   1088         public BluetoothDevice getActiveDevice() {
   1089             A2dpService service = getService();
   1090             if (service == null) {
   1091                 return null;
   1092             }
   1093             return service.getActiveDevice();
   1094         }
   1095 
   1096         @Override
   1097         public boolean setPriority(BluetoothDevice device, int priority) {
   1098             A2dpService service = getService();
   1099             if (service == null) {
   1100                 return false;
   1101             }
   1102             return service.setPriority(device, priority);
   1103         }
   1104 
   1105         @Override
   1106         public int getPriority(BluetoothDevice device) {
   1107             A2dpService service = getService();
   1108             if (service == null) {
   1109                 return BluetoothProfile.PRIORITY_UNDEFINED;
   1110             }
   1111             return service.getPriority(device);
   1112         }
   1113 
   1114         @Override
   1115         public boolean isAvrcpAbsoluteVolumeSupported() {
   1116             A2dpService service = getService();
   1117             if (service == null) {
   1118                 return false;
   1119             }
   1120             return service.isAvrcpAbsoluteVolumeSupported();
   1121         }
   1122 
   1123         @Override
   1124         public void setAvrcpAbsoluteVolume(int volume) {
   1125             A2dpService service = getService();
   1126             if (service == null) {
   1127                 return;
   1128             }
   1129             service.setAvrcpAbsoluteVolume(volume);
   1130         }
   1131 
   1132         @Override
   1133         public boolean isA2dpPlaying(BluetoothDevice device) {
   1134             A2dpService service = getService();
   1135             if (service == null) {
   1136                 return false;
   1137             }
   1138             return service.isA2dpPlaying(device);
   1139         }
   1140 
   1141         @Override
   1142         public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
   1143             A2dpService service = getService();
   1144             if (service == null) {
   1145                 return null;
   1146             }
   1147             return service.getCodecStatus(device);
   1148         }
   1149 
   1150         @Override
   1151         public void setCodecConfigPreference(BluetoothDevice device,
   1152                                              BluetoothCodecConfig codecConfig) {
   1153             A2dpService service = getService();
   1154             if (service == null) {
   1155                 return;
   1156             }
   1157             service.setCodecConfigPreference(device, codecConfig);
   1158         }
   1159 
   1160         @Override
   1161         public void enableOptionalCodecs(BluetoothDevice device) {
   1162             A2dpService service = getService();
   1163             if (service == null) {
   1164                 return;
   1165             }
   1166             service.enableOptionalCodecs(device);
   1167         }
   1168 
   1169         @Override
   1170         public void disableOptionalCodecs(BluetoothDevice device) {
   1171             A2dpService service = getService();
   1172             if (service == null) {
   1173                 return;
   1174             }
   1175             service.disableOptionalCodecs(device);
   1176         }
   1177 
   1178         public int supportsOptionalCodecs(BluetoothDevice device) {
   1179             A2dpService service = getService();
   1180             if (service == null) {
   1181                 return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
   1182             }
   1183             return service.getSupportsOptionalCodecs(device);
   1184         }
   1185 
   1186         public int getOptionalCodecsEnabled(BluetoothDevice device) {
   1187             A2dpService service = getService();
   1188             if (service == null) {
   1189                 return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
   1190             }
   1191             return service.getOptionalCodecsEnabled(device);
   1192         }
   1193 
   1194         public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
   1195             A2dpService service = getService();
   1196             if (service == null) {
   1197                 return;
   1198             }
   1199             service.setOptionalCodecsEnabled(device, value);
   1200         }
   1201     }
   1202 
   1203     @Override
   1204     public void dump(StringBuilder sb) {
   1205         super.dump(sb);
   1206         ProfileService.println(sb, "mActiveDevice: " + mActiveDevice);
   1207         for (A2dpStateMachine sm : mStateMachines.values()) {
   1208             sm.dump(sb);
   1209         }
   1210         if (mAvrcp != null) {
   1211             mAvrcp.dump(sb);
   1212         }
   1213     }
   1214 }
   1215