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