1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.bluetooth;
18 
19 import android.annotation.NonNull;
20 import android.annotation.RequiresPermission;
21 import android.annotation.SdkConstant;
22 import android.annotation.SdkConstant.SdkConstantType;
23 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
24 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
25 import android.compat.annotation.UnsupportedAppUsage;
26 import android.content.Attributable;
27 import android.content.AttributionSource;
28 import android.content.Context;
29 import android.os.Binder;
30 import android.os.Build;
31 import android.os.Bundle;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 import android.util.Log;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 /**
40  * Public API to control Hands Free Profile (HFP role only).
41  * <p>
42  * This class defines methods that shall be used by application to manage profile
43  * connection, calls states and calls actions.
44  * <p>
45  *
46  * @hide
47  */
48 public final class BluetoothHeadsetClient implements BluetoothProfile {
49     private static final String TAG = "BluetoothHeadsetClient";
50     private static final boolean DBG = true;
51     private static final boolean VDBG = false;
52 
53     /**
54      * Intent sent whenever connection to remote changes.
55      *
56      * <p>It includes two extras:
57      * <code>BluetoothProfile.EXTRA_PREVIOUS_STATE</code>
58      * and <code>BluetoothProfile.EXTRA_STATE</code>, which
59      * are mandatory.
60      * <p>There are also non mandatory feature extras:
61      * {@link #EXTRA_AG_FEATURE_3WAY_CALLING},
62      * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION},
63      * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT},
64      * {@link #EXTRA_AG_FEATURE_REJECT_CALL},
65      * {@link #EXTRA_AG_FEATURE_ECC},
66      * {@link #EXTRA_AG_FEATURE_RESPONSE_AND_HOLD},
67      * {@link #EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL},
68      * {@link #EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL},
69      * {@link #EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT},
70      * {@link #EXTRA_AG_FEATURE_MERGE},
71      * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH},
72      * sent as boolean values only when <code>EXTRA_STATE</code>
73      * is set to <code>STATE_CONNECTED</code>.</p>
74      *
75      * <p>Note that features supported by AG are being sent as
76      * booleans with value <code>true</code>,
77      * and not supported ones are <strong>not</strong> being sent at all.</p>
78      */
79     @RequiresBluetoothConnectPermission
80     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
81     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
82     public static final String ACTION_CONNECTION_STATE_CHANGED =
83             "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED";
84 
85     /**
86      * Intent sent whenever audio state changes.
87      *
88      * <p>It includes two mandatory extras:
89      * {@link BluetoothProfile#EXTRA_STATE},
90      * {@link BluetoothProfile#EXTRA_PREVIOUS_STATE},
91      * with possible values:
92      * {@link #STATE_AUDIO_CONNECTING},
93      * {@link #STATE_AUDIO_CONNECTED},
94      * {@link #STATE_AUDIO_DISCONNECTED}</p>
95      * <p>When <code>EXTRA_STATE</code> is set
96      * to </code>STATE_AUDIO_CONNECTED</code>,
97      * it also includes {@link #EXTRA_AUDIO_WBS}
98      * indicating wide band speech support.</p>
99      */
100     @RequiresBluetoothConnectPermission
101     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
102     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
103     public static final String ACTION_AUDIO_STATE_CHANGED =
104             "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED";
105 
106     /**
107      * Intent sending updates of the Audio Gateway state.
108      * Each extra is being sent only when value it
109      * represents has been changed recently on AG.
110      * <p>It can contain one or more of the following extras:
111      * {@link #EXTRA_NETWORK_STATUS},
112      * {@link #EXTRA_NETWORK_SIGNAL_STRENGTH},
113      * {@link #EXTRA_NETWORK_ROAMING},
114      * {@link #EXTRA_BATTERY_LEVEL},
115      * {@link #EXTRA_OPERATOR_NAME},
116      * {@link #EXTRA_VOICE_RECOGNITION},
117      * {@link #EXTRA_IN_BAND_RING}</p>
118      */
119     @RequiresBluetoothConnectPermission
120     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
121     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
122     public static final String ACTION_AG_EVENT =
123             "android.bluetooth.headsetclient.profile.action.AG_EVENT";
124 
125     /**
126      * Intent sent whenever state of a call changes.
127      *
128      * <p>It includes:
129      * {@link #EXTRA_CALL},
130      * with value of {@link BluetoothHeadsetClientCall} instance,
131      * representing actual call state.</p>
132      */
133     @RequiresBluetoothConnectPermission
134     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
135     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
136     public static final String ACTION_CALL_CHANGED =
137             "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED";
138 
139     /**
140      * Intent that notifies about the result of the last issued action.
141      * Please note that not every action results in explicit action result code being sent.
142      * Instead other notifications about new Audio Gateway state might be sent,
143      * like <code>ACTION_AG_EVENT</code> with <code>EXTRA_VOICE_RECOGNITION</code> value
144      * when for example user started voice recognition from HF unit.
145      */
146     @RequiresBluetoothConnectPermission
147     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
148     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
149     public static final String ACTION_RESULT =
150             "android.bluetooth.headsetclient.profile.action.RESULT";
151 
152     /**
153      * Intent that notifies about vendor specific event arrival. Events not defined in
154      * HFP spec will be matched with supported vendor event list and this intent will
155      * be broadcasted upon a match. Supported vendor events are of format of
156      * of "+eventCode" or "+eventCode=xxxx" or "+eventCode:=xxxx".
157      * Vendor event can be a response to an vendor specific command or unsolicited.
158      *
159      */
160     @RequiresBluetoothConnectPermission
161     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
162     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
163     public static final String ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT =
164             "android.bluetooth.headsetclient.profile.action.VENDOR_SPECIFIC_EVENT";
165 
166     /**
167      * Intent that notifies about the number attached to the last voice tag
168      * recorded on AG.
169      *
170      * <p>It contains:
171      * {@link #EXTRA_NUMBER},
172      * with a <code>String</code> value representing phone number.</p>
173      */
174     @RequiresBluetoothConnectPermission
175     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
176     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
177     public static final String ACTION_LAST_VTAG =
178             "android.bluetooth.headsetclient.profile.action.LAST_VTAG";
179 
180     public static final int STATE_AUDIO_DISCONNECTED = 0;
181     public static final int STATE_AUDIO_CONNECTING = 1;
182     public static final int STATE_AUDIO_CONNECTED = 2;
183 
184     /**
185      * Extra with information if connected audio is WBS.
186      * <p>Possible values: <code>true</code>,
187      * <code>false</code>.</p>
188      */
189     public static final String EXTRA_AUDIO_WBS =
190             "android.bluetooth.headsetclient.extra.AUDIO_WBS";
191 
192     /**
193      * Extra for AG_EVENT indicates network status.
194      * <p>Value: 0 - network unavailable,
195      * 1 - network available </p>
196      */
197     public static final String EXTRA_NETWORK_STATUS =
198             "android.bluetooth.headsetclient.extra.NETWORK_STATUS";
199     /**
200      * Extra for AG_EVENT intent indicates network signal strength.
201      * <p>Value: <code>Integer</code> representing signal strength.</p>
202      */
203     public static final String EXTRA_NETWORK_SIGNAL_STRENGTH =
204             "android.bluetooth.headsetclient.extra.NETWORK_SIGNAL_STRENGTH";
205     /**
206      * Extra for AG_EVENT intent indicates roaming state.
207      * <p>Value: 0 - no roaming
208      * 1 - active roaming</p>
209      */
210     public static final String EXTRA_NETWORK_ROAMING =
211             "android.bluetooth.headsetclient.extra.NETWORK_ROAMING";
212     /**
213      * Extra for AG_EVENT intent indicates the battery level.
214      * <p>Value: <code>Integer</code> representing signal strength.</p>
215      */
216     public static final String EXTRA_BATTERY_LEVEL =
217             "android.bluetooth.headsetclient.extra.BATTERY_LEVEL";
218     /**
219      * Extra for AG_EVENT intent indicates operator name.
220      * <p>Value: <code>String</code> representing operator name.</p>
221      */
222     public static final String EXTRA_OPERATOR_NAME =
223             "android.bluetooth.headsetclient.extra.OPERATOR_NAME";
224     /**
225      * Extra for AG_EVENT intent indicates voice recognition state.
226      * <p>Value:
227      * 0 - voice recognition stopped,
228      * 1 - voice recognition started.</p>
229      */
230     public static final String EXTRA_VOICE_RECOGNITION =
231             "android.bluetooth.headsetclient.extra.VOICE_RECOGNITION";
232     /**
233      * Extra for AG_EVENT intent indicates in band ring state.
234      * <p>Value:
235      * 0 - in band ring tone not supported, or
236      * 1 - in band ring tone supported.</p>
237      */
238     public static final String EXTRA_IN_BAND_RING =
239             "android.bluetooth.headsetclient.extra.IN_BAND_RING";
240 
241     /**
242      * Extra for AG_EVENT intent indicates subscriber info.
243      * <p>Value: <code>String</code> containing subscriber information.</p>
244      */
245     public static final String EXTRA_SUBSCRIBER_INFO =
246             "android.bluetooth.headsetclient.extra.SUBSCRIBER_INFO";
247 
248     /**
249      * Extra for AG_CALL_CHANGED intent indicates the
250      * {@link BluetoothHeadsetClientCall} object that has changed.
251      */
252     public static final String EXTRA_CALL =
253             "android.bluetooth.headsetclient.extra.CALL";
254 
255     /**
256      * Extra for ACTION_LAST_VTAG intent.
257      * <p>Value: <code>String</code> representing phone number
258      * corresponding to last voice tag recorded on AG</p>
259      */
260     public static final String EXTRA_NUMBER =
261             "android.bluetooth.headsetclient.extra.NUMBER";
262 
263     /**
264      * Extra for ACTION_RESULT intent that shows the result code of
265      * last issued action.
266      * <p>Possible results:
267      * {@link #ACTION_RESULT_OK},
268      * {@link #ACTION_RESULT_ERROR},
269      * {@link #ACTION_RESULT_ERROR_NO_CARRIER},
270      * {@link #ACTION_RESULT_ERROR_BUSY},
271      * {@link #ACTION_RESULT_ERROR_NO_ANSWER},
272      * {@link #ACTION_RESULT_ERROR_DELAYED},
273      * {@link #ACTION_RESULT_ERROR_BLACKLISTED},
274      * {@link #ACTION_RESULT_ERROR_CME}</p>
275      */
276     public static final String EXTRA_RESULT_CODE =
277             "android.bluetooth.headsetclient.extra.RESULT_CODE";
278 
279     /**
280      * Extra for ACTION_RESULT intent that shows the extended result code of
281      * last issued action.
282      * <p>Value: <code>Integer</code> - error code.</p>
283      */
284     public static final String EXTRA_CME_CODE =
285             "android.bluetooth.headsetclient.extra.CME_CODE";
286 
287     /**
288      * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that
289      * indicates vendor ID.
290      */
291     public static final String EXTRA_VENDOR_ID =
292             "android.bluetooth.headsetclient.extra.VENDOR_ID";
293 
294      /**
295      * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that
296      * indicates vendor event code.
297      */
298     public static final String EXTRA_VENDOR_EVENT_CODE =
299             "android.bluetooth.headsetclient.extra.VENDOR_EVENT_CODE";
300 
301      /**
302      * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that
303      * contains full vendor event including event code and full arguments.
304      */
305     public static final String EXTRA_VENDOR_EVENT_FULL_ARGS =
306             "android.bluetooth.headsetclient.extra.VENDOR_EVENT_FULL_ARGS";
307 
308 
309     /* Extras for AG_FEATURES, extras type is boolean */
310     // TODO verify if all of those are actually useful
311     /**
312      * AG feature: three way calling.
313      */
314     public static final String EXTRA_AG_FEATURE_3WAY_CALLING =
315             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING";
316     /**
317      * AG feature: voice recognition.
318      */
319     public static final String EXTRA_AG_FEATURE_VOICE_RECOGNITION =
320             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION";
321     /**
322      * AG feature: fetching phone number for voice tagging procedure.
323      */
324     public static final String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT =
325             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT";
326     /**
327      * AG feature: ability to reject incoming call.
328      */
329     public static final String EXTRA_AG_FEATURE_REJECT_CALL =
330             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_REJECT_CALL";
331     /**
332      * AG feature: enhanced call handling (terminate specific call, private consultation).
333      */
334     public static final String EXTRA_AG_FEATURE_ECC =
335             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC";
336     /**
337      * AG feature: response and hold.
338      */
339     public static final String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD =
340             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD";
341     /**
342      * AG call handling feature: accept held or waiting call in three way calling scenarios.
343      */
344     public static final String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL =
345             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL";
346     /**
347      * AG call handling feature: release held or waiting call in three way calling scenarios.
348      */
349     public static final String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL =
350             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL";
351     /**
352      * AG call handling feature: release active call and accept held or waiting call in three way
353      * calling scenarios.
354      */
355     public static final String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT =
356             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT";
357     /**
358      * AG call handling feature: merge two calls, held and active - multi party conference mode.
359      */
360     public static final String EXTRA_AG_FEATURE_MERGE =
361             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE";
362     /**
363      * AG call handling feature: merge calls and disconnect from multi party
364      * conversation leaving peers connected to each other.
365      * Note that this feature needs to be supported by mobile network operator
366      * as it requires connection and billing transfer.
367      */
368     public static final String EXTRA_AG_FEATURE_MERGE_AND_DETACH =
369             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH";
370 
371     /* Action result codes */
372     public static final int ACTION_RESULT_OK = 0;
373     public static final int ACTION_RESULT_ERROR = 1;
374     public static final int ACTION_RESULT_ERROR_NO_CARRIER = 2;
375     public static final int ACTION_RESULT_ERROR_BUSY = 3;
376     public static final int ACTION_RESULT_ERROR_NO_ANSWER = 4;
377     public static final int ACTION_RESULT_ERROR_DELAYED = 5;
378     public static final int ACTION_RESULT_ERROR_BLACKLISTED = 6;
379     public static final int ACTION_RESULT_ERROR_CME = 7;
380 
381     /* Detailed CME error codes */
382     public static final int CME_PHONE_FAILURE = 0;
383     public static final int CME_NO_CONNECTION_TO_PHONE = 1;
384     public static final int CME_OPERATION_NOT_ALLOWED = 3;
385     public static final int CME_OPERATION_NOT_SUPPORTED = 4;
386     public static final int CME_PHSIM_PIN_REQUIRED = 5;
387     public static final int CME_PHFSIM_PIN_REQUIRED = 6;
388     public static final int CME_PHFSIM_PUK_REQUIRED = 7;
389     public static final int CME_SIM_NOT_INSERTED = 10;
390     public static final int CME_SIM_PIN_REQUIRED = 11;
391     public static final int CME_SIM_PUK_REQUIRED = 12;
392     public static final int CME_SIM_FAILURE = 13;
393     public static final int CME_SIM_BUSY = 14;
394     public static final int CME_SIM_WRONG = 15;
395     public static final int CME_INCORRECT_PASSWORD = 16;
396     public static final int CME_SIM_PIN2_REQUIRED = 17;
397     public static final int CME_SIM_PUK2_REQUIRED = 18;
398     public static final int CME_MEMORY_FULL = 20;
399     public static final int CME_INVALID_INDEX = 21;
400     public static final int CME_NOT_FOUND = 22;
401     public static final int CME_MEMORY_FAILURE = 23;
402     public static final int CME_TEXT_STRING_TOO_LONG = 24;
403     public static final int CME_INVALID_CHARACTER_IN_TEXT_STRING = 25;
404     public static final int CME_DIAL_STRING_TOO_LONG = 26;
405     public static final int CME_INVALID_CHARACTER_IN_DIAL_STRING = 27;
406     public static final int CME_NO_NETWORK_SERVICE = 30;
407     public static final int CME_NETWORK_TIMEOUT = 31;
408     public static final int CME_EMERGENCY_SERVICE_ONLY = 32;
409     public static final int CME_NO_SIMULTANOUS_VOIP_CS_CALLS = 33;
410     public static final int CME_NOT_SUPPORTED_FOR_VOIP = 34;
411     public static final int CME_SIP_RESPONSE_CODE = 35;
412     public static final int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED = 40;
413     public static final int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED = 41;
414     public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED = 42;
415     public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED = 43;
416     public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44;
417     public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45;
418     public static final int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED = 46;
419     public static final int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED = 47;
420     public static final int CME_HIDDEN_KEY_REQUIRED = 48;
421     public static final int CME_EAP_NOT_SUPPORTED = 49;
422     public static final int CME_INCORRECT_PARAMETERS = 50;
423 
424     /* Action policy for other calls when accepting call */
425     public static final int CALL_ACCEPT_NONE = 0;
426     public static final int CALL_ACCEPT_HOLD = 1;
427     public static final int CALL_ACCEPT_TERMINATE = 2;
428 
429     private final BluetoothAdapter mAdapter;
430     private final AttributionSource mAttributionSource;
431     private final BluetoothProfileConnector<IBluetoothHeadsetClient> mProfileConnector =
432             new BluetoothProfileConnector(this, BluetoothProfile.HEADSET_CLIENT,
433                     "BluetoothHeadsetClient", IBluetoothHeadsetClient.class.getName()) {
434                 @Override
435                 public IBluetoothHeadsetClient getServiceInterface(IBinder service) {
436                     return IBluetoothHeadsetClient.Stub.asInterface(Binder.allowBlocking(service));
437                 }
438     };
439 
440     /**
441      * Create a BluetoothHeadsetClient proxy object.
442      */
BluetoothHeadsetClient(Context context, ServiceListener listener, BluetoothAdapter adapter)443     /* package */ BluetoothHeadsetClient(Context context, ServiceListener listener,
444             BluetoothAdapter adapter) {
445         mAdapter = adapter;
446         mAttributionSource = adapter.getAttributionSource();
447         mProfileConnector.connect(context, listener);
448     }
449 
450     /**
451      * Close the connection to the backing service.
452      * Other public functions of BluetoothHeadsetClient will return default error
453      * results once close() has been called. Multiple invocations of close()
454      * are ok.
455      */
close()456     /*package*/ void close() {
457         if (VDBG) log("close()");
458         mProfileConnector.disconnect();
459     }
460 
getService()461     private IBluetoothHeadsetClient getService() {
462         return mProfileConnector.getService();
463     }
464 
465     /**
466      * Connects to remote device.
467      *
468      * Currently, the system supports only 1 connection. So, in case of the
469      * second connection, this implementation will disconnect already connected
470      * device automatically and will process the new one.
471      *
472      * @param device a remote device we want connect to
473      * @return <code>true</code> if command has been issued successfully; <code>false</code>
474      * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent.
475      *
476      * @hide
477      */
478     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
479     @RequiresBluetoothConnectPermission
480     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
connect(BluetoothDevice device)481     public boolean connect(BluetoothDevice device) {
482         if (DBG) log("connect(" + device + ")");
483         final IBluetoothHeadsetClient service =
484                 getService();
485         if (service != null && isEnabled() && isValidDevice(device)) {
486             try {
487                 return service.connect(device, mAttributionSource);
488             } catch (RemoteException e) {
489                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
490                 return false;
491             }
492         }
493         if (service == null) Log.w(TAG, "Proxy not attached to service");
494         return false;
495     }
496 
497     /**
498      * Disconnects remote device
499      *
500      * @param device a remote device we want disconnect
501      * @return <code>true</code> if command has been issued successfully; <code>false</code>
502      * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent.
503      *
504      * @hide
505      */
506     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
507     @RequiresBluetoothConnectPermission
508     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
disconnect(BluetoothDevice device)509     public boolean disconnect(BluetoothDevice device) {
510         if (DBG) log("disconnect(" + device + ")");
511         final IBluetoothHeadsetClient service =
512                 getService();
513         if (service != null && isEnabled() && isValidDevice(device)) {
514             try {
515                 return service.disconnect(device, mAttributionSource);
516             } catch (RemoteException e) {
517                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
518                 return false;
519             }
520         }
521         if (service == null) Log.w(TAG, "Proxy not attached to service");
522         return false;
523     }
524 
525     /**
526      * Return the list of connected remote devices
527      *
528      * @return list of connected devices; empty list if nothing is connected.
529      */
530     @Override
531     @RequiresBluetoothConnectPermission
532     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getConnectedDevices()533     public List<BluetoothDevice> getConnectedDevices() {
534         if (VDBG) log("getConnectedDevices()");
535         final IBluetoothHeadsetClient service =
536                 getService();
537         if (service != null && isEnabled()) {
538             try {
539                 return Attributable.setAttributionSource(
540                         service.getConnectedDevices(mAttributionSource), mAttributionSource);
541             } catch (RemoteException e) {
542                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
543                 return new ArrayList<BluetoothDevice>();
544             }
545         }
546         if (service == null) Log.w(TAG, "Proxy not attached to service");
547         return new ArrayList<BluetoothDevice>();
548     }
549 
550     /**
551      * Returns list of remote devices in a particular state
552      *
553      * @param states collection of states
554      * @return list of devices that state matches the states listed in <code>states</code>; empty
555      * list if nothing matches the <code>states</code>
556      */
557     @Override
558     @RequiresBluetoothConnectPermission
559     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getDevicesMatchingConnectionStates(int[] states)560     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
561         if (VDBG) log("getDevicesMatchingStates()");
562         final IBluetoothHeadsetClient service =
563                 getService();
564         if (service != null && isEnabled()) {
565             try {
566                 return Attributable.setAttributionSource(
567                         service.getDevicesMatchingConnectionStates(states, mAttributionSource),
568                         mAttributionSource);
569             } catch (RemoteException e) {
570                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
571                 return new ArrayList<BluetoothDevice>();
572             }
573         }
574         if (service == null) Log.w(TAG, "Proxy not attached to service");
575         return new ArrayList<BluetoothDevice>();
576     }
577 
578     /**
579      * Returns state of the <code>device</code>
580      *
581      * @param device a remote device
582      * @return the state of connection of the device
583      */
584     @Override
585     @RequiresBluetoothConnectPermission
586     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getConnectionState(BluetoothDevice device)587     public int getConnectionState(BluetoothDevice device) {
588         if (VDBG) log("getConnectionState(" + device + ")");
589         final IBluetoothHeadsetClient service =
590                 getService();
591         if (service != null && isEnabled() && isValidDevice(device)) {
592             try {
593                 return service.getConnectionState(device, mAttributionSource);
594             } catch (RemoteException e) {
595                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
596                 return BluetoothProfile.STATE_DISCONNECTED;
597             }
598         }
599         if (service == null) Log.w(TAG, "Proxy not attached to service");
600         return BluetoothProfile.STATE_DISCONNECTED;
601     }
602 
603     /**
604      * Set priority of the profile
605      *
606      * <p> The device should already be paired.
607      * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}
608      *
609      * @param device Paired bluetooth device
610      * @param priority
611      * @return true if priority is set, false on error
612      * @hide
613      */
614     @RequiresBluetoothConnectPermission
615     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
setPriority(BluetoothDevice device, int priority)616     public boolean setPriority(BluetoothDevice device, int priority) {
617         if (DBG) log("setPriority(" + device + ", " + priority + ")");
618         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
619     }
620 
621     /**
622      * Set connection policy of the profile
623      *
624      * <p> The device should already be paired.
625      * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
626      * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
627      *
628      * @param device Paired bluetooth device
629      * @param connectionPolicy is the connection policy to set to for this profile
630      * @return true if connectionPolicy is set, false on error
631      * @hide
632      */
633     @RequiresBluetoothConnectPermission
634     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)635     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
636             @ConnectionPolicy int connectionPolicy) {
637         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
638         final IBluetoothHeadsetClient service =
639                 getService();
640         if (service != null && isEnabled() && isValidDevice(device)) {
641             if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
642                     && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
643                 return false;
644             }
645             try {
646                 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
647             } catch (RemoteException e) {
648                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
649                 return false;
650             }
651         }
652         if (service == null) Log.w(TAG, "Proxy not attached to service");
653         return false;
654     }
655 
656     /**
657      * Get the priority of the profile.
658      *
659      * <p> The priority can be any of:
660      * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
661      *
662      * @param device Bluetooth device
663      * @return priority of the device
664      * @hide
665      */
666     @RequiresLegacyBluetoothPermission
667     @RequiresBluetoothConnectPermission
668     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getPriority(BluetoothDevice device)669     public int getPriority(BluetoothDevice device) {
670         if (VDBG) log("getPriority(" + device + ")");
671         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
672     }
673 
674     /**
675      * Get the connection policy of the profile.
676      *
677      * <p> The connection policy can be any of:
678      * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
679      * {@link #CONNECTION_POLICY_UNKNOWN}
680      *
681      * @param device Bluetooth device
682      * @return connection policy of the device
683      * @hide
684      */
685     @RequiresLegacyBluetoothPermission
686     @RequiresBluetoothConnectPermission
687     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getConnectionPolicy(@onNull BluetoothDevice device)688     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
689         if (VDBG) log("getConnectionPolicy(" + device + ")");
690         final IBluetoothHeadsetClient service =
691                 getService();
692         if (service != null && isEnabled() && isValidDevice(device)) {
693             try {
694                 return service.getConnectionPolicy(device, mAttributionSource);
695             } catch (RemoteException e) {
696                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
697                 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
698             }
699         }
700         if (service == null) Log.w(TAG, "Proxy not attached to service");
701         return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
702     }
703 
704     /**
705      * Starts voice recognition.
706      *
707      * @param device remote device
708      * @return <code>true</code> if command has been issued successfully; <code>false</code>
709      * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent.
710      *
711      * <p>Feature required for successful execution is being reported by: {@link
712      * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature
713      * is not supported.</p>
714      */
715     @RequiresBluetoothConnectPermission
716     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
startVoiceRecognition(BluetoothDevice device)717     public boolean startVoiceRecognition(BluetoothDevice device) {
718         if (DBG) log("startVoiceRecognition()");
719         final IBluetoothHeadsetClient service =
720                 getService();
721         if (service != null && isEnabled() && isValidDevice(device)) {
722             try {
723                 return service.startVoiceRecognition(device, mAttributionSource);
724             } catch (RemoteException e) {
725                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
726             }
727         }
728         if (service == null) Log.w(TAG, "Proxy not attached to service");
729         return false;
730     }
731 
732     /**
733      * Send vendor specific AT command.
734      *
735      * @param device remote device
736      * @param vendorId vendor number by Bluetooth SIG
737      * @param atCommand command to be sent. It start with + prefix and only one command at one time.
738      * @return <code>true</code> if command has been issued successfully; <code>false</code>
739      * otherwise.
740      */
741     @RequiresBluetoothConnectPermission
742     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand)743     public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId,
744                                              String atCommand) {
745         if (DBG) log("sendVendorSpecificCommand()");
746         final IBluetoothHeadsetClient service =
747                 getService();
748         if (service != null && isEnabled() && isValidDevice(device)) {
749             try {
750                 return service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource);
751             } catch (RemoteException e) {
752                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
753             }
754         }
755         if (service == null) Log.w(TAG, "Proxy not attached to service");
756         return false;
757     }
758 
759     /**
760      * Stops voice recognition.
761      *
762      * @param device remote device
763      * @return <code>true</code> if command has been issued successfully; <code>false</code>
764      * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent.
765      *
766      * <p>Feature required for successful execution is being reported by: {@link
767      * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature
768      * is not supported.</p>
769      */
770     @RequiresBluetoothConnectPermission
771     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
stopVoiceRecognition(BluetoothDevice device)772     public boolean stopVoiceRecognition(BluetoothDevice device) {
773         if (DBG) log("stopVoiceRecognition()");
774         final IBluetoothHeadsetClient service =
775                 getService();
776         if (service != null && isEnabled() && isValidDevice(device)) {
777             try {
778                 return service.stopVoiceRecognition(device, mAttributionSource);
779             } catch (RemoteException e) {
780                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
781             }
782         }
783         if (service == null) Log.w(TAG, "Proxy not attached to service");
784         return false;
785     }
786 
787     /**
788      * Returns list of all calls in any state.
789      *
790      * @param device remote device
791      * @return list of calls; empty list if none call exists
792      */
793     @RequiresBluetoothConnectPermission
794     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getCurrentCalls(BluetoothDevice device)795     public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
796         if (DBG) log("getCurrentCalls()");
797         final IBluetoothHeadsetClient service =
798                 getService();
799         if (service != null && isEnabled() && isValidDevice(device)) {
800             try {
801                 return Attributable.setAttributionSource(
802                         service.getCurrentCalls(device, mAttributionSource), mAttributionSource);
803             } catch (RemoteException e) {
804                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
805             }
806         }
807         if (service == null) Log.w(TAG, "Proxy not attached to service");
808         return null;
809     }
810 
811     /**
812      * Returns list of current values of AG indicators.
813      *
814      * @param device remote device
815      * @return bundle of AG  indicators; null if device is not in CONNECTED state
816      */
817     @RequiresBluetoothConnectPermission
818     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getCurrentAgEvents(BluetoothDevice device)819     public Bundle getCurrentAgEvents(BluetoothDevice device) {
820         if (DBG) log("getCurrentCalls()");
821         final IBluetoothHeadsetClient service =
822                 getService();
823         if (service != null && isEnabled() && isValidDevice(device)) {
824             try {
825                 return service.getCurrentAgEvents(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 null;
832     }
833 
834     /**
835      * Accepts a call
836      *
837      * @param device remote device
838      * @param flag action policy while accepting a call. Possible values {@link #CALL_ACCEPT_NONE},
839      * {@link #CALL_ACCEPT_HOLD}, {@link #CALL_ACCEPT_TERMINATE}
840      * @return <code>true</code> if command has been issued successfully; <code>false</code>
841      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
842      */
843     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
844     @RequiresBluetoothConnectPermission
845     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
acceptCall(BluetoothDevice device, int flag)846     public boolean acceptCall(BluetoothDevice device, int flag) {
847         if (DBG) log("acceptCall()");
848         final IBluetoothHeadsetClient service =
849                 getService();
850         if (service != null && isEnabled() && isValidDevice(device)) {
851             try {
852                 return service.acceptCall(device, flag, mAttributionSource);
853             } catch (RemoteException e) {
854                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
855             }
856         }
857         if (service == null) Log.w(TAG, "Proxy not attached to service");
858         return false;
859     }
860 
861     /**
862      * Holds a call.
863      *
864      * @param device remote device
865      * @return <code>true</code> if command has been issued successfully; <code>false</code>
866      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
867      */
868     @RequiresBluetoothConnectPermission
869     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
holdCall(BluetoothDevice device)870     public boolean holdCall(BluetoothDevice device) {
871         if (DBG) log("holdCall()");
872         final IBluetoothHeadsetClient service =
873                 getService();
874         if (service != null && isEnabled() && isValidDevice(device)) {
875             try {
876                 return service.holdCall(device, mAttributionSource);
877             } catch (RemoteException e) {
878                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
879             }
880         }
881         if (service == null) Log.w(TAG, "Proxy not attached to service");
882         return false;
883     }
884 
885     /**
886      * Rejects a call.
887      *
888      * @param device remote device
889      * @return <code>true</code> if command has been issued successfully; <code>false</code>
890      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
891      *
892      * <p>Feature required for successful execution is being reported by: {@link
893      * #EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is not
894      * supported.</p>
895      */
896     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
897     @RequiresBluetoothConnectPermission
898     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
rejectCall(BluetoothDevice device)899     public boolean rejectCall(BluetoothDevice device) {
900         if (DBG) log("rejectCall()");
901         final IBluetoothHeadsetClient service =
902                 getService();
903         if (service != null && isEnabled() && isValidDevice(device)) {
904             try {
905                 return service.rejectCall(device, mAttributionSource);
906             } catch (RemoteException e) {
907                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
908             }
909         }
910         if (service == null) Log.w(TAG, "Proxy not attached to service");
911         return false;
912     }
913 
914     /**
915      * Terminates a specified call.
916      *
917      * Works only when Extended Call Control is supported by Audio Gateway.
918      *
919      * @param device remote device
920      * @param call Handle of call obtained in {@link #dial(BluetoothDevice, String)} or obtained via
921      * {@link #ACTION_CALL_CHANGED}. {@code call} may be null in which case we will hangup all active
922      * calls.
923      * @return <code>true</code> if command has been issued successfully; <code>false</code>
924      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
925      *
926      * <p>Feature required for successful execution is being reported by: {@link
927      * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not
928      * supported.</p>
929      */
930     @RequiresBluetoothConnectPermission
931     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call)932     public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
933         if (DBG) log("terminateCall()");
934         final IBluetoothHeadsetClient service =
935                 getService();
936         if (service != null && isEnabled() && isValidDevice(device)) {
937             try {
938                 return service.terminateCall(device, call, mAttributionSource);
939             } catch (RemoteException e) {
940                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
941             }
942         }
943         if (service == null) Log.w(TAG, "Proxy not attached to service");
944         return false;
945     }
946 
947     /**
948      * Enters private mode with a specified call.
949      *
950      * Works only when Extended Call Control is supported by Audio Gateway.
951      *
952      * @param device remote device
953      * @param index index of the call to connect in private mode
954      * @return <code>true</code> if command has been issued successfully; <code>false</code>
955      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
956      *
957      * <p>Feature required for successful execution is being reported by: {@link
958      * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not
959      * supported.</p>
960      */
961     @RequiresBluetoothConnectPermission
962     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
enterPrivateMode(BluetoothDevice device, int index)963     public boolean enterPrivateMode(BluetoothDevice device, int index) {
964         if (DBG) log("enterPrivateMode()");
965         final IBluetoothHeadsetClient service =
966                 getService();
967         if (service != null && isEnabled() && isValidDevice(device)) {
968             try {
969                 return service.enterPrivateMode(device, index, mAttributionSource);
970             } catch (RemoteException e) {
971                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
972             }
973         }
974         if (service == null) Log.w(TAG, "Proxy not attached to service");
975         return false;
976     }
977 
978     /**
979      * Performs explicit call transfer.
980      *
981      * That means connect other calls and disconnect.
982      *
983      * @param device remote device
984      * @return <code>true</code> if command has been issued successfully; <code>false</code>
985      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
986      *
987      * <p>Feature required for successful execution is being reported by: {@link
988      * #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when feature
989      * is not supported.</p>
990      */
991     @RequiresBluetoothConnectPermission
992     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
explicitCallTransfer(BluetoothDevice device)993     public boolean explicitCallTransfer(BluetoothDevice device) {
994         if (DBG) log("explicitCallTransfer()");
995         final IBluetoothHeadsetClient service =
996                 getService();
997         if (service != null && isEnabled() && isValidDevice(device)) {
998             try {
999                 return service.explicitCallTransfer(device, mAttributionSource);
1000             } catch (RemoteException e) {
1001                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1002             }
1003         }
1004         if (service == null) Log.w(TAG, "Proxy not attached to service");
1005         return false;
1006     }
1007 
1008     /**
1009      * Places a call with specified number.
1010      *
1011      * @param device remote device
1012      * @param number valid phone number
1013      * @return <code>{@link BluetoothHeadsetClientCall} call</code> if command has been issued
1014      * successfully; <code>{@link null}</code> otherwise; upon completion HFP sends {@link
1015      * #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent otherwise;
1016      */
1017     @RequiresBluetoothConnectPermission
1018     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
dial(BluetoothDevice device, String number)1019     public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
1020         if (DBG) log("dial()");
1021         final IBluetoothHeadsetClient service =
1022                 getService();
1023         if (service != null && isEnabled() && isValidDevice(device)) {
1024             try {
1025                 return Attributable.setAttributionSource(
1026                         service.dial(device, number, mAttributionSource), mAttributionSource);
1027             } catch (RemoteException e) {
1028                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1029             }
1030         }
1031         if (service == null) Log.w(TAG, "Proxy not attached to service");
1032         return null;
1033     }
1034 
1035     /**
1036      * Sends DTMF code.
1037      *
1038      * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,#
1039      *
1040      * @param device remote device
1041      * @param code ASCII code
1042      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1043      * otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent;
1044      */
1045     @RequiresBluetoothConnectPermission
1046     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
sendDTMF(BluetoothDevice device, byte code)1047     public boolean sendDTMF(BluetoothDevice device, byte code) {
1048         if (DBG) log("sendDTMF()");
1049         final IBluetoothHeadsetClient service =
1050                 getService();
1051         if (service != null && isEnabled() && isValidDevice(device)) {
1052             try {
1053                 return service.sendDTMF(device, code, mAttributionSource);
1054             } catch (RemoteException e) {
1055                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1056             }
1057         }
1058         if (service == null) Log.w(TAG, "Proxy not attached to service");
1059         return false;
1060     }
1061 
1062     /**
1063      * Get a number corresponding to last voice tag recorded on AG.
1064      *
1065      * @param device remote device
1066      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1067      * otherwise; upon completion HFP sends {@link #ACTION_LAST_VTAG} or {@link #ACTION_RESULT}
1068      * intent;
1069      *
1070      * <p>Feature required for successful execution is being reported by: {@link
1071      * #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. This method invocation will fail silently when
1072      * feature is not supported.</p>
1073      */
1074     @RequiresBluetoothConnectPermission
1075     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getLastVoiceTagNumber(BluetoothDevice device)1076     public boolean getLastVoiceTagNumber(BluetoothDevice device) {
1077         if (DBG) log("getLastVoiceTagNumber()");
1078         final IBluetoothHeadsetClient service =
1079                 getService();
1080         if (service != null && isEnabled() && isValidDevice(device)) {
1081             try {
1082                 return service.getLastVoiceTagNumber(device, mAttributionSource);
1083             } catch (RemoteException e) {
1084                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1085             }
1086         }
1087         if (service == null) Log.w(TAG, "Proxy not attached to service");
1088         return false;
1089     }
1090 
1091     /**
1092      * Returns current audio state of Audio Gateway.
1093      *
1094      * Note: This is an internal function and shouldn't be exposed
1095      */
1096     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1097     @RequiresBluetoothConnectPermission
1098     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getAudioState(BluetoothDevice device)1099     public int getAudioState(BluetoothDevice device) {
1100         if (VDBG) log("getAudioState");
1101         final IBluetoothHeadsetClient service =
1102                 getService();
1103         if (service != null && isEnabled()) {
1104             try {
1105                 return service.getAudioState(device, mAttributionSource);
1106             } catch (RemoteException e) {
1107                 Log.e(TAG, e.toString());
1108             }
1109         } else {
1110             Log.w(TAG, "Proxy not attached to service");
1111             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1112         }
1113         return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
1114     }
1115 
1116     /**
1117      * Sets whether audio routing is allowed.
1118      *
1119      * @param device remote device
1120      * @param allowed if routing is allowed to the device Note: This is an internal function and
1121      * shouldn't be exposed
1122      */
1123     @RequiresBluetoothConnectPermission
1124     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
setAudioRouteAllowed(BluetoothDevice device, boolean allowed)1125     public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
1126         if (VDBG) log("setAudioRouteAllowed");
1127         final IBluetoothHeadsetClient service =
1128                 getService();
1129         if (service != null && isEnabled()) {
1130             try {
1131                 service.setAudioRouteAllowed(device, allowed, mAttributionSource);
1132             } catch (RemoteException e) {
1133                 Log.e(TAG, e.toString());
1134             }
1135         } else {
1136             Log.w(TAG, "Proxy not attached to service");
1137             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1138         }
1139     }
1140 
1141     /**
1142      * Returns whether audio routing is allowed.
1143      *
1144      * @param device remote device
1145      * @return whether the command succeeded Note: This is an internal function and shouldn't be
1146      * exposed
1147      */
1148     @RequiresBluetoothConnectPermission
1149     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getAudioRouteAllowed(BluetoothDevice device)1150     public boolean getAudioRouteAllowed(BluetoothDevice device) {
1151         if (VDBG) log("getAudioRouteAllowed");
1152         final IBluetoothHeadsetClient service =
1153                 getService();
1154         if (service != null && isEnabled()) {
1155             try {
1156                 return service.getAudioRouteAllowed(device, mAttributionSource);
1157             } catch (RemoteException e) {
1158                 Log.e(TAG, e.toString());
1159             }
1160         } else {
1161             Log.w(TAG, "Proxy not attached to service");
1162             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1163         }
1164         return false;
1165     }
1166 
1167     /**
1168      * Initiates a connection of audio channel.
1169      *
1170      * It setup SCO channel with remote connected Handsfree AG device.
1171      *
1172      * @param device remote device
1173      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1174      * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
1175      */
1176     @RequiresBluetoothConnectPermission
1177     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
connectAudio(BluetoothDevice device)1178     public boolean connectAudio(BluetoothDevice device) {
1179         final IBluetoothHeadsetClient service =
1180                 getService();
1181         if (service != null && isEnabled()) {
1182             try {
1183                 return service.connectAudio(device, mAttributionSource);
1184             } catch (RemoteException e) {
1185                 Log.e(TAG, e.toString());
1186             }
1187         } else {
1188             Log.w(TAG, "Proxy not attached to service");
1189             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1190         }
1191         return false;
1192     }
1193 
1194     /**
1195      * Disconnects audio channel.
1196      *
1197      * It tears down the SCO channel from remote AG device.
1198      *
1199      * @param device remote device
1200      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1201      * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
1202      */
1203     @RequiresBluetoothConnectPermission
1204     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
disconnectAudio(BluetoothDevice device)1205     public boolean disconnectAudio(BluetoothDevice device) {
1206         final IBluetoothHeadsetClient service =
1207                 getService();
1208         if (service != null && isEnabled()) {
1209             try {
1210                 return service.disconnectAudio(device, mAttributionSource);
1211             } catch (RemoteException e) {
1212                 Log.e(TAG, e.toString());
1213             }
1214         } else {
1215             Log.w(TAG, "Proxy not attached to service");
1216             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1217         }
1218         return false;
1219     }
1220 
1221     /**
1222      * Get Audio Gateway features
1223      *
1224      * @param device remote device
1225      * @return bundle of AG features; null if no service or AG not connected
1226      */
1227     @RequiresBluetoothConnectPermission
1228     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getCurrentAgFeatures(BluetoothDevice device)1229     public Bundle getCurrentAgFeatures(BluetoothDevice device) {
1230         final IBluetoothHeadsetClient service =
1231                 getService();
1232         if (service != null && isEnabled()) {
1233             try {
1234                 return service.getCurrentAgFeatures(device, mAttributionSource);
1235             } catch (RemoteException e) {
1236                 Log.e(TAG, e.toString());
1237             }
1238         } else {
1239             Log.w(TAG, "Proxy not attached to service");
1240             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1241         }
1242         return null;
1243     }
1244 
isEnabled()1245     private boolean isEnabled() {
1246         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
1247     }
1248 
isValidDevice(BluetoothDevice device)1249     private static boolean isValidDevice(BluetoothDevice device) {
1250         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
1251     }
1252 
log(String msg)1253     private static void log(String msg) {
1254         Log.d(TAG, msg);
1255     }
1256 }
1257