blob: 4241fff489f6b2af89e9dd3dc9056b6f9a8813d9 [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
William Escande9a4b7c12021-12-16 16:07:55 +010019import static android.bluetooth.BluetoothUtils.getSyncTimeout;
20
Rahul Sabnis5ed1b842021-12-23 11:36:40 -080021import android.annotation.IntDef;
Rahul Sabnise8bac9b2019-11-27 18:09:33 -080022import android.annotation.NonNull;
Jack He889d2342018-01-03 12:13:26 -080023import android.annotation.Nullable;
Selim Guruna117cb372017-10-17 17:01:38 -070024import android.annotation.RequiresPermission;
Nick Pellydac4c0d2009-09-10 10:21:56 -070025import android.annotation.SdkConstant;
26import android.annotation.SdkConstant.SdkConstantType;
Roopa Sattirajuffab9952021-08-20 15:06:57 -070027import android.annotation.SuppressLint;
28import android.annotation.SystemApi;
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060029import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
30import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
31import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
Artur Satayev3625be42019-12-10 17:47:52 +000032import android.compat.annotation.UnsupportedAppUsage;
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -060033import android.content.AttributionSource;
The Android Open Source Project33897762009-03-03 19:31:44 -080034import android.content.Context;
Mathew Inwood049f0f52020-11-04 09:29:36 +000035import android.os.Build;
The Android Open Source Project33897762009-03-03 19:31:44 -080036import android.os.IBinder;
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -070037import android.os.RemoteException;
The Android Open Source Project33897762009-03-03 19:31:44 -080038import android.util.Log;
39
William Escande9a4b7c12021-12-16 16:07:55 +010040import com.android.modules.utils.SynchronousResultReceiver;
41
Rahul Sabnis5ed1b842021-12-23 11:36:40 -080042import java.lang.annotation.Retention;
43import java.lang.annotation.RetentionPolicy;
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -070044import java.util.ArrayList;
45import java.util.List;
Rahul Sabnis9a5e3512022-03-17 23:37:38 -070046import java.util.Objects;
William Escande9a4b7c12021-12-16 16:07:55 +010047import java.util.concurrent.TimeoutException;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070048
The Android Open Source Project33897762009-03-03 19:31:44 -080049/**
The Android Open Source Project33897762009-03-03 19:31:44 -080050 * Public API for controlling the Bluetooth Headset Service. This includes both
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070051 * Bluetooth Headset and Handsfree (v1.5) profiles.
The Android Open Source Project33897762009-03-03 19:31:44 -080052 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070053 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
The Android Open Source Project33897762009-03-03 19:31:44 -080054 * Service via IPC.
55 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070056 * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
57 * the BluetoothHeadset proxy object. Use
58 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
The Android Open Source Project33897762009-03-03 19:31:44 -080059 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070060 * <p> Android only supports one connected Bluetooth Headset at a time.
61 * Each method is protected with its appropriate permission.
The Android Open Source Project33897762009-03-03 19:31:44 -080062 */
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070063public final class BluetoothHeadset implements BluetoothProfile {
The Android Open Source Project33897762009-03-03 19:31:44 -080064 private static final String TAG = "BluetoothHeadset";
William Escande60874f82022-10-27 22:51:52 -070065 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Matthew Xief8035a72012-10-09 22:10:37 -070066 private static final boolean VDBG = false;
The Android Open Source Project33897762009-03-03 19:31:44 -080067
Nick Pellydac4c0d2009-09-10 10:21:56 -070068 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070069 * Intent used to broadcast the change in connection state of the Headset
70 * profile.
71 *
72 * <p>This intent will have 3 extras:
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080073 * <ul>
Jack He910201b2017-08-22 16:06:54 -070074 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
75 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
76 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080077 * </ul>
Jaikumar Ganeshaa0427e2011-01-26 11:46:56 -080078 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070079 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
80 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070081 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060082 @RequiresLegacyBluetoothPermission
83 @RequiresBluetoothConnectPermission
84 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070085 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
86 public static final String ACTION_CONNECTION_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -070087 "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070088
89 /**
90 * Intent used to broadcast the change in the Audio Connection state of the
Jakub Pawlowski9965bf72021-01-29 09:20:44 +010091 * HFP profile.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070092 *
93 * <p>This intent will have 3 extras:
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080094 * <ul>
Jack He910201b2017-08-22 16:06:54 -070095 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
96 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
97 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080098 * </ul>
Jaikumar Ganeshaa0427e2011-01-26 11:46:56 -080099 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700100 * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
Nick Pellydac4c0d2009-09-10 10:21:56 -0700101 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600102 @RequiresLegacyBluetoothPermission
103 @RequiresBluetoothConnectPermission
104 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Nick Pellydac4c0d2009-09-10 10:21:56 -0700105 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
106 public static final String ACTION_AUDIO_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -0700107 "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
Nick Pellydac4c0d2009-09-10 10:21:56 -0700108
Jack He889d2342018-01-03 12:13:26 -0800109 /**
110 * Intent used to broadcast the selection of a connected device as active.
111 *
112 * <p>This intent will have one extra:
113 * <ul>
114 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
115 * be null if no device is active. </li>
116 * </ul>
117 *
Jack He889d2342018-01-03 12:13:26 -0800118 * @hide
119 */
William Escande5d864a42022-01-27 13:42:05 +0100120 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600121 @RequiresLegacyBluetoothPermission
122 @RequiresBluetoothConnectPermission
123 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He889d2342018-01-03 12:13:26 -0800124 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
William Escande5d864a42022-01-27 13:42:05 +0100125 @SuppressLint("ActionValue")
Jack He889d2342018-01-03 12:13:26 -0800126 public static final String ACTION_ACTIVE_DEVICE_CHANGED =
127 "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
Jaikumar Ganeshf48cda52010-04-02 14:44:43 -0700128
Nick Pellydac4c0d2009-09-10 10:21:56 -0700129 /**
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700130 * Intent used to broadcast that the headset has posted a
131 * vendor-specific event.
132 *
133 * <p>This intent will have 4 extras and 1 category.
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800134 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700135 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
136 * </li>
137 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
138 * specific command </li>
139 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
140 * command type which can be one of {@link #AT_CMD_TYPE_READ},
141 * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
142 * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
143 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
144 * arguments. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800145 * </ul>
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700146 *
Jack He910201b2017-08-22 16:06:54 -0700147 * <p> The category is the Company ID of the vendor defining the
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700148 * vendor-specific command. {@link BluetoothAssignedNumbers}
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700149 *
150 * For example, for Plantronics specific events
151 * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
152 *
153 * <p> For example, an AT+XEVENT=foo,3 will get translated into
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800154 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700155 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
156 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
157 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800158 * </ul>
Herb Jellinekaad41c52010-08-10 13:17:43 -0700159 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600160 @RequiresLegacyBluetoothPermission
161 @RequiresBluetoothConnectPermission
162 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Herb Jellinekaad41c52010-08-10 13:17:43 -0700163 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
164 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
165 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
166
167 /**
168 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
169 * intents that contains the name of the vendor-specific command.
170 */
171 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
172 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
173
174 /**
175 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700176 * intents that contains the AT command type of the vendor-specific command.
Herb Jellinekaad41c52010-08-10 13:17:43 -0700177 */
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700178 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
179 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
180
181 /**
182 * AT command type READ used with
183 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
184 * For example, AT+VGM?. There are no arguments for this command type.
185 */
186 public static final int AT_CMD_TYPE_READ = 0;
187
188 /**
189 * AT command type TEST used with
190 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
191 * For example, AT+VGM=?. There are no arguments for this command type.
192 */
193 public static final int AT_CMD_TYPE_TEST = 1;
194
195 /**
196 * AT command type SET used with
197 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
198 * For example, AT+VGM=<args>.
199 */
200 public static final int AT_CMD_TYPE_SET = 2;
201
202 /**
203 * AT command type BASIC used with
204 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
205 * For example, ATD. Single character commands and everything following the
206 * character are arguments.
207 */
208 public static final int AT_CMD_TYPE_BASIC = 3;
209
210 /**
211 * AT command type ACTION used with
212 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
213 * For example, AT+CHUP. There are no arguments for action commands.
214 */
215 public static final int AT_CMD_TYPE_ACTION = 4;
Herb Jellinekaad41c52010-08-10 13:17:43 -0700216
217 /**
218 * A Parcelable String array extra field in
219 * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
220 * the arguments to the vendor-specific command.
221 */
222 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
223 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
224
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700225 /**
226 * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
227 * for the companyId
228 */
Jack He910201b2017-08-22 16:06:54 -0700229 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY =
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700230 "android.bluetooth.headset.intent.category.companyid";
231
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700232 /**
Edward Jee7c81f1f2013-08-16 04:07:49 -0700233 * A vendor-specific command for unsolicited result code.
234 */
235 public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
236
237 /**
Jack He7f9c70b2017-06-20 17:07:40 -0700238 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700239 *
Jack He7f9c70b2017-06-20 17:07:40 -0700240 * @hide
241 */
Jack He0dfd69b2017-06-20 17:09:47 -0700242 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL";
243
244 /**
245 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700246 *
Jack He0dfd69b2017-06-20 17:09:47 -0700247 * @hide
248 */
249 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV";
250
251 /**
252 * Battery level indicator associated with
253 * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV}
Jack He910201b2017-08-22 16:06:54 -0700254 *
Jack He0dfd69b2017-06-20 17:09:47 -0700255 * @hide
256 */
257 public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1;
258
259 /**
260 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700261 *
Jack He0dfd69b2017-06-20 17:09:47 -0700262 * @hide
263 */
Jack He7f9c70b2017-06-20 17:07:40 -0700264 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT";
265
266 /**
267 * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT}
Jack He910201b2017-08-22 16:06:54 -0700268 *
Jack He7f9c70b2017-06-20 17:07:40 -0700269 * @hide
270 */
271 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY";
272
273 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800274 * Headset state when SCO audio is not connected.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700275 * This state can be one of
276 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
277 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
278 */
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800279 public static final int STATE_AUDIO_DISCONNECTED = 10;
Herb Jellinekaad41c52010-08-10 13:17:43 -0700280
281 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800282 * Headset state when SCO audio is connecting.
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700283 * This state can be one of
284 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
285 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700286 */
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800287 public static final int STATE_AUDIO_CONNECTING = 11;
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700288
289 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800290 * Headset state when SCO audio is connected.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700291 * This state can be one of
292 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
293 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Nick Pellydac4c0d2009-09-10 10:21:56 -0700294 */
Chienyuan0d3bada2019-05-29 10:29:30 +0800295 public static final int STATE_AUDIO_CONNECTED = 12;
Mudumba Ananth3246de62016-02-29 02:14:36 -0800296
297 /**
298 * Intent used to broadcast the headset's indicator status
299 *
300 * <p>This intent will have 3 extras:
301 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700302 * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which
303 * is supported by the headset ( as indicated by AT+BIND command in the SLC
304 * sequence) or whose value is changed (indicated by AT+BIEV command) </li>
305 * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li>
306 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li>
Mudumba Ananth3246de62016-02-29 02:14:36 -0800307 * </ul>
Jack He48f6a8a2017-06-22 12:56:54 -0700308 * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators
Jack He910201b2017-08-22 16:06:54 -0700309 * are given an assigned number. Below shows the assigned number of Indicator added so far
Jack He48f6a8a2017-06-22 12:56:54 -0700310 * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled
311 * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery
Jack He910201b2017-08-22 16:06:54 -0700312 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800313 * @hide
314 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600315 @RequiresLegacyBluetoothPermission
316 @RequiresBluetoothConnectPermission
317 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jeff Sharkeyd7c55662021-04-20 12:30:37 -0600318 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Mudumba Ananth3246de62016-02-29 02:14:36 -0800319 public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
320 "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
321
322 /**
Jack He48f6a8a2017-06-22 12:56:54 -0700323 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
324 * intents that contains the assigned number of the headset indicator as defined by
325 * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7
Jack He910201b2017-08-22 16:06:54 -0700326 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800327 * @hide
328 */
329 public static final String EXTRA_HF_INDICATORS_IND_ID =
330 "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
331
332 /**
Jack He48f6a8a2017-06-22 12:56:54 -0700333 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
Mudumba Ananth3246de62016-02-29 02:14:36 -0800334 * intents that contains the value of the Headset indicator that is being sent.
Jack He910201b2017-08-22 16:06:54 -0700335 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800336 * @hide
337 */
338 public static final String EXTRA_HF_INDICATORS_IND_VALUE =
339 "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
340
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600341 private final BluetoothAdapter mAdapter;
342 private final AttributionSource mAttributionSource;
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800343 private final BluetoothProfileConnector<IBluetoothHeadset> mProfileConnector =
344 new BluetoothProfileConnector(this, BluetoothProfile.HEADSET, "BluetoothHeadset",
345 IBluetoothHeadset.class.getName()) {
346 @Override
347 public IBluetoothHeadset getServiceInterface(IBinder service) {
348 return IBluetoothHeadset.Stub.asInterface(service);
fredc3c719642012-04-12 00:02:00 -0700349 }
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800350 };
fredc3c719642012-04-12 00:02:00 -0700351
The Android Open Source Project33897762009-03-03 19:31:44 -0800352 /**
353 * Create a BluetoothHeadset proxy object.
354 */
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800355 /* package */ BluetoothHeadset(Context context, ServiceListener listener,
356 BluetoothAdapter adapter) {
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600357 mAdapter = adapter;
358 mAttributionSource = adapter.getAttributionSource();
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800359 mProfileConnector.connect(context, listener);
The Android Open Source Project33897762009-03-03 19:31:44 -0800360 }
361
The Android Open Source Project33897762009-03-03 19:31:44 -0800362 /**
Santiago Seifertefe40712023-01-06 13:54:17 +0000363 * Close the connection to the backing service. Other public functions of BluetoothHeadset will
364 * return default error results once close() has been called. Multiple invocations of close()
The Android Open Source Project33897762009-03-03 19:31:44 -0800365 * are ok.
Santiago Seifertefe40712023-01-06 13:54:17 +0000366 *
367 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800368 */
Mathew Inwood7d543892018-08-01 15:07:20 +0100369 @UnsupportedAppUsage
Santiago Seifertefe40712023-01-06 13:54:17 +0000370 public void close() {
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800371 mProfileConnector.disconnect();
372 }
fredc3c719642012-04-12 00:02:00 -0700373
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800374 private IBluetoothHeadset getService() {
375 return mProfileConnector.getService();
Jeff Sharkeydb38eb72021-05-24 10:00:23 -0600376 }
377
378 /** {@hide} */
379 @Override
380 protected void finalize() throws Throwable {
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800381 // The empty finalize needs to be kept or the
382 // cts signature tests would fail.
The Android Open Source Project33897762009-03-03 19:31:44 -0800383 }
384
385 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700386 * Initiate connection to a profile of the remote bluetooth device.
387 *
388 * <p> Currently, the system supports only 1 connection to the
389 * headset/handsfree profile. The API will automatically disconnect connected
390 * devices before connecting.
391 *
392 * <p> This API returns false in scenarios like the profile on the
393 * device is already connected or Bluetooth is not turned on.
394 * When this API returns true, it is guaranteed that
395 * connection state intent for the profile will be broadcasted with
396 * the state. Users can get the connection state of the profile
397 * from this intent.
398 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700399 * @param device Remote Bluetooth Device
Jack He910201b2017-08-22 16:06:54 -0700400 * @return false on immediate error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700401 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800402 */
Selim Guruna117cb372017-10-17 17:01:38 -0700403 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600404 @RequiresLegacyBluetoothAdminPermission
405 @RequiresBluetoothConnectPermission
406 @RequiresPermission(allOf = {
407 android.Manifest.permission.BLUETOOTH_CONNECT,
408 android.Manifest.permission.MODIFY_PHONE_STATE,
409 })
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700410 public boolean connect(BluetoothDevice device) {
411 if (DBG) log("connect(" + device + ")");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800412 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100413 final boolean defaultValue = false;
414 if (service == null) {
415 Log.w(TAG, "Proxy not attached to service");
416 if (DBG) log(Log.getStackTraceString(new Throwable()));
417 } else if (isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800418 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700419 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande4644ccd2022-08-24 18:07:07 -0700420 service.connect(device, mAttributionSource, recv);
William Escande9a4b7c12021-12-16 16:07:55 +0100421 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
422 } catch (RemoteException | TimeoutException e) {
423 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700424 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800425 }
William Escande9a4b7c12021-12-16 16:07:55 +0100426 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800427 }
428
429 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700430 * Initiate disconnection from a profile
431 *
432 * <p> This API will return false in scenarios like the profile on the
433 * Bluetooth device is not in connected state etc. When this API returns,
434 * true, it is guaranteed that the connection state change
435 * intent will be broadcasted with the state. Users can get the
436 * disconnection state of the profile from this intent.
437 *
438 * <p> If the disconnection is initiated by a remote device, the state
439 * will transition from {@link #STATE_CONNECTED} to
440 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
441 * host (local) device the state will transition from
442 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
443 * state {@link #STATE_DISCONNECTED}. The transition to
444 * {@link #STATE_DISCONNECTING} can be used to distinguish between the
445 * two scenarios.
446 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700447 * @param device Remote Bluetooth Device
Jack He910201b2017-08-22 16:06:54 -0700448 * @return false on immediate error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700449 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800450 */
Selim Guruna117cb372017-10-17 17:01:38 -0700451 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600452 @RequiresLegacyBluetoothAdminPermission
453 @RequiresBluetoothConnectPermission
454 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700455 public boolean disconnect(BluetoothDevice device) {
456 if (DBG) log("disconnect(" + device + ")");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800457 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100458 final boolean defaultValue = false;
459 if (service == null) {
460 Log.w(TAG, "Proxy not attached to service");
461 if (DBG) log(Log.getStackTraceString(new Throwable()));
462 } else if (isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800463 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700464 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande4644ccd2022-08-24 18:07:07 -0700465 service.disconnect(device, mAttributionSource, recv);
William Escande9a4b7c12021-12-16 16:07:55 +0100466 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
467 } catch (RemoteException | TimeoutException e) {
468 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700469 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800470 }
William Escande9a4b7c12021-12-16 16:07:55 +0100471 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800472 }
473
474 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700475 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800476 */
Jack He9e045d22017-08-22 21:21:23 -0700477 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600478 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600479 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700480 public List<BluetoothDevice> getConnectedDevices() {
Matthew Xief8035a72012-10-09 22:10:37 -0700481 if (VDBG) log("getConnectedDevices()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800482 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100483 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
484 if (service == null) {
485 Log.w(TAG, "Proxy not attached to service");
486 if (DBG) log(Log.getStackTraceString(new Throwable()));
487 } else if (isEnabled()) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800488 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100489 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700490 SynchronousResultReceiver.get();
William Escande4644ccd2022-08-24 18:07:07 -0700491 service.getConnectedDevices(mAttributionSource, recv);
Jeff Sharkey98f30442021-06-03 09:26:53 -0600492 return Attributable.setAttributionSource(
William Escande9a4b7c12021-12-16 16:07:55 +0100493 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
Jeff Sharkey98f30442021-06-03 09:26:53 -0600494 mAttributionSource);
William Escande9a4b7c12021-12-16 16:07:55 +0100495 } catch (RemoteException | TimeoutException e) {
496 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700497 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800498 }
William Escande9a4b7c12021-12-16 16:07:55 +0100499 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800500 }
501
502 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700503 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800504 */
Jack He9e045d22017-08-22 21:21:23 -0700505 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600506 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600507 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700508 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Matthew Xief8035a72012-10-09 22:10:37 -0700509 if (VDBG) log("getDevicesMatchingStates()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800510 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100511 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
512 if (service == null) {
513 Log.w(TAG, "Proxy not attached to service");
514 if (DBG) log(Log.getStackTraceString(new Throwable()));
515 } else if (isEnabled()) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800516 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100517 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700518 SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100519 service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
Jeff Sharkey98f30442021-06-03 09:26:53 -0600520 return Attributable.setAttributionSource(
William Escande9a4b7c12021-12-16 16:07:55 +0100521 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600522 mAttributionSource);
William Escande9a4b7c12021-12-16 16:07:55 +0100523 } catch (RemoteException | TimeoutException e) {
524 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700525 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800526 }
William Escande9a4b7c12021-12-16 16:07:55 +0100527 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800528 }
529
530 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700531 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800532 */
Jack He9e045d22017-08-22 21:21:23 -0700533 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600534 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600535 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700536 public int getConnectionState(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700537 if (VDBG) log("getConnectionState(" + device + ")");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800538 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100539 final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
540 if (service == null) {
541 Log.w(TAG, "Proxy not attached to service");
542 if (DBG) log(Log.getStackTraceString(new Throwable()));
543 } else if (isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800544 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700545 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
William Escande4644ccd2022-08-24 18:07:07 -0700546 service.getConnectionState(device, mAttributionSource, recv);
William Escande9a4b7c12021-12-16 16:07:55 +0100547 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
548 } catch (RemoteException | TimeoutException e) {
549 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700550 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800551 }
William Escande9a4b7c12021-12-16 16:07:55 +0100552 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800553 }
554
555 /**
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800556 * Set connection policy of the profile
557 *
558 * <p> The device should already be paired.
559 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
560 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
561 *
562 * @param device Paired bluetooth device
563 * @param connectionPolicy is the connection policy to set to for this profile
564 * @return true if connectionPolicy is set, false on error
565 * @hide
566 */
567 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600568 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600569 @RequiresPermission(allOf = {
570 android.Manifest.permission.BLUETOOTH_CONNECT,
571 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
572 android.Manifest.permission.MODIFY_PHONE_STATE,
573 })
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800574 public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
575 @ConnectionPolicy int connectionPolicy) {
576 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800577 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100578 final boolean defaultValue = false;
579 if (service == null) {
580 Log.w(TAG, "Proxy not attached to service");
581 if (DBG) log(Log.getStackTraceString(new Throwable()));
582 } else if (isEnabled() && isValidDevice(device)
583 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
584 || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800585 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700586 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100587 service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
588 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
589 } catch (RemoteException | TimeoutException e) {
590 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700591 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800592 }
William Escande9a4b7c12021-12-16 16:07:55 +0100593 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800594 }
595
596 /**
Felix Stern692d36d2023-09-08 09:55:56 +0000597 * Get the priority of the profile.
598 *
599 * <p> The priority can be any of:
600 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
601 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
602 *
603 * @param device Bluetooth device
604 * @return priority of the device
605 * @hide
606 */
607 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
608 @RequiresLegacyBluetoothPermission
609 @RequiresBluetoothConnectPermission
610 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
611 public int getPriority(BluetoothDevice device) {
612 if (VDBG) log("getPriority(" + device + ")");
613 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
614 }
615
616 /**
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800617 * Get the connection policy of the profile.
618 *
619 * <p> The connection policy can be any of:
620 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
621 * {@link #CONNECTION_POLICY_UNKNOWN}
622 *
623 * @param device Bluetooth device
624 * @return connection policy of the device
625 * @hide
626 */
627 @SystemApi
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600628 @RequiresBluetoothConnectPermission
629 @RequiresPermission(allOf = {
630 android.Manifest.permission.BLUETOOTH_CONNECT,
631 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
632 })
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800633 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
634 if (VDBG) log("getConnectionPolicy(" + device + ")");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800635 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100636 final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
637 if (service == null) {
638 Log.w(TAG, "Proxy not attached to service");
639 if (DBG) log(Log.getStackTraceString(new Throwable()));
640 } else if (isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800641 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700642 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100643 service.getConnectionPolicy(device, mAttributionSource, recv);
644 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
645 } catch (RemoteException | TimeoutException e) {
646 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700647 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800648 }
William Escande9a4b7c12021-12-16 16:07:55 +0100649 return defaultValue;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700650 }
651
652 /**
Rahul Sabnisc611e732020-12-14 10:54:45 -0800653 * Checks whether the headset supports some form of noise reduction
654 *
655 * @param device Bluetooth device
656 * @return true if echo cancellation and/or noise reduction is supported, false otherwise
657 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600658 @RequiresLegacyBluetoothPermission
659 @RequiresBluetoothConnectPermission
660 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Rahul Sabnisc611e732020-12-14 10:54:45 -0800661 public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) {
662 if (DBG) log("isNoiseReductionSupported()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800663 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100664 final boolean defaultValue = false;
665 if (service == null) {
666 Log.w(TAG, "Proxy not attached to service");
667 if (DBG) log(Log.getStackTraceString(new Throwable()));
668 } else if (isEnabled() && isValidDevice(device)) {
Rahul Sabnisc611e732020-12-14 10:54:45 -0800669 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700670 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100671 service.isNoiseReductionSupported(device, mAttributionSource, recv);
672 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
673 } catch (RemoteException | TimeoutException e) {
674 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Rahul Sabnisc611e732020-12-14 10:54:45 -0800675 }
676 }
William Escande9a4b7c12021-12-16 16:07:55 +0100677 return defaultValue;
Rahul Sabnisc611e732020-12-14 10:54:45 -0800678 }
679
680 /**
681 * Checks whether the headset supports voice recognition
682 *
683 * @param device Bluetooth device
684 * @return true if voice recognition is supported, false otherwise
685 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600686 @RequiresLegacyBluetoothPermission
687 @RequiresBluetoothConnectPermission
688 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Rahul Sabnisc611e732020-12-14 10:54:45 -0800689 public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
690 if (DBG) log("isVoiceRecognitionSupported()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800691 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100692 final boolean defaultValue = false;
693 if (service == null) {
694 Log.w(TAG, "Proxy not attached to service");
695 if (DBG) log(Log.getStackTraceString(new Throwable()));
696 } else if (isEnabled() && isValidDevice(device)) {
Rahul Sabnisc611e732020-12-14 10:54:45 -0800697 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700698 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100699 service.isVoiceRecognitionSupported(device, mAttributionSource, recv);
700 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
701 } catch (RemoteException | TimeoutException e) {
702 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Rahul Sabnisc611e732020-12-14 10:54:45 -0800703 }
704 }
William Escande9a4b7c12021-12-16 16:07:55 +0100705 return defaultValue;
Rahul Sabnisc611e732020-12-14 10:54:45 -0800706 }
707
708 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700709 * Start Bluetooth voice recognition. This methods sends the voice
710 * recognition AT command to the headset and establishes the
711 * audio connection.
712 *
713 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800714 * If this function returns true, this intent will be broadcasted with
715 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700716 *
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800717 * <p> {@link #EXTRA_STATE} will transition from
718 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
719 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
720 * in case of failure to establish the audio connection.
721 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700722 * @param device Bluetooth headset
Jack Hec46a01e2018-05-02 19:10:56 -0700723 * @return false if there is no headset connected, or the connected headset doesn't support
724 * voice recognition, or voice recognition is already started, or audio channel is occupied,
725 * or on error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700726 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600727 @RequiresLegacyBluetoothPermission
728 @RequiresBluetoothConnectPermission
729 @RequiresPermission(allOf = {
730 android.Manifest.permission.BLUETOOTH_CONNECT,
731 android.Manifest.permission.MODIFY_PHONE_STATE,
732 })
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700733 public boolean startVoiceRecognition(BluetoothDevice device) {
734 if (DBG) log("startVoiceRecognition()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800735 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100736 final boolean defaultValue = false;
737 if (service == null) {
738 Log.w(TAG, "Proxy not attached to service");
739 if (DBG) log(Log.getStackTraceString(new Throwable()));
740 } else if (isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700741 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700742 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100743 service.startVoiceRecognition(device, mAttributionSource, recv);
744 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
745 } catch (RemoteException | TimeoutException e) {
746 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700747 }
748 }
William Escande9a4b7c12021-12-16 16:07:55 +0100749 return defaultValue;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700750 }
751
752 /**
753 * Stop Bluetooth Voice Recognition mode, and shut down the
754 * Bluetooth audio path.
755 *
Jack Hec46a01e2018-05-02 19:10:56 -0700756 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
757 * If this function returns true, this intent will be broadcasted with
758 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
759 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700760 * @param device Bluetooth headset
Jack Hec46a01e2018-05-02 19:10:56 -0700761 * @return false if there is no headset connected, or voice recognition has not started,
762 * or voice recognition has ended on this headset, or on error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700763 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600764 @RequiresLegacyBluetoothPermission
765 @RequiresBluetoothConnectPermission
766 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700767 public boolean stopVoiceRecognition(BluetoothDevice device) {
768 if (DBG) log("stopVoiceRecognition()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800769 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100770 final boolean defaultValue = false;
771 if (service == null) {
772 Log.w(TAG, "Proxy not attached to service");
773 if (DBG) log(Log.getStackTraceString(new Throwable()));
774 } else if (isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700775 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700776 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100777 service.stopVoiceRecognition(device, mAttributionSource, recv);
778 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
779 } catch (RemoteException | TimeoutException e) {
780 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700781 }
782 }
William Escande9a4b7c12021-12-16 16:07:55 +0100783 return defaultValue;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700784 }
785
786 /**
787 * Check if Bluetooth SCO audio is connected.
788 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700789 * @param device Bluetooth headset
Jack He910201b2017-08-22 16:06:54 -0700790 * @return true if SCO is connected, false otherwise or on error
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700791 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600792 @RequiresLegacyBluetoothPermission
793 @RequiresBluetoothConnectPermission
794 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700795 public boolean isAudioConnected(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700796 if (VDBG) log("isAudioConnected()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800797 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100798 final boolean defaultValue = false;
799 if (service == null) {
800 Log.w(TAG, "Proxy not attached to service");
801 if (DBG) log(Log.getStackTraceString(new Throwable()));
802 } else if (isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700803 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700804 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100805 service.isAudioConnected(device, mAttributionSource, recv);
806 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
807 } catch (RemoteException | TimeoutException e) {
808 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700809 }
810 }
William Escande9a4b7c12021-12-16 16:07:55 +0100811 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800812 }
813
Eric Laurent2e66fa22010-03-17 14:59:27 -0700814
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800815 /** @hide */
816 @Retention(RetentionPolicy.SOURCE)
817 @IntDef(value = {
818 BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
819 BluetoothHeadset.STATE_AUDIO_CONNECTING,
820 BluetoothHeadset.STATE_AUDIO_CONNECTED,
821 BluetoothStatusCodes.ERROR_TIMEOUT
822 })
823 public @interface GetAudioStateReturnValues {}
824
Jaikumar Ganeshb84bbd92010-06-02 14:36:14 -0700825 /**
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700826 * Get the current audio state of the Headset.
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800827 *
828 * @param device is the Bluetooth device for which the audio state is being queried
829 * @return the audio state of the device or an error code
Rahul Sabnis9a5e3512022-03-17 23:37:38 -0700830 * @throws NullPointerException if the device is null
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700831 *
832 * @hide
833 */
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800834 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600835 @RequiresBluetoothConnectPermission
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800836 @RequiresPermission(allOf = {
837 android.Manifest.permission.BLUETOOTH_CONNECT,
838 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
839 })
840 public @GetAudioStateReturnValues int getAudioState(@NonNull BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700841 if (VDBG) log("getAudioState");
Rahul Sabnis9a5e3512022-03-17 23:37:38 -0700842 Objects.requireNonNull(device);
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800843 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100844 final int defaultValue = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
845 if (service == null) {
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700846 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +0100847 if (DBG) log(Log.getStackTraceString(new Throwable()));
848 } else if (!isDisabled()) {
849 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700850 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100851 service.getAudioState(device, mAttributionSource, recv);
852 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800853 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100854 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800855 throw e.rethrowFromSystemServer();
856 } catch (TimeoutException e) {
857 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
858 return BluetoothStatusCodes.ERROR_TIMEOUT;
William Escande9a4b7c12021-12-16 16:07:55 +0100859 }
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700860 }
William Escande9a4b7c12021-12-16 16:07:55 +0100861 return defaultValue;
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700862 }
863
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800864 /** @hide */
865 @Retention(RetentionPolicy.SOURCE)
866 @IntDef(value = {
867 BluetoothStatusCodes.SUCCESS,
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800868 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
869 BluetoothStatusCodes.ERROR_TIMEOUT,
Md Shahriar Hossain Sajib8b4df2f2022-02-18 13:56:27 +0800870 BluetoothStatusCodes.ERROR_UNKNOWN,
871 })
872 public @interface SetAudioRouteAllowedReturnValues {}
873
874 /** @hide */
875 @Retention(RetentionPolicy.SOURCE)
876 @IntDef(value = {
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800877 BluetoothStatusCodes.ALLOWED,
878 BluetoothStatusCodes.NOT_ALLOWED,
Md Shahriar Hossain Sajib8b4df2f2022-02-18 13:56:27 +0800879 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
880 BluetoothStatusCodes.ERROR_TIMEOUT,
881 BluetoothStatusCodes.ERROR_UNKNOWN,
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800882 })
Md Shahriar Hossain Sajib8b4df2f2022-02-18 13:56:27 +0800883 public @interface GetAudioRouteAllowedReturnValues {}
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800884
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -0700885 /**
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800886 * Sets whether audio routing is allowed. When set to {@code false}, the AG
887 * will not route any audio to the HF unless explicitly told to. This method
888 * should be used in cases where the SCO channel is shared between multiple
889 * profiles and must be delegated by a source knowledgeable.
Bryce Lee0e154a32015-11-16 08:55:52 -0800890 *
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800891 * @param allowed {@code true} if the profile can reroute audio,
892 * {@code false} otherwise.
893 * @return {@link BluetoothStatusCodes#SUCCESS} upon successful setting,
894 * otherwise an error code.
895 *
Bryce Lee0e154a32015-11-16 08:55:52 -0800896 * @hide
897 */
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800898 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600899 @RequiresBluetoothConnectPermission
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800900 @RequiresPermission(allOf = {
901 android.Manifest.permission.BLUETOOTH_CONNECT,
902 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
903 })
Md Shahriar Hossain Sajib8b4df2f2022-02-18 13:56:27 +0800904 public @SetAudioRouteAllowedReturnValues int setAudioRouteAllowed(boolean allowed) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800905 if (VDBG) log("setAudioRouteAllowed");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800906 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100907 if (service == null) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800908 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +0100909 if (DBG) log(Log.getStackTraceString(new Throwable()));
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800910 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700911 } else if (isEnabled()) {
912 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700913 final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700914 service.setAudioRouteAllowed(allowed, mAttributionSource, recv);
915 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
916 return BluetoothStatusCodes.SUCCESS;
917 } catch (TimeoutException e) {
918 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
919 return BluetoothStatusCodes.ERROR_TIMEOUT;
920 } catch (RemoteException e) {
921 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
922 e.rethrowFromSystemServer();
923 }
Bryce Lee0e154a32015-11-16 08:55:52 -0800924 }
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700925
926 Log.e(TAG, "setAudioRouteAllowed: Bluetooth disabled, but profile service still bound");
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800927 return BluetoothStatusCodes.ERROR_UNKNOWN;
Bryce Lee0e154a32015-11-16 08:55:52 -0800928 }
929
930 /**
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800931 * @return {@link BluetoothStatusCodes#ALLOWED} if audio routing is allowed,
932 * {@link BluetoothStatusCodes#NOT_ALLOWED} if audio routing is not allowed, or
933 * an error code if an error occurs.
934 * see {@link #setAudioRouteAllowed(boolean)}.
Bryce Lee0e154a32015-11-16 08:55:52 -0800935 *
936 * @hide
937 */
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800938 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600939 @RequiresBluetoothConnectPermission
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800940 @RequiresPermission(allOf = {
941 android.Manifest.permission.BLUETOOTH_CONNECT,
942 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
943 })
Md Shahriar Hossain Sajib8b4df2f2022-02-18 13:56:27 +0800944 public @GetAudioRouteAllowedReturnValues int getAudioRouteAllowed() {
Bryce Lee0e154a32015-11-16 08:55:52 -0800945 if (VDBG) log("getAudioRouteAllowed");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800946 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100947 if (service == null) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800948 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +0100949 if (DBG) log(Log.getStackTraceString(new Throwable()));
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800950 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700951 } else if (isEnabled()) {
952 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700953 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700954 service.getAudioRouteAllowed(mAttributionSource, recv);
955 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(false)
956 ? BluetoothStatusCodes.ALLOWED : BluetoothStatusCodes.NOT_ALLOWED;
957 } catch (TimeoutException e) {
958 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
959 return BluetoothStatusCodes.ERROR_TIMEOUT;
960 } catch (RemoteException e) {
961 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
962 e.rethrowFromSystemServer();
963 }
Bryce Lee0e154a32015-11-16 08:55:52 -0800964 }
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700965
966 Log.e(TAG, "getAudioRouteAllowed: Bluetooth disabled, but profile service still bound");
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800967 return BluetoothStatusCodes.ERROR_UNKNOWN;
Bryce Lee0e154a32015-11-16 08:55:52 -0800968 }
969
970 /**
Jack He798d7282017-05-09 17:16:01 -0700971 * Force SCO audio to be opened regardless any other restrictions
972 *
Jack He910201b2017-08-22 16:06:54 -0700973 * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio
974 * False to use SCO audio in normal manner
Jack He798d7282017-05-09 17:16:01 -0700975 * @hide
976 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600977 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600978 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He798d7282017-05-09 17:16:01 -0700979 public void setForceScoAudio(boolean forced) {
980 if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800981 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100982 if (service == null) {
Jack He798d7282017-05-09 17:16:01 -0700983 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +0100984 if (DBG) log(Log.getStackTraceString(new Throwable()));
985 } else if (isEnabled()) {
986 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700987 final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100988 service.setForceScoAudio(forced, mAttributionSource, recv);
989 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
990 } catch (RemoteException | TimeoutException e) {
991 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
992 }
Jack He798d7282017-05-09 17:16:01 -0700993 }
994 }
995
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800996 /** @hide */
997 @Retention(RetentionPolicy.SOURCE)
998 @IntDef(value = {
999 BluetoothStatusCodes.SUCCESS,
1000 BluetoothStatusCodes.ERROR_UNKNOWN,
1001 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
1002 BluetoothStatusCodes.ERROR_TIMEOUT,
1003 BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED,
1004 BluetoothStatusCodes.ERROR_NO_ACTIVE_DEVICES,
1005 BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE,
1006 BluetoothStatusCodes.ERROR_AUDIO_ROUTE_BLOCKED,
1007 BluetoothStatusCodes.ERROR_CALL_ACTIVE,
1008 BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED
1009 })
1010 public @interface ConnectAudioReturnValues {}
Matthew Xief3ee3512012-02-16 16:57:18 -08001011
1012 /**
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001013 * Initiates a connection of SCO audio to the current active HFP device. The active HFP device
1014 * can be identified with {@link BluetoothAdapter#getActiveDevices(int)}.
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001015 * <p>
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001016 * If this function returns {@link BluetoothStatusCodes#SUCCESS}, the intent
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001017 * {@link #ACTION_AUDIO_STATE_CHANGED} will be broadcasted twice. First with
1018 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. This will be followed by a
1019 * broadcast with {@link #EXTRA_STATE} set to either {@link #STATE_AUDIO_CONNECTED} if the audio
1020 * connection is established or {@link #STATE_AUDIO_DISCONNECTED} if there was a failure in
1021 * establishing the audio connection.
Matthew Xief3ee3512012-02-16 16:57:18 -08001022 *
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001023 * @return whether the connection was successfully initiated or an error code on failure
Matthew Xief3ee3512012-02-16 16:57:18 -08001024 * @hide
1025 */
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001026 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001027 @RequiresBluetoothConnectPermission
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001028 @RequiresPermission(allOf = {
1029 android.Manifest.permission.BLUETOOTH_CONNECT,
1030 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1031 })
1032 public @ConnectAudioReturnValues int connectAudio() {
William Escande9a4b7c12021-12-16 16:07:55 +01001033 if (VDBG) log("connectAudio()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001034 final IBluetoothHeadset service = getService();
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001035 final int defaultValue = BluetoothStatusCodes.ERROR_UNKNOWN;
William Escande9a4b7c12021-12-16 16:07:55 +01001036 if (service == null) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001037 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001038 if (DBG) log(Log.getStackTraceString(new Throwable()));
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001039 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
William Escande9a4b7c12021-12-16 16:07:55 +01001040 } else if (isEnabled()) {
1041 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -07001042 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +01001043 service.connectAudio(mAttributionSource, recv);
1044 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001045 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001046 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001047 throw e.rethrowFromSystemServer();
1048 } catch (TimeoutException e) {
1049 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1050 return BluetoothStatusCodes.ERROR_TIMEOUT;
William Escande9a4b7c12021-12-16 16:07:55 +01001051 }
Matthew Xief3ee3512012-02-16 16:57:18 -08001052 }
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -07001053
1054 Log.e(TAG, "connectAudio: Bluetooth disabled, but profile service still bound");
1055 return defaultValue;
Matthew Xief3ee3512012-02-16 16:57:18 -08001056 }
1057
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001058 /** @hide */
1059 @Retention(RetentionPolicy.SOURCE)
1060 @IntDef(value = {
1061 BluetoothStatusCodes.SUCCESS,
1062 BluetoothStatusCodes.ERROR_UNKNOWN,
1063 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
1064 BluetoothStatusCodes.ERROR_TIMEOUT,
1065 BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED,
1066 BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED
1067 })
1068 public @interface DisconnectAudioReturnValues {}
1069
Matthew Xief3ee3512012-02-16 16:57:18 -08001070 /**
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001071 * Initiates a disconnection of HFP SCO audio from actively connected devices. It also tears
1072 * down voice recognition or virtual voice call, if any exists.
Matthew Xief3ee3512012-02-16 16:57:18 -08001073 *
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001074 * <p> If this function returns {@link BluetoothStatusCodes#SUCCESS}, the intent
1075 * {@link #ACTION_AUDIO_STATE_CHANGED} will be broadcasted with {@link #EXTRA_STATE} set to
1076 * {@link #STATE_AUDIO_DISCONNECTED}.
Jack Hec46a01e2018-05-02 19:10:56 -07001077 *
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001078 * @return whether the disconnection was initiated successfully or an error code on failure
Matthew Xief3ee3512012-02-16 16:57:18 -08001079 * @hide
1080 */
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001081 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001082 @RequiresBluetoothConnectPermission
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001083 @RequiresPermission(allOf = {
1084 android.Manifest.permission.BLUETOOTH_CONNECT,
1085 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1086 })
1087 public @DisconnectAudioReturnValues int disconnectAudio() {
William Escande9a4b7c12021-12-16 16:07:55 +01001088 if (VDBG) log("disconnectAudio()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001089 final IBluetoothHeadset service = getService();
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001090 final int defaultValue = BluetoothStatusCodes.ERROR_UNKNOWN;
William Escande9a4b7c12021-12-16 16:07:55 +01001091 if (service == null) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001092 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001093 if (DBG) log(Log.getStackTraceString(new Throwable()));
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001094 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
William Escande9a4b7c12021-12-16 16:07:55 +01001095 } else if (isEnabled()) {
1096 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -07001097 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +01001098 service.disconnectAudio(mAttributionSource, recv);
1099 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001100 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001101 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001102 throw e.rethrowFromSystemServer();
1103 } catch (TimeoutException e) {
1104 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1105 return BluetoothStatusCodes.ERROR_TIMEOUT;
William Escande9a4b7c12021-12-16 16:07:55 +01001106 }
Matthew Xief3ee3512012-02-16 16:57:18 -08001107 }
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -07001108
1109 Log.e(TAG, "disconnectAudio: Bluetooth disabled, but profile service still bound");
1110 return defaultValue;
Matthew Xief3ee3512012-02-16 16:57:18 -08001111 }
1112
1113 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001114 * Initiates a SCO channel connection as a virtual voice call to the current active device
1115 * Active handsfree device will be notified of incoming call and connected call.
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001116 *
Jack Hec46a01e2018-05-02 19:10:56 -07001117 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1118 * If this function returns true, this intent will be broadcasted with
1119 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
1120 *
1121 * <p> {@link #EXTRA_STATE} will transition from
1122 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
1123 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
1124 * in case of failure to establish the audio connection.
1125 *
1126 * @return true if successful, false if one of the following case applies
1127 * - SCO audio is not idle (connecting or connected)
1128 * - virtual call has already started
1129 * - there is no active device
1130 * - a Telecom managed call is going on
1131 * - binder is dead or Bluetooth is disabled or other error
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001132 * @hide
1133 */
Roopa Sattirajuffab9952021-08-20 15:06:57 -07001134 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001135 @RequiresLegacyBluetoothAdminPermission
1136 @RequiresBluetoothConnectPermission
1137 @RequiresPermission(allOf = {
1138 android.Manifest.permission.BLUETOOTH_CONNECT,
1139 android.Manifest.permission.MODIFY_PHONE_STATE,
Etienne Ruffieuxb5ae5d32022-02-25 14:08:27 +00001140 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001141 })
Jack Hec46a01e2018-05-02 19:10:56 -07001142 public boolean startScoUsingVirtualVoiceCall() {
Jaikumar Ganesheea6d262011-01-24 13:55:27 -08001143 if (DBG) log("startScoUsingVirtualVoiceCall()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001144 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001145 final boolean defaultValue = false;
1146 if (service == null) {
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001147 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001148 if (DBG) log(Log.getStackTraceString(new Throwable()));
1149 } else if (isEnabled()) {
1150 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -07001151 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +01001152 service.startScoUsingVirtualVoiceCall(mAttributionSource, recv);
1153 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1154 } catch (RemoteException | TimeoutException e) {
1155 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1156 }
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001157 }
William Escande9a4b7c12021-12-16 16:07:55 +01001158 return defaultValue;
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001159 }
1160
1161 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001162 * Terminates an ongoing SCO connection and the associated virtual call.
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001163 *
Jack Hec46a01e2018-05-02 19:10:56 -07001164 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1165 * If this function returns true, this intent will be broadcasted with
1166 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
1167 *
1168 * @return true if successful, false if one of the following case applies
1169 * - virtual voice call is not started or has ended
1170 * - binder is dead or Bluetooth is disabled or other error
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001171 * @hide
1172 */
Roopa Sattirajuffab9952021-08-20 15:06:57 -07001173 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001174 @RequiresLegacyBluetoothAdminPermission
1175 @RequiresBluetoothConnectPermission
1176 @RequiresPermission(allOf = {
1177 android.Manifest.permission.BLUETOOTH_CONNECT,
1178 android.Manifest.permission.MODIFY_PHONE_STATE,
Etienne Ruffieuxb5ae5d32022-02-25 14:08:27 +00001179 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001180 })
Jack Hec46a01e2018-05-02 19:10:56 -07001181 public boolean stopScoUsingVirtualVoiceCall() {
Jaikumar Ganesheea6d262011-01-24 13:55:27 -08001182 if (DBG) log("stopScoUsingVirtualVoiceCall()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001183 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001184 final boolean defaultValue = false;
1185 if (service == null) {
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001186 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001187 if (DBG) log(Log.getStackTraceString(new Throwable()));
1188 } else if (isEnabled()) {
1189 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -07001190 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +01001191 service.stopScoUsingVirtualVoiceCall(mAttributionSource, recv);
1192 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1193 } catch (RemoteException | TimeoutException e) {
1194 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1195 }
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001196 }
William Escande9a4b7c12021-12-16 16:07:55 +01001197 return defaultValue;
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001198 }
1199
Matthew Xief3ee3512012-02-16 16:57:18 -08001200 /**
1201 * Notify Headset of phone state change.
1202 * This is a backdoor for phone app to call BluetoothHeadset since
1203 * there is currently not a good way to get precise call state change outside
1204 * of phone app.
1205 *
1206 * @hide
1207 */
Mathew Inwood049f0f52020-11-04 09:29:36 +00001208 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001209 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001210 @RequiresPermission(allOf = {
1211 android.Manifest.permission.BLUETOOTH_CONNECT,
1212 android.Manifest.permission.MODIFY_PHONE_STATE,
1213 })
Matthew Xief3ee3512012-02-16 16:57:18 -08001214 public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
Benson Lied8d3392018-07-17 18:19:59 +08001215 int type, String name) {
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001216 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001217 if (service == null) {
1218 Log.w(TAG, "Proxy not attached to service");
1219 if (DBG) log(Log.getStackTraceString(new Throwable()));
1220 } else if (isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001221 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -06001222 service.phoneStateChanged(numActive, numHeld, callState, number, type, name,
1223 mAttributionSource);
William Escande9a4b7c12021-12-16 16:07:55 +01001224 } catch (RemoteException e) {
1225 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Matthew Xief3ee3512012-02-16 16:57:18 -08001226 }
Matthew Xief3ee3512012-02-16 16:57:18 -08001227 }
1228 }
1229
1230 /**
Matthew Xief3ee3512012-02-16 16:57:18 -08001231 * Send Headset of CLCC response
1232 *
1233 * @hide
1234 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001235 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001236 @RequiresPermission(allOf = {
1237 android.Manifest.permission.BLUETOOTH_CONNECT,
1238 android.Manifest.permission.MODIFY_PHONE_STATE,
1239 })
Matthew Xief3ee3512012-02-16 16:57:18 -08001240 public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
Jack He910201b2017-08-22 16:06:54 -07001241 String number, int type) {
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001242 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001243 if (service == null) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001244 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001245 if (DBG) log(Log.getStackTraceString(new Throwable()));
1246 } else if (isEnabled()) {
1247 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -07001248 final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +01001249 service.clccResponse(index, direction, status, mode, mpty, number, type,
1250 mAttributionSource, recv);
1251 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1252 } catch (RemoteException | TimeoutException e) {
1253 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1254 }
Matthew Xief3ee3512012-02-16 16:57:18 -08001255 }
1256 }
1257
Edward Jee7c81f1f2013-08-16 04:07:49 -07001258 /**
1259 * Sends a vendor-specific unsolicited result code to the headset.
1260 *
Jack He910201b2017-08-22 16:06:54 -07001261 * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code
1262 * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the
1263 * string <code>"+ANDROID: 0"</code> will be sent.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001264 *
Ying Wang29d93892013-08-26 17:48:22 -07001265 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001266 *
Edward Jee7c81f1f2013-08-16 04:07:49 -07001267 * @param device Bluetooth headset.
1268 * @param command A vendor-specific command.
1269 * @param arg The argument that will be attached to the command.
1270 * @return {@code false} if there is no headset connected, or if the command is not an allowed
Jack He910201b2017-08-22 16:06:54 -07001271 * vendor-specific unsolicited result code, or on error. {@code true} otherwise.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001272 * @throws IllegalArgumentException if {@code command} is {@code null}.
1273 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001274 @RequiresLegacyBluetoothPermission
1275 @RequiresBluetoothConnectPermission
1276 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Edward Jee7c81f1f2013-08-16 04:07:49 -07001277 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
1278 String arg) {
1279 if (DBG) {
1280 log("sendVendorSpecificResultCode()");
1281 }
1282 if (command == null) {
1283 throw new IllegalArgumentException("command is null");
1284 }
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001285 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001286 final boolean defaultValue = false;
Jack He1f686f62017-08-17 12:11:18 -07001287 if (service == null) {
Edward Jee7c81f1f2013-08-16 04:07:49 -07001288 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001289 if (DBG) log(Log.getStackTraceString(new Throwable()));
1290 } else if (isEnabled() && isValidDevice(device)) {
1291 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -07001292 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +01001293 service.sendVendorSpecificResultCode(device, command, arg,
1294 mAttributionSource, recv);
1295 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1296 } catch (RemoteException | TimeoutException e) {
1297 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1298 }
Edward Jee7c81f1f2013-08-16 04:07:49 -07001299 }
William Escande9a4b7c12021-12-16 16:07:55 +01001300 return defaultValue;
Edward Jee7c81f1f2013-08-16 04:07:49 -07001301 }
1302
Mudumba Ananth80bf6282014-04-27 13:11:00 -07001303 /**
Jack He889d2342018-01-03 12:13:26 -08001304 * Select a connected device as active.
1305 *
1306 * The active device selection is per profile. An active device's
1307 * purpose is profile-specific. For example, in HFP and HSP profiles,
1308 * it is the device used for phone call audio. If a remote device is not
1309 * connected, it cannot be selected as active.
1310 *
1311 * <p> This API returns false in scenarios like the profile on the
1312 * device is not connected or Bluetooth is not turned on.
1313 * When this API returns true, it is guaranteed that the
1314 * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
1315 * with the active device.
1316 *
Jack He889d2342018-01-03 12:13:26 -08001317 * @param device Remote Bluetooth Device, could be null if phone call audio should not be
1318 * streamed to a headset
1319 * @return false on immediate error, true otherwise
1320 * @hide
1321 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001322 @RequiresLegacyBluetoothAdminPermission
1323 @RequiresBluetoothConnectPermission
1324 @RequiresPermission(allOf = {
1325 android.Manifest.permission.BLUETOOTH_CONNECT,
1326 android.Manifest.permission.MODIFY_PHONE_STATE,
1327 })
Mathew Inwoodb426f902021-01-06 12:05:47 +00001328 @UnsupportedAppUsage(trackingBug = 171933273)
Jack He889d2342018-01-03 12:13:26 -08001329 public boolean setActiveDevice(@Nullable BluetoothDevice device) {
1330 if (DBG) {
1331 Log.d(TAG, "setActiveDevice: " + device);
1332 }
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001333 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001334 final boolean defaultValue = false;
Jack He889d2342018-01-03 12:13:26 -08001335 if (service == null) {
1336 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001337 if (DBG) log(Log.getStackTraceString(new Throwable()));
1338 } else if (isEnabled() && (device == null || isValidDevice(device))) {
1339 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -07001340 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +01001341 service.setActiveDevice(device, mAttributionSource, recv);
1342 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1343 } catch (RemoteException | TimeoutException e) {
1344 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1345 }
Jack He889d2342018-01-03 12:13:26 -08001346 }
William Escande9a4b7c12021-12-16 16:07:55 +01001347 return defaultValue;
Jack He889d2342018-01-03 12:13:26 -08001348 }
1349
1350 /**
1351 * Get the connected device that is active.
1352 *
Jack He889d2342018-01-03 12:13:26 -08001353 * @return the connected device that is active or null if no device
1354 * is active.
1355 * @hide
1356 */
Mathew Inwoodb426f902021-01-06 12:05:47 +00001357 @UnsupportedAppUsage(trackingBug = 171933273)
Rahul Sabnisd9798612019-12-04 14:21:10 -08001358 @Nullable
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001359 @RequiresLegacyBluetoothPermission
1360 @RequiresBluetoothConnectPermission
1361 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He889d2342018-01-03 12:13:26 -08001362 public BluetoothDevice getActiveDevice() {
William Escande9a4b7c12021-12-16 16:07:55 +01001363 if (VDBG) Log.d(TAG, "getActiveDevice");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001364 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001365 final BluetoothDevice defaultValue = null;
Jack He889d2342018-01-03 12:13:26 -08001366 if (service == null) {
1367 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001368 if (DBG) log(Log.getStackTraceString(new Throwable()));
1369 } else if (isEnabled()) {
1370 try {
1371 final SynchronousResultReceiver<BluetoothDevice> recv =
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -07001372 SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +01001373 service.getActiveDevice(mAttributionSource, recv);
1374 return Attributable.setAttributionSource(
1375 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
1376 mAttributionSource);
1377 } catch (RemoteException | TimeoutException e) {
1378 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1379 }
Jack He889d2342018-01-03 12:13:26 -08001380 }
William Escande9a4b7c12021-12-16 16:07:55 +01001381 return defaultValue;
Jack He889d2342018-01-03 12:13:26 -08001382 }
1383
1384 /**
Jack Hec5fde732018-01-05 17:17:06 -08001385 * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an
1386 * active connection.
Jack Hed8d204d2016-11-17 16:19:43 -08001387 *
Jack Hec5fde732018-01-05 17:17:06 -08001388 * @return true if in-band ringing is enabled, false if in-band ringing is disabled
1389 * @hide
1390 */
Roopa Sattirajuffab9952021-08-20 15:06:57 -07001391 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001392 @RequiresLegacyBluetoothPermission
1393 @RequiresBluetoothConnectPermission
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001394 @RequiresPermission(allOf = {
1395 android.Manifest.permission.BLUETOOTH_CONNECT,
1396 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1397 })
Jack Hec5fde732018-01-05 17:17:06 -08001398 public boolean isInbandRingingEnabled() {
William Escande9a4b7c12021-12-16 16:07:55 +01001399 if (DBG) log("isInbandRingingEnabled()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001400 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001401 final boolean defaultValue = false;
Jack Hec5fde732018-01-05 17:17:06 -08001402 if (service == null) {
1403 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001404 if (DBG) log(Log.getStackTraceString(new Throwable()));
1405 } else if (isEnabled()) {
1406 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -07001407 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +01001408 service.isInbandRingingEnabled(mAttributionSource, recv);
1409 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1410 } catch (RemoteException | TimeoutException e) {
1411 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1412 }
Jack Hec5fde732018-01-05 17:17:06 -08001413 }
William Escande9a4b7c12021-12-16 16:07:55 +01001414 return defaultValue;
Jack Hec5fde732018-01-05 17:17:06 -08001415 }
1416
Mathew Inwood7d543892018-08-01 15:07:20 +01001417 @UnsupportedAppUsage
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001418 private boolean isEnabled() {
Jack He1f686f62017-08-17 12:11:18 -07001419 return mAdapter.getState() == BluetoothAdapter.STATE_ON;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001420 }
1421
Jaikumar Ganesh2fbe8f42011-04-06 11:09:30 -07001422 private boolean isDisabled() {
Jack He1f686f62017-08-17 12:11:18 -07001423 return mAdapter.getState() == BluetoothAdapter.STATE_OFF;
Jaikumar Ganesh2fbe8f42011-04-06 11:09:30 -07001424 }
1425
Jack He1f686f62017-08-17 12:11:18 -07001426 private static boolean isValidDevice(BluetoothDevice device) {
1427 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001428 }
1429
The Android Open Source Project0047a0f2009-03-05 20:00:43 -08001430 private static void log(String msg) {
1431 Log.d(TAG, msg);
1432 }
The Android Open Source Project33897762009-03-03 19:31:44 -08001433}