1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.bluetooth;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.os.Message;
24import android.bluetooth.BluetoothAdapter;
25import android.os.PowerManager;
26import android.server.BluetoothA2dpService;
27import android.server.BluetoothService;
28import android.util.Log;
29import android.util.Pair;
30
31import com.android.internal.util.State;
32import com.android.internal.util.StateMachine;
33
34import java.util.Set;
35
36/**
37 * This class is the Profile connection state machine associated with a remote
38 * device. When the device bonds an instance of this class is created.
39 * This tracks incoming and outgoing connections of all the profiles. Incoming
40 * connections are preferred over outgoing connections and HFP preferred over
41 * A2DP. When the device is unbonded, the instance is removed.
42 *
43 * States:
44 * {@link BondedDevice}: This state represents a bonded device. When in this
45 * state none of the profiles are in transition states.
46 *
47 * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition
48 * state because of a outgoing Connect or Disconnect.
49 *
50 * {@link IncomingHandsfree}: Handsfree profile connection is in a transition
51 * state because of a incoming Connect or Disconnect.
52 *
53 * {@link IncomingA2dp}: A2dp profile connection is in a transition
54 * state because of a incoming Connect or Disconnect.
55 *
56 * {@link OutgoingA2dp}: A2dp profile connection is in a transition
57 * state because of a outgoing Connect or Disconnect.
58 *
59 * Todo(): Write tests for this class, when the Android Mock support is completed.
60 * @hide
61 */
62public final class BluetoothDeviceProfileState extends StateMachine {
63    private static final String TAG = "BluetoothDeviceProfileState";
64    private static final boolean DBG = false;
65
66    // TODO(): Restructure the state machine to make it scalable with regard to profiles.
67    public static final int CONNECT_HFP_OUTGOING = 1;
68    public static final int CONNECT_HFP_INCOMING = 2;
69    public static final int CONNECT_A2DP_OUTGOING = 3;
70    public static final int CONNECT_A2DP_INCOMING = 4;
71    public static final int CONNECT_HID_OUTGOING = 5;
72    public static final int CONNECT_HID_INCOMING = 6;
73
74    public static final int DISCONNECT_HFP_OUTGOING = 50;
75    private static final int DISCONNECT_HFP_INCOMING = 51;
76    public static final int DISCONNECT_A2DP_OUTGOING = 52;
77    public static final int DISCONNECT_A2DP_INCOMING = 53;
78    public static final int DISCONNECT_HID_OUTGOING = 54;
79    public static final int DISCONNECT_HID_INCOMING = 55;
80    public static final int DISCONNECT_PBAP_OUTGOING = 56;
81
82    public static final int UNPAIR = 100;
83    public static final int AUTO_CONNECT_PROFILES = 101;
84    public static final int TRANSITION_TO_STABLE = 102;
85    public static final int CONNECT_OTHER_PROFILES = 103;
86    private static final int CONNECTION_ACCESS_REQUEST_REPLY = 104;
87    private static final int CONNECTION_ACCESS_REQUEST_EXPIRY = 105;
88
89    public static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs
90    private static final int CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT = 7000; // 7 secs
91    private static final int CONNECTION_ACCESS_UNDEFINED = -1;
92    private static final long INIT_INCOMING_REJECT_TIMER = 1000; // 1 sec
93    private static final long MAX_INCOMING_REJECT_TIMER = 3600 * 1000 * 4; // 4 hours
94
95    private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
96    private static final String ACCESS_AUTHORITY_CLASS =
97        "com.android.settings.bluetooth.BluetoothPermissionRequest";
98
99    private BondedDevice mBondedDevice = new BondedDevice();
100    private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree();
101    private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
102    private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
103    private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
104    private OutgoingHid mOutgoingHid = new OutgoingHid();
105    private IncomingHid mIncomingHid = new IncomingHid();
106
107    private Context mContext;
108    private BluetoothService mService;
109    private BluetoothA2dpService mA2dpService;
110    private BluetoothHeadset  mHeadsetService;
111    private BluetoothPbap     mPbapService;
112    private PbapServiceListener mPbap;
113    private BluetoothAdapter mAdapter;
114    private boolean mPbapServiceConnected;
115    private boolean mAutoConnectionPending;
116    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
117
118    private BluetoothDevice mDevice;
119    private int mHeadsetState = BluetoothProfile.STATE_DISCONNECTED;
120    private int mA2dpState = BluetoothProfile.STATE_DISCONNECTED;
121    private long mIncomingRejectTimer;
122    private boolean mConnectionAccessReplyReceived = false;
123    private Pair<Integer, String> mIncomingConnections;
124    private PowerManager.WakeLock mWakeLock;
125    private PowerManager mPowerManager;
126    private boolean mPairingRequestRcvd = false;
127
128    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
129        @Override
130        public void onReceive(Context context, Intent intent) {
131            String action = intent.getAction();
132            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
133            if (device == null || !device.equals(mDevice)) return;
134
135            if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
136                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
137                int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
138                // We trust this device now
139                if (newState == BluetoothHeadset.STATE_CONNECTED) {
140                    setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
141                }
142                mA2dpState = newState;
143                if (oldState == BluetoothA2dp.STATE_CONNECTED &&
144                    newState == BluetoothA2dp.STATE_DISCONNECTED) {
145                    sendMessage(DISCONNECT_A2DP_INCOMING);
146                }
147                if (newState == BluetoothProfile.STATE_CONNECTED ||
148                    newState == BluetoothProfile.STATE_DISCONNECTED) {
149                    sendMessage(TRANSITION_TO_STABLE);
150                }
151            } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
152                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
153                int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
154                // We trust this device now
155                if (newState == BluetoothHeadset.STATE_CONNECTED) {
156                    setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
157                }
158                mHeadsetState = newState;
159                if (oldState == BluetoothHeadset.STATE_CONNECTED &&
160                    newState == BluetoothHeadset.STATE_DISCONNECTED) {
161                    sendMessage(DISCONNECT_HFP_INCOMING);
162                }
163                if (newState == BluetoothProfile.STATE_CONNECTED ||
164                    newState == BluetoothProfile.STATE_DISCONNECTED) {
165                    sendMessage(TRANSITION_TO_STABLE);
166                }
167            } else if (action.equals(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED)) {
168                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
169                int oldState =
170                    intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
171                // We trust this device now
172                if (newState == BluetoothHeadset.STATE_CONNECTED) {
173                    setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
174                }
175                if (oldState == BluetoothProfile.STATE_CONNECTED &&
176                    newState == BluetoothProfile.STATE_DISCONNECTED) {
177                    sendMessage(DISCONNECT_HID_INCOMING);
178                }
179                if (newState == BluetoothProfile.STATE_CONNECTED ||
180                    newState == BluetoothProfile.STATE_DISCONNECTED) {
181                    sendMessage(TRANSITION_TO_STABLE);
182                }
183            } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
184                // This is technically not needed, but we can get stuck sometimes.
185                // For example, if incoming A2DP fails, we are not informed by Bluez
186                sendMessage(TRANSITION_TO_STABLE);
187            } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
188                mWakeLock.release();
189                int val = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
190                                             BluetoothDevice.CONNECTION_ACCESS_NO);
191                Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_REPLY);
192                msg.arg1 = val;
193                sendMessage(msg);
194            } else if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
195                mPairingRequestRcvd = true;
196            } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
197                int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
198                        BluetoothDevice.ERROR);
199                if (state == BluetoothDevice.BOND_BONDED && mPairingRequestRcvd) {
200                    setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
201                    mPairingRequestRcvd = false;
202                } else if (state == BluetoothDevice.BOND_NONE) {
203                    mPairingRequestRcvd = false;
204                }
205            }
206        }
207    };
208
209    private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) {
210        // This works only because these broadcast intents are "sticky"
211        Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
212        if (i != null) {
213            int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
214            if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
215                BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
216                if (device != null && autoConnectDevice.equals(device)) {
217                    return true;
218                }
219            }
220        }
221        return false;
222    }
223
224    public BluetoothDeviceProfileState(Context context, String address,
225          BluetoothService service, BluetoothA2dpService a2dpService, boolean setTrust) {
226        super(address);
227        mContext = context;
228        mDevice = new BluetoothDevice(address);
229        mService = service;
230        mA2dpService = a2dpService;
231
232        addState(mBondedDevice);
233        addState(mOutgoingHandsfree);
234        addState(mIncomingHandsfree);
235        addState(mIncomingA2dp);
236        addState(mOutgoingA2dp);
237        addState(mOutgoingHid);
238        addState(mIncomingHid);
239        setInitialState(mBondedDevice);
240
241        IntentFilter filter = new IntentFilter();
242        // Fine-grained state broadcasts
243        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
244        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
245        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
246        filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
247        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
248        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
249        filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
250        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
251
252        mContext.registerReceiver(mBroadcastReceiver, filter);
253
254        mAdapter = BluetoothAdapter.getDefaultAdapter();
255        mAdapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
256                                BluetoothProfile.HEADSET);
257        // TODO(): Convert PBAP to the new Profile APIs.
258        mPbap = new PbapServiceListener();
259
260        mIncomingConnections = mService.getIncomingState(address);
261        mIncomingRejectTimer = readTimerValue();
262        mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
263        mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
264                                              PowerManager.ACQUIRE_CAUSES_WAKEUP |
265                                              PowerManager.ON_AFTER_RELEASE, TAG);
266        mWakeLock.setReferenceCounted(false);
267
268        if (setTrust) {
269            setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
270        }
271    }
272
273    private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
274        new BluetoothProfile.ServiceListener() {
275        public void onServiceConnected(int profile, BluetoothProfile proxy) {
276            synchronized(BluetoothDeviceProfileState.this) {
277                mHeadsetService = (BluetoothHeadset) proxy;
278                if (mAutoConnectionPending) {
279                    sendMessage(AUTO_CONNECT_PROFILES);
280                    mAutoConnectionPending = false;
281                }
282            }
283        }
284        public void onServiceDisconnected(int profile) {
285            synchronized(BluetoothDeviceProfileState.this) {
286                mHeadsetService = null;
287            }
288        }
289    };
290
291    private class PbapServiceListener implements BluetoothPbap.ServiceListener {
292        public PbapServiceListener() {
293            mPbapService = new BluetoothPbap(mContext, this);
294        }
295        public void onServiceConnected() {
296            synchronized(BluetoothDeviceProfileState.this) {
297                mPbapServiceConnected = true;
298            }
299        }
300        public void onServiceDisconnected() {
301            synchronized(BluetoothDeviceProfileState.this) {
302                mPbapServiceConnected = false;
303            }
304        }
305    }
306
307    private class BondedDevice extends State {
308        @Override
309        public void enter() {
310            Log.i(TAG, "Entering ACL Connected state with: " + getCurrentMessage().what);
311            Message m = new Message();
312            m.copyFrom(getCurrentMessage());
313            sendMessageAtFrontOfQueue(m);
314        }
315        @Override
316        public boolean processMessage(Message message) {
317            log("ACL Connected State -> Processing Message: " + message.what);
318            switch(message.what) {
319                case CONNECT_HFP_OUTGOING:
320                case DISCONNECT_HFP_OUTGOING:
321                    transitionTo(mOutgoingHandsfree);
322                    break;
323                case CONNECT_HFP_INCOMING:
324                    transitionTo(mIncomingHandsfree);
325                    break;
326                case DISCONNECT_HFP_INCOMING:
327                    transitionTo(mIncomingHandsfree);
328                    break;
329                case CONNECT_A2DP_OUTGOING:
330                case DISCONNECT_A2DP_OUTGOING:
331                    transitionTo(mOutgoingA2dp);
332                    break;
333                case CONNECT_A2DP_INCOMING:
334                case DISCONNECT_A2DP_INCOMING:
335                    transitionTo(mIncomingA2dp);
336                    break;
337                case CONNECT_HID_OUTGOING:
338                case DISCONNECT_HID_OUTGOING:
339                    transitionTo(mOutgoingHid);
340                    break;
341                case CONNECT_HID_INCOMING:
342                case DISCONNECT_HID_INCOMING:
343                    transitionTo(mIncomingHid);
344                    break;
345                case DISCONNECT_PBAP_OUTGOING:
346                    processCommand(DISCONNECT_PBAP_OUTGOING);
347                    break;
348                case UNPAIR:
349                    if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
350                        sendMessage(DISCONNECT_HFP_OUTGOING);
351                        deferMessage(message);
352                        break;
353                    } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) {
354                        sendMessage(DISCONNECT_A2DP_OUTGOING);
355                        deferMessage(message);
356                        break;
357                    } else if (mService.getInputDeviceConnectionState(mDevice) !=
358                            BluetoothInputDevice.STATE_DISCONNECTED) {
359                        sendMessage(DISCONNECT_HID_OUTGOING);
360                        deferMessage(message);
361                        break;
362                    }
363                    processCommand(UNPAIR);
364                    break;
365                case AUTO_CONNECT_PROFILES:
366                    if (isPhoneDocked(mDevice)) {
367                        // Don't auto connect to docks.
368                        break;
369                    } else {
370                        if (mHeadsetService == null) {
371                              mAutoConnectionPending = true;
372                        } else if (mHeadsetService.getPriority(mDevice) ==
373                              BluetoothHeadset.PRIORITY_AUTO_CONNECT &&
374                              mHeadsetService.getDevicesMatchingConnectionStates(
375                                  new int[] {BluetoothProfile.STATE_CONNECTED,
376                                             BluetoothProfile.STATE_CONNECTING,
377                                             BluetoothProfile.STATE_DISCONNECTING}).size() == 0) {
378                            mHeadsetService.connect(mDevice);
379                        }
380                        if (mA2dpService != null &&
381                              mA2dpService.getPriority(mDevice) ==
382                              BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
383                              mA2dpService.getDevicesMatchingConnectionStates(
384                                  new int[] {BluetoothA2dp.STATE_CONNECTED,
385                                             BluetoothProfile.STATE_CONNECTING,
386                                             BluetoothProfile.STATE_DISCONNECTING}).size() == 0) {
387                            mA2dpService.connect(mDevice);
388                        }
389                        if (mService.getInputDevicePriority(mDevice) ==
390                              BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
391                            mService.connectInputDevice(mDevice);
392                        }
393                    }
394                    break;
395                case CONNECT_OTHER_PROFILES:
396                    if (isPhoneDocked(mDevice)) {
397                       break;
398                    }
399                    if (message.arg1 == CONNECT_A2DP_OUTGOING) {
400                        if (mA2dpService != null &&
401                            mA2dpService.getConnectedDevices().size() == 0) {
402                            Log.i(TAG, "A2dp:Connect Other Profiles");
403                            mA2dpService.connect(mDevice);
404                        }
405                    } else if (message.arg1 == CONNECT_HFP_OUTGOING) {
406                        if (mHeadsetService == null) {
407                            deferMessage(message);
408                        } else {
409                            if (mHeadsetService.getConnectedDevices().size() == 0) {
410                                Log.i(TAG, "Headset:Connect Other Profiles");
411                                mHeadsetService.connect(mDevice);
412                            }
413                        }
414                    }
415                    break;
416                case TRANSITION_TO_STABLE:
417                    // ignore.
418                    break;
419                case SM_QUIT_CMD:
420                    mContext.unregisterReceiver(mBroadcastReceiver);
421                    mBroadcastReceiver = null;
422                    mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadsetService);
423                    mBluetoothProfileServiceListener = null;
424                    mOutgoingHandsfree = null;
425                    mPbap = null;
426                    mPbapService.close();
427                    mPbapService = null;
428                    mIncomingHid = null;
429                    mOutgoingHid = null;
430                    mIncomingHandsfree = null;
431                    mOutgoingHandsfree = null;
432                    mIncomingA2dp = null;
433                    mOutgoingA2dp = null;
434                    mBondedDevice = null;
435                    // There is a problem in the State Machine code
436                    // where things are not cleaned up properly, when quit message
437                    // is handled so return NOT_HANDLED as a workaround.
438                    return NOT_HANDLED;
439                default:
440                    return NOT_HANDLED;
441            }
442            return HANDLED;
443        }
444    }
445
446    private class OutgoingHandsfree extends State {
447        private boolean mStatus = false;
448        private int mCommand;
449
450        @Override
451        public void enter() {
452            Log.i(TAG, "Entering OutgoingHandsfree state with: " + getCurrentMessage().what);
453            mCommand = getCurrentMessage().what;
454            if (mCommand != CONNECT_HFP_OUTGOING &&
455                mCommand != DISCONNECT_HFP_OUTGOING) {
456                Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand);
457            }
458            mStatus = processCommand(mCommand);
459            if (!mStatus) {
460                sendMessage(TRANSITION_TO_STABLE);
461                mService.sendProfileStateMessage(BluetoothProfileState.HFP,
462                                                 BluetoothProfileState.TRANSITION_TO_STABLE);
463            }
464        }
465
466        @Override
467        public boolean processMessage(Message message) {
468            log("OutgoingHandsfree State -> Processing Message: " + message.what);
469            Message deferMsg = new Message();
470            int command = message.what;
471            switch(command) {
472                case CONNECT_HFP_OUTGOING:
473                    if (command != mCommand) {
474                        // Disconnect followed by a connect - defer
475                        deferMessage(message);
476                    }
477                    break;
478                case CONNECT_HFP_INCOMING:
479                    if (mCommand == CONNECT_HFP_OUTGOING) {
480                        // Cancel outgoing connect, accept incoming
481                        cancelCommand(CONNECT_HFP_OUTGOING);
482                        transitionTo(mIncomingHandsfree);
483                    } else {
484                        // We have done the disconnect but we are not
485                        // sure which state we are in at this point.
486                        deferMessage(message);
487                    }
488                    break;
489                case CONNECT_A2DP_INCOMING:
490                    // accept incoming A2DP, retry HFP_OUTGOING
491                    transitionTo(mIncomingA2dp);
492
493                    if (mStatus) {
494                        deferMsg.what = mCommand;
495                        deferMessage(deferMsg);
496                    }
497                    break;
498                case CONNECT_A2DP_OUTGOING:
499                    deferMessage(message);
500                    break;
501                case DISCONNECT_HFP_OUTGOING:
502                    if (mCommand == CONNECT_HFP_OUTGOING) {
503                        // Cancel outgoing connect
504                        cancelCommand(CONNECT_HFP_OUTGOING);
505                        processCommand(DISCONNECT_HFP_OUTGOING);
506                    }
507                    // else ignore
508                    break;
509                case DISCONNECT_HFP_INCOMING:
510                    // When this happens the socket would be closed and the headset
511                    // state moved to DISCONNECTED, cancel the outgoing thread.
512                    // if it still is in CONNECTING state
513                    cancelCommand(CONNECT_HFP_OUTGOING);
514                    break;
515                case DISCONNECT_A2DP_OUTGOING:
516                    deferMessage(message);
517                    break;
518                case DISCONNECT_A2DP_INCOMING:
519                    // Bluez will handle the disconnect. If because of this the outgoing
520                    // handsfree connection has failed, then retry.
521                    if (mStatus) {
522                       deferMsg.what = mCommand;
523                       deferMessage(deferMsg);
524                    }
525                    break;
526                case CONNECT_HID_OUTGOING:
527                case DISCONNECT_HID_OUTGOING:
528                    deferMessage(message);
529                    break;
530                case CONNECT_HID_INCOMING:
531                    transitionTo(mIncomingHid);
532                    if (mStatus) {
533                        deferMsg.what = mCommand;
534                        deferMessage(deferMsg);
535                    }
536                    break;
537                case DISCONNECT_HID_INCOMING:
538                    if (mStatus) {
539                        deferMsg.what = mCommand;
540                        deferMessage(deferMsg);
541                    }
542                    break; // ignore
543                case DISCONNECT_PBAP_OUTGOING:
544                case UNPAIR:
545                case AUTO_CONNECT_PROFILES:
546                case CONNECT_OTHER_PROFILES:
547                    deferMessage(message);
548                    break;
549                case TRANSITION_TO_STABLE:
550                    transitionTo(mBondedDevice);
551                    break;
552                default:
553                    return NOT_HANDLED;
554            }
555            return HANDLED;
556        }
557    }
558
559    private class IncomingHandsfree extends State {
560        private boolean mStatus = false;
561        private int mCommand;
562
563        @Override
564        public void enter() {
565            Log.i(TAG, "Entering IncomingHandsfree state with: " + getCurrentMessage().what);
566            mCommand = getCurrentMessage().what;
567            if (mCommand != CONNECT_HFP_INCOMING &&
568                mCommand != DISCONNECT_HFP_INCOMING) {
569                Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand);
570            }
571            mStatus = processCommand(mCommand);
572            if (!mStatus) {
573                sendMessage(TRANSITION_TO_STABLE);
574                mService.sendProfileStateMessage(BluetoothProfileState.HFP,
575                                                 BluetoothProfileState.TRANSITION_TO_STABLE);
576            }
577        }
578
579        @Override
580        public boolean processMessage(Message message) {
581            log("IncomingHandsfree State -> Processing Message: " + message.what);
582            switch(message.what) {
583                case CONNECT_HFP_OUTGOING:
584                    deferMessage(message);
585                    break;
586                case CONNECT_HFP_INCOMING:
587                    // Ignore
588                    Log.e(TAG, "Error: Incoming connection with a pending incoming connection");
589                    break;
590                case CONNECTION_ACCESS_REQUEST_REPLY:
591                    int val = message.arg1;
592                    mConnectionAccessReplyReceived = true;
593                    boolean value = false;
594                    if (val == BluetoothDevice.CONNECTION_ACCESS_YES) {
595                        value = true;
596                    }
597                    setTrust(val);
598
599                    handleIncomingConnection(CONNECT_HFP_INCOMING, value);
600                    break;
601                case CONNECTION_ACCESS_REQUEST_EXPIRY:
602                    if (!mConnectionAccessReplyReceived) {
603                        handleIncomingConnection(CONNECT_HFP_INCOMING, false);
604                        sendConnectionAccessRemovalIntent();
605                        sendMessage(TRANSITION_TO_STABLE);
606                    }
607                    break;
608                case CONNECT_A2DP_INCOMING:
609                    // Serialize the commands.
610                    deferMessage(message);
611                    break;
612                case CONNECT_A2DP_OUTGOING:
613                    deferMessage(message);
614                    break;
615                case DISCONNECT_HFP_OUTGOING:
616                    // We don't know at what state we are in the incoming HFP connection state.
617                    // We can be changing from DISCONNECTED to CONNECTING, or
618                    // from CONNECTING to CONNECTED, so serializing this command is
619                    // the safest option.
620                    deferMessage(message);
621                    break;
622                case DISCONNECT_HFP_INCOMING:
623                    // Nothing to do here, we will already be DISCONNECTED
624                    // by this point.
625                    break;
626                case DISCONNECT_A2DP_OUTGOING:
627                    deferMessage(message);
628                    break;
629                case DISCONNECT_A2DP_INCOMING:
630                    // Bluez handles incoming A2DP disconnect.
631                    // If this causes incoming HFP to fail, it is more of a headset problem
632                    // since both connections are incoming ones.
633                    break;
634                case CONNECT_HID_OUTGOING:
635                case DISCONNECT_HID_OUTGOING:
636                    deferMessage(message);
637                    break;
638                case CONNECT_HID_INCOMING:
639                case DISCONNECT_HID_INCOMING:
640                     break; // ignore
641                case DISCONNECT_PBAP_OUTGOING:
642                case UNPAIR:
643                case AUTO_CONNECT_PROFILES:
644                case CONNECT_OTHER_PROFILES:
645                    deferMessage(message);
646                    break;
647                case TRANSITION_TO_STABLE:
648                    transitionTo(mBondedDevice);
649                    break;
650                default:
651                    return NOT_HANDLED;
652            }
653            return HANDLED;
654        }
655    }
656
657    private class OutgoingA2dp extends State {
658        private boolean mStatus = false;
659        private int mCommand;
660
661        @Override
662        public void enter() {
663            Log.i(TAG, "Entering OutgoingA2dp state with: " + getCurrentMessage().what);
664            mCommand = getCurrentMessage().what;
665            if (mCommand != CONNECT_A2DP_OUTGOING &&
666                mCommand != DISCONNECT_A2DP_OUTGOING) {
667                Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand);
668            }
669            mStatus = processCommand(mCommand);
670            if (!mStatus) {
671                sendMessage(TRANSITION_TO_STABLE);
672                mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
673                                                 BluetoothProfileState.TRANSITION_TO_STABLE);
674            }
675        }
676
677        @Override
678        public boolean processMessage(Message message) {
679            log("OutgoingA2dp State->Processing Message: " + message.what);
680            Message deferMsg = new Message();
681            switch(message.what) {
682                case CONNECT_HFP_OUTGOING:
683                    processCommand(CONNECT_HFP_OUTGOING);
684
685                    // Don't cancel A2DP outgoing as there is no guarantee it
686                    // will get canceled.
687                    // It might already be connected but we might not have got the
688                    // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here.
689                    // The worst case, the connection will fail, retry.
690                    // The same applies to Disconnecting an A2DP connection.
691                    if (mStatus) {
692                        deferMsg.what = mCommand;
693                        deferMessage(deferMsg);
694                    }
695                    break;
696                case CONNECT_HFP_INCOMING:
697                    processCommand(CONNECT_HFP_INCOMING);
698
699                    // Don't cancel A2DP outgoing as there is no guarantee
700                    // it will get canceled.
701                    // The worst case, the connection will fail, retry.
702                    if (mStatus) {
703                        deferMsg.what = mCommand;
704                        deferMessage(deferMsg);
705                    }
706                    break;
707                case CONNECT_A2DP_INCOMING:
708                    // Bluez will take care of conflicts between incoming and outgoing
709                    // connections.
710                    transitionTo(mIncomingA2dp);
711                    break;
712                case CONNECT_A2DP_OUTGOING:
713                    // Ignore
714                    break;
715                case DISCONNECT_HFP_OUTGOING:
716                    deferMessage(message);
717                    break;
718                case DISCONNECT_HFP_INCOMING:
719                    // At this point, we are already disconnected
720                    // with HFP. Sometimes A2DP connection can
721                    // fail due to the disconnection of HFP. So add a retry
722                    // for the A2DP.
723                    if (mStatus) {
724                        deferMsg.what = mCommand;
725                        deferMessage(deferMsg);
726                    }
727                    break;
728                case DISCONNECT_A2DP_OUTGOING:
729                    deferMessage(message);
730                    break;
731                case DISCONNECT_A2DP_INCOMING:
732                    // Ignore, will be handled by Bluez
733                    break;
734                case CONNECT_HID_OUTGOING:
735                case DISCONNECT_HID_OUTGOING:
736                    deferMessage(message);
737                    break;
738                case CONNECT_HID_INCOMING:
739                    transitionTo(mIncomingHid);
740                    if (mStatus) {
741                        deferMsg.what = mCommand;
742                        deferMessage(deferMsg);
743                    }
744                    break;
745                case DISCONNECT_HID_INCOMING:
746                    if (mStatus) {
747                        deferMsg.what = mCommand;
748                        deferMessage(deferMsg);
749                    }
750                    break; // ignore
751                case DISCONNECT_PBAP_OUTGOING:
752                case UNPAIR:
753                case AUTO_CONNECT_PROFILES:
754                case CONNECT_OTHER_PROFILES:
755                    deferMessage(message);
756                    break;
757                case TRANSITION_TO_STABLE:
758                    transitionTo(mBondedDevice);
759                    break;
760                default:
761                    return NOT_HANDLED;
762            }
763            return HANDLED;
764        }
765    }
766
767    private class IncomingA2dp extends State {
768        private boolean mStatus = false;
769        private int mCommand;
770
771        @Override
772        public void enter() {
773            Log.i(TAG, "Entering IncomingA2dp state with: " + getCurrentMessage().what);
774            mCommand = getCurrentMessage().what;
775            if (mCommand != CONNECT_A2DP_INCOMING &&
776                mCommand != DISCONNECT_A2DP_INCOMING) {
777                Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand);
778            }
779            mStatus = processCommand(mCommand);
780            if (!mStatus) {
781                sendMessage(TRANSITION_TO_STABLE);
782                mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
783                                                 BluetoothProfileState.TRANSITION_TO_STABLE);
784            }
785        }
786
787        @Override
788        public boolean processMessage(Message message) {
789            log("IncomingA2dp State->Processing Message: " + message.what);
790            switch(message.what) {
791                case CONNECT_HFP_OUTGOING:
792                    deferMessage(message);
793                    break;
794                case CONNECT_HFP_INCOMING:
795                    // Shouldn't happen, but serialize the commands.
796                    deferMessage(message);
797                    break;
798                case CONNECT_A2DP_INCOMING:
799                    // ignore
800                    break;
801                case CONNECTION_ACCESS_REQUEST_REPLY:
802                    int val = message.arg1;
803                    mConnectionAccessReplyReceived = true;
804                    boolean value = false;
805                    if (val == BluetoothDevice.CONNECTION_ACCESS_YES) {
806                        value = true;
807                    }
808                    setTrust(val);
809                    handleIncomingConnection(CONNECT_A2DP_INCOMING, value);
810                    break;
811                case CONNECTION_ACCESS_REQUEST_EXPIRY:
812                    // The check protects the race condition between REQUEST_REPLY
813                    // and the timer expiry.
814                    if (!mConnectionAccessReplyReceived) {
815                        handleIncomingConnection(CONNECT_A2DP_INCOMING, false);
816                        sendConnectionAccessRemovalIntent();
817                        sendMessage(TRANSITION_TO_STABLE);
818                    }
819                    break;
820                case CONNECT_A2DP_OUTGOING:
821                    // Defer message and retry
822                    deferMessage(message);
823                    break;
824                case DISCONNECT_HFP_OUTGOING:
825                    deferMessage(message);
826                    break;
827                case DISCONNECT_HFP_INCOMING:
828                    // Shouldn't happen but if does, we can handle it.
829                    // Depends if the headset can handle it.
830                    // Incoming A2DP will be handled by Bluez, Disconnect HFP
831                    // the socket would have already been closed.
832                    // ignore
833                    break;
834                case DISCONNECT_A2DP_OUTGOING:
835                    deferMessage(message);
836                    break;
837                case DISCONNECT_A2DP_INCOMING:
838                    // Ignore, will be handled by Bluez
839                    break;
840                case CONNECT_HID_OUTGOING:
841                case DISCONNECT_HID_OUTGOING:
842                    deferMessage(message);
843                    break;
844                case CONNECT_HID_INCOMING:
845                case DISCONNECT_HID_INCOMING:
846                     break; // ignore
847                case DISCONNECT_PBAP_OUTGOING:
848                case UNPAIR:
849                case AUTO_CONNECT_PROFILES:
850                case CONNECT_OTHER_PROFILES:
851                    deferMessage(message);
852                    break;
853                case TRANSITION_TO_STABLE:
854                    transitionTo(mBondedDevice);
855                    break;
856                default:
857                    return NOT_HANDLED;
858            }
859            return HANDLED;
860        }
861    }
862
863
864    private class OutgoingHid extends State {
865        private boolean mStatus = false;
866        private int mCommand;
867
868        @Override
869        public void enter() {
870            log("Entering OutgoingHid state with: " + getCurrentMessage().what);
871            mCommand = getCurrentMessage().what;
872            if (mCommand != CONNECT_HID_OUTGOING &&
873                mCommand != DISCONNECT_HID_OUTGOING) {
874                Log.e(TAG, "Error: OutgoingHid state with command:" + mCommand);
875            }
876            mStatus = processCommand(mCommand);
877            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
878        }
879
880        @Override
881        public boolean processMessage(Message message) {
882            log("OutgoingHid State->Processing Message: " + message.what);
883            Message deferMsg = new Message();
884            switch(message.what) {
885                // defer all outgoing messages
886                case CONNECT_HFP_OUTGOING:
887                case CONNECT_A2DP_OUTGOING:
888                case CONNECT_HID_OUTGOING:
889                case DISCONNECT_HFP_OUTGOING:
890                case DISCONNECT_A2DP_OUTGOING:
891                case DISCONNECT_HID_OUTGOING:
892                    deferMessage(message);
893                    break;
894
895                case CONNECT_HFP_INCOMING:
896                    transitionTo(mIncomingHandsfree);
897                    break;
898                case CONNECT_A2DP_INCOMING:
899                    transitionTo(mIncomingA2dp);
900
901                    // Don't cancel HID outgoing as there is no guarantee it
902                    // will get canceled.
903                    // It might already be connected but we might not have got the
904                    // INPUT_DEVICE_STATE_CHANGE. Hence, no point disconnecting here.
905                    // The worst case, the connection will fail, retry.
906                    if (mStatus) {
907                        deferMsg.what = mCommand;
908                        deferMessage(deferMsg);
909                    }
910                    break;
911                case CONNECT_HID_INCOMING:
912                  // Bluez will take care of the conflicts
913                    transitionTo(mIncomingHid);
914                    break;
915
916                case DISCONNECT_HFP_INCOMING:
917                case DISCONNECT_A2DP_INCOMING:
918                    // At this point, we are already disconnected
919                    // with HFP. Sometimes HID connection can
920                    // fail due to the disconnection of HFP. So add a retry
921                    // for the HID.
922                    if (mStatus) {
923                        deferMsg.what = mCommand;
924                        deferMessage(deferMsg);
925                    }
926                    break;
927                case DISCONNECT_HID_INCOMING:
928                    // Ignore, will be handled by Bluez
929                    break;
930                case DISCONNECT_PBAP_OUTGOING:
931                case UNPAIR:
932                case AUTO_CONNECT_PROFILES:
933                    deferMessage(message);
934                    break;
935                case TRANSITION_TO_STABLE:
936                    transitionTo(mBondedDevice);
937                    break;
938                default:
939                    return NOT_HANDLED;
940            }
941            return HANDLED;
942        }
943    }
944
945  private class IncomingHid extends State {
946      private boolean mStatus = false;
947      private int mCommand;
948
949      @Override
950    public void enter() {
951          log("Entering IncomingHid state with: " + getCurrentMessage().what);
952          mCommand = getCurrentMessage().what;
953          if (mCommand != CONNECT_HID_INCOMING &&
954              mCommand != DISCONNECT_HID_INCOMING) {
955              Log.e(TAG, "Error: IncomingHid state with command:" + mCommand);
956          }
957          mStatus = processCommand(mCommand);
958          if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
959      }
960
961      @Override
962    public boolean processMessage(Message message) {
963          log("IncomingHid State->Processing Message: " + message.what);
964          Message deferMsg = new Message();
965          switch(message.what) {
966              case CONNECT_HFP_OUTGOING:
967              case CONNECT_HFP_INCOMING:
968              case DISCONNECT_HFP_OUTGOING:
969              case CONNECT_A2DP_INCOMING:
970              case CONNECT_A2DP_OUTGOING:
971              case DISCONNECT_A2DP_OUTGOING:
972              case CONNECT_HID_OUTGOING:
973              case CONNECT_HID_INCOMING:
974              case DISCONNECT_HID_OUTGOING:
975                  deferMessage(message);
976                  break;
977              case CONNECTION_ACCESS_REQUEST_REPLY:
978                  mConnectionAccessReplyReceived = true;
979                  int val = message.arg1;
980                  setTrust(val);
981                  handleIncomingConnection(CONNECT_HID_INCOMING,
982                      val == BluetoothDevice.CONNECTION_ACCESS_YES);
983                  break;
984              case CONNECTION_ACCESS_REQUEST_EXPIRY:
985                  if (!mConnectionAccessReplyReceived) {
986                      handleIncomingConnection(CONNECT_HID_INCOMING, false);
987                      sendConnectionAccessRemovalIntent();
988                      sendMessage(TRANSITION_TO_STABLE);
989                  }
990                  break;
991              case DISCONNECT_HFP_INCOMING:
992                  // Shouldn't happen but if does, we can handle it.
993                  // Depends if the headset can handle it.
994                  // Incoming HID will be handled by Bluez, Disconnect HFP
995                  // the socket would have already been closed.
996                  // ignore
997                  break;
998              case DISCONNECT_HID_INCOMING:
999              case DISCONNECT_A2DP_INCOMING:
1000                  // Ignore, will be handled by Bluez
1001                  break;
1002              case DISCONNECT_PBAP_OUTGOING:
1003              case UNPAIR:
1004              case AUTO_CONNECT_PROFILES:
1005                  deferMessage(message);
1006                  break;
1007              case TRANSITION_TO_STABLE:
1008                  transitionTo(mBondedDevice);
1009                  break;
1010              default:
1011                  return NOT_HANDLED;
1012          }
1013          return HANDLED;
1014      }
1015  }
1016
1017
1018    synchronized void cancelCommand(int command) {
1019        if (command == CONNECT_HFP_OUTGOING ) {
1020            // Cancel the outgoing thread.
1021            if (mHeadsetService != null) {
1022                mHeadsetService.cancelConnectThread();
1023            }
1024            // HeadsetService is down. Phone process most likely crashed.
1025            // The thread would have got killed.
1026        }
1027    }
1028
1029    synchronized void deferProfileServiceMessage(int command) {
1030        Message msg = new Message();
1031        msg.what = command;
1032        deferMessage(msg);
1033    }
1034
1035    private void updateIncomingAllowedTimer() {
1036        // Not doing a perfect exponential backoff because
1037        // we want two different rates. For all practical
1038        // purposes, this is good enough.
1039        if (mIncomingRejectTimer == 0) mIncomingRejectTimer = INIT_INCOMING_REJECT_TIMER;
1040
1041        mIncomingRejectTimer *= 5;
1042        if (mIncomingRejectTimer > MAX_INCOMING_REJECT_TIMER) {
1043            mIncomingRejectTimer = MAX_INCOMING_REJECT_TIMER;
1044        }
1045        writeTimerValue(mIncomingRejectTimer);
1046    }
1047
1048    private boolean handleIncomingConnection(int command, boolean accept) {
1049        boolean ret = false;
1050        Log.i(TAG, "handleIncomingConnection:" + command + ":" + accept);
1051        switch (command) {
1052            case CONNECT_HFP_INCOMING:
1053                if (!accept) {
1054                    ret = mHeadsetService.rejectIncomingConnect(mDevice);
1055                    sendMessage(TRANSITION_TO_STABLE);
1056                    updateIncomingAllowedTimer();
1057                } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
1058                    writeTimerValue(0);
1059                    ret =  mHeadsetService.acceptIncomingConnect(mDevice);
1060                } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
1061                    writeTimerValue(0);
1062                    handleConnectionOfOtherProfiles(command);
1063                    ret = mHeadsetService.createIncomingConnect(mDevice);
1064                }
1065                break;
1066            case CONNECT_A2DP_INCOMING:
1067                if (!accept) {
1068                    ret = mA2dpService.allowIncomingConnect(mDevice, false);
1069                    sendMessage(TRANSITION_TO_STABLE);
1070                    updateIncomingAllowedTimer();
1071                } else {
1072                    writeTimerValue(0);
1073                    ret = mA2dpService.allowIncomingConnect(mDevice, true);
1074                    handleConnectionOfOtherProfiles(command);
1075                }
1076                break;
1077            case CONNECT_HID_INCOMING:
1078                if (!accept) {
1079                    ret = mService.allowIncomingProfileConnect(mDevice, false);
1080                    sendMessage(TRANSITION_TO_STABLE);
1081                    updateIncomingAllowedTimer();
1082                } else {
1083                    writeTimerValue(0);
1084                    ret = mService.allowIncomingProfileConnect(mDevice, true);
1085                }
1086                break;
1087            default:
1088                Log.e(TAG, "Waiting for incoming connection but state changed to:" + command);
1089                break;
1090       }
1091       return ret;
1092    }
1093
1094    private void sendConnectionAccessIntent() {
1095        mConnectionAccessReplyReceived = false;
1096
1097        if (!mPowerManager.isScreenOn()) mWakeLock.acquire();
1098
1099        Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
1100        intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
1101        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
1102                        BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION);
1103        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
1104        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
1105    }
1106
1107    private void sendConnectionAccessRemovalIntent() {
1108        mWakeLock.release();
1109        Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
1110        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
1111        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
1112    }
1113
1114    private int getTrust() {
1115        String address = mDevice.getAddress();
1116        if (mIncomingConnections != null) return mIncomingConnections.first;
1117        return CONNECTION_ACCESS_UNDEFINED;
1118    }
1119
1120
1121    private String getStringValue(long value) {
1122        StringBuilder sbr = new StringBuilder();
1123        sbr.append(Long.toString(System.currentTimeMillis()));
1124        sbr.append("-");
1125        sbr.append(Long.toString(value));
1126        return sbr.toString();
1127    }
1128
1129    private void setTrust(int value) {
1130        String second;
1131        if (mIncomingConnections == null) {
1132            second = getStringValue(INIT_INCOMING_REJECT_TIMER);
1133        } else {
1134            second = mIncomingConnections.second;
1135        }
1136
1137        mIncomingConnections = new Pair(value, second);
1138        mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections);
1139    }
1140
1141    private void writeTimerValue(long value) {
1142        Integer first;
1143        if (mIncomingConnections == null) {
1144            first = CONNECTION_ACCESS_UNDEFINED;
1145        } else {
1146            first = mIncomingConnections.first;
1147        }
1148        mIncomingConnections = new Pair(first, getStringValue(value));
1149        mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections);
1150    }
1151
1152    private long readTimerValue() {
1153        if (mIncomingConnections == null)
1154            return 0;
1155        String value = mIncomingConnections.second;
1156        String[] splits = value.split("-");
1157        if (splits != null && splits.length == 2) {
1158            return Long.parseLong(splits[1]);
1159        }
1160        return 0;
1161    }
1162
1163    private boolean readIncomingAllowedValue() {
1164        if (readTimerValue() == 0) return true;
1165        String value = mIncomingConnections.second;
1166        String[] splits = value.split("-");
1167        if (splits != null && splits.length == 2) {
1168            long val1 = Long.parseLong(splits[0]);
1169            long val2 = Long.parseLong(splits[1]);
1170            if (val1 + val2 <= System.currentTimeMillis()) {
1171                return true;
1172            }
1173        }
1174        return false;
1175    }
1176
1177    synchronized boolean processCommand(int command) {
1178        log("Processing command:" + command);
1179        switch(command) {
1180            case  CONNECT_HFP_OUTGOING:
1181                if (mHeadsetService == null) {
1182                    deferProfileServiceMessage(command);
1183                } else {
1184                    return mHeadsetService.connectHeadsetInternal(mDevice);
1185                }
1186                break;
1187            case CONNECT_HFP_INCOMING:
1188                if (mHeadsetService == null) {
1189                    deferProfileServiceMessage(command);
1190                } else {
1191                    processIncomingConnectCommand(command);
1192                    return true;
1193                }
1194                break;
1195            case CONNECT_A2DP_OUTGOING:
1196                if (mA2dpService != null) {
1197                    return mA2dpService.connectSinkInternal(mDevice);
1198                }
1199                break;
1200            case CONNECT_A2DP_INCOMING:
1201                processIncomingConnectCommand(command);
1202                return true;
1203            case CONNECT_HID_OUTGOING:
1204                return mService.connectInputDeviceInternal(mDevice);
1205            case CONNECT_HID_INCOMING:
1206                processIncomingConnectCommand(command);
1207                return true;
1208            case DISCONNECT_HFP_OUTGOING:
1209                if (mHeadsetService == null) {
1210                    deferProfileServiceMessage(command);
1211                } else {
1212                    // Disconnect PBAP
1213                    // TODO(): Add PBAP to the state machine.
1214                    Message m = new Message();
1215                    m.what = DISCONNECT_PBAP_OUTGOING;
1216                    deferMessage(m);
1217                    if (mHeadsetService.getPriority(mDevice) ==
1218                        BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
1219                        mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
1220                    }
1221                    return mHeadsetService.disconnectHeadsetInternal(mDevice);
1222                }
1223                break;
1224            case DISCONNECT_HFP_INCOMING:
1225                // ignore
1226                return true;
1227            case DISCONNECT_A2DP_INCOMING:
1228                // ignore
1229                return true;
1230            case DISCONNECT_A2DP_OUTGOING:
1231                if (mA2dpService != null) {
1232                    if (mA2dpService.getPriority(mDevice) ==
1233                        BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
1234                        mA2dpService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
1235                    }
1236                    return mA2dpService.disconnectSinkInternal(mDevice);
1237                }
1238                break;
1239            case DISCONNECT_HID_INCOMING:
1240                // ignore
1241                return true;
1242            case DISCONNECT_HID_OUTGOING:
1243                if (mService.getInputDevicePriority(mDevice) ==
1244                    BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
1245                    mService.setInputDevicePriority(mDevice, BluetoothInputDevice.PRIORITY_ON);
1246                }
1247                return mService.disconnectInputDeviceInternal(mDevice);
1248            case DISCONNECT_PBAP_OUTGOING:
1249                if (!mPbapServiceConnected) {
1250                    deferProfileServiceMessage(command);
1251                } else {
1252                    return mPbapService.disconnect();
1253                }
1254                break;
1255            case UNPAIR:
1256                writeTimerValue(INIT_INCOMING_REJECT_TIMER);
1257                setTrust(CONNECTION_ACCESS_UNDEFINED);
1258                return mService.removeBondInternal(mDevice.getAddress());
1259            default:
1260                Log.e(TAG, "Error: Unknown Command");
1261        }
1262        return false;
1263    }
1264
1265    private void processIncomingConnectCommand(int command) {
1266        // Check if device is already trusted
1267        int access = getTrust();
1268        if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
1269            handleIncomingConnection(command, true);
1270        } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO &&
1271                   !readIncomingAllowedValue()) {
1272            handleIncomingConnection(command, false);
1273        } else {
1274            sendConnectionAccessIntent();
1275            Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY);
1276            sendMessageDelayed(msg,
1277                               CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT);
1278        }
1279    }
1280
1281    private void handleConnectionOfOtherProfiles(int command) {
1282        // The white paper recommendations mentions that when there is a
1283        // link loss, it is the responsibility of the remote device to connect.
1284        // Many connect only 1 profile - and they connect the second profile on
1285        // some user action (like play being pressed) and so we need this code.
1286        // Auto Connect code only connects to the last connected device - which
1287        // is useful in cases like when the phone reboots. But consider the
1288        // following case:
1289        // User is connected to the car's phone and  A2DP profile.
1290        // User comes to the desk  and places the phone in the dock
1291        // (or any speaker or music system or even another headset) and thus
1292        // gets connected to the A2DP profile.  User goes back to the car.
1293        // Ideally the car's system is supposed to send incoming connections
1294        // from both Handsfree and A2DP profile. But they don't. The Auto
1295        // connect code, will not work here because we only auto connect to the
1296        // last connected device for that profile which in this case is the dock.
1297        // Now suppose a user is using 2 headsets simultaneously, one for the
1298        // phone profile one for the A2DP profile. If this is the use case, we
1299        // expect the user to use the preference to turn off the A2DP profile in
1300        // the Settings screen for the first headset. Else, after link loss,
1301        // there can be an incoming connection from the first headset which
1302        // might result in the connection of the A2DP profile (if the second
1303        // headset is slower) and thus the A2DP profile on the second headset
1304        // will never get connected.
1305        //
1306        // TODO(): Handle other profiles here.
1307        switch (command) {
1308            case CONNECT_HFP_INCOMING:
1309                // Connect A2DP if there is no incoming connection
1310                // If the priority is OFF - don't auto connect.
1311                if (mA2dpService.getPriority(mDevice) == BluetoothProfile.PRIORITY_ON ||
1312                        mA2dpService.getPriority(mDevice) ==
1313                            BluetoothProfile.PRIORITY_AUTO_CONNECT) {
1314                    Message msg = new Message();
1315                    msg.what = CONNECT_OTHER_PROFILES;
1316                    msg.arg1 = CONNECT_A2DP_OUTGOING;
1317                    sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY);
1318                }
1319                break;
1320            case CONNECT_A2DP_INCOMING:
1321                // This is again against spec. HFP incoming connections should be made
1322                // before A2DP, so we should not hit this case. But many devices
1323                // don't follow this.
1324                if (mHeadsetService != null &&
1325                    (mHeadsetService.getPriority(mDevice) == BluetoothProfile.PRIORITY_ON ||
1326                        mHeadsetService.getPriority(mDevice) ==
1327                            BluetoothProfile.PRIORITY_AUTO_CONNECT)) {
1328                    Message msg = new Message();
1329                    msg.what = CONNECT_OTHER_PROFILES;
1330                    msg.arg1 = CONNECT_HFP_OUTGOING;
1331                    sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY);
1332                }
1333                break;
1334            default:
1335                break;
1336        }
1337
1338    }
1339
1340    /*package*/ BluetoothDevice getDevice() {
1341        return mDevice;
1342    }
1343
1344    private void log(String message) {
1345        if (DBG) {
1346            Log.i(TAG, "Device:" + mDevice + " Message:" + message);
1347        }
1348    }
1349}
1350