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 package com.android.bluetooth.a2dpsink;
17 
18 import android.bluetooth.BluetoothAdapter;
19 import android.bluetooth.BluetoothAudioConfig;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothProfile;
22 import android.bluetooth.IBluetoothA2dpSink;
23 import android.media.AudioManager;
24 import android.util.Log;
25 
26 import com.android.bluetooth.Utils;
27 import com.android.bluetooth.btservice.AdapterService;
28 import com.android.bluetooth.btservice.ProfileService;
29 import com.android.internal.annotations.VisibleForTesting;
30 
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.concurrent.ConcurrentHashMap;
37 
38 /**
39  * Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application.
40  * @hide
41  */
42 public class A2dpSinkService extends ProfileService {
43     private static final String TAG = "A2dpSinkService";
44     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
45     static final int MAXIMUM_CONNECTED_DEVICES = 1;
46 
47     private final BluetoothAdapter mAdapter;
48     protected Map<BluetoothDevice, A2dpSinkStateMachine> mDeviceStateMap =
49             new ConcurrentHashMap<>(1);
50 
51     private final Object mStreamHandlerLock = new Object();
52 
53     private A2dpSinkStreamHandler mA2dpSinkStreamHandler;
54     private static A2dpSinkService sService;
55 
56     static {
classInitNative()57         classInitNative();
58     }
59 
60     @Override
start()61     protected boolean start() {
62         synchronized (mStreamHandlerLock) {
63             mA2dpSinkStreamHandler = new A2dpSinkStreamHandler(this, this);
64         }
65         initNative();
66         setA2dpSinkService(this);
67         return true;
68     }
69 
70     @Override
stop()71     protected boolean stop() {
72         setA2dpSinkService(null);
73         cleanupNative();
74         for (A2dpSinkStateMachine stateMachine : mDeviceStateMap.values()) {
75             stateMachine.quitNow();
76         }
77         mDeviceStateMap.clear();
78         synchronized (mStreamHandlerLock) {
79             if (mA2dpSinkStreamHandler != null) {
80                 mA2dpSinkStreamHandler.cleanup();
81                 mA2dpSinkStreamHandler = null;
82             }
83         }
84         return true;
85     }
86 
getA2dpSinkService()87     public static synchronized A2dpSinkService getA2dpSinkService() {
88         return sService;
89     }
90 
91     /**
92      * Testing API to inject a mockA2dpSinkService.
93      * @hide
94      */
95     @VisibleForTesting
setA2dpSinkService(A2dpSinkService service)96     public static synchronized void setA2dpSinkService(A2dpSinkService service) {
97         sService = service;
98     }
99 
100 
A2dpSinkService()101     public A2dpSinkService() {
102         mAdapter = BluetoothAdapter.getDefaultAdapter();
103     }
104 
105     /**
106      * Request audio focus such that the designated device can stream audio
107      */
requestAudioFocus(BluetoothDevice device, boolean request)108     public void requestAudioFocus(BluetoothDevice device, boolean request) {
109         synchronized (mStreamHandlerLock) {
110             if (mA2dpSinkStreamHandler == null) return;
111             mA2dpSinkStreamHandler.requestAudioFocus(request);
112         }
113     }
114 
115     /**
116      * Get the current Bluetooth Audio focus state
117      *
118      * @return AudioManger.AUDIOFOCUS_* states on success, or AudioManager.ERROR on error
119      */
getFocusState()120     public int getFocusState() {
121         synchronized (mStreamHandlerLock) {
122             if (mA2dpSinkStreamHandler == null) return AudioManager.ERROR;
123             return mA2dpSinkStreamHandler.getFocusState();
124         }
125     }
126 
isA2dpPlaying(BluetoothDevice device)127     boolean isA2dpPlaying(BluetoothDevice device) {
128         enforceCallingOrSelfPermission(
129                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
130         synchronized (mStreamHandlerLock) {
131             if (mA2dpSinkStreamHandler == null) return false;
132             return mA2dpSinkStreamHandler.isPlaying();
133         }
134     }
135 
136     @Override
initBinder()137     protected IProfileServiceBinder initBinder() {
138         return new A2dpSinkServiceBinder(this);
139     }
140 
141     //Binder object: Must be static class or memory leak may occur
142     private static class A2dpSinkServiceBinder extends IBluetoothA2dpSink.Stub
143             implements IProfileServiceBinder {
144         private A2dpSinkService mService;
145 
getService()146         private A2dpSinkService getService() {
147             if (!Utils.checkCaller()) {
148                 Log.w(TAG, "A2dp call not allowed for non-active user");
149                 return null;
150             }
151 
152             if (mService != null) {
153                 return mService;
154             }
155             return null;
156         }
157 
A2dpSinkServiceBinder(A2dpSinkService svc)158         A2dpSinkServiceBinder(A2dpSinkService svc) {
159             mService = svc;
160         }
161 
162         @Override
cleanup()163         public void cleanup() {
164             mService = null;
165         }
166 
167         @Override
connect(BluetoothDevice device)168         public boolean connect(BluetoothDevice device) {
169             A2dpSinkService service = getService();
170             if (service == null) {
171                 return false;
172             }
173             return service.connect(device);
174         }
175 
176         @Override
disconnect(BluetoothDevice device)177         public boolean disconnect(BluetoothDevice device) {
178             A2dpSinkService service = getService();
179             if (service == null) {
180                 return false;
181             }
182             return service.disconnect(device);
183         }
184 
185         @Override
getConnectedDevices()186         public List<BluetoothDevice> getConnectedDevices() {
187             A2dpSinkService service = getService();
188             if (service == null) {
189                 return new ArrayList<BluetoothDevice>(0);
190             }
191             return service.getConnectedDevices();
192         }
193 
194         @Override
getDevicesMatchingConnectionStates(int[] states)195         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
196             A2dpSinkService service = getService();
197             if (service == null) {
198                 return new ArrayList<BluetoothDevice>(0);
199             }
200             return service.getDevicesMatchingConnectionStates(states);
201         }
202 
203         @Override
getConnectionState(BluetoothDevice device)204         public int getConnectionState(BluetoothDevice device) {
205             A2dpSinkService service = getService();
206             if (service == null) {
207                 return BluetoothProfile.STATE_DISCONNECTED;
208             }
209             return service.getConnectionState(device);
210         }
211 
212         @Override
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)213         public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
214             A2dpSinkService service = getService();
215             if (service == null) {
216                 return false;
217             }
218             return service.setConnectionPolicy(device, connectionPolicy);
219         }
220 
221         @Override
getConnectionPolicy(BluetoothDevice device)222         public int getConnectionPolicy(BluetoothDevice device) {
223             A2dpSinkService service = getService();
224             if (service == null) {
225                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
226             }
227             return service.getConnectionPolicy(device);
228         }
229 
230         @Override
isA2dpPlaying(BluetoothDevice device)231         public boolean isA2dpPlaying(BluetoothDevice device) {
232             A2dpSinkService service = getService();
233             if (service == null) {
234                 return false;
235             }
236             return service.isA2dpPlaying(device);
237         }
238 
239         @Override
getAudioConfig(BluetoothDevice device)240         public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
241             A2dpSinkService service = getService();
242             if (service == null) {
243                 return null;
244             }
245             return service.getAudioConfig(device);
246         }
247     }
248 
249     /* Generic Profile Code */
250 
251     /**
252      * Connect the given Bluetooth device.
253      *
254      * @return true if connection is successful, false otherwise.
255      */
connect(BluetoothDevice device)256     public boolean connect(BluetoothDevice device) {
257         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
258                 "Need BLUETOOTH_PRIVILEGED permission");
259         if (device == null) {
260             throw new IllegalArgumentException("Null device");
261         }
262         if (DBG) {
263             StringBuilder sb = new StringBuilder();
264             dump(sb);
265             Log.d(TAG, " connect device: " + device
266                     + ", InstanceMap start state: " + sb.toString());
267         }
268         if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
269             Log.w(TAG, "Connection not allowed: <" + device.getAddress()
270                     + "> is CONNECTION_POLICY_FORBIDDEN");
271             return false;
272         }
273 
274         A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(device);
275         if (stateMachine != null) {
276             stateMachine.connect();
277             return true;
278         } else {
279             // a state machine instance doesn't exist yet, and the max has been reached.
280             Log.e(TAG, "Maxed out on the number of allowed A2DP Sink connections. "
281                     + "Connect request rejected on " + device);
282             return false;
283         }
284     }
285 
286     /**
287      * Disconnect the given Bluetooth device.
288      *
289      * @return true if disconnect is successful, false otherwise.
290      */
disconnect(BluetoothDevice device)291     public boolean disconnect(BluetoothDevice device) {
292         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
293         if (DBG) {
294             StringBuilder sb = new StringBuilder();
295             dump(sb);
296             Log.d(TAG, "A2DP disconnect device: " + device
297                     + ", InstanceMap start state: " + sb.toString());
298         }
299 
300         A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
301         // a state machine instance doesn't exist. maybe it is already gone?
302         if (stateMachine == null) {
303             return false;
304         }
305         int connectionState = stateMachine.getState();
306         if (connectionState == BluetoothProfile.STATE_DISCONNECTED
307                 || connectionState == BluetoothProfile.STATE_DISCONNECTING) {
308             return false;
309         }
310         // upon completion of disconnect, the state machine will remove itself from the available
311         // devices map
312         stateMachine.disconnect();
313         return true;
314     }
315 
removeStateMachine(A2dpSinkStateMachine stateMachine)316     void removeStateMachine(A2dpSinkStateMachine stateMachine) {
317         mDeviceStateMap.remove(stateMachine.getDevice());
318     }
319 
getConnectedDevices()320     public List<BluetoothDevice> getConnectedDevices() {
321         return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
322     }
323 
getOrCreateStateMachine(BluetoothDevice device)324     protected A2dpSinkStateMachine getOrCreateStateMachine(BluetoothDevice device) {
325         A2dpSinkStateMachine newStateMachine = new A2dpSinkStateMachine(device, this);
326         A2dpSinkStateMachine existingStateMachine =
327                 mDeviceStateMap.putIfAbsent(device, newStateMachine);
328         // Given null is not a valid value in our map, ConcurrentHashMap will return null if the
329         // key was absent and our new value was added. We should then start and return it.
330         if (existingStateMachine == null) {
331             newStateMachine.start();
332             return newStateMachine;
333         }
334         return existingStateMachine;
335     }
336 
getDevicesMatchingConnectionStates(int[] states)337     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
338         if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
339         List<BluetoothDevice> deviceList = new ArrayList<>();
340         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
341         int connectionState;
342         for (BluetoothDevice device : bondedDevices) {
343             connectionState = getConnectionState(device);
344             if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState);
345             for (int i = 0; i < states.length; i++) {
346                 if (connectionState == states[i]) {
347                     deviceList.add(device);
348                 }
349             }
350         }
351         if (DBG) Log.d(TAG, deviceList.toString());
352         Log.d(TAG, "GetDevicesDone");
353         return deviceList;
354     }
355 
356     /**
357      * Get the current connection state of the profile
358      *
359      * @param device is the remote bluetooth device
360      * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected,
361      * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected,
362      * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or
363      * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
364      */
getConnectionState(BluetoothDevice device)365     public int getConnectionState(BluetoothDevice device) {
366         A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
367         return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
368                 : stateMachine.getState();
369     }
370 
371     /**
372      * Set connection policy of the profile and connects it if connectionPolicy is
373      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
374      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
375      *
376      * <p> The device should already be paired.
377      * Connection policy can be one of:
378      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
379      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
380      * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
381      *
382      * @param device Paired bluetooth device
383      * @param connectionPolicy is the connection policy to set to for this profile
384      * @return true if connectionPolicy is set, false on error
385      */
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)386     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
387         enforceCallingOrSelfPermission(
388                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
389         if (DBG) {
390             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
391         }
392         AdapterService.getAdapterService().getDatabase()
393                 .setProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK, connectionPolicy);
394         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
395             connect(device);
396         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
397             disconnect(device);
398         }
399         return true;
400     }
401 
402     /**
403      * Get the connection policy of the profile.
404      *
405      * @param device the remote device
406      * @return connection policy of the specified device
407      */
getConnectionPolicy(BluetoothDevice device)408     public int getConnectionPolicy(BluetoothDevice device) {
409         enforceCallingOrSelfPermission(
410                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
411         return AdapterService.getAdapterService().getDatabase()
412                 .getProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK);
413     }
414 
415 
416     @Override
dump(StringBuilder sb)417     public void dump(StringBuilder sb) {
418         super.dump(sb);
419         ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size());
420         for (A2dpSinkStateMachine stateMachine : mDeviceStateMap.values()) {
421             ProfileService.println(sb,
422                     "==== StateMachine for " + stateMachine.getDevice() + " ====");
423             stateMachine.dump(sb);
424         }
425     }
426 
getAudioConfig(BluetoothDevice device)427     BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
428         A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
429         // a state machine instance doesn't exist. maybe it is already gone?
430         if (stateMachine == null) {
431             return null;
432         }
433         return stateMachine.getAudioConfig();
434     }
435 
436     /* JNI interfaces*/
437 
classInitNative()438     private static native void classInitNative();
439 
initNative()440     private native void initNative();
441 
cleanupNative()442     private native void cleanupNative();
443 
connectA2dpNative(byte[] address)444     native boolean connectA2dpNative(byte[] address);
445 
disconnectA2dpNative(byte[] address)446     native boolean disconnectA2dpNative(byte[] address);
447 
448     /**
449      * set A2DP state machine as the active device
450      * the active device is the only one that will receive passthrough commands and the only one
451      * that will have its audio decoded
452      *
453      * @hide
454      * @param address
455      * @return active device request has been scheduled
456      */
setActiveDeviceNative(byte[] address)457     public native boolean setActiveDeviceNative(byte[] address);
458 
459     /**
460      * inform A2DP decoder of the current audio focus
461      *
462      * @param focusGranted
463      */
464     @VisibleForTesting
informAudioFocusStateNative(int focusGranted)465     public native void informAudioFocusStateNative(int focusGranted);
466 
467     /**
468      * inform A2DP decoder the desired audio gain
469      *
470      * @param gain
471      */
472     @VisibleForTesting
informAudioTrackGainNative(float gain)473     public native void informAudioTrackGainNative(float gain);
474 
onConnectionStateChanged(byte[] address, int state)475     private void onConnectionStateChanged(byte[] address, int state) {
476         StackEvent event = StackEvent.connectionStateChanged(getDevice(address), state);
477         A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice);
478         stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
479     }
480 
onAudioStateChanged(byte[] address, int state)481     private void onAudioStateChanged(byte[] address, int state) {
482         synchronized (mStreamHandlerLock) {
483             if (mA2dpSinkStreamHandler == null) {
484                 Log.e(TAG, "Received audio state change before we've been started");
485                 return;
486             } else if (state == StackEvent.AUDIO_STATE_STARTED) {
487                 mA2dpSinkStreamHandler.obtainMessage(
488                         A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
489             } else if (state == StackEvent.AUDIO_STATE_STOPPED
490                     || state == StackEvent.AUDIO_STATE_REMOTE_SUSPEND) {
491                 mA2dpSinkStreamHandler.obtainMessage(
492                         A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
493             }
494         }
495     }
496 
onAudioConfigChanged(byte[] address, int sampleRate, int channelCount)497     private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
498         StackEvent event = StackEvent.audioConfigChanged(getDevice(address), sampleRate,
499                 channelCount);
500         A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice);
501         stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
502     }
503 }
504