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