blob: 55a6b4c6b4d4e1e00145d0b9c96d428b8955df0d [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.bluetooth;
18
Jack He05f4bc42018-01-03 12:13:26 -080019import android.annotation.Nullable;
Selim Gurun4029fa62017-10-17 17:01:38 -070020import android.annotation.RequiresPermission;
Nick Pelly005b2282009-09-10 10:21:56 -070021import android.annotation.SdkConstant;
22import android.annotation.SdkConstant.SdkConstantType;
Selim Gurun4029fa62017-10-17 17:01:38 -070023import android.annotation.SystemApi;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.content.ComponentName;
25import android.content.Context;
Jeff Sharkey0a17db12016-11-04 11:23:46 -060026import android.os.Binder;
Benjamin Franze8b98922014-11-12 15:57:54 +000027import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.os.IBinder;
Benjamin Franze8b98922014-11-12 15:57:54 +000029import android.os.Looper;
30import android.os.Message;
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -070031import android.os.RemoteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.util.Log;
33
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -070034import java.util.ArrayList;
35import java.util.List;
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070036
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037/**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038 * Public API for controlling the Bluetooth Headset Service. This includes both
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070039 * Bluetooth Headset and Handsfree (v1.5) profiles.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040 *
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070041 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042 * Service via IPC.
43 *
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070044 * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
45 * the BluetoothHeadset proxy object. Use
46 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047 *
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070048 * <p> Android only supports one connected Bluetooth Headset at a time.
49 * Each method is protected with its appropriate permission.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050 */
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070051public final class BluetoothHeadset implements BluetoothProfile {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052 private static final String TAG = "BluetoothHeadset";
fredc0f420372012-04-12 00:02:00 -070053 private static final boolean DBG = true;
Matthew Xie563e4142012-10-09 22:10:37 -070054 private static final boolean VDBG = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055
Nick Pelly005b2282009-09-10 10:21:56 -070056 /**
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070057 * Intent used to broadcast the change in connection state of the Headset
58 * profile.
59 *
60 * <p>This intent will have 3 extras:
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -080061 * <ul>
Jack Hea355e5e2017-08-22 16:06:54 -070062 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
63 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
64 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -080065 * </ul>
Jaikumar Ganesh0706fed2011-01-26 11:46:56 -080066 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070067 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
68 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
69 *
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -080070 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
71 * receive.
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070072 */
73 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
74 public static final String ACTION_CONNECTION_STATE_CHANGED =
Jack Hea355e5e2017-08-22 16:06:54 -070075 "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070076
77 /**
78 * Intent used to broadcast the change in the Audio Connection state of the
79 * A2DP profile.
80 *
81 * <p>This intent will have 3 extras:
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -080082 * <ul>
Jack Hea355e5e2017-08-22 16:06:54 -070083 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
84 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
85 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -080086 * </ul>
Jaikumar Ganesh0706fed2011-01-26 11:46:56 -080087 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070088 * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
89 *
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -080090 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
91 * to receive.
Nick Pelly005b2282009-09-10 10:21:56 -070092 */
93 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
94 public static final String ACTION_AUDIO_STATE_CHANGED =
Jack Hea355e5e2017-08-22 16:06:54 -070095 "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
Nick Pelly005b2282009-09-10 10:21:56 -070096
Jack He05f4bc42018-01-03 12:13:26 -080097 /**
98 * Intent used to broadcast the selection of a connected device as active.
99 *
100 * <p>This intent will have one extra:
101 * <ul>
102 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
103 * be null if no device is active. </li>
104 * </ul>
105 *
106 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
107 * receive.
108 *
109 * @hide
110 */
111 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
112 public static final String ACTION_ACTIVE_DEVICE_CHANGED =
113 "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
Jaikumar Ganeshc24dbdb2010-04-02 14:44:43 -0700114
Nick Pelly005b2282009-09-10 10:21:56 -0700115 /**
Jaikumar Ganeshe775b3d2010-09-29 11:34:59 -0700116 * Intent used to broadcast that the headset has posted a
117 * vendor-specific event.
118 *
119 * <p>This intent will have 4 extras and 1 category.
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -0800120 * <ul>
Jack Hea355e5e2017-08-22 16:06:54 -0700121 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
122 * </li>
123 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
124 * specific command </li>
125 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
126 * command type which can be one of {@link #AT_CMD_TYPE_READ},
127 * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
128 * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
129 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
130 * arguments. </li>
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -0800131 * </ul>
Jaikumar Ganeshe775b3d2010-09-29 11:34:59 -0700132 *
Jack Hea355e5e2017-08-22 16:06:54 -0700133 * <p> The category is the Company ID of the vendor defining the
Jaikumar Ganeshe775b3d2010-09-29 11:34:59 -0700134 * vendor-specific command. {@link BluetoothAssignedNumbers}
Jaikumar Ganeshe775b3d2010-09-29 11:34:59 -0700135 *
136 * For example, for Plantronics specific events
137 * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
138 *
139 * <p> For example, an AT+XEVENT=foo,3 will get translated into
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -0800140 * <ul>
Jack Hea355e5e2017-08-22 16:06:54 -0700141 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
142 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
143 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -0800144 * </ul>
145 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
146 * to receive.
Herb Jellineka4733942010-08-10 13:17:43 -0700147 */
148 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
149 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
150 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
151
152 /**
153 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
154 * intents that contains the name of the vendor-specific command.
155 */
156 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
157 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
158
159 /**
160 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
Jaikumar Ganeshe775b3d2010-09-29 11:34:59 -0700161 * intents that contains the AT command type of the vendor-specific command.
Herb Jellineka4733942010-08-10 13:17:43 -0700162 */
Jaikumar Ganeshe775b3d2010-09-29 11:34:59 -0700163 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
164 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
165
166 /**
167 * AT command type READ used with
168 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
169 * For example, AT+VGM?. There are no arguments for this command type.
170 */
171 public static final int AT_CMD_TYPE_READ = 0;
172
173 /**
174 * AT command type TEST used with
175 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
176 * For example, AT+VGM=?. There are no arguments for this command type.
177 */
178 public static final int AT_CMD_TYPE_TEST = 1;
179
180 /**
181 * AT command type SET used with
182 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
183 * For example, AT+VGM=<args>.
184 */
185 public static final int AT_CMD_TYPE_SET = 2;
186
187 /**
188 * AT command type BASIC used with
189 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
190 * For example, ATD. Single character commands and everything following the
191 * character are arguments.
192 */
193 public static final int AT_CMD_TYPE_BASIC = 3;
194
195 /**
196 * AT command type ACTION used with
197 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
198 * For example, AT+CHUP. There are no arguments for action commands.
199 */
200 public static final int AT_CMD_TYPE_ACTION = 4;
Herb Jellineka4733942010-08-10 13:17:43 -0700201
202 /**
203 * A Parcelable String array extra field in
204 * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
205 * the arguments to the vendor-specific command.
206 */
207 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
208 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
209
Jaikumar Ganeshe775b3d2010-09-29 11:34:59 -0700210 /**
211 * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
212 * for the companyId
213 */
Jack Hea355e5e2017-08-22 16:06:54 -0700214 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY =
Jaikumar Ganeshe775b3d2010-09-29 11:34:59 -0700215 "android.bluetooth.headset.intent.category.companyid";
216
Jaikumar Ganesh30d18162010-11-01 11:59:57 -0700217 /**
Edward Jee922d41b2013-08-16 04:07:49 -0700218 * A vendor-specific command for unsolicited result code.
219 */
220 public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
221
222 /**
Jack He0fcbba22017-06-20 17:07:40 -0700223 * A vendor-specific AT command
Jack Hea355e5e2017-08-22 16:06:54 -0700224 *
Jack He0fcbba22017-06-20 17:07:40 -0700225 * @hide
226 */
Jack He679d0bc2017-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 Hea355e5e2017-08-22 16:06:54 -0700231 *
Jack He679d0bc2017-06-20 17:09:47 -0700232 * @hide
233 */
234 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV";
235
236 /**
237 * Battery level indicator associated with
238 * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV}
Jack Hea355e5e2017-08-22 16:06:54 -0700239 *
Jack He679d0bc2017-06-20 17:09:47 -0700240 * @hide
241 */
242 public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1;
243
244 /**
245 * A vendor-specific AT command
Jack Hea355e5e2017-08-22 16:06:54 -0700246 *
Jack He679d0bc2017-06-20 17:09:47 -0700247 * @hide
248 */
Jack He0fcbba22017-06-20 17:07:40 -0700249 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT";
250
251 /**
252 * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT}
Jack Hea355e5e2017-08-22 16:06:54 -0700253 *
Jack He0fcbba22017-06-20 17:07:40 -0700254 * @hide
255 */
256 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY";
257
258 /**
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -0800259 * Headset state when SCO audio is not connected.
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700260 * This state can be one of
261 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
262 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
263 */
Jaikumar Ganeshb0a1d012010-11-11 10:49:46 -0800264 public static final int STATE_AUDIO_DISCONNECTED = 10;
Herb Jellineka4733942010-08-10 13:17:43 -0700265
266 /**
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -0800267 * Headset state when SCO audio is connecting.
Jaikumar Ganesh30d18162010-11-01 11:59:57 -0700268 * This state can be one of
269 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
270 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Jaikumar Ganesh30d18162010-11-01 11:59:57 -0700271 */
Jaikumar Ganeshb0a1d012010-11-11 10:49:46 -0800272 public static final int STATE_AUDIO_CONNECTING = 11;
Jaikumar Ganesh30d18162010-11-01 11:59:57 -0700273
274 /**
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -0800275 * Headset state when SCO audio is connected.
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700276 * This state can be one of
277 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
278 * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
Nick Pelly005b2282009-09-10 10:21:56 -0700279 */
Mudumba Ananth0d98ebf2016-02-29 02:14:36 -0800280
281 /**
282 * Intent used to broadcast the headset's indicator status
283 *
284 * <p>This intent will have 3 extras:
285 * <ul>
Jack Hea355e5e2017-08-22 16:06:54 -0700286 * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which
287 * is supported by the headset ( as indicated by AT+BIND command in the SLC
288 * sequence) or whose value is changed (indicated by AT+BIEV command) </li>
289 * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li>
290 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li>
Mudumba Ananth0d98ebf2016-02-29 02:14:36 -0800291 * </ul>
Jack He73795442017-06-22 12:56:54 -0700292 * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators
Jack Hea355e5e2017-08-22 16:06:54 -0700293 * are given an assigned number. Below shows the assigned number of Indicator added so far
Jack He73795442017-06-22 12:56:54 -0700294 * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled
295 * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery
296 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
Jack Hea355e5e2017-08-22 16:06:54 -0700297 *
Mudumba Ananth0d98ebf2016-02-29 02:14:36 -0800298 * @hide
299 */
300 public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
301 "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
302
303 /**
Jack He73795442017-06-22 12:56:54 -0700304 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
305 * intents that contains the assigned number of the headset indicator as defined by
306 * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7
Jack Hea355e5e2017-08-22 16:06:54 -0700307 *
Mudumba Ananth0d98ebf2016-02-29 02:14:36 -0800308 * @hide
309 */
310 public static final String EXTRA_HF_INDICATORS_IND_ID =
311 "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
312
313 /**
Jack He73795442017-06-22 12:56:54 -0700314 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
Mudumba Ananth0d98ebf2016-02-29 02:14:36 -0800315 * intents that contains the value of the Headset indicator that is being sent.
Jack Hea355e5e2017-08-22 16:06:54 -0700316 *
Mudumba Ananth0d98ebf2016-02-29 02:14:36 -0800317 * @hide
318 */
319 public static final String EXTRA_HF_INDICATORS_IND_VALUE =
320 "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
321
Jaikumar Ganeshb0a1d012010-11-11 10:49:46 -0800322 public static final int STATE_AUDIO_CONNECTED = 12;
323
Benjamin Franze8b98922014-11-12 15:57:54 +0000324 private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
325 private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700326
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700327 private Context mContext;
328 private ServiceListener mServiceListener;
Jack He16eeac32017-08-17 12:11:18 -0700329 private volatile IBluetoothHeadset mService;
Matthew Xiebf246ef2012-03-21 23:15:06 -0700330 private BluetoothAdapter mAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800331
Jack He2992cd02017-08-22 21:21:23 -0700332 private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
fredc0f420372012-04-12 00:02:00 -0700333 new IBluetoothStateChangeCallback.Stub() {
334 public void onBluetoothStateChange(boolean up) {
335 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
336 if (!up) {
Jack Hea355e5e2017-08-22 16:06:54 -0700337 if (VDBG) Log.d(TAG, "Unbinding service...");
Benjamin Franze8b98922014-11-12 15:57:54 +0000338 doUnbind();
fredc0f420372012-04-12 00:02:00 -0700339 } else {
340 synchronized (mConnection) {
341 try {
342 if (mService == null) {
Jack Hea355e5e2017-08-22 16:06:54 -0700343 if (VDBG) Log.d(TAG, "Binding service...");
Dianne Hackborn221ea892013-08-04 16:50:16 -0700344 doBind();
fredc0f420372012-04-12 00:02:00 -0700345 }
346 } catch (Exception re) {
Jack Hea355e5e2017-08-22 16:06:54 -0700347 Log.e(TAG, "", re);
fredc0f420372012-04-12 00:02:00 -0700348 }
349 }
350 }
351 }
Jack Hea355e5e2017-08-22 16:06:54 -0700352 };
fredc0f420372012-04-12 00:02:00 -0700353
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354 /**
355 * Create a BluetoothHeadset proxy object.
356 */
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700357 /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 mContext = context;
359 mServiceListener = l;
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700360 mAdapter = BluetoothAdapter.getDefaultAdapter();
fredc0f420372012-04-12 00:02:00 -0700361
362 IBluetoothManager mgr = mAdapter.getBluetoothManager();
363 if (mgr != null) {
364 try {
365 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
366 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700367 Log.e(TAG, "", e);
fredc0f420372012-04-12 00:02:00 -0700368 }
369 }
370
Dianne Hackborn221ea892013-08-04 16:50:16 -0700371 doBind();
372 }
373
374 boolean doBind() {
Benjamin Franze8b98922014-11-12 15:57:54 +0000375 try {
376 return mAdapter.getBluetoothManager().bindBluetoothProfileService(
377 BluetoothProfile.HEADSET, mConnection);
378 } catch (RemoteException e) {
379 Log.e(TAG, "Unable to bind HeadsetService", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800380 }
Benjamin Franze8b98922014-11-12 15:57:54 +0000381 return false;
382 }
383
384 void doUnbind() {
385 synchronized (mConnection) {
386 if (mService != null) {
387 try {
388 mAdapter.getBluetoothManager().unbindBluetoothProfileService(
389 BluetoothProfile.HEADSET, mConnection);
390 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700391 Log.e(TAG, "Unable to unbind HeadsetService", e);
Benjamin Franze8b98922014-11-12 15:57:54 +0000392 }
393 }
394 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395 }
396
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 /**
398 * Close the connection to the backing service.
399 * Other public functions of BluetoothHeadset will return default error
400 * results once close() has been called. Multiple invocations of close()
401 * are ok.
402 */
Matthew Xie13450df2012-03-22 17:18:37 -0700403 /*package*/ void close() {
Matthew Xie563e4142012-10-09 22:10:37 -0700404 if (VDBG) log("close()");
fredc0f420372012-04-12 00:02:00 -0700405
406 IBluetoothManager mgr = mAdapter.getBluetoothManager();
407 if (mgr != null) {
408 try {
409 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
410 } catch (Exception e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700411 Log.e(TAG, "", e);
fredc0f420372012-04-12 00:02:00 -0700412 }
413 }
Benjamin Franzc88b6bd2014-12-16 15:33:03 +0000414 mServiceListener = null;
Benjamin Franze8b98922014-11-12 15:57:54 +0000415 doUnbind();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800416 }
417
418 /**
Jaikumar Ganeshf8789162011-05-26 13:56:40 -0700419 * Initiate connection to a profile of the remote bluetooth device.
420 *
421 * <p> Currently, the system supports only 1 connection to the
422 * headset/handsfree profile. The API will automatically disconnect connected
423 * devices before connecting.
424 *
425 * <p> This API returns false in scenarios like the profile on the
426 * device is already connected or Bluetooth is not turned on.
427 * When this API returns true, it is guaranteed that
428 * connection state intent for the profile will be broadcasted with
429 * the state. Users can get the connection state of the profile
430 * from this intent.
431 *
432 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
433 * permission.
434 *
435 * @param device Remote Bluetooth Device
Jack Hea355e5e2017-08-22 16:06:54 -0700436 * @return false on immediate error, true otherwise
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700437 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800438 */
Selim Gurun4029fa62017-10-17 17:01:38 -0700439 @SystemApi
440 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700441 public boolean connect(BluetoothDevice device) {
442 if (DBG) log("connect(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700443 final IBluetoothHeadset service = mService;
444 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 try {
Jack He16eeac32017-08-17 12:11:18 -0700446 return service.connect(device);
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700447 } catch (RemoteException e) {
448 Log.e(TAG, Log.getStackTraceString(new Throwable()));
449 return false;
450 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 }
Jack He16eeac32017-08-17 12:11:18 -0700452 if (service == null) Log.w(TAG, "Proxy not attached to service");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 return false;
454 }
455
456 /**
Jaikumar Ganeshf8789162011-05-26 13:56:40 -0700457 * Initiate disconnection from a profile
458 *
459 * <p> This API will return false in scenarios like the profile on the
460 * Bluetooth device is not in connected state etc. When this API returns,
461 * true, it is guaranteed that the connection state change
462 * intent will be broadcasted with the state. Users can get the
463 * disconnection state of the profile from this intent.
464 *
465 * <p> If the disconnection is initiated by a remote device, the state
466 * will transition from {@link #STATE_CONNECTED} to
467 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
468 * host (local) device the state will transition from
469 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
470 * state {@link #STATE_DISCONNECTED}. The transition to
471 * {@link #STATE_DISCONNECTING} can be used to distinguish between the
472 * two scenarios.
473 *
474 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
475 * permission.
476 *
477 * @param device Remote Bluetooth Device
Jack Hea355e5e2017-08-22 16:06:54 -0700478 * @return false on immediate error, true otherwise
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700479 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800480 */
Selim Gurun4029fa62017-10-17 17:01:38 -0700481 @SystemApi
482 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700483 public boolean disconnect(BluetoothDevice device) {
484 if (DBG) log("disconnect(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700485 final IBluetoothHeadset service = mService;
486 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800487 try {
Jack He16eeac32017-08-17 12:11:18 -0700488 return service.disconnect(device);
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700489 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700490 Log.e(TAG, Log.getStackTraceString(new Throwable()));
491 return false;
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700492 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 }
Jack He16eeac32017-08-17 12:11:18 -0700494 if (service == null) Log.w(TAG, "Proxy not attached to service");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 return false;
496 }
497
498 /**
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700499 * {@inheritDoc}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500 */
Jack He2992cd02017-08-22 21:21:23 -0700501 @Override
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700502 public List<BluetoothDevice> getConnectedDevices() {
Matthew Xie563e4142012-10-09 22:10:37 -0700503 if (VDBG) log("getConnectedDevices()");
Jack He16eeac32017-08-17 12:11:18 -0700504 final IBluetoothHeadset service = mService;
505 if (service != null && isEnabled()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506 try {
Jack He16eeac32017-08-17 12:11:18 -0700507 return service.getConnectedDevices();
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700508 } catch (RemoteException e) {
509 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700510 return new ArrayList<BluetoothDevice>();
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700511 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 }
Jack He16eeac32017-08-17 12:11:18 -0700513 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700514 return new ArrayList<BluetoothDevice>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800515 }
516
517 /**
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700518 * {@inheritDoc}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 */
Jack He2992cd02017-08-22 21:21:23 -0700520 @Override
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700521 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Matthew Xie563e4142012-10-09 22:10:37 -0700522 if (VDBG) log("getDevicesMatchingStates()");
Jack He16eeac32017-08-17 12:11:18 -0700523 final IBluetoothHeadset service = mService;
524 if (service != null && isEnabled()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800525 try {
Jack He16eeac32017-08-17 12:11:18 -0700526 return service.getDevicesMatchingConnectionStates(states);
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700527 } catch (RemoteException e) {
528 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700529 return new ArrayList<BluetoothDevice>();
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700530 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800531 }
Jack He16eeac32017-08-17 12:11:18 -0700532 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700533 return new ArrayList<BluetoothDevice>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800534 }
535
536 /**
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700537 * {@inheritDoc}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800538 */
Jack He2992cd02017-08-22 21:21:23 -0700539 @Override
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700540 public int getConnectionState(BluetoothDevice device) {
Matthew Xie563e4142012-10-09 22:10:37 -0700541 if (VDBG) log("getConnectionState(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700542 final IBluetoothHeadset service = mService;
543 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544 try {
Jack He16eeac32017-08-17 12:11:18 -0700545 return service.getConnectionState(device);
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700546 } catch (RemoteException e) {
547 Log.e(TAG, Log.getStackTraceString(new Throwable()));
548 return BluetoothProfile.STATE_DISCONNECTED;
549 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800550 }
Jack He16eeac32017-08-17 12:11:18 -0700551 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700552 return BluetoothProfile.STATE_DISCONNECTED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800553 }
554
555 /**
Jaikumar Ganeshf8789162011-05-26 13:56:40 -0700556 * Set priority of the profile
557 *
558 * <p> The device should already be paired.
Jack Hea355e5e2017-08-22 16:06:54 -0700559 * Priority can be one of {@link #PRIORITY_ON} or
Jaikumar Ganeshf8789162011-05-26 13:56:40 -0700560 * {@link #PRIORITY_OFF},
561 *
562 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
563 * permission.
564 *
565 * @param device Paired bluetooth device
566 * @param priority
567 * @return true if priority is set, false on error
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700568 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 */
Selim Gurun4029fa62017-10-17 17:01:38 -0700570 @SystemApi
571 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
Nick Pellybd022f42009-08-14 18:33:38 -0700572 public boolean setPriority(BluetoothDevice device, int priority) {
573 if (DBG) log("setPriority(" + device + ", " + priority + ")");
Jack He16eeac32017-08-17 12:11:18 -0700574 final IBluetoothHeadset service = mService;
575 if (service != null && isEnabled() && isValidDevice(device)) {
Jack He2992cd02017-08-22 21:21:23 -0700576 if (priority != BluetoothProfile.PRIORITY_OFF
577 && priority != BluetoothProfile.PRIORITY_ON) {
Jack Hea355e5e2017-08-22 16:06:54 -0700578 return false;
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700579 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800580 try {
Jack He16eeac32017-08-17 12:11:18 -0700581 return service.setPriority(device, priority);
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700582 } catch (RemoteException e) {
583 Log.e(TAG, Log.getStackTraceString(new Throwable()));
584 return false;
585 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800586 }
Jack He16eeac32017-08-17 12:11:18 -0700587 if (service == null) Log.w(TAG, "Proxy not attached to service");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800588 return false;
589 }
590
591 /**
Jaikumar Ganeshf8789162011-05-26 13:56:40 -0700592 * Get the priority of the profile.
593 *
594 * <p> The priority can be any of:
595 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
596 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
597 *
598 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
599 *
600 * @param device Bluetooth device
601 * @return priority of the device
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700602 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603 */
Nick Pellybd022f42009-08-14 18:33:38 -0700604 public int getPriority(BluetoothDevice device) {
Matthew Xie563e4142012-10-09 22:10:37 -0700605 if (VDBG) log("getPriority(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700606 final IBluetoothHeadset service = mService;
607 if (service != null && isEnabled() && isValidDevice(device)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800608 try {
Jack He16eeac32017-08-17 12:11:18 -0700609 return service.getPriority(device);
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700610 } catch (RemoteException e) {
611 Log.e(TAG, Log.getStackTraceString(new Throwable()));
612 return PRIORITY_OFF;
613 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800614 }
Jack He16eeac32017-08-17 12:11:18 -0700615 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700616 return PRIORITY_OFF;
617 }
618
619 /**
620 * Start Bluetooth voice recognition. This methods sends the voice
621 * recognition AT command to the headset and establishes the
622 * audio connection.
623 *
624 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
Jaikumar Ganeshb0a1d012010-11-11 10:49:46 -0800625 * If this function returns true, this intent will be broadcasted with
626 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700627 *
Jaikumar Ganeshb0a1d012010-11-11 10:49:46 -0800628 * <p> {@link #EXTRA_STATE} will transition from
629 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
630 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
631 * in case of failure to establish the audio connection.
632 *
633 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700634 *
635 * @param device Bluetooth headset
Jack Hea355e5e2017-08-22 16:06:54 -0700636 * @return false if there is no headset connected of if the connected headset doesn't support
637 * voice recognition or on error, true otherwise
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700638 */
639 public boolean startVoiceRecognition(BluetoothDevice device) {
640 if (DBG) log("startVoiceRecognition()");
Jack He16eeac32017-08-17 12:11:18 -0700641 final IBluetoothHeadset service = mService;
642 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700643 try {
Jack He16eeac32017-08-17 12:11:18 -0700644 return service.startVoiceRecognition(device);
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700645 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700646 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700647 }
648 }
Jack He16eeac32017-08-17 12:11:18 -0700649 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700650 return false;
651 }
652
653 /**
654 * Stop Bluetooth Voice Recognition mode, and shut down the
655 * Bluetooth audio path.
656 *
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -0800657 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700658 *
659 * @param device Bluetooth headset
Jack Hea355e5e2017-08-22 16:06:54 -0700660 * @return false if there is no headset connected or on error, true otherwise
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700661 */
662 public boolean stopVoiceRecognition(BluetoothDevice device) {
663 if (DBG) log("stopVoiceRecognition()");
Jack He16eeac32017-08-17 12:11:18 -0700664 final IBluetoothHeadset service = mService;
665 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700666 try {
Jack He16eeac32017-08-17 12:11:18 -0700667 return service.stopVoiceRecognition(device);
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700668 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700669 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700670 }
671 }
Jack He16eeac32017-08-17 12:11:18 -0700672 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700673 return false;
674 }
675
676 /**
677 * Check if Bluetooth SCO audio is connected.
678 *
Jaikumar Ganeshc8fa4ff2011-01-25 16:03:13 -0800679 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700680 *
681 * @param device Bluetooth headset
Jack Hea355e5e2017-08-22 16:06:54 -0700682 * @return true if SCO is connected, false otherwise or on error
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700683 */
684 public boolean isAudioConnected(BluetoothDevice device) {
Matthew Xie563e4142012-10-09 22:10:37 -0700685 if (VDBG) log("isAudioConnected()");
Jack He16eeac32017-08-17 12:11:18 -0700686 final IBluetoothHeadset service = mService;
687 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700688 try {
Jack He16eeac32017-08-17 12:11:18 -0700689 return service.isAudioConnected(device);
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700690 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700691 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700692 }
693 }
Jack He16eeac32017-08-17 12:11:18 -0700694 if (service == null) Log.w(TAG, "Proxy not attached to service");
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700695 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696 }
697
698 /**
Eric Laurentd726b352010-03-17 14:59:27 -0700699 * Indicates if current platform supports voice dialing over bluetooth SCO.
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700700 *
Eric Laurentd726b352010-03-17 14:59:27 -0700701 * @return true if voice dialing over bluetooth is supported, false otherwise.
702 * @hide
703 */
704 public static boolean isBluetoothVoiceDialingEnabled(Context context) {
705 return context.getResources().getBoolean(
706 com.android.internal.R.bool.config_bluetooth_sco_off_call);
707 }
708
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700709 /**
Jaikumar Ganesh30d18162010-11-01 11:59:57 -0700710 * Get the current audio state of the Headset.
711 * Note: This is an internal function and shouldn't be exposed
712 *
713 * @hide
714 */
715 public int getAudioState(BluetoothDevice device) {
Matthew Xie563e4142012-10-09 22:10:37 -0700716 if (VDBG) log("getAudioState");
Jack He16eeac32017-08-17 12:11:18 -0700717 final IBluetoothHeadset service = mService;
718 if (service != null && !isDisabled()) {
Jaikumar Ganesh30d18162010-11-01 11:59:57 -0700719 try {
Jack He16eeac32017-08-17 12:11:18 -0700720 return service.getAudioState(device);
Jack Hea355e5e2017-08-22 16:06:54 -0700721 } catch (RemoteException e) {
722 Log.e(TAG, e.toString());
723 }
Jaikumar Ganesh30d18162010-11-01 11:59:57 -0700724 } else {
725 Log.w(TAG, "Proxy not attached to service");
726 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
727 }
728 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
729 }
730
Jaikumar Ganeshf2e6b132010-10-26 17:10:09 -0700731 /**
Bryce Lee0db53d92015-11-16 08:55:52 -0800732 * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
733 * audio to the HF unless explicitly told to.
734 * This method should be used in cases where the SCO channel is shared between multiple profiles
735 * and must be delegated by a source knowledgeable
736 * Note: This is an internal function and shouldn't be exposed
737 *
738 * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
Bryce Lee0db53d92015-11-16 08:55:52 -0800739 * @hide
740 */
741 public void setAudioRouteAllowed(boolean allowed) {
742 if (VDBG) log("setAudioRouteAllowed");
Jack He16eeac32017-08-17 12:11:18 -0700743 final IBluetoothHeadset service = mService;
744 if (service != null && isEnabled()) {
Bryce Lee0db53d92015-11-16 08:55:52 -0800745 try {
Jack He16eeac32017-08-17 12:11:18 -0700746 service.setAudioRouteAllowed(allowed);
Jack Hea355e5e2017-08-22 16:06:54 -0700747 } catch (RemoteException e) {
748 Log.e(TAG, e.toString());
749 }
Bryce Lee0db53d92015-11-16 08:55:52 -0800750 } else {
751 Log.w(TAG, "Proxy not attached to service");
752 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
753 }
754 }
755
756 /**
757 * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
758 * Note: This is an internal function and shouldn't be exposed
759 *
760 * @hide
761 */
762 public boolean getAudioRouteAllowed() {
763 if (VDBG) log("getAudioRouteAllowed");
Jack He16eeac32017-08-17 12:11:18 -0700764 final IBluetoothHeadset service = mService;
765 if (service != null && isEnabled()) {
Bryce Lee0db53d92015-11-16 08:55:52 -0800766 try {
Jack He16eeac32017-08-17 12:11:18 -0700767 return service.getAudioRouteAllowed();
Jack Hea355e5e2017-08-22 16:06:54 -0700768 } catch (RemoteException e) {
769 Log.e(TAG, e.toString());
770 }
Bryce Lee0db53d92015-11-16 08:55:52 -0800771 } else {
772 Log.w(TAG, "Proxy not attached to service");
773 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
774 }
775 return false;
776 }
777
778 /**
Jack He1dd1e032017-05-09 17:16:01 -0700779 * Force SCO audio to be opened regardless any other restrictions
780 *
Jack Hea355e5e2017-08-22 16:06:54 -0700781 * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio
782 * False to use SCO audio in normal manner
Jack He1dd1e032017-05-09 17:16:01 -0700783 * @hide
784 */
785 public void setForceScoAudio(boolean forced) {
786 if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
Jack He16eeac32017-08-17 12:11:18 -0700787 final IBluetoothHeadset service = mService;
788 if (service != null && isEnabled()) {
Jack He1dd1e032017-05-09 17:16:01 -0700789 try {
Jack He16eeac32017-08-17 12:11:18 -0700790 service.setForceScoAudio(forced);
Jack He1dd1e032017-05-09 17:16:01 -0700791 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700792 Log.e(TAG, e.toString());
Jack He1dd1e032017-05-09 17:16:01 -0700793 }
794 } else {
795 Log.w(TAG, "Proxy not attached to service");
796 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
797 }
798 }
799
800 /**
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800801 * Check if Bluetooth SCO audio is connected.
802 *
803 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
804 *
Jack Hea355e5e2017-08-22 16:06:54 -0700805 * @return true if SCO is connected, false otherwise or on error
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800806 * @hide
807 */
808 public boolean isAudioOn() {
Matthew Xie563e4142012-10-09 22:10:37 -0700809 if (VDBG) log("isAudioOn()");
Jack He16eeac32017-08-17 12:11:18 -0700810 final IBluetoothHeadset service = mService;
811 if (service != null && isEnabled()) {
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800812 try {
Jack He16eeac32017-08-17 12:11:18 -0700813 return service.isAudioOn();
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800814 } catch (RemoteException e) {
Jack Hea355e5e2017-08-22 16:06:54 -0700815 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800816 }
817 }
Jack He16eeac32017-08-17 12:11:18 -0700818 if (service == null) Log.w(TAG, "Proxy not attached to service");
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800819 return false;
820
821 }
822
823 /**
824 * Initiates a connection of headset audio.
825 * It setup SCO channel with remote connected headset device.
826 *
Jack Hea355e5e2017-08-22 16:06:54 -0700827 * @return true if successful false if there was some error such as there is no connected
828 * headset
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800829 * @hide
830 */
831 public boolean connectAudio() {
Jack He16eeac32017-08-17 12:11:18 -0700832 final IBluetoothHeadset service = mService;
833 if (service != null && isEnabled()) {
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800834 try {
Jack He16eeac32017-08-17 12:11:18 -0700835 return service.connectAudio();
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800836 } catch (RemoteException e) {
837 Log.e(TAG, e.toString());
838 }
839 } else {
840 Log.w(TAG, "Proxy not attached to service");
841 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
842 }
843 return false;
844 }
845
846 /**
847 * Initiates a disconnection of headset audio.
848 * It tears down the SCO channel from remote headset device.
849 *
Jack Hea355e5e2017-08-22 16:06:54 -0700850 * @return true if successful false if there was some error such as there is no connected SCO
851 * channel
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800852 * @hide
853 */
854 public boolean disconnectAudio() {
Jack He16eeac32017-08-17 12:11:18 -0700855 final IBluetoothHeadset service = mService;
856 if (service != null && isEnabled()) {
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800857 try {
Jack He16eeac32017-08-17 12:11:18 -0700858 return service.disconnectAudio();
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800859 } catch (RemoteException e) {
860 Log.e(TAG, e.toString());
861 }
862 } else {
863 Log.w(TAG, "Proxy not attached to service");
864 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
865 }
866 return false;
867 }
868
869 /**
Jaikumar Ganeshdde68c62011-01-24 13:55:27 -0800870 * Initiates a SCO channel connection with the headset (if connected).
871 * Also initiates a virtual voice call for Handsfree devices as many devices
872 * do not accept SCO audio without a call.
873 * This API allows the handsfree device to be used for routing non-cellular
874 * call audio.
Jaikumar Ganeshf2e6b132010-10-26 17:10:09 -0700875 *
876 * @param device Remote Bluetooth Device
877 * @return true if successful, false if there was some error.
878 * @hide
879 */
Jaikumar Ganeshdde68c62011-01-24 13:55:27 -0800880 public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
881 if (DBG) log("startScoUsingVirtualVoiceCall()");
Jack He16eeac32017-08-17 12:11:18 -0700882 final IBluetoothHeadset service = mService;
883 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganeshf2e6b132010-10-26 17:10:09 -0700884 try {
Jack He16eeac32017-08-17 12:11:18 -0700885 return service.startScoUsingVirtualVoiceCall(device);
Jaikumar Ganeshf2e6b132010-10-26 17:10:09 -0700886 } catch (RemoteException e) {
887 Log.e(TAG, e.toString());
888 }
889 } else {
890 Log.w(TAG, "Proxy not attached to service");
891 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
892 }
893 return false;
894 }
895
896 /**
Jaikumar Ganeshdde68c62011-01-24 13:55:27 -0800897 * Terminates an ongoing SCO connection and the associated virtual
898 * call.
Jaikumar Ganeshf2e6b132010-10-26 17:10:09 -0700899 *
900 * @param device Remote Bluetooth Device
901 * @return true if successful, false if there was some error.
902 * @hide
903 */
Jaikumar Ganeshdde68c62011-01-24 13:55:27 -0800904 public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
905 if (DBG) log("stopScoUsingVirtualVoiceCall()");
Jack He16eeac32017-08-17 12:11:18 -0700906 final IBluetoothHeadset service = mService;
907 if (service != null && isEnabled() && isValidDevice(device)) {
Jaikumar Ganeshf2e6b132010-10-26 17:10:09 -0700908 try {
Jack He16eeac32017-08-17 12:11:18 -0700909 return service.stopScoUsingVirtualVoiceCall(device);
Jaikumar Ganeshf2e6b132010-10-26 17:10:09 -0700910 } catch (RemoteException e) {
911 Log.e(TAG, e.toString());
912 }
913 } else {
914 Log.w(TAG, "Proxy not attached to service");
915 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
916 }
917 return false;
918 }
919
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800920 /**
921 * Notify Headset of phone state change.
922 * This is a backdoor for phone app to call BluetoothHeadset since
923 * there is currently not a good way to get precise call state change outside
924 * of phone app.
925 *
926 * @hide
927 */
928 public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
Jack Hea355e5e2017-08-22 16:06:54 -0700929 int type) {
Jack He16eeac32017-08-17 12:11:18 -0700930 final IBluetoothHeadset service = mService;
931 if (service != null && isEnabled()) {
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800932 try {
Jack He16eeac32017-08-17 12:11:18 -0700933 service.phoneStateChanged(numActive, numHeld, callState, number, type);
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800934 } catch (RemoteException e) {
935 Log.e(TAG, e.toString());
936 }
937 } else {
938 Log.w(TAG, "Proxy not attached to service");
939 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
940 }
941 }
942
943 /**
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800944 * Send Headset of CLCC response
945 *
946 * @hide
947 */
948 public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
Jack Hea355e5e2017-08-22 16:06:54 -0700949 String number, int type) {
Jack He16eeac32017-08-17 12:11:18 -0700950 final IBluetoothHeadset service = mService;
951 if (service != null && isEnabled()) {
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800952 try {
Jack He16eeac32017-08-17 12:11:18 -0700953 service.clccResponse(index, direction, status, mode, mpty, number, type);
Matthew Xie3e8c82e2012-02-16 16:57:18 -0800954 } catch (RemoteException e) {
955 Log.e(TAG, e.toString());
956 }
957 } else {
958 Log.w(TAG, "Proxy not attached to service");
959 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
960 }
961 }
962
Edward Jee922d41b2013-08-16 04:07:49 -0700963 /**
964 * Sends a vendor-specific unsolicited result code to the headset.
965 *
Jack Hea355e5e2017-08-22 16:06:54 -0700966 * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code
967 * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the
968 * string <code>"+ANDROID: 0"</code> will be sent.
Edward Jee922d41b2013-08-16 04:07:49 -0700969 *
Ying Wangb2405782013-08-26 17:48:22 -0700970 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
Edward Jee922d41b2013-08-16 04:07:49 -0700971 *
972 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
973 *
974 * @param device Bluetooth headset.
975 * @param command A vendor-specific command.
976 * @param arg The argument that will be attached to the command.
977 * @return {@code false} if there is no headset connected, or if the command is not an allowed
Jack Hea355e5e2017-08-22 16:06:54 -0700978 * vendor-specific unsolicited result code, or on error. {@code true} otherwise.
Edward Jee922d41b2013-08-16 04:07:49 -0700979 * @throws IllegalArgumentException if {@code command} is {@code null}.
980 */
981 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
982 String arg) {
983 if (DBG) {
984 log("sendVendorSpecificResultCode()");
985 }
986 if (command == null) {
987 throw new IllegalArgumentException("command is null");
988 }
Jack He16eeac32017-08-17 12:11:18 -0700989 final IBluetoothHeadset service = mService;
990 if (service != null && isEnabled() && isValidDevice(device)) {
Edward Jee922d41b2013-08-16 04:07:49 -0700991 try {
Jack He16eeac32017-08-17 12:11:18 -0700992 return service.sendVendorSpecificResultCode(device, command, arg);
Edward Jee922d41b2013-08-16 04:07:49 -0700993 } catch (RemoteException e) {
994 Log.e(TAG, Log.getStackTraceString(new Throwable()));
995 }
996 }
Jack He16eeac32017-08-17 12:11:18 -0700997 if (service == null) {
Edward Jee922d41b2013-08-16 04:07:49 -0700998 Log.w(TAG, "Proxy not attached to service");
999 }
1000 return false;
1001 }
1002
Mudumba Ananth177d0782014-04-27 13:11:00 -07001003 /**
Jack He05f4bc42018-01-03 12:13:26 -08001004 * Select a connected device as active.
1005 *
1006 * The active device selection is per profile. An active device's
1007 * purpose is profile-specific. For example, in HFP and HSP profiles,
1008 * it is the device used for phone call audio. If a remote device is not
1009 * connected, it cannot be selected as active.
1010 *
1011 * <p> This API returns false in scenarios like the profile on the
1012 * device is not connected or Bluetooth is not turned on.
1013 * When this API returns true, it is guaranteed that the
1014 * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
1015 * with the active device.
1016 *
1017 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
1018 * permission.
1019 *
1020 * @param device Remote Bluetooth Device, could be null if phone call audio should not be
1021 * streamed to a headset
1022 * @return false on immediate error, true otherwise
1023 * @hide
1024 */
1025 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
1026 public boolean setActiveDevice(@Nullable BluetoothDevice device) {
1027 if (DBG) {
1028 Log.d(TAG, "setActiveDevice: " + device);
1029 }
1030 final IBluetoothHeadset service = mService;
1031 if (service != null && isEnabled() && (device == null || isValidDevice(device))) {
1032 try {
1033 return service.setActiveDevice(device);
1034 } catch (RemoteException e) {
1035 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1036 }
1037 }
1038 if (service == null) {
1039 Log.w(TAG, "Proxy not attached to service");
1040 }
1041 return false;
1042 }
1043
1044 /**
1045 * Get the connected device that is active.
1046 *
1047 * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
1048 * permission.
1049 *
1050 * @return the connected device that is active or null if no device
1051 * is active.
1052 * @hide
1053 */
1054 @RequiresPermission(android.Manifest.permission.BLUETOOTH)
1055 public BluetoothDevice getActiveDevice() {
1056 if (VDBG) {
1057 Log.d(TAG, "getActiveDevice");
1058 }
1059 final IBluetoothHeadset service = mService;
1060 if (service != null && isEnabled()) {
1061 try {
1062 return service.getActiveDevice();
1063 } catch (RemoteException e) {
1064 Log.e(TAG, Log.getStackTraceString(new Throwable()));
1065 }
1066 }
1067 if (service == null) {
1068 Log.w(TAG, "Proxy not attached to service");
1069 }
1070 return null;
1071 }
1072
1073 /**
Jack Hee86bdca2016-11-17 16:19:43 -08001074 * check if in-band ringing is supported for this platform.
1075 *
Jack Hea355e5e2017-08-22 16:06:54 -07001076 * @return true if in-band ringing is supported false if in-band ringing is not supported
Jack Hee86bdca2016-11-17 16:19:43 -08001077 * @hide
1078 */
1079 public static boolean isInbandRingingSupported(Context context) {
1080 return context.getResources().getBoolean(
1081 com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support);
1082 }
1083
Jack He2992cd02017-08-22 21:21:23 -07001084 private final IBluetoothProfileServiceConnection mConnection =
1085 new IBluetoothProfileServiceConnection.Stub() {
Benjamin Franze8b98922014-11-12 15:57:54 +00001086 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001087 public void onServiceConnected(ComponentName className, IBinder service) {
1088 if (DBG) Log.d(TAG, "Proxy object connected");
Jeff Sharkey0a17db12016-11-04 11:23:46 -06001089 mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service));
Benjamin Franze8b98922014-11-12 15:57:54 +00001090 mHandler.sendMessage(mHandler.obtainMessage(
1091 MESSAGE_HEADSET_SERVICE_CONNECTED));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001092 }
Jack Hea355e5e2017-08-22 16:06:54 -07001093
Benjamin Franze8b98922014-11-12 15:57:54 +00001094 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001095 public void onServiceDisconnected(ComponentName className) {
1096 if (DBG) Log.d(TAG, "Proxy object disconnected");
1097 mService = null;
Benjamin Franze8b98922014-11-12 15:57:54 +00001098 mHandler.sendMessage(mHandler.obtainMessage(
1099 MESSAGE_HEADSET_SERVICE_DISCONNECTED));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001100 }
1101 };
The Android Open Source Projectf5b4b982009-03-05 20:00:43 -08001102
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -07001103 private boolean isEnabled() {
Jack He16eeac32017-08-17 12:11:18 -07001104 return mAdapter.getState() == BluetoothAdapter.STATE_ON;
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -07001105 }
1106
Jaikumar Ganesh6f7a9732011-04-06 11:09:30 -07001107 private boolean isDisabled() {
Jack He16eeac32017-08-17 12:11:18 -07001108 return mAdapter.getState() == BluetoothAdapter.STATE_OFF;
Jaikumar Ganesh6f7a9732011-04-06 11:09:30 -07001109 }
1110
Jack He16eeac32017-08-17 12:11:18 -07001111 private static boolean isValidDevice(BluetoothDevice device) {
1112 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -07001113 }
1114
The Android Open Source Projectf5b4b982009-03-05 20:00:43 -08001115 private static void log(String msg) {
1116 Log.d(TAG, msg);
1117 }
Benjamin Franze8b98922014-11-12 15:57:54 +00001118
1119 private final Handler mHandler = new Handler(Looper.getMainLooper()) {
1120 @Override
1121 public void handleMessage(Message msg) {
1122 switch (msg.what) {
1123 case MESSAGE_HEADSET_SERVICE_CONNECTED: {
1124 if (mServiceListener != null) {
1125 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
1126 BluetoothHeadset.this);
1127 }
1128 break;
1129 }
1130 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
1131 if (mServiceListener != null) {
1132 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
1133 }
Benjamin Franze8b98922014-11-12 15:57:54 +00001134 break;
1135 }
1136 }
1137 }
1138 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001139}