blob: ec18d42698c13ddbab61d967e9f657b312bb8387 [file] [log] [blame]
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301/*
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
17package android.bluetooth;
18
Mathew Inwood4dc66d32018-08-01 15:07:20 +010019import android.annotation.UnsupportedAppUsage;
Hemant Gupta7aca90f2013-08-19 19:03:51 +053020import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
Jeff Sharkey0a17db12016-11-04 11:23:46 -060024import android.os.Binder;
Hemant Gupta7aca90f2013-08-19 19:03:51 +053025import android.os.Bundle;
26import android.os.IBinder;
27import android.os.RemoteException;
28import android.util.Log;
29
30import java.util.ArrayList;
31import 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
Jack Hea355e5e2017-08-22 16:06:54 -070041 */
Mike Lockwoodcf916d32014-06-12 11:23:40 -070042public final class BluetoothHeadsetClient implements BluetoothProfile {
43 private static final String TAG = "BluetoothHeadsetClient";
Hemant Gupta7aca90f2013-08-19 19:03:51 +053044 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 =
Jack Hea355e5e2017-08-22 16:06:54 -070074 "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED";
Hemant Gupta7aca90f2013-08-19 19:03:51 +053075
76 /**
77 * Intent sent whenever audio state changes.
78 *
79 * <p>It includes two mandatory extras:
Jack He16eeac32017-08-17 12:11:18 -070080 * {@link BluetoothProfile#EXTRA_STATE},
81 * {@link BluetoothProfile#EXTRA_PREVIOUS_STATE},
Hemant Gupta7aca90f2013-08-19 19:03:51 +053082 * 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 =
Jack Hea355e5e2017-08-22 16:06:54 -070092 "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED";
Hemant Gupta7aca90f2013-08-19 19:03:51 +053093
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 =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700108 "android.bluetooth.headsetclient.profile.action.AG_EVENT";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530109
110 /**
111 * Intent sent whenever state of a call changes.
112 *
113 * <p>It includes:
114 * {@link #EXTRA_CALL},
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700115 * with value of {@link BluetoothHeadsetClientCall} instance,
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530116 * representing actual call state.</p>
117 */
118 public static final String ACTION_CALL_CHANGED =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700119 "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530120
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 =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700129 "android.bluetooth.headsetclient.profile.action.RESULT";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530130
131 /**
132 * Intent that notifies about the number attached to the last voice tag
133 * recorded on AG.
134 *
135 * <p>It contains:
136 * {@link #EXTRA_NUMBER},
137 * with a <code>String</code> value representing phone number.</p>
138 */
139 public static final String ACTION_LAST_VTAG =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700140 "android.bluetooth.headsetclient.profile.action.LAST_VTAG";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530141
142 public static final int STATE_AUDIO_DISCONNECTED = 0;
143 public static final int STATE_AUDIO_CONNECTING = 1;
144 public static final int STATE_AUDIO_CONNECTED = 2;
145
146 /**
147 * Extra with information if connected audio is WBS.
148 * <p>Possible values: <code>true</code>,
Jack Hea355e5e2017-08-22 16:06:54 -0700149 * <code>false</code>.</p>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530150 */
151 public static final String EXTRA_AUDIO_WBS =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700152 "android.bluetooth.headsetclient.extra.AUDIO_WBS";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530153
154 /**
155 * Extra for AG_EVENT indicates network status.
156 * <p>Value: 0 - network unavailable,
Jack Hea355e5e2017-08-22 16:06:54 -0700157 * 1 - network available </p>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530158 */
159 public static final String EXTRA_NETWORK_STATUS =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700160 "android.bluetooth.headsetclient.extra.NETWORK_STATUS";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530161 /**
162 * Extra for AG_EVENT intent indicates network signal strength.
163 * <p>Value: <code>Integer</code> representing signal strength.</p>
164 */
165 public static final String EXTRA_NETWORK_SIGNAL_STRENGTH =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700166 "android.bluetooth.headsetclient.extra.NETWORK_SIGNAL_STRENGTH";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530167 /**
168 * Extra for AG_EVENT intent indicates roaming state.
169 * <p>Value: 0 - no roaming
Jack Hea355e5e2017-08-22 16:06:54 -0700170 * 1 - active roaming</p>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530171 */
172 public static final String EXTRA_NETWORK_ROAMING =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700173 "android.bluetooth.headsetclient.extra.NETWORK_ROAMING";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530174 /**
175 * Extra for AG_EVENT intent indicates the battery level.
176 * <p>Value: <code>Integer</code> representing signal strength.</p>
177 */
178 public static final String EXTRA_BATTERY_LEVEL =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700179 "android.bluetooth.headsetclient.extra.BATTERY_LEVEL";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530180 /**
181 * Extra for AG_EVENT intent indicates operator name.
182 * <p>Value: <code>String</code> representing operator name.</p>
183 */
184 public static final String EXTRA_OPERATOR_NAME =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700185 "android.bluetooth.headsetclient.extra.OPERATOR_NAME";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530186 /**
187 * Extra for AG_EVENT intent indicates voice recognition state.
188 * <p>Value:
Jack Hea355e5e2017-08-22 16:06:54 -0700189 * 0 - voice recognition stopped,
190 * 1 - voice recognition started.</p>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530191 */
192 public static final String EXTRA_VOICE_RECOGNITION =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700193 "android.bluetooth.headsetclient.extra.VOICE_RECOGNITION";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530194 /**
195 * Extra for AG_EVENT intent indicates in band ring state.
196 * <p>Value:
Jack Hea355e5e2017-08-22 16:06:54 -0700197 * 0 - in band ring tone not supported, or
198 * 1 - in band ring tone supported.</p>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530199 */
200 public static final String EXTRA_IN_BAND_RING =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700201 "android.bluetooth.headsetclient.extra.IN_BAND_RING";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530202
203 /**
204 * Extra for AG_EVENT intent indicates subscriber info.
205 * <p>Value: <code>String</code> containing subscriber information.</p>
206 */
207 public static final String EXTRA_SUBSCRIBER_INFO =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700208 "android.bluetooth.headsetclient.extra.SUBSCRIBER_INFO";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530209
210 /**
Jack Hea355e5e2017-08-22 16:06:54 -0700211 * Extra for AG_CALL_CHANGED intent indicates the
212 * {@link BluetoothHeadsetClientCall} object that has changed.
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530213 */
214 public static final String EXTRA_CALL =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700215 "android.bluetooth.headsetclient.extra.CALL";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530216
217 /**
218 * Extra for ACTION_LAST_VTAG intent.
219 * <p>Value: <code>String</code> representing phone number
220 * corresponding to last voice tag recorded on AG</p>
221 */
222 public static final String EXTRA_NUMBER =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700223 "android.bluetooth.headsetclient.extra.NUMBER";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530224
225 /**
226 * Extra for ACTION_RESULT intent that shows the result code of
227 * last issued action.
228 * <p>Possible results:
229 * {@link #ACTION_RESULT_OK},
230 * {@link #ACTION_RESULT_ERROR},
231 * {@link #ACTION_RESULT_ERROR_NO_CARRIER},
232 * {@link #ACTION_RESULT_ERROR_BUSY},
233 * {@link #ACTION_RESULT_ERROR_NO_ANSWER},
234 * {@link #ACTION_RESULT_ERROR_DELAYED},
235 * {@link #ACTION_RESULT_ERROR_BLACKLISTED},
236 * {@link #ACTION_RESULT_ERROR_CME}</p>
237 */
238 public static final String EXTRA_RESULT_CODE =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700239 "android.bluetooth.headsetclient.extra.RESULT_CODE";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530240
241 /**
242 * Extra for ACTION_RESULT intent that shows the extended result code of
243 * last issued action.
244 * <p>Value: <code>Integer</code> - error code.</p>
245 */
246 public static final String EXTRA_CME_CODE =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700247 "android.bluetooth.headsetclient.extra.CME_CODE";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530248
249 /* Extras for AG_FEATURES, extras type is boolean */
250 // TODO verify if all of those are actually useful
251 /**
252 * AG feature: three way calling.
253 */
Jack He2992cd02017-08-22 21:21:23 -0700254 public static final String EXTRA_AG_FEATURE_3WAY_CALLING =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700255 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530256 /**
257 * AG feature: voice recognition.
258 */
Jack He2992cd02017-08-22 21:21:23 -0700259 public static final String EXTRA_AG_FEATURE_VOICE_RECOGNITION =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700260 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530261 /**
262 * AG feature: fetching phone number for voice tagging procedure.
263 */
Jack He2992cd02017-08-22 21:21:23 -0700264 public static final String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700265 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530266 /**
267 * AG feature: ability to reject incoming call.
268 */
Jack He2992cd02017-08-22 21:21:23 -0700269 public static final String EXTRA_AG_FEATURE_REJECT_CALL =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700270 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_REJECT_CALL";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530271 /**
272 * AG feature: enhanced call handling (terminate specific call, private consultation).
273 */
Jack He2992cd02017-08-22 21:21:23 -0700274 public static final String EXTRA_AG_FEATURE_ECC =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700275 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530276 /**
277 * AG feature: response and hold.
278 */
Jack He2992cd02017-08-22 21:21:23 -0700279 public static final String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700280 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530281 /**
282 * AG call handling feature: accept held or waiting call in three way calling scenarios.
283 */
Jack He2992cd02017-08-22 21:21:23 -0700284 public static final String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700285 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530286 /**
287 * AG call handling feature: release held or waiting call in three way calling scenarios.
288 */
Jack He2992cd02017-08-22 21:21:23 -0700289 public static final String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700290 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530291 /**
292 * AG call handling feature: release active call and accept held or waiting call in three way
293 * calling scenarios.
294 */
Jack He2992cd02017-08-22 21:21:23 -0700295 public static final String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700296 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530297 /**
298 * AG call handling feature: merge two calls, held and active - multi party conference mode.
299 */
Jack He2992cd02017-08-22 21:21:23 -0700300 public static final String EXTRA_AG_FEATURE_MERGE =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700301 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530302 /**
303 * AG call handling feature: merge calls and disconnect from multi party
304 * conversation leaving peers connected to each other.
305 * Note that this feature needs to be supported by mobile network operator
306 * as it requires connection and billing transfer.
307 */
Jack He2992cd02017-08-22 21:21:23 -0700308 public static final String EXTRA_AG_FEATURE_MERGE_AND_DETACH =
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700309 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH";
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530310
311 /* Action result codes */
Jack He2992cd02017-08-22 21:21:23 -0700312 public static final int ACTION_RESULT_OK = 0;
313 public static final int ACTION_RESULT_ERROR = 1;
314 public static final int ACTION_RESULT_ERROR_NO_CARRIER = 2;
315 public static final int ACTION_RESULT_ERROR_BUSY = 3;
316 public static final int ACTION_RESULT_ERROR_NO_ANSWER = 4;
317 public static final int ACTION_RESULT_ERROR_DELAYED = 5;
318 public static final int ACTION_RESULT_ERROR_BLACKLISTED = 6;
319 public static final int ACTION_RESULT_ERROR_CME = 7;
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530320
321 /* Detailed CME error codes */
Jack He2992cd02017-08-22 21:21:23 -0700322 public static final int CME_PHONE_FAILURE = 0;
323 public static final int CME_NO_CONNECTION_TO_PHONE = 1;
324 public static final int CME_OPERATION_NOT_ALLOWED = 3;
325 public static final int CME_OPERATION_NOT_SUPPORTED = 4;
326 public static final int CME_PHSIM_PIN_REQUIRED = 5;
327 public static final int CME_PHFSIM_PIN_REQUIRED = 6;
328 public static final int CME_PHFSIM_PUK_REQUIRED = 7;
329 public static final int CME_SIM_NOT_INSERTED = 10;
330 public static final int CME_SIM_PIN_REQUIRED = 11;
331 public static final int CME_SIM_PUK_REQUIRED = 12;
332 public static final int CME_SIM_FAILURE = 13;
333 public static final int CME_SIM_BUSY = 14;
334 public static final int CME_SIM_WRONG = 15;
335 public static final int CME_INCORRECT_PASSWORD = 16;
336 public static final int CME_SIM_PIN2_REQUIRED = 17;
337 public static final int CME_SIM_PUK2_REQUIRED = 18;
338 public static final int CME_MEMORY_FULL = 20;
339 public static final int CME_INVALID_INDEX = 21;
340 public static final int CME_NOT_FOUND = 22;
341 public static final int CME_MEMORY_FAILURE = 23;
342 public static final int CME_TEXT_STRING_TOO_LONG = 24;
343 public static final int CME_INVALID_CHARACTER_IN_TEXT_STRING = 25;
344 public static final int CME_DIAL_STRING_TOO_LONG = 26;
345 public static final int CME_INVALID_CHARACTER_IN_DIAL_STRING = 27;
346 public static final int CME_NO_NETWORK_SERVICE = 30;
347 public static final int CME_NETWORK_TIMEOUT = 31;
348 public static final int CME_EMERGENCY_SERVICE_ONLY = 32;
349 public static final int CME_NO_SIMULTANOUS_VOIP_CS_CALLS = 33;
350 public static final int CME_NOT_SUPPORTED_FOR_VOIP = 34;
351 public static final int CME_SIP_RESPONSE_CODE = 35;
352 public static final int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED = 40;
353 public static final int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED = 41;
354 public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED = 42;
355 public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED = 43;
356 public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44;
357 public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45;
358 public static final int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED = 46;
359 public static final int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED = 47;
360 public static final int CME_HIDDEN_KEY_REQUIRED = 48;
361 public static final int CME_EAP_NOT_SUPPORTED = 49;
362 public static final int CME_INCORRECT_PARAMETERS = 50;
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530363
364 /* Action policy for other calls when accepting call */
365 public static final int CALL_ACCEPT_NONE = 0;
366 public static final int CALL_ACCEPT_HOLD = 1;
367 public static final int CALL_ACCEPT_TERMINATE = 2;
368
369 private Context mContext;
370 private ServiceListener mServiceListener;
Jack He16eeac32017-08-17 12:11:18 -0700371 private volatile IBluetoothHeadsetClient mService;
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530372 private BluetoothAdapter mAdapter;
373
Jack He2992cd02017-08-22 21:21:23 -0700374 private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530375 new IBluetoothStateChangeCallback.Stub() {
376 @Override
377 public void onBluetoothStateChange(boolean up) {
378 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
379 if (!up) {
Jack Hea355e5e2017-08-22 16:06:54 -0700380 if (VDBG) Log.d(TAG, "Unbinding service...");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530381 synchronized (mConnection) {
382 try {
383 mService = null;
384 mContext.unbindService(mConnection);
385 } catch (Exception re) {
Jack Hea355e5e2017-08-22 16:06:54 -0700386 Log.e(TAG, "", re);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530387 }
388 }
389 } else {
390 synchronized (mConnection) {
391 try {
392 if (mService == null) {
Jack Hea355e5e2017-08-22 16:06:54 -0700393 if (VDBG) Log.d(TAG, "Binding service...");
394 Intent intent = new Intent(
395 IBluetoothHeadsetClient.class.getName());
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700396 doBind();
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530397 }
398 } catch (Exception re) {
Jack Hea355e5e2017-08-22 16:06:54 -0700399 Log.e(TAG, "", re);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530400 }
401 }
402 }
403 }
Jack Hea355e5e2017-08-22 16:06:54 -0700404 };
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530405
406 /**
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700407 * Create a BluetoothHeadsetClient proxy object.
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530408 */
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700409 /*package*/ BluetoothHeadsetClient(Context context, ServiceListener l) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530410 mContext = context;
411 mServiceListener = l;
412 mAdapter = BluetoothAdapter.getDefaultAdapter();
413
414 IBluetoothManager mgr = mAdapter.getBluetoothManager();
415 if (mgr != null) {
416 try {
417 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
418 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700419 Log.e(TAG, "", e);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530420 }
421 }
422
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700423 doBind();
424 }
425
426 boolean doBind() {
427 Intent intent = new Intent(IBluetoothHeadsetClient.class.getName());
428 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
429 intent.setComponent(comp);
430 if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700431 mContext.getUser())) {
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700432 Log.e(TAG, "Could not bind to Bluetooth Headset Client Service with " + intent);
433 return false;
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530434 }
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700435 return true;
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530436 }
437
438 /**
439 * Close the connection to the backing service.
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700440 * Other public functions of BluetoothHeadsetClient will return default error
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530441 * results once close() has been called. Multiple invocations of close()
442 * are ok.
443 */
444 /*package*/ void close() {
445 if (VDBG) log("close()");
446
447 IBluetoothManager mgr = mAdapter.getBluetoothManager();
448 if (mgr != null) {
449 try {
450 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
451 } catch (Exception e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700452 Log.e(TAG, "", e);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530453 }
454 }
455
456 synchronized (mConnection) {
457 if (mService != null) {
458 try {
459 mService = null;
460 mContext.unbindService(mConnection);
461 } catch (Exception re) {
Jack Hea355e5e2017-08-22 16:06:54 -0700462 Log.e(TAG, "", re);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530463 }
464 }
465 }
466 mServiceListener = null;
467 }
468
469 /**
470 * Connects to remote device.
471 *
472 * Currently, the system supports only 1 connection. So, in case of the
473 * second connection, this implementation will disconnect already connected
474 * device automatically and will process the new one.
475 *
Jack Hea355e5e2017-08-22 16:06:54 -0700476 * @param device a remote device we want connect to
477 * @return <code>true</code> if command has been issued successfully; <code>false</code>
478 * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent.
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530479 */
Mathew Inwood4dc66d32018-08-01 15:07:20 +0100480 @UnsupportedAppUsage
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530481 public boolean connect(BluetoothDevice device) {
482 if (DBG) log("connect(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700483 final IBluetoothHeadsetClient service = mService;
484 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530485 try {
Jack He16eeac32017-08-17 12:11:18 -0700486 return service.connect(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530487 } catch (RemoteException e) {
488 Log.e(TAG, Log.getStackTraceString(new Throwable()));
489 return false;
490 }
491 }
Jack He16eeac32017-08-17 12:11:18 -0700492 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530493 return false;
494 }
495
496 /**
497 * Disconnects remote device
498 *
Jack Hea355e5e2017-08-22 16:06:54 -0700499 * @param device a remote device we want disconnect
500 * @return <code>true</code> if command has been issued successfully; <code>false</code>
501 * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent.
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530502 */
Mathew Inwood4dc66d32018-08-01 15:07:20 +0100503 @UnsupportedAppUsage
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530504 public boolean disconnect(BluetoothDevice device) {
505 if (DBG) log("disconnect(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700506 final IBluetoothHeadsetClient service = mService;
507 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530508 try {
Jack He16eeac32017-08-17 12:11:18 -0700509 return service.disconnect(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530510 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700511 Log.e(TAG, Log.getStackTraceString(new Throwable()));
512 return false;
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530513 }
514 }
Jack He16eeac32017-08-17 12:11:18 -0700515 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530516 return false;
517 }
518
519 /**
520 * Return the list of connected remote devices
521 *
522 * @return list of connected devices; empty list if nothing is connected.
523 */
524 @Override
525 public List<BluetoothDevice> getConnectedDevices() {
526 if (VDBG) log("getConnectedDevices()");
Jack He16eeac32017-08-17 12:11:18 -0700527 final IBluetoothHeadsetClient service = mService;
528 if (service != null && isEnabled()) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530529 try {
Jack He16eeac32017-08-17 12:11:18 -0700530 return service.getConnectedDevices();
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530531 } catch (RemoteException e) {
532 Log.e(TAG, Log.getStackTraceString(new Throwable()));
533 return new ArrayList<BluetoothDevice>();
534 }
535 }
Jack He16eeac32017-08-17 12:11:18 -0700536 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530537 return new ArrayList<BluetoothDevice>();
538 }
539
540 /**
541 * Returns list of remote devices in a particular state
542 *
Jack Hea355e5e2017-08-22 16:06:54 -0700543 * @param states collection of states
544 * @return list of devices that state matches the states listed in <code>states</code>; empty
545 * list if nothing matches the <code>states</code>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530546 */
547 @Override
548 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
549 if (VDBG) log("getDevicesMatchingStates()");
Jack He16eeac32017-08-17 12:11:18 -0700550 final IBluetoothHeadsetClient service = mService;
551 if (service != null && isEnabled()) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530552 try {
Jack He16eeac32017-08-17 12:11:18 -0700553 return service.getDevicesMatchingConnectionStates(states);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530554 } catch (RemoteException e) {
555 Log.e(TAG, Log.getStackTraceString(new Throwable()));
556 return new ArrayList<BluetoothDevice>();
557 }
558 }
Jack He16eeac32017-08-17 12:11:18 -0700559 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530560 return new ArrayList<BluetoothDevice>();
561 }
562
563 /**
564 * Returns state of the <code>device</code>
565 *
Jack Hea355e5e2017-08-22 16:06:54 -0700566 * @param device a remote device
567 * @return the state of connection of the device
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530568 */
569 @Override
570 public int getConnectionState(BluetoothDevice device) {
571 if (VDBG) log("getConnectionState(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700572 final IBluetoothHeadsetClient service = mService;
573 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530574 try {
Jack He16eeac32017-08-17 12:11:18 -0700575 return service.getConnectionState(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530576 } catch (RemoteException e) {
577 Log.e(TAG, Log.getStackTraceString(new Throwable()));
578 return BluetoothProfile.STATE_DISCONNECTED;
579 }
580 }
Jack He16eeac32017-08-17 12:11:18 -0700581 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530582 return BluetoothProfile.STATE_DISCONNECTED;
583 }
584
585 /**
586 * Set priority of the profile
587 *
588 * The device should already be paired.
589 */
590 public boolean setPriority(BluetoothDevice device, int priority) {
591 if (DBG) log("setPriority(" + device + ", " + priority + ")");
Jack He16eeac32017-08-17 12:11:18 -0700592 final IBluetoothHeadsetClient service = mService;
593 if (service != null && isEnabled() && isValidDevice(device)) {
Jack He2992cd02017-08-22 21:21:23 -0700594 if (priority != BluetoothProfile.PRIORITY_OFF
595 && priority != BluetoothProfile.PRIORITY_ON) {
Jack Hea355e5e2017-08-22 16:06:54 -0700596 return false;
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530597 }
598 try {
Jack He16eeac32017-08-17 12:11:18 -0700599 return service.setPriority(device, priority);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530600 } catch (RemoteException e) {
601 Log.e(TAG, Log.getStackTraceString(new Throwable()));
602 return false;
603 }
604 }
Jack He16eeac32017-08-17 12:11:18 -0700605 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530606 return false;
607 }
608
609 /**
610 * Get the priority of the profile.
611 */
612 public int getPriority(BluetoothDevice device) {
613 if (VDBG) log("getPriority(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700614 final IBluetoothHeadsetClient service = mService;
615 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530616 try {
Jack He16eeac32017-08-17 12:11:18 -0700617 return service.getPriority(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530618 } catch (RemoteException e) {
619 Log.e(TAG, Log.getStackTraceString(new Throwable()));
620 return PRIORITY_OFF;
621 }
622 }
Jack He16eeac32017-08-17 12:11:18 -0700623 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530624 return PRIORITY_OFF;
625 }
626
627 /**
628 * Starts voice recognition.
629 *
Jack Hea355e5e2017-08-22 16:06:54 -0700630 * @param device remote device
631 * @return <code>true</code> if command has been issued successfully; <code>false</code>
632 * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent.
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530633 *
Jack Hea355e5e2017-08-22 16:06:54 -0700634 * <p>Feature required for successful execution is being reported by: {@link
635 * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature
636 * is not supported.</p>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530637 */
638 public boolean startVoiceRecognition(BluetoothDevice device) {
639 if (DBG) log("startVoiceRecognition()");
Jack He16eeac32017-08-17 12:11:18 -0700640 final IBluetoothHeadsetClient service = mService;
641 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530642 try {
Jack He16eeac32017-08-17 12:11:18 -0700643 return service.startVoiceRecognition(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530644 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700645 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530646 }
647 }
Jack He16eeac32017-08-17 12:11:18 -0700648 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530649 return false;
650 }
651
652 /**
653 * Stops voice recognition.
654 *
Jack Hea355e5e2017-08-22 16:06:54 -0700655 * @param device remote device
656 * @return <code>true</code> if command has been issued successfully; <code>false</code>
657 * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent.
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530658 *
Jack Hea355e5e2017-08-22 16:06:54 -0700659 * <p>Feature required for successful execution is being reported by: {@link
660 * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature
661 * is not supported.</p>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530662 */
663 public boolean stopVoiceRecognition(BluetoothDevice device) {
664 if (DBG) log("stopVoiceRecognition()");
Jack He16eeac32017-08-17 12:11:18 -0700665 final IBluetoothHeadsetClient service = mService;
666 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530667 try {
Jack He16eeac32017-08-17 12:11:18 -0700668 return service.stopVoiceRecognition(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530669 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700670 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530671 }
672 }
Jack He16eeac32017-08-17 12:11:18 -0700673 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530674 return false;
675 }
676
677 /**
678 * Returns list of all calls in any state.
679 *
Jack Hea355e5e2017-08-22 16:06:54 -0700680 * @param device remote device
681 * @return list of calls; empty list if none call exists
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530682 */
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700683 public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530684 if (DBG) log("getCurrentCalls()");
Jack He16eeac32017-08-17 12:11:18 -0700685 final IBluetoothHeadsetClient service = mService;
686 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530687 try {
Jack He16eeac32017-08-17 12:11:18 -0700688 return service.getCurrentCalls(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530689 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700690 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530691 }
692 }
Jack He16eeac32017-08-17 12:11:18 -0700693 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530694 return null;
695 }
696
697 /**
698 * Returns list of current values of AG indicators.
699 *
Jack Hea355e5e2017-08-22 16:06:54 -0700700 * @param device remote device
701 * @return bundle of AG indicators; null if device is not in CONNECTED state
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530702 */
703 public Bundle getCurrentAgEvents(BluetoothDevice device) {
704 if (DBG) log("getCurrentCalls()");
Jack He16eeac32017-08-17 12:11:18 -0700705 final IBluetoothHeadsetClient service = mService;
706 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530707 try {
Jack He16eeac32017-08-17 12:11:18 -0700708 return service.getCurrentAgEvents(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530709 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700710 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530711 }
712 }
Jack He16eeac32017-08-17 12:11:18 -0700713 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530714 return null;
715 }
716
717 /**
718 * Accepts a call
719 *
Jack Hea355e5e2017-08-22 16:06:54 -0700720 * @param device remote device
721 * @param flag action policy while accepting a call. Possible values {@link #CALL_ACCEPT_NONE},
722 * {@link #CALL_ACCEPT_HOLD}, {@link #CALL_ACCEPT_TERMINATE}
723 * @return <code>true</code> if command has been issued successfully; <code>false</code>
724 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530725 */
Mathew Inwood4dc66d32018-08-01 15:07:20 +0100726 @UnsupportedAppUsage
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530727 public boolean acceptCall(BluetoothDevice device, int flag) {
728 if (DBG) log("acceptCall()");
Jack He16eeac32017-08-17 12:11:18 -0700729 final IBluetoothHeadsetClient service = mService;
730 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530731 try {
Jack He16eeac32017-08-17 12:11:18 -0700732 return service.acceptCall(device, flag);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530733 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700734 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530735 }
736 }
Jack He16eeac32017-08-17 12:11:18 -0700737 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530738 return false;
739 }
740
741 /**
742 * Holds a call.
743 *
Jack Hea355e5e2017-08-22 16:06:54 -0700744 * @param device remote device
745 * @return <code>true</code> if command has been issued successfully; <code>false</code>
746 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530747 */
748 public boolean holdCall(BluetoothDevice device) {
749 if (DBG) log("holdCall()");
Jack He16eeac32017-08-17 12:11:18 -0700750 final IBluetoothHeadsetClient service = mService;
751 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530752 try {
Jack He16eeac32017-08-17 12:11:18 -0700753 return service.holdCall(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530754 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700755 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530756 }
757 }
Jack He16eeac32017-08-17 12:11:18 -0700758 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530759 return false;
760 }
761
762 /**
763 * Rejects a call.
764 *
Jack Hea355e5e2017-08-22 16:06:54 -0700765 * @param device remote device
766 * @return <code>true</code> if command has been issued successfully; <code>false</code>
767 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530768 *
Jack Hea355e5e2017-08-22 16:06:54 -0700769 * <p>Feature required for successful execution is being reported by: {@link
770 * #EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is not
771 * supported.</p>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530772 */
Mathew Inwood4dc66d32018-08-01 15:07:20 +0100773 @UnsupportedAppUsage
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530774 public boolean rejectCall(BluetoothDevice device) {
775 if (DBG) log("rejectCall()");
Jack He16eeac32017-08-17 12:11:18 -0700776 final IBluetoothHeadsetClient service = mService;
777 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530778 try {
Jack He16eeac32017-08-17 12:11:18 -0700779 return service.rejectCall(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530780 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700781 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530782 }
783 }
Jack He16eeac32017-08-17 12:11:18 -0700784 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530785 return false;
786 }
787
788 /**
789 * Terminates a specified call.
790 *
791 * Works only when Extended Call Control is supported by Audio Gateway.
792 *
Jack Hea355e5e2017-08-22 16:06:54 -0700793 * @param device remote device
Jack He2992cd02017-08-22 21:21:23 -0700794 * @param call Handle of call obtained in {@link #dial(BluetoothDevice, String)} or obtained via
795 * {@link #ACTION_CALL_CHANGED}. {@code call} may be null in which case we will hangup all active
Jack Hea355e5e2017-08-22 16:06:54 -0700796 * calls.
797 * @return <code>true</code> if command has been issued successfully; <code>false</code>
798 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530799 *
Jack Hea355e5e2017-08-22 16:06:54 -0700800 * <p>Feature required for successful execution is being reported by: {@link
801 * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not
802 * supported.</p>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530803 */
Sanket Agarwal40bb6f32016-06-27 20:13:54 -0700804 public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530805 if (DBG) log("terminateCall()");
Jack He16eeac32017-08-17 12:11:18 -0700806 final IBluetoothHeadsetClient service = mService;
807 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530808 try {
Jack He16eeac32017-08-17 12:11:18 -0700809 return service.terminateCall(device, call);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530810 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700811 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530812 }
813 }
Jack He16eeac32017-08-17 12:11:18 -0700814 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530815 return false;
816 }
817
818 /**
819 * Enters private mode with a specified call.
820 *
821 * Works only when Extended Call Control is supported by Audio Gateway.
822 *
Jack Hea355e5e2017-08-22 16:06:54 -0700823 * @param device remote device
824 * @param index index of the call to connect in private mode
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.
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530827 *
Jack Hea355e5e2017-08-22 16:06:54 -0700828 * <p>Feature required for successful execution is being reported by: {@link
829 * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not
830 * supported.</p>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530831 */
832 public boolean enterPrivateMode(BluetoothDevice device, int index) {
833 if (DBG) log("enterPrivateMode()");
Jack He16eeac32017-08-17 12:11:18 -0700834 final IBluetoothHeadsetClient service = mService;
835 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530836 try {
Jack He16eeac32017-08-17 12:11:18 -0700837 return service.enterPrivateMode(device, index);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530838 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700839 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530840 }
841 }
Jack He16eeac32017-08-17 12:11:18 -0700842 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530843 return false;
844 }
845
846 /**
847 * Performs explicit call transfer.
848 *
849 * That means connect other calls and disconnect.
850 *
Jack Hea355e5e2017-08-22 16:06:54 -0700851 * @param device remote device
852 * @return <code>true</code> if command has been issued successfully; <code>false</code>
853 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530854 *
Jack Hea355e5e2017-08-22 16:06:54 -0700855 * <p>Feature required for successful execution is being reported by: {@link
856 * #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when feature
857 * is not supported.</p>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530858 */
859 public boolean explicitCallTransfer(BluetoothDevice device) {
860 if (DBG) log("explicitCallTransfer()");
Jack He16eeac32017-08-17 12:11:18 -0700861 final IBluetoothHeadsetClient service = mService;
862 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530863 try {
Jack He16eeac32017-08-17 12:11:18 -0700864 return service.explicitCallTransfer(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530865 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700866 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530867 }
868 }
Jack He16eeac32017-08-17 12:11:18 -0700869 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530870 return false;
871 }
872
873 /**
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530874 * Places a call with specified number.
875 *
Jack Hea355e5e2017-08-22 16:06:54 -0700876 * @param device remote device
877 * @param number valid phone number
878 * @return <code>{@link BluetoothHeadsetClientCall} call</code> if command has been issued
879 * successfully; <code>{@link null}</code> otherwise; upon completion HFP sends {@link
880 * #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent otherwise;
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530881 */
Sanket Agarwal40bb6f32016-06-27 20:13:54 -0700882 public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530883 if (DBG) log("dial()");
Jack He16eeac32017-08-17 12:11:18 -0700884 final IBluetoothHeadsetClient service = mService;
885 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530886 try {
Jack He16eeac32017-08-17 12:11:18 -0700887 return service.dial(device, number);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530888 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700889 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530890 }
891 }
Jack He16eeac32017-08-17 12:11:18 -0700892 if (service == null) Log.w(TAG, "Proxy not attached to service");
Sanket Agarwal40bb6f32016-06-27 20:13:54 -0700893 return null;
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530894 }
895
896 /**
897 * Sends DTMF code.
898 *
899 * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,#
900 *
Jack Hea355e5e2017-08-22 16:06:54 -0700901 * @param device remote device
902 * @param code ASCII code
903 * @return <code>true</code> if command has been issued successfully; <code>false</code>
904 * otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent;
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530905 */
906 public boolean sendDTMF(BluetoothDevice device, byte code) {
907 if (DBG) log("sendDTMF()");
Jack He16eeac32017-08-17 12:11:18 -0700908 final IBluetoothHeadsetClient service = mService;
909 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530910 try {
Jack He16eeac32017-08-17 12:11:18 -0700911 return service.sendDTMF(device, code);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530912 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700913 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530914 }
915 }
Jack He16eeac32017-08-17 12:11:18 -0700916 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530917 return false;
918 }
919
920 /**
921 * Get a number corresponding to last voice tag recorded on AG.
922 *
Jack Hea355e5e2017-08-22 16:06:54 -0700923 * @param device remote device
924 * @return <code>true</code> if command has been issued successfully; <code>false</code>
925 * otherwise; upon completion HFP sends {@link #ACTION_LAST_VTAG} or {@link #ACTION_RESULT}
926 * intent;
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530927 *
Jack Hea355e5e2017-08-22 16:06:54 -0700928 * <p>Feature required for successful execution is being reported by: {@link
929 * #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. This method invocation will fail silently when
930 * feature is not supported.</p>
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530931 */
932 public boolean getLastVoiceTagNumber(BluetoothDevice device) {
933 if (DBG) log("getLastVoiceTagNumber()");
Jack He16eeac32017-08-17 12:11:18 -0700934 final IBluetoothHeadsetClient service = mService;
935 if (service != null && isEnabled() && isValidDevice(device)) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530936 try {
Jack He16eeac32017-08-17 12:11:18 -0700937 return service.getLastVoiceTagNumber(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530938 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700939 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530940 }
941 }
Jack He16eeac32017-08-17 12:11:18 -0700942 if (service == null) Log.w(TAG, "Proxy not attached to service");
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530943 return false;
944 }
945
946 /**
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530947 * Returns current audio state of Audio Gateway.
948 *
949 * Note: This is an internal function and shouldn't be exposed
950 */
Mathew Inwood4dc66d32018-08-01 15:07:20 +0100951 @UnsupportedAppUsage
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530952 public int getAudioState(BluetoothDevice device) {
953 if (VDBG) log("getAudioState");
Jack He16eeac32017-08-17 12:11:18 -0700954 final IBluetoothHeadsetClient service = mService;
955 if (service != null && isEnabled()) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530956 try {
Jack He16eeac32017-08-17 12:11:18 -0700957 return service.getAudioState(device);
Jack Hea355e5e2017-08-22 16:06:54 -0700958 } catch (RemoteException e) {
959 Log.e(TAG, e.toString());
960 }
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530961 } else {
962 Log.w(TAG, "Proxy not attached to service");
963 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
964 }
Mike Lockwoodcf916d32014-06-12 11:23:40 -0700965 return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
Hemant Gupta7aca90f2013-08-19 19:03:51 +0530966 }
967
968 /**
Bryce Leedc133822015-10-28 22:21:54 -0700969 * Sets whether audio routing is allowed.
970 *
Jack Hea355e5e2017-08-22 16:06:54 -0700971 * @param device remote device
972 * @param allowed if routing is allowed to the device Note: This is an internal function and
973 * shouldn't be exposed
Bryce Leedc133822015-10-28 22:21:54 -0700974 */
Sanket Agarwal039eeb82017-01-20 14:55:15 -0800975 public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
Bryce Leedc133822015-10-28 22:21:54 -0700976 if (VDBG) log("setAudioRouteAllowed");
Jack He16eeac32017-08-17 12:11:18 -0700977 final IBluetoothHeadsetClient service = mService;
978 if (service != null && isEnabled()) {
Bryce Leedc133822015-10-28 22:21:54 -0700979 try {
Jack He16eeac32017-08-17 12:11:18 -0700980 service.setAudioRouteAllowed(device, allowed);
Jack Hea355e5e2017-08-22 16:06:54 -0700981 } catch (RemoteException e) {
982 Log.e(TAG, e.toString());
983 }
Bryce Leedc133822015-10-28 22:21:54 -0700984 } else {
985 Log.w(TAG, "Proxy not attached to service");
986 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
987 }
988 }
989
990 /**
991 * Returns whether audio routing is allowed.
Jack Hea355e5e2017-08-22 16:06:54 -0700992 *
993 * @param device remote device
994 * @return whether the command succeeded Note: This is an internal function and shouldn't be
995 * exposed
Bryce Leedc133822015-10-28 22:21:54 -0700996 */
Sanket Agarwal039eeb82017-01-20 14:55:15 -0800997 public boolean getAudioRouteAllowed(BluetoothDevice device) {
Bryce Leedc133822015-10-28 22:21:54 -0700998 if (VDBG) log("getAudioRouteAllowed");
Jack He16eeac32017-08-17 12:11:18 -0700999 final IBluetoothHeadsetClient service = mService;
1000 if (service != null && isEnabled()) {
Bryce Leedc133822015-10-28 22:21:54 -07001001 try {
Jack He16eeac32017-08-17 12:11:18 -07001002 return service.getAudioRouteAllowed(device);
Jack Hea355e5e2017-08-22 16:06:54 -07001003 } catch (RemoteException e) {
1004 Log.e(TAG, e.toString());
1005 }
Bryce Leedc133822015-10-28 22:21:54 -07001006 } else {
1007 Log.w(TAG, "Proxy not attached to service");
1008 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1009 }
1010 return false;
1011 }
1012
1013 /**
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301014 * Initiates a connection of audio channel.
1015 *
1016 * It setup SCO channel with remote connected Handsfree AG device.
1017 *
Jack Hea355e5e2017-08-22 16:06:54 -07001018 * @param device remote device
1019 * @return <code>true</code> if command has been issued successfully; <code>false</code>
1020 * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301021 */
Sanket Agarwal039eeb82017-01-20 14:55:15 -08001022 public boolean connectAudio(BluetoothDevice device) {
Jack He16eeac32017-08-17 12:11:18 -07001023 final IBluetoothHeadsetClient service = mService;
1024 if (service != null && isEnabled()) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301025 try {
Jack He16eeac32017-08-17 12:11:18 -07001026 return service.connectAudio(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301027 } catch (RemoteException e) {
1028 Log.e(TAG, e.toString());
1029 }
1030 } else {
1031 Log.w(TAG, "Proxy not attached to service");
1032 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1033 }
1034 return false;
1035 }
1036
1037 /**
1038 * Disconnects audio channel.
1039 *
1040 * It tears down the SCO channel from remote AG device.
1041 *
Jack Hea355e5e2017-08-22 16:06:54 -07001042 * @param device remote device
1043 * @return <code>true</code> if command has been issued successfully; <code>false</code>
1044 * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301045 */
Sanket Agarwal039eeb82017-01-20 14:55:15 -08001046 public boolean disconnectAudio(BluetoothDevice device) {
Jack He16eeac32017-08-17 12:11:18 -07001047 final IBluetoothHeadsetClient service = mService;
1048 if (service != null && isEnabled()) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301049 try {
Jack He16eeac32017-08-17 12:11:18 -07001050 return service.disconnectAudio(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301051 } catch (RemoteException e) {
1052 Log.e(TAG, e.toString());
1053 }
1054 } else {
1055 Log.w(TAG, "Proxy not attached to service");
1056 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1057 }
1058 return false;
1059 }
1060
1061 /**
1062 * Get Audio Gateway features
1063 *
Jack Hea355e5e2017-08-22 16:06:54 -07001064 * @param device remote device
1065 * @return bundle of AG features; null if no service or AG not connected
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301066 */
1067 public Bundle getCurrentAgFeatures(BluetoothDevice device) {
Jack He16eeac32017-08-17 12:11:18 -07001068 final IBluetoothHeadsetClient service = mService;
1069 if (service != null && isEnabled()) {
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301070 try {
Jack He16eeac32017-08-17 12:11:18 -07001071 return service.getCurrentAgFeatures(device);
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301072 } 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 null;
1080 }
1081
1082
Jack He16eeac32017-08-17 12:11:18 -07001083 private final ServiceConnection mConnection = new ServiceConnection() {
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301084 @Override
1085 public void onServiceConnected(ComponentName className, IBinder service) {
1086 if (DBG) Log.d(TAG, "Proxy object connected");
Jeff Sharkey0a17db12016-11-04 11:23:46 -06001087 mService = IBluetoothHeadsetClient.Stub.asInterface(Binder.allowBlocking(service));
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301088
1089 if (mServiceListener != null) {
Mike Lockwoodcf916d32014-06-12 11:23:40 -07001090 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET_CLIENT,
1091 BluetoothHeadsetClient.this);
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301092 }
1093 }
Jack Hea355e5e2017-08-22 16:06:54 -07001094
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301095 @Override
1096 public void onServiceDisconnected(ComponentName className) {
1097 if (DBG) Log.d(TAG, "Proxy object disconnected");
1098 mService = null;
1099 if (mServiceListener != null) {
Mike Lockwoodcf916d32014-06-12 11:23:40 -07001100 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET_CLIENT);
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301101 }
1102 }
1103 };
1104
1105 private boolean isEnabled() {
Jack He16eeac32017-08-17 12:11:18 -07001106 return mAdapter.getState() == BluetoothAdapter.STATE_ON;
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301107 }
1108
Jack He16eeac32017-08-17 12:11:18 -07001109 private static boolean isValidDevice(BluetoothDevice device) {
1110 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Hemant Gupta7aca90f2013-08-19 19:03:51 +05301111 }
1112
1113 private static void log(String msg) {
1114 Log.d(TAG, msg);
1115 }
1116}