• 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 android.bluetooth;
18 
19 import android.Manifest;
20 import android.annotation.NonNull;
21 import android.annotation.RequiresPermission;
22 import android.annotation.SdkConstant;
23 import android.annotation.SdkConstant.SdkConstantType;
24 import android.annotation.SuppressLint;
25 import android.annotation.SystemApi;
26 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
27 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
28 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
29 import android.compat.annotation.UnsupportedAppUsage;
30 import android.content.AttributionSource;
31 import android.content.Context;
32 import android.os.Build;
33 import android.os.IBinder;
34 import android.os.RemoteException;
35 import android.util.Log;
36 
37 import java.util.Collections;
38 import java.util.List;
39 
40 /**
41  * This class provides the public APIs to control the Bluetooth A2DP Sink profile.
42  *
43  * <p>BluetoothA2dpSink is a proxy object for controlling the Bluetooth A2DP Sink Service via IPC.
44  * Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothA2dpSink proxy object.
45  *
46  * @hide
47  */
48 @SystemApi
49 public final class BluetoothA2dpSink implements BluetoothProfile {
50     private static final String TAG = "BluetoothA2dpSink";
51     private static final boolean DBG = true;
52     private static final boolean VDBG = false;
53 
54     /**
55      * Intent used to broadcast the change in connection state of the A2DP Sink profile.
56      *
57      * <p>This intent will have 3 extras:
58      *
59      * <ul>
60      *   <li>{@link #EXTRA_STATE} - The current state of the profile.
61      *   <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
62      *   <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
63      * </ul>
64      *
65      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
66      * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
67      * #STATE_DISCONNECTING}.
68      *
69      * @hide
70      */
71     @SystemApi
72     @SuppressLint("ActionValue")
73     @RequiresBluetoothConnectPermission
74     @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
75     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
76     public static final String ACTION_CONNECTION_STATE_CHANGED =
77             "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
78 
79     private final BluetoothAdapter mAdapter;
80     private final AttributionSource mAttributionSource;
81 
82     private IBluetoothA2dpSink mService;
83 
84     /**
85      * Create a BluetoothA2dp proxy object for interacting with the local Bluetooth A2DP service.
86      */
BluetoothA2dpSink(Context context, BluetoothAdapter adapter)87     /* package */ BluetoothA2dpSink(Context context, BluetoothAdapter adapter) {
88         mAdapter = adapter;
89         mAttributionSource = adapter.getAttributionSource();
90         mService = null;
91     }
92 
93     /** @hide */
94     @Override
onServiceConnected(IBinder service)95     public void onServiceConnected(IBinder service) {
96         mService = IBluetoothA2dpSink.Stub.asInterface(service);
97     }
98 
99     /** @hide */
100     @Override
onServiceDisconnected()101     public void onServiceDisconnected() {
102         mService = null;
103     }
104 
getService()105     private IBluetoothA2dpSink getService() {
106         return mService;
107     }
108 
109     /** @hide */
110     @Override
getAdapter()111     public BluetoothAdapter getAdapter() {
112         return mAdapter;
113     }
114 
115     @Override
116     @SuppressWarnings("Finalize") // empty finalize for api signature
finalize()117     public void finalize() {}
118 
119     /**
120      * Initiate connection to a profile of the remote bluetooth device.
121      *
122      * <p>Currently, the system supports only 1 connection to the A2DP profile. The API will
123      * automatically disconnect connected devices before connecting.
124      *
125      * <p>This API returns false in scenarios like the profile on the device is already connected or
126      * Bluetooth is not turned on. When this API returns true, it is guaranteed that connection
127      * state intent for the profile will be broadcasted with the state. Users can get the connection
128      * state of the profile from this intent.
129      *
130      * @param device Remote Bluetooth Device
131      * @return false on immediate error, true otherwise
132      * @hide
133      */
134     @RequiresBluetoothConnectPermission
135     @RequiresPermission(
136             allOf = {
137                 android.Manifest.permission.BLUETOOTH_CONNECT,
138                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
139             })
connect(BluetoothDevice device)140     public boolean connect(BluetoothDevice device) {
141         if (DBG) log("connect(" + device + ")");
142         final IBluetoothA2dpSink service = getService();
143         if (service == null) {
144             Log.w(TAG, "Proxy not attached to service");
145             if (DBG) log(Log.getStackTraceString(new Throwable()));
146         } else if (isEnabled() && isValidDevice(device)) {
147             try {
148                 return service.connect(device, mAttributionSource);
149             } catch (RemoteException e) {
150                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
151             }
152         }
153         return false;
154     }
155 
156     /**
157      * Initiate disconnection from a profile
158      *
159      * <p>This API will return false in scenarios like the profile on the Bluetooth device is not in
160      * connected state etc. When this API returns, true, it is guaranteed that the connection state
161      * change intent will be broadcasted with the state. Users can get the disconnection state of
162      * the profile from this intent.
163      *
164      * <p>If the disconnection is initiated by a remote device, the state will transition from
165      * {@link #STATE_CONNECTED} to {@link #STATE_DISCONNECTED}. If the disconnect is initiated by
166      * the host (local) device the state will transition from {@link #STATE_CONNECTED} to state
167      * {@link #STATE_DISCONNECTING} to state {@link #STATE_DISCONNECTED}. The transition to {@link
168      * #STATE_DISCONNECTING} can be used to distinguish between the two scenarios.
169      *
170      * @param device Remote Bluetooth Device
171      * @return false on immediate error, true otherwise
172      * @hide
173      */
174     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
175     @RequiresLegacyBluetoothAdminPermission
176     @RequiresBluetoothConnectPermission
177     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
disconnect(BluetoothDevice device)178     public boolean disconnect(BluetoothDevice device) {
179         if (DBG) log("disconnect(" + device + ")");
180         final IBluetoothA2dpSink service = getService();
181         if (service == null) {
182             Log.w(TAG, "Proxy not attached to service");
183             if (DBG) log(Log.getStackTraceString(new Throwable()));
184         } else if (isEnabled() && isValidDevice(device)) {
185             try {
186                 return service.disconnect(device, mAttributionSource);
187             } catch (RemoteException e) {
188                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
189             }
190         }
191         return false;
192     }
193 
194     /**
195      * {@inheritDoc}
196      *
197      * @hide
198      */
199     @Override
200     @RequiresBluetoothConnectPermission
201     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getConnectedDevices()202     public List<BluetoothDevice> getConnectedDevices() {
203         if (VDBG) log("getConnectedDevices()");
204         final IBluetoothA2dpSink service = getService();
205         if (service == null) {
206             Log.w(TAG, "Proxy not attached to service");
207             if (DBG) log(Log.getStackTraceString(new Throwable()));
208         } else if (isEnabled()) {
209             try {
210                 return Attributable.setAttributionSource(
211                         service.getConnectedDevices(mAttributionSource), mAttributionSource);
212             } catch (RemoteException e) {
213                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
214             }
215         }
216         return Collections.emptyList();
217     }
218 
219     /**
220      * {@inheritDoc}
221      *
222      * @hide
223      */
224     @Override
225     @RequiresBluetoothConnectPermission
226     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getDevicesMatchingConnectionStates(int[] states)227     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
228         if (VDBG) log("getDevicesMatchingStates()");
229         final IBluetoothA2dpSink service = getService();
230         if (service == null) {
231             Log.w(TAG, "Proxy not attached to service");
232             if (DBG) log(Log.getStackTraceString(new Throwable()));
233         } else if (isEnabled()) {
234             try {
235                 return Attributable.setAttributionSource(
236                         service.getDevicesMatchingConnectionStates(states, mAttributionSource),
237                         mAttributionSource);
238             } catch (RemoteException e) {
239                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
240             }
241         }
242         return Collections.emptyList();
243     }
244 
245     /**
246      * {@inheritDoc}
247      *
248      * @hide
249      */
250     @Override
251     @RequiresBluetoothConnectPermission
252     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getConnectionState(BluetoothDevice device)253     public int getConnectionState(BluetoothDevice device) {
254         if (VDBG) log("getConnectionState(" + device + ")");
255         final IBluetoothA2dpSink service = getService();
256         if (service == null) {
257             Log.w(TAG, "Proxy not attached to service");
258             if (DBG) log(Log.getStackTraceString(new Throwable()));
259         } else if (isEnabled() && isValidDevice(device)) {
260             try {
261                 return service.getConnectionState(device, mAttributionSource);
262             } catch (RemoteException e) {
263                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
264             }
265         }
266         return BluetoothProfile.STATE_DISCONNECTED;
267     }
268 
269     /**
270      * Get the current audio configuration for the A2DP source device, or null if the device has no
271      * audio configuration
272      *
273      * @param device Remote bluetooth device.
274      * @return audio configuration for the device, or null
275      * @see BluetoothAudioConfig
276      * @hide
277      */
278     @RequiresLegacyBluetoothPermission
279     @RequiresBluetoothConnectPermission
280     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getAudioConfig(BluetoothDevice device)281     public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
282         if (VDBG) log("getAudioConfig(" + device + ")");
283         final IBluetoothA2dpSink service = getService();
284         if (service == null) {
285             Log.w(TAG, "Proxy not attached to service");
286             if (DBG) log(Log.getStackTraceString(new Throwable()));
287         } else if (isEnabled() && isValidDevice(device)) {
288             try {
289                 return service.getAudioConfig(device, mAttributionSource);
290             } catch (RemoteException e) {
291                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
292             }
293         }
294         return null;
295     }
296 
297     /**
298      * Set priority of the profile
299      *
300      * <p>The device should already be paired. Priority can be one of {@link #PRIORITY_ON} or {@link
301      * #PRIORITY_OFF}
302      *
303      * @param device Paired bluetooth device
304      * @return true if priority is set, false on error
305      * @hide
306      */
307     @RequiresBluetoothConnectPermission
308     @RequiresPermission(
309             allOf = {
310                 android.Manifest.permission.BLUETOOTH_CONNECT,
311                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
312             })
setPriority(BluetoothDevice device, int priority)313     public boolean setPriority(BluetoothDevice device, int priority) {
314         if (DBG) log("setPriority(" + device + ", " + priority + ")");
315         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
316     }
317 
318     /**
319      * Set connection policy of the profile
320      *
321      * <p>The device should already be paired. Connection policy can be one of {@link
322      * #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, {@link
323      * #CONNECTION_POLICY_UNKNOWN}
324      *
325      * @param device Paired bluetooth device
326      * @param connectionPolicy is the connection policy to set to for this profile
327      * @return true if connectionPolicy is set, false on error
328      * @hide
329      */
330     @SystemApi
331     @RequiresBluetoothConnectPermission
332     @RequiresPermission(
333             allOf = {
334                 android.Manifest.permission.BLUETOOTH_CONNECT,
335                 android.Manifest.permission.BLUETOOTH_PRIVILEGED
336             })
setConnectionPolicy( @onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)337     public boolean setConnectionPolicy(
338             @NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) {
339         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
340         final IBluetoothA2dpSink service = getService();
341         if (service == null) {
342             Log.w(TAG, "Proxy not attached to service");
343             if (DBG) log(Log.getStackTraceString(new Throwable()));
344         } else if (isEnabled()
345                 && isValidDevice(device)
346                 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
347                         || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
348             try {
349                 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
350             } catch (RemoteException e) {
351                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
352             }
353         }
354         return false;
355     }
356 
357     /**
358      * Get the priority of the profile.
359      *
360      * <p>The priority can be any of: {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link
361      * #PRIORITY_UNDEFINED}
362      *
363      * @param device Bluetooth device
364      * @return priority of the device
365      * @hide
366      */
367     @RequiresBluetoothConnectPermission
368     @RequiresPermission(
369             allOf = {
370                 android.Manifest.permission.BLUETOOTH_CONNECT,
371                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
372             })
getPriority(BluetoothDevice device)373     public int getPriority(BluetoothDevice device) {
374         if (VDBG) log("getPriority(" + device + ")");
375         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
376     }
377 
378     /**
379      * Get the connection policy of the profile.
380      *
381      * <p>The connection policy can be any of: {@link #CONNECTION_POLICY_ALLOWED}, {@link
382      * #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
383      *
384      * @param device Bluetooth device
385      * @return connection policy of the device
386      * @hide
387      */
388     @SystemApi
389     @RequiresBluetoothConnectPermission
390     @RequiresPermission(
391             allOf = {
392                 android.Manifest.permission.BLUETOOTH_CONNECT,
393                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
394             })
getConnectionPolicy(@onNull BluetoothDevice device)395     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
396         if (VDBG) log("getConnectionPolicy(" + device + ")");
397         final IBluetoothA2dpSink service = getService();
398         if (service == null) {
399             Log.w(TAG, "Proxy not attached to service");
400             if (DBG) log(Log.getStackTraceString(new Throwable()));
401         } else if (isEnabled() && isValidDevice(device)) {
402             try {
403                 return service.getConnectionPolicy(device, mAttributionSource);
404             } catch (RemoteException e) {
405                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
406             }
407         }
408         return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
409     }
410 
411     /**
412      * Check if audio is playing on the bluetooth device (A2DP profile is streaming music).
413      *
414      * @param device BluetoothDevice device
415      * @return true if audio is playing (A2dp is streaming music), false otherwise
416      * @hide
417      */
418     @SystemApi
419     @RequiresBluetoothConnectPermission
420     @RequiresPermission(
421             allOf = {
422                 android.Manifest.permission.BLUETOOTH_CONNECT,
423                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
424             })
isAudioPlaying(@onNull BluetoothDevice device)425     public boolean isAudioPlaying(@NonNull BluetoothDevice device) {
426         if (VDBG) log("isAudioPlaying(" + device + ")");
427         final IBluetoothA2dpSink service = getService();
428         if (service == null) {
429             Log.w(TAG, "Proxy not attached to service");
430             if (DBG) log(Log.getStackTraceString(new Throwable()));
431         } else if (isEnabled() && isValidDevice(device)) {
432             try {
433                 return service.isA2dpPlaying(device, mAttributionSource);
434             } catch (RemoteException e) {
435                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
436             }
437         }
438         return false;
439     }
440 
441     /**
442      * Helper for converting a state to a string.
443      *
444      * <p>For debug use only - strings are not internationalized.
445      *
446      * @hide
447      */
stateToString(int state)448     public static String stateToString(int state) {
449         switch (state) {
450             case STATE_DISCONNECTED:
451                 return "disconnected";
452             case STATE_CONNECTING:
453                 return "connecting";
454             case STATE_CONNECTED:
455                 return "connected";
456             case STATE_DISCONNECTING:
457                 return "disconnecting";
458             case BluetoothA2dp.STATE_PLAYING:
459                 return "playing";
460             case BluetoothA2dp.STATE_NOT_PLAYING:
461                 return "not playing";
462             default:
463                 return "<unknown state " + state + ">";
464         }
465     }
466 
isEnabled()467     private boolean isEnabled() {
468         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
469     }
470 
isValidDevice(BluetoothDevice device)471     private static boolean isValidDevice(BluetoothDevice device) {
472         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
473     }
474 
log(String msg)475     private static void log(String msg) {
476         Log.d(TAG, msg);
477     }
478 }
479