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