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