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