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