A2dpService.java revision 1a5532f2043cc2eaf136a35eace240f8bebd9bbe
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.BluetoothCodecConfig;
21import android.bluetooth.BluetoothCodecStatus;
22import android.bluetooth.BluetoothDevice;
23import android.bluetooth.BluetoothProfile;
24import android.bluetooth.BluetoothUuid;
25import android.bluetooth.IBluetoothA2dp;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.os.HandlerThread;
31import android.os.ParcelUuid;
32import android.provider.Settings;
33import android.util.Log;
34
35import com.android.bluetooth.Utils;
36import com.android.bluetooth.avrcp.Avrcp;
37import com.android.bluetooth.btservice.ProfileService;
38
39import java.util.ArrayList;
40import java.util.List;
41import java.util.Objects;
42
43/**
44 * Provides Bluetooth A2DP profile, as a service in the Bluetooth application.
45 * @hide
46 */
47public class A2dpService extends ProfileService {
48    private static final boolean DBG = true;
49    private static final String TAG = "A2dpService";
50
51    private HandlerThread mStateMachinesThread = null;
52    private A2dpStateMachine mStateMachine;
53    private Avrcp mAvrcp;
54    private A2dpNativeInterface mA2dpNativeInterface = null;
55
56    private BroadcastReceiver mConnectionStateChangedReceiver = null;
57
58    public A2dpService() {
59        mA2dpNativeInterface = A2dpNativeInterface.getInstance();
60    }
61
62    private class CodecSupportReceiver extends BroadcastReceiver {
63        @Override
64        public void onReceive(Context context, Intent intent) {
65            if (!BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
66                return;
67            }
68            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
69            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
70            if (state != BluetoothProfile.STATE_CONNECTED || device == null) {
71                return;
72            }
73            // Each time a device connects, we want to re-check if it supports optional
74            // codecs (perhaps it's had a firmware update, etc.) and save that state if
75            // it differs from what we had saved before.
76            int previousSupport = getSupportsOptionalCodecs(device);
77            boolean supportsOptional = false;
78            for (BluetoothCodecConfig config : mStateMachine.getCodecStatus()
79                    .getCodecsSelectableCapabilities()) {
80                if (!config.isMandatoryCodec()) {
81                    supportsOptional = true;
82                    break;
83                }
84            }
85            if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
86                    || supportsOptional != (previousSupport
87                    == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
88                setSupportsOptionalCodecs(device, supportsOptional);
89            }
90            if (supportsOptional) {
91                int enabled = getOptionalCodecsEnabled(device);
92                if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
93                    enableOptionalCodecs();
94                } else if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED) {
95                    disableOptionalCodecs();
96                }
97            }
98        }
99    }
100
101    ;
102
103    private static A2dpService sA2dpService;
104    static final ParcelUuid[] A2DP_SOURCE_UUID = {
105            BluetoothUuid.AudioSource
106    };
107    static final ParcelUuid[] A2DP_SOURCE_SINK_UUIDS = {
108            BluetoothUuid.AudioSource, BluetoothUuid.AudioSink
109    };
110
111    @Override
112    protected String getName() {
113        return TAG;
114    }
115
116    @Override
117    protected IProfileServiceBinder initBinder() {
118        return new BluetoothA2dpBinder(this);
119    }
120
121    @Override
122    protected boolean start() {
123        if (DBG) {
124            Log.d(TAG, "start()");
125        }
126
127        mStateMachinesThread = new HandlerThread("A2dpService.StateMachines");
128        mStateMachinesThread.start();
129
130        mAvrcp = Avrcp.make(this);
131        mStateMachine = A2dpStateMachine.make(this, this, mA2dpNativeInterface,
132                                              mStateMachinesThread.getLooper());
133        setA2dpService(this);
134        if (mConnectionStateChangedReceiver == null) {
135            IntentFilter filter = new IntentFilter();
136            filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
137            mConnectionStateChangedReceiver = new CodecSupportReceiver();
138            registerReceiver(mConnectionStateChangedReceiver, filter);
139        }
140        return true;
141    }
142
143    @Override
144    protected boolean stop() {
145        if (DBG) {
146            Log.d(TAG, "stop()");
147        }
148
149        if (mStateMachine != null) {
150            mStateMachine.doQuit();
151        }
152        if (mAvrcp != null) {
153            mAvrcp.doQuit();
154        }
155        if (mStateMachinesThread != null) {
156            mStateMachinesThread.quit();
157            mStateMachinesThread = null;
158        }
159        return true;
160    }
161
162    @Override
163    protected boolean cleanup() {
164        if (DBG) {
165            Log.d(TAG, "cleanup()");
166        }
167
168        if (mConnectionStateChangedReceiver != null) {
169            unregisterReceiver(mConnectionStateChangedReceiver);
170            mConnectionStateChangedReceiver = null;
171        }
172        if (mStateMachine != null) {
173            mStateMachine.cleanup();
174            mStateMachine = null;
175        }
176        if (mAvrcp != null) {
177            mAvrcp.cleanup();
178            mAvrcp = null;
179        }
180        clearA2dpService();
181        return true;
182    }
183
184    //API Methods
185
186    public static synchronized A2dpService getA2dpService() {
187        if (sA2dpService != null && sA2dpService.isAvailable()) {
188            if (DBG) {
189                Log.d(TAG, "getA2DPService(): returning " + sA2dpService);
190            }
191            return sA2dpService;
192        }
193        if (DBG) {
194            if (sA2dpService == null) {
195                Log.d(TAG, "getA2dpService(): service is NULL");
196            } else if (!(sA2dpService.isAvailable())) {
197                Log.d(TAG, "getA2dpService(): service is not available");
198            }
199        }
200        return null;
201    }
202
203    private static synchronized void setA2dpService(A2dpService instance) {
204        if (instance != null && instance.isAvailable()) {
205            if (DBG) {
206                Log.d(TAG, "setA2dpService(): set to: " + instance);
207            }
208            sA2dpService = instance;
209        } else {
210            if (DBG) {
211                if (sA2dpService == null) {
212                    Log.d(TAG, "setA2dpService(): service not available");
213                } else if (!sA2dpService.isAvailable()) {
214                    Log.d(TAG, "setA2dpService(): service is cleaning up");
215                }
216            }
217        }
218    }
219
220    private static synchronized void clearA2dpService() {
221        sA2dpService = null;
222    }
223
224    public boolean connect(BluetoothDevice device) {
225        if (DBG) {
226            Log.d(TAG, "connect(): " + device);
227        }
228
229        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
230
231        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
232            return false;
233        }
234        ParcelUuid[] featureUuids = device.getUuids();
235        if ((BluetoothUuid.containsAnyUuid(featureUuids, A2DP_SOURCE_UUID))
236                && !(BluetoothUuid.containsAllUuids(featureUuids, A2DP_SOURCE_SINK_UUIDS))) {
237            Log.e(TAG, "Remote does not have A2dp Sink UUID");
238            return false;
239        }
240
241        int connectionState = mStateMachine.getConnectionState(device);
242        if (connectionState == BluetoothProfile.STATE_CONNECTED
243                || connectionState == BluetoothProfile.STATE_CONNECTING) {
244            return false;
245        }
246
247        mStateMachine.sendMessage(A2dpStateMachine.CONNECT, device);
248        return true;
249    }
250
251    boolean disconnect(BluetoothDevice device) {
252        if (DBG) {
253            Log.d(TAG, "disconnect(): " + device);
254        }
255
256        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
257        int connectionState = mStateMachine.getConnectionState(device);
258        if (connectionState != BluetoothProfile.STATE_CONNECTED
259                && connectionState != BluetoothProfile.STATE_CONNECTING) {
260            return false;
261        }
262
263        mStateMachine.sendMessage(A2dpStateMachine.DISCONNECT, device);
264        return true;
265    }
266
267    public List<BluetoothDevice> getConnectedDevices() {
268        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
269        return mStateMachine.getConnectedDevices();
270    }
271
272    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
273        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
274        return mStateMachine.getDevicesMatchingConnectionStates(states);
275    }
276
277    public int getConnectionState(BluetoothDevice device) {
278        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
279        return mStateMachine.getConnectionState(device);
280    }
281
282    public boolean setPriority(BluetoothDevice device, int priority) {
283        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
284        Settings.Global.putInt(getContentResolver(),
285                Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
286        if (DBG) {
287            Log.d(TAG, "Saved priority " + device + " = " + priority);
288        }
289        return true;
290    }
291
292    public int getPriority(BluetoothDevice device) {
293        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
294        int priority = Settings.Global.getInt(getContentResolver(),
295                Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
296                BluetoothProfile.PRIORITY_UNDEFINED);
297        return priority;
298    }
299
300    /* Absolute volume implementation */
301    public boolean isAvrcpAbsoluteVolumeSupported() {
302        return mAvrcp.isAbsoluteVolumeSupported();
303    }
304
305    public void adjustAvrcpAbsoluteVolume(int direction) {
306        mAvrcp.adjustVolume(direction);
307    }
308
309    public void setAvrcpAbsoluteVolume(int volume) {
310        mAvrcp.setAbsoluteVolume(volume);
311    }
312
313    public void setAvrcpAudioState(int state) {
314        mAvrcp.setA2dpAudioState(state);
315    }
316
317    public void resetAvrcpBlacklist(BluetoothDevice device) {
318        if (mAvrcp != null) {
319            mAvrcp.resetBlackList(device.getAddress());
320        }
321    }
322
323    synchronized boolean isA2dpPlaying(BluetoothDevice device) {
324        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
325        if (DBG) {
326            Log.d(TAG, "isA2dpPlaying(" + device + ")");
327        }
328        return mStateMachine.isPlaying(device);
329    }
330
331    public BluetoothCodecStatus getCodecStatus() {
332        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
333        if (DBG) {
334            Log.d(TAG, "getCodecStatus()");
335        }
336        return mStateMachine.getCodecStatus();
337    }
338
339    public void setCodecConfigPreference(BluetoothCodecConfig codecConfig) {
340        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
341        if (DBG) {
342            Log.d(TAG, "setCodecConfigPreference(): " + Objects.toString(codecConfig));
343        }
344        mStateMachine.setCodecConfigPreference(codecConfig);
345    }
346
347    public void enableOptionalCodecs() {
348        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
349        if (DBG) {
350            Log.d(TAG, "enableOptionalCodecs()");
351        }
352        mStateMachine.enableOptionalCodecs();
353    }
354
355    public void disableOptionalCodecs() {
356        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
357        if (DBG) {
358            Log.d(TAG, "disableOptionalCodecs()");
359        }
360        mStateMachine.disableOptionalCodecs();
361    }
362
363    public int getSupportsOptionalCodecs(BluetoothDevice device) {
364        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
365        int support = Settings.Global.getInt(getContentResolver(),
366                Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
367                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
368        return support;
369    }
370
371    public void setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport) {
372        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
373        int value = doesSupport ? BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED
374                : BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
375        Settings.Global.putInt(getContentResolver(),
376                Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
377                value);
378    }
379
380    public int getOptionalCodecsEnabled(BluetoothDevice device) {
381        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
382        return Settings.Global.getInt(getContentResolver(),
383                Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
384                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
385    }
386
387    public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
388        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
389        if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
390                && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
391                && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
392            Log.w(TAG, "Unexpected value passed to setOptionalCodecsEnabled:" + value);
393            return;
394        }
395        Settings.Global.putInt(getContentResolver(),
396                Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
397                value);
398    }
399
400    // Handle messages from native (JNI) to Java
401    void messageFromNative(A2dpStackEvent stackEvent) {
402        mStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, stackEvent);
403    }
404
405    // Binder object: Must be static class or memory leak may occur
406    private static class BluetoothA2dpBinder extends IBluetoothA2dp.Stub
407            implements IProfileServiceBinder {
408        private A2dpService mService;
409
410        private A2dpService getService() {
411            if (!Utils.checkCaller()) {
412                Log.w(TAG, "A2dp call not allowed for non-active user");
413                return null;
414            }
415
416            if (mService != null && mService.isAvailable()) {
417                return mService;
418            }
419            return null;
420        }
421
422        BluetoothA2dpBinder(A2dpService svc) {
423            mService = svc;
424        }
425
426        @Override
427        public boolean cleanup() {
428            mService = null;
429            return true;
430        }
431
432        @Override
433        public boolean connect(BluetoothDevice device) {
434            A2dpService service = getService();
435            if (service == null) {
436                return false;
437            }
438            return service.connect(device);
439        }
440
441        @Override
442        public boolean disconnect(BluetoothDevice device) {
443            A2dpService service = getService();
444            if (service == null) {
445                return false;
446            }
447            return service.disconnect(device);
448        }
449
450        @Override
451        public List<BluetoothDevice> getConnectedDevices() {
452            A2dpService service = getService();
453            if (service == null) {
454                return new ArrayList<BluetoothDevice>(0);
455            }
456            return service.getConnectedDevices();
457        }
458
459        @Override
460        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
461            A2dpService service = getService();
462            if (service == null) {
463                return new ArrayList<BluetoothDevice>(0);
464            }
465            return service.getDevicesMatchingConnectionStates(states);
466        }
467
468        @Override
469        public int getConnectionState(BluetoothDevice device) {
470            A2dpService service = getService();
471            if (service == null) {
472                return BluetoothProfile.STATE_DISCONNECTED;
473            }
474            return service.getConnectionState(device);
475        }
476
477        @Override
478        public boolean setPriority(BluetoothDevice device, int priority) {
479            A2dpService service = getService();
480            if (service == null) {
481                return false;
482            }
483            return service.setPriority(device, priority);
484        }
485
486        @Override
487        public int getPriority(BluetoothDevice device) {
488            A2dpService service = getService();
489            if (service == null) {
490                return BluetoothProfile.PRIORITY_UNDEFINED;
491            }
492            return service.getPriority(device);
493        }
494
495        @Override
496        public boolean isAvrcpAbsoluteVolumeSupported() {
497            A2dpService service = getService();
498            if (service == null) {
499                return false;
500            }
501            return service.isAvrcpAbsoluteVolumeSupported();
502        }
503
504        @Override
505        public void adjustAvrcpAbsoluteVolume(int direction) {
506            A2dpService service = getService();
507            if (service == null) {
508                return;
509            }
510            service.adjustAvrcpAbsoluteVolume(direction);
511        }
512
513        @Override
514        public void setAvrcpAbsoluteVolume(int volume) {
515            A2dpService service = getService();
516            if (service == null) {
517                return;
518            }
519            service.setAvrcpAbsoluteVolume(volume);
520        }
521
522        @Override
523        public boolean isA2dpPlaying(BluetoothDevice device) {
524            A2dpService service = getService();
525            if (service == null) {
526                return false;
527            }
528            return service.isA2dpPlaying(device);
529        }
530
531        @Override
532        public BluetoothCodecStatus getCodecStatus() {
533            A2dpService service = getService();
534            if (service == null) {
535                return null;
536            }
537            return service.getCodecStatus();
538        }
539
540        @Override
541        public void setCodecConfigPreference(BluetoothCodecConfig codecConfig) {
542            A2dpService service = getService();
543            if (service == null) {
544                return;
545            }
546            service.setCodecConfigPreference(codecConfig);
547        }
548
549        @Override
550        public void enableOptionalCodecs() {
551            A2dpService service = getService();
552            if (service == null) {
553                return;
554            }
555            service.enableOptionalCodecs();
556        }
557
558        @Override
559        public void disableOptionalCodecs() {
560            A2dpService service = getService();
561            if (service == null) {
562                return;
563            }
564            service.disableOptionalCodecs();
565        }
566
567        public int supportsOptionalCodecs(BluetoothDevice device) {
568            A2dpService service = getService();
569            if (service == null) {
570                return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
571            }
572            return service.getSupportsOptionalCodecs(device);
573        }
574
575        public int getOptionalCodecsEnabled(BluetoothDevice device) {
576            A2dpService service = getService();
577            if (service == null) {
578                return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
579            }
580            return service.getOptionalCodecsEnabled(device);
581        }
582
583        public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
584            A2dpService service = getService();
585            if (service == null) {
586                return;
587            }
588            service.setOptionalCodecsEnabled(device, value);
589        }
590    }
591
592    ;
593
594    @Override
595    public void dump(StringBuilder sb) {
596        super.dump(sb);
597        if (mStateMachine != null) {
598            mStateMachine.dump(sb);
599        }
600        if (mAvrcp != null) {
601            mAvrcp.dump(sb);
602        }
603    }
604}
605