blob: 91449df0f4b5a124b069352653625c083b20c4cf [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.ComponentName;
35import android.content.Context;
Jeff Sharkey19a8d092021-04-28 09:25:36 -060036import android.content.pm.PackageManager;
Mathew Inwood049f0f52020-11-04 09:29:36 +000037import android.os.Build;
Benjamin Franz3362fef2014-11-12 15:57:54 +000038import android.os.Handler;
The Android Open Source Project33897762009-03-03 19:31:44 -080039import android.os.IBinder;
Benjamin Franz3362fef2014-11-12 15:57:54 +000040import android.os.Looper;
41import android.os.Message;
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -070042import android.os.RemoteException;
Jeff Sharkeydb38eb72021-05-24 10:00:23 -060043import android.util.CloseGuard;
The Android Open Source Project33897762009-03-03 19:31:44 -080044import android.util.Log;
45
William Escande9a4b7c12021-12-16 16:07:55 +010046import com.android.modules.utils.SynchronousResultReceiver;
47
Rahul Sabnis5ed1b842021-12-23 11:36:40 -080048import java.lang.annotation.Retention;
49import java.lang.annotation.RetentionPolicy;
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -070050import java.util.ArrayList;
51import java.util.List;
William Escande9a4b7c12021-12-16 16:07:55 +010052import java.util.concurrent.TimeoutException;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070053
The Android Open Source Project33897762009-03-03 19:31:44 -080054/**
The Android Open Source Project33897762009-03-03 19:31:44 -080055 * Public API for controlling the Bluetooth Headset Service. This includes both
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070056 * Bluetooth Headset and Handsfree (v1.5) profiles.
The Android Open Source Project33897762009-03-03 19:31:44 -080057 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070058 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
The Android Open Source Project33897762009-03-03 19:31:44 -080059 * Service via IPC.
60 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070061 * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
62 * the BluetoothHeadset proxy object. Use
63 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
The Android Open Source Project33897762009-03-03 19:31:44 -080064 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070065 * <p> Android only supports one connected Bluetooth Headset at a time.
66 * Each method is protected with its appropriate permission.
The Android Open Source Project33897762009-03-03 19:31:44 -080067 */
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070068public final class BluetoothHeadset implements BluetoothProfile {
The Android Open Source Project33897762009-03-03 19:31:44 -080069 private static final String TAG = "BluetoothHeadset";
fredc3c719642012-04-12 00:02:00 -070070 private static final boolean DBG = true;
Matthew Xief8035a72012-10-09 22:10:37 -070071 private static final boolean VDBG = false;
The Android Open Source Project33897762009-03-03 19:31:44 -080072
Nick Pellydac4c0d2009-09-10 10:21:56 -070073 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070074 * Intent used to broadcast the change in connection state of the Headset
75 * profile.
76 *
77 * <p>This intent will have 3 extras:
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080078 * <ul>
Jack He910201b2017-08-22 16:06:54 -070079 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
80 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
81 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080082 * </ul>
Jaikumar Ganeshaa0427e2011-01-26 11:46:56 -080083 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070084 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
85 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070086 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060087 @RequiresLegacyBluetoothPermission
88 @RequiresBluetoothConnectPermission
89 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070090 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
91 public static final String ACTION_CONNECTION_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -070092 "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070093
94 /**
95 * Intent used to broadcast the change in the Audio Connection state of the
Jakub Pawlowski9965bf72021-01-29 09:20:44 +010096 * HFP profile.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070097 *
98 * <p>This intent will have 3 extras:
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080099 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700100 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
101 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
102 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800103 * </ul>
Jaikumar Ganeshaa0427e2011-01-26 11:46:56 -0800104 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700105 * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
Nick Pellydac4c0d2009-09-10 10:21:56 -0700106 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600107 @RequiresLegacyBluetoothPermission
108 @RequiresBluetoothConnectPermission
109 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Nick Pellydac4c0d2009-09-10 10:21:56 -0700110 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
111 public static final String ACTION_AUDIO_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -0700112 "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
Nick Pellydac4c0d2009-09-10 10:21:56 -0700113
Jack He889d2342018-01-03 12:13:26 -0800114 /**
115 * Intent used to broadcast the selection of a connected device as active.
116 *
117 * <p>This intent will have one extra:
118 * <ul>
119 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
120 * be null if no device is active. </li>
121 * </ul>
122 *
Jack He889d2342018-01-03 12:13:26 -0800123 * @hide
124 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600125 @RequiresLegacyBluetoothPermission
126 @RequiresBluetoothConnectPermission
127 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He889d2342018-01-03 12:13:26 -0800128 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Mathew Inwoodb426f902021-01-06 12:05:47 +0000129 @UnsupportedAppUsage(trackingBug = 171933273)
Jack He889d2342018-01-03 12:13:26 -0800130 public static final String ACTION_ACTIVE_DEVICE_CHANGED =
131 "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
Jaikumar Ganeshf48cda52010-04-02 14:44:43 -0700132
Nick Pellydac4c0d2009-09-10 10:21:56 -0700133 /**
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700134 * Intent used to broadcast that the headset has posted a
135 * vendor-specific event.
136 *
137 * <p>This intent will have 4 extras and 1 category.
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800138 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700139 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
140 * </li>
141 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
142 * specific command </li>
143 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
144 * command type which can be one of {@link #AT_CMD_TYPE_READ},
145 * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
146 * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
147 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
148 * arguments. </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800149 * </ul>
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700150 *
Jack He910201b2017-08-22 16:06:54 -0700151 * <p> The category is the Company ID of the vendor defining the
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700152 * vendor-specific command. {@link BluetoothAssignedNumbers}
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700153 *
154 * For example, for Plantronics specific events
155 * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
156 *
157 * <p> For example, an AT+XEVENT=foo,3 will get translated into
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800158 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700159 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
160 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
161 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800162 * </ul>
Herb Jellinekaad41c52010-08-10 13:17:43 -0700163 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600164 @RequiresLegacyBluetoothPermission
165 @RequiresBluetoothConnectPermission
166 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Herb Jellinekaad41c52010-08-10 13:17:43 -0700167 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
168 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
169 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
170
171 /**
172 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
173 * intents that contains the name of the vendor-specific command.
174 */
175 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
176 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
177
178 /**
179 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700180 * intents that contains the AT command type of the vendor-specific command.
Herb Jellinekaad41c52010-08-10 13:17:43 -0700181 */
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700182 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
183 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
184
185 /**
186 * AT command type READ 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_READ = 0;
191
192 /**
193 * AT command type TEST used with
194 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
195 * For example, AT+VGM=?. There are no arguments for this command type.
196 */
197 public static final int AT_CMD_TYPE_TEST = 1;
198
199 /**
200 * AT command type SET used with
201 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
202 * For example, AT+VGM=<args>.
203 */
204 public static final int AT_CMD_TYPE_SET = 2;
205
206 /**
207 * AT command type BASIC used with
208 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
209 * For example, ATD. Single character commands and everything following the
210 * character are arguments.
211 */
212 public static final int AT_CMD_TYPE_BASIC = 3;
213
214 /**
215 * AT command type ACTION used with
216 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
217 * For example, AT+CHUP. There are no arguments for action commands.
218 */
219 public static final int AT_CMD_TYPE_ACTION = 4;
Herb Jellinekaad41c52010-08-10 13:17:43 -0700220
221 /**
222 * A Parcelable String array extra field in
223 * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
224 * the arguments to the vendor-specific command.
225 */
226 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
227 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
228
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700229 /**
230 * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
231 * for the companyId
232 */
Jack He910201b2017-08-22 16:06:54 -0700233 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY =
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700234 "android.bluetooth.headset.intent.category.companyid";
235
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700236 /**
Edward Jee7c81f1f2013-08-16 04:07:49 -0700237 * A vendor-specific command for unsolicited result code.
238 */
239 public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
240
241 /**
Jack He7f9c70b2017-06-20 17:07:40 -0700242 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700243 *
Jack He7f9c70b2017-06-20 17:07:40 -0700244 * @hide
245 */
Jack He0dfd69b2017-06-20 17:09:47 -0700246 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL";
247
248 /**
249 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700250 *
Jack He0dfd69b2017-06-20 17:09:47 -0700251 * @hide
252 */
253 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV";
254
255 /**
256 * Battery level indicator associated with
257 * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV}
Jack He910201b2017-08-22 16:06:54 -0700258 *
Jack He0dfd69b2017-06-20 17:09:47 -0700259 * @hide
260 */
261 public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1;
262
263 /**
264 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700265 *
Jack He0dfd69b2017-06-20 17:09:47 -0700266 * @hide
267 */
Jack He7f9c70b2017-06-20 17:07:40 -0700268 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT";
269
270 /**
271 * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT}
Jack He910201b2017-08-22 16:06:54 -0700272 *
Jack He7f9c70b2017-06-20 17:07:40 -0700273 * @hide
274 */
275 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY";
276
277 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800278 * Headset state when SCO audio is not connected.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700279 * This state can be one of
280 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
281 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
282 */
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800283 public static final int STATE_AUDIO_DISCONNECTED = 10;
Herb Jellinekaad41c52010-08-10 13:17:43 -0700284
285 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800286 * Headset state when SCO audio is connecting.
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700287 * This state can be one of
288 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
289 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700290 */
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800291 public static final int STATE_AUDIO_CONNECTING = 11;
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700292
293 /**
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800294 * Headset state when SCO audio is connected.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700295 * This state can be one of
296 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
297 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Nick Pellydac4c0d2009-09-10 10:21:56 -0700298 */
Chienyuan0d3bada2019-05-29 10:29:30 +0800299 public static final int STATE_AUDIO_CONNECTED = 12;
Mudumba Ananth3246de62016-02-29 02:14:36 -0800300
301 /**
302 * Intent used to broadcast the headset's indicator status
303 *
304 * <p>This intent will have 3 extras:
305 * <ul>
Jack He910201b2017-08-22 16:06:54 -0700306 * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which
307 * is supported by the headset ( as indicated by AT+BIND command in the SLC
308 * sequence) or whose value is changed (indicated by AT+BIEV command) </li>
309 * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li>
310 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li>
Mudumba Ananth3246de62016-02-29 02:14:36 -0800311 * </ul>
Jack He48f6a8a2017-06-22 12:56:54 -0700312 * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators
Jack He910201b2017-08-22 16:06:54 -0700313 * are given an assigned number. Below shows the assigned number of Indicator added so far
Jack He48f6a8a2017-06-22 12:56:54 -0700314 * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled
315 * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery
Jack He910201b2017-08-22 16:06:54 -0700316 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800317 * @hide
318 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600319 @RequiresLegacyBluetoothPermission
320 @RequiresBluetoothConnectPermission
321 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jeff Sharkeyd7c55662021-04-20 12:30:37 -0600322 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Mudumba Ananth3246de62016-02-29 02:14:36 -0800323 public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
324 "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
325
326 /**
Jack He48f6a8a2017-06-22 12:56:54 -0700327 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
328 * intents that contains the assigned number of the headset indicator as defined by
329 * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7
Jack He910201b2017-08-22 16:06:54 -0700330 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800331 * @hide
332 */
333 public static final String EXTRA_HF_INDICATORS_IND_ID =
334 "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
335
336 /**
Jack He48f6a8a2017-06-22 12:56:54 -0700337 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
Mudumba Ananth3246de62016-02-29 02:14:36 -0800338 * intents that contains the value of the Headset indicator that is being sent.
Jack He910201b2017-08-22 16:06:54 -0700339 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800340 * @hide
341 */
342 public static final String EXTRA_HF_INDICATORS_IND_VALUE =
343 "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
344
Benjamin Franz3362fef2014-11-12 15:57:54 +0000345 private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
346 private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700347
Jeff Sharkeydb38eb72021-05-24 10:00:23 -0600348 private final CloseGuard mCloseGuard = new CloseGuard();
349
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700350 private Context mContext;
351 private ServiceListener mServiceListener;
Jack He1f686f62017-08-17 12:11:18 -0700352 private volatile IBluetoothHeadset mService;
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600353 private final BluetoothAdapter mAdapter;
354 private final AttributionSource mAttributionSource;
The Android Open Source Project33897762009-03-03 19:31:44 -0800355
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600356 @SuppressLint("AndroidFrameworkBluetoothPermission")
Jack He9e045d22017-08-22 21:21:23 -0700357 private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
fredc3c719642012-04-12 00:02:00 -0700358 new IBluetoothStateChangeCallback.Stub() {
359 public void onBluetoothStateChange(boolean up) {
360 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
361 if (!up) {
Benjamin Franz3362fef2014-11-12 15:57:54 +0000362 doUnbind();
fredc3c719642012-04-12 00:02:00 -0700363 } else {
Ugo Yu1652b942019-03-26 21:38:08 +0800364 doBind();
fredc3c719642012-04-12 00:02:00 -0700365 }
366 }
Jack He910201b2017-08-22 16:06:54 -0700367 };
fredc3c719642012-04-12 00:02:00 -0700368
The Android Open Source Project33897762009-03-03 19:31:44 -0800369 /**
370 * Create a BluetoothHeadset proxy object.
371 */
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600372 /* package */ BluetoothHeadset(Context context, ServiceListener l, BluetoothAdapter adapter) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800373 mContext = context;
374 mServiceListener = l;
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600375 mAdapter = adapter;
376 mAttributionSource = adapter.getAttributionSource();
fredc3c719642012-04-12 00:02:00 -0700377
Jeff Sharkey19a8d092021-04-28 09:25:36 -0600378 // Preserve legacy compatibility where apps were depending on
379 // registerStateChangeCallback() performing a permissions check which
380 // has been relaxed in modern platform versions
381 if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R
382 && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH)
383 != PackageManager.PERMISSION_GRANTED) {
384 throw new SecurityException("Need BLUETOOTH permission");
385 }
386
fredc3c719642012-04-12 00:02:00 -0700387 IBluetoothManager mgr = mAdapter.getBluetoothManager();
388 if (mgr != null) {
389 try {
390 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
391 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700392 Log.e(TAG, "", e);
fredc3c719642012-04-12 00:02:00 -0700393 }
394 }
395
Dianne Hackborn3875ec62013-08-04 16:50:16 -0700396 doBind();
Jeff Sharkeydb38eb72021-05-24 10:00:23 -0600397 mCloseGuard.open("close");
Dianne Hackborn3875ec62013-08-04 16:50:16 -0700398 }
399
Ugo Yu1652b942019-03-26 21:38:08 +0800400 private boolean doBind() {
401 synchronized (mConnection) {
402 if (mService == null) {
403 if (VDBG) Log.d(TAG, "Binding service...");
404 try {
405 return mAdapter.getBluetoothManager().bindBluetoothProfileService(
406 BluetoothProfile.HEADSET, mConnection);
407 } catch (RemoteException e) {
408 Log.e(TAG, "Unable to bind HeadsetService", e);
409 }
410 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800411 }
Benjamin Franz3362fef2014-11-12 15:57:54 +0000412 return false;
413 }
414
Ugo Yu1652b942019-03-26 21:38:08 +0800415 private void doUnbind() {
Benjamin Franz3362fef2014-11-12 15:57:54 +0000416 synchronized (mConnection) {
417 if (mService != null) {
Ugo Yu1652b942019-03-26 21:38:08 +0800418 if (VDBG) Log.d(TAG, "Unbinding service...");
Benjamin Franz3362fef2014-11-12 15:57:54 +0000419 try {
420 mAdapter.getBluetoothManager().unbindBluetoothProfileService(
421 BluetoothProfile.HEADSET, mConnection);
422 } catch (RemoteException e) {
Jack He910201b2017-08-22 16:06:54 -0700423 Log.e(TAG, "Unable to unbind HeadsetService", e);
Ugo Yu1652b942019-03-26 21:38:08 +0800424 } finally {
425 mService = null;
Benjamin Franz3362fef2014-11-12 15:57:54 +0000426 }
427 }
428 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800429 }
430
The Android Open Source Project33897762009-03-03 19:31:44 -0800431 /**
432 * Close the connection to the backing service.
433 * Other public functions of BluetoothHeadset will return default error
434 * results once close() has been called. Multiple invocations of close()
435 * are ok.
436 */
Mathew Inwood7d543892018-08-01 15:07:20 +0100437 @UnsupportedAppUsage
Matthew Xie78912492012-03-22 17:18:37 -0700438 /*package*/ void close() {
Matthew Xief8035a72012-10-09 22:10:37 -0700439 if (VDBG) log("close()");
fredc3c719642012-04-12 00:02:00 -0700440
441 IBluetoothManager mgr = mAdapter.getBluetoothManager();
442 if (mgr != null) {
443 try {
444 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
Ugo Yu1652b942019-03-26 21:38:08 +0800445 } catch (RemoteException re) {
446 Log.e(TAG, "", re);
fredc3c719642012-04-12 00:02:00 -0700447 }
448 }
Benjamin Franz733656c2014-12-16 15:33:03 +0000449 mServiceListener = null;
Benjamin Franz3362fef2014-11-12 15:57:54 +0000450 doUnbind();
Jeff Sharkeydb38eb72021-05-24 10:00:23 -0600451 mCloseGuard.close();
452 }
453
454 /** {@hide} */
455 @Override
456 protected void finalize() throws Throwable {
457 mCloseGuard.warnIfOpen();
458 close();
The Android Open Source Project33897762009-03-03 19:31:44 -0800459 }
460
461 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700462 * Initiate connection to a profile of the remote bluetooth device.
463 *
464 * <p> Currently, the system supports only 1 connection to the
465 * headset/handsfree profile. The API will automatically disconnect connected
466 * devices before connecting.
467 *
468 * <p> This API returns false in scenarios like the profile on the
469 * device is already connected or Bluetooth is not turned on.
470 * When this API returns true, it is guaranteed that
471 * connection state intent for the profile will be broadcasted with
472 * the state. Users can get the connection state of the profile
473 * from this intent.
474 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700475 * @param device Remote Bluetooth Device
Jack He910201b2017-08-22 16:06:54 -0700476 * @return false on immediate error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700477 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800478 */
Selim Guruna117cb372017-10-17 17:01:38 -0700479 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600480 @RequiresLegacyBluetoothAdminPermission
481 @RequiresBluetoothConnectPermission
482 @RequiresPermission(allOf = {
483 android.Manifest.permission.BLUETOOTH_CONNECT,
484 android.Manifest.permission.MODIFY_PHONE_STATE,
485 })
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700486 public boolean connect(BluetoothDevice device) {
487 if (DBG) log("connect(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700488 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100489 final boolean defaultValue = false;
490 if (service == null) {
491 Log.w(TAG, "Proxy not attached to service");
492 if (DBG) log(Log.getStackTraceString(new Throwable()));
493 } else if (isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800494 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100495 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
496 service.connectWithAttribution(device, mAttributionSource, recv);
497 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
498 } catch (RemoteException | TimeoutException e) {
499 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700500 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800501 }
William Escande9a4b7c12021-12-16 16:07:55 +0100502 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800503 }
504
505 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700506 * Initiate disconnection from a profile
507 *
508 * <p> This API will return false in scenarios like the profile on the
509 * Bluetooth device is not in connected state etc. When this API returns,
510 * true, it is guaranteed that the connection state change
511 * intent will be broadcasted with the state. Users can get the
512 * disconnection state of the profile from this intent.
513 *
514 * <p> If the disconnection is initiated by a remote device, the state
515 * will transition from {@link #STATE_CONNECTED} to
516 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
517 * host (local) device the state will transition from
518 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
519 * state {@link #STATE_DISCONNECTED}. The transition to
520 * {@link #STATE_DISCONNECTING} can be used to distinguish between the
521 * two scenarios.
522 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700523 * @param device Remote Bluetooth Device
Jack He910201b2017-08-22 16:06:54 -0700524 * @return false on immediate error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700525 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800526 */
Selim Guruna117cb372017-10-17 17:01:38 -0700527 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600528 @RequiresLegacyBluetoothAdminPermission
529 @RequiresBluetoothConnectPermission
530 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700531 public boolean disconnect(BluetoothDevice device) {
532 if (DBG) log("disconnect(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700533 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100534 final boolean defaultValue = false;
535 if (service == null) {
536 Log.w(TAG, "Proxy not attached to service");
537 if (DBG) log(Log.getStackTraceString(new Throwable()));
538 } else if (isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800539 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100540 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
541 service.disconnectWithAttribution(device, mAttributionSource, recv);
542 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
543 } catch (RemoteException | TimeoutException e) {
544 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700545 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800546 }
William Escande9a4b7c12021-12-16 16:07:55 +0100547 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800548 }
549
550 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700551 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800552 */
Jack He9e045d22017-08-22 21:21:23 -0700553 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600554 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600555 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700556 public List<BluetoothDevice> getConnectedDevices() {
Matthew Xief8035a72012-10-09 22:10:37 -0700557 if (VDBG) log("getConnectedDevices()");
Jack He1f686f62017-08-17 12:11:18 -0700558 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100559 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
560 if (service == null) {
561 Log.w(TAG, "Proxy not attached to service");
562 if (DBG) log(Log.getStackTraceString(new Throwable()));
563 } else if (isEnabled()) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800564 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100565 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
566 new SynchronousResultReceiver();
567 service.getConnectedDevicesWithAttribution(mAttributionSource, recv);
Jeff Sharkey98f30442021-06-03 09:26:53 -0600568 return Attributable.setAttributionSource(
William Escande9a4b7c12021-12-16 16:07:55 +0100569 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
Jeff Sharkey98f30442021-06-03 09:26:53 -0600570 mAttributionSource);
William Escande9a4b7c12021-12-16 16:07:55 +0100571 } catch (RemoteException | TimeoutException e) {
572 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700573 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800574 }
William Escande9a4b7c12021-12-16 16:07:55 +0100575 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800576 }
577
578 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700579 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800580 */
Jack He9e045d22017-08-22 21:21:23 -0700581 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600582 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600583 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700584 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Matthew Xief8035a72012-10-09 22:10:37 -0700585 if (VDBG) log("getDevicesMatchingStates()");
Jack He1f686f62017-08-17 12:11:18 -0700586 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100587 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
588 if (service == null) {
589 Log.w(TAG, "Proxy not attached to service");
590 if (DBG) log(Log.getStackTraceString(new Throwable()));
591 } else if (isEnabled()) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800592 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100593 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
594 new SynchronousResultReceiver();
595 service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
Jeff Sharkey98f30442021-06-03 09:26:53 -0600596 return Attributable.setAttributionSource(
William Escande9a4b7c12021-12-16 16:07:55 +0100597 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600598 mAttributionSource);
William Escande9a4b7c12021-12-16 16:07:55 +0100599 } catch (RemoteException | TimeoutException e) {
600 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700601 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800602 }
William Escande9a4b7c12021-12-16 16:07:55 +0100603 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800604 }
605
606 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700607 * {@inheritDoc}
The Android Open Source Project33897762009-03-03 19:31:44 -0800608 */
Jack He9e045d22017-08-22 21:21:23 -0700609 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600610 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600611 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700612 public int getConnectionState(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700613 if (VDBG) log("getConnectionState(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700614 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100615 final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
616 if (service == null) {
617 Log.w(TAG, "Proxy not attached to service");
618 if (DBG) log(Log.getStackTraceString(new Throwable()));
619 } else if (isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800620 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100621 final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
622 service.getConnectionStateWithAttribution(device, mAttributionSource, recv);
623 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
624 } catch (RemoteException | TimeoutException e) {
625 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700626 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800627 }
William Escande9a4b7c12021-12-16 16:07:55 +0100628 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800629 }
630
631 /**
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800632 * Set connection policy of the profile
633 *
634 * <p> The device should already be paired.
635 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
636 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
637 *
638 * @param device Paired bluetooth device
639 * @param connectionPolicy is the connection policy to set to for this profile
640 * @return true if connectionPolicy is set, false on error
641 * @hide
642 */
643 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600644 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600645 @RequiresPermission(allOf = {
646 android.Manifest.permission.BLUETOOTH_CONNECT,
647 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
648 android.Manifest.permission.MODIFY_PHONE_STATE,
649 })
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800650 public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
651 @ConnectionPolicy int connectionPolicy) {
652 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
Jack He1f686f62017-08-17 12:11:18 -0700653 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100654 final boolean defaultValue = false;
655 if (service == null) {
656 Log.w(TAG, "Proxy not attached to service");
657 if (DBG) log(Log.getStackTraceString(new Throwable()));
658 } else if (isEnabled() && isValidDevice(device)
659 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
660 || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800661 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100662 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
663 service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
664 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
665 } catch (RemoteException | TimeoutException e) {
666 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700667 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800668 }
William Escande9a4b7c12021-12-16 16:07:55 +0100669 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800670 }
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 + ")");
William Escande9a4b7c12021-12-16 16:07:55 +0100689 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800690 }
691
692 /**
693 * Get the connection policy of the profile.
694 *
695 * <p> The connection policy can be any of:
696 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
697 * {@link #CONNECTION_POLICY_UNKNOWN}
698 *
699 * @param device Bluetooth device
700 * @return connection policy of the device
701 * @hide
702 */
703 @SystemApi
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600704 @RequiresBluetoothConnectPermission
705 @RequiresPermission(allOf = {
706 android.Manifest.permission.BLUETOOTH_CONNECT,
707 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
708 })
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800709 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
710 if (VDBG) log("getConnectionPolicy(" + device + ")");
Jack He1f686f62017-08-17 12:11:18 -0700711 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100712 final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
713 if (service == null) {
714 Log.w(TAG, "Proxy not attached to service");
715 if (DBG) log(Log.getStackTraceString(new Throwable()));
716 } else if (isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800717 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100718 final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
719 service.getConnectionPolicy(device, mAttributionSource, recv);
720 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
721 } catch (RemoteException | TimeoutException e) {
722 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700723 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800724 }
William Escande9a4b7c12021-12-16 16:07:55 +0100725 return defaultValue;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700726 }
727
728 /**
Rahul Sabnisc611e732020-12-14 10:54:45 -0800729 * Checks whether the headset supports some form of noise reduction
730 *
731 * @param device Bluetooth device
732 * @return true if echo cancellation and/or noise reduction is supported, false otherwise
733 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600734 @RequiresLegacyBluetoothPermission
735 @RequiresBluetoothConnectPermission
736 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Rahul Sabnisc611e732020-12-14 10:54:45 -0800737 public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) {
738 if (DBG) log("isNoiseReductionSupported()");
739 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100740 final boolean defaultValue = false;
741 if (service == null) {
742 Log.w(TAG, "Proxy not attached to service");
743 if (DBG) log(Log.getStackTraceString(new Throwable()));
744 } else if (isEnabled() && isValidDevice(device)) {
Rahul Sabnisc611e732020-12-14 10:54:45 -0800745 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100746 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
747 service.isNoiseReductionSupported(device, mAttributionSource, recv);
748 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
749 } catch (RemoteException | TimeoutException e) {
750 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Rahul Sabnisc611e732020-12-14 10:54:45 -0800751 }
752 }
William Escande9a4b7c12021-12-16 16:07:55 +0100753 return defaultValue;
Rahul Sabnisc611e732020-12-14 10:54:45 -0800754 }
755
756 /**
757 * Checks whether the headset supports voice recognition
758 *
759 * @param device Bluetooth device
760 * @return true if voice recognition is supported, false otherwise
761 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600762 @RequiresLegacyBluetoothPermission
763 @RequiresBluetoothConnectPermission
764 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Rahul Sabnisc611e732020-12-14 10:54:45 -0800765 public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
766 if (DBG) log("isVoiceRecognitionSupported()");
767 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100768 final boolean defaultValue = false;
769 if (service == null) {
770 Log.w(TAG, "Proxy not attached to service");
771 if (DBG) log(Log.getStackTraceString(new Throwable()));
772 } else if (isEnabled() && isValidDevice(device)) {
Rahul Sabnisc611e732020-12-14 10:54:45 -0800773 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100774 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
775 service.isVoiceRecognitionSupported(device, mAttributionSource, recv);
776 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
777 } catch (RemoteException | TimeoutException e) {
778 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Rahul Sabnisc611e732020-12-14 10:54:45 -0800779 }
780 }
William Escande9a4b7c12021-12-16 16:07:55 +0100781 return defaultValue;
Rahul Sabnisc611e732020-12-14 10:54:45 -0800782 }
783
784 /**
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700785 * Start Bluetooth voice recognition. This methods sends the voice
786 * recognition AT command to the headset and establishes the
787 * audio connection.
788 *
789 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800790 * If this function returns true, this intent will be broadcasted with
791 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700792 *
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800793 * <p> {@link #EXTRA_STATE} will transition from
794 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
795 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
796 * in case of failure to establish the audio connection.
797 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700798 * @param device Bluetooth headset
Jack Hec46a01e2018-05-02 19:10:56 -0700799 * @return false if there is no headset connected, or the connected headset doesn't support
800 * voice recognition, or voice recognition is already started, or audio channel is occupied,
801 * or on error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700802 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600803 @RequiresLegacyBluetoothPermission
804 @RequiresBluetoothConnectPermission
805 @RequiresPermission(allOf = {
806 android.Manifest.permission.BLUETOOTH_CONNECT,
807 android.Manifest.permission.MODIFY_PHONE_STATE,
808 })
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700809 public boolean startVoiceRecognition(BluetoothDevice device) {
810 if (DBG) log("startVoiceRecognition()");
Jack He1f686f62017-08-17 12:11:18 -0700811 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100812 final boolean defaultValue = false;
813 if (service == null) {
814 Log.w(TAG, "Proxy not attached to service");
815 if (DBG) log(Log.getStackTraceString(new Throwable()));
816 } else if (isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700817 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100818 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
819 service.startVoiceRecognition(device, mAttributionSource, recv);
820 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
821 } catch (RemoteException | TimeoutException e) {
822 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700823 }
824 }
William Escande9a4b7c12021-12-16 16:07:55 +0100825 return defaultValue;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700826 }
827
828 /**
829 * Stop Bluetooth Voice Recognition mode, and shut down the
830 * Bluetooth audio path.
831 *
Jack Hec46a01e2018-05-02 19:10:56 -0700832 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
833 * If this function returns true, this intent will be broadcasted with
834 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
835 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700836 * @param device Bluetooth headset
Jack Hec46a01e2018-05-02 19:10:56 -0700837 * @return false if there is no headset connected, or voice recognition has not started,
838 * or voice recognition has ended on this headset, or on error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700839 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600840 @RequiresLegacyBluetoothPermission
841 @RequiresBluetoothConnectPermission
842 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700843 public boolean stopVoiceRecognition(BluetoothDevice device) {
844 if (DBG) log("stopVoiceRecognition()");
Jack He1f686f62017-08-17 12:11:18 -0700845 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100846 final boolean defaultValue = false;
847 if (service == null) {
848 Log.w(TAG, "Proxy not attached to service");
849 if (DBG) log(Log.getStackTraceString(new Throwable()));
850 } else if (isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700851 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100852 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
853 service.stopVoiceRecognition(device, mAttributionSource, recv);
854 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
855 } catch (RemoteException | TimeoutException e) {
856 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700857 }
858 }
William Escande9a4b7c12021-12-16 16:07:55 +0100859 return defaultValue;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700860 }
861
862 /**
863 * Check if Bluetooth SCO audio is connected.
864 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700865 * @param device Bluetooth headset
Jack He910201b2017-08-22 16:06:54 -0700866 * @return true if SCO is connected, false otherwise or on error
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700867 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600868 @RequiresLegacyBluetoothPermission
869 @RequiresBluetoothConnectPermission
870 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700871 public boolean isAudioConnected(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700872 if (VDBG) log("isAudioConnected()");
Jack He1f686f62017-08-17 12:11:18 -0700873 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100874 final boolean defaultValue = false;
875 if (service == null) {
876 Log.w(TAG, "Proxy not attached to service");
877 if (DBG) log(Log.getStackTraceString(new Throwable()));
878 } else if (isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700879 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100880 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
881 service.isAudioConnected(device, mAttributionSource, recv);
882 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
883 } catch (RemoteException | TimeoutException e) {
884 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700885 }
886 }
William Escande9a4b7c12021-12-16 16:07:55 +0100887 return defaultValue;
The Android Open Source Project33897762009-03-03 19:31:44 -0800888 }
889
890 /**
Eric Laurent2e66fa22010-03-17 14:59:27 -0700891 * Indicates if current platform supports voice dialing over bluetooth SCO.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700892 *
Eric Laurent2e66fa22010-03-17 14:59:27 -0700893 * @return true if voice dialing over bluetooth is supported, false otherwise.
894 * @hide
895 */
896 public static boolean isBluetoothVoiceDialingEnabled(Context context) {
897 return context.getResources().getBoolean(
898 com.android.internal.R.bool.config_bluetooth_sco_off_call);
899 }
900
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800901 /** @hide */
902 @Retention(RetentionPolicy.SOURCE)
903 @IntDef(value = {
904 BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
905 BluetoothHeadset.STATE_AUDIO_CONNECTING,
906 BluetoothHeadset.STATE_AUDIO_CONNECTED,
907 BluetoothStatusCodes.ERROR_TIMEOUT
908 })
909 public @interface GetAudioStateReturnValues {}
910
Jaikumar Ganeshb84bbd92010-06-02 14:36:14 -0700911 /**
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700912 * Get the current audio state of the Headset.
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800913 *
914 * @param device is the Bluetooth device for which the audio state is being queried
915 * @return the audio state of the device or an error code
916 * @throws IllegalArgumentException if the device is null
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700917 *
918 * @hide
919 */
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800920 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600921 @RequiresBluetoothConnectPermission
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800922 @RequiresPermission(allOf = {
923 android.Manifest.permission.BLUETOOTH_CONNECT,
924 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
925 })
926 public @GetAudioStateReturnValues int getAudioState(@NonNull BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700927 if (VDBG) log("getAudioState");
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800928 if (device == null) {
929 throw new IllegalArgumentException("device cannot be null");
930 }
Jack He1f686f62017-08-17 12:11:18 -0700931 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100932 final int defaultValue = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
933 if (service == null) {
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700934 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +0100935 if (DBG) log(Log.getStackTraceString(new Throwable()));
936 } else if (!isDisabled()) {
937 try {
938 final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
939 service.getAudioState(device, mAttributionSource, recv);
940 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800941 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100942 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800943 throw e.rethrowFromSystemServer();
944 } catch (TimeoutException e) {
945 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
946 return BluetoothStatusCodes.ERROR_TIMEOUT;
William Escande9a4b7c12021-12-16 16:07:55 +0100947 }
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700948 }
William Escande9a4b7c12021-12-16 16:07:55 +0100949 return defaultValue;
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700950 }
951
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -0700952 /**
Bryce Lee0e154a32015-11-16 08:55:52 -0800953 * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
954 * audio to the HF unless explicitly told to.
955 * This method should be used in cases where the SCO channel is shared between multiple profiles
956 * and must be delegated by a source knowledgeable
957 * Note: This is an internal function and shouldn't be exposed
958 *
959 * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
Bryce Lee0e154a32015-11-16 08:55:52 -0800960 * @hide
961 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600962 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600963 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Bryce Lee0e154a32015-11-16 08:55:52 -0800964 public void setAudioRouteAllowed(boolean allowed) {
965 if (VDBG) log("setAudioRouteAllowed");
Jack He1f686f62017-08-17 12:11:18 -0700966 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100967 if (service == null) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800968 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +0100969 if (DBG) log(Log.getStackTraceString(new Throwable()));
970 } else if (isEnabled()) {
971 try {
972 final SynchronousResultReceiver recv = new SynchronousResultReceiver();
973 service.setAudioRouteAllowed(allowed, mAttributionSource, recv);
974 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
975 } catch (RemoteException | TimeoutException e) {
976 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
977 }
Bryce Lee0e154a32015-11-16 08:55:52 -0800978 }
979 }
980
981 /**
982 * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
983 * Note: This is an internal function and shouldn't be exposed
984 *
985 * @hide
986 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600987 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600988 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Bryce Lee0e154a32015-11-16 08:55:52 -0800989 public boolean getAudioRouteAllowed() {
990 if (VDBG) log("getAudioRouteAllowed");
Jack He1f686f62017-08-17 12:11:18 -0700991 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +0100992 final boolean defaultValue = false;
993 if (service == null) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800994 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +0100995 if (DBG) log(Log.getStackTraceString(new Throwable()));
996 } else if (isEnabled()) {
997 try {
998 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
999 service.getAudioRouteAllowed(mAttributionSource, recv);
1000 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1001 } catch (RemoteException | TimeoutException e) {
1002 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1003 }
Bryce Lee0e154a32015-11-16 08:55:52 -08001004 }
William Escande9a4b7c12021-12-16 16:07:55 +01001005 return defaultValue;
Bryce Lee0e154a32015-11-16 08:55:52 -08001006 }
1007
1008 /**
Jack He798d7282017-05-09 17:16:01 -07001009 * Force SCO audio to be opened regardless any other restrictions
1010 *
Jack He910201b2017-08-22 16:06:54 -07001011 * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio
1012 * False to use SCO audio in normal manner
Jack He798d7282017-05-09 17:16:01 -07001013 * @hide
1014 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001015 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001016 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He798d7282017-05-09 17:16:01 -07001017 public void setForceScoAudio(boolean forced) {
1018 if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
Jack He1f686f62017-08-17 12:11:18 -07001019 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +01001020 if (service == null) {
Jack He798d7282017-05-09 17:16:01 -07001021 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001022 if (DBG) log(Log.getStackTraceString(new Throwable()));
1023 } else if (isEnabled()) {
1024 try {
1025 final SynchronousResultReceiver recv = new SynchronousResultReceiver();
1026 service.setForceScoAudio(forced, mAttributionSource, recv);
1027 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1028 } catch (RemoteException | TimeoutException e) {
1029 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1030 }
Jack He798d7282017-05-09 17:16:01 -07001031 }
1032 }
1033
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001034 /** @hide */
1035 @Retention(RetentionPolicy.SOURCE)
1036 @IntDef(value = {
1037 BluetoothStatusCodes.SUCCESS,
1038 BluetoothStatusCodes.ERROR_UNKNOWN,
1039 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
1040 BluetoothStatusCodes.ERROR_TIMEOUT,
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001041 BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED,
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001042 BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED,
1043 BluetoothStatusCodes.ERROR_NO_ACTIVE_DEVICES,
1044 BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE,
1045 BluetoothStatusCodes.ERROR_AUDIO_ROUTE_BLOCKED,
1046 BluetoothStatusCodes.ERROR_CALL_ACTIVE,
1047 BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED
1048 })
1049 public @interface ConnectAudioReturnValues {}
Matthew Xief3ee3512012-02-16 16:57:18 -08001050
1051 /**
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001052 * Initiates a connection of SCO audio to the current active HFP device. The active HFP device
1053 * can be identified with {@link BluetoothAdapter#getActiveDevices(int)}.
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001054 * <p>
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001055 * If this function returns {@link BluetoothStatusCodes#SUCCESS}, the intent
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001056 * {@link #ACTION_AUDIO_STATE_CHANGED} will be broadcasted twice. First with
1057 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. This will be followed by a
1058 * broadcast with {@link #EXTRA_STATE} set to either {@link #STATE_AUDIO_CONNECTED} if the audio
1059 * connection is established or {@link #STATE_AUDIO_DISCONNECTED} if there was a failure in
1060 * establishing the audio connection.
Matthew Xief3ee3512012-02-16 16:57:18 -08001061 *
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001062 * @return whether the connection was successfully initiated or an error code on failure
Matthew Xief3ee3512012-02-16 16:57:18 -08001063 * @hide
1064 */
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001065 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001066 @RequiresBluetoothConnectPermission
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001067 @RequiresPermission(allOf = {
1068 android.Manifest.permission.BLUETOOTH_CONNECT,
1069 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1070 })
1071 public @ConnectAudioReturnValues int connectAudio() {
William Escande9a4b7c12021-12-16 16:07:55 +01001072 if (VDBG) log("connectAudio()");
Jack He1f686f62017-08-17 12:11:18 -07001073 final IBluetoothHeadset service = mService;
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001074 final int defaultValue = BluetoothStatusCodes.ERROR_UNKNOWN;
William Escande9a4b7c12021-12-16 16:07:55 +01001075 if (service == null) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001076 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001077 if (DBG) log(Log.getStackTraceString(new Throwable()));
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001078 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
William Escande9a4b7c12021-12-16 16:07:55 +01001079 } else if (isEnabled()) {
1080 try {
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001081 final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
William Escande9a4b7c12021-12-16 16:07:55 +01001082 service.connectAudio(mAttributionSource, recv);
1083 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001084 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001085 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001086 throw e.rethrowFromSystemServer();
1087 } catch (TimeoutException e) {
1088 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1089 return BluetoothStatusCodes.ERROR_TIMEOUT;
William Escande9a4b7c12021-12-16 16:07:55 +01001090 }
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001091 } else {
1092 return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
Matthew Xief3ee3512012-02-16 16:57:18 -08001093 }
Matthew Xief3ee3512012-02-16 16:57:18 -08001094 }
1095
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001096 /** @hide */
1097 @Retention(RetentionPolicy.SOURCE)
1098 @IntDef(value = {
1099 BluetoothStatusCodes.SUCCESS,
1100 BluetoothStatusCodes.ERROR_UNKNOWN,
1101 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
1102 BluetoothStatusCodes.ERROR_TIMEOUT,
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001103 BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED,
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001104 BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED,
1105 BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED
1106 })
1107 public @interface DisconnectAudioReturnValues {}
1108
Matthew Xief3ee3512012-02-16 16:57:18 -08001109 /**
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001110 * Initiates a disconnection of HFP SCO audio from actively connected devices. It also tears
1111 * down voice recognition or virtual voice call, if any exists.
Matthew Xief3ee3512012-02-16 16:57:18 -08001112 *
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001113 * <p> If this function returns {@link BluetoothStatusCodes#SUCCESS}, the intent
1114 * {@link #ACTION_AUDIO_STATE_CHANGED} will be broadcasted with {@link #EXTRA_STATE} set to
1115 * {@link #STATE_AUDIO_DISCONNECTED}.
Jack Hec46a01e2018-05-02 19:10:56 -07001116 *
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001117 * @return whether the disconnection was initiated successfully or an error code on failure
Matthew Xief3ee3512012-02-16 16:57:18 -08001118 * @hide
1119 */
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001120 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001121 @RequiresBluetoothConnectPermission
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001122 @RequiresPermission(allOf = {
1123 android.Manifest.permission.BLUETOOTH_CONNECT,
1124 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1125 })
1126 public @DisconnectAudioReturnValues int disconnectAudio() {
William Escande9a4b7c12021-12-16 16:07:55 +01001127 if (VDBG) log("disconnectAudio()");
Jack He1f686f62017-08-17 12:11:18 -07001128 final IBluetoothHeadset service = mService;
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001129 final int defaultValue = BluetoothStatusCodes.ERROR_UNKNOWN;
William Escande9a4b7c12021-12-16 16:07:55 +01001130 if (service == null) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001131 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001132 if (DBG) log(Log.getStackTraceString(new Throwable()));
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001133 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
William Escande9a4b7c12021-12-16 16:07:55 +01001134 } else if (isEnabled()) {
1135 try {
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001136 final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
William Escande9a4b7c12021-12-16 16:07:55 +01001137 service.disconnectAudio(mAttributionSource, recv);
1138 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001139 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001140 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001141 throw e.rethrowFromSystemServer();
1142 } catch (TimeoutException e) {
1143 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1144 return BluetoothStatusCodes.ERROR_TIMEOUT;
William Escande9a4b7c12021-12-16 16:07:55 +01001145 }
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001146 } else {
1147 return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
Matthew Xief3ee3512012-02-16 16:57:18 -08001148 }
Matthew Xief3ee3512012-02-16 16:57:18 -08001149 }
1150
1151 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001152 * Initiates a SCO channel connection as a virtual voice call to the current active device
1153 * Active handsfree device will be notified of incoming call and connected call.
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001154 *
Jack Hec46a01e2018-05-02 19:10:56 -07001155 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1156 * If this function returns true, this intent will be broadcasted with
1157 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
1158 *
1159 * <p> {@link #EXTRA_STATE} will transition from
1160 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
1161 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
1162 * in case of failure to establish the audio connection.
1163 *
1164 * @return true if successful, false if one of the following case applies
1165 * - SCO audio is not idle (connecting or connected)
1166 * - virtual call has already started
1167 * - there is no active device
1168 * - a Telecom managed call is going on
1169 * - binder is dead or Bluetooth is disabled or other error
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001170 * @hide
1171 */
Roopa Sattirajuffab9952021-08-20 15:06:57 -07001172 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001173 @RequiresLegacyBluetoothAdminPermission
1174 @RequiresBluetoothConnectPermission
1175 @RequiresPermission(allOf = {
1176 android.Manifest.permission.BLUETOOTH_CONNECT,
1177 android.Manifest.permission.MODIFY_PHONE_STATE,
1178 })
Jack Hec46a01e2018-05-02 19:10:56 -07001179 public boolean startScoUsingVirtualVoiceCall() {
Jaikumar Ganesheea6d262011-01-24 13:55:27 -08001180 if (DBG) log("startScoUsingVirtualVoiceCall()");
Jack He1f686f62017-08-17 12:11:18 -07001181 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +01001182 final boolean defaultValue = false;
1183 if (service == null) {
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001184 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001185 if (DBG) log(Log.getStackTraceString(new Throwable()));
1186 } else if (isEnabled()) {
1187 try {
1188 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
1189 service.startScoUsingVirtualVoiceCall(mAttributionSource, recv);
1190 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1191 } catch (RemoteException | TimeoutException e) {
1192 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1193 }
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001194 }
William Escande9a4b7c12021-12-16 16:07:55 +01001195 return defaultValue;
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001196 }
1197
1198 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001199 * Terminates an ongoing SCO connection and the associated virtual call.
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001200 *
Jack Hec46a01e2018-05-02 19:10:56 -07001201 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
1202 * If this function returns true, this intent will be broadcasted with
1203 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
1204 *
1205 * @return true if successful, false if one of the following case applies
1206 * - virtual voice call is not started or has ended
1207 * - binder is dead or Bluetooth is disabled or other error
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001208 * @hide
1209 */
Roopa Sattirajuffab9952021-08-20 15:06:57 -07001210 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001211 @RequiresLegacyBluetoothAdminPermission
1212 @RequiresBluetoothConnectPermission
1213 @RequiresPermission(allOf = {
1214 android.Manifest.permission.BLUETOOTH_CONNECT,
1215 android.Manifest.permission.MODIFY_PHONE_STATE,
1216 })
Jack Hec46a01e2018-05-02 19:10:56 -07001217 public boolean stopScoUsingVirtualVoiceCall() {
Jaikumar Ganesheea6d262011-01-24 13:55:27 -08001218 if (DBG) log("stopScoUsingVirtualVoiceCall()");
Jack He1f686f62017-08-17 12:11:18 -07001219 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +01001220 final boolean defaultValue = false;
1221 if (service == null) {
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001222 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001223 if (DBG) log(Log.getStackTraceString(new Throwable()));
1224 } else if (isEnabled()) {
1225 try {
1226 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
1227 service.stopScoUsingVirtualVoiceCall(mAttributionSource, recv);
1228 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1229 } catch (RemoteException | TimeoutException e) {
1230 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1231 }
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001232 }
William Escande9a4b7c12021-12-16 16:07:55 +01001233 return defaultValue;
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001234 }
1235
Matthew Xief3ee3512012-02-16 16:57:18 -08001236 /**
1237 * Notify Headset of phone state change.
1238 * This is a backdoor for phone app to call BluetoothHeadset since
1239 * there is currently not a good way to get precise call state change outside
1240 * of phone app.
1241 *
1242 * @hide
1243 */
Mathew Inwood049f0f52020-11-04 09:29:36 +00001244 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001245 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001246 @RequiresPermission(allOf = {
1247 android.Manifest.permission.BLUETOOTH_CONNECT,
1248 android.Manifest.permission.MODIFY_PHONE_STATE,
1249 })
Matthew Xief3ee3512012-02-16 16:57:18 -08001250 public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
Benson Lied8d3392018-07-17 18:19:59 +08001251 int type, String name) {
Jack He1f686f62017-08-17 12:11:18 -07001252 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +01001253 if (service == null) {
1254 Log.w(TAG, "Proxy not attached to service");
1255 if (DBG) log(Log.getStackTraceString(new Throwable()));
1256 } else if (isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001257 try {
Jeff Sharkey43ee69e2021-04-23 14:13:57 -06001258 service.phoneStateChanged(numActive, numHeld, callState, number, type, name,
1259 mAttributionSource);
William Escande9a4b7c12021-12-16 16:07:55 +01001260 } catch (RemoteException e) {
1261 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Matthew Xief3ee3512012-02-16 16:57:18 -08001262 }
Matthew Xief3ee3512012-02-16 16:57:18 -08001263 }
1264 }
1265
1266 /**
Matthew Xief3ee3512012-02-16 16:57:18 -08001267 * Send Headset of CLCC response
1268 *
1269 * @hide
1270 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001271 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001272 @RequiresPermission(allOf = {
1273 android.Manifest.permission.BLUETOOTH_CONNECT,
1274 android.Manifest.permission.MODIFY_PHONE_STATE,
1275 })
Matthew Xief3ee3512012-02-16 16:57:18 -08001276 public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
Jack He910201b2017-08-22 16:06:54 -07001277 String number, int type) {
Jack He1f686f62017-08-17 12:11:18 -07001278 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +01001279 if (service == null) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001280 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001281 if (DBG) log(Log.getStackTraceString(new Throwable()));
1282 } else if (isEnabled()) {
1283 try {
1284 final SynchronousResultReceiver recv = new SynchronousResultReceiver();
1285 service.clccResponse(index, direction, status, mode, mpty, number, type,
1286 mAttributionSource, recv);
1287 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
1288 } catch (RemoteException | TimeoutException e) {
1289 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1290 }
Matthew Xief3ee3512012-02-16 16:57:18 -08001291 }
1292 }
1293
Edward Jee7c81f1f2013-08-16 04:07:49 -07001294 /**
1295 * Sends a vendor-specific unsolicited result code to the headset.
1296 *
Jack He910201b2017-08-22 16:06:54 -07001297 * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code
1298 * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the
1299 * string <code>"+ANDROID: 0"</code> will be sent.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001300 *
Ying Wang29d93892013-08-26 17:48:22 -07001301 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001302 *
Edward Jee7c81f1f2013-08-16 04:07:49 -07001303 * @param device Bluetooth headset.
1304 * @param command A vendor-specific command.
1305 * @param arg The argument that will be attached to the command.
1306 * @return {@code false} if there is no headset connected, or if the command is not an allowed
Jack He910201b2017-08-22 16:06:54 -07001307 * vendor-specific unsolicited result code, or on error. {@code true} otherwise.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001308 * @throws IllegalArgumentException if {@code command} is {@code null}.
1309 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001310 @RequiresLegacyBluetoothPermission
1311 @RequiresBluetoothConnectPermission
1312 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Edward Jee7c81f1f2013-08-16 04:07:49 -07001313 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
1314 String arg) {
1315 if (DBG) {
1316 log("sendVendorSpecificResultCode()");
1317 }
1318 if (command == null) {
1319 throw new IllegalArgumentException("command is null");
1320 }
Jack He1f686f62017-08-17 12:11:18 -07001321 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +01001322 final boolean defaultValue = false;
Jack He1f686f62017-08-17 12:11:18 -07001323 if (service == null) {
Edward Jee7c81f1f2013-08-16 04:07:49 -07001324 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001325 if (DBG) log(Log.getStackTraceString(new Throwable()));
1326 } else if (isEnabled() && isValidDevice(device)) {
1327 try {
1328 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
1329 service.sendVendorSpecificResultCode(device, command, arg,
1330 mAttributionSource, recv);
1331 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1332 } catch (RemoteException | TimeoutException e) {
1333 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1334 }
Edward Jee7c81f1f2013-08-16 04:07:49 -07001335 }
William Escande9a4b7c12021-12-16 16:07:55 +01001336 return defaultValue;
Edward Jee7c81f1f2013-08-16 04:07:49 -07001337 }
1338
Mudumba Ananth80bf6282014-04-27 13:11:00 -07001339 /**
Jack He889d2342018-01-03 12:13:26 -08001340 * Select a connected device as active.
1341 *
1342 * The active device selection is per profile. An active device's
1343 * purpose is profile-specific. For example, in HFP and HSP profiles,
1344 * it is the device used for phone call audio. If a remote device is not
1345 * connected, it cannot be selected as active.
1346 *
1347 * <p> This API returns false in scenarios like the profile on the
1348 * device is not connected or Bluetooth is not turned on.
1349 * When this API returns true, it is guaranteed that the
1350 * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
1351 * with the active device.
1352 *
Jack He889d2342018-01-03 12:13:26 -08001353 * @param device Remote Bluetooth Device, could be null if phone call audio should not be
1354 * streamed to a headset
1355 * @return false on immediate error, true otherwise
1356 * @hide
1357 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001358 @RequiresLegacyBluetoothAdminPermission
1359 @RequiresBluetoothConnectPermission
1360 @RequiresPermission(allOf = {
1361 android.Manifest.permission.BLUETOOTH_CONNECT,
1362 android.Manifest.permission.MODIFY_PHONE_STATE,
1363 })
Mathew Inwoodb426f902021-01-06 12:05:47 +00001364 @UnsupportedAppUsage(trackingBug = 171933273)
Jack He889d2342018-01-03 12:13:26 -08001365 public boolean setActiveDevice(@Nullable BluetoothDevice device) {
1366 if (DBG) {
1367 Log.d(TAG, "setActiveDevice: " + device);
1368 }
1369 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +01001370 final boolean defaultValue = false;
Jack He889d2342018-01-03 12:13:26 -08001371 if (service == null) {
1372 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001373 if (DBG) log(Log.getStackTraceString(new Throwable()));
1374 } else if (isEnabled() && (device == null || isValidDevice(device))) {
1375 try {
1376 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
1377 service.setActiveDevice(device, mAttributionSource, recv);
1378 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1379 } catch (RemoteException | TimeoutException e) {
1380 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1381 }
Jack He889d2342018-01-03 12:13:26 -08001382 }
William Escande9a4b7c12021-12-16 16:07:55 +01001383 return defaultValue;
Jack He889d2342018-01-03 12:13:26 -08001384 }
1385
1386 /**
1387 * Get the connected device that is active.
1388 *
Jack He889d2342018-01-03 12:13:26 -08001389 * @return the connected device that is active or null if no device
1390 * is active.
1391 * @hide
1392 */
Mathew Inwoodb426f902021-01-06 12:05:47 +00001393 @UnsupportedAppUsage(trackingBug = 171933273)
Rahul Sabnisd9798612019-12-04 14:21:10 -08001394 @Nullable
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001395 @RequiresLegacyBluetoothPermission
1396 @RequiresBluetoothConnectPermission
1397 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He889d2342018-01-03 12:13:26 -08001398 public BluetoothDevice getActiveDevice() {
William Escande9a4b7c12021-12-16 16:07:55 +01001399 if (VDBG) Log.d(TAG, "getActiveDevice");
Jack He889d2342018-01-03 12:13:26 -08001400 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +01001401 final BluetoothDevice defaultValue = null;
Jack He889d2342018-01-03 12:13:26 -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 {
1407 final SynchronousResultReceiver<BluetoothDevice> recv =
1408 new SynchronousResultReceiver();
1409 service.getActiveDevice(mAttributionSource, recv);
1410 return Attributable.setAttributionSource(
1411 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
1412 mAttributionSource);
1413 } catch (RemoteException | TimeoutException e) {
1414 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1415 }
Jack He889d2342018-01-03 12:13:26 -08001416 }
William Escande9a4b7c12021-12-16 16:07:55 +01001417 return defaultValue;
Jack He889d2342018-01-03 12:13:26 -08001418 }
1419
1420 /**
Jack Hec5fde732018-01-05 17:17:06 -08001421 * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an
1422 * active connection.
Jack Hed8d204d2016-11-17 16:19:43 -08001423 *
Jack Hec5fde732018-01-05 17:17:06 -08001424 * @return true if in-band ringing is enabled, false if in-band ringing is disabled
1425 * @hide
1426 */
Roopa Sattirajuffab9952021-08-20 15:06:57 -07001427 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001428 @RequiresLegacyBluetoothPermission
1429 @RequiresBluetoothConnectPermission
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001430 @RequiresPermission(allOf = {
1431 android.Manifest.permission.BLUETOOTH_CONNECT,
1432 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1433 })
Jack Hec5fde732018-01-05 17:17:06 -08001434 public boolean isInbandRingingEnabled() {
William Escande9a4b7c12021-12-16 16:07:55 +01001435 if (DBG) log("isInbandRingingEnabled()");
Jack Hec5fde732018-01-05 17:17:06 -08001436 final IBluetoothHeadset service = mService;
William Escande9a4b7c12021-12-16 16:07:55 +01001437 final boolean defaultValue = false;
Jack Hec5fde732018-01-05 17:17:06 -08001438 if (service == null) {
1439 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001440 if (DBG) log(Log.getStackTraceString(new Throwable()));
1441 } else if (isEnabled()) {
1442 try {
1443 final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
1444 service.isInbandRingingEnabled(mAttributionSource, recv);
1445 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1446 } catch (RemoteException | TimeoutException e) {
1447 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1448 }
Jack Hec5fde732018-01-05 17:17:06 -08001449 }
William Escande9a4b7c12021-12-16 16:07:55 +01001450 return defaultValue;
Jack Hec5fde732018-01-05 17:17:06 -08001451 }
1452
1453 /**
1454 * Check if in-band ringing is supported for this platform.
1455 *
1456 * @return true if in-band ringing is supported, false if in-band ringing is not supported
Jack Hed8d204d2016-11-17 16:19:43 -08001457 * @hide
1458 */
1459 public static boolean isInbandRingingSupported(Context context) {
1460 return context.getResources().getBoolean(
1461 com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support);
1462 }
1463
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001464 @SuppressLint("AndroidFrameworkBluetoothPermission")
Jack He9e045d22017-08-22 21:21:23 -07001465 private final IBluetoothProfileServiceConnection mConnection =
1466 new IBluetoothProfileServiceConnection.Stub() {
Benjamin Franz3362fef2014-11-12 15:57:54 +00001467 @Override
The Android Open Source Project33897762009-03-03 19:31:44 -08001468 public void onServiceConnected(ComponentName className, IBinder service) {
1469 if (DBG) Log.d(TAG, "Proxy object connected");
William Escande9a4b7c12021-12-16 16:07:55 +01001470 mService = IBluetoothHeadset.Stub.asInterface(service);
Benjamin Franz3362fef2014-11-12 15:57:54 +00001471 mHandler.sendMessage(mHandler.obtainMessage(
1472 MESSAGE_HEADSET_SERVICE_CONNECTED));
The Android Open Source Project33897762009-03-03 19:31:44 -08001473 }
Jack He910201b2017-08-22 16:06:54 -07001474
Benjamin Franz3362fef2014-11-12 15:57:54 +00001475 @Override
The Android Open Source Project33897762009-03-03 19:31:44 -08001476 public void onServiceDisconnected(ComponentName className) {
1477 if (DBG) Log.d(TAG, "Proxy object disconnected");
Ugo Yu1652b942019-03-26 21:38:08 +08001478 doUnbind();
Benjamin Franz3362fef2014-11-12 15:57:54 +00001479 mHandler.sendMessage(mHandler.obtainMessage(
1480 MESSAGE_HEADSET_SERVICE_DISCONNECTED));
The Android Open Source Project33897762009-03-03 19:31:44 -08001481 }
1482 };
The Android Open Source Project0047a0f2009-03-05 20:00:43 -08001483
Mathew Inwood7d543892018-08-01 15:07:20 +01001484 @UnsupportedAppUsage
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001485 private boolean isEnabled() {
Jack He1f686f62017-08-17 12:11:18 -07001486 return mAdapter.getState() == BluetoothAdapter.STATE_ON;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001487 }
1488
Jaikumar Ganesh2fbe8f42011-04-06 11:09:30 -07001489 private boolean isDisabled() {
Jack He1f686f62017-08-17 12:11:18 -07001490 return mAdapter.getState() == BluetoothAdapter.STATE_OFF;
Jaikumar Ganesh2fbe8f42011-04-06 11:09:30 -07001491 }
1492
Jack He1f686f62017-08-17 12:11:18 -07001493 private static boolean isValidDevice(BluetoothDevice device) {
1494 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001495 }
1496
The Android Open Source Project0047a0f2009-03-05 20:00:43 -08001497 private static void log(String msg) {
1498 Log.d(TAG, msg);
1499 }
Benjamin Franz3362fef2014-11-12 15:57:54 +00001500
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001501 @SuppressLint("AndroidFrameworkBluetoothPermission")
Benjamin Franz3362fef2014-11-12 15:57:54 +00001502 private final Handler mHandler = new Handler(Looper.getMainLooper()) {
1503 @Override
1504 public void handleMessage(Message msg) {
1505 switch (msg.what) {
1506 case MESSAGE_HEADSET_SERVICE_CONNECTED: {
1507 if (mServiceListener != null) {
1508 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
1509 BluetoothHeadset.this);
1510 }
1511 break;
1512 }
1513 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
1514 if (mServiceListener != null) {
1515 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
1516 }
Benjamin Franz3362fef2014-11-12 15:57:54 +00001517 break;
1518 }
1519 }
1520 }
1521 };
The Android Open Source Project33897762009-03-03 19:31:44 -08001522}