• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.nfc.handover;
18 
19 import android.bluetooth.BluetoothA2dp;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothClass;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothHeadset;
24 import android.bluetooth.BluetoothInputDevice;
25 import android.bluetooth.BluetoothProfile;
26 import android.bluetooth.BluetoothUuid;
27 import android.bluetooth.OobData;
28 import android.content.BroadcastReceiver;
29 import android.content.ContentResolver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.media.session.MediaSessionLegacyHelper;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.os.Message;
37 import android.os.ParcelUuid;
38 import android.provider.Settings;
39 import android.util.Log;
40 import android.view.KeyEvent;
41 import android.widget.Toast;
42 
43 import 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  */
55 public 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";
61 
62     static final int TIMEOUT_MS = 20000;
63     static final int RETRY_PAIRING_WAIT_TIME_MS = 2000;
64     static final int RETRY_CONNECT_WAIT_TIME_MS = 5000;
65 
66     static final int STATE_INIT = 0;
67     static final int STATE_WAITING_FOR_PROXIES = 1;
68     static final int STATE_INIT_COMPLETE = 2;
69     static final int STATE_WAITING_FOR_BOND_CONFIRMATION = 3;
70     static final int STATE_BONDING = 4;
71     static final int STATE_CONNECTING = 5;
72     static final int STATE_DISCONNECTING = 6;
73     static final int STATE_COMPLETE = 7;
74 
75     static final int RESULT_PENDING = 0;
76     static final int RESULT_CONNECTED = 1;
77     static final int RESULT_DISCONNECTED = 2;
78 
79     static final int ACTION_INIT = 0;
80     static final int ACTION_DISCONNECT = 1;
81     static final int ACTION_CONNECT = 2;
82 
83     static final int MSG_TIMEOUT = 1;
84     static final int MSG_NEXT_STEP = 2;
85     static final int MSG_RETRY = 3;
86 
87     static final int MAX_RETRY_COUNT = 3;
88 
89     final Context mContext;
90     final BluetoothDevice mDevice;
91     final String mName;
92     final Callback mCallback;
93     final BluetoothAdapter mBluetoothAdapter;
94     final int mTransport;
95     final boolean mProvisioning;
96 
97     final Object mLock = new Object();
98 
99     // only used on main thread
100     int mAction;
101     int mState;
102     int mHfpResult;  // used only in STATE_CONNECTING and STATE_DISCONNETING
103     int mA2dpResult; // used only in STATE_CONNECTING and STATE_DISCONNETING
104     int mHidResult;
105     int mRetryCount;
106     OobData mOobData;
107     boolean mIsHeadsetAvailable;
108     boolean mIsA2dpAvailable;
109 
110     // protected by mLock
111     BluetoothA2dp mA2dp;
112     BluetoothHeadset mHeadset;
113     BluetoothInputDevice mInput;
114 
115     public interface Callback {
onBluetoothPeripheralHandoverComplete(boolean connected)116         public void onBluetoothPeripheralHandoverComplete(boolean connected);
117     }
118 
BluetoothPeripheralHandover(Context context, BluetoothDevice device, String name, int transport, OobData oobData, ParcelUuid[] uuids, BluetoothClass btClass, Callback callback)119     public BluetoothPeripheralHandover(Context context, BluetoothDevice device, String name,
120             int transport, OobData oobData, ParcelUuid[] uuids, BluetoothClass btClass,
121             Callback callback) {
122         checkMainThread();  // mHandler must get get constructed on Main Thread for toasts to work
123         mContext = context;
124         mDevice = device;
125         mName = name;
126         mTransport = transport;
127         mOobData = oobData;
128         mCallback = callback;
129         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
130 
131         ContentResolver contentResolver = mContext.getContentResolver();
132         mProvisioning = Settings.Secure.getInt(contentResolver,
133                 Settings.Global.DEVICE_PROVISIONED, 0) == 0;
134 
135         mIsHeadsetAvailable = hasHeadsetCapability(uuids, btClass);
136         mIsA2dpAvailable = hasA2dpCapability(uuids, btClass);
137 
138         // Capability information is from NDEF optional field, then it might be empty.
139         // If all capabilities indicate false, try to connect Headset and A2dp just in case.
140         if (!mIsHeadsetAvailable && !mIsA2dpAvailable) {
141             mIsHeadsetAvailable = true;
142             mIsA2dpAvailable = true;
143         }
144 
145         mState = STATE_INIT;
146     }
147 
hasStarted()148     public boolean hasStarted() {
149         return mState != STATE_INIT;
150     }
151 
152     /**
153      * Main entry point. This method is usually called after construction,
154      * to begin the BT sequence. Must be called on Main thread.
155      */
start()156     public boolean start() {
157         checkMainThread();
158         if (mState != STATE_INIT || mBluetoothAdapter == null
159                 || (mProvisioning && mTransport != BluetoothDevice.TRANSPORT_LE)) {
160             return false;
161         }
162 
163 
164         IntentFilter filter = new IntentFilter();
165         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
166         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
167         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
168         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
169         filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
170         filter.addAction(ACTION_ALLOW_CONNECT);
171         filter.addAction(ACTION_DENY_CONNECT);
172 
173         mContext.registerReceiver(mReceiver, filter);
174 
175         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), TIMEOUT_MS);
176 
177         mAction = ACTION_INIT;
178         mRetryCount = 0;
179 
180         nextStep();
181 
182         return true;
183     }
184 
185     /**
186      * Called to execute next step in state machine
187      */
nextStep()188     void nextStep() {
189         if (mAction == ACTION_INIT) {
190             nextStepInit();
191         } else if (mAction == ACTION_CONNECT) {
192             nextStepConnect();
193         } else {
194             nextStepDisconnect();
195         }
196     }
197 
198     /*
199      * Enables bluetooth and gets the profile proxies
200      */
nextStepInit()201     void nextStepInit() {
202         switch (mState) {
203             case STATE_INIT:
204                 if (mA2dp == null || mHeadset == null || mInput == null) {
205                     mState = STATE_WAITING_FOR_PROXIES;
206                     if (!getProfileProxys()) {
207                         complete(false);
208                     }
209                     break;
210                 }
211                 // fall-through
212             case STATE_WAITING_FOR_PROXIES:
213                 mState = STATE_INIT_COMPLETE;
214                 // Check connected devices and see if we need to disconnect
215                 synchronized(mLock) {
216                     if (mTransport == BluetoothDevice.TRANSPORT_LE) {
217                         if (mInput.getConnectedDevices().contains(mDevice)) {
218                             Log.i(TAG, "ACTION_DISCONNECT addr=" + mDevice + " name=" + mName);
219                             mAction = ACTION_DISCONNECT;
220                         } else {
221                             Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName);
222                             mAction = ACTION_CONNECT;
223                         }
224                     } else {
225                         if (mA2dp.getConnectedDevices().contains(mDevice) ||
226                                 mHeadset.getConnectedDevices().contains(mDevice)) {
227                             Log.i(TAG, "ACTION_DISCONNECT addr=" + mDevice + " name=" + mName);
228                             mAction = ACTION_DISCONNECT;
229                         } else {
230                             // Check if each profile of the device is disabled or not
231                             if (mHeadset.getPriority(mDevice) == BluetoothProfile.PRIORITY_OFF) {
232                                 mIsHeadsetAvailable = false;
233                             }
234                             if (mA2dp.getPriority(mDevice) == BluetoothProfile.PRIORITY_OFF) {
235                                 mIsA2dpAvailable = false;
236                             }
237                             if (!mIsHeadsetAvailable && !mIsA2dpAvailable) {
238                                 Log.i(TAG, "Both Headset and A2DP profiles are unavailable");
239                                 complete(false);
240                                 break;
241                             }
242                             Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName);
243                             mAction = ACTION_CONNECT;
244                         }
245                     }
246                 }
247                 nextStep();
248         }
249 
250     }
251 
nextStepDisconnect()252     void nextStepDisconnect() {
253         switch (mState) {
254             case STATE_INIT_COMPLETE:
255                 mState = STATE_DISCONNECTING;
256                 synchronized (mLock) {
257                     if (mTransport == BluetoothDevice.TRANSPORT_LE) {
258                         if (mInput.getConnectionState(mDevice)
259                                 != BluetoothProfile.STATE_DISCONNECTED) {
260                             mHidResult = RESULT_PENDING;
261                             mInput.disconnect(mDevice);
262                             toast(getToastString(R.string.disconnecting_peripheral));
263                             break;
264                         } else {
265                             mHidResult = RESULT_DISCONNECTED;
266                         }
267                     } else {
268                         if (mHeadset.getConnectionState(mDevice)
269                                 != BluetoothProfile.STATE_DISCONNECTED) {
270                             mHfpResult = RESULT_PENDING;
271                             mHeadset.disconnect(mDevice);
272                         } else {
273                             mHfpResult = RESULT_DISCONNECTED;
274                         }
275                         if (mA2dp.getConnectionState(mDevice)
276                                 != BluetoothProfile.STATE_DISCONNECTED) {
277                             mA2dpResult = RESULT_PENDING;
278                             mA2dp.disconnect(mDevice);
279                         } else {
280                             mA2dpResult = RESULT_DISCONNECTED;
281                         }
282                         if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
283                             toast(getToastString(R.string.disconnecting_peripheral));
284                             break;
285                         }
286                     }
287                 }
288                 // fall-through
289             case STATE_DISCONNECTING:
290                 if (mTransport == BluetoothDevice.TRANSPORT_LE) {
291                     if (mHidResult == RESULT_DISCONNECTED) {
292                         toast(getToastString(R.string.disconnected_peripheral));
293                         complete(false);
294                     }
295 
296                     break;
297                 } else {
298                     if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
299                         // still disconnecting
300                         break;
301                     }
302                     if (mA2dpResult == RESULT_DISCONNECTED && mHfpResult == RESULT_DISCONNECTED) {
303                         toast(getToastString(R.string.disconnected_peripheral));
304                     }
305                     complete(false);
306                     break;
307                 }
308 
309         }
310 
311     }
312 
getToastString(int resid)313     private String getToastString(int resid) {
314         return mContext.getString(resid, mName != null ? mName : R.string.device);
315     }
316 
getProfileProxys()317     boolean getProfileProxys() {
318 
319         if (mTransport == BluetoothDevice.TRANSPORT_LE) {
320             if (!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.INPUT_DEVICE))
321                 return false;
322         } else {
323             if(!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.HEADSET))
324                 return false;
325 
326             if(!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.A2DP))
327                 return false;
328         }
329 
330         return true;
331     }
332 
nextStepConnect()333     void nextStepConnect() {
334         switch (mState) {
335             case STATE_INIT_COMPLETE:
336 
337                 if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
338                     requestPairConfirmation();
339                     mState = STATE_WAITING_FOR_BOND_CONFIRMATION;
340                     break;
341                 }
342 
343                 if (mTransport == BluetoothDevice.TRANSPORT_LE) {
344                     if (mDevice.getBondState() != BluetoothDevice.BOND_NONE) {
345                         mDevice.removeBond();
346                         requestPairConfirmation();
347                         mState = STATE_WAITING_FOR_BOND_CONFIRMATION;
348                         break;
349                     }
350                 }
351                 // fall-through
352             case STATE_WAITING_FOR_BOND_CONFIRMATION:
353                 if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
354                     startBonding();
355                     break;
356                 }
357                 // fall-through
358             case STATE_BONDING:
359                 // Bluetooth Profile service will correctly serialize
360                 // HFP then A2DP connect
361                 mState = STATE_CONNECTING;
362                 synchronized (mLock) {
363                     if (mTransport != BluetoothDevice.TRANSPORT_LE) {
364                         if (mHeadset.getConnectionState(mDevice) !=
365                                 BluetoothProfile.STATE_CONNECTED) {
366                             if (mIsHeadsetAvailable) {
367                                 mHfpResult = RESULT_PENDING;
368                                 mHeadset.connect(mDevice);
369                             } else {
370                                 mHfpResult = RESULT_DISCONNECTED;
371                             }
372                         } else {
373                             mHfpResult = RESULT_CONNECTED;
374                         }
375                         if (mA2dp.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) {
376                             if (mIsA2dpAvailable) {
377                                 mA2dpResult = RESULT_PENDING;
378                                 mA2dp.connect(mDevice);
379                             } else {
380                                 mA2dpResult = RESULT_DISCONNECTED;
381                             }
382                         } else {
383                             mA2dpResult = RESULT_CONNECTED;
384                         }
385                         if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
386                             if (mRetryCount == 0) {
387                                 toast(getToastString(R.string.connecting_peripheral));
388                             }
389                             if (mRetryCount < MAX_RETRY_COUNT) {
390                                 sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS);
391                                 break;
392                             }
393                         }
394                     }
395                 }
396                 // fall-through
397             case STATE_CONNECTING:
398                 if (mTransport != BluetoothDevice.TRANSPORT_LE) {
399                     if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
400                         // another connection type still pending
401                         break;
402                     }
403                     if (mA2dpResult == RESULT_CONNECTED || mHfpResult == RESULT_CONNECTED) {
404                         // we'll take either as success
405                         toast(getToastString(R.string.connected_peripheral));
406                         if (mA2dpResult == RESULT_CONNECTED) startTheMusic();
407                         mDevice.setAlias(mName);
408                         complete(true);
409                     } else {
410                         toast (getToastString(R.string.connect_peripheral_failed));
411                         complete(false);
412                     }
413                 }
414                 break;
415         }
416     }
417 
startBonding()418     void startBonding() {
419         mState = STATE_BONDING;
420         if (mRetryCount == 0) {
421             toast(getToastString(R.string.pairing_peripheral));
422         }
423         if (mOobData != null) {
424             if (!mDevice.createBondOutOfBand(mTransport, mOobData)) {
425                 toast(getToastString(R.string.pairing_peripheral_failed));
426                 complete(false);
427             }
428         } else if (!mDevice.createBond(mTransport)) {
429                 toast(getToastString(R.string.pairing_peripheral_failed));
430                 complete(false);
431         }
432     }
433 
handleIntent(Intent intent)434     void handleIntent(Intent intent) {
435         String action = intent.getAction();
436         // Everything requires the device to match...
437         BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
438         if (!mDevice.equals(device)) return;
439 
440         if (ACTION_ALLOW_CONNECT.equals(action)) {
441             mHandler.removeMessages(MSG_TIMEOUT);
442             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), TIMEOUT_MS);
443             nextStepConnect();
444         } else if (ACTION_DENY_CONNECT.equals(action)) {
445             complete(false);
446         } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)
447                 && mState == STATE_BONDING) {
448             int bond = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
449                     BluetoothAdapter.ERROR);
450             if (bond == BluetoothDevice.BOND_BONDED) {
451                 mRetryCount = 0;
452                 nextStepConnect();
453             } else if (bond == BluetoothDevice.BOND_NONE) {
454                 if (mRetryCount < MAX_RETRY_COUNT) {
455                     sendRetryMessage(RETRY_PAIRING_WAIT_TIME_MS);
456                 } else {
457                     toast(getToastString(R.string.pairing_peripheral_failed));
458                     complete(false);
459                 }
460             }
461         } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
462                 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) {
463             int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
464             if (state == BluetoothProfile.STATE_CONNECTED) {
465                 mHfpResult = RESULT_CONNECTED;
466                 nextStep();
467             } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
468                 if (mAction == ACTION_CONNECT && mRetryCount < MAX_RETRY_COUNT) {
469                     sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS);
470                 } else {
471                     mHfpResult = RESULT_DISCONNECTED;
472                     nextStep();
473                 }
474             }
475         } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
476                 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) {
477             int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
478             if (state == BluetoothProfile.STATE_CONNECTED) {
479                 mA2dpResult = RESULT_CONNECTED;
480                 nextStep();
481             } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
482                 if (mAction == ACTION_CONNECT && mRetryCount < MAX_RETRY_COUNT) {
483                     sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS);
484                 } else {
485                     mA2dpResult = RESULT_DISCONNECTED;
486                     nextStep();
487                 }
488             }
489         } else if (BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
490                 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) {
491             int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
492             if (state == BluetoothProfile.STATE_CONNECTED) {
493                 mHidResult = RESULT_CONNECTED;
494                 nextStep();
495             } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
496                 mHidResult = RESULT_DISCONNECTED;
497                 nextStep();
498             }
499         }
500     }
501 
complete(boolean connected)502     void complete(boolean connected) {
503         if (DBG) Log.d(TAG, "complete()");
504         mState = STATE_COMPLETE;
505         mContext.unregisterReceiver(mReceiver);
506         mHandler.removeMessages(MSG_TIMEOUT);
507         mHandler.removeMessages(MSG_RETRY);
508         synchronized (mLock) {
509             if (mA2dp != null) {
510                 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dp);
511             }
512             if (mHeadset != null) {
513                 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadset);
514             }
515 
516             if (mInput != null) {
517                 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.INPUT_DEVICE, mInput);
518             }
519 
520             mA2dp = null;
521             mHeadset = null;
522             mInput = null;
523         }
524         mCallback.onBluetoothPeripheralHandoverComplete(connected);
525     }
526 
toast(CharSequence text)527     void toast(CharSequence text) {
528         Toast.makeText(mContext,  text, Toast.LENGTH_SHORT).show();
529     }
530 
startTheMusic()531     void startTheMusic() {
532         MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
533         if (helper != null) {
534             KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
535             helper.sendMediaButtonEvent(keyEvent, false);
536             keyEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY);
537             helper.sendMediaButtonEvent(keyEvent, false);
538         } else {
539             Log.w(TAG, "Unable to send media key event");
540         }
541     }
542 
requestPairConfirmation()543     void requestPairConfirmation() {
544         Intent dialogIntent = new Intent(mContext, ConfirmConnectActivity.class);
545         dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
546         dialogIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
547 
548         mContext.startActivity(dialogIntent);
549     }
550 
hasA2dpCapability(ParcelUuid[] uuids, BluetoothClass btClass)551     boolean hasA2dpCapability(ParcelUuid[] uuids, BluetoothClass btClass) {
552         if (uuids != null) {
553             for (ParcelUuid uuid : uuids) {
554                 if (BluetoothUuid.isAudioSink(uuid) || BluetoothUuid.isAdvAudioDist(uuid)) {
555                     return true;
556                 }
557             }
558         }
559         if (btClass != null && btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
560             return true;
561         }
562         return false;
563     }
564 
hasHeadsetCapability(ParcelUuid[] uuids, BluetoothClass btClass)565     boolean hasHeadsetCapability(ParcelUuid[] uuids, BluetoothClass btClass) {
566         if (uuids != null) {
567             for (ParcelUuid uuid : uuids) {
568                 if (BluetoothUuid.isHandsfree(uuid) || BluetoothUuid.isHeadset(uuid)) {
569                     return true;
570                 }
571             }
572         }
573         if (btClass != null && btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
574             return true;
575         }
576         return false;
577     }
578 
579     final Handler mHandler = new Handler() {
580         @Override
581         public void handleMessage(Message msg) {
582             switch (msg.what) {
583                 case MSG_TIMEOUT:
584                     if (mState == STATE_COMPLETE) return;
585                     Log.i(TAG, "Timeout completing BT handover");
586                     complete(false);
587                     break;
588                 case MSG_NEXT_STEP:
589                     nextStep();
590                     break;
591                 case MSG_RETRY:
592                     mHandler.removeMessages(MSG_RETRY);
593                     if (mState == STATE_BONDING) {
594                         mState = STATE_WAITING_FOR_BOND_CONFIRMATION;
595                     } else if (mState == STATE_CONNECTING) {
596                         mState = STATE_BONDING;
597                     }
598                     mRetryCount++;
599                     nextStepConnect();
600                     break;
601             }
602         }
603     };
604 
605     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
606         @Override
607         public void onReceive(Context context, Intent intent) {
608             handleIntent(intent);
609         }
610     };
611 
checkMainThread()612     static void checkMainThread() {
613         if (Looper.myLooper() != Looper.getMainLooper()) {
614             throw new IllegalThreadStateException("must be called on main thread");
615         }
616     }
617 
618     @Override
onServiceConnected(int profile, BluetoothProfile proxy)619     public void onServiceConnected(int profile, BluetoothProfile proxy) {
620         synchronized (mLock) {
621             switch (profile) {
622                 case BluetoothProfile.HEADSET:
623                     mHeadset = (BluetoothHeadset) proxy;
624                     if (mA2dp != null) {
625                         mHandler.sendEmptyMessage(MSG_NEXT_STEP);
626                     }
627                     break;
628                 case BluetoothProfile.A2DP:
629                     mA2dp = (BluetoothA2dp) proxy;
630                     if (mHeadset != null) {
631                         mHandler.sendEmptyMessage(MSG_NEXT_STEP);
632                     }
633                     break;
634                 case BluetoothProfile.INPUT_DEVICE:
635                     mInput = (BluetoothInputDevice) proxy;
636                     if (mInput != null) {
637                         mHandler.sendEmptyMessage(MSG_NEXT_STEP);
638                     }
639                     break;
640             }
641         }
642     }
643 
644     @Override
onServiceDisconnected(int profile)645     public void onServiceDisconnected(int profile) {
646         // We can ignore these
647     }
648 
sendRetryMessage(int waitTime)649     void sendRetryMessage(int waitTime) {
650         if (!mHandler.hasMessages(MSG_RETRY)) {
651             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY), waitTime);
652         }
653     }
654 }
655