• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.bluetooth;
17 
18 import static android.bluetooth.BluetoothUtils.getSyncTimeout;
19 
20 import android.annotation.IntRange;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SdkConstant;
25 import android.annotation.SdkConstant.SdkConstantType;
26 import android.annotation.SuppressLint;
27 import android.annotation.SystemApi;
28 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
29 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
30 import android.compat.annotation.UnsupportedAppUsage;
31 import android.content.AttributionSource;
32 import android.content.Context;
33 import android.os.Build;
34 import android.os.Bundle;
35 import android.os.IBinder;
36 import android.os.Parcel;
37 import android.os.Parcelable;
38 import android.os.RemoteException;
39 import android.util.CloseGuard;
40 import android.util.Log;
41 
42 import com.android.modules.utils.SynchronousResultReceiver;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.concurrent.TimeoutException;
47 
48 /**
49  * This class provides the System APIs to interact with the Hands-Free Client profile.
50  *
51  * <p>BluetoothHeadsetClient is a proxy object for controlling the Bluetooth HFP Client
52  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
53  * the BluetoothHeadsetClient proxy object.
54  *
55  * @hide
56  */
57 @SystemApi
58 public final class BluetoothHeadsetClient implements BluetoothProfile, AutoCloseable {
59     private static final String TAG = "BluetoothHeadsetClient";
60     private static final boolean DBG = true;
61     private static final boolean VDBG = false;
62     private final CloseGuard mCloseGuard;
63 
64     /**
65      * Intent used to broadcast the change in connection state of the HFP Client profile.
66      *
67      * <p>This intent will have 3 extras:
68      * <ul>
69      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
70      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
71      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
72      * </ul>
73      *
74      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
75      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
76      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
77      *
78      * @hide
79      */
80     @SuppressLint("ActionValue")
81     @SystemApi
82     @RequiresBluetoothConnectPermission
83     @RequiresPermission(allOf = {
84             android.Manifest.permission.BLUETOOTH_CONNECT,
85             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
86     })
87     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
88     public static final String ACTION_CONNECTION_STATE_CHANGED =
89             "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED";
90 
91     /**
92      * Intent sent whenever audio state changes.
93      *
94      * <p>It includes two mandatory extras:
95      * {@link BluetoothProfile#EXTRA_STATE},
96      * {@link BluetoothProfile#EXTRA_PREVIOUS_STATE},
97      * with possible values:
98      * {@link #STATE_AUDIO_CONNECTING},
99      * {@link #STATE_AUDIO_CONNECTED},
100      * {@link #STATE_AUDIO_DISCONNECTED}</p>
101      * <p>When <code>EXTRA_STATE</code> is set
102      * to </code>STATE_AUDIO_CONNECTED</code>,
103      * it also includes {@link #EXTRA_AUDIO_WBS}
104      * indicating wide band speech support.</p>
105      *
106      * @hide
107      */
108     @RequiresBluetoothConnectPermission
109     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
110     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
111     @SuppressLint("ActionValue")
112     public static final String ACTION_AUDIO_STATE_CHANGED =
113             "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED";
114 
115     /**
116      * Intent sending updates of the Audio Gateway state.
117      * Each extra is being sent only when value it
118      * represents has been changed recently on AG.
119      * <p>It can contain one or more of the following extras:
120      * {@link #EXTRA_NETWORK_STATUS},
121      * {@link #EXTRA_NETWORK_SIGNAL_STRENGTH},
122      * {@link #EXTRA_NETWORK_ROAMING},
123      * {@link #EXTRA_BATTERY_LEVEL},
124      * {@link #EXTRA_OPERATOR_NAME},
125      * {@link #EXTRA_VOICE_RECOGNITION},
126      * {@link #EXTRA_IN_BAND_RING}</p>
127      *
128      * @hide
129      */
130     @RequiresBluetoothConnectPermission
131     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
132     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
133     public static final String ACTION_AG_EVENT =
134             "android.bluetooth.headsetclient.profile.action.AG_EVENT";
135 
136     /**
137      * Intent sent whenever state of a call changes.
138      *
139      * <p>It includes:
140      * {@link #EXTRA_CALL},
141      * with value of {@link BluetoothHeadsetClientCall} instance,
142      * representing actual call state.</p>
143      *
144      * @hide
145      */
146     @RequiresBluetoothConnectPermission
147     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
148     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
149     public static final String ACTION_CALL_CHANGED =
150             "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED";
151 
152     /**
153      * Intent that notifies about the result of the last issued action.
154      * Please note that not every action results in explicit action result code being sent.
155      * Instead other notifications about new Audio Gateway state might be sent,
156      * like <code>ACTION_AG_EVENT</code> with <code>EXTRA_VOICE_RECOGNITION</code> value
157      * when for example user started voice recognition from HF unit.
158      *
159      * @hide
160      */
161     @RequiresBluetoothConnectPermission
162     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
163     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
164     public static final String ACTION_RESULT =
165             "android.bluetooth.headsetclient.profile.action.RESULT";
166 
167     /**
168      * Intent that notifies about vendor specific event arrival. Events not defined in
169      * HFP spec will be matched with supported vendor event list and this intent will
170      * be broadcasted upon a match. Supported vendor events are of format of
171      * of "+eventCode" or "+eventCode=xxxx" or "+eventCode:=xxxx".
172      * Vendor event can be a response to an vendor specific command or unsolicited.
173      *
174      * @hide
175      */
176     @RequiresBluetoothConnectPermission
177     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
178     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
179     public static final String ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT =
180             "android.bluetooth.headsetclient.profile.action.VENDOR_SPECIFIC_EVENT";
181 
182     /**
183      * Intent that notifies about the number attached to the last voice tag
184      * recorded on AG.
185      *
186      * <p>It contains:
187      * {@link #EXTRA_NUMBER},
188      * with a <code>String</code> value representing phone number.</p>
189      *
190      * @hide
191      */
192     @RequiresBluetoothConnectPermission
193     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
194     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
195     public static final String ACTION_LAST_VTAG =
196             "android.bluetooth.headsetclient.profile.action.LAST_VTAG";
197 
198     /**
199      * @hide
200      */
201     public static final int STATE_AUDIO_DISCONNECTED = 0;
202 
203     /**
204      * @hide
205      */
206     public static final int STATE_AUDIO_CONNECTING = 1;
207 
208     /**
209      * @hide
210      */
211     public static final int STATE_AUDIO_CONNECTED = 2;
212 
213     /**
214      * Extra with information if connected audio is WBS.
215      * <p>Possible values: <code>true</code>,
216      * <code>false</code>.</p>
217      *
218      * @hide
219      */
220     public static final String EXTRA_AUDIO_WBS =
221             "android.bluetooth.headsetclient.extra.AUDIO_WBS";
222 
223     /**
224      * Extra for AG_EVENT indicates network status.
225      * <p>Value: 0 - network unavailable,
226      * 1 - network available </p>
227      *
228      * @hide
229      */
230     public static final String EXTRA_NETWORK_STATUS =
231             "android.bluetooth.headsetclient.extra.NETWORK_STATUS";
232 
233     /**
234      * Extra for AG_EVENT intent indicates network signal strength.
235      * <p>Value: <code>Integer</code> representing signal strength.</p>
236      *
237      * @hide
238      */
239     public static final String EXTRA_NETWORK_SIGNAL_STRENGTH =
240             "android.bluetooth.headsetclient.extra.NETWORK_SIGNAL_STRENGTH";
241 
242     /**
243      * Extra for AG_EVENT intent indicates roaming state.
244      * <p>Value: 0 - no roaming
245      * 1 - active roaming</p>
246      *
247      * @hide
248      */
249     public static final String EXTRA_NETWORK_ROAMING =
250             "android.bluetooth.headsetclient.extra.NETWORK_ROAMING";
251 
252     /**
253      * Extra for AG_EVENT intent indicates the battery level.
254      * <p>Value: <code>Integer</code> representing signal strength.</p>
255      *
256      * @hide
257      */
258     public static final String EXTRA_BATTERY_LEVEL =
259             "android.bluetooth.headsetclient.extra.BATTERY_LEVEL";
260 
261     /**
262      * Extra for AG_EVENT intent indicates operator name.
263      * <p>Value: <code>String</code> representing operator name.</p>
264      *
265      * @hide
266      */
267     public static final String EXTRA_OPERATOR_NAME =
268             "android.bluetooth.headsetclient.extra.OPERATOR_NAME";
269 
270     /**
271      * Extra for AG_EVENT intent indicates voice recognition state.
272      * <p>Value:
273      * 0 - voice recognition stopped,
274      * 1 - voice recognition started.</p>
275      *
276      * @hide
277      */
278     public static final String EXTRA_VOICE_RECOGNITION =
279             "android.bluetooth.headsetclient.extra.VOICE_RECOGNITION";
280 
281     /**
282      * Extra for AG_EVENT intent indicates in band ring state.
283      * <p>Value:
284      * 0 - in band ring tone not supported, or
285      * 1 - in band ring tone supported.</p>
286      *
287      * @hide
288      */
289     public static final String EXTRA_IN_BAND_RING =
290             "android.bluetooth.headsetclient.extra.IN_BAND_RING";
291 
292     /**
293      * Extra for AG_EVENT intent indicates subscriber info.
294      * <p>Value: <code>String</code> containing subscriber information.</p>
295      *
296      * @hide
297      */
298     public static final String EXTRA_SUBSCRIBER_INFO =
299             "android.bluetooth.headsetclient.extra.SUBSCRIBER_INFO";
300 
301     /**
302      * Extra for AG_CALL_CHANGED intent indicates the
303      * {@link BluetoothHeadsetClientCall} object that has changed.
304      *
305      * @hide
306      */
307     public static final String EXTRA_CALL =
308             "android.bluetooth.headsetclient.extra.CALL";
309 
310     /**
311      * Extra for ACTION_LAST_VTAG intent.
312      * <p>Value: <code>String</code> representing phone number
313      * corresponding to last voice tag recorded on AG</p>
314      *
315      * @hide
316      */
317     public static final String EXTRA_NUMBER =
318             "android.bluetooth.headsetclient.extra.NUMBER";
319 
320     /**
321      * Extra for ACTION_RESULT intent that shows the result code of
322      * last issued action.
323      * <p>Possible results:
324      * {@link #ACTION_RESULT_OK},
325      * {@link #ACTION_RESULT_ERROR},
326      * {@link #ACTION_RESULT_ERROR_NO_CARRIER},
327      * {@link #ACTION_RESULT_ERROR_BUSY},
328      * {@link #ACTION_RESULT_ERROR_NO_ANSWER},
329      * {@link #ACTION_RESULT_ERROR_DELAYED},
330      * {@link #ACTION_RESULT_ERROR_BLACKLISTED},
331      * {@link #ACTION_RESULT_ERROR_CME}</p>
332      *
333      * @hide
334      */
335     public static final String EXTRA_RESULT_CODE =
336             "android.bluetooth.headsetclient.extra.RESULT_CODE";
337 
338     /**
339      * Extra for ACTION_RESULT intent that shows the extended result code of
340      * last issued action.
341      * <p>Value: <code>Integer</code> - error code.</p>
342      *
343      * @hide
344      */
345     public static final String EXTRA_CME_CODE =
346             "android.bluetooth.headsetclient.extra.CME_CODE";
347 
348     /**
349      * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that
350      * indicates vendor ID.
351      *
352      * @hide
353      */
354     public static final String EXTRA_VENDOR_ID =
355             "android.bluetooth.headsetclient.extra.VENDOR_ID";
356 
357     /**
358      * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that
359      * indicates vendor event code.
360      *
361      * @hide
362      */
363     public static final String EXTRA_VENDOR_EVENT_CODE =
364             "android.bluetooth.headsetclient.extra.VENDOR_EVENT_CODE";
365 
366     /**
367      * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that
368      * contains full vendor event including event code and full arguments.
369      *
370      * @hide
371      */
372     public static final String EXTRA_VENDOR_EVENT_FULL_ARGS =
373             "android.bluetooth.headsetclient.extra.VENDOR_EVENT_FULL_ARGS";
374 
375     /* Extras for AG_FEATURES, extras type is boolean */
376     // TODO verify if all of those are actually useful
377     /**
378      * AG feature: three way calling.
379      *
380      * @hide
381      */
382     public static final String EXTRA_AG_FEATURE_3WAY_CALLING =
383             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING";
384 
385     /**
386      * AG feature: voice recognition.
387      *
388      * @hide
389      */
390     public static final String EXTRA_AG_FEATURE_VOICE_RECOGNITION =
391             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION";
392 
393     /**
394      * AG feature: fetching phone number for voice tagging procedure.
395      *
396      * @hide
397      */
398     public static final String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT =
399             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT";
400 
401     /**
402      * AG feature: ability to reject incoming call.
403      *
404      * @hide
405      */
406     public static final String EXTRA_AG_FEATURE_REJECT_CALL =
407             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_REJECT_CALL";
408 
409     /**
410      * AG feature: enhanced call handling (terminate specific call, private consultation).
411      *
412      * @hide
413      */
414     public static final String EXTRA_AG_FEATURE_ECC =
415             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC";
416 
417     /**
418      * AG feature: response and hold.
419      *
420      * @hide
421      */
422     public static final String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD =
423             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD";
424 
425     /**
426      * AG call handling feature: accept held or waiting call in three way calling scenarios.
427      *
428      * @hide
429      */
430     public static final String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL =
431             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL";
432 
433     /**
434      * AG call handling feature: release held or waiting call in three way calling scenarios.
435      *
436      * @hide
437      */
438     public static final String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL =
439             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL";
440 
441     /**
442      * AG call handling feature: release active call and accept held or waiting call in three way
443      * calling scenarios.
444      *
445      * @hide
446      */
447     public static final String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT =
448             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT";
449 
450     /**
451      * AG call handling feature: merge two calls, held and active - multi party conference mode.
452      *
453      * @hide
454      */
455     public static final String EXTRA_AG_FEATURE_MERGE =
456             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE";
457 
458     /**
459      * AG call handling feature: merge calls and disconnect from multi party
460      * conversation leaving peers connected to each other.
461      * Note that this feature needs to be supported by mobile network operator
462      * as it requires connection and billing transfer.
463      *
464      * @hide
465      */
466     public static final String EXTRA_AG_FEATURE_MERGE_AND_DETACH =
467             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH";
468 
469     /* Action result codes */
470     /**
471      * @hide
472      */
473     public static final int ACTION_RESULT_OK = 0;
474 
475     /**
476      * @hide
477      */
478     public static final int ACTION_RESULT_ERROR = 1;
479 
480     /**
481      * @hide
482      */
483     public static final int ACTION_RESULT_ERROR_NO_CARRIER = 2;
484 
485     /**
486      * @hide
487      */
488     public static final int ACTION_RESULT_ERROR_BUSY = 3;
489 
490     /**
491      * @hide
492      */
493     public static final int ACTION_RESULT_ERROR_NO_ANSWER = 4;
494 
495     /**
496      * @hide
497      */
498     public static final int ACTION_RESULT_ERROR_DELAYED = 5;
499 
500     /**
501      * @hide
502      */
503     public static final int ACTION_RESULT_ERROR_BLACKLISTED = 6;
504 
505     /**
506      * @hide
507      */
508     public static final int ACTION_RESULT_ERROR_CME = 7;
509 
510     /* Detailed CME error codes */
511     /**
512      * @hide
513      */
514     public static final int CME_PHONE_FAILURE = 0;
515 
516     /**
517      * @hide
518      */
519     public static final int CME_NO_CONNECTION_TO_PHONE = 1;
520 
521     /**
522      * @hide
523      */
524     public static final int CME_OPERATION_NOT_ALLOWED = 3;
525 
526     /**
527      * @hide
528      */
529     public static final int CME_OPERATION_NOT_SUPPORTED = 4;
530 
531     /**
532      * @hide
533      */
534     public static final int CME_PHSIM_PIN_REQUIRED = 5;
535 
536     /**
537      * @hide
538      */
539     public static final int CME_PHFSIM_PIN_REQUIRED = 6;
540 
541     /**
542      * @hide
543      */
544     public static final int CME_PHFSIM_PUK_REQUIRED = 7;
545 
546     /**
547      * @hide
548      */
549     public static final int CME_SIM_NOT_INSERTED = 10;
550 
551     /**
552      * @hide
553      */
554     public static final int CME_SIM_PIN_REQUIRED = 11;
555 
556     /**
557      * @hide
558      */
559     public static final int CME_SIM_PUK_REQUIRED = 12;
560 
561     /**
562      * @hide
563      */
564     public static final int CME_SIM_FAILURE = 13;
565 
566     /**
567      * @hide
568      */
569     public static final int CME_SIM_BUSY = 14;
570 
571     /**
572      * @hide
573      */
574     public static final int CME_SIM_WRONG = 15;
575 
576     /**
577      * @hide
578      */
579     public static final int CME_INCORRECT_PASSWORD = 16;
580 
581     /**
582      * @hide
583      */
584     public static final int CME_SIM_PIN2_REQUIRED = 17;
585 
586     /**
587      * @hide
588      */
589     public static final int CME_SIM_PUK2_REQUIRED = 18;
590 
591     /**
592      * @hide
593      */
594     public static final int CME_MEMORY_FULL = 20;
595 
596     /**
597      * @hide
598      */
599     public static final int CME_INVALID_INDEX = 21;
600 
601     /**
602      * @hide
603      */
604     public static final int CME_NOT_FOUND = 22;
605 
606     /**
607      * @hide
608      */
609     public static final int CME_MEMORY_FAILURE = 23;
610 
611     /**
612      * @hide
613      */
614     public static final int CME_TEXT_STRING_TOO_LONG = 24;
615 
616     /**
617      * @hide
618      */
619     public static final int CME_INVALID_CHARACTER_IN_TEXT_STRING = 25;
620 
621     /**
622      * @hide
623      */
624     public static final int CME_DIAL_STRING_TOO_LONG = 26;
625 
626     /**
627      * @hide
628      */
629     public static final int CME_INVALID_CHARACTER_IN_DIAL_STRING = 27;
630 
631     /**
632      * @hide
633      */
634     public static final int CME_NO_NETWORK_SERVICE = 30;
635 
636     /**
637      * @hide
638      */
639     public static final int CME_NETWORK_TIMEOUT = 31;
640 
641     /**
642      * @hide
643      */
644     public static final int CME_EMERGENCY_SERVICE_ONLY = 32;
645 
646     /**
647      * @hide
648      */
649     public static final int CME_NO_SIMULTANOUS_VOIP_CS_CALLS = 33;
650 
651     /**
652      * @hide
653      */
654     public static final int CME_NOT_SUPPORTED_FOR_VOIP = 34;
655     /**
656      * @hide
657      */
658     public static final int CME_SIP_RESPONSE_CODE = 35;
659 
660     /**
661      * @hide
662      */
663     public static final int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED = 40;
664 
665     /**
666      * @hide
667      */
668     public static final int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED = 41;
669 
670     /**
671      * @hide
672      */
673     public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED = 42;
674 
675     /**
676      * @hide
677      */
678     public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED = 43;
679 
680     /**
681      * @hide
682      */
683     public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44;
684 
685     /**
686      * @hide
687      */
688     public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45;
689 
690     /**
691      * @hide
692      */
693     public static final int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED = 46;
694 
695     /**
696      * @hide
697      */
698     public static final int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED = 47;
699 
700     /**
701      * @hide
702      */
703     public static final int CME_HIDDEN_KEY_REQUIRED = 48;
704 
705     /**
706      * @hide
707      */
708     public static final int CME_EAP_NOT_SUPPORTED = 49;
709 
710     /**
711      * @hide
712      */
713     public static final int CME_INCORRECT_PARAMETERS = 50;
714 
715     /* Action policy for other calls when accepting call */
716     /**
717      * @hide
718      */
719     public static final int CALL_ACCEPT_NONE = 0;
720 
721     /**
722      * @hide
723      */
724     public static final int CALL_ACCEPT_HOLD = 1;
725 
726     /**
727      * @hide
728      */
729     public static final int CALL_ACCEPT_TERMINATE = 2;
730 
731     private final BluetoothAdapter mAdapter;
732     private final AttributionSource mAttributionSource;
733     private final BluetoothProfileConnector<IBluetoothHeadsetClient> mProfileConnector =
734             new BluetoothProfileConnector(this, BluetoothProfile.HEADSET_CLIENT,
735                     "BluetoothHeadsetClient", IBluetoothHeadsetClient.class.getName()) {
736                 @Override
737                 public IBluetoothHeadsetClient getServiceInterface(IBinder service) {
738                     return IBluetoothHeadsetClient.Stub.asInterface(service);
739                 }
740     };
741 
742     /**
743      * Create a BluetoothHeadsetClient proxy object.
744      */
BluetoothHeadsetClient(Context context, ServiceListener listener, BluetoothAdapter adapter)745     BluetoothHeadsetClient(Context context, ServiceListener listener,
746             BluetoothAdapter adapter) {
747         mAdapter = adapter;
748         mAttributionSource = adapter.getAttributionSource();
749         mProfileConnector.connect(context, listener);
750         mCloseGuard = new CloseGuard();
751         mCloseGuard.open("close");
752     }
753 
754     /**
755      * Close the connection to the backing service.
756      * Other public functions of BluetoothHeadsetClient will return default error
757      * results once close() has been called. Multiple invocations of close()
758      * are ok.
759      *
760      * @hide
761      */
close()762     public void close() {
763         if (VDBG) log("close()");
764         mProfileConnector.disconnect();
765         if (mCloseGuard != null) {
766             mCloseGuard.close();
767         }
768     }
769 
getService()770     private IBluetoothHeadsetClient getService() {
771         return mProfileConnector.getService();
772     }
773 
774     /** @hide */
finalize()775     protected void finalize() {
776         if (mCloseGuard != null) {
777             mCloseGuard.warnIfOpen();
778         }
779         close();
780     }
781 
782     /**
783      * Connects to remote device.
784      *
785      * Currently, the system supports only 1 connection. So, in case of the
786      * second connection, this implementation will disconnect already connected
787      * device automatically and will process the new one.
788      *
789      * @param device a remote device we want connect to
790      * @return <code>true</code> if command has been issued successfully; <code>false</code>
791      * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent.
792      *
793      * @hide
794      */
795     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
796     @RequiresBluetoothConnectPermission
797     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
connect(BluetoothDevice device)798     public boolean connect(BluetoothDevice device) {
799         if (DBG) log("connect(" + device + ")");
800         final IBluetoothHeadsetClient service = getService();
801         final boolean defaultValue = false;
802         if (service == null) {
803             Log.w(TAG, "Proxy not attached to service");
804             if (DBG) log(Log.getStackTraceString(new Throwable()));
805         } else if (isEnabled() && isValidDevice(device)) {
806             try {
807                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
808                 service.connect(device, mAttributionSource, recv);
809                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
810             } catch (RemoteException | TimeoutException e) {
811                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
812             }
813         }
814         return defaultValue;
815     }
816 
817     /**
818      * Disconnects remote device
819      *
820      * @param device a remote device we want disconnect
821      * @return <code>true</code> if command has been issued successfully; <code>false</code>
822      * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent.
823      *
824      * @hide
825      */
826     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
827     @RequiresBluetoothConnectPermission
828     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
disconnect(BluetoothDevice device)829     public boolean disconnect(BluetoothDevice device) {
830         if (DBG) log("disconnect(" + device + ")");
831         final IBluetoothHeadsetClient service = getService();
832         final boolean defaultValue = false;
833         if (service == null) {
834             Log.w(TAG, "Proxy not attached to service");
835             if (DBG) log(Log.getStackTraceString(new Throwable()));
836         } else if (isEnabled() && isValidDevice(device)) {
837             try {
838                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
839                 service.disconnect(device, mAttributionSource, recv);
840                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
841             } catch (RemoteException | TimeoutException e) {
842                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
843             }
844         }
845         return defaultValue;
846     }
847 
848     /**
849      * {@inheritDoc}
850      * @hide
851      */
852     @SystemApi
853     @Override
854     @RequiresBluetoothConnectPermission
855     @RequiresPermission(allOf = {
856             android.Manifest.permission.BLUETOOTH_CONNECT,
857             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
858     })
getConnectedDevices()859     public @NonNull List<BluetoothDevice> getConnectedDevices() {
860         if (VDBG) log("getConnectedDevices()");
861         final IBluetoothHeadsetClient service = getService();
862         final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
863         if (service == null) {
864             Log.w(TAG, "Proxy not attached to service");
865             if (DBG) log(Log.getStackTraceString(new Throwable()));
866         } else if (isEnabled()) {
867             try {
868                 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
869                         SynchronousResultReceiver.get();
870                 service.getConnectedDevices(mAttributionSource, recv);
871                 return Attributable.setAttributionSource(
872                         recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
873                         mAttributionSource);
874             } catch (RemoteException e) {
875                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
876                 throw e.rethrowFromSystemServer();
877             } catch (TimeoutException e) {
878                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
879             }
880         }
881         return defaultValue;
882     }
883 
884     /**
885      * {@inheritDoc}
886      * @hide
887      */
888     @SystemApi
889     @Override
890     @RequiresBluetoothConnectPermission
891     @RequiresPermission(allOf = {
892             android.Manifest.permission.BLUETOOTH_CONNECT,
893             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
894     })
getDevicesMatchingConnectionStates( @onNull int[] states)895     public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
896             @NonNull int[] states) {
897         if (VDBG) log("getDevicesMatchingStates()");
898         final IBluetoothHeadsetClient service = getService();
899         final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
900         if (service == null) {
901             Log.w(TAG, "Proxy not attached to service");
902             if (DBG) log(Log.getStackTraceString(new Throwable()));
903         } else if (isEnabled()) {
904             try {
905                 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
906                         SynchronousResultReceiver.get();
907                 service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
908                 return Attributable.setAttributionSource(
909                         recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
910                         mAttributionSource);
911             } catch (RemoteException e) {
912                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
913                 throw e.rethrowFromSystemServer();
914             } catch (TimeoutException e) {
915                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
916             }
917         }
918         return defaultValue;
919     }
920 
921     /**
922      * {@inheritDoc}
923      * @hide
924      */
925     @SystemApi
926     @Override
927     @RequiresBluetoothConnectPermission
928     @RequiresPermission(allOf = {
929             android.Manifest.permission.BLUETOOTH_CONNECT,
930             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
931     })
getConnectionState(@onNull BluetoothDevice device)932     public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
933         if (VDBG) log("getConnectionState(" + device + ")");
934         final IBluetoothHeadsetClient service = getService();
935         final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
936         if (service == null) {
937             Log.w(TAG, "Proxy not attached to service");
938             if (DBG) log(Log.getStackTraceString(new Throwable()));
939         } else if (isEnabled() && isValidDevice(device)) {
940             try {
941                 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
942                 service.getConnectionState(device, mAttributionSource, recv);
943                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
944             } catch (RemoteException e) {
945                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
946                 throw e.rethrowFromSystemServer();
947             } catch (TimeoutException e) {
948                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
949             }
950         }
951         return defaultValue;
952     }
953 
954     /**
955      * Set priority of the profile
956      *
957      * <p> The device should already be paired.
958      * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}
959      *
960      * @param device Paired bluetooth device
961      * @param priority
962      * @return true if priority is set, false on error
963      * @hide
964      */
965     @RequiresBluetoothConnectPermission
966     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
setPriority(BluetoothDevice device, int priority)967     public boolean setPriority(BluetoothDevice device, int priority) {
968         if (DBG) log("setPriority(" + device + ", " + priority + ")");
969         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
970     }
971 
972     /**
973      * Set connection policy of the profile
974      *
975      * <p> The device should already be paired.
976      * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
977      * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
978      *
979      * @param device Paired bluetooth device
980      * @param connectionPolicy is the connection policy to set to for this profile
981      * @return true if connectionPolicy is set, false on error
982      * @hide
983      */
984     @SystemApi
985     @RequiresBluetoothConnectPermission
986     @RequiresPermission(allOf = {
987             android.Manifest.permission.BLUETOOTH_CONNECT,
988             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
989     })
setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)990     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
991             @ConnectionPolicy int connectionPolicy) {
992         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
993         final IBluetoothHeadsetClient service = getService();
994         final boolean defaultValue = false;
995         if (service == null) {
996             Log.w(TAG, "Proxy not attached to service");
997             if (DBG) log(Log.getStackTraceString(new Throwable()));
998         } else if (isEnabled() && isValidDevice(device)
999                 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
1000                     || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
1001             try {
1002                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1003                 service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
1004                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1005             } catch (RemoteException e) {
1006                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1007                 throw e.rethrowFromSystemServer();
1008             } catch (TimeoutException e) {
1009                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1010             }
1011         }
1012         return defaultValue;
1013     }
1014 
1015     /**
1016      * Get the priority of the profile.
1017      *
1018      * <p> The priority can be any of:
1019      * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
1020      *
1021      * @param device Bluetooth device
1022      * @return priority of the device
1023      * @hide
1024      */
1025     @RequiresLegacyBluetoothPermission
1026     @RequiresBluetoothConnectPermission
1027     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getPriority(BluetoothDevice device)1028     public int getPriority(BluetoothDevice device) {
1029         if (VDBG) log("getPriority(" + device + ")");
1030         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
1031     }
1032 
1033     /**
1034      * Get the connection policy of the profile.
1035      *
1036      * <p> The connection policy can be any of:
1037      * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
1038      * {@link #CONNECTION_POLICY_UNKNOWN}
1039      *
1040      * @param device Bluetooth device
1041      * @return connection policy of the device
1042      * @hide
1043      */
1044     @SystemApi
1045     @RequiresLegacyBluetoothPermission
1046     @RequiresBluetoothConnectPermission
1047     @RequiresPermission(allOf = {
1048             android.Manifest.permission.BLUETOOTH_CONNECT,
1049             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1050     })
getConnectionPolicy(@onNull BluetoothDevice device)1051     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
1052         if (VDBG) log("getConnectionPolicy(" + device + ")");
1053         final IBluetoothHeadsetClient service = getService();
1054         final @ConnectionPolicy int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
1055         if (service == null) {
1056             Log.w(TAG, "Proxy not attached to service");
1057             if (DBG) log(Log.getStackTraceString(new Throwable()));
1058         } else if (isEnabled() && isValidDevice(device)) {
1059             try {
1060                 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
1061                 service.getConnectionPolicy(device, mAttributionSource, recv);
1062                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1063             } catch (RemoteException e) {
1064                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1065                 throw e.rethrowFromSystemServer();
1066             } catch (TimeoutException e) {
1067                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1068             }
1069         }
1070         return defaultValue;
1071     }
1072 
1073     /**
1074      * Starts voice recognition.
1075      *
1076      * @param device remote device
1077      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1078      * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent.
1079      *
1080      * <p>Feature required for successful execution is being reported by: {@link
1081      * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature
1082      * is not supported.</p>
1083      *
1084      * @hide
1085      */
1086     @RequiresBluetoothConnectPermission
1087     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
startVoiceRecognition(BluetoothDevice device)1088     public boolean startVoiceRecognition(BluetoothDevice device) {
1089         if (DBG) log("startVoiceRecognition()");
1090         final IBluetoothHeadsetClient service = getService();
1091         final boolean defaultValue = false;
1092         if (service == null) {
1093             Log.w(TAG, "Proxy not attached to service");
1094             if (DBG) log(Log.getStackTraceString(new Throwable()));
1095         } else if (isEnabled() && isValidDevice(device)) {
1096             try {
1097                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1098                 service.startVoiceRecognition(device, mAttributionSource, recv);
1099                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1100             } catch (RemoteException | TimeoutException e) {
1101                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1102             }
1103         }
1104         return defaultValue;
1105     }
1106 
1107     /**
1108      * Send vendor specific AT command.
1109      *
1110      * @param device remote device
1111      * @param vendorId vendor number by Bluetooth SIG
1112      * @param atCommand command to be sent. It start with + prefix and only one command at one time.
1113      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1114      * otherwise.
1115      *
1116      * @hide
1117      */
1118     @RequiresBluetoothConnectPermission
1119     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand)1120     public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) {
1121         if (DBG) log("sendVendorSpecificCommand()");
1122         final IBluetoothHeadsetClient service = getService();
1123         final boolean defaultValue = false;
1124         if (service == null) {
1125             Log.w(TAG, "Proxy not attached to service");
1126             if (DBG) log(Log.getStackTraceString(new Throwable()));
1127         } else if (isEnabled() && isValidDevice(device)) {
1128             try {
1129                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1130                 service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource, recv);
1131                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1132             } catch (RemoteException | TimeoutException e) {
1133                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1134             }
1135         }
1136         return defaultValue;
1137     }
1138 
1139     /**
1140      * Stops voice recognition.
1141      *
1142      * @param device remote device
1143      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1144      * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent.
1145      *
1146      * <p>Feature required for successful execution is being reported by: {@link
1147      * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature
1148      * is not supported.</p>
1149      *
1150      * @hide
1151      */
1152     @RequiresBluetoothConnectPermission
1153     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
stopVoiceRecognition(BluetoothDevice device)1154     public boolean stopVoiceRecognition(BluetoothDevice device) {
1155         if (DBG) log("stopVoiceRecognition()");
1156         final IBluetoothHeadsetClient service = getService();
1157         final boolean defaultValue = false;
1158         if (service == null) {
1159             Log.w(TAG, "Proxy not attached to service");
1160             if (DBG) log(Log.getStackTraceString(new Throwable()));
1161         } else if (isEnabled() && isValidDevice(device)) {
1162             try {
1163                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1164                 service.stopVoiceRecognition(device, mAttributionSource, recv);
1165                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1166             } catch (RemoteException | TimeoutException e) {
1167                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1168             }
1169         }
1170         return defaultValue;
1171     }
1172 
1173     /**
1174      * Returns list of all calls in any state.
1175      *
1176      * @param device remote device
1177      * @return list of calls; empty list if none call exists
1178      *
1179      * @hide
1180      */
1181     @RequiresBluetoothConnectPermission
1182     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getCurrentCalls(BluetoothDevice device)1183     public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
1184         if (DBG) log("getCurrentCalls()");
1185         final IBluetoothHeadsetClient service = getService();
1186         final List<BluetoothHeadsetClientCall> defaultValue = null;
1187         if (service == null) {
1188             Log.w(TAG, "Proxy not attached to service");
1189             if (DBG) log(Log.getStackTraceString(new Throwable()));
1190         } else if (isEnabled() && isValidDevice(device)) {
1191             try {
1192                 final SynchronousResultReceiver<List<BluetoothHeadsetClientCall>> recv =
1193                         SynchronousResultReceiver.get();
1194                 service.getCurrentCalls(device, mAttributionSource, recv);
1195                 return Attributable.setAttributionSource(
1196                         recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
1197                         mAttributionSource);
1198             } catch (RemoteException | TimeoutException e) {
1199                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1200             }
1201         }
1202         return defaultValue;
1203     }
1204 
1205     /**
1206      * Returns list of current values of AG indicators.
1207      *
1208      * @param device remote device
1209      * @return bundle of AG  indicators; null if device is not in CONNECTED state
1210      *
1211      * @hide
1212      */
1213     @RequiresBluetoothConnectPermission
1214     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getCurrentAgEvents(BluetoothDevice device)1215     public Bundle getCurrentAgEvents(BluetoothDevice device) {
1216         if (DBG) log("getCurrentAgEvents()");
1217         final IBluetoothHeadsetClient service = getService();
1218         final Bundle defaultValue = null;
1219         if (service == null) {
1220             Log.w(TAG, "Proxy not attached to service");
1221             if (DBG) log(Log.getStackTraceString(new Throwable()));
1222         } else if (isEnabled() && isValidDevice(device)) {
1223             try {
1224                 final SynchronousResultReceiver<Bundle> recv = SynchronousResultReceiver.get();
1225                 service.getCurrentAgEvents(device, mAttributionSource, recv);
1226                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1227             } catch (RemoteException | TimeoutException e) {
1228                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1229             }
1230         }
1231         return defaultValue;
1232     }
1233 
1234     /**
1235      * Accepts a call
1236      *
1237      * @param device remote device
1238      * @param flag action policy while accepting a call. Possible values {@link #CALL_ACCEPT_NONE},
1239      * {@link #CALL_ACCEPT_HOLD}, {@link #CALL_ACCEPT_TERMINATE}
1240      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1241      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
1242      *
1243      * @hide
1244      */
1245     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1246     @RequiresBluetoothConnectPermission
1247     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
acceptCall(BluetoothDevice device, int flag)1248     public boolean acceptCall(BluetoothDevice device, int flag) {
1249         if (DBG) log("acceptCall()");
1250         final IBluetoothHeadsetClient service = getService();
1251         final boolean defaultValue = false;
1252         if (service == null) {
1253             Log.w(TAG, "Proxy not attached to service");
1254             if (DBG) log(Log.getStackTraceString(new Throwable()));
1255         } else if (isEnabled() && isValidDevice(device)) {
1256             try {
1257                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1258                 service.acceptCall(device, flag, mAttributionSource, recv);
1259                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1260             } catch (RemoteException | TimeoutException e) {
1261                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1262             }
1263         }
1264         return defaultValue;
1265     }
1266 
1267     /**
1268      * Holds a call.
1269      *
1270      * @param device remote device
1271      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1272      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
1273      *
1274      * @hide
1275      */
1276     @RequiresBluetoothConnectPermission
1277     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
holdCall(BluetoothDevice device)1278     public boolean holdCall(BluetoothDevice device) {
1279         if (DBG) log("holdCall()");
1280         final IBluetoothHeadsetClient service = getService();
1281         final boolean defaultValue = false;
1282         if (service == null) {
1283             Log.w(TAG, "Proxy not attached to service");
1284             if (DBG) log(Log.getStackTraceString(new Throwable()));
1285         } else if (isEnabled() && isValidDevice(device)) {
1286             try {
1287                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1288                 service.holdCall(device, mAttributionSource, recv);
1289                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1290             } catch (RemoteException | TimeoutException e) {
1291                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1292             }
1293         }
1294         return defaultValue;
1295     }
1296 
1297     /**
1298      * Rejects a call.
1299      *
1300      * @param device remote device
1301      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1302      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
1303      *
1304      * <p>Feature required for successful execution is being reported by: {@link
1305      * #EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is not
1306      * supported.</p>
1307      *
1308      * @hide
1309      */
1310     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1311     @RequiresBluetoothConnectPermission
1312     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
rejectCall(BluetoothDevice device)1313     public boolean rejectCall(BluetoothDevice device) {
1314         if (DBG) log("rejectCall()");
1315         final IBluetoothHeadsetClient service = getService();
1316         final boolean defaultValue = false;
1317         if (service == null) {
1318             Log.w(TAG, "Proxy not attached to service");
1319             if (DBG) log(Log.getStackTraceString(new Throwable()));
1320         } else if (isEnabled() && isValidDevice(device)) {
1321             try {
1322                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1323                 service.rejectCall(device, mAttributionSource, recv);
1324                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1325             } catch (RemoteException | TimeoutException e) {
1326                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1327             }
1328         }
1329         return defaultValue;
1330     }
1331 
1332     /**
1333      * Terminates a specified call.
1334      *
1335      * Works only when Extended Call Control is supported by Audio Gateway.
1336      *
1337      * @param device remote device
1338      * @param call Handle of call obtained in {@link #dial(BluetoothDevice, String)} or obtained via
1339      * {@link #ACTION_CALL_CHANGED}. {@code call} may be null in which case we will hangup all active
1340      * calls.
1341      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1342      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
1343      *
1344      * <p>Feature required for successful execution is being reported by: {@link
1345      * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not
1346      * supported.</p>
1347      *
1348      * @hide
1349      */
1350     @RequiresBluetoothConnectPermission
1351     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call)1352     public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
1353         if (DBG) log("terminateCall()");
1354         final IBluetoothHeadsetClient service = getService();
1355         final boolean defaultValue = false;
1356         if (service == null) {
1357             Log.w(TAG, "Proxy not attached to service");
1358             if (DBG) log(Log.getStackTraceString(new Throwable()));
1359         } else if (isEnabled() && isValidDevice(device)) {
1360             try {
1361                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1362                 service.terminateCall(device, call, mAttributionSource, recv);
1363                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1364             } catch (RemoteException | TimeoutException e) {
1365                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1366             }
1367         }
1368         return defaultValue;
1369     }
1370 
1371     /**
1372      * Enters private mode with a specified call.
1373      *
1374      * Works only when Extended Call Control is supported by Audio Gateway.
1375      *
1376      * @param device remote device
1377      * @param index index of the call to connect in private mode
1378      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1379      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
1380      *
1381      * <p>Feature required for successful execution is being reported by: {@link
1382      * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not
1383      * supported.</p>
1384      *
1385      * @hide
1386      */
1387     @RequiresBluetoothConnectPermission
1388     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
enterPrivateMode(BluetoothDevice device, int index)1389     public boolean enterPrivateMode(BluetoothDevice device, int index) {
1390         if (DBG) log("enterPrivateMode()");
1391         final IBluetoothHeadsetClient service = getService();
1392         final boolean defaultValue = false;
1393         if (service == null) {
1394             Log.w(TAG, "Proxy not attached to service");
1395             if (DBG) log(Log.getStackTraceString(new Throwable()));
1396         } else if (isEnabled() && isValidDevice(device)) {
1397             try {
1398                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1399                 service.enterPrivateMode(device, index, mAttributionSource, recv);
1400                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1401             } catch (RemoteException | TimeoutException e) {
1402                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1403             }
1404         }
1405         return defaultValue;
1406     }
1407 
1408     /**
1409      * Performs explicit call transfer.
1410      *
1411      * That means connect other calls and disconnect.
1412      *
1413      * @param device remote device
1414      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1415      * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
1416      *
1417      * <p>Feature required for successful execution is being reported by: {@link
1418      * #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when feature
1419      * is not supported.</p>
1420      *
1421      * @hide
1422      */
1423     @RequiresBluetoothConnectPermission
1424     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
explicitCallTransfer(BluetoothDevice device)1425     public boolean explicitCallTransfer(BluetoothDevice device) {
1426         if (DBG) log("explicitCallTransfer()");
1427         final IBluetoothHeadsetClient service = getService();
1428         final boolean defaultValue = false;
1429         if (service == null) {
1430             Log.w(TAG, "Proxy not attached to service");
1431             if (DBG) log(Log.getStackTraceString(new Throwable()));
1432         } else if (isEnabled() && isValidDevice(device)) {
1433             try {
1434                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1435                 service.explicitCallTransfer(device, mAttributionSource, recv);
1436                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1437             } catch (RemoteException | TimeoutException e) {
1438                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1439             }
1440         }
1441         return defaultValue;
1442     }
1443 
1444     /**
1445      * Places a call with specified number.
1446      *
1447      * @param device remote device
1448      * @param number valid phone number
1449      * @return <code>{@link BluetoothHeadsetClientCall} call</code> if command has been issued
1450      * successfully; <code>{@link null}</code> otherwise; upon completion HFP sends {@link
1451      * #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent otherwise;
1452      *
1453      * @hide
1454      */
1455     @RequiresBluetoothConnectPermission
1456     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
dial(BluetoothDevice device, String number)1457     public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
1458         if (DBG) log("dial()");
1459         final IBluetoothHeadsetClient service = getService();
1460         final BluetoothHeadsetClientCall defaultValue = null;
1461         if (service == null) {
1462             Log.w(TAG, "Proxy not attached to service");
1463             if (DBG) log(Log.getStackTraceString(new Throwable()));
1464         } else if (isEnabled() && isValidDevice(device)) {
1465             try {
1466                 final SynchronousResultReceiver<BluetoothHeadsetClientCall> recv =
1467                         SynchronousResultReceiver.get();
1468                 service.dial(device, number, mAttributionSource, recv);
1469                 return Attributable.setAttributionSource(
1470                         recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
1471                         mAttributionSource);
1472             } catch (RemoteException | TimeoutException e) {
1473                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1474             }
1475         }
1476         return defaultValue;
1477     }
1478 
1479     /**
1480      * Sends DTMF code.
1481      *
1482      * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,#
1483      *
1484      * @param device remote device
1485      * @param code ASCII code
1486      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1487      * otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent;
1488      *
1489      * @hide
1490      */
1491     @RequiresBluetoothConnectPermission
1492     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
sendDTMF(BluetoothDevice device, byte code)1493     public boolean sendDTMF(BluetoothDevice device, byte code) {
1494         if (DBG) log("sendDTMF()");
1495         final IBluetoothHeadsetClient service = getService();
1496         final boolean defaultValue = false;
1497         if (service == null) {
1498             Log.w(TAG, "Proxy not attached to service");
1499             if (DBG) log(Log.getStackTraceString(new Throwable()));
1500         } else if (isEnabled() && isValidDevice(device)) {
1501             try {
1502                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1503                 service.sendDTMF(device, code, mAttributionSource, recv);
1504                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1505             } catch (RemoteException | TimeoutException e) {
1506                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1507             }
1508         }
1509         return defaultValue;
1510     }
1511 
1512     /**
1513      * Get a number corresponding to last voice tag recorded on AG.
1514      *
1515      * @param device remote device
1516      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1517      * otherwise; upon completion HFP sends {@link #ACTION_LAST_VTAG} or {@link #ACTION_RESULT}
1518      * intent;
1519      *
1520      * <p>Feature required for successful execution is being reported by: {@link
1521      * #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. This method invocation will fail silently when
1522      * feature is not supported.</p>
1523      *
1524      * @hide
1525      */
1526     @RequiresBluetoothConnectPermission
1527     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getLastVoiceTagNumber(BluetoothDevice device)1528     public boolean getLastVoiceTagNumber(BluetoothDevice device) {
1529         if (DBG) log("getLastVoiceTagNumber()");
1530         final IBluetoothHeadsetClient service = getService();
1531         final boolean defaultValue = false;
1532         if (service == null) {
1533             Log.w(TAG, "Proxy not attached to service");
1534             if (DBG) log(Log.getStackTraceString(new Throwable()));
1535         } else if (isEnabled() && isValidDevice(device)) {
1536             try {
1537                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1538                 service.getLastVoiceTagNumber(device, mAttributionSource, recv);
1539                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1540             } catch (RemoteException | TimeoutException e) {
1541                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1542             }
1543         }
1544         return defaultValue;
1545     }
1546 
1547     /**
1548      * Returns current audio state of Audio Gateway.
1549      *
1550      * Note: This is an internal function and shouldn't be exposed
1551      *
1552      * @hide
1553      */
1554     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1555     @RequiresBluetoothConnectPermission
1556     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getAudioState(BluetoothDevice device)1557     public int getAudioState(BluetoothDevice device) {
1558         if (VDBG) log("getAudioState");
1559         final IBluetoothHeadsetClient service = getService();
1560         final int defaultValue = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
1561         if (service == null) {
1562             Log.w(TAG, "Proxy not attached to service");
1563             if (DBG) log(Log.getStackTraceString(new Throwable()));
1564         } else if (isEnabled()) {
1565             try {
1566                 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
1567                 service.getAudioState(device, mAttributionSource, recv);
1568                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1569             } catch (RemoteException | TimeoutException e) {
1570                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1571             }
1572         } else {
1573             return defaultValue;
1574         }
1575         return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
1576     }
1577 
1578     /**
1579      * Sets whether audio routing is allowed.
1580      *
1581      * @param device remote device
1582      * @param allowed if routing is allowed to the device Note: This is an internal function and
1583      * shouldn't be exposed
1584      *
1585      * @hide
1586      */
1587     @RequiresBluetoothConnectPermission
1588     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
setAudioRouteAllowed(BluetoothDevice device, boolean allowed)1589     public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
1590         if (VDBG) log("setAudioRouteAllowed");
1591         final IBluetoothHeadsetClient service = getService();
1592         if (service == null) {
1593             Log.w(TAG, "Proxy not attached to service");
1594             if (DBG) log(Log.getStackTraceString(new Throwable()));
1595         } else if (isEnabled()) {
1596             try {
1597                 final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
1598                 service.setAudioRouteAllowed(device, allowed, mAttributionSource, recv);
1599                 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1600             } catch (RemoteException | TimeoutException e) {
1601                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1602             }
1603         }
1604     }
1605 
1606     /**
1607      * Returns whether audio routing is allowed.
1608      *
1609      * @param device remote device
1610      * @return whether the command succeeded Note: This is an internal function and shouldn't be
1611      * exposed
1612      *
1613      * @hide
1614      */
1615     @RequiresBluetoothConnectPermission
1616     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getAudioRouteAllowed(BluetoothDevice device)1617     public boolean getAudioRouteAllowed(BluetoothDevice device) {
1618         if (VDBG) log("getAudioRouteAllowed");
1619         final IBluetoothHeadsetClient service = getService();
1620         final boolean defaultValue = false;
1621         if (service == null) {
1622             Log.w(TAG, "Proxy not attached to service");
1623             if (DBG) log(Log.getStackTraceString(new Throwable()));
1624         } else if (isEnabled()) {
1625             try {
1626                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1627                 service.getAudioRouteAllowed(device, mAttributionSource, recv);
1628                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1629             } catch (RemoteException | TimeoutException e) {
1630                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1631             }
1632         }
1633         return defaultValue;
1634     }
1635 
1636     /**
1637      * Initiates a connection of audio channel.
1638      *
1639      * It setup SCO channel with remote connected Handsfree AG device.
1640      *
1641      * @param device remote device
1642      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1643      * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
1644      *
1645      * @hide
1646      */
1647     @RequiresBluetoothConnectPermission
1648     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
connectAudio(BluetoothDevice device)1649     public boolean connectAudio(BluetoothDevice device) {
1650         if (VDBG) log("connectAudio");
1651         final IBluetoothHeadsetClient service = getService();
1652         final boolean defaultValue = false;
1653         if (service == null) {
1654             Log.w(TAG, "Proxy not attached to service");
1655             if (DBG) log(Log.getStackTraceString(new Throwable()));
1656         } else if (isEnabled()) {
1657             try {
1658                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1659                 service.connectAudio(device, mAttributionSource, recv);
1660                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1661             } catch (RemoteException | TimeoutException e) {
1662                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1663             }
1664         }
1665         return defaultValue;
1666     }
1667 
1668     /**
1669      * Disconnects audio channel.
1670      *
1671      * It tears down the SCO channel from remote AG device.
1672      *
1673      * @param device remote device
1674      * @return <code>true</code> if command has been issued successfully; <code>false</code>
1675      * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
1676      *
1677      * @hide
1678      */
1679     @RequiresBluetoothConnectPermission
1680     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
disconnectAudio(BluetoothDevice device)1681     public boolean disconnectAudio(BluetoothDevice device) {
1682         if (VDBG) log("disconnectAudio");
1683         final IBluetoothHeadsetClient service = getService();
1684         final boolean defaultValue = false;
1685         if (service == null) {
1686             Log.w(TAG, "Proxy not attached to service");
1687             if (DBG) log(Log.getStackTraceString(new Throwable()));
1688         } else if (isEnabled()) {
1689             try {
1690                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1691                 service.disconnectAudio(device, mAttributionSource, recv);
1692                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1693             } catch (RemoteException | TimeoutException e) {
1694                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1695             }
1696         }
1697         return defaultValue;
1698     }
1699 
1700     /**
1701      * Get Audio Gateway features
1702      *
1703      * @param device remote device
1704      * @return bundle of AG features; null if no service or AG not connected
1705      *
1706      * @hide
1707      */
1708     @RequiresBluetoothConnectPermission
1709     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getCurrentAgFeatures(BluetoothDevice device)1710     public Bundle getCurrentAgFeatures(BluetoothDevice device) {
1711         if (VDBG) log("getCurrentAgFeatures");
1712         final IBluetoothHeadsetClient service = getService();
1713         final Bundle defaultValue = null;
1714         if (service == null) {
1715             Log.w(TAG, "Proxy not attached to service");
1716             if (DBG) log(Log.getStackTraceString(new Throwable()));
1717         } else if (isEnabled()) {
1718             try {
1719                 final SynchronousResultReceiver<Bundle> recv = SynchronousResultReceiver.get();
1720                 service.getCurrentAgFeatures(device, mAttributionSource, recv);
1721                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1722             } catch (RemoteException | TimeoutException e) {
1723                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1724             }
1725         }
1726         return defaultValue;
1727     }
1728 
1729     /**
1730      * A class that contains the network service info provided by the HFP Client profile
1731      *
1732      * @hide
1733      */
1734     @SystemApi
1735     public static final class NetworkServiceState implements Parcelable {
1736         /** The device associated with this service state */
1737         private final BluetoothDevice mDevice;
1738 
1739         /** True if there is service available, False otherwise */
1740         private final boolean mIsServiceAvailable;
1741 
1742         /** The name of the operator associated with the remote device's current network */
1743         private final String mOperatorName;
1744 
1745         /**
1746          * The general signal strength, from 0 to 5
1747          */
1748         private final int mSignalStrength;
1749 
1750         /** True if we are network roaming, False otherwise */
1751         private final boolean mIsRoaming;
1752 
1753         /**
1754          * Create a NetworkServiceState Object
1755          *
1756          * @param device The device associated with this network signal state
1757          * @param isServiceAvailable True if there is service available, False otherwise
1758          * @param operatorName The name of the operator associated with the remote device's current
1759          *                     network. Use Null if the value is unknown
1760          * @param signalStrength The general signal strength
1761          * @param isRoaming True if we are network roaming, False otherwise
1762          *
1763          * @hide
1764          */
NetworkServiceState(BluetoothDevice device, boolean isServiceAvailable, String operatorName, int signalStrength, boolean isRoaming)1765         public NetworkServiceState(BluetoothDevice device, boolean isServiceAvailable,
1766                 String operatorName, int signalStrength, boolean isRoaming) {
1767             mDevice = device;
1768             mIsServiceAvailable = isServiceAvailable;
1769             mOperatorName = operatorName;
1770             mSignalStrength = signalStrength;
1771             mIsRoaming = isRoaming;
1772         }
1773 
1774         /**
1775          * Get the device associated with this network service state
1776          *
1777          * @return a BluetoothDevice associated with this state
1778          *
1779          * @hide
1780          */
1781         @SystemApi
getDevice()1782         public @NonNull BluetoothDevice getDevice() {
1783             return mDevice;
1784         }
1785 
1786         /**
1787          * Get the network service availablility state
1788          *
1789          * @return True if there is service available, False otherwise
1790          *
1791          * @hide
1792          */
1793         @SystemApi
isServiceAvailable()1794         public boolean isServiceAvailable() {
1795             return mIsServiceAvailable;
1796         }
1797 
1798         /**
1799          * Get the network operator name
1800          *
1801          * @return A string representing the name of the operator the remote device is on, or null
1802          *         if unknown.
1803          *
1804          * @hide
1805          */
1806         @SystemApi
getNetworkOperatorName()1807         public @Nullable String getNetworkOperatorName() {
1808             return mOperatorName;
1809         }
1810 
1811         /**
1812          * The HFP Client defined signal strength, from 0 to 5.
1813          *
1814          * Bluetooth HFP v1.8 specifies that the signal strength of a device can be [0, 5]. It does
1815          * not place any requirements on how a device derives those values. While they're typically
1816          * derived from signal quality/RSSI buckets, there's no way to be certain on the exact
1817          * meaning. Derivation methods can even change between wireless cellular technologies.
1818          *
1819          * That said, you can "generally" interpret the values relative to each other as follows:
1820          *   - Level 0: None/Unknown
1821          *   - Level 1: Very Poor
1822          *   - Level 2: Poor
1823          *   - Level 3: Fair
1824          *   - Level 4: Good
1825          *   - Level 5: Great
1826          *
1827          * @return the HFP Client defined signal strength, range [0, 5]
1828          *
1829          * @hide
1830          */
1831         @SystemApi
getSignalStrength()1832         public @IntRange(from = 0, to = 5) int getSignalStrength() {
1833             return mSignalStrength;
1834         }
1835 
1836         /**
1837          * Get the network service roaming status
1838          *
1839          * * @return True if we are network roaming, False otherwise
1840          *
1841          * @hide
1842          */
1843         @SystemApi
isRoaming()1844         public boolean isRoaming() {
1845             return mIsRoaming;
1846         }
1847 
1848         /**
1849          * {@link Parcelable.Creator} interface implementation.
1850          */
1851         public static final @NonNull Parcelable.Creator<NetworkServiceState> CREATOR =
1852                 new Parcelable.Creator<NetworkServiceState>() {
1853             public NetworkServiceState createFromParcel(Parcel in) {
1854                 return new NetworkServiceState((BluetoothDevice) in.readParcelable(null),
1855                         in.readInt() == 1, in.readString(), in.readInt(), in.readInt() == 1);
1856             }
1857 
1858             public @NonNull NetworkServiceState[] newArray(int size) {
1859                 return new NetworkServiceState[size];
1860             }
1861         };
1862 
1863         /**
1864          * @hide
1865          */
1866         @Override
writeToParcel(@onNull Parcel out, int flags)1867         public void writeToParcel(@NonNull Parcel out, int flags) {
1868             out.writeParcelable(mDevice, 0);
1869             out.writeInt(mIsServiceAvailable ? 1 : 0);
1870             out.writeString(mOperatorName);
1871             out.writeInt(mSignalStrength);
1872             out.writeInt(mIsRoaming ? 1 : 0);
1873         }
1874 
1875         /**
1876          * @hide
1877          */
1878         @Override
describeContents()1879         public int describeContents() {
1880             return 0;
1881         }
1882     }
1883 
1884     /**
1885      * Intent used to broadcast the change in network service state of an HFP Client device
1886      *
1887      * <p>This intent will have 2 extras:
1888      * <ul>
1889      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
1890      * <li> {@link EXTRA_NETWORK_SERVICE_STATE} - A {@link NetworkServiceState} object. </li>
1891      * </ul>
1892      *
1893      * @hide
1894      */
1895     @SuppressLint("ActionValue")
1896     @SystemApi
1897     @RequiresBluetoothConnectPermission
1898     @RequiresPermission(allOf = {
1899             android.Manifest.permission.BLUETOOTH_CONNECT,
1900             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1901     })
1902     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
1903     public static final String ACTION_NETWORK_SERVICE_STATE_CHANGED =
1904             "android.bluetooth.headsetclient.profile.action.NETWORK_SERVICE_STATE_CHANGED";
1905 
1906     /**
1907      * Extra for the network service state changed intent.
1908      *
1909      * This extra represents the current network service state of a connected Bluetooth device.
1910      *
1911      * @hide
1912      */
1913     @SuppressLint("ActionValue")
1914     @SystemApi
1915     public static final String EXTRA_NETWORK_SERVICE_STATE =
1916             "android.bluetooth.headsetclient.extra.EXTRA_NETWORK_SERVICE_STATE";
1917 
1918     /**
1919      * Get the network service state for a device
1920      *
1921      * @param device The {@link BluetoothDevice} you want the network service state for
1922      * @return A {@link NetworkServiceState} representing the network service state of the device,
1923      *         or null if the device is not connected
1924      * @hide
1925      */
1926     @SystemApi
1927     @RequiresBluetoothConnectPermission
1928     @RequiresPermission(allOf = {
1929             android.Manifest.permission.BLUETOOTH_CONNECT,
1930             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1931     })
getNetworkServiceState(@onNull BluetoothDevice device)1932     public @Nullable NetworkServiceState getNetworkServiceState(@NonNull BluetoothDevice device) {
1933         if (device == null) {
1934             return null;
1935         }
1936 
1937         Bundle agEvents = getCurrentAgEvents(device);
1938         if (agEvents == null) {
1939             return null;
1940         }
1941 
1942         boolean isServiceAvailable = (agEvents.getInt(EXTRA_NETWORK_STATUS, 0) == 1);
1943         int signalStrength = agEvents.getInt(EXTRA_NETWORK_SIGNAL_STRENGTH, 0);
1944         String operatorName = agEvents.getString(EXTRA_OPERATOR_NAME, null);
1945         boolean isRoaming = (agEvents.getInt(EXTRA_NETWORK_ROAMING, 0) == 1);
1946 
1947         return new NetworkServiceState(device, isServiceAvailable, operatorName, signalStrength,
1948                 isRoaming);
1949     }
1950 
isEnabled()1951     private boolean isEnabled() {
1952         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
1953     }
1954 
isValidDevice(BluetoothDevice device)1955     private static boolean isValidDevice(BluetoothDevice device) {
1956         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
1957     }
1958 
log(String msg)1959     private static void log(String msg) {
1960         Log.d(TAG, msg);
1961     }
1962 }
1963