blob: f59ae338ee2fa4067cd47ba86e56f6d663aba482 [file] [log] [blame]
The Android Open Source Project33897762009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008 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
Jack Hec46a01e2018-05-02 19:10:56 -070019import android.Manifest;
Rahul Sabnise8bac9b2019-11-27 18:09:33 -080020import android.annotation.NonNull;
Jack He889d2342018-01-03 12:13:26 -080021import android.annotation.Nullable;
Selim Guruna117cb372017-10-17 17:01:38 -070022import android.annotation.RequiresPermission;
Nick Pellydac4c0d2009-09-10 10:21:56 -070023import android.annotation.SdkConstant;
24import android.annotation.SdkConstant.SdkConstantType;
Selim Guruna117cb372017-10-17 17:01:38 -070025import android.annotation.SystemApi;
Artur Satayev3625be42019-12-10 17:47:52 +000026import android.compat.annotation.UnsupportedAppUsage;
The Android Open Source Project33897762009-03-03 19:31:44 -080027import android.content.ComponentName;
28import android.content.Context;
Jeff Sharkey73458a82016-11-04 11:23:46 -060029import android.os.Binder;
Mathew Inwood049f0f52020-11-04 09:29:36 +000030import android.os.Build;
Benjamin Franz3362fef2014-11-12 15:57:54 +000031import android.os.Handler;
The Android Open Source Project33897762009-03-03 19:31:44 -080032import android.os.IBinder;
Benjamin Franz3362fef2014-11-12 15:57:54 +000033import android.os.Looper;
34import android.os.Message;
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -070035import android.os.RemoteException;
The Android Open Source Project33897762009-03-03 19:31:44 -080036import android.util.Log;
37
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -070038import java.util.ArrayList;
39import java.util.List;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070040
The Android Open Source Project33897762009-03-03 19:31:44 -080041/**
The Android Open Source Project33897762009-03-03 19:31:44 -080042 * Public API for controlling the Bluetooth Headset Service. This includes both
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070043 * Bluetooth Headset and Handsfree (v1.5) profiles.
The Android Open Source Project33897762009-03-03 19:31:44 -080044 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070045 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
The Android Open Source Project33897762009-03-03 19:31:44 -080046 * Service via IPC.
47 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070048 * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
49 * the BluetoothHeadset proxy object. Use
50 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
The Android Open Source Project33897762009-03-03 19:31:44 -080051 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070052 * <p> Android only supports one connected Bluetooth Headset at a time.
53 * Each method is protected with its appropriate permission.
The Android Open Source Project33897762009-03-03 19:31:44 -080054 */
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070055public final class BluetoothHeadset implements BluetoothProfile {
The Android Open Source Project33897762009-03-03 19:31:44 -080056 private static final String TAG = "BluetoothHeadset";
fredc3c719642012-04-12 00:02:00 -070057 private static final boolean DBG = true;
Matthew Xief8035a72012-10-09 22:10:37 -070058 private static final boolean VDBG = false;
The Android Open Source Project33897762009-03-03 19:31:44 -080059
Nick Pellydac4c0d2009-09-10 10:21:56 -070060 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070061 * Intent used to broadcast the change in connection state of the Headset
62 * profile.
63 *
64 * <p>This intent will have 3 extras:
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080065 * <ul>
Jack He910201b2017-08-22 16:06:54 -070066 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
67 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
68 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080069 * </ul>
Jaikumar Ganeshaa0427e2011-01-26 11:46:56 -080070 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070071 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
72 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
73 *
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080074 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
75 * receive.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070076 */
77 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
78 public static final String ACTION_CONNECTION_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -070079 "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070080
81 /**
82 * Intent used to broadcast the change in the Audio Connection state of the
83 * A2DP profile.
84 *
85 * <p>This intent will have 3 extras:
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080086 * <ul>
Jack He910201b2017-08-22 16:06:54 -070087 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
88 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
89 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080090 * </ul>
Jaikumar Ganeshaa0427e2011-01-26 11:46:56 -080091 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070092 * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
93 *
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080094 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
95 * to receive.
Nick Pellydac4c0d2009-09-10 10:21:56 -070096 */
97 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
98 public static final String ACTION_AUDIO_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -070099 "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
Nick Pellydac4c0d2009-09-10 10:21:56 -0700100
Jack He889d2342018-01-03 12:13:26 -0800101 /**
102 * Intent used to broadcast the selection of a connected device as active.
103 *
104 * <p>This intent will have one extra:
105 * <ul>
106 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
107 * be null if no device is active. </li>
108 * </ul>
109 *
110 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
111 * receive.
112 *
113 * @hide
114 */
115 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Mathew Inwood049f0f52020-11-04 09:29:36 +0000116 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Jack He889d2342018-01-03 12:13:26 -0800117 public static final String ACTION_ACTIVE_DEVICE_CHANGED =
118 "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
Jaikumar Ganeshf48cda52010-04-02 14:44:43 -0700119
Nick Pellydac4c0d2009-09-10 10:21:56 -0700120 /**
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700121 * Intent used to broadcast that the headset has posted a
122 * vendor-specific event.
123 *
124 * <p>This intent will have 4 extras and 1 category.
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800125 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700126 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
127 * </li>
128 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
129 * specific command </li>
130 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
131 * command type which can be one of {@link #AT_CMD_TYPE_READ},
132 * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
133 * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
134 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
135 * arguments. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800136 * </ul>
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700137 *
Jack He910201b2017-08-22 16:06:54 -0700138 * <p> The category is the Company ID of the vendor defining the
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700139 * vendor-specific command. {@link BluetoothAssignedNumbers}
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700140 *
141 * For example, for Plantronics specific events
142 * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
143 *
144 * <p> For example, an AT+XEVENT=foo,3 will get translated into
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800145 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700146 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
147 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
148 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800149 * </ul>
150 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
151 * to receive.
Herb Jellinekaad41c52010-08-10 13:17:43 -0700152 */
153 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
154 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
155 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
156
157 /**
158 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
159 * intents that contains the name of the vendor-specific command.
160 */
161 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
162 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
163
164 /**
165 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700166 * intents that contains the AT command type of the vendor-specific command.
Herb Jellinekaad41c52010-08-10 13:17:43 -0700167 */
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700168 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
169 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
170
171 /**
172 * AT command type READ used with
173 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
174 * For example, AT+VGM?. There are no arguments for this command type.
175 */
176 public static final int AT_CMD_TYPE_READ = 0;
177
178 /**
179 * AT command type TEST used with
180 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
181 * For example, AT+VGM=?. There are no arguments for this command type.
182 */
183 public static final int AT_CMD_TYPE_TEST = 1;
184
185 /**
186 * AT command type SET used with
187 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
188 * For example, AT+VGM=<args>.
189 */
190 public static final int AT_CMD_TYPE_SET = 2;
191
192 /**
193 * AT command type BASIC used with
194 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
195 * For example, ATD. Single character commands and everything following the
196 * character are arguments.
197 */
198 public static final int AT_CMD_TYPE_BASIC = 3;
199
200 /**
201 * AT command type ACTION used with
202 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
203 * For example, AT+CHUP. There are no arguments for action commands.
204 */
205 public static final int AT_CMD_TYPE_ACTION = 4;
Herb Jellinekaad41c52010-08-10 13:17:43 -0700206
207 /**
208 * A Parcelable String array extra field in
209 * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
210 * the arguments to the vendor-specific command.
211 */
212 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
213 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
214
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700215 /**
216 * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
217 * for the companyId
218 */
Jack He910201b2017-08-22 16:06:54 -0700219 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY =
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700220 "android.bluetooth.headset.intent.category.companyid";
221
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700222 /**
Edward Jee7c81f1f2013-08-16 04:07:49 -0700223 * A vendor-specific command for unsolicited result code.
224 */
225 public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
226
227 /**
Jack He7f9c70b2017-06-20 17:07:40 -0700228 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700229 *
Jack He7f9c70b2017-06-20 17:07:40 -0700230 * @hide
231 */
Jack He0dfd69b2017-06-20 17:09:47 -0700232 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL";
233
234 /**
235 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700236 *
Jack He0dfd69b2017-06-20 17:09:47 -0700237 * @hide
238 */
239 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV";
240
241 /**
242 * Battery level indicator associated with
243 * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV}
Jack He910201b2017-08-22 16:06:54 -0700244 *
Jack He0dfd69b2017-06-20 17:09:47 -0700245 * @hide
246 */
247 public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1;
248
249 /**
250 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700251 *
Jack He0dfd69b2017-06-20 17:09:47 -0700252 * @hide
253 */
Jack He7f9c70b2017-06-20 17:07:40 -0700254 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT";
255
256 /**
257 * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT}
Jack He910201b2017-08-22 16:06:54 -0700258 *
Jack He7f9c70b2017-06-20 17:07:40 -0700259 * @hide
260 */
261 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY";
262
263 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800264 * Headset state when SCO audio is not connected.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700265 * This state can be one of
266 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
267 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
268 */
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800269 public static final int STATE_AUDIO_DISCONNECTED = 10;
Herb Jellinekaad41c52010-08-10 13:17:43 -0700270
271 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800272 * Headset state when SCO audio is connecting.
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700273 * This state can be one of
274 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
275 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700276 */
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800277 public static final int STATE_AUDIO_CONNECTING = 11;
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700278
279 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800280 * Headset state when SCO audio is connected.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700281 * This state can be one of
282 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
283 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Nick Pellydac4c0d2009-09-10 10:21:56 -0700284 */
Chienyuan0d3bada2019-05-29 10:29:30 +0800285 public static final int STATE_AUDIO_CONNECTED = 12;
Mudumba Ananth3246de62016-02-29 02:14:36 -0800286
287 /**
288 * Intent used to broadcast the headset's indicator status
289 *
290 * <p>This intent will have 3 extras:
291 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700292 * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which
293 * is supported by the headset ( as indicated by AT+BIND command in the SLC
294 * sequence) or whose value is changed (indicated by AT+BIEV command) </li>
295 * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li>
296 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li>
Mudumba Ananth3246de62016-02-29 02:14:36 -0800297 * </ul>
Jack He48f6a8a2017-06-22 12:56:54 -0700298 * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators
Jack He910201b2017-08-22 16:06:54 -0700299 * are given an assigned number. Below shows the assigned number of Indicator added so far
Jack He48f6a8a2017-06-22 12:56:54 -0700300 * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled
301 * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery
302 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
Jack He910201b2017-08-22 16:06:54 -0700303 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800304 * @hide
305 */
306 public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
307 "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
308
309 /**
Jack He48f6a8a2017-06-22 12:56:54 -0700310 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
311 * intents that contains the assigned number of the headset indicator as defined by
312 * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7
Jack He910201b2017-08-22 16:06:54 -0700313 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800314 * @hide
315 */
316 public static final String EXTRA_HF_INDICATORS_IND_ID =
317 "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
318
319 /**
Jack He48f6a8a2017-06-22 12:56:54 -0700320 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
Mudumba Ananth3246de62016-02-29 02:14:36 -0800321 * intents that contains the value of the Headset indicator that is being sent.
Jack He910201b2017-08-22 16:06:54 -0700322 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800323 * @hide
324 */
325 public static final String EXTRA_HF_INDICATORS_IND_VALUE =
326 "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
327
Benjamin Franz3362fef2014-11-12 15:57:54 +0000328 private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
329 private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700330
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700331 private Context mContext;
332 private ServiceListener mServiceListener;
Jack He1f686f62017-08-17 12:11:18 -0700333 private volatile IBluetoothHeadset mService;
Matthew Xie5e9fb032012-03-21 23:15:06 -0700334 private BluetoothAdapter mAdapter;
The Android Open Source Project33897762009-03-03 19:31:44 -0800335
Jack He9e045d22017-08-22 21:21:23 -0700336 private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
fredc3c719642012-04-12 00:02:00 -0700337 new IBluetoothStateChangeCallback.Stub() {
338 public void onBluetoothStateChange(boolean up) {
339 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
340 if (!up) {
Benjamin Franz3362fef2014-11-12 15:57:54 +0000341 doUnbind();
fredc3c719642012-04-12 00:02:00 -0700342 } else {
Ugo Yu1652b942019-03-26 21:38:08 +0800343 doBind();
fredc3c719642012-04-12 00:02:00 -0700344 }
345 }
Jack He910201b2017-08-22 16:06:54 -0700346 };
fredc3c719642012-04-12 00:02:00 -0700347
The Android Open Source Project33897762009-03-03 19:31:44 -0800348 /**
349 * Create a BluetoothHeadset proxy object.
350 */
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700351 /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800352 mContext = context;
353 mServiceListener = l;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700354 mAdapter = BluetoothAdapter.getDefaultAdapter();
fredc3c719642012-04-12 00:02:00 -0700355
356 IBluetoothManager mgr = mAdapter.getBluetoothManager();
357 if (mgr != null) {
358 try {
359 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
360 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700361 Log.e(TAG, "", e);
fredc3c719642012-04-12 00:02:00 -0700362 }
363 }
364
Dianne Hackborn3875ec62013-08-04 16:50:16 -0700365 doBind();
366 }
367
Ugo Yu1652b942019-03-26 21:38:08 +0800368 private boolean doBind() {
369 synchronized (mConnection) {
370 if (mService == null) {
371 if (VDBG) Log.d(TAG, "Binding service...");
372 try {
373 return mAdapter.getBluetoothManager().bindBluetoothProfileService(
374 BluetoothProfile.HEADSET, mConnection);
375 } catch (RemoteException e) {
376 Log.e(TAG, "Unable to bind HeadsetService", e);
377 }
378 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800379 }
Benjamin Franz3362fef2014-11-12 15:57:54 +0000380 return false;
381 }
382
Ugo Yu1652b942019-03-26 21:38:08 +0800383 private void doUnbind() {
Benjamin Franz3362fef2014-11-12 15:57:54 +0000384 synchronized (mConnection) {
385 if (mService != null) {
Ugo Yu1652b942019-03-26 21:38:08 +0800386 if (VDBG) Log.d(TAG, "Unbinding service...");
Benjamin Franz3362fef2014-11-12 15:57:54 +0000387 try {
388 mAdapter.getBluetoothManager().unbindBluetoothProfileService(
389 BluetoothProfile.HEADSET, mConnection);
390 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700391 Log.e(TAG, "Unable to unbind HeadsetService", e);
Ugo Yu1652b942019-03-26 21:38:08 +0800392 } finally {
393 mService = null;
Benjamin Franz3362fef2014-11-12 15:57:54 +0000394 }
395 }
396 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800397 }
398
The Android Open Source Project33897762009-03-03 19:31:44 -0800399 /**
400 * Close the connection to the backing service.
401 * Other public functions of BluetoothHeadset will return default error
402 * results once close() has been called. Multiple invocations of close()
403 * are ok.
404 */
Mathew Inwood7d543892018-08-01 15:07:20 +0100405 @UnsupportedAppUsage
Matthew Xie78912492012-03-22 17:18:37 -0700406 /*package*/ void close() {
Matthew Xief8035a72012-10-09 22:10:37 -0700407 if (VDBG) log("close()");
fredc3c719642012-04-12 00:02:00 -0700408
409 IBluetoothManager mgr = mAdapter.getBluetoothManager();
410 if (mgr != null) {
411 try {
412 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
Ugo Yu1652b942019-03-26 21:38:08 +0800413 } catch (RemoteException re) {
414 Log.e(TAG, "", re);
fredc3c719642012-04-12 00:02:00 -0700415 }
416 }
Benjamin Franz733656c2014-12-16 15:33:03 +0000417 mServiceListener = null;
Benjamin Franz3362fef2014-11-12 15:57:54 +0000418 doUnbind();
The Android Open Source Project33897762009-03-03 19:31:44 -0800419 }
420
421 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700422 * Initiate connection to a profile of the remote bluetooth device.
423 *
424 * <p> Currently, the system supports only 1 connection to the
425 * headset/handsfree profile. The API will automatically disconnect connected
426 * devices before connecting.
427 *
428 * <p> This API returns false in scenarios like the profile on the
429 * device is already connected or Bluetooth is not turned on.
430 * When this API returns true, it is guaranteed that
431 * connection state intent for the profile will be broadcasted with
432 * the state. Users can get the connection state of the profile
433 * from this intent.
434 *
435 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
436 * permission.
437 *
438 * @param device Remote Bluetooth Device
Jack He910201b2017-08-22 16:06:54 -0700439 * @return false on immediate error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700440 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800441 */
Selim Guruna117cb372017-10-17 17:01:38 -0700442 @SystemApi
443 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700444 public boolean connect(BluetoothDevice device) {
445 if (DBG) log("connect(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700446 final IBluetoothHeadset service = mService;
447 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800448 try {
Jack He1f686f62017-08-17 12:11:18 -0700449 return service.connect(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700450 } catch (RemoteException e) {
451 Log.e(TAG, Log.getStackTraceString(new Throwable()));
452 return false;
453 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800454 }
Jack He1f686f62017-08-17 12:11:18 -0700455 if (service == null) Log.w(TAG, "Proxy not attached to service");
The Android Open Source Project33897762009-03-03 19:31:44 -0800456 return false;
457 }
458
459 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700460 * Initiate disconnection from a profile
461 *
462 * <p> This API will return false in scenarios like the profile on the
463 * Bluetooth device is not in connected state etc. When this API returns,
464 * true, it is guaranteed that the connection state change
465 * intent will be broadcasted with the state. Users can get the
466 * disconnection state of the profile from this intent.
467 *
468 * <p> If the disconnection is initiated by a remote device, the state
469 * will transition from {@link #STATE_CONNECTED} to
470 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
471 * host (local) device the state will transition from
472 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
473 * state {@link #STATE_DISCONNECTED}. The transition to
474 * {@link #STATE_DISCONNECTING} can be used to distinguish between the
475 * two scenarios.
476 *
477 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
478 * permission.
479 *
480 * @param device Remote Bluetooth Device
Jack He910201b2017-08-22 16:06:54 -0700481 * @return false on immediate error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700482 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800483 */
Selim Guruna117cb372017-10-17 17:01:38 -0700484 @SystemApi
485 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700486 public boolean disconnect(BluetoothDevice device) {
487 if (DBG) log("disconnect(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700488 final IBluetoothHeadset service = mService;
489 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800490 try {
Jack He1f686f62017-08-17 12:11:18 -0700491 return service.disconnect(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700492 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700493 Log.e(TAG, Log.getStackTraceString(new Throwable()));
494 return false;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700495 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800496 }
Jack He1f686f62017-08-17 12:11:18 -0700497 if (service == null) Log.w(TAG, "Proxy not attached to service");
The Android Open Source Project33897762009-03-03 19:31:44 -0800498 return false;
499 }
500
501 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700502 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800503 */
Jack He9e045d22017-08-22 21:21:23 -0700504 @Override
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700505 public List<BluetoothDevice> getConnectedDevices() {
Matthew Xief8035a72012-10-09 22:10:37 -0700506 if (VDBG) log("getConnectedDevices()");
Jack He1f686f62017-08-17 12:11:18 -0700507 final IBluetoothHeadset service = mService;
508 if (service != null && isEnabled()) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800509 try {
Jack He1f686f62017-08-17 12:11:18 -0700510 return service.getConnectedDevices();
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700511 } catch (RemoteException e) {
512 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700513 return new ArrayList<BluetoothDevice>();
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700514 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800515 }
Jack He1f686f62017-08-17 12:11:18 -0700516 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700517 return new ArrayList<BluetoothDevice>();
The Android Open Source Project33897762009-03-03 19:31:44 -0800518 }
519
520 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700521 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800522 */
Jack He9e045d22017-08-22 21:21:23 -0700523 @Override
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700524 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Matthew Xief8035a72012-10-09 22:10:37 -0700525 if (VDBG) log("getDevicesMatchingStates()");
Jack He1f686f62017-08-17 12:11:18 -0700526 final IBluetoothHeadset service = mService;
527 if (service != null && isEnabled()) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800528 try {
Jack He1f686f62017-08-17 12:11:18 -0700529 return service.getDevicesMatchingConnectionStates(states);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700530 } catch (RemoteException e) {
531 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700532 return new ArrayList<BluetoothDevice>();
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700533 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800534 }
Jack He1f686f62017-08-17 12:11:18 -0700535 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700536 return new ArrayList<BluetoothDevice>();
The Android Open Source Project33897762009-03-03 19:31:44 -0800537 }
538
539 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700540 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800541 */
Jack He9e045d22017-08-22 21:21:23 -0700542 @Override
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700543 public int getConnectionState(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700544 if (VDBG) log("getConnectionState(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700545 final IBluetoothHeadset service = mService;
546 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800547 try {
Jack He1f686f62017-08-17 12:11:18 -0700548 return service.getConnectionState(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700549 } catch (RemoteException e) {
550 Log.e(TAG, Log.getStackTraceString(new Throwable()));
551 return BluetoothProfile.STATE_DISCONNECTED;
552 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800553 }
Jack He1f686f62017-08-17 12:11:18 -0700554 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700555 return BluetoothProfile.STATE_DISCONNECTED;
The Android Open Source Project33897762009-03-03 19:31:44 -0800556 }
557
558 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700559 * Set priority of the profile
560 *
561 * <p> The device should already be paired.
Selim Gurun9e6b35b2018-01-09 14:35:19 -0800562 * Priority can be one of {@link BluetoothProfile#PRIORITY_ON} or
Rahul Sabnisd9798612019-12-04 14:21:10 -0800563 * {@link BluetoothProfile#PRIORITY_OFF}
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700564 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700565 * @param device Paired bluetooth device
566 * @param priority
567 * @return true if priority is set, false on error
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700568 * @hide
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800569 * @deprecated Replaced with {@link #setConnectionPolicy(BluetoothDevice, int)}
The Android Open Source Project33897762009-03-03 19:31:44 -0800570 */
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800571 @Deprecated
Selim Guruna117cb372017-10-17 17:01:38 -0700572 @SystemApi
573 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
Nick Pelly2d664882009-08-14 18:33:38 -0700574 public boolean setPriority(BluetoothDevice device, int priority) {
575 if (DBG) log("setPriority(" + device + ", " + priority + ")");
Rahul Sabnisc228ce22020-03-18 17:46:33 -0700576 final IBluetoothHeadset service = mService;
577 if (service != null && isEnabled() && isValidDevice(device)) {
578 if (priority != BluetoothProfile.PRIORITY_OFF
579 && priority != BluetoothProfile.PRIORITY_ON) {
580 return false;
581 }
582 try {
583 return service.setPriority(
584 device, BluetoothAdapter.priorityToConnectionPolicy(priority));
585 } catch (RemoteException e) {
586 Log.e(TAG, Log.getStackTraceString(new Throwable()));
587 return false;
588 }
589 }
590 if (service == null) Log.w(TAG, "Proxy not attached to service");
591 return false;
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800592 }
593
594 /**
595 * Set connection policy of the profile
596 *
597 * <p> The device should already be paired.
598 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
599 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
600 *
601 * @param device Paired bluetooth device
602 * @param connectionPolicy is the connection policy to set to for this profile
603 * @return true if connectionPolicy is set, false on error
604 * @hide
605 */
606 @SystemApi
Rahul Sabnisc228ce22020-03-18 17:46:33 -0700607 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800608 public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
609 @ConnectionPolicy int connectionPolicy) {
610 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
Jack He1f686f62017-08-17 12:11:18 -0700611 final IBluetoothHeadset service = mService;
612 if (service != null && isEnabled() && isValidDevice(device)) {
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800613 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
614 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
Jack He910201b2017-08-22 16:06:54 -0700615 return false;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700616 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800617 try {
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800618 return service.setConnectionPolicy(device, connectionPolicy);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700619 } catch (RemoteException e) {
620 Log.e(TAG, Log.getStackTraceString(new Throwable()));
621 return false;
622 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800623 }
Jack He1f686f62017-08-17 12:11:18 -0700624 if (service == null) Log.w(TAG, "Proxy not attached to service");
The Android Open Source Project33897762009-03-03 19:31:44 -0800625 return false;
626 }
627
628 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700629 * Get the priority of the profile.
630 *
631 * <p> The priority can be any of:
632 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
633 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
634 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700635 * @param device Bluetooth device
636 * @return priority of the device
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700637 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800638 */
Mathew Inwood049f0f52020-11-04 09:29:36 +0000639 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800640 @RequiresPermission(Manifest.permission.BLUETOOTH)
Nick Pelly2d664882009-08-14 18:33:38 -0700641 public int getPriority(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700642 if (VDBG) log("getPriority(" + device + ")");
Rahul Sabnisc228ce22020-03-18 17:46:33 -0700643 final IBluetoothHeadset service = mService;
644 if (service != null && isEnabled() && isValidDevice(device)) {
645 try {
646 return BluetoothAdapter.connectionPolicyToPriority(service.getPriority(device));
647 } catch (RemoteException e) {
648 Log.e(TAG, Log.getStackTraceString(new Throwable()));
649 return BluetoothProfile.PRIORITY_OFF;
650 }
651 }
652 if (service == null) Log.w(TAG, "Proxy not attached to service");
653 return BluetoothProfile.PRIORITY_OFF;
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800654 }
655
656 /**
657 * Get the connection policy of the profile.
658 *
659 * <p> The connection policy can be any of:
660 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
661 * {@link #CONNECTION_POLICY_UNKNOWN}
662 *
663 * @param device Bluetooth device
664 * @return connection policy of the device
665 * @hide
666 */
667 @SystemApi
Rahul Sabnisc228ce22020-03-18 17:46:33 -0700668 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800669 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
670 if (VDBG) log("getConnectionPolicy(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700671 final IBluetoothHeadset service = mService;
672 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800673 try {
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800674 return service.getConnectionPolicy(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700675 } catch (RemoteException e) {
676 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800677 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700678 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800679 }
Jack He1f686f62017-08-17 12:11:18 -0700680 if (service == null) Log.w(TAG, "Proxy not attached to service");
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800681 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700682 }
683
684 /**
Rahul Sabnisc611e732020-12-14 10:54:45 -0800685 * Checks whether the headset supports some form of noise reduction
686 *
687 * @param device Bluetooth device
688 * @return true if echo cancellation and/or noise reduction is supported, false otherwise
689 */
690 @RequiresPermission(Manifest.permission.BLUETOOTH)
691 public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) {
692 if (DBG) log("isNoiseReductionSupported()");
693 final IBluetoothHeadset service = mService;
694 if (service != null && isEnabled() && isValidDevice(device)) {
695 try {
696 return service.isNoiseReductionSupported(device);
697 } catch (RemoteException e) {
698 Log.e(TAG, Log.getStackTraceString(new Throwable()));
699 }
700 }
701 if (service == null) Log.w(TAG, "Proxy not attached to service");
702 return false;
703 }
704
705 /**
706 * Checks whether the headset supports voice recognition
707 *
708 * @param device Bluetooth device
709 * @return true if voice recognition is supported, false otherwise
710 */
711 @RequiresPermission(Manifest.permission.BLUETOOTH)
712 public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
713 if (DBG) log("isVoiceRecognitionSupported()");
714 final IBluetoothHeadset service = mService;
715 if (service != null && isEnabled() && isValidDevice(device)) {
716 try {
717 return service.isVoiceRecognitionSupported(device);
718 } catch (RemoteException e) {
719 Log.e(TAG, Log.getStackTraceString(new Throwable()));
720 }
721 }
722 if (service == null) Log.w(TAG, "Proxy not attached to service");
723 return false;
724 }
725
726 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700727 * Start Bluetooth voice recognition. This methods sends the voice
728 * recognition AT command to the headset and establishes the
729 * audio connection.
730 *
731 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800732 * If this function returns true, this intent will be broadcasted with
733 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700734 *
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800735 * <p> {@link #EXTRA_STATE} will transition from
736 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
737 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
738 * in case of failure to establish the audio connection.
739 *
740 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700741 *
742 * @param device Bluetooth headset
Jack Hec46a01e2018-05-02 19:10:56 -0700743 * @return false if there is no headset connected, or the connected headset doesn't support
744 * voice recognition, or voice recognition is already started, or audio channel is occupied,
745 * or on error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700746 */
747 public boolean startVoiceRecognition(BluetoothDevice device) {
748 if (DBG) log("startVoiceRecognition()");
Jack He1f686f62017-08-17 12:11:18 -0700749 final IBluetoothHeadset service = mService;
750 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700751 try {
Jack He1f686f62017-08-17 12:11:18 -0700752 return service.startVoiceRecognition(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700753 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700754 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700755 }
756 }
Jack He1f686f62017-08-17 12:11:18 -0700757 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700758 return false;
759 }
760
761 /**
762 * Stop Bluetooth Voice Recognition mode, and shut down the
763 * Bluetooth audio path.
764 *
Jack Hec46a01e2018-05-02 19:10:56 -0700765 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
766 * If this function returns true, this intent will be broadcasted with
767 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
768 *
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800769 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700770 *
771 * @param device Bluetooth headset
Jack Hec46a01e2018-05-02 19:10:56 -0700772 * @return false if there is no headset connected, or voice recognition has not started,
773 * or voice recognition has ended on this headset, or on error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700774 */
775 public boolean stopVoiceRecognition(BluetoothDevice device) {
776 if (DBG) log("stopVoiceRecognition()");
Jack He1f686f62017-08-17 12:11:18 -0700777 final IBluetoothHeadset service = mService;
778 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700779 try {
Jack He1f686f62017-08-17 12:11:18 -0700780 return service.stopVoiceRecognition(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700781 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700782 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700783 }
784 }
Jack He1f686f62017-08-17 12:11:18 -0700785 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700786 return false;
787 }
788
789 /**
790 * Check if Bluetooth SCO audio is connected.
791 *
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800792 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700793 *
794 * @param device Bluetooth headset
Jack He910201b2017-08-22 16:06:54 -0700795 * @return true if SCO is connected, false otherwise or on error
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700796 */
797 public boolean isAudioConnected(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700798 if (VDBG) log("isAudioConnected()");
Jack He1f686f62017-08-17 12:11:18 -0700799 final IBluetoothHeadset service = mService;
800 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700801 try {
Jack He1f686f62017-08-17 12:11:18 -0700802 return service.isAudioConnected(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700803 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700804 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700805 }
806 }
Jack He1f686f62017-08-17 12:11:18 -0700807 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700808 return false;
The Android Open Source Project33897762009-03-03 19:31:44 -0800809 }
810
811 /**
Eric Laurent2e66fa22010-03-17 14:59:27 -0700812 * Indicates if current platform supports voice dialing over bluetooth SCO.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700813 *
Eric Laurent2e66fa22010-03-17 14:59:27 -0700814 * @return true if voice dialing over bluetooth is supported, false otherwise.
815 * @hide
816 */
817 public static boolean isBluetoothVoiceDialingEnabled(Context context) {
818 return context.getResources().getBoolean(
819 com.android.internal.R.bool.config_bluetooth_sco_off_call);
820 }
821
Jaikumar Ganeshb84bbd92010-06-02 14:36:14 -0700822 /**
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700823 * Get the current audio state of the Headset.
824 * Note: This is an internal function and shouldn't be exposed
825 *
826 * @hide
827 */
Mathew Inwood049f0f52020-11-04 09:29:36 +0000828 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700829 public int getAudioState(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700830 if (VDBG) log("getAudioState");
Jack He1f686f62017-08-17 12:11:18 -0700831 final IBluetoothHeadset service = mService;
832 if (service != null && !isDisabled()) {
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700833 try {
Jack He1f686f62017-08-17 12:11:18 -0700834 return service.getAudioState(device);
Jack He910201b2017-08-22 16:06:54 -0700835 } catch (RemoteException e) {
836 Log.e(TAG, e.toString());
837 }
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700838 } else {
839 Log.w(TAG, "Proxy not attached to service");
840 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
841 }
842 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
843 }
844
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -0700845 /**
Bryce Lee0e154a32015-11-16 08:55:52 -0800846 * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
847 * audio to the HF unless explicitly told to.
848 * This method should be used in cases where the SCO channel is shared between multiple profiles
849 * and must be delegated by a source knowledgeable
850 * Note: This is an internal function and shouldn't be exposed
851 *
852 * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
Bryce Lee0e154a32015-11-16 08:55:52 -0800853 * @hide
854 */
855 public void setAudioRouteAllowed(boolean allowed) {
856 if (VDBG) log("setAudioRouteAllowed");
Jack He1f686f62017-08-17 12:11:18 -0700857 final IBluetoothHeadset service = mService;
858 if (service != null && isEnabled()) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800859 try {
Jack He1f686f62017-08-17 12:11:18 -0700860 service.setAudioRouteAllowed(allowed);
Jack He910201b2017-08-22 16:06:54 -0700861 } catch (RemoteException e) {
862 Log.e(TAG, e.toString());
863 }
Bryce Lee0e154a32015-11-16 08:55:52 -0800864 } else {
865 Log.w(TAG, "Proxy not attached to service");
866 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
867 }
868 }
869
870 /**
871 * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
872 * Note: This is an internal function and shouldn't be exposed
873 *
874 * @hide
875 */
876 public boolean getAudioRouteAllowed() {
877 if (VDBG) log("getAudioRouteAllowed");
Jack He1f686f62017-08-17 12:11:18 -0700878 final IBluetoothHeadset service = mService;
879 if (service != null && isEnabled()) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800880 try {
Jack He1f686f62017-08-17 12:11:18 -0700881 return service.getAudioRouteAllowed();
Jack He910201b2017-08-22 16:06:54 -0700882 } catch (RemoteException e) {
883 Log.e(TAG, e.toString());
884 }
Bryce Lee0e154a32015-11-16 08:55:52 -0800885 } else {
886 Log.w(TAG, "Proxy not attached to service");
887 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
888 }
889 return false;
890 }
891
892 /**
Jack He798d7282017-05-09 17:16:01 -0700893 * Force SCO audio to be opened regardless any other restrictions
894 *
Jack He910201b2017-08-22 16:06:54 -0700895 * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio
896 * False to use SCO audio in normal manner
Jack He798d7282017-05-09 17:16:01 -0700897 * @hide
898 */
899 public void setForceScoAudio(boolean forced) {
900 if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
Jack He1f686f62017-08-17 12:11:18 -0700901 final IBluetoothHeadset service = mService;
902 if (service != null && isEnabled()) {
Jack He798d7282017-05-09 17:16:01 -0700903 try {
Jack He1f686f62017-08-17 12:11:18 -0700904 service.setForceScoAudio(forced);
Jack He798d7282017-05-09 17:16:01 -0700905 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700906 Log.e(TAG, e.toString());
Jack He798d7282017-05-09 17:16:01 -0700907 }
908 } else {
909 Log.w(TAG, "Proxy not attached to service");
910 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
911 }
912 }
913
914 /**
Jack Hec46a01e2018-05-02 19:10:56 -0700915 * Check if at least one headset's SCO audio is connected or connecting
Matthew Xief3ee3512012-02-16 16:57:18 -0800916 *
917 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
918 *
Jack Hec46a01e2018-05-02 19:10:56 -0700919 * @return true if at least one device's SCO audio is connected or connecting, false otherwise
920 * or on error
Matthew Xief3ee3512012-02-16 16:57:18 -0800921 * @hide
922 */
923 public boolean isAudioOn() {
Matthew Xief8035a72012-10-09 22:10:37 -0700924 if (VDBG) log("isAudioOn()");
Jack He1f686f62017-08-17 12:11:18 -0700925 final IBluetoothHeadset service = mService;
926 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -0800927 try {
Jack He1f686f62017-08-17 12:11:18 -0700928 return service.isAudioOn();
Matthew Xief3ee3512012-02-16 16:57:18 -0800929 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700930 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Matthew Xief3ee3512012-02-16 16:57:18 -0800931 }
932 }
Jack He1f686f62017-08-17 12:11:18 -0700933 if (service == null) Log.w(TAG, "Proxy not attached to service");
Matthew Xief3ee3512012-02-16 16:57:18 -0800934 return false;
935
936 }
937
938 /**
Jack Hec46a01e2018-05-02 19:10:56 -0700939 * Initiates a connection of headset audio to the current active device
Matthew Xief3ee3512012-02-16 16:57:18 -0800940 *
Jack Hec46a01e2018-05-02 19:10:56 -0700941 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
942 * If this function returns true, this intent will be broadcasted with
943 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
944 *
945 * <p> {@link #EXTRA_STATE} will transition from
946 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
947 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
948 * in case of failure to establish the audio connection.
949 *
950 * Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true
951 * before calling this method
952 *
953 * @return false if there was some error such as there is no active headset
Matthew Xief3ee3512012-02-16 16:57:18 -0800954 * @hide
955 */
Mathew Inwood7d543892018-08-01 15:07:20 +0100956 @UnsupportedAppUsage
Matthew Xief3ee3512012-02-16 16:57:18 -0800957 public boolean connectAudio() {
Jack He1f686f62017-08-17 12:11:18 -0700958 final IBluetoothHeadset service = mService;
959 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -0800960 try {
Jack He1f686f62017-08-17 12:11:18 -0700961 return service.connectAudio();
Matthew Xief3ee3512012-02-16 16:57:18 -0800962 } catch (RemoteException e) {
963 Log.e(TAG, e.toString());
964 }
965 } else {
966 Log.w(TAG, "Proxy not attached to service");
967 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
968 }
969 return false;
970 }
971
972 /**
Jack Hec46a01e2018-05-02 19:10:56 -0700973 * Initiates a disconnection of HFP SCO audio.
974 * Tear down voice recognition or virtual voice call if any.
Matthew Xief3ee3512012-02-16 16:57:18 -0800975 *
Jack Hec46a01e2018-05-02 19:10:56 -0700976 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
977 * If this function returns true, this intent will be broadcasted with
978 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
979 *
980 * @return false if audio is not connected, or on error, true otherwise
Matthew Xief3ee3512012-02-16 16:57:18 -0800981 * @hide
982 */
Mathew Inwood7d543892018-08-01 15:07:20 +0100983 @UnsupportedAppUsage
Matthew Xief3ee3512012-02-16 16:57:18 -0800984 public boolean disconnectAudio() {
Jack He1f686f62017-08-17 12:11:18 -0700985 final IBluetoothHeadset service = mService;
986 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -0800987 try {
Jack He1f686f62017-08-17 12:11:18 -0700988 return service.disconnectAudio();
Matthew Xief3ee3512012-02-16 16:57:18 -0800989 } catch (RemoteException e) {
990 Log.e(TAG, e.toString());
991 }
992 } else {
993 Log.w(TAG, "Proxy not attached to service");
994 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
995 }
996 return false;
997 }
998
999 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001000 * Initiates a SCO channel connection as a virtual voice call to the current active device
1001 * Active handsfree device will be notified of incoming call and connected call.
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001002 *
Jack Hec46a01e2018-05-02 19:10:56 -07001003 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1004 * If this function returns true, this intent will be broadcasted with
1005 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
1006 *
1007 * <p> {@link #EXTRA_STATE} will transition from
1008 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
1009 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
1010 * in case of failure to establish the audio connection.
1011 *
1012 * @return true if successful, false if one of the following case applies
1013 * - SCO audio is not idle (connecting or connected)
1014 * - virtual call has already started
1015 * - there is no active device
1016 * - a Telecom managed call is going on
1017 * - binder is dead or Bluetooth is disabled or other error
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001018 * @hide
1019 */
Jack Hec46a01e2018-05-02 19:10:56 -07001020 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
Mathew Inwood7d543892018-08-01 15:07:20 +01001021 @UnsupportedAppUsage
Jack Hec46a01e2018-05-02 19:10:56 -07001022 public boolean startScoUsingVirtualVoiceCall() {
Jaikumar Ganesheea6d262011-01-24 13:55:27 -08001023 if (DBG) log("startScoUsingVirtualVoiceCall()");
Jack He1f686f62017-08-17 12:11:18 -07001024 final IBluetoothHeadset service = mService;
Jack Hec46a01e2018-05-02 19:10:56 -07001025 if (service != null && isEnabled()) {
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001026 try {
Jack Hec46a01e2018-05-02 19:10:56 -07001027 return service.startScoUsingVirtualVoiceCall();
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001028 } 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 false;
1036 }
1037
1038 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001039 * Terminates an ongoing SCO connection and the associated virtual call.
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001040 *
Jack Hec46a01e2018-05-02 19:10:56 -07001041 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1042 * If this function returns true, this intent will be broadcasted with
1043 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
1044 *
1045 * @return true if successful, false if one of the following case applies
1046 * - virtual voice call is not started or has ended
1047 * - binder is dead or Bluetooth is disabled or other error
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001048 * @hide
1049 */
Jack Hec46a01e2018-05-02 19:10:56 -07001050 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
Mathew Inwood7d543892018-08-01 15:07:20 +01001051 @UnsupportedAppUsage
Jack Hec46a01e2018-05-02 19:10:56 -07001052 public boolean stopScoUsingVirtualVoiceCall() {
Jaikumar Ganesheea6d262011-01-24 13:55:27 -08001053 if (DBG) log("stopScoUsingVirtualVoiceCall()");
Jack He1f686f62017-08-17 12:11:18 -07001054 final IBluetoothHeadset service = mService;
Jack Hec46a01e2018-05-02 19:10:56 -07001055 if (service != null && isEnabled()) {
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001056 try {
Jack Hec46a01e2018-05-02 19:10:56 -07001057 return service.stopScoUsingVirtualVoiceCall();
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001058 } catch (RemoteException e) {
1059 Log.e(TAG, e.toString());
1060 }
1061 } else {
1062 Log.w(TAG, "Proxy not attached to service");
1063 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1064 }
1065 return false;
1066 }
1067
Matthew Xief3ee3512012-02-16 16:57:18 -08001068 /**
1069 * Notify Headset of phone state change.
1070 * This is a backdoor for phone app to call BluetoothHeadset since
1071 * there is currently not a good way to get precise call state change outside
1072 * of phone app.
1073 *
1074 * @hide
1075 */
Mathew Inwood049f0f52020-11-04 09:29:36 +00001076 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Matthew Xief3ee3512012-02-16 16:57:18 -08001077 public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
Benson Lied8d3392018-07-17 18:19:59 +08001078 int type, String name) {
Jack He1f686f62017-08-17 12:11:18 -07001079 final IBluetoothHeadset service = mService;
1080 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001081 try {
Benson Lied8d3392018-07-17 18:19:59 +08001082 service.phoneStateChanged(numActive, numHeld, callState, number, type, name);
Matthew Xief3ee3512012-02-16 16:57:18 -08001083 } catch (RemoteException e) {
1084 Log.e(TAG, e.toString());
1085 }
1086 } else {
1087 Log.w(TAG, "Proxy not attached to service");
1088 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1089 }
1090 }
1091
1092 /**
Matthew Xief3ee3512012-02-16 16:57:18 -08001093 * Send Headset of CLCC response
1094 *
1095 * @hide
1096 */
1097 public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
Jack He910201b2017-08-22 16:06:54 -07001098 String number, int type) {
Jack He1f686f62017-08-17 12:11:18 -07001099 final IBluetoothHeadset service = mService;
1100 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001101 try {
Jack He1f686f62017-08-17 12:11:18 -07001102 service.clccResponse(index, direction, status, mode, mpty, number, type);
Matthew Xief3ee3512012-02-16 16:57:18 -08001103 } catch (RemoteException e) {
1104 Log.e(TAG, e.toString());
1105 }
1106 } else {
1107 Log.w(TAG, "Proxy not attached to service");
1108 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1109 }
1110 }
1111
Edward Jee7c81f1f2013-08-16 04:07:49 -07001112 /**
1113 * Sends a vendor-specific unsolicited result code to the headset.
1114 *
Jack He910201b2017-08-22 16:06:54 -07001115 * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code
1116 * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the
1117 * string <code>"+ANDROID: 0"</code> will be sent.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001118 *
Ying Wang29d93892013-08-26 17:48:22 -07001119 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001120 *
1121 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1122 *
1123 * @param device Bluetooth headset.
1124 * @param command A vendor-specific command.
1125 * @param arg The argument that will be attached to the command.
1126 * @return {@code false} if there is no headset connected, or if the command is not an allowed
Jack He910201b2017-08-22 16:06:54 -07001127 * vendor-specific unsolicited result code, or on error. {@code true} otherwise.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001128 * @throws IllegalArgumentException if {@code command} is {@code null}.
1129 */
1130 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
1131 String arg) {
1132 if (DBG) {
1133 log("sendVendorSpecificResultCode()");
1134 }
1135 if (command == null) {
1136 throw new IllegalArgumentException("command is null");
1137 }
Jack He1f686f62017-08-17 12:11:18 -07001138 final IBluetoothHeadset service = mService;
1139 if (service != null && isEnabled() && isValidDevice(device)) {
Edward Jee7c81f1f2013-08-16 04:07:49 -07001140 try {
Jack He1f686f62017-08-17 12:11:18 -07001141 return service.sendVendorSpecificResultCode(device, command, arg);
Edward Jee7c81f1f2013-08-16 04:07:49 -07001142 } catch (RemoteException e) {
1143 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1144 }
1145 }
Jack He1f686f62017-08-17 12:11:18 -07001146 if (service == null) {
Edward Jee7c81f1f2013-08-16 04:07:49 -07001147 Log.w(TAG, "Proxy not attached to service");
1148 }
1149 return false;
1150 }
1151
Mudumba Ananth80bf6282014-04-27 13:11:00 -07001152 /**
Jack He889d2342018-01-03 12:13:26 -08001153 * Select a connected device as active.
1154 *
1155 * The active device selection is per profile. An active device's
1156 * purpose is profile-specific. For example, in HFP and HSP profiles,
1157 * it is the device used for phone call audio. If a remote device is not
1158 * connected, it cannot be selected as active.
1159 *
1160 * <p> This API returns false in scenarios like the profile on the
1161 * device is not connected or Bluetooth is not turned on.
1162 * When this API returns true, it is guaranteed that the
1163 * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
1164 * with the active device.
1165 *
1166 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
1167 * permission.
1168 *
1169 * @param device Remote Bluetooth Device, could be null if phone call audio should not be
1170 * streamed to a headset
1171 * @return false on immediate error, true otherwise
1172 * @hide
1173 */
1174 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
Mathew Inwood049f0f52020-11-04 09:29:36 +00001175 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Jack He889d2342018-01-03 12:13:26 -08001176 public boolean setActiveDevice(@Nullable BluetoothDevice device) {
1177 if (DBG) {
1178 Log.d(TAG, "setActiveDevice: " + device);
1179 }
1180 final IBluetoothHeadset service = mService;
1181 if (service != null && isEnabled() && (device == null || isValidDevice(device))) {
1182 try {
1183 return service.setActiveDevice(device);
1184 } catch (RemoteException e) {
1185 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1186 }
1187 }
1188 if (service == null) {
1189 Log.w(TAG, "Proxy not attached to service");
1190 }
1191 return false;
1192 }
1193
1194 /**
1195 * Get the connected device that is active.
1196 *
Jack He889d2342018-01-03 12:13:26 -08001197 * @return the connected device that is active or null if no device
1198 * is active.
1199 * @hide
1200 */
Mathew Inwood049f0f52020-11-04 09:29:36 +00001201 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Rahul Sabnisd9798612019-12-04 14:21:10 -08001202 @Nullable
Rahul Sabnisc228ce22020-03-18 17:46:33 -07001203 @RequiresPermission(Manifest.permission.BLUETOOTH)
Jack He889d2342018-01-03 12:13:26 -08001204 public BluetoothDevice getActiveDevice() {
1205 if (VDBG) {
1206 Log.d(TAG, "getActiveDevice");
1207 }
1208 final IBluetoothHeadset service = mService;
1209 if (service != null && isEnabled()) {
1210 try {
1211 return service.getActiveDevice();
1212 } catch (RemoteException e) {
1213 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1214 }
1215 }
1216 if (service == null) {
1217 Log.w(TAG, "Proxy not attached to service");
1218 }
1219 return null;
1220 }
1221
1222 /**
Jack Hec5fde732018-01-05 17:17:06 -08001223 * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an
1224 * active connection.
Jack Hed8d204d2016-11-17 16:19:43 -08001225 *
Jack Hec5fde732018-01-05 17:17:06 -08001226 * @return true if in-band ringing is enabled, false if in-band ringing is disabled
1227 * @hide
1228 */
1229 @RequiresPermission(android.Manifest.permission.BLUETOOTH)
1230 public boolean isInbandRingingEnabled() {
1231 if (DBG) {
1232 log("isInbandRingingEnabled()");
1233 }
1234 final IBluetoothHeadset service = mService;
1235 if (service != null && isEnabled()) {
1236 try {
1237 return service.isInbandRingingEnabled();
1238 } catch (RemoteException e) {
1239 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1240 }
1241 }
1242 if (service == null) {
1243 Log.w(TAG, "Proxy not attached to service");
1244 }
1245 return false;
1246 }
1247
1248 /**
1249 * Check if in-band ringing is supported for this platform.
1250 *
1251 * @return true if in-band ringing is supported, false if in-band ringing is not supported
Jack Hed8d204d2016-11-17 16:19:43 -08001252 * @hide
1253 */
1254 public static boolean isInbandRingingSupported(Context context) {
1255 return context.getResources().getBoolean(
1256 com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support);
1257 }
1258
Jack He9e045d22017-08-22 21:21:23 -07001259 private final IBluetoothProfileServiceConnection mConnection =
1260 new IBluetoothProfileServiceConnection.Stub() {
Benjamin Franz3362fef2014-11-12 15:57:54 +00001261 @Override
The Android Open Source Project33897762009-03-03 19:31:44 -08001262 public void onServiceConnected(ComponentName className, IBinder service) {
1263 if (DBG) Log.d(TAG, "Proxy object connected");
Jeff Sharkey73458a82016-11-04 11:23:46 -06001264 mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service));
Benjamin Franz3362fef2014-11-12 15:57:54 +00001265 mHandler.sendMessage(mHandler.obtainMessage(
1266 MESSAGE_HEADSET_SERVICE_CONNECTED));
The Android Open Source Project33897762009-03-03 19:31:44 -08001267 }
Jack He910201b2017-08-22 16:06:54 -07001268
Benjamin Franz3362fef2014-11-12 15:57:54 +00001269 @Override
The Android Open Source Project33897762009-03-03 19:31:44 -08001270 public void onServiceDisconnected(ComponentName className) {
1271 if (DBG) Log.d(TAG, "Proxy object disconnected");
Ugo Yu1652b942019-03-26 21:38:08 +08001272 doUnbind();
Benjamin Franz3362fef2014-11-12 15:57:54 +00001273 mHandler.sendMessage(mHandler.obtainMessage(
1274 MESSAGE_HEADSET_SERVICE_DISCONNECTED));
The Android Open Source Project33897762009-03-03 19:31:44 -08001275 }
1276 };
The Android Open Source Project0047a0f2009-03-05 20:00:43 -08001277
Mathew Inwood7d543892018-08-01 15:07:20 +01001278 @UnsupportedAppUsage
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001279 private boolean isEnabled() {
Jack He1f686f62017-08-17 12:11:18 -07001280 return mAdapter.getState() == BluetoothAdapter.STATE_ON;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001281 }
1282
Jaikumar Ganesh2fbe8f42011-04-06 11:09:30 -07001283 private boolean isDisabled() {
Jack He1f686f62017-08-17 12:11:18 -07001284 return mAdapter.getState() == BluetoothAdapter.STATE_OFF;
Jaikumar Ganesh2fbe8f42011-04-06 11:09:30 -07001285 }
1286
Jack He1f686f62017-08-17 12:11:18 -07001287 private static boolean isValidDevice(BluetoothDevice device) {
1288 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001289 }
1290
The Android Open Source Project0047a0f2009-03-05 20:00:43 -08001291 private static void log(String msg) {
1292 Log.d(TAG, msg);
1293 }
Benjamin Franz3362fef2014-11-12 15:57:54 +00001294
1295 private final Handler mHandler = new Handler(Looper.getMainLooper()) {
1296 @Override
1297 public void handleMessage(Message msg) {
1298 switch (msg.what) {
1299 case MESSAGE_HEADSET_SERVICE_CONNECTED: {
1300 if (mServiceListener != null) {
1301 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
1302 BluetoothHeadset.this);
1303 }
1304 break;
1305 }
1306 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
1307 if (mServiceListener != null) {
1308 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
1309 }
Benjamin Franz3362fef2014-11-12 15:57:54 +00001310 break;
1311 }
1312 }
1313 }
1314 };
The Android Open Source Project33897762009-03-03 19:31:44 -08001315}