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