A2dpSinkService.java revision 2df89213f063ef9d38f1531253299f80fdb0739d
1/*
2 * Copyright (C) 2014 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.a2dpsink;
18
19import android.bluetooth.BluetoothAudioConfig;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothProfile;
22import android.bluetooth.IBluetoothA2dpSink;
23import android.content.Intent;
24import android.provider.Settings;
25import android.util.Log;
26
27import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
28import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
29
30import com.android.bluetooth.btservice.ProfileService;
31import com.android.bluetooth.Utils;
32
33import java.util.ArrayList;
34import java.util.List;
35
36/**
37 * Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application.
38 * @hide
39 */
40public class A2dpSinkService extends ProfileService {
41    private static final boolean DBG = true;
42    private static final String TAG = "A2dpSinkService";
43
44    private A2dpSinkStateMachine mStateMachine;
45    private static A2dpSinkService sA2dpSinkService;
46
47    protected String getName() {
48        return TAG;
49    }
50
51    protected IProfileServiceBinder initBinder() {
52        return new BluetoothA2dpSinkBinder(this);
53    }
54
55    protected boolean start() {
56        if (DBG) {
57            Log.d(TAG, "start()");
58        }
59        // Start the media browser service.
60        Intent startIntent = new Intent(this, A2dpMediaBrowserService.class);
61        startService(startIntent);
62        mStateMachine = A2dpSinkStateMachine.make(this, this);
63        setA2dpSinkService(this);
64        return true;
65    }
66
67    protected boolean stop() {
68        if (DBG) {
69            Log.d(TAG, "stop()");
70        }
71        if(mStateMachine != null) {
72            mStateMachine.doQuit();
73        }
74        Intent stopIntent = new Intent(this, A2dpMediaBrowserService.class);
75        stopService(stopIntent);
76        return true;
77    }
78
79    protected boolean cleanup() {
80        if (mStateMachine!= null) {
81            mStateMachine.cleanup();
82        }
83        clearA2dpSinkService();
84        return true;
85    }
86
87    //API Methods
88
89    public static synchronized A2dpSinkService getA2dpSinkService(){
90        if (sA2dpSinkService != null && sA2dpSinkService.isAvailable()) {
91            if (DBG) Log.d(TAG, "getA2dpSinkService(): returning " + sA2dpSinkService);
92            return sA2dpSinkService;
93        }
94        if (DBG)  {
95            if (sA2dpSinkService == null) {
96                Log.d(TAG, "getA2dpSinkService(): service is NULL");
97            } else if (!(sA2dpSinkService.isAvailable())) {
98                Log.d(TAG,"getA2dpSinkService(): service is not available");
99            }
100        }
101        return null;
102    }
103
104    private static synchronized void setA2dpSinkService(A2dpSinkService instance) {
105        if (instance != null && instance.isAvailable()) {
106            if (DBG) Log.d(TAG, "setA2dpSinkService(): set to: " + sA2dpSinkService);
107            sA2dpSinkService = instance;
108        } else {
109            if (DBG)  {
110                if (sA2dpSinkService == null) {
111                    Log.d(TAG, "setA2dpSinkService(): service not available");
112                } else if (!sA2dpSinkService.isAvailable()) {
113                    Log.d(TAG,"setA2dpSinkService(): service is cleaning up");
114                }
115            }
116        }
117    }
118
119    private static synchronized void clearA2dpSinkService() {
120        sA2dpSinkService = null;
121    }
122
123    public boolean connect(BluetoothDevice device) {
124        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
125                                       "Need BLUETOOTH ADMIN permission");
126
127        int connectionState = mStateMachine.getConnectionState(device);
128        if (connectionState == BluetoothProfile.STATE_CONNECTED ||
129            connectionState == BluetoothProfile.STATE_CONNECTING) {
130            return false;
131        }
132
133        mStateMachine.sendMessage(A2dpSinkStateMachine.CONNECT, device);
134        return true;
135    }
136
137    boolean disconnect(BluetoothDevice device) {
138        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
139                                       "Need BLUETOOTH ADMIN permission");
140        int connectionState = mStateMachine.getConnectionState(device);
141        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
142            connectionState != BluetoothProfile.STATE_CONNECTING) {
143            return false;
144        }
145
146        mStateMachine.sendMessage(A2dpSinkStateMachine.DISCONNECT, device);
147        return true;
148    }
149
150    public List<BluetoothDevice> getConnectedDevices() {
151        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
152        return mStateMachine.getConnectedDevices();
153    }
154
155    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
156        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
157        return mStateMachine.getDevicesMatchingConnectionStates(states);
158    }
159
160    int getConnectionState(BluetoothDevice device) {
161        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
162        return mStateMachine.getConnectionState(device);
163    }
164
165    public boolean setPriority(BluetoothDevice device, int priority) {
166        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
167                                       "Need BLUETOOTH_ADMIN permission");
168        Settings.Global.putInt(getContentResolver(),
169            Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
170            priority);
171        if (DBG) {
172            Log.d(TAG,"Saved priority " + device + " = " + priority);
173        }
174        return true;
175    }
176
177    public int getPriority(BluetoothDevice device) {
178        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
179                                       "Need BLUETOOTH_ADMIN permission");
180        int priority = Settings.Global.getInt(getContentResolver(),
181            Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
182            BluetoothProfile.PRIORITY_UNDEFINED);
183        return priority;
184    }
185
186    /**
187     * Called by AVRCP controller to provide information about the last user intent on CT.
188     *
189     * If the user has pressed play in the last attempt then A2DP Sink component will grant focus to
190     * any incoming sound from the phone (and also retain focus for a few seconds before
191     * relinquishing. On the other hand if the user has pressed pause/stop then the A2DP sink
192     * component will take the focus away but also notify the stack to throw away incoming data.
193     */
194    public void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
195        if (mStateMachine != null) {
196            if (keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PLAY &&
197                keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
198                mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PLAY);
199            } else if ((keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE ||
200                       keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_STOP) &&
201                       keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
202                mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PAUSE);
203            }
204        }
205    }
206
207    /**
208     * Called by AVRCP controller to provide information about the last user intent on TG.
209     *
210     * Tf the user has pressed pause on the TG then we can preempt streaming music. This is opposed
211     * to when the streaming stops abruptly (jitter) in which case we will wait for sometime before
212     * stopping playback.
213     */
214    public void informTGStatePlaying(BluetoothDevice device, boolean isPlaying) {
215        if (mStateMachine != null) {
216            if (!isPlaying) {
217                mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE);
218            } else {
219                mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PLAY);
220            }
221        }
222    }
223
224    synchronized boolean isA2dpPlaying(BluetoothDevice device) {
225        enforceCallingOrSelfPermission(BLUETOOTH_PERM,
226                                       "Need BLUETOOTH permission");
227        if (DBG) {
228            Log.d(TAG, "isA2dpPlaying(" + device + ")");
229        }
230        return mStateMachine.isPlaying(device);
231    }
232
233    BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
234        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
235        return mStateMachine.getAudioConfig(device);
236    }
237
238    //Binder object: Must be static class or memory leak may occur
239    private static class BluetoothA2dpSinkBinder extends IBluetoothA2dpSink.Stub
240        implements IProfileServiceBinder {
241        private A2dpSinkService mService;
242
243        private A2dpSinkService getService() {
244            if (!Utils.checkCaller()) {
245                Log.w(TAG,"A2dp call not allowed for non-active user");
246                return null;
247            }
248
249            if (mService != null && mService.isAvailable()) {
250                return mService;
251            }
252            return null;
253        }
254
255        BluetoothA2dpSinkBinder(A2dpSinkService svc) {
256            mService = svc;
257        }
258
259        public boolean cleanup()  {
260            mService = null;
261            return true;
262        }
263
264        public boolean connect(BluetoothDevice device) {
265            A2dpSinkService service = getService();
266            if (service == null) return false;
267            return service.connect(device);
268        }
269
270        public boolean disconnect(BluetoothDevice device) {
271            A2dpSinkService service = getService();
272            if (service == null) return false;
273            return service.disconnect(device);
274        }
275
276        public List<BluetoothDevice> getConnectedDevices() {
277            A2dpSinkService service = getService();
278            if (service == null) return new ArrayList<BluetoothDevice>(0);
279            return service.getConnectedDevices();
280        }
281
282        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
283            A2dpSinkService service = getService();
284            if (service == null) return new ArrayList<BluetoothDevice>(0);
285            return service.getDevicesMatchingConnectionStates(states);
286        }
287
288        public int getConnectionState(BluetoothDevice device) {
289            A2dpSinkService service = getService();
290            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
291            return service.getConnectionState(device);
292        }
293
294        public boolean isA2dpPlaying(BluetoothDevice device) {
295            A2dpSinkService service = getService();
296            if (service == null) return false;
297            return service.isA2dpPlaying(device);
298        }
299
300        public boolean setPriority(BluetoothDevice device, int priority) {
301            A2dpSinkService service = getService();
302            if (service == null) return false;
303            return service.setPriority(device, priority);
304        }
305
306        public int getPriority(BluetoothDevice device) {
307            A2dpSinkService service = getService();
308            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
309            return service.getPriority(device);
310        }
311
312        public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
313            A2dpSinkService service = getService();
314            if (service == null) return null;
315            return service.getAudioConfig(device);
316        }
317    };
318
319    @Override
320    public void dump(StringBuilder sb) {
321        super.dump(sb);
322        if (mStateMachine != null) {
323            mStateMachine.dump(sb);
324        }
325    }
326}
327