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.BluetoothDevice;
21 import android.bluetooth.BluetoothProfile;
22 import android.bluetooth.IBluetoothA2dpSink;
23 import android.content.Intent;
24 import android.provider.Settings;
25 import android.util.Log;
26 
27 import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
28 import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
29 
30 import com.android.bluetooth.btservice.ProfileService;
31 import com.android.bluetooth.Utils;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 /**
37  * Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application.
38  * @hide
39  */
40 public 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 
getName()47     protected String getName() {
48         return TAG;
49     }
50 
initBinder()51     protected IProfileServiceBinder initBinder() {
52         return new BluetoothA2dpSinkBinder(this);
53     }
54 
start()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 
stop()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 
cleanup()79     protected boolean cleanup() {
80         if (mStateMachine!= null) {
81             mStateMachine.cleanup();
82         }
83         clearA2dpSinkService();
84         return true;
85     }
86 
87     //API Methods
88 
getA2dpSinkService()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 
setA2dpSinkService(A2dpSinkService instance)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 
clearA2dpSinkService()119     private static synchronized void clearA2dpSinkService() {
120         sA2dpSinkService = null;
121     }
122 
connect(BluetoothDevice device)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 
disconnect(BluetoothDevice device)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 
getConnectedDevices()154     public List<BluetoothDevice> getConnectedDevices() {
155         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
156         return mStateMachine.getConnectedDevices();
157     }
158 
getDevicesMatchingConnectionStates(int[] states)159     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
160         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
161         return mStateMachine.getDevicesMatchingConnectionStates(states);
162     }
163 
getConnectionState(BluetoothDevice device)164     int getConnectionState(BluetoothDevice device) {
165         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
166         return mStateMachine.getConnectionState(device);
167     }
168 
setPriority(BluetoothDevice device, int priority)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 
getPriority(BluetoothDevice device)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      */
informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode, int keyState)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      */
informTGStatePlaying(BluetoothDevice device, boolean isPlaying)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 
isA2dpPlaying(BluetoothDevice device)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 
getAudioConfig(BluetoothDevice device)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 
getService()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 
BluetoothA2dpSinkBinder(A2dpSinkService svc)259         BluetoothA2dpSinkBinder(A2dpSinkService svc) {
260             mService = svc;
261         }
262 
cleanup()263         public boolean cleanup()  {
264             mService = null;
265             return true;
266         }
267 
connect(BluetoothDevice device)268         public boolean connect(BluetoothDevice device) {
269             A2dpSinkService service = getService();
270             if (service == null) return false;
271             return service.connect(device);
272         }
273 
disconnect(BluetoothDevice device)274         public boolean disconnect(BluetoothDevice device) {
275             A2dpSinkService service = getService();
276             if (service == null) return false;
277             return service.disconnect(device);
278         }
279 
getConnectedDevices()280         public List<BluetoothDevice> getConnectedDevices() {
281             A2dpSinkService service = getService();
282             if (service == null) return new ArrayList<BluetoothDevice>(0);
283             return service.getConnectedDevices();
284         }
285 
getDevicesMatchingConnectionStates(int[] states)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 
getConnectionState(BluetoothDevice device)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 
isA2dpPlaying(BluetoothDevice device)298         public boolean isA2dpPlaying(BluetoothDevice device) {
299             A2dpSinkService service = getService();
300             if (service == null) return false;
301             return service.isA2dpPlaying(device);
302         }
303 
setPriority(BluetoothDevice device, int priority)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 
getPriority(BluetoothDevice device)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 
getAudioConfig(BluetoothDevice device)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
dump(StringBuilder sb)324     public void dump(StringBuilder sb) {
325         super.dump(sb);
326         if (mStateMachine != null) {
327             mStateMachine.dump(sb);
328         }
329     }
330 }
331