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