BluetoothPeripheralHandover.java revision 226307d8c2952c42855005d7c0107b42b066bc9a
1a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly/*
2a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * Copyright (C) 2012 The Android Open Source Project
3a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly *
4a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * Licensed under the Apache License, Version 2.0 (the "License");
5a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * you may not use this file except in compliance with the License.
6a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * You may obtain a copy of the License at
7a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly *
8a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly *      http://www.apache.org/licenses/LICENSE-2.0
9a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly *
10a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * Unless required by applicable law or agreed to in writing, software
11a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * distributed under the License is distributed on an "AS IS" BASIS,
12a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * See the License for the specific language governing permissions and
14a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * limitations under the License.
15a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly */
16a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
17a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellypackage com.android.nfc.handover;
18a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
19a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.bluetooth.BluetoothA2dp;
20a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.bluetooth.BluetoothAdapter;
21a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.bluetooth.BluetoothDevice;
22a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.bluetooth.BluetoothHeadset;
23a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.bluetooth.BluetoothProfile;
24a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.content.BroadcastReceiver;
25a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.content.Context;
26a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.content.Intent;
27a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.content.IntentFilter;
28a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.os.Handler;
29a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.os.Looper;
30a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.os.Message;
31a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.util.Log;
32a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.view.KeyEvent;
33a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.widget.Toast;
34a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
35226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport com.android.nfc.handover.HandoverManager.HandoverPowerManager;
36226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen
37a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly/**
38a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * Connects / Disconnects from a Bluetooth headset (or any device that
39a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * might implement BT HSP, HFP or A2DP sink) when touched with NFC.
40a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly *
41a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * This object is created on an NFC interaction, and determines what
42a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * sequence of Bluetooth actions to take, and executes them. It is not
43a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * designed to be re-used after the sequence has completed or timed out.
44a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * Subsequent NFC interactions should use new objects.
45a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly *
46226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen * TODO: prevent auto-connecting to other devices and other incoming a2dp/hsp
47226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen * connects.
48a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * TODO: il8n / UI review
49a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly */
50a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellypublic class BluetoothHeadsetHandover {
51a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final String TAG = HandoverManager.TAG;
52a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final boolean DBG = HandoverManager.DBG;
53a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
54a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final int TIMEOUT_MS = 20000;
55a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
56a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final int STATE_INIT = 0;
57a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final int STATE_TURNING_ON = 1;
58a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final int STATE_BONDING = 2;
59a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final int STATE_CONNECTING = 3;
60a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final int STATE_DISCONNECTING = 4;
61a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final int STATE_COMPLETE = 5;
62a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
63a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final int RESULT_PENDING = 0;
64a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final int RESULT_CONNECTED = 1;
65a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final int RESULT_DISCONNECTED = 2;
66a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
67a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final int ACTION_DISCONNECT = 1;
68a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    static final int ACTION_CONNECT = 2;
69a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
7049913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    static final int MSG_TIMEOUT = 1;
71a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
72a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    final Context mContext;
73a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    final BluetoothDevice mDevice;
74a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    final String mName;
75226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen    final HandoverPowerManager mHandoverPowerManager;
76a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    final BluetoothA2dp mA2dp;
77a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    final BluetoothHeadset mHeadset;
78a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    final Callback mCallback;
79a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
8049913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    // only used on main thread
81a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    int mAction;
82a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    int mState;
83a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    int mHfpResult;  // used only in STATE_CONNECTING and STATE_DISCONNETING
84a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    int mA2dpResult; // used only in STATE_CONNECTING and STATE_DISCONNETING
85a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
86a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    public interface Callback {
87226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen        public void onBluetoothHeadsetHandoverComplete(boolean connected);
88a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    }
89a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
90a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    public BluetoothHeadsetHandover(Context context, BluetoothDevice device, String name,
91226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen            HandoverPowerManager powerManager, BluetoothA2dp a2dp, BluetoothHeadset headset,
92a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            Callback callback) {
9349913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        checkMainThread();  // mHandler must get get constructed on Main Thread for toasts to work
94a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mContext = context;
95a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mDevice = device;
96a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mName = name;
97226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen        mHandoverPowerManager = powerManager;
98a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mA2dp = a2dp;
99a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mHeadset = headset;
100a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mCallback = callback;
101a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mState = STATE_INIT;
102a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    }
103a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
104a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    /**
105a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly     * Main entry point. This method is usually called after construction,
10649913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly     * to begin the BT sequence. Must be called on Main thread.
107a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly     */
10849913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    public void start() {
10949913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        checkMainThread();
11049913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        if (mState != STATE_INIT) return;
11149913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly
112a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        IntentFilter filter = new IntentFilter();
113a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
114a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
115a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
116a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
117a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mContext.registerReceiver(mReceiver, filter);
118a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
119a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        if (mA2dp.getConnectedDevices().contains(mDevice) ||
120a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                mHeadset.getConnectedDevices().contains(mDevice)) {
121a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            Log.i(TAG, "ACTION_DISCONNECT addr=" + mDevice + " name=" + mName);
122a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            mAction = ACTION_DISCONNECT;
123a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        } else {
124a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName);
125a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            mAction = ACTION_CONNECT;
126a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        }
127a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), TIMEOUT_MS);
128a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        nextStep();
129a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    }
130a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
131a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    /**
132a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly     * Called to execute next step in state machine
133a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly     */
13449913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    void nextStep() {
135a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        if (mAction == ACTION_CONNECT) {
136a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            nextStepConnect();
137a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        } else {
138a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            nextStepDisconnect();
139a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        }
140a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    }
141a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
14249913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    void nextStepDisconnect() {
143a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        switch (mState) {
144a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            case STATE_INIT:
145a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                mState = STATE_DISCONNECTING;
146a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                if (mHeadset.getConnectionState(mDevice) != BluetoothProfile.STATE_DISCONNECTED) {
147a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    mHfpResult = RESULT_PENDING;
148a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    mHeadset.disconnect(mDevice);
149a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                } else {
150a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    mHfpResult = RESULT_DISCONNECTED;
151a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                }
152a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                if (mA2dp.getConnectionState(mDevice) != BluetoothProfile.STATE_DISCONNECTED) {
153a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    mA2dpResult = RESULT_PENDING;
154a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    mA2dp.disconnect(mDevice);
155a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                } else {
156a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    mA2dpResult = RESULT_DISCONNECTED;
157a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                }
158a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
159a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    toast("Disconnecting " + mName + "...");
160a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    break;
161a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                }
162a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                // fall-through
163a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            case STATE_DISCONNECTING:
164a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
165a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    // still disconnecting
166a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    break;
167a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                }
168a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                if (mA2dpResult == RESULT_DISCONNECTED && mHfpResult == RESULT_DISCONNECTED) {
169a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    toast("Disconnected " + mName);
170a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                }
171226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                complete(false);
172a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                break;
173a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        }
174a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    }
175a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
17649913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    void nextStepConnect() {
177a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        switch (mState) {
178a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            case STATE_INIT:
179226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                if (!mHandoverPowerManager.isBluetoothEnabled()) {
180226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                    if (mHandoverPowerManager.enableBluetooth()) {
181226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                        // Bluetooth is being enabled
182226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                        mState = STATE_TURNING_ON;
183226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                    } else {
184226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                        toast("Failed to enable Bluetooth");
185226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                        complete(false);
186226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                    }
187a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    break;
188a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                }
189a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                // fall-through
190a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            case STATE_TURNING_ON:
191a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
192a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    startBonding();
193a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    break;
194a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                }
195a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                // fall-through
196a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            case STATE_BONDING:
197a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                // Bluetooth Profile service will correctly serialize
198a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                // HFP then A2DP connect
199a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                mState = STATE_CONNECTING;
200a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                if (mHeadset.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) {
201a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    mHfpResult = RESULT_PENDING;
202a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    mHeadset.connect(mDevice);
203a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                } else {
204a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    mHfpResult = RESULT_CONNECTED;
205a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                }
206a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                if (mA2dp.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) {
207a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    mA2dpResult = RESULT_PENDING;
208a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    mA2dp.connect(mDevice);
209a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                } else {
210a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    mA2dpResult = RESULT_CONNECTED;
211a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                }
212a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
213a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    toast("Connecting " + mName + "...");
214a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    break;
215a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                }
216a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                // fall-through
217a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            case STATE_CONNECTING:
218a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
219a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    // another connection type still pending
220a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    break;
221a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                }
222a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                if (mA2dpResult == RESULT_CONNECTED || mHfpResult == RESULT_CONNECTED) {
223a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    // we'll take either as success
224a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    toast("Connected " + mName);
225a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    if (mA2dpResult == RESULT_CONNECTED) startTheMusic();
226226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                    complete(true);
227a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                } else {
228a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    toast ("Failed to connect " + mName);
229226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                    complete(false);
230a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                }
231a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                break;
232a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        }
233a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    }
234a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
23549913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    void startBonding() {
236a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mState = STATE_BONDING;
237a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        toast("Pairing " + mName + "...");
238a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        if (!mDevice.createBond()) {
239a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            toast("Failed to pair " + mName);
240226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen            complete(false);
241a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        }
242a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    }
243a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
24449913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    void handleIntent(Intent intent) {
245a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        String action = intent.getAction();
246a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action) && mState == STATE_TURNING_ON) {
247a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
248a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            if (state == BluetoothAdapter.STATE_ON) {
249a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                nextStepConnect();
250a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            } else if (state == BluetoothAdapter.STATE_OFF) {
251a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                toast("Failed to enable Bluetooth");
252226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                complete(false);
253a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            }
254a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            return;
255a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        }
256a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
257a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        // Everything else requires the device to match...
258a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
259a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        if (!mDevice.equals(device)) return;
260a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
261a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action) && mState == STATE_BONDING) {
262a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            int bond = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
263a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    BluetoothAdapter.ERROR);
264a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            if (bond == BluetoothDevice.BOND_BONDED) {
265a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                nextStepConnect();
266a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            } else if (bond == BluetoothDevice.BOND_NONE) {
267a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                toast("Failed to pair " + mName);
268226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                complete(false);
269a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            }
270a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
271a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) {
272a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
273a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            if (state == BluetoothProfile.STATE_CONNECTED) {
274a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                mHfpResult = RESULT_CONNECTED;
275a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                nextStep();
276a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
277a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                mHfpResult = RESULT_DISCONNECTED;
278a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                nextStep();
279a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            }
280a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
281a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) {
282a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
283a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            if (state == BluetoothProfile.STATE_CONNECTED) {
284a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                mA2dpResult = RESULT_CONNECTED;
285a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                nextStep();
286a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
287a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                mA2dpResult = RESULT_DISCONNECTED;
288a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                nextStep();
289a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            }
290a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        }
291a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    }
292a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
293226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen    void complete(boolean connected) {
294a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        if (DBG) Log.d(TAG, "complete()");
295a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mState = STATE_COMPLETE;
296a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mContext.unregisterReceiver(mReceiver);
297a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        mHandler.removeMessages(MSG_TIMEOUT);
298226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen        mCallback.onBluetoothHeadsetHandoverComplete(connected);
299a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    }
300a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
301a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    void toast(CharSequence text) {
30249913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        Toast.makeText(mContext,  text, Toast.LENGTH_SHORT).show();
30349913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    }
30449913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly
30549913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    void startTheMusic() {
30649913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
30749913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN,
30849913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly                KeyEvent.KEYCODE_MEDIA_PLAY));
30949913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        mContext.sendOrderedBroadcast(intent, null);
31049913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP,
31149913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly                KeyEvent.KEYCODE_MEDIA_PLAY));
31249913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        mContext.sendOrderedBroadcast(intent, null);
313a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    }
314a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
315a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    final Handler mHandler = new Handler() {
316a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        @Override
317a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        public void handleMessage(Message msg) {
318a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            switch (msg.what) {
319a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                case MSG_TIMEOUT:
32049913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly                    if (mState == STATE_COMPLETE) return;
32149913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly                    Log.i(TAG, "Timeout completing BT handover");
322226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen                    complete(false);
323a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly                    break;
324a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly            }
325a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly        }
326a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    };
327a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly
32849913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    final BroadcastReceiver mReceiver = new BroadcastReceiver() {
32949913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        @Override
33049913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        public void onReceive(Context context, Intent intent) {
33149913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly            handleIntent(intent);
33249913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        }
33349913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    };
33449913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly
33549913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly    static void checkMainThread() {
33649913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        if (Looper.myLooper() != Looper.getMainLooper()) {
33749913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly            throw new IllegalThreadStateException("must be called on main thread");
33849913d6a0ed709ff6edde40bb040605997b561f3Nick Pelly        }
339a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly    }
340a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly}
341