blob: 0ec2c6a97e6f21f077ad9bd6d4b3c467a07c591a [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 +010019
Rahul Sabnis5ed1b842021-12-23 11:36:40 -080020import android.annotation.IntDef;
Rahul Sabnise8bac9b2019-11-27 18:09:33 -080021import android.annotation.NonNull;
Jack He889d2342018-01-03 12:13:26 -080022import android.annotation.Nullable;
Selim Guruna117cb372017-10-17 17:01:38 -070023import android.annotation.RequiresPermission;
Nick Pellydac4c0d2009-09-10 10:21:56 -070024import android.annotation.SdkConstant;
25import android.annotation.SdkConstant.SdkConstantType;
Roopa Sattirajuffab9952021-08-20 15:06:57 -070026import android.annotation.SuppressLint;
27import android.annotation.SystemApi;
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060028import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
29import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
30import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
Artur Satayev3625be42019-12-10 17:47:52 +000031import android.compat.annotation.UnsupportedAppUsage;
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -060032import android.content.AttributionSource;
The Android Open Source Project33897762009-03-03 19:31:44 -080033import android.content.Context;
Mathew Inwood049f0f52020-11-04 09:29:36 +000034import android.os.Build;
David Duartef5b3bc52023-10-17 00:55:06 +000035import android.os.IBinder;
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -070036import android.os.RemoteException;
The Android Open Source Project33897762009-03-03 19:31:44 -080037import android.util.Log;
38
Rahul Sabnis5ed1b842021-12-23 11:36:40 -080039import java.lang.annotation.Retention;
40import java.lang.annotation.RetentionPolicy;
William Escande5e8e1532024-03-27 18:02:23 +000041import java.util.Collections;
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -070042import java.util.List;
Rahul Sabnis9a5e3512022-03-17 23:37:38 -070043import java.util.Objects;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070044
The Android Open Source Project33897762009-03-03 19:31:44 -080045/**
David Duarteee52b7e2023-12-02 01:32:11 +000046 * Public API for controlling the Bluetooth Headset Service. This includes both Bluetooth Headset
47 * and Handsfree (v1.5) profiles.
The Android Open Source Project33897762009-03-03 19:31:44 -080048 *
David Duarteee52b7e2023-12-02 01:32:11 +000049 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset Service via IPC.
The Android Open Source Project33897762009-03-03 19:31:44 -080050 *
David Duarteee52b7e2023-12-02 01:32:11 +000051 * <p>Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothHeadset proxy object. Use
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070052 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
The Android Open Source Project33897762009-03-03 19:31:44 -080053 *
David Duarteee52b7e2023-12-02 01:32:11 +000054 * <p>Android only supports one connected Bluetooth Headset at a time. Each method is protected with
55 * its appropriate permission.
The Android Open Source Project33897762009-03-03 19:31:44 -080056 */
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070057public final class BluetoothHeadset implements BluetoothProfile {
The Android Open Source Project33897762009-03-03 19:31:44 -080058 private static final String TAG = "BluetoothHeadset";
William Escande60874f82022-10-27 22:51:52 -070059 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Matthew Xief8035a72012-10-09 22:10:37 -070060 private static final boolean VDBG = false;
The Android Open Source Project33897762009-03-03 19:31:44 -080061
Nick Pellydac4c0d2009-09-10 10:21:56 -070062 /**
David Duarteee52b7e2023-12-02 01:32:11 +000063 * Intent used to broadcast the change in connection state of the Headset profile.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070064 *
65 * <p>This intent will have 3 extras:
David Duarteee52b7e2023-12-02 01:32:11 +000066 *
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080067 * <ul>
David Duarteee52b7e2023-12-02 01:32:11 +000068 * <li>{@link #EXTRA_STATE} - The current state of the profile.
69 * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
70 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080071 * </ul>
David Duarteee52b7e2023-12-02 01:32:11 +000072 *
73 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
74 * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
75 * #STATE_DISCONNECTING}.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070076 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060077 @RequiresLegacyBluetoothPermission
78 @RequiresBluetoothConnectPermission
79 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070080 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
81 public static final String ACTION_CONNECTION_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -070082 "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070083
84 /**
David Duarteee52b7e2023-12-02 01:32:11 +000085 * Intent used to broadcast the change in the Audio Connection state of the HFP profile.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -070086 *
87 * <p>This intent will have 3 extras:
David Duarteee52b7e2023-12-02 01:32:11 +000088 *
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080089 * <ul>
David Duarteee52b7e2023-12-02 01:32:11 +000090 * <li>{@link #EXTRA_STATE} - The current state of the profile.
91 * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
92 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
Jaikumar Ganeshb3427572011-01-25 16:03:13 -080093 * </ul>
David Duarteee52b7e2023-12-02 01:32:11 +000094 *
95 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
96 * #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
Nick Pellydac4c0d2009-09-10 10:21:56 -070097 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060098 @RequiresLegacyBluetoothPermission
99 @RequiresBluetoothConnectPermission
100 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Nick Pellydac4c0d2009-09-10 10:21:56 -0700101 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
102 public static final String ACTION_AUDIO_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -0700103 "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
Nick Pellydac4c0d2009-09-10 10:21:56 -0700104
Jack He889d2342018-01-03 12:13:26 -0800105 /**
106 * Intent used to broadcast the selection of a connected device as active.
107 *
108 * <p>This intent will have one extra:
David Duarteee52b7e2023-12-02 01:32:11 +0000109 *
Jack He889d2342018-01-03 12:13:26 -0800110 * <ul>
David Duarteee52b7e2023-12-02 01:32:11 +0000111 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can be null if no device
112 * is active.
Jack He889d2342018-01-03 12:13:26 -0800113 * </ul>
114 *
Jack He889d2342018-01-03 12:13:26 -0800115 * @hide
116 */
William Escande5d864a42022-01-27 13:42:05 +0100117 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600118 @RequiresLegacyBluetoothPermission
119 @RequiresBluetoothConnectPermission
120 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He889d2342018-01-03 12:13:26 -0800121 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
William Escande5d864a42022-01-27 13:42:05 +0100122 @SuppressLint("ActionValue")
Jack He889d2342018-01-03 12:13:26 -0800123 public static final String ACTION_ACTIVE_DEVICE_CHANGED =
124 "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
Jaikumar Ganeshf48cda52010-04-02 14:44:43 -0700125
Nick Pellydac4c0d2009-09-10 10:21:56 -0700126 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000127 * Intent used to broadcast that the headset has posted a vendor-specific event.
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700128 *
129 * <p>This intent will have 4 extras and 1 category.
David Duarteee52b7e2023-12-02 01:32:11 +0000130 *
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800131 * <ul>
David Duarteee52b7e2023-12-02 01:32:11 +0000132 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
133 * <li>{@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor specific command
134 * <li>{@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT command type which can
135 * be one of {@link #AT_CMD_TYPE_READ}, {@link #AT_CMD_TYPE_TEST}, or {@link
136 * #AT_CMD_TYPE_SET}, {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}.
137 * <li>{@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command arguments.
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800138 * </ul>
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700139 *
David Duarteee52b7e2023-12-02 01:32:11 +0000140 * <p>The category is the Company ID of the vendor defining the vendor-specific command. {@link
141 * BluetoothAssignedNumbers}
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700142 *
David Duarteee52b7e2023-12-02 01:32:11 +0000143 * <p>For example, for Plantronics specific events Category will be {@link
144 * #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700145 *
David Duarteee52b7e2023-12-02 01:32:11 +0000146 * <p>For example, an AT+XEVENT=foo,3 will get translated into
147 *
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800148 * <ul>
David Duarteee52b7e2023-12-02 01:32:11 +0000149 * <li>EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT
150 * <li>EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET
151 * <li>EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3
Jaikumar Ganeshb3427572011-01-25 16:03:13 -0800152 * </ul>
Herb Jellinekaad41c52010-08-10 13:17:43 -0700153 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600154 @RequiresLegacyBluetoothPermission
155 @RequiresBluetoothConnectPermission
156 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Herb Jellinekaad41c52010-08-10 13:17:43 -0700157 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
158 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
159 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
160
161 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000162 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
163 * the name of the vendor-specific command.
Herb Jellinekaad41c52010-08-10 13:17:43 -0700164 */
165 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
166 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
167
168 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000169 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains the
170 * AT command type of the vendor-specific command.
Herb Jellinekaad41c52010-08-10 13:17:43 -0700171 */
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700172 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
173 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
174
175 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000176 * AT command type READ used with {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} For
177 * example, AT+VGM?. There are no arguments for this command type.
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700178 */
179 public static final int AT_CMD_TYPE_READ = 0;
180
181 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000182 * AT command type TEST used with {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} For
183 * example, AT+VGM=?. There are no arguments for this command type.
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700184 */
185 public static final int AT_CMD_TYPE_TEST = 1;
186
187 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000188 * AT command type SET used with {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} For
189 * example, AT+VGM=<args>.
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700190 */
191 public static final int AT_CMD_TYPE_SET = 2;
192
193 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000194 * AT command type BASIC used with {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} For
195 * example, ATD. Single character commands and everything following the character are arguments.
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700196 */
197 public static final int AT_CMD_TYPE_BASIC = 3;
198
199 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000200 * AT command type ACTION used with {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} For
201 * example, AT+CHUP. There are no arguments for action commands.
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700202 */
203 public static final int AT_CMD_TYPE_ACTION = 4;
Herb Jellinekaad41c52010-08-10 13:17:43 -0700204
205 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000206 * A Parcelable String array extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
207 * intents that contains the arguments to the vendor-specific command.
Herb Jellinekaad41c52010-08-10 13:17:43 -0700208 */
209 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
210 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
211
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700212 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000213 * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} for the
214 * companyId
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700215 */
Jack He910201b2017-08-22 16:06:54 -0700216 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY =
Jaikumar Ganeshee628ed2010-09-29 11:34:59 -0700217 "android.bluetooth.headset.intent.category.companyid";
218
David Duarteee52b7e2023-12-02 01:32:11 +0000219 /** A vendor-specific command for unsolicited result code. */
Edward Jee7c81f1f2013-08-16 04:07:49 -0700220 public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
221
222 /**
Jack He7f9c70b2017-06-20 17:07:40 -0700223 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700224 *
Jack He7f9c70b2017-06-20 17:07:40 -0700225 * @hide
226 */
Jack He0dfd69b2017-06-20 17:09:47 -0700227 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL";
228
229 /**
230 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700231 *
Jack He0dfd69b2017-06-20 17:09:47 -0700232 * @hide
233 */
234 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV";
235
236 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000237 * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV}
Jack He910201b2017-08-22 16:06:54 -0700238 *
Jack He0dfd69b2017-06-20 17:09:47 -0700239 * @hide
240 */
241 public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1;
242
243 /**
244 * A vendor-specific AT command
Jack He910201b2017-08-22 16:06:54 -0700245 *
Jack He0dfd69b2017-06-20 17:09:47 -0700246 * @hide
247 */
Jack He7f9c70b2017-06-20 17:07:40 -0700248 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT";
249
250 /**
251 * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT}
Jack He910201b2017-08-22 16:06:54 -0700252 *
Jack He7f9c70b2017-06-20 17:07:40 -0700253 * @hide
254 */
255 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY";
256
257 /**
Poompatai Puntitpong0e4d90a2023-11-03 08:58:09 +0000258 * A vendor-specific AT command that asks for the information about device manufacturer.
259 *
260 * @hide
261 */
262 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_CGMI = "+CGMI";
263
264 /**
265 * A vendor-specific AT command that asks for the information about the model of the device.
266 *
267 * @hide
268 */
269 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_CGMM = "+CGMM";
270
271 /**
272 * A vendor-specific AT command that asks for the revision information, for Android we will
273 * return the OS version and build number.
274 *
275 * @hide
276 */
277 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_CGMR = "+CGMR";
278
279 /**
280 * A vendor-specific AT command that asks for the device's serial number.
281 *
282 * @hide
283 */
284 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_CGSN = "+CGSN";
285
286 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000287 * Headset state when SCO audio is not connected. This state can be one of {@link #EXTRA_STATE}
288 * or {@link #EXTRA_PREVIOUS_STATE} of {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700289 */
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800290 public static final int STATE_AUDIO_DISCONNECTED = 10;
Herb Jellinekaad41c52010-08-10 13:17:43 -0700291
292 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000293 * Headset state when SCO audio is connecting. This state can be one of {@link #EXTRA_STATE} or
294 * {@link #EXTRA_PREVIOUS_STATE} of {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700295 */
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800296 public static final int STATE_AUDIO_CONNECTING = 11;
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700297
298 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000299 * Headset state when SCO audio is connected. This state can be one of {@link #EXTRA_STATE} or
300 * {@link #EXTRA_PREVIOUS_STATE} of {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Nick Pellydac4c0d2009-09-10 10:21:56 -0700301 */
Chienyuan0d3bada2019-05-29 10:29:30 +0800302 public static final int STATE_AUDIO_CONNECTED = 12;
Mudumba Ananth3246de62016-02-29 02:14:36 -0800303
304 /**
305 * Intent used to broadcast the headset's indicator status
306 *
307 * <p>This intent will have 3 extras:
David Duarteee52b7e2023-12-02 01:32:11 +0000308 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800309 * <ul>
David Duarteee52b7e2023-12-02 01:32:11 +0000310 * <li>{@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which is
311 * supported by the headset ( as indicated by AT+BIND command in the SLC sequence) or
312 * whose value is changed (indicated by AT+BIEV command)
313 * <li>{@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator.
314 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - Remote device.
Mudumba Ananth3246de62016-02-29 02:14:36 -0800315 * </ul>
David Duarteee52b7e2023-12-02 01:32:11 +0000316 *
Jack He48f6a8a2017-06-22 12:56:54 -0700317 * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators
David Duarteee52b7e2023-12-02 01:32:11 +0000318 * are given an assigned number. Below shows the assigned number of Indicator added so far -
319 * Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled - Battery Level - 2, Valid
320 * Values: 0~100 - Remaining level of Battery
Jack He910201b2017-08-22 16:06:54 -0700321 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800322 * @hide
323 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600324 @RequiresLegacyBluetoothPermission
325 @RequiresBluetoothConnectPermission
326 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jeff Sharkeyd7c55662021-04-20 12:30:37 -0600327 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Mudumba Ananth3246de62016-02-29 02:14:36 -0800328 public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
329 "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
330
331 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000332 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} intents that contains the
333 * assigned number of the headset indicator as defined by Bluetooth SIG that is being sent.
334 * Value range is 0-65535 as defined in HFP 1.7
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_ID =
339 "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
340
341 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000342 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} intents that contains the
343 * value of the Headset indicator that is being sent.
Jack He910201b2017-08-22 16:06:54 -0700344 *
Mudumba Ananth3246de62016-02-29 02:14:36 -0800345 * @hide
346 */
347 public static final String EXTRA_HF_INDICATORS_IND_VALUE =
348 "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
349
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600350 private final BluetoothAdapter mAdapter;
351 private final AttributionSource mAttributionSource;
David Duartef5b3bc52023-10-17 00:55:06 +0000352
353 private IBluetoothHeadset mService;
fredc3c719642012-04-12 00:02:00 -0700354
David Duarteee52b7e2023-12-02 01:32:11 +0000355 /** Create a BluetoothHeadset proxy object. */
David Duartef5b3bc52023-10-17 00:55:06 +0000356 /* package */ BluetoothHeadset(Context context, BluetoothAdapter adapter) {
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600357 mAdapter = adapter;
358 mAttributionSource = adapter.getAttributionSource();
David Duartef5b3bc52023-10-17 00:55:06 +0000359 mService = null;
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() {
David Duartef5b3bc52023-10-17 00:55:06 +0000371 mAdapter.closeProfileProxy(this);
372 }
373
374 /** @hide */
375 @Override
376 public void onServiceConnected(IBinder service) {
377 mService = IBluetoothHeadset.Stub.asInterface(service);
378 }
379
380 /** @hide */
381 @Override
382 public void onServiceDisconnected() {
383 mService = null;
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800384 }
fredc3c719642012-04-12 00:02:00 -0700385
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800386 private IBluetoothHeadset getService() {
David Duartef5b3bc52023-10-17 00:55:06 +0000387 return mService;
388 }
389
390 /** @hide */
391 @Override
392 public BluetoothAdapter getAdapter() {
393 return mAdapter;
Jeff Sharkeydb38eb72021-05-24 10:00:23 -0600394 }
395
David Duarte5a02bb42023-12-04 23:07:42 +0000396 /** @hide */
Jeff Sharkeydb38eb72021-05-24 10:00:23 -0600397 @Override
David Duarte5a02bb42023-12-04 23:07:42 +0000398 @SuppressWarnings("Finalize") // empty finalize for api signature
Jeff Sharkeydb38eb72021-05-24 10:00:23 -0600399 protected void finalize() throws Throwable {
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800400 // The empty finalize needs to be kept or the
401 // cts signature tests would fail.
The Android Open Source Project33897762009-03-03 19:31:44 -0800402 }
403
404 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700405 * Initiate connection to a profile of the remote bluetooth device.
406 *
David Duarteee52b7e2023-12-02 01:32:11 +0000407 * <p>Currently, the system supports only 1 connection to the headset/handsfree profile. The API
408 * will automatically disconnect connected devices before connecting.
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700409 *
David Duarteee52b7e2023-12-02 01:32:11 +0000410 * <p>This API returns false in scenarios like the profile on the device is already connected or
411 * Bluetooth is not turned on. When this API returns true, it is guaranteed that connection
412 * state intent for the profile will be broadcasted with the state. Users can get the connection
413 * state of the profile from this intent.
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700414 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700415 * @param device Remote Bluetooth Device
Jack He910201b2017-08-22 16:06:54 -0700416 * @return false on immediate error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700417 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800418 */
Selim Guruna117cb372017-10-17 17:01:38 -0700419 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600420 @RequiresLegacyBluetoothAdminPermission
421 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +0000422 @RequiresPermission(
423 allOf = {
424 android.Manifest.permission.BLUETOOTH_CONNECT,
425 android.Manifest.permission.MODIFY_PHONE_STATE,
426 })
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700427 public boolean connect(BluetoothDevice device) {
428 if (DBG) log("connect(" + device + ")");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800429 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100430 if (service == null) {
431 Log.w(TAG, "Proxy not attached to service");
432 if (DBG) log(Log.getStackTraceString(new Throwable()));
433 } else if (isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800434 try {
William Escande879a70e2024-03-21 21:01:41 -0700435 return service.connect(device, mAttributionSource);
436 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100437 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700438 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800439 }
William Escande879a70e2024-03-21 21:01:41 -0700440 return false;
The Android Open Source Project33897762009-03-03 19:31:44 -0800441 }
442
443 /**
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700444 * Initiate disconnection from a profile
445 *
David Duarteee52b7e2023-12-02 01:32:11 +0000446 * <p>This API will return false in scenarios like the profile on the Bluetooth device is not in
447 * connected state etc. When this API returns, true, it is guaranteed that the connection state
448 * change intent will be broadcasted with the state. Users can get the disconnection state of
449 * the profile from this intent.
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700450 *
David Duarteee52b7e2023-12-02 01:32:11 +0000451 * <p>If the disconnection is initiated by a remote device, the state will transition from
452 * {@link #STATE_CONNECTED} to {@link #STATE_DISCONNECTED}. If the disconnect is initiated by
453 * the host (local) device the state will transition from {@link #STATE_CONNECTED} to state
454 * {@link #STATE_DISCONNECTING} to state {@link #STATE_DISCONNECTED}. The transition to {@link
455 * #STATE_DISCONNECTING} can be used to distinguish between the two scenarios.
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700456 *
Jaikumar Ganesha7266d32011-05-26 13:56:40 -0700457 * @param device Remote Bluetooth Device
Jack He910201b2017-08-22 16:06:54 -0700458 * @return false on immediate error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700459 * @hide
The Android Open Source Project33897762009-03-03 19:31:44 -0800460 */
Selim Guruna117cb372017-10-17 17:01:38 -0700461 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600462 @RequiresLegacyBluetoothAdminPermission
463 @RequiresBluetoothConnectPermission
464 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700465 public boolean disconnect(BluetoothDevice device) {
466 if (DBG) log("disconnect(" + device + ")");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800467 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100468 if (service == null) {
469 Log.w(TAG, "Proxy not attached to service");
470 if (DBG) log(Log.getStackTraceString(new Throwable()));
471 } else if (isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800472 try {
William Escande879a70e2024-03-21 21:01:41 -0700473 return service.disconnect(device, mAttributionSource);
474 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100475 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700476 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800477 }
William Escande879a70e2024-03-21 21:01:41 -0700478 return false;
The Android Open Source Project33897762009-03-03 19:31:44 -0800479 }
480
David Duarteee52b7e2023-12-02 01:32:11 +0000481 /** {@inheritDoc} */
Jack He9e045d22017-08-22 21:21:23 -0700482 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600483 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600484 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700485 public List<BluetoothDevice> getConnectedDevices() {
Matthew Xief8035a72012-10-09 22:10:37 -0700486 if (VDBG) log("getConnectedDevices()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800487 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100488 if (service == null) {
489 Log.w(TAG, "Proxy not attached to service");
490 if (DBG) log(Log.getStackTraceString(new Throwable()));
491 } else if (isEnabled()) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800492 try {
Jeff Sharkey98f30442021-06-03 09:26:53 -0600493 return Attributable.setAttributionSource(
William Escande879a70e2024-03-21 21:01:41 -0700494 service.getConnectedDevices(mAttributionSource), mAttributionSource);
495 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100496 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 Escande5e8e1532024-03-27 18:02:23 +0000499 return Collections.emptyList();
The Android Open Source Project33897762009-03-03 19:31:44 -0800500 }
501
David Duarteee52b7e2023-12-02 01:32:11 +0000502 /** {@inheritDoc} */
Jack He9e045d22017-08-22 21:21:23 -0700503 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600504 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600505 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganeshd8fc4dd2010-10-18 16:41:53 -0700506 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Matthew Xief8035a72012-10-09 22:10:37 -0700507 if (VDBG) log("getDevicesMatchingStates()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800508 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100509 if (service == null) {
510 Log.w(TAG, "Proxy not attached to service");
511 if (DBG) log(Log.getStackTraceString(new Throwable()));
512 } else if (isEnabled()) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800513 try {
Jeff Sharkey98f30442021-06-03 09:26:53 -0600514 return Attributable.setAttributionSource(
William Escande879a70e2024-03-21 21:01:41 -0700515 service.getDevicesMatchingConnectionStates(states, mAttributionSource),
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600516 mAttributionSource);
William Escande879a70e2024-03-21 21:01:41 -0700517 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100518 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700519 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800520 }
William Escande5e8e1532024-03-27 18:02:23 +0000521 return Collections.emptyList();
The Android Open Source Project33897762009-03-03 19:31:44 -0800522 }
523
David Duarteee52b7e2023-12-02 01:32:11 +0000524 /** {@inheritDoc} */
Jack He9e045d22017-08-22 21:21:23 -0700525 @Override
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600526 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600527 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700528 public int getConnectionState(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700529 if (VDBG) log("getConnectionState(" + device + ")");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800530 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100531 if (service == null) {
532 Log.w(TAG, "Proxy not attached to service");
533 if (DBG) log(Log.getStackTraceString(new Throwable()));
534 } else if (isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800535 try {
William Escande879a70e2024-03-21 21:01:41 -0700536 return service.getConnectionState(device, mAttributionSource);
537 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100538 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700539 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800540 }
William Escande879a70e2024-03-21 21:01:41 -0700541 return BluetoothProfile.STATE_DISCONNECTED;
The Android Open Source Project33897762009-03-03 19:31:44 -0800542 }
543
544 /**
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800545 * Set connection policy of the profile
546 *
David Duarteee52b7e2023-12-02 01:32:11 +0000547 * <p>The device should already be paired. Connection policy can be one of {@link
548 * #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, {@link
549 * #CONNECTION_POLICY_UNKNOWN}
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800550 *
551 * @param device Paired bluetooth device
552 * @param connectionPolicy is the connection policy to set to for this profile
553 * @return true if connectionPolicy is set, false on error
554 * @hide
555 */
556 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600557 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +0000558 @RequiresPermission(
559 allOf = {
560 android.Manifest.permission.BLUETOOTH_CONNECT,
561 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
562 android.Manifest.permission.MODIFY_PHONE_STATE,
563 })
564 public boolean setConnectionPolicy(
565 @NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) {
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800566 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800567 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100568 if (service == null) {
569 Log.w(TAG, "Proxy not attached to service");
570 if (DBG) log(Log.getStackTraceString(new Throwable()));
David Duarteee52b7e2023-12-02 01:32:11 +0000571 } else if (isEnabled()
572 && isValidDevice(device)
William Escande9a4b7c12021-12-16 16:07:55 +0100573 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
David Duarteee52b7e2023-12-02 01:32:11 +0000574 || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800575 try {
William Escande879a70e2024-03-21 21:01:41 -0700576 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
577 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100578 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700579 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800580 }
William Escande879a70e2024-03-21 21:01:41 -0700581 return false;
The Android Open Source Project33897762009-03-03 19:31:44 -0800582 }
583
584 /**
Felix Stern692d36d2023-09-08 09:55:56 +0000585 * Get the priority of the profile.
586 *
David Duarteee52b7e2023-12-02 01:32:11 +0000587 * <p>The priority can be any of: {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, {@link
588 * #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
Felix Stern692d36d2023-09-08 09:55:56 +0000589 *
590 * @param device Bluetooth device
591 * @return priority of the device
592 * @hide
593 */
594 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
595 @RequiresLegacyBluetoothPermission
596 @RequiresBluetoothConnectPermission
597 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
598 public int getPriority(BluetoothDevice device) {
599 if (VDBG) log("getPriority(" + device + ")");
600 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
601 }
602
603 /**
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800604 * Get the connection policy of the profile.
605 *
David Duarteee52b7e2023-12-02 01:32:11 +0000606 * <p>The connection policy can be any of: {@link #CONNECTION_POLICY_ALLOWED}, {@link
607 * #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800608 *
609 * @param device Bluetooth device
610 * @return connection policy of the device
611 * @hide
612 */
613 @SystemApi
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600614 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +0000615 @RequiresPermission(
616 allOf = {
617 android.Manifest.permission.BLUETOOTH_CONNECT,
618 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
619 })
Rahul Sabnise8bac9b2019-11-27 18:09:33 -0800620 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
621 if (VDBG) log("getConnectionPolicy(" + device + ")");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800622 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100623 if (service == null) {
624 Log.w(TAG, "Proxy not attached to service");
625 if (DBG) log(Log.getStackTraceString(new Throwable()));
626 } else if (isEnabled() && isValidDevice(device)) {
The Android Open Source Project33897762009-03-03 19:31:44 -0800627 try {
William Escande879a70e2024-03-21 21:01:41 -0700628 return service.getConnectionPolicy(device, mAttributionSource);
629 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100630 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700631 }
The Android Open Source Project33897762009-03-03 19:31:44 -0800632 }
William Escande879a70e2024-03-21 21:01:41 -0700633 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700634 }
635
636 /**
Rahul Sabnisc611e732020-12-14 10:54:45 -0800637 * Checks whether the headset supports some form of noise reduction
638 *
639 * @param device Bluetooth device
640 * @return true if echo cancellation and/or noise reduction is supported, false otherwise
641 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600642 @RequiresLegacyBluetoothPermission
643 @RequiresBluetoothConnectPermission
644 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Rahul Sabnisc611e732020-12-14 10:54:45 -0800645 public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) {
646 if (DBG) log("isNoiseReductionSupported()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800647 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100648 if (service == null) {
649 Log.w(TAG, "Proxy not attached to service");
650 if (DBG) log(Log.getStackTraceString(new Throwable()));
651 } else if (isEnabled() && isValidDevice(device)) {
Rahul Sabnisc611e732020-12-14 10:54:45 -0800652 try {
William Escande879a70e2024-03-21 21:01:41 -0700653 return service.isNoiseReductionSupported(device, mAttributionSource);
654 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100655 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Rahul Sabnisc611e732020-12-14 10:54:45 -0800656 }
657 }
William Escande879a70e2024-03-21 21:01:41 -0700658 return false;
Rahul Sabnisc611e732020-12-14 10:54:45 -0800659 }
660
661 /**
662 * Checks whether the headset supports voice recognition
663 *
664 * @param device Bluetooth device
665 * @return true if voice recognition is supported, false otherwise
666 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600667 @RequiresLegacyBluetoothPermission
668 @RequiresBluetoothConnectPermission
669 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Rahul Sabnisc611e732020-12-14 10:54:45 -0800670 public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
671 if (DBG) log("isVoiceRecognitionSupported()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800672 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100673 if (service == null) {
674 Log.w(TAG, "Proxy not attached to service");
675 if (DBG) log(Log.getStackTraceString(new Throwable()));
676 } else if (isEnabled() && isValidDevice(device)) {
Rahul Sabnisc611e732020-12-14 10:54:45 -0800677 try {
William Escande879a70e2024-03-21 21:01:41 -0700678 return service.isVoiceRecognitionSupported(device, mAttributionSource);
679 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100680 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Rahul Sabnisc611e732020-12-14 10:54:45 -0800681 }
682 }
William Escande879a70e2024-03-21 21:01:41 -0700683 return false;
Rahul Sabnisc611e732020-12-14 10:54:45 -0800684 }
685
686 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000687 * Start Bluetooth voice recognition. This methods sends the voice recognition AT command to the
688 * headset and establishes the audio connection.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700689 *
David Duarteee52b7e2023-12-02 01:32:11 +0000690 * <p>Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. If this function returns true,
691 * this intent will be broadcasted with {@link #EXTRA_STATE} set to {@link
692 * #STATE_AUDIO_CONNECTING}.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700693 *
David Duarteee52b7e2023-12-02 01:32:11 +0000694 * <p>{@link #EXTRA_STATE} will transition from {@link #STATE_AUDIO_CONNECTING} to {@link
695 * #STATE_AUDIO_CONNECTED} when audio connection is established and to {@link
696 * #STATE_AUDIO_DISCONNECTED} in case of failure to establish the audio connection.
Jaikumar Ganesh8ea890d2010-11-11 10:49:46 -0800697 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700698 * @param device Bluetooth headset
Jack Hec46a01e2018-05-02 19:10:56 -0700699 * @return false if there is no headset connected, or the connected headset doesn't support
David Duarteee52b7e2023-12-02 01:32:11 +0000700 * voice recognition, or voice recognition is already started, or audio channel is occupied,
701 * or on error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700702 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600703 @RequiresLegacyBluetoothPermission
704 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +0000705 @RequiresPermission(
706 allOf = {
707 android.Manifest.permission.BLUETOOTH_CONNECT,
708 android.Manifest.permission.MODIFY_PHONE_STATE,
709 })
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700710 public boolean startVoiceRecognition(BluetoothDevice device) {
711 if (DBG) log("startVoiceRecognition()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800712 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100713 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)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700717 try {
William Escande879a70e2024-03-21 21:01:41 -0700718 return service.startVoiceRecognition(device, mAttributionSource);
719 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100720 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700721 }
722 }
William Escande879a70e2024-03-21 21:01:41 -0700723 return false;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700724 }
725
726 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000727 * Stop Bluetooth Voice Recognition mode, and shut down the Bluetooth audio path.
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700728 *
David Duarteee52b7e2023-12-02 01:32:11 +0000729 * <p>Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. If this function returns true,
730 * this intent will be broadcasted with {@link #EXTRA_STATE} set to {@link
731 * #STATE_AUDIO_DISCONNECTED}.
Jack Hec46a01e2018-05-02 19:10:56 -0700732 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700733 * @param device Bluetooth headset
David Duarteee52b7e2023-12-02 01:32:11 +0000734 * @return false if there is no headset connected, or voice recognition has not started, or
735 * voice recognition has ended on this headset, or on error, true otherwise
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700736 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600737 @RequiresLegacyBluetoothPermission
738 @RequiresBluetoothConnectPermission
739 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700740 public boolean stopVoiceRecognition(BluetoothDevice device) {
741 if (DBG) log("stopVoiceRecognition()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800742 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100743 if (service == null) {
744 Log.w(TAG, "Proxy not attached to service");
745 if (DBG) log(Log.getStackTraceString(new Throwable()));
746 } else if (isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700747 try {
William Escande879a70e2024-03-21 21:01:41 -0700748 return service.stopVoiceRecognition(device, mAttributionSource);
749 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100750 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700751 }
752 }
William Escande879a70e2024-03-21 21:01:41 -0700753 return false;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700754 }
755
756 /**
757 * Check if Bluetooth SCO audio is connected.
758 *
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700759 * @param device Bluetooth headset
Jack He910201b2017-08-22 16:06:54 -0700760 * @return true if SCO is connected, false otherwise or on error
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700761 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600762 @RequiresLegacyBluetoothPermission
763 @RequiresBluetoothConnectPermission
764 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700765 public boolean isAudioConnected(BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700766 if (VDBG) log("isAudioConnected()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800767 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100768 if (service == null) {
769 Log.w(TAG, "Proxy not attached to service");
770 if (DBG) log(Log.getStackTraceString(new Throwable()));
771 } else if (isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700772 try {
William Escande879a70e2024-03-21 21:01:41 -0700773 return service.isAudioConnected(device, mAttributionSource);
774 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100775 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh2af07762010-08-24 17:36:13 -0700776 }
777 }
William Escande879a70e2024-03-21 21:01:41 -0700778 return false;
The Android Open Source Project33897762009-03-03 19:31:44 -0800779 }
780
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800781 /** @hide */
782 @Retention(RetentionPolicy.SOURCE)
David Duarteee52b7e2023-12-02 01:32:11 +0000783 @IntDef(
784 value = {
785 BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
786 BluetoothHeadset.STATE_AUDIO_CONNECTING,
787 BluetoothHeadset.STATE_AUDIO_CONNECTED,
788 BluetoothStatusCodes.ERROR_TIMEOUT
789 })
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800790 public @interface GetAudioStateReturnValues {}
791
Jaikumar Ganeshb84bbd92010-06-02 14:36:14 -0700792 /**
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700793 * Get the current audio state of the Headset.
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800794 *
795 * @param device is the Bluetooth device for which the audio state is being queried
796 * @return the audio state of the device or an error code
Rahul Sabnis9a5e3512022-03-17 23:37:38 -0700797 * @throws NullPointerException if the device is null
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700798 * @hide
799 */
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800800 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600801 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +0000802 @RequiresPermission(
803 allOf = {
804 android.Manifest.permission.BLUETOOTH_CONNECT,
805 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
806 })
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800807 public @GetAudioStateReturnValues int getAudioState(@NonNull BluetoothDevice device) {
Matthew Xief8035a72012-10-09 22:10:37 -0700808 if (VDBG) log("getAudioState");
Rahul Sabnis9a5e3512022-03-17 23:37:38 -0700809 Objects.requireNonNull(device);
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800810 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100811 if (service == null) {
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700812 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +0100813 if (DBG) log(Log.getStackTraceString(new Throwable()));
814 } else if (!isDisabled()) {
815 try {
William Escande879a70e2024-03-21 21:01:41 -0700816 return service.getAudioState(device, mAttributionSource);
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800817 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100818 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
William Escande17a262f2023-09-06 21:48:54 -0700819 throw e.rethrowAsRuntimeException();
William Escande9a4b7c12021-12-16 16:07:55 +0100820 }
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700821 }
William Escande879a70e2024-03-21 21:01:41 -0700822 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
Jaikumar Ganesh23501e22010-11-01 11:59:57 -0700823 }
824
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800825 /** @hide */
826 @Retention(RetentionPolicy.SOURCE)
David Duarteee52b7e2023-12-02 01:32:11 +0000827 @IntDef(
828 value = {
829 BluetoothStatusCodes.SUCCESS,
830 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
831 BluetoothStatusCodes.ERROR_TIMEOUT,
832 BluetoothStatusCodes.ERROR_UNKNOWN,
833 })
Md Shahriar Hossain Sajib8b4df2f2022-02-18 13:56:27 +0800834 public @interface SetAudioRouteAllowedReturnValues {}
835
836 /** @hide */
837 @Retention(RetentionPolicy.SOURCE)
David Duarteee52b7e2023-12-02 01:32:11 +0000838 @IntDef(
839 value = {
840 BluetoothStatusCodes.ALLOWED,
841 BluetoothStatusCodes.NOT_ALLOWED,
842 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
843 BluetoothStatusCodes.ERROR_TIMEOUT,
844 BluetoothStatusCodes.ERROR_UNKNOWN,
845 })
Md Shahriar Hossain Sajib8b4df2f2022-02-18 13:56:27 +0800846 public @interface GetAudioRouteAllowedReturnValues {}
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800847
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -0700848 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000849 * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
850 * audio to the HF unless explicitly told to. This method should be used in cases where the SCO
851 * channel is shared between multiple profiles and must be delegated by a source knowledgeable.
Bryce Lee0e154a32015-11-16 08:55:52 -0800852 *
David Duarteee52b7e2023-12-02 01:32:11 +0000853 * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
854 * @return {@link BluetoothStatusCodes#SUCCESS} upon successful setting, otherwise an error
855 * code.
Bryce Lee0e154a32015-11-16 08:55:52 -0800856 * @hide
857 */
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800858 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600859 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +0000860 @RequiresPermission(
861 allOf = {
862 android.Manifest.permission.BLUETOOTH_CONNECT,
863 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
864 })
Md Shahriar Hossain Sajib8b4df2f2022-02-18 13:56:27 +0800865 public @SetAudioRouteAllowedReturnValues int setAudioRouteAllowed(boolean allowed) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800866 if (VDBG) log("setAudioRouteAllowed");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800867 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100868 if (service == null) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800869 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +0100870 if (DBG) log(Log.getStackTraceString(new Throwable()));
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800871 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700872 } else if (isEnabled()) {
873 try {
William Escande879a70e2024-03-21 21:01:41 -0700874 service.setAudioRouteAllowed(allowed, mAttributionSource);
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700875 return BluetoothStatusCodes.SUCCESS;
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700876 } catch (RemoteException e) {
877 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
William Escande17a262f2023-09-06 21:48:54 -0700878 throw e.rethrowAsRuntimeException();
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700879 }
Bryce Lee0e154a32015-11-16 08:55:52 -0800880 }
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700881
882 Log.e(TAG, "setAudioRouteAllowed: Bluetooth disabled, but profile service still bound");
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800883 return BluetoothStatusCodes.ERROR_UNKNOWN;
Bryce Lee0e154a32015-11-16 08:55:52 -0800884 }
885
886 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000887 * @return {@link BluetoothStatusCodes#ALLOWED} if audio routing is allowed, {@link
888 * BluetoothStatusCodes#NOT_ALLOWED} if audio routing is not allowed, or an error code if an
889 * error occurs. see {@link #setAudioRouteAllowed(boolean)}.
Bryce Lee0e154a32015-11-16 08:55:52 -0800890 * @hide
891 */
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800892 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600893 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +0000894 @RequiresPermission(
895 allOf = {
896 android.Manifest.permission.BLUETOOTH_CONNECT,
897 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
898 })
Md Shahriar Hossain Sajib8b4df2f2022-02-18 13:56:27 +0800899 public @GetAudioRouteAllowedReturnValues int getAudioRouteAllowed() {
Bryce Lee0e154a32015-11-16 08:55:52 -0800900 if (VDBG) log("getAudioRouteAllowed");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800901 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100902 if (service == null) {
Bryce Lee0e154a32015-11-16 08:55:52 -0800903 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +0100904 if (DBG) log(Log.getStackTraceString(new Throwable()));
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800905 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700906 } else if (isEnabled()) {
907 try {
William Escande879a70e2024-03-21 21:01:41 -0700908 return service.getAudioRouteAllowed(mAttributionSource)
David Duarteee52b7e2023-12-02 01:32:11 +0000909 ? BluetoothStatusCodes.ALLOWED
910 : BluetoothStatusCodes.NOT_ALLOWED;
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700911 } catch (RemoteException e) {
912 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
William Escande17a262f2023-09-06 21:48:54 -0700913 throw e.rethrowAsRuntimeException();
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700914 }
Bryce Lee0e154a32015-11-16 08:55:52 -0800915 }
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700916
917 Log.e(TAG, "getAudioRouteAllowed: Bluetooth disabled, but profile service still bound");
Md Shahriar Hossain Sajib7901c002022-01-19 15:37:26 +0800918 return BluetoothStatusCodes.ERROR_UNKNOWN;
Bryce Lee0e154a32015-11-16 08:55:52 -0800919 }
920
921 /**
Jack He798d7282017-05-09 17:16:01 -0700922 * Force SCO audio to be opened regardless any other restrictions
923 *
Jack He910201b2017-08-22 16:06:54 -0700924 * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio
David Duarteee52b7e2023-12-02 01:32:11 +0000925 * False to use SCO audio in normal manner
Jack He798d7282017-05-09 17:16:01 -0700926 * @hide
927 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600928 @RequiresBluetoothConnectPermission
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -0600929 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He798d7282017-05-09 17:16:01 -0700930 public void setForceScoAudio(boolean forced) {
931 if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800932 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100933 if (service == null) {
Jack He798d7282017-05-09 17:16:01 -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 (isEnabled()) {
937 try {
William Escande879a70e2024-03-21 21:01:41 -0700938 service.setForceScoAudio(forced, mAttributionSource);
939 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100940 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
941 }
Jack He798d7282017-05-09 17:16:01 -0700942 }
943 }
944
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800945 /** @hide */
946 @Retention(RetentionPolicy.SOURCE)
David Duarteee52b7e2023-12-02 01:32:11 +0000947 @IntDef(
948 value = {
949 BluetoothStatusCodes.SUCCESS,
950 BluetoothStatusCodes.ERROR_UNKNOWN,
951 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
952 BluetoothStatusCodes.ERROR_TIMEOUT,
953 BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED,
954 BluetoothStatusCodes.ERROR_NO_ACTIVE_DEVICES,
955 BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE,
956 BluetoothStatusCodes.ERROR_AUDIO_ROUTE_BLOCKED,
957 BluetoothStatusCodes.ERROR_CALL_ACTIVE,
958 BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED
959 })
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800960 public @interface ConnectAudioReturnValues {}
Matthew Xief3ee3512012-02-16 16:57:18 -0800961
962 /**
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800963 * Initiates a connection of SCO audio to the current active HFP device. The active HFP device
964 * can be identified with {@link BluetoothAdapter#getActiveDevices(int)}.
David Duarteee52b7e2023-12-02 01:32:11 +0000965 *
966 * <p>If this function returns {@link BluetoothStatusCodes#SUCCESS}, the intent {@link
967 * #ACTION_AUDIO_STATE_CHANGED} will be broadcasted twice. First with {@link #EXTRA_STATE} set
968 * to {@link #STATE_AUDIO_CONNECTING}. This will be followed by a broadcast with {@link
969 * #EXTRA_STATE} set to either {@link #STATE_AUDIO_CONNECTED} if the audio connection is
970 * established or {@link #STATE_AUDIO_DISCONNECTED} if there was a failure in establishing the
971 * audio connection.
Matthew Xief3ee3512012-02-16 16:57:18 -0800972 *
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800973 * @return whether the connection was successfully initiated or an error code on failure
Matthew Xief3ee3512012-02-16 16:57:18 -0800974 * @hide
975 */
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800976 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -0600977 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +0000978 @RequiresPermission(
979 allOf = {
980 android.Manifest.permission.BLUETOOTH_CONNECT,
981 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
982 })
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800983 public @ConnectAudioReturnValues int connectAudio() {
William Escande9a4b7c12021-12-16 16:07:55 +0100984 if (VDBG) log("connectAudio()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -0800985 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +0100986 if (service == null) {
Matthew Xief3ee3512012-02-16 16:57:18 -0800987 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +0100988 if (DBG) log(Log.getStackTraceString(new Throwable()));
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -0800989 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
William Escande9a4b7c12021-12-16 16:07:55 +0100990 } else if (isEnabled()) {
991 try {
William Escande879a70e2024-03-21 21:01:41 -0700992 return service.connectAudio(mAttributionSource);
Rahul Sabnis5ed1b842021-12-23 11:36:40 -0800993 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +0100994 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
William Escande17a262f2023-09-06 21:48:54 -0700995 throw e.rethrowAsRuntimeException();
William Escande9a4b7c12021-12-16 16:07:55 +0100996 }
Matthew Xief3ee3512012-02-16 16:57:18 -0800997 }
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -0700998
999 Log.e(TAG, "connectAudio: Bluetooth disabled, but profile service still bound");
William Escande879a70e2024-03-21 21:01:41 -07001000 return BluetoothStatusCodes.ERROR_UNKNOWN;
Matthew Xief3ee3512012-02-16 16:57:18 -08001001 }
1002
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001003 /** @hide */
1004 @Retention(RetentionPolicy.SOURCE)
David Duarteee52b7e2023-12-02 01:32:11 +00001005 @IntDef(
1006 value = {
1007 BluetoothStatusCodes.SUCCESS,
1008 BluetoothStatusCodes.ERROR_UNKNOWN,
1009 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
1010 BluetoothStatusCodes.ERROR_TIMEOUT,
1011 BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED,
1012 BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED
1013 })
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001014 public @interface DisconnectAudioReturnValues {}
1015
Matthew Xief3ee3512012-02-16 16:57:18 -08001016 /**
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001017 * Initiates a disconnection of HFP SCO audio from actively connected devices. It also tears
1018 * down voice recognition or virtual voice call, if any exists.
Matthew Xief3ee3512012-02-16 16:57:18 -08001019 *
David Duarteee52b7e2023-12-02 01:32:11 +00001020 * <p>If this function returns {@link BluetoothStatusCodes#SUCCESS}, the intent {@link
1021 * #ACTION_AUDIO_STATE_CHANGED} will be broadcasted with {@link #EXTRA_STATE} set to {@link
1022 * #STATE_AUDIO_DISCONNECTED}.
Jack Hec46a01e2018-05-02 19:10:56 -07001023 *
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001024 * @return whether the disconnection was initiated successfully or an error code on failure
Matthew Xief3ee3512012-02-16 16:57:18 -08001025 * @hide
1026 */
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001027 @SystemApi
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001028 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +00001029 @RequiresPermission(
1030 allOf = {
1031 android.Manifest.permission.BLUETOOTH_CONNECT,
1032 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1033 })
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001034 public @DisconnectAudioReturnValues int disconnectAudio() {
William Escande9a4b7c12021-12-16 16:07:55 +01001035 if (VDBG) log("disconnectAudio()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001036 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001037 if (service == null) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001038 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001039 if (DBG) log(Log.getStackTraceString(new Throwable()));
Rahul Sabnisb83a2fe2022-01-24 14:38:40 -08001040 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
William Escande9a4b7c12021-12-16 16:07:55 +01001041 } else if (isEnabled()) {
1042 try {
William Escande879a70e2024-03-21 21:01:41 -07001043 return service.disconnectAudio(mAttributionSource);
Rahul Sabnis5ed1b842021-12-23 11:36:40 -08001044 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001045 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
William Escande17a262f2023-09-06 21:48:54 -07001046 throw e.rethrowAsRuntimeException();
William Escande9a4b7c12021-12-16 16:07:55 +01001047 }
Matthew Xief3ee3512012-02-16 16:57:18 -08001048 }
Rahul Sabnisfc0cd8b2022-03-22 16:38:38 -07001049
1050 Log.e(TAG, "disconnectAudio: Bluetooth disabled, but profile service still bound");
William Escande879a70e2024-03-21 21:01:41 -07001051 return BluetoothStatusCodes.ERROR_UNKNOWN;
Matthew Xief3ee3512012-02-16 16:57:18 -08001052 }
1053
1054 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001055 * Initiates a SCO channel connection as a virtual voice call to the current active device
1056 * Active handsfree device will be notified of incoming call and connected call.
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001057 *
David Duarteee52b7e2023-12-02 01:32:11 +00001058 * <p>Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. If this function returns true,
1059 * this intent will be broadcasted with {@link #EXTRA_STATE} set to {@link
1060 * #STATE_AUDIO_CONNECTING}.
Jack Hec46a01e2018-05-02 19:10:56 -07001061 *
David Duarteee52b7e2023-12-02 01:32:11 +00001062 * <p>{@link #EXTRA_STATE} will transition from {@link #STATE_AUDIO_CONNECTING} to {@link
1063 * #STATE_AUDIO_CONNECTED} when audio connection is established and to {@link
1064 * #STATE_AUDIO_DISCONNECTED} in case of failure to establish the audio connection.
Jack Hec46a01e2018-05-02 19:10:56 -07001065 *
David Duarteee52b7e2023-12-02 01:32:11 +00001066 * @return true if successful, false if one of the following case applies - SCO audio is not
1067 * idle (connecting or connected) - virtual call has already started - there is no active
1068 * device - a Telecom managed call is going on - binder is dead or Bluetooth is disabled or
1069 * other error
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001070 * @hide
1071 */
Roopa Sattirajuffab9952021-08-20 15:06:57 -07001072 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001073 @RequiresLegacyBluetoothAdminPermission
1074 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +00001075 @RequiresPermission(
1076 allOf = {
1077 android.Manifest.permission.BLUETOOTH_CONNECT,
1078 android.Manifest.permission.MODIFY_PHONE_STATE,
1079 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1080 })
Jack Hec46a01e2018-05-02 19:10:56 -07001081 public boolean startScoUsingVirtualVoiceCall() {
Jaikumar Ganesheea6d262011-01-24 13:55:27 -08001082 if (DBG) log("startScoUsingVirtualVoiceCall()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001083 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001084 if (service == null) {
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001085 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001086 if (DBG) log(Log.getStackTraceString(new Throwable()));
1087 } else if (isEnabled()) {
1088 try {
William Escande879a70e2024-03-21 21:01:41 -07001089 return service.startScoUsingVirtualVoiceCall(mAttributionSource);
1090 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001091 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1092 }
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001093 }
William Escande879a70e2024-03-21 21:01:41 -07001094 return false;
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001095 }
1096
1097 /**
Jack Hec46a01e2018-05-02 19:10:56 -07001098 * Terminates an ongoing SCO connection and the associated virtual call.
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001099 *
David Duarteee52b7e2023-12-02 01:32:11 +00001100 * <p>Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. If this function returns true,
1101 * this intent will be broadcasted with {@link #EXTRA_STATE} set to {@link
1102 * #STATE_AUDIO_DISCONNECTED}.
Jack Hec46a01e2018-05-02 19:10:56 -07001103 *
David Duarteee52b7e2023-12-02 01:32:11 +00001104 * @return true if successful, false if one of the following case applies - virtual voice call
1105 * is not started or has ended - binder is dead or Bluetooth is disabled or other error
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001106 * @hide
1107 */
Roopa Sattirajuffab9952021-08-20 15:06:57 -07001108 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001109 @RequiresLegacyBluetoothAdminPermission
1110 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +00001111 @RequiresPermission(
1112 allOf = {
1113 android.Manifest.permission.BLUETOOTH_CONNECT,
1114 android.Manifest.permission.MODIFY_PHONE_STATE,
1115 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1116 })
Jack Hec46a01e2018-05-02 19:10:56 -07001117 public boolean stopScoUsingVirtualVoiceCall() {
Jaikumar Ganesheea6d262011-01-24 13:55:27 -08001118 if (DBG) log("stopScoUsingVirtualVoiceCall()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001119 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001120 if (service == null) {
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001121 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001122 if (DBG) log(Log.getStackTraceString(new Throwable()));
1123 } else if (isEnabled()) {
1124 try {
William Escande879a70e2024-03-21 21:01:41 -07001125 return service.stopScoUsingVirtualVoiceCall(mAttributionSource);
1126 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001127 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1128 }
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001129 }
William Escande879a70e2024-03-21 21:01:41 -07001130 return false;
Jaikumar Ganesh7af32b42010-10-26 17:10:09 -07001131 }
1132
Matthew Xief3ee3512012-02-16 16:57:18 -08001133 /**
David Duarteee52b7e2023-12-02 01:32:11 +00001134 * Notify Headset of phone state change. This is a backdoor for phone app to call
1135 * BluetoothHeadset since there is currently not a good way to get precise call state change
1136 * outside of phone app.
Matthew Xief3ee3512012-02-16 16:57:18 -08001137 *
1138 * @hide
1139 */
Mathew Inwood049f0f52020-11-04 09:29:36 +00001140 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001141 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +00001142 @RequiresPermission(
1143 allOf = {
1144 android.Manifest.permission.BLUETOOTH_CONNECT,
1145 android.Manifest.permission.MODIFY_PHONE_STATE,
1146 })
1147 public void phoneStateChanged(
1148 int numActive, int numHeld, int callState, String number, int type, String name) {
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001149 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001150 if (service == null) {
1151 Log.w(TAG, "Proxy not attached to service");
1152 if (DBG) log(Log.getStackTraceString(new Throwable()));
1153 } else if (isEnabled()) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001154 try {
David Duarteee52b7e2023-12-02 01:32:11 +00001155 service.phoneStateChanged(
1156 numActive, numHeld, callState, number, type, name, mAttributionSource);
1157 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001158 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Matthew Xief3ee3512012-02-16 16:57:18 -08001159 }
Matthew Xief3ee3512012-02-16 16:57:18 -08001160 }
1161 }
1162
1163 /**
Matthew Xief3ee3512012-02-16 16:57:18 -08001164 * Send Headset of CLCC response
1165 *
1166 * @hide
1167 */
Jeff Sharkey5ba8bfc2021-04-16 09:53:23 -06001168 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +00001169 @RequiresPermission(
1170 allOf = {
1171 android.Manifest.permission.BLUETOOTH_CONNECT,
1172 android.Manifest.permission.MODIFY_PHONE_STATE,
1173 })
1174 public void clccResponse(
1175 int index, int direction, int status, int mode, boolean mpty, String number, int type) {
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001176 final IBluetoothHeadset service = getService();
William Escande9a4b7c12021-12-16 16:07:55 +01001177 if (service == null) {
Matthew Xief3ee3512012-02-16 16:57:18 -08001178 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001179 if (DBG) log(Log.getStackTraceString(new Throwable()));
1180 } else if (isEnabled()) {
1181 try {
David Duarteee52b7e2023-12-02 01:32:11 +00001182 service.clccResponse(
William Escande879a70e2024-03-21 21:01:41 -07001183 index, direction, status, mode, mpty, number, type, mAttributionSource);
1184 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001185 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1186 }
Matthew Xief3ee3512012-02-16 16:57:18 -08001187 }
1188 }
1189
Edward Jee7c81f1f2013-08-16 04:07:49 -07001190 /**
1191 * Sends a vendor-specific unsolicited result code to the headset.
1192 *
Jack He910201b2017-08-22 16:06:54 -07001193 * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code
1194 * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the
1195 * string <code>"+ANDROID: 0"</code> will be sent.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001196 *
Ying Wang29d93892013-08-26 17:48:22 -07001197 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001198 *
Edward Jee7c81f1f2013-08-16 04:07:49 -07001199 * @param device Bluetooth headset.
1200 * @param command A vendor-specific command.
1201 * @param arg The argument that will be attached to the command.
1202 * @return {@code false} if there is no headset connected, or if the command is not an allowed
David Duarteee52b7e2023-12-02 01:32:11 +00001203 * vendor-specific unsolicited result code, or on error. {@code true} otherwise.
Edward Jee7c81f1f2013-08-16 04:07:49 -07001204 * @throws IllegalArgumentException if {@code command} is {@code null}.
1205 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001206 @RequiresLegacyBluetoothPermission
1207 @RequiresBluetoothConnectPermission
1208 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
David Duarteee52b7e2023-12-02 01:32:11 +00001209 public boolean sendVendorSpecificResultCode(
1210 BluetoothDevice device, String command, String arg) {
Edward Jee7c81f1f2013-08-16 04:07:49 -07001211 if (DBG) {
1212 log("sendVendorSpecificResultCode()");
1213 }
1214 if (command == null) {
1215 throw new IllegalArgumentException("command is null");
1216 }
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001217 final IBluetoothHeadset service = getService();
Jack He1f686f62017-08-17 12:11:18 -07001218 if (service == null) {
Edward Jee7c81f1f2013-08-16 04:07:49 -07001219 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001220 if (DBG) log(Log.getStackTraceString(new Throwable()));
1221 } else if (isEnabled() && isValidDevice(device)) {
1222 try {
William Escande879a70e2024-03-21 21:01:41 -07001223 return service.sendVendorSpecificResultCode(
1224 device, command, arg, mAttributionSource);
1225 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001226 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1227 }
Edward Jee7c81f1f2013-08-16 04:07:49 -07001228 }
William Escande879a70e2024-03-21 21:01:41 -07001229 return false;
Edward Jee7c81f1f2013-08-16 04:07:49 -07001230 }
1231
Mudumba Ananth80bf6282014-04-27 13:11:00 -07001232 /**
Jack He889d2342018-01-03 12:13:26 -08001233 * Select a connected device as active.
1234 *
David Duarteee52b7e2023-12-02 01:32:11 +00001235 * <p>The active device selection is per profile. An active device's purpose is
1236 * profile-specific. For example, in HFP and HSP profiles, it is the device used for phone call
1237 * audio. If a remote device is not connected, it cannot be selected as active.
Jack He889d2342018-01-03 12:13:26 -08001238 *
David Duarteee52b7e2023-12-02 01:32:11 +00001239 * <p>This API returns false in scenarios like the profile on the device is not connected or
1240 * Bluetooth is not turned on. When this API returns true, it is guaranteed that the {@link
1241 * #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted with the active device.
Jack He889d2342018-01-03 12:13:26 -08001242 *
Jack He889d2342018-01-03 12:13:26 -08001243 * @param device Remote Bluetooth Device, could be null if phone call audio should not be
David Duarteee52b7e2023-12-02 01:32:11 +00001244 * streamed to a headset
Jack He889d2342018-01-03 12:13:26 -08001245 * @return false on immediate error, true otherwise
1246 * @hide
1247 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001248 @RequiresLegacyBluetoothAdminPermission
1249 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +00001250 @RequiresPermission(
1251 allOf = {
1252 android.Manifest.permission.BLUETOOTH_CONNECT,
1253 android.Manifest.permission.MODIFY_PHONE_STATE,
1254 })
Mathew Inwoodb426f902021-01-06 12:05:47 +00001255 @UnsupportedAppUsage(trackingBug = 171933273)
Jack He889d2342018-01-03 12:13:26 -08001256 public boolean setActiveDevice(@Nullable BluetoothDevice device) {
1257 if (DBG) {
1258 Log.d(TAG, "setActiveDevice: " + device);
1259 }
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001260 final IBluetoothHeadset service = getService();
Jack He889d2342018-01-03 12:13:26 -08001261 if (service == null) {
1262 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001263 if (DBG) log(Log.getStackTraceString(new Throwable()));
1264 } else if (isEnabled() && (device == null || isValidDevice(device))) {
1265 try {
William Escande879a70e2024-03-21 21:01:41 -07001266 return service.setActiveDevice(device, mAttributionSource);
1267 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001268 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1269 }
Jack He889d2342018-01-03 12:13:26 -08001270 }
William Escande879a70e2024-03-21 21:01:41 -07001271 return false;
Jack He889d2342018-01-03 12:13:26 -08001272 }
1273
1274 /**
1275 * Get the connected device that is active.
1276 *
David Duarteee52b7e2023-12-02 01:32:11 +00001277 * @return the connected device that is active or null if no device is active.
Jack He889d2342018-01-03 12:13:26 -08001278 * @hide
1279 */
Mathew Inwoodb426f902021-01-06 12:05:47 +00001280 @UnsupportedAppUsage(trackingBug = 171933273)
Rahul Sabnisd9798612019-12-04 14:21:10 -08001281 @Nullable
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001282 @RequiresLegacyBluetoothPermission
1283 @RequiresBluetoothConnectPermission
1284 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jack He889d2342018-01-03 12:13:26 -08001285 public BluetoothDevice getActiveDevice() {
William Escande9a4b7c12021-12-16 16:07:55 +01001286 if (VDBG) Log.d(TAG, "getActiveDevice");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001287 final IBluetoothHeadset service = getService();
Jack He889d2342018-01-03 12:13:26 -08001288 if (service == null) {
1289 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001290 if (DBG) log(Log.getStackTraceString(new Throwable()));
1291 } else if (isEnabled()) {
1292 try {
William Escande9a4b7c12021-12-16 16:07:55 +01001293 return Attributable.setAttributionSource(
William Escande879a70e2024-03-21 21:01:41 -07001294 service.getActiveDevice(mAttributionSource), mAttributionSource);
1295 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001296 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1297 }
Jack He889d2342018-01-03 12:13:26 -08001298 }
William Escande879a70e2024-03-21 21:01:41 -07001299 return null;
Jack He889d2342018-01-03 12:13:26 -08001300 }
1301
1302 /**
Jack Hec5fde732018-01-05 17:17:06 -08001303 * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an
1304 * active connection.
Jack Hed8d204d2016-11-17 16:19:43 -08001305 *
Jack Hec5fde732018-01-05 17:17:06 -08001306 * @return true if in-band ringing is enabled, false if in-band ringing is disabled
1307 * @hide
1308 */
Roopa Sattirajuffab9952021-08-20 15:06:57 -07001309 @SystemApi
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -06001310 @RequiresLegacyBluetoothPermission
1311 @RequiresBluetoothConnectPermission
David Duarteee52b7e2023-12-02 01:32:11 +00001312 @RequiresPermission(
1313 allOf = {
1314 android.Manifest.permission.BLUETOOTH_CONNECT,
1315 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1316 })
Jack Hec5fde732018-01-05 17:17:06 -08001317 public boolean isInbandRingingEnabled() {
William Escande9a4b7c12021-12-16 16:07:55 +01001318 if (DBG) log("isInbandRingingEnabled()");
Etienne Ruffieuxc3bbf1a2022-11-07 14:40:27 -08001319 final IBluetoothHeadset service = getService();
Jack Hec5fde732018-01-05 17:17:06 -08001320 if (service == null) {
1321 Log.w(TAG, "Proxy not attached to service");
William Escande9a4b7c12021-12-16 16:07:55 +01001322 if (DBG) log(Log.getStackTraceString(new Throwable()));
1323 } else if (isEnabled()) {
1324 try {
William Escande879a70e2024-03-21 21:01:41 -07001325 return service.isInbandRingingEnabled(mAttributionSource);
1326 } catch (RemoteException e) {
William Escande9a4b7c12021-12-16 16:07:55 +01001327 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1328 }
Jack Hec5fde732018-01-05 17:17:06 -08001329 }
William Escande879a70e2024-03-21 21:01:41 -07001330 return false;
Jack Hec5fde732018-01-05 17:17:06 -08001331 }
1332
Mathew Inwood7d543892018-08-01 15:07:20 +01001333 @UnsupportedAppUsage
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001334 private boolean isEnabled() {
Jack He1f686f62017-08-17 12:11:18 -07001335 return mAdapter.getState() == BluetoothAdapter.STATE_ON;
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001336 }
1337
Jaikumar Ganesh2fbe8f42011-04-06 11:09:30 -07001338 private boolean isDisabled() {
Jack He1f686f62017-08-17 12:11:18 -07001339 return mAdapter.getState() == BluetoothAdapter.STATE_OFF;
Jaikumar Ganesh2fbe8f42011-04-06 11:09:30 -07001340 }
1341
Jack He1f686f62017-08-17 12:11:18 -07001342 private static boolean isValidDevice(BluetoothDevice device) {
1343 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Jaikumar Ganesh2af07762010-08-24 17:36:13 -07001344 }
1345
The Android Open Source Project0047a0f2009-03-05 20:00:43 -08001346 private static void log(String msg) {
1347 Log.d(TAG, msg);
1348 }
The Android Open Source Project33897762009-03-03 19:31:44 -08001349}