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