blob: 3bf517c046f353f4a8a402ccdf5614e2dcd93f8c [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;
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -060031import android.content.AttributionSource;
The Android Open Source Project33897762009-03-03 19:31:44 -080032import android.content.ComponentName;
33import android.content.Context;
Jeff Sharkey19a8d092021-04-28 09:25:36 -060034import android.content.pm.PackageManager;
Jeff Sharkey73458a82016-11-04 11:23:46 -060035import android.os.Binder;
Mathew Inwood049f0f52020-11-04 09:29:36 +000036import android.os.Build;
Benjamin Franz3362fef2014-11-12 15:57:54 +000037import android.os.Handler;
The Android Open Source Project33897762009-03-03 19:31:44 -080038import android.os.IBinder;
Benjamin Franz3362fef2014-11-12 15:57:54 +000039import android.os.Looper;
40import android.os.Message;
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -070041import android.os.RemoteException;
The Android Open Source Project33897762009-03-03 19:31:44 -080042import android.util.Log;
43
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -070044import java.util.ArrayList;
45import java.util.List;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070046
The Android Open Source Project33897762009-03-03 19:31:44 -080047/**
The Android Open Source Project33897762009-03-03 19:31:44 -080048 * Public API for controlling the Bluetooth Headset Service. This includes both
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070049 * Bluetooth Headset and Handsfree (v1.5) profiles.
The Android Open Source Project33897762009-03-03 19:31:44 -080050 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070051 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
The Android Open Source Project33897762009-03-03 19:31:44 -080052 * Service via IPC.
53 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070054 * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
55 * the BluetoothHeadset proxy object. Use
56 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
The Android Open Source Project33897762009-03-03 19:31:44 -080057 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070058 * <p> Android only supports one connected Bluetooth Headset at a time.
59 * Each method is protected with its appropriate permission.
The Android Open Source Project33897762009-03-03 19:31:44 -080060 */
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070061public final class BluetoothHeadset implements BluetoothProfile {
The Android Open Source Project33897762009-03-03 19:31:44 -080062 private static final String TAG = "BluetoothHeadset";
fredc3c719642012-04-12 00:02:00 -070063 private static final boolean DBG = true;
Matthew Xief8035a72012-10-09 22:10:37 -070064 private static final boolean VDBG = false;
The Android Open Source Project33897762009-03-03 19:31:44 -080065
Nick Pellydac4c0d2009-09-10 10:21:56 -070066 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070067 * Intent used to broadcast the change in connection state of the Headset
68 * profile.
69 *
70 * <p>This intent will have 3 extras:
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080071 * <ul>
Jack He910201b2017-08-22 16:06:54 -070072 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
73 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
74 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080075 * </ul>
Jaikumar Ganeshaa0427e2011-01-26 11:46:56 -080076 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070077 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
78 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070079 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060080 @RequiresLegacyBluetoothPermission
81 @RequiresBluetoothConnectPermission
82 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070083 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
84 public static final String ACTION_CONNECTION_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -070085 "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070086
87 /**
88 * Intent used to broadcast the change in the Audio Connection state of the
Jakub Pawlowski9965bf72021-01-29 09:20:44 +010089 * HFP profile.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070090 *
91 * <p>This intent will have 3 extras:
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080092 * <ul>
Jack He910201b2017-08-22 16:06:54 -070093 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
94 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
95 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080096 * </ul>
Jaikumar Ganeshaa0427e2011-01-26 11:46:56 -080097 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070098 * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
Nick Pellydac4c0d2009-09-10 10:21:56 -070099 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600100 @RequiresLegacyBluetoothPermission
101 @RequiresBluetoothConnectPermission
102 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Nick Pellydac4c0d2009-09-10 10:21:56 -0700103 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
104 public static final String ACTION_AUDIO_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -0700105 "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
Nick Pellydac4c0d2009-09-10 10:21:56 -0700106
Jack He889d2342018-01-03 12:13:26 -0800107 /**
108 * Intent used to broadcast the selection of a connected device as active.
109 *
110 * <p>This intent will have one extra:
111 * <ul>
112 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
113 * be null if no device is active. </li>
114 * </ul>
115 *
Jack He889d2342018-01-03 12:13:26 -0800116 * @hide
117 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600118 @RequiresLegacyBluetoothPermission
119 @RequiresBluetoothConnectPermission
120 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He889d2342018-01-03 12:13:26 -0800121 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Mathew Inwoodb426f902021-01-06 12:05:47 +0000122 @UnsupportedAppUsage(trackingBug = 171933273)
Jack He889d2342018-01-03 12:13:26 -0800123 public static final String ACTION_ACTIVE_DEVICE_CHANGED =
124 "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
Jaikumar Ganeshf48cda52010-04-02 14:44:43 -0700125
Nick Pellydac4c0d2009-09-10 10:21:56 -0700126 /**
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700127 * Intent used to broadcast that the headset has posted a
128 * vendor-specific event.
129 *
130 * <p>This intent will have 4 extras and 1 category.
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800131 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700132 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
133 * </li>
134 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
135 * specific command </li>
136 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
137 * command type which can be one of {@link #AT_CMD_TYPE_READ},
138 * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
139 * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
140 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
141 * arguments. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800142 * </ul>
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700143 *
Jack He910201b2017-08-22 16:06:54 -0700144 * <p> The category is the Company ID of the vendor defining the
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700145 * vendor-specific command. {@link BluetoothAssignedNumbers}
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700146 *
147 * For example, for Plantronics specific events
148 * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
149 *
150 * <p> For example, an AT+XEVENT=foo,3 will get translated into
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800151 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700152 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
153 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
154 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800155 * </ul>
Herb Jellinekaad41c52010-08-10 13:17:43 -0700156 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600157 @RequiresLegacyBluetoothPermission
158 @RequiresBluetoothConnectPermission
159 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Herb Jellinekaad41c52010-08-10 13:17:43 -0700160 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
161 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
162 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
163
164 /**
165 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
166 * intents that contains the name of the vendor-specific command.
167 */
168 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
169 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
170
171 /**
172 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700173 * intents that contains the AT command type of the vendor-specific command.
Herb Jellinekaad41c52010-08-10 13:17:43 -0700174 */
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700175 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
176 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
177
178 /**
179 * AT command type READ 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_READ = 0;
184
185 /**
186 * AT command type TEST used with
187 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
188 * For example, AT+VGM=?. There are no arguments for this command type.
189 */
190 public static final int AT_CMD_TYPE_TEST = 1;
191
192 /**
193 * AT command type SET used with
194 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
195 * For example, AT+VGM=<args>.
196 */
197 public static final int AT_CMD_TYPE_SET = 2;
198
199 /**
200 * AT command type BASIC used with
201 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
202 * For example, ATD. Single character commands and everything following the
203 * character are arguments.
204 */
205 public static final int AT_CMD_TYPE_BASIC = 3;
206
207 /**
208 * AT command type ACTION used with
209 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
210 * For example, AT+CHUP. There are no arguments for action commands.
211 */
212 public static final int AT_CMD_TYPE_ACTION = 4;
Herb Jellinekaad41c52010-08-10 13:17:43 -0700213
214 /**
215 * A Parcelable String array extra field in
216 * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
217 * the arguments to the vendor-specific command.
218 */
219 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
220 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
221
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700222 /**
223 * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
224 * for the companyId
225 */
Jack He910201b2017-08-22 16:06:54 -0700226 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY =
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700227 "android.bluetooth.headset.intent.category.companyid";
228
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700229 /**
Edward Jee7c81f1f2013-08-16 04:07:49 -0700230 * A vendor-specific command for unsolicited result code.
231 */
232 public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
233
234 /**
Jack He7f9c70b2017-06-20 17:07:40 -0700235 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700236 *
Jack He7f9c70b2017-06-20 17:07:40 -0700237 * @hide
238 */
Jack He0dfd69b2017-06-20 17:09:47 -0700239 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL";
240
241 /**
242 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700243 *
Jack He0dfd69b2017-06-20 17:09:47 -0700244 * @hide
245 */
246 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV";
247
248 /**
249 * Battery level indicator associated with
250 * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV}
Jack He910201b2017-08-22 16:06:54 -0700251 *
Jack He0dfd69b2017-06-20 17:09:47 -0700252 * @hide
253 */
254 public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1;
255
256 /**
257 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700258 *
Jack He0dfd69b2017-06-20 17:09:47 -0700259 * @hide
260 */
Jack He7f9c70b2017-06-20 17:07:40 -0700261 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT";
262
263 /**
264 * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT}
Jack He910201b2017-08-22 16:06:54 -0700265 *
Jack He7f9c70b2017-06-20 17:07:40 -0700266 * @hide
267 */
268 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY";
269
270 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800271 * Headset state when SCO audio is not connected.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700272 * This state can be one of
273 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
274 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
275 */
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800276 public static final int STATE_AUDIO_DISCONNECTED = 10;
Herb Jellinekaad41c52010-08-10 13:17:43 -0700277
278 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800279 * Headset state when SCO audio is connecting.
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700280 * This state can be one of
281 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
282 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700283 */
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800284 public static final int STATE_AUDIO_CONNECTING = 11;
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700285
286 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800287 * Headset state when SCO audio is connected.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700288 * This state can be one of
289 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
290 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Nick Pellydac4c0d2009-09-10 10:21:56 -0700291 */
Chienyuan0d3bada2019-05-29 10:29:30 +0800292 public static final int STATE_AUDIO_CONNECTED = 12;
Mudumba Ananth3246de62016-02-29 02:14:36 -0800293
294 /**
295 * Intent used to broadcast the headset's indicator status
296 *
297 * <p>This intent will have 3 extras:
298 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700299 * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which
300 * is supported by the headset ( as indicated by AT+BIND command in the SLC
301 * sequence) or whose value is changed (indicated by AT+BIEV command) </li>
302 * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li>
303 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li>
Mudumba Ananth3246de62016-02-29 02:14:36 -0800304 * </ul>
Jack He48f6a8a2017-06-22 12:56:54 -0700305 * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators
Jack He910201b2017-08-22 16:06:54 -0700306 * are given an assigned number. Below shows the assigned number of Indicator added so far
Jack He48f6a8a2017-06-22 12:56:54 -0700307 * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled
308 * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery
Jack He910201b2017-08-22 16:06:54 -0700309 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800310 * @hide
311 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600312 @RequiresLegacyBluetoothPermission
313 @RequiresBluetoothConnectPermission
314 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jeff Sharkeyd7c55662021-04-20 12:30:37 -0600315 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Mudumba Ananth3246de62016-02-29 02:14:36 -0800316 public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
317 "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
318
319 /**
Jack He48f6a8a2017-06-22 12:56:54 -0700320 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
321 * intents that contains the assigned number of the headset indicator as defined by
322 * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7
Jack He910201b2017-08-22 16:06:54 -0700323 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800324 * @hide
325 */
326 public static final String EXTRA_HF_INDICATORS_IND_ID =
327 "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
328
329 /**
Jack He48f6a8a2017-06-22 12:56:54 -0700330 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
Mudumba Ananth3246de62016-02-29 02:14:36 -0800331 * intents that contains the value of the Headset indicator that is being sent.
Jack He910201b2017-08-22 16:06:54 -0700332 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800333 * @hide
334 */
335 public static final String EXTRA_HF_INDICATORS_IND_VALUE =
336 "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
337
Benjamin Franz3362fef2014-11-12 15:57:54 +0000338 private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
339 private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700340
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700341 private Context mContext;
342 private ServiceListener mServiceListener;
Jack He1f686f62017-08-17 12:11:18 -0700343 private volatile IBluetoothHeadset mService;
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600344 private final BluetoothAdapter mAdapter;
345 private final AttributionSource mAttributionSource;
The Android Open Source Project33897762009-03-03 19:31:44 -0800346
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600347 @SuppressLint("AndroidFrameworkBluetoothPermission")
Jack He9e045d22017-08-22 21:21:23 -0700348 private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
fredc3c719642012-04-12 00:02:00 -0700349 new IBluetoothStateChangeCallback.Stub() {
350 public void onBluetoothStateChange(boolean up) {
351 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
352 if (!up) {
Benjamin Franz3362fef2014-11-12 15:57:54 +0000353 doUnbind();
fredc3c719642012-04-12 00:02:00 -0700354 } else {
Ugo Yu1652b942019-03-26 21:38:08 +0800355 doBind();
fredc3c719642012-04-12 00:02:00 -0700356 }
357 }
Jack He910201b2017-08-22 16:06:54 -0700358 };
fredc3c719642012-04-12 00:02:00 -0700359
The Android Open Source Project33897762009-03-03 19:31:44 -0800360 /**
361 * Create a BluetoothHeadset proxy object.
362 */
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600363 /* package */ BluetoothHeadset(Context context, ServiceListener l, BluetoothAdapter adapter) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800364 mContext = context;
365 mServiceListener = l;
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600366 mAdapter = adapter;
367 mAttributionSource = adapter.getAttributionSource();
fredc3c719642012-04-12 00:02:00 -0700368
Jeff Sharkey19a8d092021-04-28 09:25:36 -0600369 // Preserve legacy compatibility where apps were depending on
370 // registerStateChangeCallback() performing a permissions check which
371 // has been relaxed in modern platform versions
372 if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R
373 && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH)
374 != PackageManager.PERMISSION_GRANTED) {
375 throw new SecurityException("Need BLUETOOTH permission");
376 }
377
fredc3c719642012-04-12 00:02:00 -0700378 IBluetoothManager mgr = mAdapter.getBluetoothManager();
379 if (mgr != null) {
380 try {
381 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
382 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700383 Log.e(TAG, "", e);
fredc3c719642012-04-12 00:02:00 -0700384 }
385 }
386
Dianne Hackborn3875ec62013-08-04 16:50:16 -0700387 doBind();
388 }
389
Ugo Yu1652b942019-03-26 21:38:08 +0800390 private boolean doBind() {
391 synchronized (mConnection) {
392 if (mService == null) {
393 if (VDBG) Log.d(TAG, "Binding service...");
394 try {
395 return mAdapter.getBluetoothManager().bindBluetoothProfileService(
396 BluetoothProfile.HEADSET, mConnection);
397 } catch (RemoteException e) {
398 Log.e(TAG, "Unable to bind HeadsetService", e);
399 }
400 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800401 }
Benjamin Franz3362fef2014-11-12 15:57:54 +0000402 return false;
403 }
404
Ugo Yu1652b942019-03-26 21:38:08 +0800405 private void doUnbind() {
Benjamin Franz3362fef2014-11-12 15:57:54 +0000406 synchronized (mConnection) {
407 if (mService != null) {
Ugo Yu1652b942019-03-26 21:38:08 +0800408 if (VDBG) Log.d(TAG, "Unbinding service...");
Benjamin Franz3362fef2014-11-12 15:57:54 +0000409 try {
410 mAdapter.getBluetoothManager().unbindBluetoothProfileService(
411 BluetoothProfile.HEADSET, mConnection);
412 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700413 Log.e(TAG, "Unable to unbind HeadsetService", e);
Ugo Yu1652b942019-03-26 21:38:08 +0800414 } finally {
415 mService = null;
Benjamin Franz3362fef2014-11-12 15:57:54 +0000416 }
417 }
418 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800419 }
420
The Android Open Source Project33897762009-03-03 19:31:44 -0800421 /**
422 * Close the connection to the backing service.
423 * Other public functions of BluetoothHeadset will return default error
424 * results once close() has been called. Multiple invocations of close()
425 * are ok.
426 */
Mathew Inwood7d543892018-08-01 15:07:20 +0100427 @UnsupportedAppUsage
Matthew Xie78912492012-03-22 17:18:37 -0700428 /*package*/ void close() {
Matthew Xief8035a72012-10-09 22:10:37 -0700429 if (VDBG) log("close()");
fredc3c719642012-04-12 00:02:00 -0700430
431 IBluetoothManager mgr = mAdapter.getBluetoothManager();
432 if (mgr != null) {
433 try {
434 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
Ugo Yu1652b942019-03-26 21:38:08 +0800435 } catch (RemoteException re) {
436 Log.e(TAG, "", re);
fredc3c719642012-04-12 00:02:00 -0700437 }
438 }
Benjamin Franz733656c2014-12-16 15:33:03 +0000439 mServiceListener = null;
Benjamin Franz3362fef2014-11-12 15:57:54 +0000440 doUnbind();
The Android Open Source Project33897762009-03-03 19:31:44 -0800441 }
442
443 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700444 * Initiate connection to a profile of the remote bluetooth device.
445 *
446 * <p> Currently, the system supports only 1 connection to the
447 * headset/handsfree profile. The API will automatically disconnect connected
448 * devices before connecting.
449 *
450 * <p> This API returns false in scenarios like the profile on the
451 * device is already connected or Bluetooth is not turned on.
452 * When this API returns true, it is guaranteed that
453 * connection state intent for the profile will be broadcasted with
454 * the state. Users can get the connection state of the profile
455 * from this intent.
456 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700457 * @param device Remote Bluetooth Device
Jack He910201b2017-08-22 16:06:54 -0700458 * @return false on immediate error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700459 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800460 */
Selim Guruna117cb372017-10-17 17:01:38 -0700461 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600462 @RequiresLegacyBluetoothAdminPermission
463 @RequiresBluetoothConnectPermission
464 @RequiresPermission(allOf = {
465 android.Manifest.permission.BLUETOOTH_CONNECT,
466 android.Manifest.permission.MODIFY_PHONE_STATE,
467 })
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700468 public boolean connect(BluetoothDevice device) {
469 if (DBG) log("connect(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700470 final IBluetoothHeadset service = mService;
471 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800472 try {
Jack He1f686f62017-08-17 12:11:18 -0700473 return service.connect(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700474 } catch (RemoteException e) {
475 Log.e(TAG, Log.getStackTraceString(new Throwable()));
476 return false;
477 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800478 }
Jack He1f686f62017-08-17 12:11:18 -0700479 if (service == null) Log.w(TAG, "Proxy not attached to service");
The Android Open Source Project33897762009-03-03 19:31:44 -0800480 return false;
481 }
482
483 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700484 * Initiate disconnection from a profile
485 *
486 * <p> This API will return false in scenarios like the profile on the
487 * Bluetooth device is not in connected state etc. When this API returns,
488 * true, it is guaranteed that the connection state change
489 * intent will be broadcasted with the state. Users can get the
490 * disconnection state of the profile from this intent.
491 *
492 * <p> If the disconnection is initiated by a remote device, the state
493 * will transition from {@link #STATE_CONNECTED} to
494 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
495 * host (local) device the state will transition from
496 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
497 * state {@link #STATE_DISCONNECTED}. The transition to
498 * {@link #STATE_DISCONNECTING} can be used to distinguish between the
499 * two scenarios.
500 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700501 * @param device Remote Bluetooth Device
Jack He910201b2017-08-22 16:06:54 -0700502 * @return false on immediate error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700503 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800504 */
Selim Guruna117cb372017-10-17 17:01:38 -0700505 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600506 @RequiresLegacyBluetoothAdminPermission
507 @RequiresBluetoothConnectPermission
508 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700509 public boolean disconnect(BluetoothDevice device) {
510 if (DBG) log("disconnect(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700511 final IBluetoothHeadset service = mService;
512 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800513 try {
Jack He1f686f62017-08-17 12:11:18 -0700514 return service.disconnect(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700515 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700516 Log.e(TAG, Log.getStackTraceString(new Throwable()));
517 return false;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700518 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800519 }
Jack He1f686f62017-08-17 12:11:18 -0700520 if (service == null) Log.w(TAG, "Proxy not attached to service");
The Android Open Source Project33897762009-03-03 19:31:44 -0800521 return false;
522 }
523
524 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700525 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800526 */
Jack He9e045d22017-08-22 21:21:23 -0700527 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600528 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600529 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700530 public List<BluetoothDevice> getConnectedDevices() {
Matthew Xief8035a72012-10-09 22:10:37 -0700531 if (VDBG) log("getConnectedDevices()");
Jack He1f686f62017-08-17 12:11:18 -0700532 final IBluetoothHeadset service = mService;
533 if (service != null && isEnabled()) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800534 try {
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600535 return BluetoothDevice.setAttributionSource(
536 service.getConnectedDevices(), mAttributionSource);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700537 } catch (RemoteException e) {
538 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700539 return new ArrayList<BluetoothDevice>();
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700540 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800541 }
Jack He1f686f62017-08-17 12:11:18 -0700542 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700543 return new ArrayList<BluetoothDevice>();
The Android Open Source Project33897762009-03-03 19:31:44 -0800544 }
545
546 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700547 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800548 */
Jack He9e045d22017-08-22 21:21:23 -0700549 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600550 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600551 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700552 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Matthew Xief8035a72012-10-09 22:10:37 -0700553 if (VDBG) log("getDevicesMatchingStates()");
Jack He1f686f62017-08-17 12:11:18 -0700554 final IBluetoothHeadset service = mService;
555 if (service != null && isEnabled()) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800556 try {
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600557 return BluetoothDevice.setAttributionSource(
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600558 service.getDevicesMatchingConnectionStates(states, mAttributionSource),
559 mAttributionSource);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700560 } catch (RemoteException e) {
561 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700562 return new ArrayList<BluetoothDevice>();
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700563 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800564 }
Jack He1f686f62017-08-17 12:11:18 -0700565 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700566 return new ArrayList<BluetoothDevice>();
The Android Open Source Project33897762009-03-03 19:31:44 -0800567 }
568
569 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700570 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800571 */
Jack He9e045d22017-08-22 21:21:23 -0700572 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600573 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600574 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700575 public int getConnectionState(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700576 if (VDBG) log("getConnectionState(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700577 final IBluetoothHeadset service = mService;
578 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800579 try {
Jack He1f686f62017-08-17 12:11:18 -0700580 return service.getConnectionState(device);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700581 } catch (RemoteException e) {
582 Log.e(TAG, Log.getStackTraceString(new Throwable()));
583 return BluetoothProfile.STATE_DISCONNECTED;
584 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800585 }
Jack He1f686f62017-08-17 12:11:18 -0700586 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700587 return BluetoothProfile.STATE_DISCONNECTED;
The Android Open Source Project33897762009-03-03 19:31:44 -0800588 }
589
590 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700591 * Set priority of the profile
592 *
593 * <p> The device should already be paired.
Selim Gurun9e6b35b2018-01-09 14:35:19 -0800594 * Priority can be one of {@link BluetoothProfile#PRIORITY_ON} or
Rahul Sabnisd9798612019-12-04 14:21:10 -0800595 * {@link BluetoothProfile#PRIORITY_OFF}
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700596 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700597 * @param device Paired bluetooth device
598 * @param priority
599 * @return true if priority is set, false on error
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700600 * @hide
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800601 * @deprecated Replaced with {@link #setConnectionPolicy(BluetoothDevice, int)}
Rahul Sabnis9095a452021-03-24 00:19:00 -0700602 * @removed
The Android Open Source Project33897762009-03-03 19:31:44 -0800603 */
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800604 @Deprecated
Selim Guruna117cb372017-10-17 17:01:38 -0700605 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600606 @RequiresLegacyBluetoothAdminPermission
607 @RequiresBluetoothConnectPermission
608 @RequiresPermission(allOf = {
609 android.Manifest.permission.BLUETOOTH_CONNECT,
610 android.Manifest.permission.MODIFY_PHONE_STATE,
611 })
Nick Pelly2d664882009-08-14 18:33:38 -0700612 public boolean setPriority(BluetoothDevice device, int priority) {
613 if (DBG) log("setPriority(" + device + ", " + priority + ")");
Rahul Sabnisc228ce22020-03-18 17:46:33 -0700614 final IBluetoothHeadset service = mService;
615 if (service != null && isEnabled() && isValidDevice(device)) {
616 if (priority != BluetoothProfile.PRIORITY_OFF
617 && priority != BluetoothProfile.PRIORITY_ON) {
618 return false;
619 }
620 try {
621 return service.setPriority(
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600622 device, BluetoothAdapter.priorityToConnectionPolicy(priority),
623 mAttributionSource);
Rahul Sabnisc228ce22020-03-18 17:46:33 -0700624 } catch (RemoteException e) {
625 Log.e(TAG, Log.getStackTraceString(new Throwable()));
626 return false;
627 }
628 }
629 if (service == null) Log.w(TAG, "Proxy not attached to service");
630 return false;
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800631 }
632
633 /**
634 * Set connection policy of the profile
635 *
636 * <p> The device should already be paired.
637 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
638 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
639 *
640 * @param device Paired bluetooth device
641 * @param connectionPolicy is the connection policy to set to for this profile
642 * @return true if connectionPolicy is set, false on error
643 * @hide
644 */
645 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600646 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600647 @RequiresPermission(allOf = {
648 android.Manifest.permission.BLUETOOTH_CONNECT,
649 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
650 android.Manifest.permission.MODIFY_PHONE_STATE,
651 })
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800652 public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
653 @ConnectionPolicy int connectionPolicy) {
654 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
Jack He1f686f62017-08-17 12:11:18 -0700655 final IBluetoothHeadset service = mService;
656 if (service != null && isEnabled() && isValidDevice(device)) {
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800657 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
658 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
Jack He910201b2017-08-22 16:06:54 -0700659 return false;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700660 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800661 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600662 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700663 } catch (RemoteException e) {
664 Log.e(TAG, Log.getStackTraceString(new Throwable()));
665 return false;
666 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800667 }
Jack He1f686f62017-08-17 12:11:18 -0700668 if (service == null) Log.w(TAG, "Proxy not attached to service");
The Android Open Source Project33897762009-03-03 19:31:44 -0800669 return false;
670 }
671
672 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700673 * Get the priority of the profile.
674 *
675 * <p> The priority can be any of:
676 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
677 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
678 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700679 * @param device Bluetooth device
680 * @return priority of the device
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700681 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800682 */
Mathew Inwood049f0f52020-11-04 09:29:36 +0000683 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600684 @RequiresLegacyBluetoothPermission
685 @RequiresBluetoothConnectPermission
686 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Nick Pelly2d664882009-08-14 18:33:38 -0700687 public int getPriority(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700688 if (VDBG) log("getPriority(" + device + ")");
Rahul Sabnisc228ce22020-03-18 17:46:33 -0700689 final IBluetoothHeadset service = mService;
690 if (service != null && isEnabled() && isValidDevice(device)) {
691 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600692 return BluetoothAdapter.connectionPolicyToPriority(
693 service.getPriority(device, mAttributionSource));
Rahul Sabnisc228ce22020-03-18 17:46:33 -0700694 } catch (RemoteException e) {
695 Log.e(TAG, Log.getStackTraceString(new Throwable()));
696 return BluetoothProfile.PRIORITY_OFF;
697 }
698 }
699 if (service == null) Log.w(TAG, "Proxy not attached to service");
700 return BluetoothProfile.PRIORITY_OFF;
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800701 }
702
703 /**
704 * Get the connection policy of the profile.
705 *
706 * <p> The connection policy can be any of:
707 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
708 * {@link #CONNECTION_POLICY_UNKNOWN}
709 *
710 * @param device Bluetooth device
711 * @return connection policy of the device
712 * @hide
713 */
714 @SystemApi
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600715 @RequiresBluetoothConnectPermission
716 @RequiresPermission(allOf = {
717 android.Manifest.permission.BLUETOOTH_CONNECT,
718 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
719 })
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800720 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
721 if (VDBG) log("getConnectionPolicy(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700722 final IBluetoothHeadset service = mService;
723 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800724 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600725 return service.getConnectionPolicy(device, mAttributionSource);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700726 } catch (RemoteException e) {
727 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800728 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700729 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800730 }
Jack He1f686f62017-08-17 12:11:18 -0700731 if (service == null) Log.w(TAG, "Proxy not attached to service");
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800732 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700733 }
734
735 /**
Rahul Sabnisc611e732020-12-14 10:54:45 -0800736 * Checks whether the headset supports some form of noise reduction
737 *
738 * @param device Bluetooth device
739 * @return true if echo cancellation and/or noise reduction 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 isNoiseReductionSupported(@NonNull BluetoothDevice device) {
745 if (DBG) log("isNoiseReductionSupported()");
746 final IBluetoothHeadset service = mService;
747 if (service != null && isEnabled() && isValidDevice(device)) {
748 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600749 return service.isNoiseReductionSupported(device, mAttributionSource);
Rahul Sabnisc611e732020-12-14 10:54:45 -0800750 } 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 /**
759 * Checks whether the headset supports voice recognition
760 *
761 * @param device Bluetooth device
762 * @return true if voice recognition is supported, false otherwise
763 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600764 @RequiresLegacyBluetoothPermission
765 @RequiresBluetoothConnectPermission
766 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Rahul Sabnisc611e732020-12-14 10:54:45 -0800767 public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
768 if (DBG) log("isVoiceRecognitionSupported()");
769 final IBluetoothHeadset service = mService;
770 if (service != null && isEnabled() && isValidDevice(device)) {
771 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600772 return service.isVoiceRecognitionSupported(device, mAttributionSource);
Rahul Sabnisc611e732020-12-14 10:54:45 -0800773 } catch (RemoteException e) {
774 Log.e(TAG, Log.getStackTraceString(new Throwable()));
775 }
776 }
777 if (service == null) Log.w(TAG, "Proxy not attached to service");
778 return false;
779 }
780
781 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700782 * Start Bluetooth voice recognition. This methods sends the voice
783 * recognition AT command to the headset and establishes the
784 * audio connection.
785 *
786 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800787 * If this function returns true, this intent will be broadcasted with
788 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700789 *
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800790 * <p> {@link #EXTRA_STATE} will transition from
791 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
792 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
793 * in case of failure to establish the audio connection.
794 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700795 * @param device Bluetooth headset
Jack Hec46a01e2018-05-02 19:10:56 -0700796 * @return false if there is no headset connected, or the connected headset doesn't support
797 * voice recognition, or voice recognition is already started, or audio channel is occupied,
798 * or on error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700799 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600800 @RequiresLegacyBluetoothPermission
801 @RequiresBluetoothConnectPermission
802 @RequiresPermission(allOf = {
803 android.Manifest.permission.BLUETOOTH_CONNECT,
804 android.Manifest.permission.MODIFY_PHONE_STATE,
805 })
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700806 public boolean startVoiceRecognition(BluetoothDevice device) {
807 if (DBG) log("startVoiceRecognition()");
Jack He1f686f62017-08-17 12:11:18 -0700808 final IBluetoothHeadset service = mService;
809 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700810 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600811 return service.startVoiceRecognition(device, mAttributionSource);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700812 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700813 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700814 }
815 }
Jack He1f686f62017-08-17 12:11:18 -0700816 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700817 return false;
818 }
819
820 /**
821 * Stop Bluetooth Voice Recognition mode, and shut down the
822 * Bluetooth audio path.
823 *
Jack Hec46a01e2018-05-02 19:10:56 -0700824 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
825 * If this function returns true, this intent will be broadcasted with
826 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
827 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700828 * @param device Bluetooth headset
Jack Hec46a01e2018-05-02 19:10:56 -0700829 * @return false if there is no headset connected, or voice recognition has not started,
830 * or voice recognition has ended on this headset, or on error, true otherwise
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 stopVoiceRecognition(BluetoothDevice device) {
836 if (DBG) log("stopVoiceRecognition()");
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 {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600840 return service.stopVoiceRecognition(device, mAttributionSource);
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;
847 }
848
849 /**
850 * Check if Bluetooth SCO audio is connected.
851 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700852 * @param device Bluetooth headset
Jack He910201b2017-08-22 16:06:54 -0700853 * @return true if SCO is connected, false otherwise or on error
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700854 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600855 @RequiresLegacyBluetoothPermission
856 @RequiresBluetoothConnectPermission
857 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700858 public boolean isAudioConnected(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700859 if (VDBG) log("isAudioConnected()");
Jack He1f686f62017-08-17 12:11:18 -0700860 final IBluetoothHeadset service = mService;
861 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700862 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600863 return service.isAudioConnected(device, mAttributionSource);
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700864 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700865 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700866 }
867 }
Jack He1f686f62017-08-17 12:11:18 -0700868 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700869 return false;
The Android Open Source Project33897762009-03-03 19:31:44 -0800870 }
871
872 /**
Eric Laurent2e66fa22010-03-17 14:59:27 -0700873 * Indicates if current platform supports voice dialing over bluetooth SCO.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700874 *
Eric Laurent2e66fa22010-03-17 14:59:27 -0700875 * @return true if voice dialing over bluetooth is supported, false otherwise.
876 * @hide
877 */
878 public static boolean isBluetoothVoiceDialingEnabled(Context context) {
879 return context.getResources().getBoolean(
880 com.android.internal.R.bool.config_bluetooth_sco_off_call);
881 }
882
Jaikumar Ganeshb84bbd92010-06-02 14:36:14 -0700883 /**
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700884 * Get the current audio state of the Headset.
885 * Note: This is an internal function and shouldn't be exposed
886 *
887 * @hide
888 */
Mathew Inwood049f0f52020-11-04 09:29:36 +0000889 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600890 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600891 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700892 public int getAudioState(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700893 if (VDBG) log("getAudioState");
Jack He1f686f62017-08-17 12:11:18 -0700894 final IBluetoothHeadset service = mService;
895 if (service != null && !isDisabled()) {
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700896 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600897 return service.getAudioState(device, mAttributionSource);
Jack He910201b2017-08-22 16:06:54 -0700898 } catch (RemoteException e) {
899 Log.e(TAG, e.toString());
900 }
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700901 } else {
902 Log.w(TAG, "Proxy not attached to service");
903 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
904 }
905 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
906 }
907
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -0700908 /**
Bryce Lee0e154a32015-11-16 08:55:52 -0800909 * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
910 * audio to the HF unless explicitly told to.
911 * This method should be used in cases where the SCO channel is shared between multiple profiles
912 * and must be delegated by a source knowledgeable
913 * Note: This is an internal function and shouldn't be exposed
914 *
915 * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
Bryce Lee0e154a32015-11-16 08:55:52 -0800916 * @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 void setAudioRouteAllowed(boolean allowed) {
921 if (VDBG) log("setAudioRouteAllowed");
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 {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600925 service.setAudioRouteAllowed(allowed, mAttributionSource);
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 }
934
935 /**
936 * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
937 * Note: This is an internal function and shouldn't be exposed
938 *
939 * @hide
940 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600941 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600942 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Bryce Lee0e154a32015-11-16 08:55:52 -0800943 public boolean getAudioRouteAllowed() {
944 if (VDBG) log("getAudioRouteAllowed");
Jack He1f686f62017-08-17 12:11:18 -0700945 final IBluetoothHeadset service = mService;
946 if (service != null && isEnabled()) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800947 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600948 return service.getAudioRouteAllowed(mAttributionSource);
Jack He910201b2017-08-22 16:06:54 -0700949 } catch (RemoteException e) {
950 Log.e(TAG, e.toString());
951 }
Bryce Lee0e154a32015-11-16 08:55:52 -0800952 } else {
953 Log.w(TAG, "Proxy not attached to service");
954 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
955 }
956 return false;
957 }
958
959 /**
Jack He798d7282017-05-09 17:16:01 -0700960 * Force SCO audio to be opened regardless any other restrictions
961 *
Jack He910201b2017-08-22 16:06:54 -0700962 * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio
963 * False to use SCO audio in normal manner
Jack He798d7282017-05-09 17:16:01 -0700964 * @hide
965 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600966 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600967 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He798d7282017-05-09 17:16:01 -0700968 public void setForceScoAudio(boolean forced) {
969 if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
Jack He1f686f62017-08-17 12:11:18 -0700970 final IBluetoothHeadset service = mService;
971 if (service != null && isEnabled()) {
Jack He798d7282017-05-09 17:16:01 -0700972 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600973 service.setForceScoAudio(forced, mAttributionSource);
Jack He798d7282017-05-09 17:16:01 -0700974 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700975 Log.e(TAG, e.toString());
Jack He798d7282017-05-09 17:16:01 -0700976 }
977 } else {
978 Log.w(TAG, "Proxy not attached to service");
979 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
980 }
981 }
982
983 /**
Jack Hec46a01e2018-05-02 19:10:56 -0700984 * Check if at least one headset's SCO audio is connected or connecting
Matthew Xief3ee3512012-02-16 16:57:18 -0800985 *
Jack Hec46a01e2018-05-02 19:10:56 -0700986 * @return true if at least one device's SCO audio is connected or connecting, false otherwise
987 * or on error
Matthew Xief3ee3512012-02-16 16:57:18 -0800988 * @hide
989 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600990 @RequiresLegacyBluetoothPermission
991 @RequiresBluetoothConnectPermission
992 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Matthew Xief3ee3512012-02-16 16:57:18 -0800993 public boolean isAudioOn() {
Matthew Xief8035a72012-10-09 22:10:37 -0700994 if (VDBG) log("isAudioOn()");
Jack He1f686f62017-08-17 12:11:18 -0700995 final IBluetoothHeadset service = mService;
996 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -0800997 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600998 return service.isAudioOn(mAttributionSource);
Matthew Xief3ee3512012-02-16 16:57:18 -0800999 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -07001000 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Matthew Xief3ee3512012-02-16 16:57:18 -08001001 }
1002 }
Jack He1f686f62017-08-17 12:11:18 -07001003 if (service == null) Log.w(TAG, "Proxy not attached to service");
Matthew Xief3ee3512012-02-16 16:57:18 -08001004 return false;
1005
1006 }
1007
1008 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001009 * Initiates a connection of headset audio to the current active device
Matthew Xief3ee3512012-02-16 16:57:18 -08001010 *
Jack Hec46a01e2018-05-02 19:10:56 -07001011 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1012 * If this function returns true, this intent will be broadcasted with
1013 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
1014 *
1015 * <p> {@link #EXTRA_STATE} will transition from
1016 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
1017 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
1018 * in case of failure to establish the audio connection.
1019 *
1020 * Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true
1021 * before calling this method
1022 *
1023 * @return false if there was some error such as there is no active headset
Matthew Xief3ee3512012-02-16 16:57:18 -08001024 * @hide
1025 */
Mathew Inwood7d543892018-08-01 15:07:20 +01001026 @UnsupportedAppUsage
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001027 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001028 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Matthew Xief3ee3512012-02-16 16:57:18 -08001029 public boolean connectAudio() {
Jack He1f686f62017-08-17 12:11:18 -07001030 final IBluetoothHeadset service = mService;
1031 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001032 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -06001033 return service.connectAudio(mAttributionSource);
Matthew Xief3ee3512012-02-16 16:57:18 -08001034 } catch (RemoteException e) {
1035 Log.e(TAG, e.toString());
1036 }
1037 } else {
1038 Log.w(TAG, "Proxy not attached to service");
1039 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1040 }
1041 return false;
1042 }
1043
1044 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001045 * Initiates a disconnection of HFP SCO audio.
1046 * Tear down voice recognition or virtual voice call if any.
Matthew Xief3ee3512012-02-16 16:57:18 -08001047 *
Jack Hec46a01e2018-05-02 19:10:56 -07001048 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1049 * If this function returns true, this intent will be broadcasted with
1050 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
1051 *
1052 * @return false if audio is not connected, or on error, true otherwise
Matthew Xief3ee3512012-02-16 16:57:18 -08001053 * @hide
1054 */
Mathew Inwood7d543892018-08-01 15:07:20 +01001055 @UnsupportedAppUsage
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001056 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001057 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Matthew Xief3ee3512012-02-16 16:57:18 -08001058 public boolean disconnectAudio() {
Jack He1f686f62017-08-17 12:11:18 -07001059 final IBluetoothHeadset service = mService;
1060 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001061 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -06001062 return service.disconnectAudio(mAttributionSource);
Matthew Xief3ee3512012-02-16 16:57:18 -08001063 } catch (RemoteException e) {
1064 Log.e(TAG, e.toString());
1065 }
1066 } else {
1067 Log.w(TAG, "Proxy not attached to service");
1068 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1069 }
1070 return false;
1071 }
1072
1073 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001074 * Initiates a SCO channel connection as a virtual voice call to the current active device
1075 * Active handsfree device will be notified of incoming call and connected call.
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001076 *
Jack Hec46a01e2018-05-02 19:10:56 -07001077 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1078 * If this function returns true, this intent will be broadcasted with
1079 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
1080 *
1081 * <p> {@link #EXTRA_STATE} will transition from
1082 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
1083 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
1084 * in case of failure to establish the audio connection.
1085 *
1086 * @return true if successful, false if one of the following case applies
1087 * - SCO audio is not idle (connecting or connected)
1088 * - virtual call has already started
1089 * - there is no active device
1090 * - a Telecom managed call is going on
1091 * - binder is dead or Bluetooth is disabled or other error
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001092 * @hide
1093 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001094 @RequiresLegacyBluetoothAdminPermission
1095 @RequiresBluetoothConnectPermission
1096 @RequiresPermission(allOf = {
1097 android.Manifest.permission.BLUETOOTH_CONNECT,
1098 android.Manifest.permission.MODIFY_PHONE_STATE,
1099 })
Mathew Inwood7d543892018-08-01 15:07:20 +01001100 @UnsupportedAppUsage
Jack Hec46a01e2018-05-02 19:10:56 -07001101 public boolean startScoUsingVirtualVoiceCall() {
Jaikumar Ganesheea6d262011-01-24 13:55:27 -08001102 if (DBG) log("startScoUsingVirtualVoiceCall()");
Jack He1f686f62017-08-17 12:11:18 -07001103 final IBluetoothHeadset service = mService;
Jack Hec46a01e2018-05-02 19:10:56 -07001104 if (service != null && isEnabled()) {
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001105 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -06001106 return service.startScoUsingVirtualVoiceCall(mAttributionSource);
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001107 } catch (RemoteException e) {
1108 Log.e(TAG, e.toString());
1109 }
1110 } else {
1111 Log.w(TAG, "Proxy not attached to service");
1112 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1113 }
1114 return false;
1115 }
1116
1117 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001118 * Terminates an ongoing SCO connection and the associated virtual call.
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001119 *
Jack Hec46a01e2018-05-02 19:10:56 -07001120 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1121 * If this function returns true, this intent will be broadcasted with
1122 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
1123 *
1124 * @return true if successful, false if one of the following case applies
1125 * - virtual voice call is not started or has ended
1126 * - binder is dead or Bluetooth is disabled or other error
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001127 * @hide
1128 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001129 @RequiresLegacyBluetoothAdminPermission
1130 @RequiresBluetoothConnectPermission
1131 @RequiresPermission(allOf = {
1132 android.Manifest.permission.BLUETOOTH_CONNECT,
1133 android.Manifest.permission.MODIFY_PHONE_STATE,
1134 })
Mathew Inwood7d543892018-08-01 15:07:20 +01001135 @UnsupportedAppUsage
Jack Hec46a01e2018-05-02 19:10:56 -07001136 public boolean stopScoUsingVirtualVoiceCall() {
Jaikumar Ganesheea6d262011-01-24 13:55:27 -08001137 if (DBG) log("stopScoUsingVirtualVoiceCall()");
Jack He1f686f62017-08-17 12:11:18 -07001138 final IBluetoothHeadset service = mService;
Jack Hec46a01e2018-05-02 19:10:56 -07001139 if (service != null && isEnabled()) {
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001140 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -06001141 return service.stopScoUsingVirtualVoiceCall(mAttributionSource);
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001142 } catch (RemoteException e) {
1143 Log.e(TAG, e.toString());
1144 }
1145 } else {
1146 Log.w(TAG, "Proxy not attached to service");
1147 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1148 }
1149 return false;
1150 }
1151
Matthew Xief3ee3512012-02-16 16:57:18 -08001152 /**
1153 * Notify Headset of phone state change.
1154 * This is a backdoor for phone app to call BluetoothHeadset since
1155 * there is currently not a good way to get precise call state change outside
1156 * of phone app.
1157 *
1158 * @hide
1159 */
Mathew Inwood049f0f52020-11-04 09:29:36 +00001160 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001161 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001162 @RequiresPermission(allOf = {
1163 android.Manifest.permission.BLUETOOTH_CONNECT,
1164 android.Manifest.permission.MODIFY_PHONE_STATE,
1165 })
Matthew Xief3ee3512012-02-16 16:57:18 -08001166 public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
Benson Lied8d3392018-07-17 18:19:59 +08001167 int type, String name) {
Jack He1f686f62017-08-17 12:11:18 -07001168 final IBluetoothHeadset service = mService;
1169 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001170 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -06001171 service.phoneStateChanged(numActive, numHeld, callState, number, type, name,
1172 mAttributionSource);
Matthew Xief3ee3512012-02-16 16:57:18 -08001173 } catch (RemoteException e) {
1174 Log.e(TAG, e.toString());
1175 }
1176 } else {
1177 Log.w(TAG, "Proxy not attached to service");
1178 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1179 }
1180 }
1181
1182 /**
Matthew Xief3ee3512012-02-16 16:57:18 -08001183 * Send Headset of CLCC response
1184 *
1185 * @hide
1186 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001187 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001188 @RequiresPermission(allOf = {
1189 android.Manifest.permission.BLUETOOTH_CONNECT,
1190 android.Manifest.permission.MODIFY_PHONE_STATE,
1191 })
Matthew Xief3ee3512012-02-16 16:57:18 -08001192 public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
Jack He910201b2017-08-22 16:06:54 -07001193 String number, int type) {
Jack He1f686f62017-08-17 12:11:18 -07001194 final IBluetoothHeadset service = mService;
1195 if (service != null && isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001196 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -06001197 service.clccResponse(index, direction, status, mode, mpty, number, type,
1198 mAttributionSource);
Matthew Xief3ee3512012-02-16 16:57:18 -08001199 } catch (RemoteException e) {
1200 Log.e(TAG, e.toString());
1201 }
1202 } else {
1203 Log.w(TAG, "Proxy not attached to service");
1204 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1205 }
1206 }
1207
Edward Jee7c81f1f2013-08-16 04:07:49 -07001208 /**
1209 * Sends a vendor-specific unsolicited result code to the headset.
1210 *
Jack He910201b2017-08-22 16:06:54 -07001211 * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code
1212 * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the
1213 * string <code>"+ANDROID: 0"</code> will be sent.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001214 *
Ying Wang29d93892013-08-26 17:48:22 -07001215 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001216 *
Edward Jee7c81f1f2013-08-16 04:07:49 -07001217 * @param device Bluetooth headset.
1218 * @param command A vendor-specific command.
1219 * @param arg The argument that will be attached to the command.
1220 * @return {@code false} if there is no headset connected, or if the command is not an allowed
Jack He910201b2017-08-22 16:06:54 -07001221 * vendor-specific unsolicited result code, or on error. {@code true} otherwise.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001222 * @throws IllegalArgumentException if {@code command} is {@code null}.
1223 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001224 @RequiresLegacyBluetoothPermission
1225 @RequiresBluetoothConnectPermission
1226 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Edward Jee7c81f1f2013-08-16 04:07:49 -07001227 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
1228 String arg) {
1229 if (DBG) {
1230 log("sendVendorSpecificResultCode()");
1231 }
1232 if (command == null) {
1233 throw new IllegalArgumentException("command is null");
1234 }
Jack He1f686f62017-08-17 12:11:18 -07001235 final IBluetoothHeadset service = mService;
1236 if (service != null && isEnabled() && isValidDevice(device)) {
Edward Jee7c81f1f2013-08-16 04:07:49 -07001237 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -06001238 return service.sendVendorSpecificResultCode(device, command, arg,
1239 mAttributionSource);
Edward Jee7c81f1f2013-08-16 04:07:49 -07001240 } catch (RemoteException e) {
1241 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1242 }
1243 }
Jack He1f686f62017-08-17 12:11:18 -07001244 if (service == null) {
Edward Jee7c81f1f2013-08-16 04:07:49 -07001245 Log.w(TAG, "Proxy not attached to service");
1246 }
1247 return false;
1248 }
1249
Mudumba Ananth80bf6282014-04-27 13:11:00 -07001250 /**
Jack He889d2342018-01-03 12:13:26 -08001251 * Select a connected device as active.
1252 *
1253 * The active device selection is per profile. An active device's
1254 * purpose is profile-specific. For example, in HFP and HSP profiles,
1255 * it is the device used for phone call audio. If a remote device is not
1256 * connected, it cannot be selected as active.
1257 *
1258 * <p> This API returns false in scenarios like the profile on the
1259 * device is not connected or Bluetooth is not turned on.
1260 * When this API returns true, it is guaranteed that the
1261 * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
1262 * with the active device.
1263 *
Jack He889d2342018-01-03 12:13:26 -08001264 * @param device Remote Bluetooth Device, could be null if phone call audio should not be
1265 * streamed to a headset
1266 * @return false on immediate error, true otherwise
1267 * @hide
1268 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001269 @RequiresLegacyBluetoothAdminPermission
1270 @RequiresBluetoothConnectPermission
1271 @RequiresPermission(allOf = {
1272 android.Manifest.permission.BLUETOOTH_CONNECT,
1273 android.Manifest.permission.MODIFY_PHONE_STATE,
1274 })
Mathew Inwoodb426f902021-01-06 12:05:47 +00001275 @UnsupportedAppUsage(trackingBug = 171933273)
Jack He889d2342018-01-03 12:13:26 -08001276 public boolean setActiveDevice(@Nullable BluetoothDevice device) {
1277 if (DBG) {
1278 Log.d(TAG, "setActiveDevice: " + device);
1279 }
1280 final IBluetoothHeadset service = mService;
1281 if (service != null && isEnabled() && (device == null || isValidDevice(device))) {
1282 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -06001283 return service.setActiveDevice(device, mAttributionSource);
Jack He889d2342018-01-03 12:13:26 -08001284 } catch (RemoteException e) {
1285 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1286 }
1287 }
1288 if (service == null) {
1289 Log.w(TAG, "Proxy not attached to service");
1290 }
1291 return false;
1292 }
1293
1294 /**
1295 * Get the connected device that is active.
1296 *
Jack He889d2342018-01-03 12:13:26 -08001297 * @return the connected device that is active or null if no device
1298 * is active.
1299 * @hide
1300 */
Mathew Inwoodb426f902021-01-06 12:05:47 +00001301 @UnsupportedAppUsage(trackingBug = 171933273)
Rahul Sabnisd9798612019-12-04 14:21:10 -08001302 @Nullable
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001303 @RequiresLegacyBluetoothPermission
1304 @RequiresBluetoothConnectPermission
1305 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He889d2342018-01-03 12:13:26 -08001306 public BluetoothDevice getActiveDevice() {
1307 if (VDBG) {
1308 Log.d(TAG, "getActiveDevice");
1309 }
1310 final IBluetoothHeadset service = mService;
1311 if (service != null && isEnabled()) {
1312 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -06001313 return service.getActiveDevice(mAttributionSource);
Jack He889d2342018-01-03 12:13:26 -08001314 } catch (RemoteException e) {
1315 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1316 }
1317 }
1318 if (service == null) {
1319 Log.w(TAG, "Proxy not attached to service");
1320 }
1321 return null;
1322 }
1323
1324 /**
Jack Hec5fde732018-01-05 17:17:06 -08001325 * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an
1326 * active connection.
Jack Hed8d204d2016-11-17 16:19:43 -08001327 *
Jack Hec5fde732018-01-05 17:17:06 -08001328 * @return true if in-band ringing is enabled, false if in-band ringing is disabled
1329 * @hide
1330 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001331 @RequiresLegacyBluetoothPermission
1332 @RequiresBluetoothConnectPermission
1333 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack Hec5fde732018-01-05 17:17:06 -08001334 public boolean isInbandRingingEnabled() {
1335 if (DBG) {
1336 log("isInbandRingingEnabled()");
1337 }
1338 final IBluetoothHeadset service = mService;
1339 if (service != null && isEnabled()) {
1340 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -06001341 return service.isInbandRingingEnabled(mAttributionSource);
Jack Hec5fde732018-01-05 17:17:06 -08001342 } catch (RemoteException e) {
1343 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1344 }
1345 }
1346 if (service == null) {
1347 Log.w(TAG, "Proxy not attached to service");
1348 }
1349 return false;
1350 }
1351
1352 /**
1353 * Check if in-band ringing is supported for this platform.
1354 *
1355 * @return true if in-band ringing is supported, false if in-band ringing is not supported
Jack Hed8d204d2016-11-17 16:19:43 -08001356 * @hide
1357 */
1358 public static boolean isInbandRingingSupported(Context context) {
1359 return context.getResources().getBoolean(
1360 com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support);
1361 }
1362
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001363 @SuppressLint("AndroidFrameworkBluetoothPermission")
Jack He9e045d22017-08-22 21:21:23 -07001364 private final IBluetoothProfileServiceConnection mConnection =
1365 new IBluetoothProfileServiceConnection.Stub() {
Benjamin Franz3362fef2014-11-12 15:57:54 +00001366 @Override
The Android Open Source Project33897762009-03-03 19:31:44 -08001367 public void onServiceConnected(ComponentName className, IBinder service) {
1368 if (DBG) Log.d(TAG, "Proxy object connected");
Jeff Sharkey73458a82016-11-04 11:23:46 -06001369 mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service));
Benjamin Franz3362fef2014-11-12 15:57:54 +00001370 mHandler.sendMessage(mHandler.obtainMessage(
1371 MESSAGE_HEADSET_SERVICE_CONNECTED));
The Android Open Source Project33897762009-03-03 19:31:44 -08001372 }
Jack He910201b2017-08-22 16:06:54 -07001373
Benjamin Franz3362fef2014-11-12 15:57:54 +00001374 @Override
The Android Open Source Project33897762009-03-03 19:31:44 -08001375 public void onServiceDisconnected(ComponentName className) {
1376 if (DBG) Log.d(TAG, "Proxy object disconnected");
Ugo Yu1652b942019-03-26 21:38:08 +08001377 doUnbind();
Benjamin Franz3362fef2014-11-12 15:57:54 +00001378 mHandler.sendMessage(mHandler.obtainMessage(
1379 MESSAGE_HEADSET_SERVICE_DISCONNECTED));
The Android Open Source Project33897762009-03-03 19:31:44 -08001380 }
1381 };
The Android Open Source Project0047a0f2009-03-05 20:00:43 -08001382
Mathew Inwood7d543892018-08-01 15:07:20 +01001383 @UnsupportedAppUsage
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001384 private boolean isEnabled() {
Jack He1f686f62017-08-17 12:11:18 -07001385 return mAdapter.getState() == BluetoothAdapter.STATE_ON;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001386 }
1387
Jaikumar Ganesh2fbe8f42011-04-06 11:09:30 -07001388 private boolean isDisabled() {
Jack He1f686f62017-08-17 12:11:18 -07001389 return mAdapter.getState() == BluetoothAdapter.STATE_OFF;
Jaikumar Ganesh2fbe8f42011-04-06 11:09:30 -07001390 }
1391
Jack He1f686f62017-08-17 12:11:18 -07001392 private static boolean isValidDevice(BluetoothDevice device) {
1393 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001394 }
1395
The Android Open Source Project0047a0f2009-03-05 20:00:43 -08001396 private static void log(String msg) {
1397 Log.d(TAG, msg);
1398 }
Benjamin Franz3362fef2014-11-12 15:57:54 +00001399
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001400 @SuppressLint("AndroidFrameworkBluetoothPermission")
Benjamin Franz3362fef2014-11-12 15:57:54 +00001401 private final Handler mHandler = new Handler(Looper.getMainLooper()) {
1402 @Override
1403 public void handleMessage(Message msg) {
1404 switch (msg.what) {
1405 case MESSAGE_HEADSET_SERVICE_CONNECTED: {
1406 if (mServiceListener != null) {
1407 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
1408 BluetoothHeadset.this);
1409 }
1410 break;
1411 }
1412 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
1413 if (mServiceListener != null) {
1414 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
1415 }
Benjamin Franz3362fef2014-11-12 15:57:54 +00001416 break;
1417 }
1418 }
1419 }
1420 };
The Android Open Source Project33897762009-03-03 19:31:44 -08001421}