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.BluetoothDevice;
20import android.bluetooth.BluetoothProfile;
21import android.bluetooth.BluetoothUuid;
22import android.bluetooth.IBluetoothA2dp;
23import android.content.Context;
24import android.content.Intent;
25import android.os.ParcelUuid;
26import android.provider.Settings;
27import android.util.Log;
28import com.android.bluetooth.avrcp.Avrcp;
29import com.android.bluetooth.btservice.ProfileService;
30import com.android.bluetooth.Utils;
31import java.util.ArrayList;
32import java.util.Iterator;
33import java.util.List;
34import java.util.Map;
35
36/**
37 * Provides Bluetooth A2DP profile, as a service in the Bluetooth application.
38 * @hide
39 */
40public class A2dpService extends ProfileService {
41    private static final boolean DBG = false;
42    private static final String TAG="A2dpService";
43
44    private A2dpStateMachine mStateMachine;
45    private Avrcp mAvrcp;
46    private static A2dpService sAd2dpService;
47    static final ParcelUuid[] A2DP_SOURCE_UUID = {
48        BluetoothUuid.AudioSource
49    };
50    static final ParcelUuid[] A2DP_SOURCE_SINK_UUIDS = {
51        BluetoothUuid.AudioSource,
52        BluetoothUuid.AudioSink
53    };
54
55    protected String getName() {
56        return TAG;
57    }
58
59    protected IProfileServiceBinder initBinder() {
60        return new BluetoothA2dpBinder(this);
61    }
62
63    protected boolean start() {
64        mAvrcp = Avrcp.make(this);
65        mStateMachine = A2dpStateMachine.make(this, this);
66        setA2dpService(this);
67        return true;
68    }
69
70    protected boolean stop() {
71        if (mStateMachine != null) {
72            mStateMachine.doQuit();
73        }
74        if (mAvrcp != null) {
75            mAvrcp.doQuit();
76        }
77        return true;
78    }
79
80    protected boolean cleanup() {
81        if (mStateMachine!= null) {
82            mStateMachine.cleanup();
83        }
84        if (mAvrcp != null) {
85            mAvrcp.cleanup();
86            mAvrcp = null;
87        }
88        clearA2dpService();
89        return true;
90    }
91
92    //API Methods
93
94    public static synchronized A2dpService getA2dpService(){
95        if (sAd2dpService != null && sAd2dpService.isAvailable()) {
96            if (DBG) Log.d(TAG, "getA2DPService(): returning " + sAd2dpService);
97            return sAd2dpService;
98        }
99        if (DBG)  {
100            if (sAd2dpService == null) {
101                Log.d(TAG, "getA2dpService(): service is NULL");
102            } else if (!(sAd2dpService.isAvailable())) {
103                Log.d(TAG,"getA2dpService(): service is not available");
104            }
105        }
106        return null;
107    }
108
109    private static synchronized void setA2dpService(A2dpService instance) {
110        if (instance != null && instance.isAvailable()) {
111            if (DBG) Log.d(TAG, "setA2dpService(): set to: " + sAd2dpService);
112            sAd2dpService = instance;
113        } else {
114            if (DBG)  {
115                if (sAd2dpService == null) {
116                    Log.d(TAG, "setA2dpService(): service not available");
117                } else if (!sAd2dpService.isAvailable()) {
118                    Log.d(TAG,"setA2dpService(): service is cleaning up");
119                }
120            }
121        }
122    }
123
124    private static synchronized void clearA2dpService() {
125        sAd2dpService = null;
126    }
127
128    public boolean connect(BluetoothDevice device) {
129        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
130                                       "Need BLUETOOTH ADMIN permission");
131
132        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
133            return false;
134        }
135        ParcelUuid[] featureUuids = device.getUuids();
136        if ((BluetoothUuid.containsAnyUuid(featureUuids, A2DP_SOURCE_UUID)) &&
137            !(BluetoothUuid.containsAllUuids(featureUuids ,A2DP_SOURCE_SINK_UUIDS))) {
138            Log.e(TAG,"Remote does not have A2dp Sink UUID");
139            return false;
140        }
141
142        int connectionState = mStateMachine.getConnectionState(device);
143        if (connectionState == BluetoothProfile.STATE_CONNECTED ||
144            connectionState == BluetoothProfile.STATE_CONNECTING) {
145            return false;
146        }
147
148        mStateMachine.sendMessage(A2dpStateMachine.CONNECT, device);
149        return true;
150    }
151
152    boolean disconnect(BluetoothDevice device) {
153        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
154                                       "Need BLUETOOTH ADMIN permission");
155        int connectionState = mStateMachine.getConnectionState(device);
156        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
157            connectionState != BluetoothProfile.STATE_CONNECTING) {
158            return false;
159        }
160
161        mStateMachine.sendMessage(A2dpStateMachine.DISCONNECT, device);
162        return true;
163    }
164
165    public List<BluetoothDevice> getConnectedDevices() {
166        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
167        return mStateMachine.getConnectedDevices();
168    }
169
170    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
171        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
172        return mStateMachine.getDevicesMatchingConnectionStates(states);
173    }
174
175    int getConnectionState(BluetoothDevice device) {
176        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
177        return mStateMachine.getConnectionState(device);
178    }
179
180    public boolean setPriority(BluetoothDevice device, int priority) {
181        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
182                                       "Need BLUETOOTH_ADMIN permission");
183        Settings.Global.putInt(getContentResolver(),
184            Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
185            priority);
186        if (DBG) Log.d(TAG,"Saved priority " + device + " = " + priority);
187        return true;
188    }
189
190    public int getPriority(BluetoothDevice device) {
191        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
192                                       "Need BLUETOOTH_ADMIN permission");
193        int priority = Settings.Global.getInt(getContentResolver(),
194            Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
195            BluetoothProfile.PRIORITY_UNDEFINED);
196        return priority;
197    }
198
199    /* Absolute volume implementation */
200    public boolean isAvrcpAbsoluteVolumeSupported() {
201        return mAvrcp.isAbsoluteVolumeSupported();
202    }
203
204    public void adjustAvrcpAbsoluteVolume(int direction) {
205        mAvrcp.adjustVolume(direction);
206    }
207
208    public void setAvrcpAbsoluteVolume(int volume) {
209        mAvrcp.setAbsoluteVolume(volume);
210    }
211
212    public void setAvrcpAudioState(int state) {
213        mAvrcp.setA2dpAudioState(state);
214    }
215
216    synchronized boolean isA2dpPlaying(BluetoothDevice device) {
217        enforceCallingOrSelfPermission(BLUETOOTH_PERM,
218                                       "Need BLUETOOTH permission");
219        if (DBG) Log.d(TAG, "isA2dpPlaying(" + device + ")");
220        return mStateMachine.isPlaying(device);
221    }
222
223    //Binder object: Must be static class or memory leak may occur
224    private static class BluetoothA2dpBinder extends IBluetoothA2dp.Stub
225        implements IProfileServiceBinder {
226        private A2dpService mService;
227
228        private A2dpService getService() {
229            if (!Utils.checkCaller()) {
230                Log.w(TAG,"A2dp call not allowed for non-active user");
231                return null;
232            }
233
234            if (mService != null && mService.isAvailable()) {
235                return mService;
236            }
237            return null;
238        }
239
240        BluetoothA2dpBinder(A2dpService svc) {
241            mService = svc;
242        }
243
244        public boolean cleanup()  {
245            mService = null;
246            return true;
247        }
248
249        public boolean connect(BluetoothDevice device) {
250            A2dpService service = getService();
251            if (service == null) return false;
252            return service.connect(device);
253        }
254
255        public boolean disconnect(BluetoothDevice device) {
256            A2dpService service = getService();
257            if (service == null) return false;
258            return service.disconnect(device);
259        }
260
261        public List<BluetoothDevice> getConnectedDevices() {
262            A2dpService service = getService();
263            if (service == null) return new ArrayList<BluetoothDevice>(0);
264            return service.getConnectedDevices();
265        }
266
267        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
268            A2dpService service = getService();
269            if (service == null) return new ArrayList<BluetoothDevice>(0);
270            return service.getDevicesMatchingConnectionStates(states);
271        }
272
273        public int getConnectionState(BluetoothDevice device) {
274            A2dpService service = getService();
275            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
276            return service.getConnectionState(device);
277        }
278
279        public boolean setPriority(BluetoothDevice device, int priority) {
280            A2dpService service = getService();
281            if (service == null) return false;
282            return service.setPriority(device, priority);
283        }
284
285        public int getPriority(BluetoothDevice device) {
286            A2dpService service = getService();
287            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
288            return service.getPriority(device);
289        }
290
291        public boolean isAvrcpAbsoluteVolumeSupported() {
292            A2dpService service = getService();
293            if (service == null) return false;
294            return service.isAvrcpAbsoluteVolumeSupported();
295        }
296
297        public void adjustAvrcpAbsoluteVolume(int direction) {
298            A2dpService service = getService();
299            if (service == null) return;
300            service.adjustAvrcpAbsoluteVolume(direction);
301        }
302
303        public void setAvrcpAbsoluteVolume(int volume) {
304            A2dpService service = getService();
305            if (service == null) return;
306            service.setAvrcpAbsoluteVolume(volume);
307        }
308
309        public boolean isA2dpPlaying(BluetoothDevice device) {
310            A2dpService service = getService();
311            if (service == null) return false;
312            return service.isA2dpPlaying(device);
313        }
314    };
315
316    @Override
317    public void dump(StringBuilder sb) {
318        super.dump(sb);
319        if (mStateMachine != null) {
320            mStateMachine.dump(sb);
321        }
322        if (mAvrcp != null) {
323            mAvrcp.dump(sb);
324        }
325    }
326}
327