blob: a1ece7fc825d36fcb8dac9988a74f5460faf79e3 [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;
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -060024import android.annotation.SuppressLint;
Nick Pellydac4c0d2009-09-10 10:21:56 -070025import android.annotation.SdkConstant.SdkConstantType;
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060026import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
27import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
28import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
Selim Guruna117cb372017-10-17 17:01:38 -070029import android.annotation.SystemApi;
Artur Satayev3625be42019-12-10 17:47:52 +000030import android.compat.annotation.UnsupportedAppUsage;
The Android Open Source Project33897762009-03-03 19:31:44 -080031import android.content.ComponentName;
32import android.content.Context;
Jeff Sharkey73458a82016-11-04 11:23:46 -060033import android.os.Binder;
Mathew Inwood049f0f52020-11-04 09:29:36 +000034import android.os.Build;
Benjamin Franz3362fef2014-11-12 15:57:54 +000035import android.os.Handler;
The Android Open Source Project33897762009-03-03 19:31:44 -080036import android.os.IBinder;
Benjamin Franz3362fef2014-11-12 15:57:54 +000037import android.os.Looper;
38import android.os.Message;
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -070039import android.os.RemoteException;
The Android Open Source Project33897762009-03-03 19:31:44 -080040import android.util.Log;
41
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -070042import java.util.ArrayList;
43import java.util.List;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070044
The Android Open Source Project33897762009-03-03 19:31:44 -080045/**
The Android Open Source Project33897762009-03-03 19:31:44 -080046 * Public API for controlling the Bluetooth Headset Service. This includes both
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070047 * Bluetooth Headset and Handsfree (v1.5) profiles.
The Android Open Source Project33897762009-03-03 19:31:44 -080048 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070049 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
The Android Open Source Project33897762009-03-03 19:31:44 -080050 * Service via IPC.
51 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070052 * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
53 * the BluetoothHeadset proxy object. Use
54 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
The Android Open Source Project33897762009-03-03 19:31:44 -080055 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070056 * <p> Android only supports one connected Bluetooth Headset at a time.
57 * Each method is protected with its appropriate permission.
The Android Open Source Project33897762009-03-03 19:31:44 -080058 */
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070059public final class BluetoothHeadset implements BluetoothProfile {
The Android Open Source Project33897762009-03-03 19:31:44 -080060 private static final String TAG = "BluetoothHeadset";
fredc3c719642012-04-12 00:02:00 -070061 private static final boolean DBG = true;
Matthew Xief8035a72012-10-09 22:10:37 -070062 private static final boolean VDBG = false;
The Android Open Source Project33897762009-03-03 19:31:44 -080063
Nick Pellydac4c0d2009-09-10 10:21:56 -070064 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070065 * Intent used to broadcast the change in connection state of the Headset
66 * profile.
67 *
68 * <p>This intent will have 3 extras:
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080069 * <ul>
Jack He910201b2017-08-22 16:06:54 -070070 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
71 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
72 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080073 * </ul>
Jaikumar Ganeshaa0427e2011-01-26 11:46:56 -080074 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070075 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
76 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070077 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060078 @RequiresLegacyBluetoothPermission
79 @RequiresBluetoothConnectPermission
80 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070081 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
82 public static final String ACTION_CONNECTION_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -070083 "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070084
85 /**
86 * Intent used to broadcast the change in the Audio Connection state of the
Jakub Pawlowski9965bf72021-01-29 09:20:44 +010087 * HFP profile.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070088 *
89 * <p>This intent will have 3 extras:
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080090 * <ul>
Jack He910201b2017-08-22 16:06:54 -070091 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
92 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
93 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080094 * </ul>
Jaikumar Ganeshaa0427e2011-01-26 11:46:56 -080095 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070096 * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
Nick Pellydac4c0d2009-09-10 10:21:56 -070097 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060098 @RequiresLegacyBluetoothPermission
99 @RequiresBluetoothConnectPermission
100 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Nick Pellydac4c0d2009-09-10 10:21:56 -0700101 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
102 public static final String ACTION_AUDIO_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -0700103 "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
Nick Pellydac4c0d2009-09-10 10:21:56 -0700104
Jack He889d2342018-01-03 12:13:26 -0800105 /**
106 * Intent used to broadcast the selection of a connected device as active.
107 *
108 * <p>This intent will have one extra:
109 * <ul>
110 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
111 * be null if no device is active. </li>
112 * </ul>
113 *
Jack He889d2342018-01-03 12:13:26 -0800114 * @hide
115 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600116 @RequiresLegacyBluetoothPermission
117 @RequiresBluetoothConnectPermission
118 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He889d2342018-01-03 12:13:26 -0800119 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Mathew Inwoodb426f902021-01-06 12:05:47 +0000120 @UnsupportedAppUsage(trackingBug = 171933273)
Jack He889d2342018-01-03 12:13:26 -0800121 public static final String ACTION_ACTIVE_DEVICE_CHANGED =
122 "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
Jaikumar Ganeshf48cda52010-04-02 14:44:43 -0700123
Nick Pellydac4c0d2009-09-10 10:21:56 -0700124 /**
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700125 * Intent used to broadcast that the headset has posted a
126 * vendor-specific event.
127 *
128 * <p>This intent will have 4 extras and 1 category.
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800129 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700130 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
131 * </li>
132 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
133 * specific command </li>
134 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
135 * command type which can be one of {@link #AT_CMD_TYPE_READ},
136 * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
137 * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
138 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
139 * arguments. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800140 * </ul>
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700141 *
Jack He910201b2017-08-22 16:06:54 -0700142 * <p> The category is the Company ID of the vendor defining the
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700143 * vendor-specific command. {@link BluetoothAssignedNumbers}
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700144 *
145 * For example, for Plantronics specific events
146 * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
147 *
148 * <p> For example, an AT+XEVENT=foo,3 will get translated into
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800149 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700150 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
151 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
152 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800153 * </ul>
Herb Jellinekaad41c52010-08-10 13:17:43 -0700154 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600155 @RequiresLegacyBluetoothPermission
156 @RequiresBluetoothConnectPermission
157 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Herb Jellinekaad41c52010-08-10 13:17:43 -0700158 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
159 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
160 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
161
162 /**
163 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
164 * intents that contains the name of the vendor-specific command.
165 */
166 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
167 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
168
169 /**
170 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700171 * intents that contains the AT command type of the vendor-specific command.
Herb Jellinekaad41c52010-08-10 13:17:43 -0700172 */
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700173 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
174 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
175
176 /**
177 * AT command type READ used with
178 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
179 * For example, AT+VGM?. There are no arguments for this command type.
180 */
181 public static final int AT_CMD_TYPE_READ = 0;
182
183 /**
184 * AT command type TEST used with
185 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
186 * For example, AT+VGM=?. There are no arguments for this command type.
187 */
188 public static final int AT_CMD_TYPE_TEST = 1;
189
190 /**
191 * AT command type SET used with
192 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
193 * For example, AT+VGM=<args>.
194 */
195 public static final int AT_CMD_TYPE_SET = 2;
196
197 /**
198 * AT command type BASIC used with
199 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
200 * For example, ATD. Single character commands and everything following the
201 * character are arguments.
202 */
203 public static final int AT_CMD_TYPE_BASIC = 3;
204
205 /**
206 * AT command type ACTION used with
207 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
208 * For example, AT+CHUP. There are no arguments for action commands.
209 */
210 public static final int AT_CMD_TYPE_ACTION = 4;
Herb Jellinekaad41c52010-08-10 13:17:43 -0700211
212 /**
213 * A Parcelable String array extra field in
214 * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
215 * the arguments to the vendor-specific command.
216 */
217 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
218 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
219
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700220 /**
221 * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
222 * for the companyId
223 */
Jack He910201b2017-08-22 16:06:54 -0700224 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY =
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700225 "android.bluetooth.headset.intent.category.companyid";
226
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700227 /**
Edward Jee7c81f1f2013-08-16 04:07:49 -0700228 * A vendor-specific command for unsolicited result code.
229 */
230 public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
231
232 /**
Jack He7f9c70b2017-06-20 17:07:40 -0700233 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700234 *
Jack He7f9c70b2017-06-20 17:07:40 -0700235 * @hide
236 */
Jack He0dfd69b2017-06-20 17:09:47 -0700237 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL";
238
239 /**
240 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700241 *
Jack He0dfd69b2017-06-20 17:09:47 -0700242 * @hide
243 */
244 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV";
245
246 /**
247 * Battery level indicator associated with
248 * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV}
Jack He910201b2017-08-22 16:06:54 -0700249 *
Jack He0dfd69b2017-06-20 17:09:47 -0700250 * @hide
251 */
252 public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1;
253
254 /**
255 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700256 *
Jack He0dfd69b2017-06-20 17:09:47 -0700257 * @hide
258 */
Jack He7f9c70b2017-06-20 17:07:40 -0700259 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT";
260
261 /**
262 * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT}
Jack He910201b2017-08-22 16:06:54 -0700263 *
Jack He7f9c70b2017-06-20 17:07:40 -0700264 * @hide
265 */
266 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY";
267
268 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800269 * Headset state when SCO audio is not connected.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700270 * This state can be one of
271 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
272 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
273 */
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800274 public static final int STATE_AUDIO_DISCONNECTED = 10;
Herb Jellinekaad41c52010-08-10 13:17:43 -0700275
276 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800277 * Headset state when SCO audio is connecting.
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700278 * This state can be one of
279 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
280 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700281 */
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800282 public static final int STATE_AUDIO_CONNECTING = 11;
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700283
284 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800285 * Headset state when SCO audio is connected.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700286 * This state can be one of
287 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
288 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Nick Pellydac4c0d2009-09-10 10:21:56 -0700289 */
Chienyuan0d3bada2019-05-29 10:29:30 +0800290 public static final int STATE_AUDIO_CONNECTED = 12;
Mudumba Ananth3246de62016-02-29 02:14:36 -0800291
292 /**
293 * Intent used to broadcast the headset's indicator status
294 *
295 * <p>This intent will have 3 extras:
296 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700297 * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which
298 * is supported by the headset ( as indicated by AT+BIND command in the SLC
299 * sequence) or whose value is changed (indicated by AT+BIEV command) </li>
300 * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li>
301 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li>
Mudumba Ananth3246de62016-02-29 02:14:36 -0800302 * </ul>
Jack He48f6a8a2017-06-22 12:56:54 -0700303 * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators
Jack He910201b2017-08-22 16:06:54 -0700304 * are given an assigned number. Below shows the assigned number of Indicator added so far
Jack He48f6a8a2017-06-22 12:56:54 -0700305 * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled
306 * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery
Jack He910201b2017-08-22 16:06:54 -0700307 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800308 * @hide
309 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600310 @RequiresLegacyBluetoothPermission
311 @RequiresBluetoothConnectPermission
312 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Mudumba Ananth3246de62016-02-29 02:14:36 -0800313 public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
314 "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
315
316 /**
Jack He48f6a8a2017-06-22 12:56:54 -0700317 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
318 * intents that contains the assigned number of the headset indicator as defined by
319 * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7
Jack He910201b2017-08-22 16:06:54 -0700320 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800321 * @hide
322 */
323 public static final String EXTRA_HF_INDICATORS_IND_ID =
324 "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
325
326 /**
Jack He48f6a8a2017-06-22 12:56:54 -0700327 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
Mudumba Ananth3246de62016-02-29 02:14:36 -0800328 * intents that contains the value of the Headset indicator that is being sent.
Jack He910201b2017-08-22 16:06:54 -0700329 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800330 * @hide
331 */
332 public static final String EXTRA_HF_INDICATORS_IND_VALUE =
333 "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
334
Benjamin Franz3362fef2014-11-12 15:57:54 +0000335 private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
336 private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700337
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700338 private Context mContext;
339 private ServiceListener mServiceListener;
Jack He1f686f62017-08-17 12:11:18 -0700340 private volatile IBluetoothHeadset mService;
Matthew Xie5e9fb032012-03-21 23:15:06 -0700341 private BluetoothAdapter mAdapter;
The Android Open Source Project33897762009-03-03 19:31:44 -0800342
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600343 @SuppressLint("AndroidFrameworkBluetoothPermission")
Jack He9e045d22017-08-22 21:21:23 -0700344 private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
fredc3c719642012-04-12 00:02:00 -0700345 new IBluetoothStateChangeCallback.Stub() {
346 public void onBluetoothStateChange(boolean up) {
347 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
348 if (!up) {
Benjamin Franz3362fef2014-11-12 15:57:54 +0000349 doUnbind();
fredc3c719642012-04-12 00:02:00 -0700350 } else {
Ugo Yu1652b942019-03-26 21:38:08 +0800351 doBind();
fredc3c719642012-04-12 00:02:00 -0700352 }
353 }
Jack He910201b2017-08-22 16:06:54 -0700354 };
fredc3c719642012-04-12 00:02:00 -0700355
The Android Open Source Project33897762009-03-03 19:31:44 -0800356 /**
357 * Create a BluetoothHeadset proxy object.
358 */
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700359 /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800360 mContext = context;
361 mServiceListener = l;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700362 mAdapter = BluetoothAdapter.getDefaultAdapter();
fredc3c719642012-04-12 00:02:00 -0700363
364 IBluetoothManager mgr = mAdapter.getBluetoothManager();
365 if (mgr != null) {
366 try {
367 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
368 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700369 Log.e(TAG, "", e);
fredc3c719642012-04-12 00:02:00 -0700370 }
371 }
372
Dianne Hackborn3875ec62013-08-04 16:50:16 -0700373 doBind();
374 }
375
Ugo Yu1652b942019-03-26 21:38:08 +0800376 private boolean doBind() {
377 synchronized (mConnection) {
378 if (mService == null) {
379 if (VDBG) Log.d(TAG, "Binding service...");
380 try {
381 return mAdapter.getBluetoothManager().bindBluetoothProfileService(
382 BluetoothProfile.HEADSET, mConnection);
383 } catch (RemoteException e) {
384 Log.e(TAG, "Unable to bind HeadsetService", e);
385 }
386 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800387 }
Benjamin Franz3362fef2014-11-12 15:57:54 +0000388 return false;
389 }
390
Ugo Yu1652b942019-03-26 21:38:08 +0800391 private void doUnbind() {
Benjamin Franz3362fef2014-11-12 15:57:54 +0000392 synchronized (mConnection) {
393 if (mService != null) {
Ugo Yu1652b942019-03-26 21:38:08 +0800394 if (VDBG) Log.d(TAG, "Unbinding service...");
Benjamin Franz3362fef2014-11-12 15:57:54 +0000395 try {
396 mAdapter.getBluetoothManager().unbindBluetoothProfileService(
397 BluetoothProfile.HEADSET, mConnection);
398 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700399 Log.e(TAG, "Unable to unbind HeadsetService", e);
Ugo Yu1652b942019-03-26 21:38:08 +0800400 } finally {
401 mService = null;
Benjamin Franz3362fef2014-11-12 15:57:54 +0000402 }
403 }
404 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800405 }
406
The Android Open Source Project33897762009-03-03 19:31:44 -0800407 /**
408 * Close the connection to the backing service.
409 * Other public functions of BluetoothHeadset will return default error
410 * results once close() has been called. Multiple invocations of close()
411 * are ok.
412 */
Mathew Inwood7d543892018-08-01 15:07:20 +0100413 @UnsupportedAppUsage
Matthew Xie78912492012-03-22 17:18:37 -0700414 /*package*/ void close() {
Matthew Xief8035a72012-10-09 22:10:37 -0700415 if (VDBG) log("close()");
fredc3c719642012-04-12 00:02:00 -0700416
417 IBluetoothManager mgr = mAdapter.getBluetoothManager();
418 if (mgr != null) {
419 try {
420 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
Ugo Yu1652b942019-03-26 21:38:08 +0800421 } catch (RemoteException re) {
422 Log.e(TAG, "", re);
fredc3c719642012-04-12 00:02:00 -0700423 }
424 }
Benjamin Franz733656c2014-12-16 15:33:03 +0000425 mServiceListener = null;
Benjamin Franz3362fef2014-11-12 15:57:54 +0000426 doUnbind();
The Android Open Source Project33897762009-03-03 19:31:44 -0800427 }
428
429 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700430 * Initiate connection to a profile of the remote bluetooth device.
431 *
432 * <p> Currently, the system supports only 1 connection to the
433 * headset/handsfree profile. The API will automatically disconnect connected
434 * devices before connecting.
435 *
436 * <p> This API returns false in scenarios like the profile on the
437 * device is already connected or Bluetooth is not turned on.
438 * When this API returns true, it is guaranteed that
439 * connection state intent for the profile will be broadcasted with
440 * the state. Users can get the connection state of the profile
441 * from this intent.
442 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700443 * @param device Remote Bluetooth Device
Jack He910201b2017-08-22 16:06:54 -0700444 * @return false on immediate error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700445 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800446 */
Selim Guruna117cb372017-10-17 17:01:38 -0700447 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600448 @RequiresLegacyBluetoothAdminPermission
449 @RequiresBluetoothConnectPermission
450 @RequiresPermission(allOf = {
451 android.Manifest.permission.BLUETOOTH_CONNECT,
452 android.Manifest.permission.MODIFY_PHONE_STATE,
453 })
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700454 public boolean connect(BluetoothDevice device) {
455 if (DBG) log("connect(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700456 final IBluetoothHeadset service = mService;
457 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800458 try {
Jack He1f686f62017-08-17 12:11:18 -0700459 return service.connect(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700460 } catch (RemoteException e) {
461 Log.e(TAG, Log.getStackTraceString(new Throwable()));
462 return false;
463 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800464 }
Jack He1f686f62017-08-17 12:11:18 -0700465 if (service == null) Log.w(TAG, "Proxy not attached to service");
The Android Open Source Project33897762009-03-03 19:31:44 -0800466 return false;
467 }
468
469 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700470 * Initiate disconnection from a profile
471 *
472 * <p> This API will return false in scenarios like the profile on the
473 * Bluetooth device is not in connected state etc. When this API returns,
474 * true, it is guaranteed that the connection state change
475 * intent will be broadcasted with the state. Users can get the
476 * disconnection state of the profile from this intent.
477 *
478 * <p> If the disconnection is initiated by a remote device, the state
479 * will transition from {@link #STATE_CONNECTED} to
480 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
481 * host (local) device the state will transition from
482 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
483 * state {@link #STATE_DISCONNECTED}. The transition to
484 * {@link #STATE_DISCONNECTING} can be used to distinguish between the
485 * two scenarios.
486 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700487 * @param device Remote Bluetooth Device
Jack He910201b2017-08-22 16:06:54 -0700488 * @return false on immediate error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700489 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800490 */
Selim Guruna117cb372017-10-17 17:01:38 -0700491 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600492 @RequiresLegacyBluetoothAdminPermission
493 @RequiresBluetoothConnectPermission
494 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700495 public boolean disconnect(BluetoothDevice device) {
496 if (DBG) log("disconnect(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700497 final IBluetoothHeadset service = mService;
498 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800499 try {
Jack He1f686f62017-08-17 12:11:18 -0700500 return service.disconnect(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700501 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700502 Log.e(TAG, Log.getStackTraceString(new Throwable()));
503 return false;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700504 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800505 }
Jack He1f686f62017-08-17 12:11:18 -0700506 if (service == null) Log.w(TAG, "Proxy not attached to service");
The Android Open Source Project33897762009-03-03 19:31:44 -0800507 return false;
508 }
509
510 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700511 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800512 */
Jack He9e045d22017-08-22 21:21:23 -0700513 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600514 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600515 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700516 public List<BluetoothDevice> getConnectedDevices() {
Matthew Xief8035a72012-10-09 22:10:37 -0700517 if (VDBG) log("getConnectedDevices()");
Jack He1f686f62017-08-17 12:11:18 -0700518 final IBluetoothHeadset service = mService;
519 if (service != null && isEnabled()) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800520 try {
Jack He1f686f62017-08-17 12:11:18 -0700521 return service.getConnectedDevices();
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700522 } catch (RemoteException e) {
523 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700524 return new ArrayList<BluetoothDevice>();
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700525 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800526 }
Jack He1f686f62017-08-17 12:11:18 -0700527 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700528 return new ArrayList<BluetoothDevice>();
The Android Open Source Project33897762009-03-03 19:31:44 -0800529 }
530
531 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700532 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800533 */
Jack He9e045d22017-08-22 21:21:23 -0700534 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600535 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600536 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700537 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Matthew Xief8035a72012-10-09 22:10:37 -0700538 if (VDBG) log("getDevicesMatchingStates()");
Jack He1f686f62017-08-17 12:11:18 -0700539 final IBluetoothHeadset service = mService;
540 if (service != null && isEnabled()) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800541 try {
Jack He1f686f62017-08-17 12:11:18 -0700542 return service.getDevicesMatchingConnectionStates(states);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700543 } catch (RemoteException e) {
544 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700545 return new ArrayList<BluetoothDevice>();
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700546 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800547 }
Jack He1f686f62017-08-17 12:11:18 -0700548 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700549 return new ArrayList<BluetoothDevice>();
The Android Open Source Project33897762009-03-03 19:31:44 -0800550 }
551
552 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700553 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800554 */
Jack He9e045d22017-08-22 21:21:23 -0700555 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600556 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600557 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700558 public int getConnectionState(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700559 if (VDBG) log("getConnectionState(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700560 final IBluetoothHeadset service = mService;
561 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800562 try {
Jack He1f686f62017-08-17 12:11:18 -0700563 return service.getConnectionState(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700564 } catch (RemoteException e) {
565 Log.e(TAG, Log.getStackTraceString(new Throwable()));
566 return BluetoothProfile.STATE_DISCONNECTED;
567 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800568 }
Jack He1f686f62017-08-17 12:11:18 -0700569 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700570 return BluetoothProfile.STATE_DISCONNECTED;
The Android Open Source Project33897762009-03-03 19:31:44 -0800571 }
572
573 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700574 * Set priority of the profile
575 *
576 * <p> The device should already be paired.
Selim Gurun9e6b35b2018-01-09 14:35:19 -0800577 * Priority can be one of {@link BluetoothProfile#PRIORITY_ON} or
Rahul Sabnisd9798612019-12-04 14:21:10 -0800578 * {@link BluetoothProfile#PRIORITY_OFF}
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700579 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700580 * @param device Paired bluetooth device
581 * @param priority
582 * @return true if priority is set, false on error
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700583 * @hide
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800584 * @deprecated Replaced with {@link #setConnectionPolicy(BluetoothDevice, int)}
Rahul Sabnis9095a452021-03-24 00:19:00 -0700585 * @removed
The Android Open Source Project33897762009-03-03 19:31:44 -0800586 */
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800587 @Deprecated
Selim Guruna117cb372017-10-17 17:01:38 -0700588 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600589 @RequiresLegacyBluetoothAdminPermission
590 @RequiresBluetoothConnectPermission
591 @RequiresPermission(allOf = {
592 android.Manifest.permission.BLUETOOTH_CONNECT,
593 android.Manifest.permission.MODIFY_PHONE_STATE,
594 })
Nick Pelly2d664882009-08-14 18:33:38 -0700595 public boolean setPriority(BluetoothDevice device, int priority) {
596 if (DBG) log("setPriority(" + device + ", " + priority + ")");
Rahul Sabnisc228ce22020-03-18 17:46:33 -0700597 final IBluetoothHeadset service = mService;
598 if (service != null && isEnabled() && isValidDevice(device)) {
599 if (priority != BluetoothProfile.PRIORITY_OFF
600 && priority != BluetoothProfile.PRIORITY_ON) {
601 return false;
602 }
603 try {
604 return service.setPriority(
605 device, BluetoothAdapter.priorityToConnectionPolicy(priority));
606 } catch (RemoteException e) {
607 Log.e(TAG, Log.getStackTraceString(new Throwable()));
608 return false;
609 }
610 }
611 if (service == null) Log.w(TAG, "Proxy not attached to service");
612 return false;
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800613 }
614
615 /**
616 * Set connection policy of the profile
617 *
618 * <p> The device should already be paired.
619 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
620 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
621 *
622 * @param device Paired bluetooth device
623 * @param connectionPolicy is the connection policy to set to for this profile
624 * @return true if connectionPolicy is set, false on error
625 * @hide
626 */
627 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600628 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600629 @RequiresPermission(allOf = {
630 android.Manifest.permission.BLUETOOTH_CONNECT,
631 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
632 android.Manifest.permission.MODIFY_PHONE_STATE,
633 })
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800634 public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
635 @ConnectionPolicy int connectionPolicy) {
636 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
Jack He1f686f62017-08-17 12:11:18 -0700637 final IBluetoothHeadset service = mService;
638 if (service != null && isEnabled() && isValidDevice(device)) {
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800639 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
640 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
Jack He910201b2017-08-22 16:06:54 -0700641 return false;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700642 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800643 try {
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800644 return service.setConnectionPolicy(device, connectionPolicy);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700645 } catch (RemoteException e) {
646 Log.e(TAG, Log.getStackTraceString(new Throwable()));
647 return false;
648 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800649 }
Jack He1f686f62017-08-17 12:11:18 -0700650 if (service == null) Log.w(TAG, "Proxy not attached to service");
The Android Open Source Project33897762009-03-03 19:31:44 -0800651 return false;
652 }
653
654 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700655 * Get the priority of the profile.
656 *
657 * <p> The priority can be any of:
658 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
659 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
660 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700661 * @param device Bluetooth device
662 * @return priority of the device
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700663 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800664 */
Mathew Inwood049f0f52020-11-04 09:29:36 +0000665 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600666 @RequiresLegacyBluetoothPermission
667 @RequiresBluetoothConnectPermission
668 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Nick Pelly2d664882009-08-14 18:33:38 -0700669 public int getPriority(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700670 if (VDBG) log("getPriority(" + device + ")");
Rahul Sabnisc228ce22020-03-18 17:46:33 -0700671 final IBluetoothHeadset service = mService;
672 if (service != null && isEnabled() && isValidDevice(device)) {
673 try {
674 return BluetoothAdapter.connectionPolicyToPriority(service.getPriority(device));
675 } catch (RemoteException e) {
676 Log.e(TAG, Log.getStackTraceString(new Throwable()));
677 return BluetoothProfile.PRIORITY_OFF;
678 }
679 }
680 if (service == null) Log.w(TAG, "Proxy not attached to service");
681 return BluetoothProfile.PRIORITY_OFF;
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800682 }
683
684 /**
685 * Get the connection policy of the profile.
686 *
687 * <p> The connection policy can be any of:
688 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
689 * {@link #CONNECTION_POLICY_UNKNOWN}
690 *
691 * @param device Bluetooth device
692 * @return connection policy of the device
693 * @hide
694 */
695 @SystemApi
Rahul Sabnisc228ce22020-03-18 17:46:33 -0700696 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800697 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
698 if (VDBG) log("getConnectionPolicy(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700699 final IBluetoothHeadset service = mService;
700 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800701 try {
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800702 return service.getConnectionPolicy(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700703 } catch (RemoteException e) {
704 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800705 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700706 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800707 }
Jack He1f686f62017-08-17 12:11:18 -0700708 if (service == null) Log.w(TAG, "Proxy not attached to service");
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800709 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700710 }
711
712 /**
Rahul Sabnisc611e732020-12-14 10:54:45 -0800713 * Checks whether the headset supports some form of noise reduction
714 *
715 * @param device Bluetooth device
716 * @return true if echo cancellation and/or noise reduction is supported, false otherwise
717 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600718 @RequiresLegacyBluetoothPermission
719 @RequiresBluetoothConnectPermission
720 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Rahul Sabnisc611e732020-12-14 10:54:45 -0800721 public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) {
722 if (DBG) log("isNoiseReductionSupported()");
723 final IBluetoothHeadset service = mService;
724 if (service != null && isEnabled() && isValidDevice(device)) {
725 try {
726 return service.isNoiseReductionSupported(device);
727 } catch (RemoteException e) {
728 Log.e(TAG, Log.getStackTraceString(new Throwable()));
729 }
730 }
731 if (service == null) Log.w(TAG, "Proxy not attached to service");
732 return false;
733 }
734
735 /**
736 * Checks whether the headset supports voice recognition
737 *
738 * @param device Bluetooth device
739 * @return true if voice recognition is supported, false otherwise
740 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600741 @RequiresLegacyBluetoothPermission
742 @RequiresBluetoothConnectPermission
743 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Rahul Sabnisc611e732020-12-14 10:54:45 -0800744 public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
745 if (DBG) log("isVoiceRecognitionSupported()");
746 final IBluetoothHeadset service = mService;
747 if (service != null && isEnabled() && isValidDevice(device)) {
748 try {
749 return service.isVoiceRecognitionSupported(device);
750 } catch (RemoteException e) {
751 Log.e(TAG, Log.getStackTraceString(new Throwable()));
752 }
753 }
754 if (service == null) Log.w(TAG, "Proxy not attached to service");
755 return false;
756 }
757
758 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700759 * Start Bluetooth voice recognition. This methods sends the voice
760 * recognition AT command to the headset and establishes the
761 * audio connection.
762 *
763 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800764 * If this function returns true, this intent will be broadcasted with
765 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700766 *
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800767 * <p> {@link #EXTRA_STATE} will transition from
768 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
769 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
770 * in case of failure to establish the audio connection.
771 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700772 * @param device Bluetooth headset
Jack Hec46a01e2018-05-02 19:10:56 -0700773 * @return false if there is no headset connected, or the connected headset doesn't support
774 * voice recognition, or voice recognition is already started, or audio channel is occupied,
775 * or on error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700776 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600777 @RequiresLegacyBluetoothPermission
778 @RequiresBluetoothConnectPermission
779 @RequiresPermission(allOf = {
780 android.Manifest.permission.BLUETOOTH_CONNECT,
781 android.Manifest.permission.MODIFY_PHONE_STATE,
782 })
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700783 public boolean startVoiceRecognition(BluetoothDevice device) {
784 if (DBG) log("startVoiceRecognition()");
Jack He1f686f62017-08-17 12:11:18 -0700785 final IBluetoothHeadset service = mService;
786 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700787 try {
Jack He1f686f62017-08-17 12:11:18 -0700788 return service.startVoiceRecognition(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700789 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700790 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700791 }
792 }
Jack He1f686f62017-08-17 12:11:18 -0700793 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700794 return false;
795 }
796
797 /**
798 * Stop Bluetooth Voice Recognition mode, and shut down the
799 * Bluetooth audio path.
800 *
Jack Hec46a01e2018-05-02 19:10:56 -0700801 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
802 * If this function returns true, this intent will be broadcasted with
803 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
804 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700805 * @param device Bluetooth headset
Jack Hec46a01e2018-05-02 19:10:56 -0700806 * @return false if there is no headset connected, or voice recognition has not started,
807 * or voice recognition has ended on this headset, or on error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700808 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600809 @RequiresLegacyBluetoothPermission
810 @RequiresBluetoothConnectPermission
811 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700812 public boolean stopVoiceRecognition(BluetoothDevice device) {
813 if (DBG) log("stopVoiceRecognition()");
Jack He1f686f62017-08-17 12:11:18 -0700814 final IBluetoothHeadset service = mService;
815 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700816 try {
Jack He1f686f62017-08-17 12:11:18 -0700817 return service.stopVoiceRecognition(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700818 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700819 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700820 }
821 }
Jack He1f686f62017-08-17 12:11:18 -0700822 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700823 return false;
824 }
825
826 /**
827 * Check if Bluetooth SCO audio is connected.
828 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700829 * @param device Bluetooth headset
Jack He910201b2017-08-22 16:06:54 -0700830 * @return true if SCO is connected, false otherwise or on error
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700831 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600832 @RequiresLegacyBluetoothPermission
833 @RequiresBluetoothConnectPermission
834 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700835 public boolean isAudioConnected(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700836 if (VDBG) log("isAudioConnected()");
Jack He1f686f62017-08-17 12:11:18 -0700837 final IBluetoothHeadset service = mService;
838 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700839 try {
Jack He1f686f62017-08-17 12:11:18 -0700840 return service.isAudioConnected(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700841 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700842 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700843 }
844 }
Jack He1f686f62017-08-17 12:11:18 -0700845 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700846 return false;
The Android Open Source Project33897762009-03-03 19:31:44 -0800847 }
848
849 /**
Eric Laurent2e66fa22010-03-17 14:59:27 -0700850 * Indicates if current platform supports voice dialing over bluetooth SCO.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700851 *
Eric Laurent2e66fa22010-03-17 14:59:27 -0700852 * @return true if voice dialing over bluetooth is supported, false otherwise.
853 * @hide
854 */
855 public static boolean isBluetoothVoiceDialingEnabled(Context context) {
856 return context.getResources().getBoolean(
857 com.android.internal.R.bool.config_bluetooth_sco_off_call);
858 }
859
Jaikumar Ganeshb84bbd92010-06-02 14:36:14 -0700860 /**
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700861 * Get the current audio state of the Headset.
862 * Note: This is an internal function and shouldn't be exposed
863 *
864 * @hide
865 */
Mathew Inwood049f0f52020-11-04 09:29:36 +0000866 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600867 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600868 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700869 public int getAudioState(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700870 if (VDBG) log("getAudioState");
Jack He1f686f62017-08-17 12:11:18 -0700871 final IBluetoothHeadset service = mService;
872 if (service != null && !isDisabled()) {
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700873 try {
Jack He1f686f62017-08-17 12:11:18 -0700874 return service.getAudioState(device);
Jack He910201b2017-08-22 16:06:54 -0700875 } catch (RemoteException e) {
876 Log.e(TAG, e.toString());
877 }
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700878 } else {
879 Log.w(TAG, "Proxy not attached to service");
880 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
881 }
882 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
883 }
884
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -0700885 /**
Bryce Lee0e154a32015-11-16 08:55:52 -0800886 * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
887 * audio to the HF unless explicitly told to.
888 * This method should be used in cases where the SCO channel is shared between multiple profiles
889 * and must be delegated by a source knowledgeable
890 * Note: This is an internal function and shouldn't be exposed
891 *
892 * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
Bryce Lee0e154a32015-11-16 08:55:52 -0800893 * @hide
894 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600895 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600896 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Bryce Lee0e154a32015-11-16 08:55:52 -0800897 public void setAudioRouteAllowed(boolean allowed) {
898 if (VDBG) log("setAudioRouteAllowed");
Jack He1f686f62017-08-17 12:11:18 -0700899 final IBluetoothHeadset service = mService;
900 if (service != null && isEnabled()) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800901 try {
Jack He1f686f62017-08-17 12:11:18 -0700902 service.setAudioRouteAllowed(allowed);
Jack He910201b2017-08-22 16:06:54 -0700903 } catch (RemoteException e) {
904 Log.e(TAG, e.toString());
905 }
Bryce Lee0e154a32015-11-16 08:55:52 -0800906 } else {
907 Log.w(TAG, "Proxy not attached to service");
908 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
909 }
910 }
911
912 /**
913 * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
914 * Note: This is an internal function and shouldn't be exposed
915 *
916 * @hide
917 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600918 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600919 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Bryce Lee0e154a32015-11-16 08:55:52 -0800920 public boolean getAudioRouteAllowed() {
921 if (VDBG) log("getAudioRouteAllowed");
Jack He1f686f62017-08-17 12:11:18 -0700922 final IBluetoothHeadset service = mService;
923 if (service != null && isEnabled()) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800924 try {
Jack He1f686f62017-08-17 12:11:18 -0700925 return service.getAudioRouteAllowed();
Jack He910201b2017-08-22 16:06:54 -0700926 } catch (RemoteException e) {
927 Log.e(TAG, e.toString());
928 }
Bryce Lee0e154a32015-11-16 08:55:52 -0800929 } else {
930 Log.w(TAG, "Proxy not attached to service");
931 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
932 }
933 return false;
934 }
935
936 /**
Jack He798d7282017-05-09 17:16:01 -0700937 * Force SCO audio to be opened regardless any other restrictions
938 *
Jack He910201b2017-08-22 16:06:54 -0700939 * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio
940 * False to use SCO audio in normal manner
Jack He798d7282017-05-09 17:16:01 -0700941 * @hide
942 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600943 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600944 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He798d7282017-05-09 17:16:01 -0700945 public void setForceScoAudio(boolean forced) {
946 if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
Jack He1f686f62017-08-17 12:11:18 -0700947 final IBluetoothHeadset service = mService;
948 if (service != null && isEnabled()) {
Jack He798d7282017-05-09 17:16:01 -0700949 try {
Jack He1f686f62017-08-17 12:11:18 -0700950 service.setForceScoAudio(forced);
Jack He798d7282017-05-09 17:16:01 -0700951 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700952 Log.e(TAG, e.toString());
Jack He798d7282017-05-09 17:16:01 -0700953 }
954 } else {
955 Log.w(TAG, "Proxy not attached to service");
956 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
957 }
958 }
959
960 /**
Jack Hec46a01e2018-05-02 19:10:56 -0700961 * Check if at least one headset's SCO audio is connected or connecting
Matthew Xief3ee3512012-02-16 16:57:18 -0800962 *
Jack Hec46a01e2018-05-02 19:10:56 -0700963 * @return true if at least one device's SCO audio is connected or connecting, false otherwise
964 * or on error
Matthew Xief3ee3512012-02-16 16:57:18 -0800965 * @hide
966 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600967 @RequiresLegacyBluetoothPermission
968 @RequiresBluetoothConnectPermission
969 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Matthew Xief3ee3512012-02-16 16:57:18 -0800970 public boolean isAudioOn() {
Matthew Xief8035a72012-10-09 22:10:37 -0700971 if (VDBG) log("isAudioOn()");
Jack He1f686f62017-08-17 12:11:18 -0700972 final IBluetoothHeadset service = mService;
973 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -0800974 try {
Jack He1f686f62017-08-17 12:11:18 -0700975 return service.isAudioOn();
Matthew Xief3ee3512012-02-16 16:57:18 -0800976 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700977 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Matthew Xief3ee3512012-02-16 16:57:18 -0800978 }
979 }
Jack He1f686f62017-08-17 12:11:18 -0700980 if (service == null) Log.w(TAG, "Proxy not attached to service");
Matthew Xief3ee3512012-02-16 16:57:18 -0800981 return false;
982
983 }
984
985 /**
Jack Hec46a01e2018-05-02 19:10:56 -0700986 * Initiates a connection of headset audio to the current active device
Matthew Xief3ee3512012-02-16 16:57:18 -0800987 *
Jack Hec46a01e2018-05-02 19:10:56 -0700988 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
989 * If this function returns true, this intent will be broadcasted with
990 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
991 *
992 * <p> {@link #EXTRA_STATE} will transition from
993 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
994 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
995 * in case of failure to establish the audio connection.
996 *
997 * Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true
998 * before calling this method
999 *
1000 * @return false if there was some error such as there is no active headset
Matthew Xief3ee3512012-02-16 16:57:18 -08001001 * @hide
1002 */
Mathew Inwood7d543892018-08-01 15:07:20 +01001003 @UnsupportedAppUsage
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001004 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001005 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Matthew Xief3ee3512012-02-16 16:57:18 -08001006 public boolean connectAudio() {
Jack He1f686f62017-08-17 12:11:18 -07001007 final IBluetoothHeadset service = mService;
1008 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001009 try {
Jack He1f686f62017-08-17 12:11:18 -07001010 return service.connectAudio();
Matthew Xief3ee3512012-02-16 16:57:18 -08001011 } catch (RemoteException e) {
1012 Log.e(TAG, e.toString());
1013 }
1014 } else {
1015 Log.w(TAG, "Proxy not attached to service");
1016 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1017 }
1018 return false;
1019 }
1020
1021 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001022 * Initiates a disconnection of HFP SCO audio.
1023 * Tear down voice recognition or virtual voice call if any.
Matthew Xief3ee3512012-02-16 16:57:18 -08001024 *
Jack Hec46a01e2018-05-02 19:10:56 -07001025 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1026 * If this function returns true, this intent will be broadcasted with
1027 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
1028 *
1029 * @return false if audio is not connected, or on error, true otherwise
Matthew Xief3ee3512012-02-16 16:57:18 -08001030 * @hide
1031 */
Mathew Inwood7d543892018-08-01 15:07:20 +01001032 @UnsupportedAppUsage
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001033 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001034 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Matthew Xief3ee3512012-02-16 16:57:18 -08001035 public boolean disconnectAudio() {
Jack He1f686f62017-08-17 12:11:18 -07001036 final IBluetoothHeadset service = mService;
1037 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001038 try {
Jack He1f686f62017-08-17 12:11:18 -07001039 return service.disconnectAudio();
Matthew Xief3ee3512012-02-16 16:57:18 -08001040 } catch (RemoteException e) {
1041 Log.e(TAG, e.toString());
1042 }
1043 } else {
1044 Log.w(TAG, "Proxy not attached to service");
1045 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1046 }
1047 return false;
1048 }
1049
1050 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001051 * Initiates a SCO channel connection as a virtual voice call to the current active device
1052 * Active handsfree device will be notified of incoming call and connected call.
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001053 *
Jack Hec46a01e2018-05-02 19:10:56 -07001054 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1055 * If this function returns true, this intent will be broadcasted with
1056 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
1057 *
1058 * <p> {@link #EXTRA_STATE} will transition from
1059 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
1060 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
1061 * in case of failure to establish the audio connection.
1062 *
1063 * @return true if successful, false if one of the following case applies
1064 * - SCO audio is not idle (connecting or connected)
1065 * - virtual call has already started
1066 * - there is no active device
1067 * - a Telecom managed call is going on
1068 * - binder is dead or Bluetooth is disabled or other error
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001069 * @hide
1070 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001071 @RequiresLegacyBluetoothAdminPermission
1072 @RequiresBluetoothConnectPermission
1073 @RequiresPermission(allOf = {
1074 android.Manifest.permission.BLUETOOTH_CONNECT,
1075 android.Manifest.permission.MODIFY_PHONE_STATE,
1076 })
Mathew Inwood7d543892018-08-01 15:07:20 +01001077 @UnsupportedAppUsage
Jack Hec46a01e2018-05-02 19:10:56 -07001078 public boolean startScoUsingVirtualVoiceCall() {
Jaikumar Ganesheea6d262011-01-24 13:55:27 -08001079 if (DBG) log("startScoUsingVirtualVoiceCall()");
Jack He1f686f62017-08-17 12:11:18 -07001080 final IBluetoothHeadset service = mService;
Jack Hec46a01e2018-05-02 19:10:56 -07001081 if (service != null && isEnabled()) {
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001082 try {
Jack Hec46a01e2018-05-02 19:10:56 -07001083 return service.startScoUsingVirtualVoiceCall();
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001084 } catch (RemoteException e) {
1085 Log.e(TAG, e.toString());
1086 }
1087 } else {
1088 Log.w(TAG, "Proxy not attached to service");
1089 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1090 }
1091 return false;
1092 }
1093
1094 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001095 * Terminates an ongoing SCO connection and the associated virtual call.
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001096 *
Jack Hec46a01e2018-05-02 19:10:56 -07001097 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1098 * If this function returns true, this intent will be broadcasted with
1099 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
1100 *
1101 * @return true if successful, false if one of the following case applies
1102 * - virtual voice call is not started or has ended
1103 * - binder is dead or Bluetooth is disabled or other error
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001104 * @hide
1105 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001106 @RequiresLegacyBluetoothAdminPermission
1107 @RequiresBluetoothConnectPermission
1108 @RequiresPermission(allOf = {
1109 android.Manifest.permission.BLUETOOTH_CONNECT,
1110 android.Manifest.permission.MODIFY_PHONE_STATE,
1111 })
Mathew Inwood7d543892018-08-01 15:07:20 +01001112 @UnsupportedAppUsage
Jack Hec46a01e2018-05-02 19:10:56 -07001113 public boolean stopScoUsingVirtualVoiceCall() {
Jaikumar Ganesheea6d262011-01-24 13:55:27 -08001114 if (DBG) log("stopScoUsingVirtualVoiceCall()");
Jack He1f686f62017-08-17 12:11:18 -07001115 final IBluetoothHeadset service = mService;
Jack Hec46a01e2018-05-02 19:10:56 -07001116 if (service != null && isEnabled()) {
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001117 try {
Jack Hec46a01e2018-05-02 19:10:56 -07001118 return service.stopScoUsingVirtualVoiceCall();
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001119 } catch (RemoteException e) {
1120 Log.e(TAG, e.toString());
1121 }
1122 } else {
1123 Log.w(TAG, "Proxy not attached to service");
1124 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1125 }
1126 return false;
1127 }
1128
Matthew Xief3ee3512012-02-16 16:57:18 -08001129 /**
1130 * Notify Headset of phone state change.
1131 * This is a backdoor for phone app to call BluetoothHeadset since
1132 * there is currently not a good way to get precise call state change outside
1133 * of phone app.
1134 *
1135 * @hide
1136 */
Mathew Inwood049f0f52020-11-04 09:29:36 +00001137 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001138 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001139 @RequiresPermission(allOf = {
1140 android.Manifest.permission.BLUETOOTH_CONNECT,
1141 android.Manifest.permission.MODIFY_PHONE_STATE,
1142 })
Matthew Xief3ee3512012-02-16 16:57:18 -08001143 public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
Benson Lied8d3392018-07-17 18:19:59 +08001144 int type, String name) {
Jack He1f686f62017-08-17 12:11:18 -07001145 final IBluetoothHeadset service = mService;
1146 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001147 try {
Benson Lied8d3392018-07-17 18:19:59 +08001148 service.phoneStateChanged(numActive, numHeld, callState, number, type, name);
Matthew Xief3ee3512012-02-16 16:57:18 -08001149 } catch (RemoteException e) {
1150 Log.e(TAG, e.toString());
1151 }
1152 } else {
1153 Log.w(TAG, "Proxy not attached to service");
1154 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1155 }
1156 }
1157
1158 /**
Matthew Xief3ee3512012-02-16 16:57:18 -08001159 * Send Headset of CLCC response
1160 *
1161 * @hide
1162 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001163 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001164 @RequiresPermission(allOf = {
1165 android.Manifest.permission.BLUETOOTH_CONNECT,
1166 android.Manifest.permission.MODIFY_PHONE_STATE,
1167 })
Matthew Xief3ee3512012-02-16 16:57:18 -08001168 public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
Jack He910201b2017-08-22 16:06:54 -07001169 String number, int type) {
Jack He1f686f62017-08-17 12:11:18 -07001170 final IBluetoothHeadset service = mService;
1171 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001172 try {
Jack He1f686f62017-08-17 12:11:18 -07001173 service.clccResponse(index, direction, status, mode, mpty, number, type);
Matthew Xief3ee3512012-02-16 16:57:18 -08001174 } catch (RemoteException e) {
1175 Log.e(TAG, e.toString());
1176 }
1177 } else {
1178 Log.w(TAG, "Proxy not attached to service");
1179 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1180 }
1181 }
1182
Edward Jee7c81f1f2013-08-16 04:07:49 -07001183 /**
1184 * Sends a vendor-specific unsolicited result code to the headset.
1185 *
Jack He910201b2017-08-22 16:06:54 -07001186 * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code
1187 * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the
1188 * string <code>"+ANDROID: 0"</code> will be sent.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001189 *
Ying Wang29d93892013-08-26 17:48:22 -07001190 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001191 *
Edward Jee7c81f1f2013-08-16 04:07:49 -07001192 * @param device Bluetooth headset.
1193 * @param command A vendor-specific command.
1194 * @param arg The argument that will be attached to the command.
1195 * @return {@code false} if there is no headset connected, or if the command is not an allowed
Jack He910201b2017-08-22 16:06:54 -07001196 * vendor-specific unsolicited result code, or on error. {@code true} otherwise.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001197 * @throws IllegalArgumentException if {@code command} is {@code null}.
1198 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001199 @RequiresLegacyBluetoothPermission
1200 @RequiresBluetoothConnectPermission
1201 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Edward Jee7c81f1f2013-08-16 04:07:49 -07001202 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
1203 String arg) {
1204 if (DBG) {
1205 log("sendVendorSpecificResultCode()");
1206 }
1207 if (command == null) {
1208 throw new IllegalArgumentException("command is null");
1209 }
Jack He1f686f62017-08-17 12:11:18 -07001210 final IBluetoothHeadset service = mService;
1211 if (service != null && isEnabled() && isValidDevice(device)) {
Edward Jee7c81f1f2013-08-16 04:07:49 -07001212 try {
Jack He1f686f62017-08-17 12:11:18 -07001213 return service.sendVendorSpecificResultCode(device, command, arg);
Edward Jee7c81f1f2013-08-16 04:07:49 -07001214 } catch (RemoteException e) {
1215 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1216 }
1217 }
Jack He1f686f62017-08-17 12:11:18 -07001218 if (service == null) {
Edward Jee7c81f1f2013-08-16 04:07:49 -07001219 Log.w(TAG, "Proxy not attached to service");
1220 }
1221 return false;
1222 }
1223
Mudumba Ananth80bf6282014-04-27 13:11:00 -07001224 /**
Jack He889d2342018-01-03 12:13:26 -08001225 * Select a connected device as active.
1226 *
1227 * The active device selection is per profile. An active device's
1228 * purpose is profile-specific. For example, in HFP and HSP profiles,
1229 * it is the device used for phone call audio. If a remote device is not
1230 * connected, it cannot be selected as active.
1231 *
1232 * <p> This API returns false in scenarios like the profile on the
1233 * device is not connected or Bluetooth is not turned on.
1234 * When this API returns true, it is guaranteed that the
1235 * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
1236 * with the active device.
1237 *
Jack He889d2342018-01-03 12:13:26 -08001238 * @param device Remote Bluetooth Device, could be null if phone call audio should not be
1239 * streamed to a headset
1240 * @return false on immediate error, true otherwise
1241 * @hide
1242 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001243 @RequiresLegacyBluetoothAdminPermission
1244 @RequiresBluetoothConnectPermission
1245 @RequiresPermission(allOf = {
1246 android.Manifest.permission.BLUETOOTH_CONNECT,
1247 android.Manifest.permission.MODIFY_PHONE_STATE,
1248 })
Mathew Inwoodb426f902021-01-06 12:05:47 +00001249 @UnsupportedAppUsage(trackingBug = 171933273)
Jack He889d2342018-01-03 12:13:26 -08001250 public boolean setActiveDevice(@Nullable BluetoothDevice device) {
1251 if (DBG) {
1252 Log.d(TAG, "setActiveDevice: " + device);
1253 }
1254 final IBluetoothHeadset service = mService;
1255 if (service != null && isEnabled() && (device == null || isValidDevice(device))) {
1256 try {
1257 return service.setActiveDevice(device);
1258 } catch (RemoteException e) {
1259 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1260 }
1261 }
1262 if (service == null) {
1263 Log.w(TAG, "Proxy not attached to service");
1264 }
1265 return false;
1266 }
1267
1268 /**
1269 * Get the connected device that is active.
1270 *
Jack He889d2342018-01-03 12:13:26 -08001271 * @return the connected device that is active or null if no device
1272 * is active.
1273 * @hide
1274 */
Mathew Inwoodb426f902021-01-06 12:05:47 +00001275 @UnsupportedAppUsage(trackingBug = 171933273)
Rahul Sabnisd9798612019-12-04 14:21:10 -08001276 @Nullable
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001277 @RequiresLegacyBluetoothPermission
1278 @RequiresBluetoothConnectPermission
1279 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He889d2342018-01-03 12:13:26 -08001280 public BluetoothDevice getActiveDevice() {
1281 if (VDBG) {
1282 Log.d(TAG, "getActiveDevice");
1283 }
1284 final IBluetoothHeadset service = mService;
1285 if (service != null && isEnabled()) {
1286 try {
1287 return service.getActiveDevice();
1288 } catch (RemoteException e) {
1289 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1290 }
1291 }
1292 if (service == null) {
1293 Log.w(TAG, "Proxy not attached to service");
1294 }
1295 return null;
1296 }
1297
1298 /**
Jack Hec5fde732018-01-05 17:17:06 -08001299 * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an
1300 * active connection.
Jack Hed8d204d2016-11-17 16:19:43 -08001301 *
Jack Hec5fde732018-01-05 17:17:06 -08001302 * @return true if in-band ringing is enabled, false if in-band ringing is disabled
1303 * @hide
1304 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001305 @RequiresLegacyBluetoothPermission
1306 @RequiresBluetoothConnectPermission
1307 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack Hec5fde732018-01-05 17:17:06 -08001308 public boolean isInbandRingingEnabled() {
1309 if (DBG) {
1310 log("isInbandRingingEnabled()");
1311 }
1312 final IBluetoothHeadset service = mService;
1313 if (service != null && isEnabled()) {
1314 try {
1315 return service.isInbandRingingEnabled();
1316 } catch (RemoteException e) {
1317 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1318 }
1319 }
1320 if (service == null) {
1321 Log.w(TAG, "Proxy not attached to service");
1322 }
1323 return false;
1324 }
1325
1326 /**
1327 * Check if in-band ringing is supported for this platform.
1328 *
1329 * @return true if in-band ringing is supported, false if in-band ringing is not supported
Jack Hed8d204d2016-11-17 16:19:43 -08001330 * @hide
1331 */
1332 public static boolean isInbandRingingSupported(Context context) {
1333 return context.getResources().getBoolean(
1334 com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support);
1335 }
1336
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001337 @SuppressLint("AndroidFrameworkBluetoothPermission")
Jack He9e045d22017-08-22 21:21:23 -07001338 private final IBluetoothProfileServiceConnection mConnection =
1339 new IBluetoothProfileServiceConnection.Stub() {
Benjamin Franz3362fef2014-11-12 15:57:54 +00001340 @Override
The Android Open Source Project33897762009-03-03 19:31:44 -08001341 public void onServiceConnected(ComponentName className, IBinder service) {
1342 if (DBG) Log.d(TAG, "Proxy object connected");
Jeff Sharkey73458a82016-11-04 11:23:46 -06001343 mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service));
Benjamin Franz3362fef2014-11-12 15:57:54 +00001344 mHandler.sendMessage(mHandler.obtainMessage(
1345 MESSAGE_HEADSET_SERVICE_CONNECTED));
The Android Open Source Project33897762009-03-03 19:31:44 -08001346 }
Jack He910201b2017-08-22 16:06:54 -07001347
Benjamin Franz3362fef2014-11-12 15:57:54 +00001348 @Override
The Android Open Source Project33897762009-03-03 19:31:44 -08001349 public void onServiceDisconnected(ComponentName className) {
1350 if (DBG) Log.d(TAG, "Proxy object disconnected");
Ugo Yu1652b942019-03-26 21:38:08 +08001351 doUnbind();
Benjamin Franz3362fef2014-11-12 15:57:54 +00001352 mHandler.sendMessage(mHandler.obtainMessage(
1353 MESSAGE_HEADSET_SERVICE_DISCONNECTED));
The Android Open Source Project33897762009-03-03 19:31:44 -08001354 }
1355 };
The Android Open Source Project0047a0f2009-03-05 20:00:43 -08001356
Mathew Inwood7d543892018-08-01 15:07:20 +01001357 @UnsupportedAppUsage
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001358 private boolean isEnabled() {
Jack He1f686f62017-08-17 12:11:18 -07001359 return mAdapter.getState() == BluetoothAdapter.STATE_ON;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001360 }
1361
Jaikumar Ganesh2fbe8f42011-04-06 11:09:30 -07001362 private boolean isDisabled() {
Jack He1f686f62017-08-17 12:11:18 -07001363 return mAdapter.getState() == BluetoothAdapter.STATE_OFF;
Jaikumar Ganesh2fbe8f42011-04-06 11:09:30 -07001364 }
1365
Jack He1f686f62017-08-17 12:11:18 -07001366 private static boolean isValidDevice(BluetoothDevice device) {
1367 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001368 }
1369
The Android Open Source Project0047a0f2009-03-05 20:00:43 -08001370 private static void log(String msg) {
1371 Log.d(TAG, msg);
1372 }
Benjamin Franz3362fef2014-11-12 15:57:54 +00001373
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001374 @SuppressLint("AndroidFrameworkBluetoothPermission")
Benjamin Franz3362fef2014-11-12 15:57:54 +00001375 private final Handler mHandler = new Handler(Looper.getMainLooper()) {
1376 @Override
1377 public void handleMessage(Message msg) {
1378 switch (msg.what) {
1379 case MESSAGE_HEADSET_SERVICE_CONNECTED: {
1380 if (mServiceListener != null) {
1381 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
1382 BluetoothHeadset.this);
1383 }
1384 break;
1385 }
1386 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
1387 if (mServiceListener != null) {
1388 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
1389 }
Benjamin Franz3362fef2014-11-12 15:57:54 +00001390 break;
1391 }
1392 }
1393 }
1394 };
The Android Open Source Project33897762009-03-03 19:31:44 -08001395}