• 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.BluetoothHidHost;
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     static final String ACTION_TIMEOUT_CONNECT = "com.android.nfc.handover.action.TIMEOUT_CONNECT";
62 
63     static final int TIMEOUT_MS = 20000;
64     static final int RETRY_PAIRING_WAIT_TIME_MS = 2000;
65     static final int RETRY_CONNECT_WAIT_TIME_MS = 5000;
66 
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;
86     static final int MSG_RETRY = 3;
87 
88     static final int MAX_RETRY_COUNT = 3;
89 
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;
106     int mRetryCount;
107     OobData mOobData;
108     boolean mIsHeadsetAvailable;
109     boolean mIsA2dpAvailable;
110 
111     // protected by mLock
112     BluetoothA2dp mA2dp;
113     BluetoothHeadset mHeadset;
114     BluetoothHidHost mInput;
115 
116     public interface Callback {
onBluetoothPeripheralHandoverComplete(boolean connected)117         public void onBluetoothPeripheralHandoverComplete(boolean connected);
118     }
119 
BluetoothPeripheralHandover(Context context, BluetoothDevice device, String name, int transport, OobData oobData, ParcelUuid[] uuids, BluetoothClass btClass, Callback callback)120     public BluetoothPeripheralHandover(Context context, BluetoothDevice device, String name,
121             int transport, OobData oobData, ParcelUuid[] uuids, BluetoothClass btClass,
122             Callback callback) {
123         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;
128         mOobData = oobData;
129         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 
136         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 
146         mState = STATE_INIT;
147     }
148 
hasStarted()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      */
start()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);
170         filter.addAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
171         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;
179         mRetryCount = 0;
180 
181         nextStep();
182 
183         return true;
184     }
185 
186     /**
187      * Called to execute next step in state machine
188      */
nextStep()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      */
nextStepInit()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 {
231                             // 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                             }
243                             Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName);
244                             mAction = ACTION_CONNECT;
245                         }
246                     }
247                 }
248                 nextStep();
249         }
250 
251     }
252 
nextStepDisconnect()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 
getToastString(int resid)314     private String getToastString(int resid) {
315         return mContext.getString(resid, mName != null ? mName : R.string.device);
316     }
317 
getProfileProxys()318     boolean getProfileProxys() {
319 
320         if (mTransport == BluetoothDevice.TRANSPORT_LE) {
321             if (!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.HID_HOST))
322                 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 
nextStepConnect()334     void nextStepConnect() {
335         switch (mState) {
336             case STATE_INIT_COMPLETE:
337 
338                 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) {
364                     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 {
374                         if (mHeadset.getConnectionState(mDevice) !=
375                                 BluetoothProfile.STATE_CONNECTED) {
376                             if (mIsHeadsetAvailable) {
377                                 mHfpResult = RESULT_PENDING;
378                                 mHeadset.connect(mDevice);
379                             } else {
380                                 mHfpResult = RESULT_DISCONNECTED;
381                             }
382                         } else {
383                             mHfpResult = RESULT_CONNECTED;
384                         }
385                         if (mA2dp.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) {
386                             if (mIsA2dpAvailable) {
387                                 mA2dpResult = RESULT_PENDING;
388                                 mA2dp.connect(mDevice);
389                             } else {
390                                 mA2dpResult = RESULT_DISCONNECTED;
391                             }
392                         } else {
393                             mA2dpResult = RESULT_CONNECTED;
394                         }
395                         if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
396                             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                             }
403                         }
404                     }
405                 }
406                 // fall-through
407             case STATE_CONNECTING:
408                 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 {
420                     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 
startBonding()439     void startBonding() {
440         mState = STATE_BONDING;
441         if (mRetryCount == 0) {
442             toast(getToastString(R.string.pairing_peripheral));
443         }
444         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);
452         }
453     }
454 
handleIntent(Intent intent)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)) {
462             mHandler.removeMessages(MSG_TIMEOUT);
463             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), TIMEOUT_MS);
464             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) {
472                 mRetryCount = 0;
473                 nextStepConnect();
474             } else if (bond == BluetoothDevice.BOND_NONE) {
475                 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                 }
481             }
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) {
489                 if (mAction == ACTION_CONNECT && mRetryCount < MAX_RETRY_COUNT) {
490                     sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS);
491                 } else {
492                     mHfpResult = RESULT_DISCONNECTED;
493                     nextStep();
494                 }
495             }
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) {
503                 if (mAction == ACTION_CONNECT && mRetryCount < MAX_RETRY_COUNT) {
504                     sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS);
505                 } else {
506                     mA2dpResult = RESULT_DISCONNECTED;
507                     nextStep();
508                 }
509             }
510         } else if (BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
511                 (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 
complete(boolean connected)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);
528         mHandler.removeMessages(MSG_RETRY);
529         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) {
538                 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HID_HOST, mInput);
539             }
540 
541             mA2dp = null;
542             mHeadset = null;
543             mInput = null;
544         }
545         mCallback.onBluetoothPeripheralHandoverComplete(connected);
546     }
547 
toast(CharSequence text)548     void toast(CharSequence text) {
549         Toast.makeText(mContext,  text, Toast.LENGTH_SHORT).show();
550     }
551 
startTheMusic()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 
requestPairConfirmation()564     void requestPairConfirmation() {
565         Intent dialogIntent = new Intent(mContext, ConfirmConnectActivity.class);
566         dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
567         dialogIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
568         dialogIntent.putExtra(BluetoothDevice.EXTRA_NAME, mName);
569 
570         mContext.startActivity(dialogIntent);
571     }
572 
hasA2dpCapability(ParcelUuid[] uuids, BluetoothClass btClass)573     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 
hasHeadsetCapability(ParcelUuid[] uuids, BluetoothClass btClass)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 
601     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");
608                     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                     }
626                     complete(false);
627                     break;
628                 case MSG_NEXT_STEP:
629                     nextStep();
630                     break;
631                 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;
641             }
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 
checkMainThread()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
onServiceConnected(int profile, BluetoothProfile proxy)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;
674                 case BluetoothProfile.HID_HOST:
675                     mInput = (BluetoothHidHost) proxy;
676                     if (mInput != null) {
677                         mHandler.sendEmptyMessage(MSG_NEXT_STEP);
678                     }
679                     break;
680             }
681         }
682     }
683 
684     @Override
onServiceDisconnected(int profile)685     public void onServiceDisconnected(int profile) {
686         // We can ignore these
687     }
688 
sendRetryMessage(int waitTime)689     void sendRetryMessage(int waitTime) {
690         if (!mHandler.hasMessages(MSG_RETRY)) {
691             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY), waitTime);
692         }
693     }
694 }
695