blob: 16456565b343ba339394f15e660bd5c6ecf6d478 [file] [log] [blame]
nxpandroid64fd68c2015-09-23 16:45:15 +05301/*
2 * Copyright (C) 2012 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 com.android.nfc.handover;
18
19import android.bluetooth.BluetoothA2dp;
20import android.bluetooth.BluetoothAdapter;
nxpandroid6fd9cdb2017-07-12 18:25:41 +053021import android.bluetooth.BluetoothClass;
nxpandroid64fd68c2015-09-23 16:45:15 +053022import android.bluetooth.BluetoothDevice;
23import android.bluetooth.BluetoothHeadset;
Suhas Suresh5cfd8502018-04-25 12:21:16 +053024import android.bluetooth.BluetoothHidHost;
nxpandroid64fd68c2015-09-23 16:45:15 +053025import android.bluetooth.BluetoothProfile;
nxpandroid6fd9cdb2017-07-12 18:25:41 +053026import android.bluetooth.BluetoothUuid;
nxpandroid281eb922016-08-25 20:27:46 +053027import android.bluetooth.OobData;
nxpandroid64fd68c2015-09-23 16:45:15 +053028import android.content.BroadcastReceiver;
29import android.content.ContentResolver;
30import android.content.Context;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.media.session.MediaSessionLegacyHelper;
34import android.os.Handler;
35import android.os.Looper;
36import android.os.Message;
37import android.os.ParcelUuid;
38import android.provider.Settings;
39import android.util.Log;
40import android.view.KeyEvent;
41import android.widget.Toast;
42
43import com.android.nfc.R;
44
45/**
46 * Connects / Disconnects from a Bluetooth headset (or any device that
47 * might implement BT HSP, HFP, A2DP, or HOGP sink) when touched with NFC.
48 *
49 * This object is created on an NFC interaction, and determines what
50 * sequence of Bluetooth actions to take, and executes them. It is not
51 * designed to be re-used after the sequence has completed or timed out.
52 * Subsequent NFC interactions should use new objects.
53 *
54 */
55public class BluetoothPeripheralHandover implements BluetoothProfile.ServiceListener {
56 static final String TAG = "BluetoothPeripheralHandover";
57 static final boolean DBG = false;
58
59 static final String ACTION_ALLOW_CONNECT = "com.android.nfc.handover.action.ALLOW_CONNECT";
60 static final String ACTION_DENY_CONNECT = "com.android.nfc.handover.action.DENY_CONNECT";
nxpandroid6fd9cdb2017-07-12 18:25:41 +053061 static final String ACTION_TIMEOUT_CONNECT = "com.android.nfc.handover.action.TIMEOUT_CONNECT";
nxpandroid64fd68c2015-09-23 16:45:15 +053062
63 static final int TIMEOUT_MS = 20000;
nxpandroid6fd9cdb2017-07-12 18:25:41 +053064 static final int RETRY_PAIRING_WAIT_TIME_MS = 2000;
65 static final int RETRY_CONNECT_WAIT_TIME_MS = 5000;
nxpandroid64fd68c2015-09-23 16:45:15 +053066
67 static final int STATE_INIT = 0;
68 static final int STATE_WAITING_FOR_PROXIES = 1;
69 static final int STATE_INIT_COMPLETE = 2;
70 static final int STATE_WAITING_FOR_BOND_CONFIRMATION = 3;
71 static final int STATE_BONDING = 4;
72 static final int STATE_CONNECTING = 5;
73 static final int STATE_DISCONNECTING = 6;
74 static final int STATE_COMPLETE = 7;
75
76 static final int RESULT_PENDING = 0;
77 static final int RESULT_CONNECTED = 1;
78 static final int RESULT_DISCONNECTED = 2;
79
80 static final int ACTION_INIT = 0;
81 static final int ACTION_DISCONNECT = 1;
82 static final int ACTION_CONNECT = 2;
83
84 static final int MSG_TIMEOUT = 1;
85 static final int MSG_NEXT_STEP = 2;
nxpandroid6fd9cdb2017-07-12 18:25:41 +053086 static final int MSG_RETRY = 3;
87
88 static final int MAX_RETRY_COUNT = 3;
nxpandroid64fd68c2015-09-23 16:45:15 +053089
90 final Context mContext;
91 final BluetoothDevice mDevice;
92 final String mName;
93 final Callback mCallback;
94 final BluetoothAdapter mBluetoothAdapter;
95 final int mTransport;
96 final boolean mProvisioning;
97
98 final Object mLock = new Object();
99
100 // only used on main thread
101 int mAction;
102 int mState;
103 int mHfpResult; // used only in STATE_CONNECTING and STATE_DISCONNETING
104 int mA2dpResult; // used only in STATE_CONNECTING and STATE_DISCONNETING
105 int mHidResult;
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530106 int mRetryCount;
nxpandroid281eb922016-08-25 20:27:46 +0530107 OobData mOobData;
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530108 boolean mIsHeadsetAvailable;
109 boolean mIsA2dpAvailable;
nxpandroid64fd68c2015-09-23 16:45:15 +0530110
111 // protected by mLock
112 BluetoothA2dp mA2dp;
113 BluetoothHeadset mHeadset;
Suhas Suresh5cfd8502018-04-25 12:21:16 +0530114 BluetoothHidHost mInput;
nxpandroid64fd68c2015-09-23 16:45:15 +0530115
116 public interface Callback {
117 public void onBluetoothPeripheralHandoverComplete(boolean connected);
118 }
119
120 public BluetoothPeripheralHandover(Context context, BluetoothDevice device, String name,
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530121 int transport, OobData oobData, ParcelUuid[] uuids, BluetoothClass btClass,
122 Callback callback) {
nxpandroid64fd68c2015-09-23 16:45:15 +0530123 checkMainThread(); // mHandler must get get constructed on Main Thread for toasts to work
124 mContext = context;
125 mDevice = device;
126 mName = name;
127 mTransport = transport;
nxpandroid281eb922016-08-25 20:27:46 +0530128 mOobData = oobData;
nxpandroid64fd68c2015-09-23 16:45:15 +0530129 mCallback = callback;
130 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
131
132 ContentResolver contentResolver = mContext.getContentResolver();
133 mProvisioning = Settings.Secure.getInt(contentResolver,
134 Settings.Global.DEVICE_PROVISIONED, 0) == 0;
135
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530136 mIsHeadsetAvailable = hasHeadsetCapability(uuids, btClass);
137 mIsA2dpAvailable = hasA2dpCapability(uuids, btClass);
138
139 // Capability information is from NDEF optional field, then it might be empty.
140 // If all capabilities indicate false, try to connect Headset and A2dp just in case.
141 if (!mIsHeadsetAvailable && !mIsA2dpAvailable) {
142 mIsHeadsetAvailable = true;
143 mIsA2dpAvailable = true;
144 }
145
nxpandroid64fd68c2015-09-23 16:45:15 +0530146 mState = STATE_INIT;
147 }
148
149 public boolean hasStarted() {
150 return mState != STATE_INIT;
151 }
152
153 /**
154 * Main entry point. This method is usually called after construction,
155 * to begin the BT sequence. Must be called on Main thread.
156 */
157 public boolean start() {
158 checkMainThread();
159 if (mState != STATE_INIT || mBluetoothAdapter == null
160 || (mProvisioning && mTransport != BluetoothDevice.TRANSPORT_LE)) {
161 return false;
162 }
163
164
165 IntentFilter filter = new IntentFilter();
166 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
167 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
168 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
169 filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
Suhas Suresh5cfd8502018-04-25 12:21:16 +0530170 filter.addAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
nxpandroid64fd68c2015-09-23 16:45:15 +0530171 filter.addAction(ACTION_ALLOW_CONNECT);
172 filter.addAction(ACTION_DENY_CONNECT);
173
174 mContext.registerReceiver(mReceiver, filter);
175
176 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), TIMEOUT_MS);
177
178 mAction = ACTION_INIT;
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530179 mRetryCount = 0;
nxpandroid64fd68c2015-09-23 16:45:15 +0530180
181 nextStep();
182
183 return true;
184 }
185
186 /**
187 * Called to execute next step in state machine
188 */
189 void nextStep() {
190 if (mAction == ACTION_INIT) {
191 nextStepInit();
192 } else if (mAction == ACTION_CONNECT) {
193 nextStepConnect();
194 } else {
195 nextStepDisconnect();
196 }
197 }
198
199 /*
200 * Enables bluetooth and gets the profile proxies
201 */
202 void nextStepInit() {
203 switch (mState) {
204 case STATE_INIT:
205 if (mA2dp == null || mHeadset == null || mInput == null) {
206 mState = STATE_WAITING_FOR_PROXIES;
207 if (!getProfileProxys()) {
208 complete(false);
209 }
210 break;
211 }
212 // fall-through
213 case STATE_WAITING_FOR_PROXIES:
214 mState = STATE_INIT_COMPLETE;
215 // Check connected devices and see if we need to disconnect
216 synchronized(mLock) {
217 if (mTransport == BluetoothDevice.TRANSPORT_LE) {
218 if (mInput.getConnectedDevices().contains(mDevice)) {
219 Log.i(TAG, "ACTION_DISCONNECT addr=" + mDevice + " name=" + mName);
220 mAction = ACTION_DISCONNECT;
221 } else {
222 Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName);
223 mAction = ACTION_CONNECT;
224 }
225 } else {
226 if (mA2dp.getConnectedDevices().contains(mDevice) ||
227 mHeadset.getConnectedDevices().contains(mDevice)) {
228 Log.i(TAG, "ACTION_DISCONNECT addr=" + mDevice + " name=" + mName);
229 mAction = ACTION_DISCONNECT;
230 } else {
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530231 // Check if each profile of the device is disabled or not
232 if (mHeadset.getPriority(mDevice) == BluetoothProfile.PRIORITY_OFF) {
233 mIsHeadsetAvailable = false;
234 }
235 if (mA2dp.getPriority(mDevice) == BluetoothProfile.PRIORITY_OFF) {
236 mIsA2dpAvailable = false;
237 }
238 if (!mIsHeadsetAvailable && !mIsA2dpAvailable) {
239 Log.i(TAG, "Both Headset and A2DP profiles are unavailable");
240 complete(false);
241 break;
242 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530243 Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName);
244 mAction = ACTION_CONNECT;
245 }
246 }
247 }
248 nextStep();
249 }
250
251 }
252
253 void nextStepDisconnect() {
254 switch (mState) {
255 case STATE_INIT_COMPLETE:
256 mState = STATE_DISCONNECTING;
257 synchronized (mLock) {
258 if (mTransport == BluetoothDevice.TRANSPORT_LE) {
259 if (mInput.getConnectionState(mDevice)
260 != BluetoothProfile.STATE_DISCONNECTED) {
261 mHidResult = RESULT_PENDING;
262 mInput.disconnect(mDevice);
263 toast(getToastString(R.string.disconnecting_peripheral));
264 break;
265 } else {
266 mHidResult = RESULT_DISCONNECTED;
267 }
268 } else {
269 if (mHeadset.getConnectionState(mDevice)
270 != BluetoothProfile.STATE_DISCONNECTED) {
271 mHfpResult = RESULT_PENDING;
272 mHeadset.disconnect(mDevice);
273 } else {
274 mHfpResult = RESULT_DISCONNECTED;
275 }
276 if (mA2dp.getConnectionState(mDevice)
277 != BluetoothProfile.STATE_DISCONNECTED) {
278 mA2dpResult = RESULT_PENDING;
279 mA2dp.disconnect(mDevice);
280 } else {
281 mA2dpResult = RESULT_DISCONNECTED;
282 }
283 if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
284 toast(getToastString(R.string.disconnecting_peripheral));
285 break;
286 }
287 }
288 }
289 // fall-through
290 case STATE_DISCONNECTING:
291 if (mTransport == BluetoothDevice.TRANSPORT_LE) {
292 if (mHidResult == RESULT_DISCONNECTED) {
293 toast(getToastString(R.string.disconnected_peripheral));
294 complete(false);
295 }
296
297 break;
298 } else {
299 if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
300 // still disconnecting
301 break;
302 }
303 if (mA2dpResult == RESULT_DISCONNECTED && mHfpResult == RESULT_DISCONNECTED) {
304 toast(getToastString(R.string.disconnected_peripheral));
305 }
306 complete(false);
307 break;
308 }
309
310 }
311
312 }
313
314 private String getToastString(int resid) {
315 return mContext.getString(resid, mName != null ? mName : R.string.device);
316 }
317
318 boolean getProfileProxys() {
319
320 if (mTransport == BluetoothDevice.TRANSPORT_LE) {
Suhas Suresh5cfd8502018-04-25 12:21:16 +0530321 if (!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.HID_HOST))
nxpandroid64fd68c2015-09-23 16:45:15 +0530322 return false;
323 } else {
324 if(!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.HEADSET))
325 return false;
326
327 if(!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.A2DP))
328 return false;
329 }
330
331 return true;
332 }
333
334 void nextStepConnect() {
335 switch (mState) {
336 case STATE_INIT_COMPLETE:
nxf500513a018e72019-04-23 17:11:41 +0530337
nxpandroid64fd68c2015-09-23 16:45:15 +0530338 if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
339 requestPairConfirmation();
340 mState = STATE_WAITING_FOR_BOND_CONFIRMATION;
341 break;
342 }
343
344 if (mTransport == BluetoothDevice.TRANSPORT_LE) {
345 if (mDevice.getBondState() != BluetoothDevice.BOND_NONE) {
346 mDevice.removeBond();
347 requestPairConfirmation();
348 mState = STATE_WAITING_FOR_BOND_CONFIRMATION;
349 break;
350 }
351 }
352 // fall-through
353 case STATE_WAITING_FOR_BOND_CONFIRMATION:
354 if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
355 startBonding();
356 break;
357 }
358 // fall-through
359 case STATE_BONDING:
360 // Bluetooth Profile service will correctly serialize
361 // HFP then A2DP connect
362 mState = STATE_CONNECTING;
363 synchronized (mLock) {
Suhas Sureshf7ab1092018-05-15 12:17:47 +0530364 if (mTransport == BluetoothDevice.TRANSPORT_LE) {
365 if (mInput.getConnectionState(mDevice)
366 != BluetoothProfile.STATE_CONNECTED) {
367 mHidResult = RESULT_PENDING;
368 toast(getToastString(R.string.connecting_peripheral));
369 break;
370 } else {
371 mHidResult = RESULT_CONNECTED;
372 }
373 } else {
nxpandroid64fd68c2015-09-23 16:45:15 +0530374 if (mHeadset.getConnectionState(mDevice) !=
375 BluetoothProfile.STATE_CONNECTED) {
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530376 if (mIsHeadsetAvailable) {
377 mHfpResult = RESULT_PENDING;
378 mHeadset.connect(mDevice);
379 } else {
380 mHfpResult = RESULT_DISCONNECTED;
381 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530382 } else {
383 mHfpResult = RESULT_CONNECTED;
384 }
385 if (mA2dp.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) {
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530386 if (mIsA2dpAvailable) {
387 mA2dpResult = RESULT_PENDING;
388 mA2dp.connect(mDevice);
389 } else {
390 mA2dpResult = RESULT_DISCONNECTED;
391 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530392 } else {
393 mA2dpResult = RESULT_CONNECTED;
394 }
395 if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530396 if (mRetryCount == 0) {
397 toast(getToastString(R.string.connecting_peripheral));
398 }
399 if (mRetryCount < MAX_RETRY_COUNT) {
400 sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS);
401 break;
402 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530403 }
404 }
405 }
406 // fall-through
407 case STATE_CONNECTING:
Suhas Sureshf7ab1092018-05-15 12:17:47 +0530408 if (mTransport == BluetoothDevice.TRANSPORT_LE) {
409 if (mHidResult == RESULT_PENDING) {
410 break;
411 } else if (mHidResult == RESULT_CONNECTED) {
412 toast(getToastString(R.string.connected_peripheral));
413 mDevice.setAlias(mName);
414 complete(true);
415 } else {
416 toast (getToastString(R.string.connect_peripheral_failed));
417 complete(false);
418 }
419 } else {
nxpandroid64fd68c2015-09-23 16:45:15 +0530420 if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
421 // another connection type still pending
422 break;
423 }
424 if (mA2dpResult == RESULT_CONNECTED || mHfpResult == RESULT_CONNECTED) {
425 // we'll take either as success
426 toast(getToastString(R.string.connected_peripheral));
427 if (mA2dpResult == RESULT_CONNECTED) startTheMusic();
428 mDevice.setAlias(mName);
429 complete(true);
430 } else {
431 toast (getToastString(R.string.connect_peripheral_failed));
432 complete(false);
433 }
434 }
435 break;
436 }
437 }
438
439 void startBonding() {
440 mState = STATE_BONDING;
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530441 if (mRetryCount == 0) {
442 toast(getToastString(R.string.pairing_peripheral));
443 }
nxpandroid281eb922016-08-25 20:27:46 +0530444 if (mOobData != null) {
445 if (!mDevice.createBondOutOfBand(mTransport, mOobData)) {
446 toast(getToastString(R.string.pairing_peripheral_failed));
447 complete(false);
448 }
449 } else if (!mDevice.createBond(mTransport)) {
450 toast(getToastString(R.string.pairing_peripheral_failed));
451 complete(false);
nxpandroid64fd68c2015-09-23 16:45:15 +0530452 }
453 }
454
455 void handleIntent(Intent intent) {
456 String action = intent.getAction();
457 // Everything requires the device to match...
458 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
459 if (!mDevice.equals(device)) return;
460
461 if (ACTION_ALLOW_CONNECT.equals(action)) {
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530462 mHandler.removeMessages(MSG_TIMEOUT);
463 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), TIMEOUT_MS);
nxpandroid64fd68c2015-09-23 16:45:15 +0530464 nextStepConnect();
465 } else if (ACTION_DENY_CONNECT.equals(action)) {
466 complete(false);
467 } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)
468 && mState == STATE_BONDING) {
469 int bond = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
470 BluetoothAdapter.ERROR);
471 if (bond == BluetoothDevice.BOND_BONDED) {
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530472 mRetryCount = 0;
nxpandroid64fd68c2015-09-23 16:45:15 +0530473 nextStepConnect();
474 } else if (bond == BluetoothDevice.BOND_NONE) {
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530475 if (mRetryCount < MAX_RETRY_COUNT) {
476 sendRetryMessage(RETRY_PAIRING_WAIT_TIME_MS);
477 } else {
478 toast(getToastString(R.string.pairing_peripheral_failed));
479 complete(false);
480 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530481 }
482 } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
483 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) {
484 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
485 if (state == BluetoothProfile.STATE_CONNECTED) {
486 mHfpResult = RESULT_CONNECTED;
487 nextStep();
488 } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530489 if (mAction == ACTION_CONNECT && mRetryCount < MAX_RETRY_COUNT) {
490 sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS);
491 } else {
492 mHfpResult = RESULT_DISCONNECTED;
493 nextStep();
494 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530495 }
496 } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
497 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) {
498 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
499 if (state == BluetoothProfile.STATE_CONNECTED) {
500 mA2dpResult = RESULT_CONNECTED;
501 nextStep();
502 } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530503 if (mAction == ACTION_CONNECT && mRetryCount < MAX_RETRY_COUNT) {
504 sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS);
505 } else {
506 mA2dpResult = RESULT_DISCONNECTED;
507 nextStep();
508 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530509 }
Suhas Suresh5cfd8502018-04-25 12:21:16 +0530510 } else if (BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
nxpandroid64fd68c2015-09-23 16:45:15 +0530511 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) {
512 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
513 if (state == BluetoothProfile.STATE_CONNECTED) {
514 mHidResult = RESULT_CONNECTED;
515 nextStep();
516 } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
517 mHidResult = RESULT_DISCONNECTED;
518 nextStep();
519 }
520 }
521 }
522
523 void complete(boolean connected) {
524 if (DBG) Log.d(TAG, "complete()");
525 mState = STATE_COMPLETE;
526 mContext.unregisterReceiver(mReceiver);
527 mHandler.removeMessages(MSG_TIMEOUT);
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530528 mHandler.removeMessages(MSG_RETRY);
nxpandroid64fd68c2015-09-23 16:45:15 +0530529 synchronized (mLock) {
530 if (mA2dp != null) {
531 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dp);
532 }
533 if (mHeadset != null) {
534 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadset);
535 }
536
537 if (mInput != null) {
Suhas Suresh5cfd8502018-04-25 12:21:16 +0530538 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HID_HOST, mInput);
nxpandroid64fd68c2015-09-23 16:45:15 +0530539 }
540
541 mA2dp = null;
542 mHeadset = null;
543 mInput = null;
544 }
545 mCallback.onBluetoothPeripheralHandoverComplete(connected);
546 }
547
548 void toast(CharSequence text) {
549 Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show();
550 }
551
552 void startTheMusic() {
553 MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
554 if (helper != null) {
555 KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
556 helper.sendMediaButtonEvent(keyEvent, false);
557 keyEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY);
558 helper.sendMediaButtonEvent(keyEvent, false);
559 } else {
560 Log.w(TAG, "Unable to send media key event");
561 }
562 }
563
564 void requestPairConfirmation() {
565 Intent dialogIntent = new Intent(mContext, ConfirmConnectActivity.class);
nxpandroid1153eb32015-11-06 18:46:58 +0530566 dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
nxpandroid64fd68c2015-09-23 16:45:15 +0530567 dialogIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
Nikhil Chhabra0bb13512018-01-08 21:00:37 +0530568 dialogIntent.putExtra(BluetoothDevice.EXTRA_NAME, mName);
nxpandroid64fd68c2015-09-23 16:45:15 +0530569
570 mContext.startActivity(dialogIntent);
571 }
572
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530573 boolean hasA2dpCapability(ParcelUuid[] uuids, BluetoothClass btClass) {
574 if (uuids != null) {
575 for (ParcelUuid uuid : uuids) {
576 if (BluetoothUuid.isAudioSink(uuid) || BluetoothUuid.isAdvAudioDist(uuid)) {
577 return true;
578 }
579 }
580 }
581 if (btClass != null && btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
582 return true;
583 }
584 return false;
585 }
586
587 boolean hasHeadsetCapability(ParcelUuid[] uuids, BluetoothClass btClass) {
588 if (uuids != null) {
589 for (ParcelUuid uuid : uuids) {
590 if (BluetoothUuid.isHandsfree(uuid) || BluetoothUuid.isHeadset(uuid)) {
591 return true;
592 }
593 }
594 }
595 if (btClass != null && btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
596 return true;
597 }
598 return false;
599 }
600
nxpandroid64fd68c2015-09-23 16:45:15 +0530601 final Handler mHandler = new Handler() {
602 @Override
603 public void handleMessage(Message msg) {
604 switch (msg.what) {
605 case MSG_TIMEOUT:
606 if (mState == STATE_COMPLETE) return;
607 Log.i(TAG, "Timeout completing BT handover");
Suhas Sureshc21db002018-04-25 12:28:57 +0530608 if (mState == STATE_WAITING_FOR_BOND_CONFIRMATION) {
609 mContext.sendBroadcast(new Intent(ACTION_TIMEOUT_CONNECT));
610 } else if (mState == STATE_BONDING) {
611 toast(getToastString(R.string.pairing_peripheral_failed));
612 } else if (mState == STATE_CONNECTING) {
613 if (mHidResult == RESULT_PENDING) {
614 mHidResult = RESULT_DISCONNECTED;
615 }
616 if (mA2dpResult == RESULT_PENDING) {
617 mA2dpResult = RESULT_DISCONNECTED;
618 }
619 if (mHfpResult == RESULT_PENDING) {
620 mHfpResult = RESULT_DISCONNECTED;
621 }
622 // Check if any one profile is connected, then it takes as success
623 nextStepConnect();
624 break;
625 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530626 complete(false);
627 break;
628 case MSG_NEXT_STEP:
629 nextStep();
630 break;
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530631 case MSG_RETRY:
632 mHandler.removeMessages(MSG_RETRY);
633 if (mState == STATE_BONDING) {
634 mState = STATE_WAITING_FOR_BOND_CONFIRMATION;
635 } else if (mState == STATE_CONNECTING) {
636 mState = STATE_BONDING;
637 }
638 mRetryCount++;
639 nextStepConnect();
640 break;
nxpandroid64fd68c2015-09-23 16:45:15 +0530641 }
642 }
643 };
644
645 final BroadcastReceiver mReceiver = new BroadcastReceiver() {
646 @Override
647 public void onReceive(Context context, Intent intent) {
648 handleIntent(intent);
649 }
650 };
651
652 static void checkMainThread() {
653 if (Looper.myLooper() != Looper.getMainLooper()) {
654 throw new IllegalThreadStateException("must be called on main thread");
655 }
656 }
657
658 @Override
659 public void onServiceConnected(int profile, BluetoothProfile proxy) {
660 synchronized (mLock) {
661 switch (profile) {
662 case BluetoothProfile.HEADSET:
663 mHeadset = (BluetoothHeadset) proxy;
664 if (mA2dp != null) {
665 mHandler.sendEmptyMessage(MSG_NEXT_STEP);
666 }
667 break;
668 case BluetoothProfile.A2DP:
669 mA2dp = (BluetoothA2dp) proxy;
670 if (mHeadset != null) {
671 mHandler.sendEmptyMessage(MSG_NEXT_STEP);
672 }
673 break;
Suhas Suresh5cfd8502018-04-25 12:21:16 +0530674 case BluetoothProfile.HID_HOST:
675 mInput = (BluetoothHidHost) proxy;
nxpandroid64fd68c2015-09-23 16:45:15 +0530676 if (mInput != null) {
677 mHandler.sendEmptyMessage(MSG_NEXT_STEP);
678 }
679 break;
680 }
681 }
682 }
683
684 @Override
685 public void onServiceDisconnected(int profile) {
686 // We can ignore these
687 }
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530688
689 void sendRetryMessage(int waitTime) {
690 if (!mHandler.hasMessages(MSG_RETRY)) {
691 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY), waitTime);
692 }
693 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530694}