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