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        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
134            return false;
135        }
136
137        mStateMachine.sendMessage(A2dpSinkStateMachine.CONNECT, device);
138        return true;
139    }
140
141    boolean disconnect(BluetoothDevice device) {
142        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
143                                       "Need BLUETOOTH ADMIN permission");
144        int connectionState = mStateMachine.getConnectionState(device);
145        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
146            connectionState != BluetoothProfile.STATE_CONNECTING) {
147            return false;
148        }
149
150        mStateMachine.sendMessage(A2dpSinkStateMachine.DISCONNECT, device);
151        return true;
152    }
153
154    public List<BluetoothDevice> getConnectedDevices() {
155        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
156        return mStateMachine.getConnectedDevices();
157    }
158
159    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
160        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
161        return mStateMachine.getDevicesMatchingConnectionStates(states);
162    }
163
164    int getConnectionState(BluetoothDevice device) {
165        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
166        return mStateMachine.getConnectionState(device);
167    }
168
169    public boolean setPriority(BluetoothDevice device, int priority) {
170        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
171                                       "Need BLUETOOTH_ADMIN permission");
172        Settings.Global.putInt(getContentResolver(),
173            Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
174            priority);
175        if (DBG) {
176            Log.d(TAG,"Saved priority " + device + " = " + priority);
177        }
178        return true;
179    }
180
181    public int getPriority(BluetoothDevice device) {
182        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
183                                       "Need BLUETOOTH_ADMIN permission");
184        int priority = Settings.Global.getInt(getContentResolver(),
185            Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
186            BluetoothProfile.PRIORITY_UNDEFINED);
187        return priority;
188    }
189
190    /**
191     * Called by AVRCP controller to provide information about the last user intent on CT.
192     *
193     * If the user has pressed play in the last attempt then A2DP Sink component will grant focus to
194     * any incoming sound from the phone (and also retain focus for a few seconds before
195     * relinquishing. On the other hand if the user has pressed pause/stop then the A2DP sink
196     * component will take the focus away but also notify the stack to throw away incoming data.
197     */
198    public void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
199        if (mStateMachine != null) {
200            if (keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PLAY &&
201                keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
202                mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PLAY);
203            } else if ((keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE ||
204                       keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_STOP) &&
205                       keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
206                mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PAUSE);
207            }
208        }
209    }
210
211    /**
212     * Called by AVRCP controller to provide information about the last user intent on TG.
213     *
214     * Tf the user has pressed pause on the TG then we can preempt streaming music. This is opposed
215     * to when the streaming stops abruptly (jitter) in which case we will wait for sometime before
216     * stopping playback.
217     */
218    public void informTGStatePlaying(BluetoothDevice device, boolean isPlaying) {
219        if (mStateMachine != null) {
220            if (!isPlaying) {
221                mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE);
222            } else {
223                mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PLAY);
224            }
225        }
226    }
227
228    synchronized boolean isA2dpPlaying(BluetoothDevice device) {
229        enforceCallingOrSelfPermission(BLUETOOTH_PERM,
230                                       "Need BLUETOOTH permission");
231        if (DBG) {
232            Log.d(TAG, "isA2dpPlaying(" + device + ")");
233        }
234        return mStateMachine.isPlaying(device);
235    }
236
237    BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
238        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
239        return mStateMachine.getAudioConfig(device);
240    }
241
242    //Binder object: Must be static class or memory leak may occur
243    private static class BluetoothA2dpSinkBinder extends IBluetoothA2dpSink.Stub
244        implements IProfileServiceBinder {
245        private A2dpSinkService mService;
246
247        private A2dpSinkService getService() {
248            if (!Utils.checkCaller()) {
249                Log.w(TAG,"A2dp call not allowed for non-active user");
250                return null;
251            }
252
253            if (mService != null && mService.isAvailable()) {
254                return mService;
255            }
256            return null;
257        }
258
259        BluetoothA2dpSinkBinder(A2dpSinkService svc) {
260            mService = svc;
261        }
262
263        public boolean cleanup()  {
264            mService = null;
265            return true;
266        }
267
268        public boolean connect(BluetoothDevice device) {
269            A2dpSinkService service = getService();
270            if (service == null) return false;
271            return service.connect(device);
272        }
273
274        public boolean disconnect(BluetoothDevice device) {
275            A2dpSinkService service = getService();
276            if (service == null) return false;
277            return service.disconnect(device);
278        }
279
280        public List<BluetoothDevice> getConnectedDevices() {
281            A2dpSinkService service = getService();
282            if (service == null) return new ArrayList<BluetoothDevice>(0);
283            return service.getConnectedDevices();
284        }
285
286        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
287            A2dpSinkService service = getService();
288            if (service == null) return new ArrayList<BluetoothDevice>(0);
289            return service.getDevicesMatchingConnectionStates(states);
290        }
291
292        public int getConnectionState(BluetoothDevice device) {
293            A2dpSinkService service = getService();
294            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
295            return service.getConnectionState(device);
296        }
297
298        public boolean isA2dpPlaying(BluetoothDevice device) {
299            A2dpSinkService service = getService();
300            if (service == null) return false;
301            return service.isA2dpPlaying(device);
302        }
303
304        public boolean setPriority(BluetoothDevice device, int priority) {
305            A2dpSinkService service = getService();
306            if (service == null) return false;
307            return service.setPriority(device, priority);
308        }
309
310        public int getPriority(BluetoothDevice device) {
311            A2dpSinkService service = getService();
312            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
313            return service.getPriority(device);
314        }
315
316        public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
317            A2dpSinkService service = getService();
318            if (service == null) return null;
319            return service.getAudioConfig(device);
320        }
321    };
322
323    @Override
324    public void dump(StringBuilder sb) {
325        super.dump(sb);
326        if (mStateMachine != null) {
327            mStateMachine.dump(sb);
328        }
329    }
330}
331