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