1 /*
2  * Copyright (C) 2008 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.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SdkConstant;
24 import android.annotation.SuppressLint;
25 import android.annotation.SdkConstant.SdkConstantType;
26 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
27 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
28 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
29 import android.annotation.SystemApi;
30 import android.compat.annotation.UnsupportedAppUsage;
31 import android.content.Attributable;
32 import android.content.AttributionSource;
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.content.pm.PackageManager;
36 import android.os.Binder;
37 import android.os.Build;
38 import android.os.Handler;
39 import android.os.IBinder;
40 import android.os.Looper;
41 import android.os.Message;
42 import android.os.RemoteException;
43 import android.util.CloseGuard;
44 import android.util.Log;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 
49 /**
50  * Public API for controlling the Bluetooth Headset Service. This includes both
51  * Bluetooth Headset and Handsfree (v1.5) profiles.
52  *
53  * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
54  * Service via IPC.
55  *
56  * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
57  * the BluetoothHeadset proxy object. Use
58  * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
59  *
60  * <p> Android only supports one connected Bluetooth Headset at a time.
61  * Each method is protected with its appropriate permission.
62  */
63 public final class BluetoothHeadset implements BluetoothProfile {
64     private static final String TAG = "BluetoothHeadset";
65     private static final boolean DBG = true;
66     private static final boolean VDBG = false;
67 
68     /**
69      * Intent used to broadcast the change in connection state of the Headset
70      * profile.
71      *
72      * <p>This intent will have 3 extras:
73      * <ul>
74      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
75      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
76      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
77      * </ul>
78      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
79      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
80      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
81      */
82     @RequiresLegacyBluetoothPermission
83     @RequiresBluetoothConnectPermission
84     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
85     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
86     public static final String ACTION_CONNECTION_STATE_CHANGED =
87             "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
88 
89     /**
90      * Intent used to broadcast the change in the Audio Connection state of the
91      * HFP profile.
92      *
93      * <p>This intent will have 3 extras:
94      * <ul>
95      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
96      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
97      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
98      * </ul>
99      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
100      * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
101      */
102     @RequiresLegacyBluetoothPermission
103     @RequiresBluetoothConnectPermission
104     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
105     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
106     public static final String ACTION_AUDIO_STATE_CHANGED =
107             "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
108 
109     /**
110      * Intent used to broadcast the selection of a connected device as active.
111      *
112      * <p>This intent will have one extra:
113      * <ul>
114      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
115      * be null if no device is active. </li>
116      * </ul>
117      *
118      * @hide
119      */
120     @RequiresLegacyBluetoothPermission
121     @RequiresBluetoothConnectPermission
122     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
123     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
124     @UnsupportedAppUsage(trackingBug = 171933273)
125     public static final String ACTION_ACTIVE_DEVICE_CHANGED =
126             "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
127 
128     /**
129      * Intent used to broadcast that the headset has posted a
130      * vendor-specific event.
131      *
132      * <p>This intent will have 4 extras and 1 category.
133      * <ul>
134      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
135      * </li>
136      * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
137      * specific command </li>
138      * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
139      * command type which can be one of  {@link #AT_CMD_TYPE_READ},
140      * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
141      * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
142      * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
143      * arguments. </li>
144      * </ul>
145      *
146      * <p> The category is the Company ID of the vendor defining the
147      * vendor-specific command. {@link BluetoothAssignedNumbers}
148      *
149      * For example, for Plantronics specific events
150      * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
151      *
152      * <p> For example, an AT+XEVENT=foo,3 will get translated into
153      * <ul>
154      * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
155      * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
156      * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
157      * </ul>
158      */
159     @RequiresLegacyBluetoothPermission
160     @RequiresBluetoothConnectPermission
161     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
162     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
163     public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
164             "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
165 
166     /**
167      * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
168      * intents that contains the name of the vendor-specific command.
169      */
170     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
171             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
172 
173     /**
174      * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
175      * intents that contains the AT command type of the vendor-specific command.
176      */
177     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
178             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
179 
180     /**
181      * AT command type READ used with
182      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
183      * For example, AT+VGM?. There are no arguments for this command type.
184      */
185     public static final int AT_CMD_TYPE_READ = 0;
186 
187     /**
188      * AT command type TEST used with
189      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
190      * For example, AT+VGM=?. There are no arguments for this command type.
191      */
192     public static final int AT_CMD_TYPE_TEST = 1;
193 
194     /**
195      * AT command type SET used with
196      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
197      * For example, AT+VGM=<args>.
198      */
199     public static final int AT_CMD_TYPE_SET = 2;
200 
201     /**
202      * AT command type BASIC used with
203      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
204      * For example, ATD. Single character commands and everything following the
205      * character are arguments.
206      */
207     public static final int AT_CMD_TYPE_BASIC = 3;
208 
209     /**
210      * AT command type ACTION used with
211      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
212      * For example, AT+CHUP. There are no arguments for action commands.
213      */
214     public static final int AT_CMD_TYPE_ACTION = 4;
215 
216     /**
217      * A Parcelable String array extra field in
218      * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
219      * the arguments to the vendor-specific command.
220      */
221     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
222             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
223 
224     /**
225      * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
226      * for the companyId
227      */
228     public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY =
229             "android.bluetooth.headset.intent.category.companyid";
230 
231     /**
232      * A vendor-specific command for unsolicited result code.
233      */
234     public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
235 
236     /**
237      * A vendor-specific AT command
238      *
239      * @hide
240      */
241     public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL";
242 
243     /**
244      * A vendor-specific AT command
245      *
246      * @hide
247      */
248     public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV";
249 
250     /**
251      * Battery level indicator associated with
252      * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV}
253      *
254      * @hide
255      */
256     public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1;
257 
258     /**
259      * A vendor-specific AT command
260      *
261      * @hide
262      */
263     public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT";
264 
265     /**
266      * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT}
267      *
268      * @hide
269      */
270     public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY";
271 
272     /**
273      * Headset state when SCO audio is not connected.
274      * This state can be one of
275      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
276      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
277      */
278     public static final int STATE_AUDIO_DISCONNECTED = 10;
279 
280     /**
281      * Headset state when SCO audio is connecting.
282      * This state can be one of
283      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
284      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
285      */
286     public static final int STATE_AUDIO_CONNECTING = 11;
287 
288     /**
289      * Headset state when SCO audio is connected.
290      * This state can be one of
291      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
292      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
293      */
294     public static final int STATE_AUDIO_CONNECTED = 12;
295 
296     /**
297      * Intent used to broadcast the headset's indicator status
298      *
299      * <p>This intent will have 3 extras:
300      * <ul>
301      * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which
302      * is supported by the headset ( as indicated by AT+BIND command in the SLC
303      * sequence) or whose value is changed (indicated by AT+BIEV command) </li>
304      * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li>
305      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li>
306      * </ul>
307      * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators
308      * are given an assigned number. Below shows the assigned number of Indicator added so far
309      * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled
310      * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery
311      *
312      * @hide
313      */
314     @RequiresLegacyBluetoothPermission
315     @RequiresBluetoothConnectPermission
316     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
317     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
318     public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
319             "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
320 
321     /**
322      * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
323      * intents that contains the assigned number of the headset indicator as defined by
324      * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7
325      *
326      * @hide
327      */
328     public static final String EXTRA_HF_INDICATORS_IND_ID =
329             "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
330 
331     /**
332      * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
333      * intents that contains the value of the Headset indicator that is being sent.
334      *
335      * @hide
336      */
337     public static final String EXTRA_HF_INDICATORS_IND_VALUE =
338             "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
339 
340     private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
341     private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
342 
343     private final CloseGuard mCloseGuard = new CloseGuard();
344 
345     private Context mContext;
346     private ServiceListener mServiceListener;
347     private volatile IBluetoothHeadset mService;
348     private final BluetoothAdapter mAdapter;
349     private final AttributionSource mAttributionSource;
350 
351     @SuppressLint("AndroidFrameworkBluetoothPermission")
352     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
353             new IBluetoothStateChangeCallback.Stub() {
354                 public void onBluetoothStateChange(boolean up) {
355                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
356                     if (!up) {
357                         doUnbind();
358                     } else {
359                         doBind();
360                     }
361                 }
362             };
363 
364     /**
365      * Create a BluetoothHeadset proxy object.
366      */
BluetoothHeadset(Context context, ServiceListener l, BluetoothAdapter adapter)367     /* package */ BluetoothHeadset(Context context, ServiceListener l, BluetoothAdapter adapter) {
368         mContext = context;
369         mServiceListener = l;
370         mAdapter = adapter;
371         mAttributionSource = adapter.getAttributionSource();
372 
373         // Preserve legacy compatibility where apps were depending on
374         // registerStateChangeCallback() performing a permissions check which
375         // has been relaxed in modern platform versions
376         if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R
377                 && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH)
378                         != PackageManager.PERMISSION_GRANTED) {
379             throw new SecurityException("Need BLUETOOTH permission");
380         }
381 
382         IBluetoothManager mgr = mAdapter.getBluetoothManager();
383         if (mgr != null) {
384             try {
385                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
386             } catch (RemoteException e) {
387                 Log.e(TAG, "", e);
388             }
389         }
390 
391         doBind();
392         mCloseGuard.open("close");
393     }
394 
doBind()395     private boolean doBind() {
396         synchronized (mConnection) {
397             if (mService == null) {
398                 if (VDBG) Log.d(TAG, "Binding service...");
399                 try {
400                     return mAdapter.getBluetoothManager().bindBluetoothProfileService(
401                             BluetoothProfile.HEADSET, mConnection);
402                 } catch (RemoteException e) {
403                     Log.e(TAG, "Unable to bind HeadsetService", e);
404                 }
405             }
406         }
407         return false;
408     }
409 
doUnbind()410     private void doUnbind() {
411         synchronized (mConnection) {
412             if (mService != null) {
413                 if (VDBG) Log.d(TAG, "Unbinding service...");
414                 try {
415                     mAdapter.getBluetoothManager().unbindBluetoothProfileService(
416                             BluetoothProfile.HEADSET, mConnection);
417                 } catch (RemoteException e) {
418                     Log.e(TAG, "Unable to unbind HeadsetService", e);
419                 } finally {
420                     mService = null;
421                 }
422             }
423         }
424     }
425 
426     /**
427      * Close the connection to the backing service.
428      * Other public functions of BluetoothHeadset will return default error
429      * results once close() has been called. Multiple invocations of close()
430      * are ok.
431      */
432     @UnsupportedAppUsage
close()433     /*package*/ void close() {
434         if (VDBG) log("close()");
435 
436         IBluetoothManager mgr = mAdapter.getBluetoothManager();
437         if (mgr != null) {
438             try {
439                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
440             } catch (RemoteException re) {
441                 Log.e(TAG, "", re);
442             }
443         }
444         mServiceListener = null;
445         doUnbind();
446         mCloseGuard.close();
447     }
448 
449     /** {@hide} */
450     @Override
finalize()451     protected void finalize() throws Throwable {
452         mCloseGuard.warnIfOpen();
453         close();
454     }
455 
456     /**
457      * Initiate connection to a profile of the remote bluetooth device.
458      *
459      * <p> Currently, the system supports only 1 connection to the
460      * headset/handsfree profile. The API will automatically disconnect connected
461      * devices before connecting.
462      *
463      * <p> This API returns false in scenarios like the profile on the
464      * device is already connected or Bluetooth is not turned on.
465      * When this API returns true, it is guaranteed that
466      * connection state intent for the profile will be broadcasted with
467      * the state. Users can get the connection state of the profile
468      * from this intent.
469      *
470      * @param device Remote Bluetooth Device
471      * @return false on immediate error, true otherwise
472      * @hide
473      */
474     @SystemApi
475     @RequiresLegacyBluetoothAdminPermission
476     @RequiresBluetoothConnectPermission
477     @RequiresPermission(allOf = {
478             android.Manifest.permission.BLUETOOTH_CONNECT,
479             android.Manifest.permission.MODIFY_PHONE_STATE,
480     })
connect(BluetoothDevice device)481     public boolean connect(BluetoothDevice device) {
482         if (DBG) log("connect(" + device + ")");
483         final IBluetoothHeadset service = mService;
484         if (service != null && isEnabled() && isValidDevice(device)) {
485             try {
486                 return service.connect(device);
487             } catch (RemoteException e) {
488                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
489                 return false;
490             }
491         }
492         if (service == null) Log.w(TAG, "Proxy not attached to service");
493         return false;
494     }
495 
496     /**
497      * Initiate disconnection from a profile
498      *
499      * <p> This API will return false in scenarios like the profile on the
500      * Bluetooth device is not in connected state etc. When this API returns,
501      * true, it is guaranteed that the connection state change
502      * intent will be broadcasted with the state. Users can get the
503      * disconnection state of the profile from this intent.
504      *
505      * <p> If the disconnection is initiated by a remote device, the state
506      * will transition from {@link #STATE_CONNECTED} to
507      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
508      * host (local) device the state will transition from
509      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
510      * state {@link #STATE_DISCONNECTED}. The transition to
511      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
512      * two scenarios.
513      *
514      * @param device Remote Bluetooth Device
515      * @return false on immediate error, true otherwise
516      * @hide
517      */
518     @SystemApi
519     @RequiresLegacyBluetoothAdminPermission
520     @RequiresBluetoothConnectPermission
521     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
disconnect(BluetoothDevice device)522     public boolean disconnect(BluetoothDevice device) {
523         if (DBG) log("disconnect(" + device + ")");
524         final IBluetoothHeadset service = mService;
525         if (service != null && isEnabled() && isValidDevice(device)) {
526             try {
527                 return service.disconnect(device);
528             } catch (RemoteException e) {
529                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
530                 return false;
531             }
532         }
533         if (service == null) Log.w(TAG, "Proxy not attached to service");
534         return false;
535     }
536 
537     /**
538      * {@inheritDoc}
539      */
540     @Override
541     @RequiresBluetoothConnectPermission
542     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getConnectedDevices()543     public List<BluetoothDevice> getConnectedDevices() {
544         if (VDBG) log("getConnectedDevices()");
545         final IBluetoothHeadset service = mService;
546         if (service != null && isEnabled()) {
547             try {
548                 return Attributable.setAttributionSource(
549                         service.getConnectedDevicesWithAttribution(mAttributionSource),
550                         mAttributionSource);
551             } catch (RemoteException e) {
552                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
553                 return new ArrayList<BluetoothDevice>();
554             }
555         }
556         if (service == null) Log.w(TAG, "Proxy not attached to service");
557         return new ArrayList<BluetoothDevice>();
558     }
559 
560     /**
561      * {@inheritDoc}
562      */
563     @Override
564     @RequiresBluetoothConnectPermission
565     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getDevicesMatchingConnectionStates(int[] states)566     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
567         if (VDBG) log("getDevicesMatchingStates()");
568         final IBluetoothHeadset service = mService;
569         if (service != null && isEnabled()) {
570             try {
571                 return Attributable.setAttributionSource(
572                         service.getDevicesMatchingConnectionStates(states, mAttributionSource),
573                         mAttributionSource);
574             } catch (RemoteException e) {
575                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
576                 return new ArrayList<BluetoothDevice>();
577             }
578         }
579         if (service == null) Log.w(TAG, "Proxy not attached to service");
580         return new ArrayList<BluetoothDevice>();
581     }
582 
583     /**
584      * {@inheritDoc}
585      */
586     @Override
587     @RequiresBluetoothConnectPermission
588     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getConnectionState(BluetoothDevice device)589     public int getConnectionState(BluetoothDevice device) {
590         if (VDBG) log("getConnectionState(" + device + ")");
591         final IBluetoothHeadset service = mService;
592         if (service != null && isEnabled() && isValidDevice(device)) {
593             try {
594                 return service.getConnectionState(device);
595             } catch (RemoteException e) {
596                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
597                 return BluetoothProfile.STATE_DISCONNECTED;
598             }
599         }
600         if (service == null) Log.w(TAG, "Proxy not attached to service");
601         return BluetoothProfile.STATE_DISCONNECTED;
602     }
603 
604     /**
605      * Set priority of the profile
606      *
607      * <p> The device should already be paired.
608      * Priority can be one of {@link BluetoothProfile#PRIORITY_ON} or
609      * {@link BluetoothProfile#PRIORITY_OFF}
610      *
611      * @param device Paired bluetooth device
612      * @param priority
613      * @return true if priority is set, false on error
614      * @hide
615      * @deprecated Replaced with {@link #setConnectionPolicy(BluetoothDevice, int)}
616      * @removed
617      */
618     @Deprecated
619     @SystemApi
620     @RequiresLegacyBluetoothAdminPermission
621     @RequiresBluetoothConnectPermission
622     @RequiresPermission(allOf = {
623             android.Manifest.permission.BLUETOOTH_CONNECT,
624             android.Manifest.permission.MODIFY_PHONE_STATE,
625     })
setPriority(BluetoothDevice device, int priority)626     public boolean setPriority(BluetoothDevice device, int priority) {
627         if (DBG) log("setPriority(" + device + ", " + priority + ")");
628         final IBluetoothHeadset service = mService;
629         if (service != null && isEnabled() && isValidDevice(device)) {
630             if (priority != BluetoothProfile.PRIORITY_OFF
631                     && priority != BluetoothProfile.PRIORITY_ON) {
632                 return false;
633             }
634             try {
635                 return service.setPriority(
636                         device, BluetoothAdapter.priorityToConnectionPolicy(priority),
637                         mAttributionSource);
638             } catch (RemoteException e) {
639                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
640                 return false;
641             }
642         }
643         if (service == null) Log.w(TAG, "Proxy not attached to service");
644         return false;
645     }
646 
647     /**
648      * Set connection policy of the profile
649      *
650      * <p> The device should already be paired.
651      * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
652      * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
653      *
654      * @param device Paired bluetooth device
655      * @param connectionPolicy is the connection policy to set to for this profile
656      * @return true if connectionPolicy is set, false on error
657      * @hide
658      */
659     @SystemApi
660     @RequiresBluetoothConnectPermission
661     @RequiresPermission(allOf = {
662             android.Manifest.permission.BLUETOOTH_CONNECT,
663             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
664             android.Manifest.permission.MODIFY_PHONE_STATE,
665     })
setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)666     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
667             @ConnectionPolicy int connectionPolicy) {
668         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
669         final IBluetoothHeadset service = mService;
670         if (service != null && isEnabled() && isValidDevice(device)) {
671             if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
672                     && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
673                 return false;
674             }
675             try {
676                 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
677             } catch (RemoteException e) {
678                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
679                 return false;
680             }
681         }
682         if (service == null) Log.w(TAG, "Proxy not attached to service");
683         return false;
684     }
685 
686     /**
687      * Get the priority of the profile.
688      *
689      * <p> The priority can be any of:
690      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
691      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
692      *
693      * @param device Bluetooth device
694      * @return priority of the device
695      * @hide
696      */
697     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
698     @RequiresLegacyBluetoothPermission
699     @RequiresBluetoothConnectPermission
700     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getPriority(BluetoothDevice device)701     public int getPriority(BluetoothDevice device) {
702         if (VDBG) log("getPriority(" + device + ")");
703         final IBluetoothHeadset service = mService;
704         if (service != null && isEnabled() && isValidDevice(device)) {
705             try {
706                 return BluetoothAdapter.connectionPolicyToPriority(
707                         service.getPriority(device, mAttributionSource));
708             } catch (RemoteException e) {
709                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
710                 return BluetoothProfile.PRIORITY_OFF;
711             }
712         }
713         if (service == null) Log.w(TAG, "Proxy not attached to service");
714         return BluetoothProfile.PRIORITY_OFF;
715     }
716 
717     /**
718      * Get the connection policy of the profile.
719      *
720      * <p> The connection policy can be any of:
721      * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
722      * {@link #CONNECTION_POLICY_UNKNOWN}
723      *
724      * @param device Bluetooth device
725      * @return connection policy of the device
726      * @hide
727      */
728     @SystemApi
729     @RequiresBluetoothConnectPermission
730     @RequiresPermission(allOf = {
731             android.Manifest.permission.BLUETOOTH_CONNECT,
732             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
733     })
getConnectionPolicy(@onNull BluetoothDevice device)734     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
735         if (VDBG) log("getConnectionPolicy(" + device + ")");
736         final IBluetoothHeadset service = mService;
737         if (service != null && isEnabled() && isValidDevice(device)) {
738             try {
739                 return service.getConnectionPolicy(device, mAttributionSource);
740             } catch (RemoteException e) {
741                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
742                 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
743             }
744         }
745         if (service == null) Log.w(TAG, "Proxy not attached to service");
746         return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
747     }
748 
749     /**
750      * Checks whether the headset supports some form of noise reduction
751      *
752      * @param device Bluetooth device
753      * @return true if echo cancellation and/or noise reduction is supported, false otherwise
754      */
755     @RequiresLegacyBluetoothPermission
756     @RequiresBluetoothConnectPermission
757     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
isNoiseReductionSupported(@onNull BluetoothDevice device)758     public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) {
759         if (DBG) log("isNoiseReductionSupported()");
760         final IBluetoothHeadset service = mService;
761         if (service != null && isEnabled() && isValidDevice(device)) {
762             try {
763                 return service.isNoiseReductionSupported(device, mAttributionSource);
764             } catch (RemoteException e) {
765                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
766             }
767         }
768         if (service == null) Log.w(TAG, "Proxy not attached to service");
769         return false;
770     }
771 
772     /**
773      * Checks whether the headset supports voice recognition
774      *
775      * @param device Bluetooth device
776      * @return true if voice recognition is supported, false otherwise
777      */
778     @RequiresLegacyBluetoothPermission
779     @RequiresBluetoothConnectPermission
780     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
isVoiceRecognitionSupported(@onNull BluetoothDevice device)781     public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
782         if (DBG) log("isVoiceRecognitionSupported()");
783         final IBluetoothHeadset service = mService;
784         if (service != null && isEnabled() && isValidDevice(device)) {
785             try {
786                 return service.isVoiceRecognitionSupported(device, mAttributionSource);
787             } catch (RemoteException e) {
788                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
789             }
790         }
791         if (service == null) Log.w(TAG, "Proxy not attached to service");
792         return false;
793     }
794 
795     /**
796      * Start Bluetooth voice recognition. This methods sends the voice
797      * recognition AT command to the headset and establishes the
798      * audio connection.
799      *
800      * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
801      * If this function returns true, this intent will be broadcasted with
802      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
803      *
804      * <p> {@link #EXTRA_STATE} will transition from
805      * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
806      * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
807      * in case of failure to establish the audio connection.
808      *
809      * @param device Bluetooth headset
810      * @return false if there is no headset connected, or the connected headset doesn't support
811      * voice recognition, or voice recognition is already started, or audio channel is occupied,
812      * or on error, true otherwise
813      */
814     @RequiresLegacyBluetoothPermission
815     @RequiresBluetoothConnectPermission
816     @RequiresPermission(allOf = {
817             android.Manifest.permission.BLUETOOTH_CONNECT,
818             android.Manifest.permission.MODIFY_PHONE_STATE,
819     })
startVoiceRecognition(BluetoothDevice device)820     public boolean startVoiceRecognition(BluetoothDevice device) {
821         if (DBG) log("startVoiceRecognition()");
822         final IBluetoothHeadset service = mService;
823         if (service != null && isEnabled() && isValidDevice(device)) {
824             try {
825                 return service.startVoiceRecognition(device, mAttributionSource);
826             } catch (RemoteException e) {
827                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
828             }
829         }
830         if (service == null) Log.w(TAG, "Proxy not attached to service");
831         return false;
832     }
833 
834     /**
835      * Stop Bluetooth Voice Recognition mode, and shut down the
836      * Bluetooth audio path.
837      *
838      * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
839      * If this function returns true, this intent will be broadcasted with
840      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
841      *
842      * @param device Bluetooth headset
843      * @return false if there is no headset connected, or voice recognition has not started,
844      * or voice recognition has ended on this headset, or on error, true otherwise
845      */
846     @RequiresLegacyBluetoothPermission
847     @RequiresBluetoothConnectPermission
848     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
stopVoiceRecognition(BluetoothDevice device)849     public boolean stopVoiceRecognition(BluetoothDevice device) {
850         if (DBG) log("stopVoiceRecognition()");
851         final IBluetoothHeadset service = mService;
852         if (service != null && isEnabled() && isValidDevice(device)) {
853             try {
854                 return service.stopVoiceRecognition(device, mAttributionSource);
855             } catch (RemoteException e) {
856                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
857             }
858         }
859         if (service == null) Log.w(TAG, "Proxy not attached to service");
860         return false;
861     }
862 
863     /**
864      * Check if Bluetooth SCO audio is connected.
865      *
866      * @param device Bluetooth headset
867      * @return true if SCO is connected, false otherwise or on error
868      */
869     @RequiresLegacyBluetoothPermission
870     @RequiresBluetoothConnectPermission
871     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
isAudioConnected(BluetoothDevice device)872     public boolean isAudioConnected(BluetoothDevice device) {
873         if (VDBG) log("isAudioConnected()");
874         final IBluetoothHeadset service = mService;
875         if (service != null && isEnabled() && isValidDevice(device)) {
876             try {
877                 return service.isAudioConnected(device, mAttributionSource);
878             } catch (RemoteException e) {
879                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
880             }
881         }
882         if (service == null) Log.w(TAG, "Proxy not attached to service");
883         return false;
884     }
885 
886     /**
887      * Indicates if current platform supports voice dialing over bluetooth SCO.
888      *
889      * @return true if voice dialing over bluetooth is supported, false otherwise.
890      * @hide
891      */
isBluetoothVoiceDialingEnabled(Context context)892     public static boolean isBluetoothVoiceDialingEnabled(Context context) {
893         return context.getResources().getBoolean(
894                 com.android.internal.R.bool.config_bluetooth_sco_off_call);
895     }
896 
897     /**
898      * Get the current audio state of the Headset.
899      * Note: This is an internal function and shouldn't be exposed
900      *
901      * @hide
902      */
903     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
904     @RequiresBluetoothConnectPermission
905     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getAudioState(BluetoothDevice device)906     public int getAudioState(BluetoothDevice device) {
907         if (VDBG) log("getAudioState");
908         final IBluetoothHeadset service = mService;
909         if (service != null && !isDisabled()) {
910             try {
911                 return service.getAudioState(device, mAttributionSource);
912             } catch (RemoteException e) {
913                 Log.e(TAG, e.toString());
914             }
915         } else {
916             Log.w(TAG, "Proxy not attached to service");
917             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
918         }
919         return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
920     }
921 
922     /**
923      * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
924      * audio to the HF unless explicitly told to.
925      * This method should be used in cases where the SCO channel is shared between multiple profiles
926      * and must be delegated by a source knowledgeable
927      * Note: This is an internal function and shouldn't be exposed
928      *
929      * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
930      * @hide
931      */
932     @RequiresBluetoothConnectPermission
933     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
setAudioRouteAllowed(boolean allowed)934     public void setAudioRouteAllowed(boolean allowed) {
935         if (VDBG) log("setAudioRouteAllowed");
936         final IBluetoothHeadset service = mService;
937         if (service != null && isEnabled()) {
938             try {
939                 service.setAudioRouteAllowed(allowed, mAttributionSource);
940             } catch (RemoteException e) {
941                 Log.e(TAG, e.toString());
942             }
943         } else {
944             Log.w(TAG, "Proxy not attached to service");
945             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
946         }
947     }
948 
949     /**
950      * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
951      * Note: This is an internal function and shouldn't be exposed
952      *
953      * @hide
954      */
955     @RequiresBluetoothConnectPermission
956     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getAudioRouteAllowed()957     public boolean getAudioRouteAllowed() {
958         if (VDBG) log("getAudioRouteAllowed");
959         final IBluetoothHeadset service = mService;
960         if (service != null && isEnabled()) {
961             try {
962                 return service.getAudioRouteAllowed(mAttributionSource);
963             } catch (RemoteException e) {
964                 Log.e(TAG, e.toString());
965             }
966         } else {
967             Log.w(TAG, "Proxy not attached to service");
968             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
969         }
970         return false;
971     }
972 
973     /**
974      * Force SCO audio to be opened regardless any other restrictions
975      *
976      * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio
977      * False to use SCO audio in normal manner
978      * @hide
979      */
980     @RequiresBluetoothConnectPermission
981     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
setForceScoAudio(boolean forced)982     public void setForceScoAudio(boolean forced) {
983         if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
984         final IBluetoothHeadset service = mService;
985         if (service != null && isEnabled()) {
986             try {
987                 service.setForceScoAudio(forced, mAttributionSource);
988             } catch (RemoteException e) {
989                 Log.e(TAG, e.toString());
990             }
991         } else {
992             Log.w(TAG, "Proxy not attached to service");
993             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
994         }
995     }
996 
997     /**
998      * Check if at least one headset's SCO audio is connected or connecting
999      *
1000      * @return true if at least one device's SCO audio is connected or connecting, false otherwise
1001      * or on error
1002      * @hide
1003      */
1004     @RequiresLegacyBluetoothPermission
1005     @RequiresBluetoothConnectPermission
1006     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
isAudioOn()1007     public boolean isAudioOn() {
1008         if (VDBG) log("isAudioOn()");
1009         final IBluetoothHeadset service = mService;
1010         if (service != null && isEnabled()) {
1011             try {
1012                 return service.isAudioOn(mAttributionSource);
1013             } catch (RemoteException e) {
1014                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1015             }
1016         }
1017         if (service == null) Log.w(TAG, "Proxy not attached to service");
1018         return false;
1019 
1020     }
1021 
1022     /**
1023      * Initiates a connection of headset audio to the current active device
1024      *
1025      * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1026      * If this function returns true, this intent will be broadcasted with
1027      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
1028      *
1029      * <p> {@link #EXTRA_STATE} will transition from
1030      * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
1031      * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
1032      * in case of failure to establish the audio connection.
1033      *
1034      * Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true
1035      * before calling this method
1036      *
1037      * @return false if there was some error such as there is no active headset
1038      * @hide
1039      */
1040     @UnsupportedAppUsage
1041     @RequiresBluetoothConnectPermission
1042     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
connectAudio()1043     public boolean connectAudio() {
1044         final IBluetoothHeadset service = mService;
1045         if (service != null && isEnabled()) {
1046             try {
1047                 return service.connectAudio(mAttributionSource);
1048             } catch (RemoteException e) {
1049                 Log.e(TAG, e.toString());
1050             }
1051         } else {
1052             Log.w(TAG, "Proxy not attached to service");
1053             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1054         }
1055         return false;
1056     }
1057 
1058     /**
1059      * Initiates a disconnection of HFP SCO audio.
1060      * Tear down voice recognition or virtual voice call if any.
1061      *
1062      * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1063      * If this function returns true, this intent will be broadcasted with
1064      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
1065      *
1066      * @return false if audio is not connected, or on error, true otherwise
1067      * @hide
1068      */
1069     @UnsupportedAppUsage
1070     @RequiresBluetoothConnectPermission
1071     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
disconnectAudio()1072     public boolean disconnectAudio() {
1073         final IBluetoothHeadset service = mService;
1074         if (service != null && isEnabled()) {
1075             try {
1076                 return service.disconnectAudio(mAttributionSource);
1077             } catch (RemoteException e) {
1078                 Log.e(TAG, e.toString());
1079             }
1080         } else {
1081             Log.w(TAG, "Proxy not attached to service");
1082             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1083         }
1084         return false;
1085     }
1086 
1087     /**
1088      * Initiates a SCO channel connection as a virtual voice call to the current active device
1089      * Active handsfree device will be notified of incoming call and connected call.
1090      *
1091      * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1092      * If this function returns true, this intent will be broadcasted with
1093      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
1094      *
1095      * <p> {@link #EXTRA_STATE} will transition from
1096      * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
1097      * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
1098      * in case of failure to establish the audio connection.
1099      *
1100      * @return true if successful, false if one of the following case applies
1101      *  - SCO audio is not idle (connecting or connected)
1102      *  - virtual call has already started
1103      *  - there is no active device
1104      *  - a Telecom managed call is going on
1105      *  - binder is dead or Bluetooth is disabled or other error
1106      * @hide
1107      */
1108     @RequiresLegacyBluetoothAdminPermission
1109     @RequiresBluetoothConnectPermission
1110     @RequiresPermission(allOf = {
1111             android.Manifest.permission.BLUETOOTH_CONNECT,
1112             android.Manifest.permission.MODIFY_PHONE_STATE,
1113     })
1114     @UnsupportedAppUsage
startScoUsingVirtualVoiceCall()1115     public boolean startScoUsingVirtualVoiceCall() {
1116         if (DBG) log("startScoUsingVirtualVoiceCall()");
1117         final IBluetoothHeadset service = mService;
1118         if (service != null && isEnabled()) {
1119             try {
1120                 return service.startScoUsingVirtualVoiceCall(mAttributionSource);
1121             } catch (RemoteException e) {
1122                 Log.e(TAG, e.toString());
1123             }
1124         } else {
1125             Log.w(TAG, "Proxy not attached to service");
1126             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1127         }
1128         return false;
1129     }
1130 
1131     /**
1132      * Terminates an ongoing SCO connection and the associated virtual call.
1133      *
1134      * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1135      * If this function returns true, this intent will be broadcasted with
1136      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
1137      *
1138      * @return true if successful, false if one of the following case applies
1139      *  - virtual voice call is not started or has ended
1140      *  - binder is dead or Bluetooth is disabled or other error
1141      * @hide
1142      */
1143     @RequiresLegacyBluetoothAdminPermission
1144     @RequiresBluetoothConnectPermission
1145     @RequiresPermission(allOf = {
1146             android.Manifest.permission.BLUETOOTH_CONNECT,
1147             android.Manifest.permission.MODIFY_PHONE_STATE,
1148     })
1149     @UnsupportedAppUsage
stopScoUsingVirtualVoiceCall()1150     public boolean stopScoUsingVirtualVoiceCall() {
1151         if (DBG) log("stopScoUsingVirtualVoiceCall()");
1152         final IBluetoothHeadset service = mService;
1153         if (service != null && isEnabled()) {
1154             try {
1155                 return service.stopScoUsingVirtualVoiceCall(mAttributionSource);
1156             } catch (RemoteException e) {
1157                 Log.e(TAG, e.toString());
1158             }
1159         } else {
1160             Log.w(TAG, "Proxy not attached to service");
1161             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1162         }
1163         return false;
1164     }
1165 
1166     /**
1167      * Notify Headset of phone state change.
1168      * This is a backdoor for phone app to call BluetoothHeadset since
1169      * there is currently not a good way to get precise call state change outside
1170      * of phone app.
1171      *
1172      * @hide
1173      */
1174     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1175     @RequiresBluetoothConnectPermission
1176     @RequiresPermission(allOf = {
1177             android.Manifest.permission.BLUETOOTH_CONNECT,
1178             android.Manifest.permission.MODIFY_PHONE_STATE,
1179     })
phoneStateChanged(int numActive, int numHeld, int callState, String number, int type, String name)1180     public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
1181             int type, String name) {
1182         final IBluetoothHeadset service = mService;
1183         if (service != null && isEnabled()) {
1184             try {
1185                 service.phoneStateChanged(numActive, numHeld, callState, number, type, name,
1186                         mAttributionSource);
1187             } catch (RemoteException e) {
1188                 Log.e(TAG, e.toString());
1189             }
1190         } else {
1191             Log.w(TAG, "Proxy not attached to service");
1192             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1193         }
1194     }
1195 
1196     /**
1197      * Send Headset of CLCC response
1198      *
1199      * @hide
1200      */
1201     @RequiresBluetoothConnectPermission
1202     @RequiresPermission(allOf = {
1203             android.Manifest.permission.BLUETOOTH_CONNECT,
1204             android.Manifest.permission.MODIFY_PHONE_STATE,
1205     })
clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type)1206     public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
1207             String number, int type) {
1208         final IBluetoothHeadset service = mService;
1209         if (service != null && isEnabled()) {
1210             try {
1211                 service.clccResponse(index, direction, status, mode, mpty, number, type,
1212                         mAttributionSource);
1213             } catch (RemoteException e) {
1214                 Log.e(TAG, e.toString());
1215             }
1216         } else {
1217             Log.w(TAG, "Proxy not attached to service");
1218             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1219         }
1220     }
1221 
1222     /**
1223      * Sends a vendor-specific unsolicited result code to the headset.
1224      *
1225      * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code
1226      * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the
1227      * string <code>"+ANDROID: 0"</code> will be sent.
1228      *
1229      * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
1230      *
1231      * @param device Bluetooth headset.
1232      * @param command A vendor-specific command.
1233      * @param arg The argument that will be attached to the command.
1234      * @return {@code false} if there is no headset connected, or if the command is not an allowed
1235      * vendor-specific unsolicited result code, or on error. {@code true} otherwise.
1236      * @throws IllegalArgumentException if {@code command} is {@code null}.
1237      */
1238     @RequiresLegacyBluetoothPermission
1239     @RequiresBluetoothConnectPermission
1240     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
sendVendorSpecificResultCode(BluetoothDevice device, String command, String arg)1241     public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
1242             String arg) {
1243         if (DBG) {
1244             log("sendVendorSpecificResultCode()");
1245         }
1246         if (command == null) {
1247             throw new IllegalArgumentException("command is null");
1248         }
1249         final IBluetoothHeadset service = mService;
1250         if (service != null && isEnabled() && isValidDevice(device)) {
1251             try {
1252                 return service.sendVendorSpecificResultCode(device, command, arg,
1253                         mAttributionSource);
1254             } catch (RemoteException e) {
1255                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1256             }
1257         }
1258         if (service == null) {
1259             Log.w(TAG, "Proxy not attached to service");
1260         }
1261         return false;
1262     }
1263 
1264     /**
1265      * Select a connected device as active.
1266      *
1267      * The active device selection is per profile. An active device's
1268      * purpose is profile-specific. For example, in HFP and HSP profiles,
1269      * it is the device used for phone call audio. If a remote device is not
1270      * connected, it cannot be selected as active.
1271      *
1272      * <p> This API returns false in scenarios like the profile on the
1273      * device is not connected or Bluetooth is not turned on.
1274      * When this API returns true, it is guaranteed that the
1275      * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
1276      * with the active device.
1277      *
1278      * @param device Remote Bluetooth Device, could be null if phone call audio should not be
1279      * streamed to a headset
1280      * @return false on immediate error, true otherwise
1281      * @hide
1282      */
1283     @RequiresLegacyBluetoothAdminPermission
1284     @RequiresBluetoothConnectPermission
1285     @RequiresPermission(allOf = {
1286             android.Manifest.permission.BLUETOOTH_CONNECT,
1287             android.Manifest.permission.MODIFY_PHONE_STATE,
1288     })
1289     @UnsupportedAppUsage(trackingBug = 171933273)
setActiveDevice(@ullable BluetoothDevice device)1290     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
1291         if (DBG) {
1292             Log.d(TAG, "setActiveDevice: " + device);
1293         }
1294         final IBluetoothHeadset service = mService;
1295         if (service != null && isEnabled() && (device == null || isValidDevice(device))) {
1296             try {
1297                 return service.setActiveDevice(device, mAttributionSource);
1298             } catch (RemoteException e) {
1299                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1300             }
1301         }
1302         if (service == null) {
1303             Log.w(TAG, "Proxy not attached to service");
1304         }
1305         return false;
1306     }
1307 
1308     /**
1309      * Get the connected device that is active.
1310      *
1311      * @return the connected device that is active or null if no device
1312      * is active.
1313      * @hide
1314      */
1315     @UnsupportedAppUsage(trackingBug = 171933273)
1316     @Nullable
1317     @RequiresLegacyBluetoothPermission
1318     @RequiresBluetoothConnectPermission
1319     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getActiveDevice()1320     public BluetoothDevice getActiveDevice() {
1321         if (VDBG) {
1322             Log.d(TAG, "getActiveDevice");
1323         }
1324         final IBluetoothHeadset service = mService;
1325         if (service != null && isEnabled()) {
1326             try {
1327                 return Attributable.setAttributionSource(
1328                         service.getActiveDevice(mAttributionSource), mAttributionSource);
1329             } catch (RemoteException e) {
1330                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1331             }
1332         }
1333         if (service == null) {
1334             Log.w(TAG, "Proxy not attached to service");
1335         }
1336         return null;
1337     }
1338 
1339     /**
1340      * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an
1341      * active connection.
1342      *
1343      * @return true if in-band ringing is enabled, false if in-band ringing is disabled
1344      * @hide
1345      */
1346     @RequiresLegacyBluetoothPermission
1347     @RequiresBluetoothConnectPermission
1348     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
isInbandRingingEnabled()1349     public boolean isInbandRingingEnabled() {
1350         if (DBG) {
1351             log("isInbandRingingEnabled()");
1352         }
1353         final IBluetoothHeadset service = mService;
1354         if (service != null && isEnabled()) {
1355             try {
1356                 return service.isInbandRingingEnabled(mAttributionSource);
1357             } catch (RemoteException e) {
1358                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1359             }
1360         }
1361         if (service == null) {
1362             Log.w(TAG, "Proxy not attached to service");
1363         }
1364         return false;
1365     }
1366 
1367     /**
1368      * Check if in-band ringing is supported for this platform.
1369      *
1370      * @return true if in-band ringing is supported, false if in-band ringing is not supported
1371      * @hide
1372      */
isInbandRingingSupported(Context context)1373     public static boolean isInbandRingingSupported(Context context) {
1374         return context.getResources().getBoolean(
1375                 com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support);
1376     }
1377 
1378     @SuppressLint("AndroidFrameworkBluetoothPermission")
1379     private final IBluetoothProfileServiceConnection mConnection =
1380             new IBluetoothProfileServiceConnection.Stub() {
1381         @Override
1382         public void onServiceConnected(ComponentName className, IBinder service) {
1383             if (DBG) Log.d(TAG, "Proxy object connected");
1384             mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service));
1385             mHandler.sendMessage(mHandler.obtainMessage(
1386                     MESSAGE_HEADSET_SERVICE_CONNECTED));
1387         }
1388 
1389         @Override
1390         public void onServiceDisconnected(ComponentName className) {
1391             if (DBG) Log.d(TAG, "Proxy object disconnected");
1392             doUnbind();
1393             mHandler.sendMessage(mHandler.obtainMessage(
1394                     MESSAGE_HEADSET_SERVICE_DISCONNECTED));
1395         }
1396     };
1397 
1398     @UnsupportedAppUsage
isEnabled()1399     private boolean isEnabled() {
1400         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
1401     }
1402 
isDisabled()1403     private boolean isDisabled() {
1404         return mAdapter.getState() == BluetoothAdapter.STATE_OFF;
1405     }
1406 
isValidDevice(BluetoothDevice device)1407     private static boolean isValidDevice(BluetoothDevice device) {
1408         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
1409     }
1410 
log(String msg)1411     private static void log(String msg) {
1412         Log.d(TAG, msg);
1413     }
1414 
1415     @SuppressLint("AndroidFrameworkBluetoothPermission")
1416     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
1417         @Override
1418         public void handleMessage(Message msg) {
1419             switch (msg.what) {
1420                 case MESSAGE_HEADSET_SERVICE_CONNECTED: {
1421                     if (mServiceListener != null) {
1422                         mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
1423                                 BluetoothHeadset.this);
1424                     }
1425                     break;
1426                 }
1427                 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
1428                     if (mServiceListener != null) {
1429                         mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
1430                     }
1431                     break;
1432                 }
1433             }
1434         }
1435     };
1436 }
1437