BluetoothDeviceProfileState.java revision adbda6f094bf957e2943f80ef63d4530f6fcfc5a
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.server.BluetoothA2dpService;
25import android.server.BluetoothService;
26import android.util.Log;
27
28import com.android.internal.util.HierarchicalState;
29import com.android.internal.util.HierarchicalStateMachine;
30
31/**
32 * This class is the Profile connection state machine associated with a remote
33 * device. When the device bonds an instance of this class is created.
34 * This tracks incoming and outgoing connections of all the profiles. Incoming
35 * connections are preferred over outgoing connections and HFP preferred over
36 * A2DP. When the device is unbonded, the instance is removed.
37 *
38 * States:
39 * {@link BondedDevice}: This state represents a bonded device. When in this
40 * state none of the profiles are in transition states.
41 *
42 * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition
43 * state because of a outgoing Connect or Disconnect.
44 *
45 * {@link IncomingHandsfree}: Handsfree profile connection is in a transition
46 * state because of a incoming Connect or Disconnect.
47 *
48 * {@link IncomingA2dp}: A2dp profile connection is in a transition
49 * state because of a incoming Connect or Disconnect.
50 *
51 * {@link OutgoingA2dp}: A2dp profile connection is in a transition
52 * state because of a outgoing Connect or Disconnect.
53 *
54 * Todo(): Write tests for this class, when the Android Mock support is completed.
55 * @hide
56 */
57public final class BluetoothDeviceProfileState extends HierarchicalStateMachine {
58    private static final String TAG = "BluetoothDeviceProfileState";
59    private static final boolean DBG = true; //STOPSHIP - Change to false
60
61    public static final int CONNECT_HFP_OUTGOING = 1;
62    public static final int CONNECT_HFP_INCOMING = 2;
63    public static final int CONNECT_A2DP_OUTGOING = 3;
64    public static final int CONNECT_A2DP_INCOMING = 4;
65
66    public static final int DISCONNECT_HFP_OUTGOING = 5;
67    private static final int DISCONNECT_HFP_INCOMING = 6;
68    public static final int DISCONNECT_A2DP_OUTGOING = 7;
69    public static final int DISCONNECT_A2DP_INCOMING = 8;
70    public static final int DISCONNECT_PBAP_OUTGOING = 9;
71
72    public static final int UNPAIR = 100;
73    public static final int AUTO_CONNECT_PROFILES = 101;
74    public static final int TRANSITION_TO_STABLE = 102;
75
76    private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs
77
78    private BondedDevice mBondedDevice = new BondedDevice();
79    private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree();
80    private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
81    private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
82    private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
83
84    private Context mContext;
85    private BluetoothService mService;
86    private BluetoothA2dpService mA2dpService;
87    private BluetoothHeadset  mHeadsetService;
88    private BluetoothPbap     mPbapService;
89    private boolean mHeadsetServiceConnected;
90    private boolean mPbapServiceConnected;
91
92    private BluetoothDevice mDevice;
93    private int mHeadsetState;
94    private int mA2dpState;
95
96    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
97        @Override
98        public void onReceive(Context context, Intent intent) {
99            String action = intent.getAction();
100            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
101            if (!device.equals(mDevice)) return;
102
103            if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
104                int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
105                int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0);
106                int initiator = intent.getIntExtra(
107                    BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR,
108                    BluetoothHeadset.LOCAL_DISCONNECT);
109                mHeadsetState = newState;
110                if (newState == BluetoothHeadset.STATE_DISCONNECTED &&
111                    initiator == BluetoothHeadset.REMOTE_DISCONNECT) {
112                    sendMessage(DISCONNECT_HFP_INCOMING);
113                }
114                if (newState == BluetoothHeadset.STATE_CONNECTED ||
115                    newState == BluetoothHeadset.STATE_DISCONNECTED) {
116                    sendMessage(TRANSITION_TO_STABLE);
117                }
118            } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
119                int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0);
120                int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0);
121                mA2dpState = newState;
122                if ((oldState == BluetoothA2dp.STATE_CONNECTED ||
123                           oldState == BluetoothA2dp.STATE_PLAYING) &&
124                           newState == BluetoothA2dp.STATE_DISCONNECTED) {
125                    sendMessage(DISCONNECT_A2DP_INCOMING);
126                }
127                if (newState == BluetoothA2dp.STATE_CONNECTED ||
128                    newState == BluetoothA2dp.STATE_DISCONNECTED) {
129                    sendMessage(TRANSITION_TO_STABLE);
130                }
131            } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
132                if (!getCurrentState().equals(mBondedDevice)) {
133                    Log.e(TAG, "State is: " + getCurrentState());
134                    return;
135                }
136                Message msg = new Message();
137                msg.what = AUTO_CONNECT_PROFILES;
138                sendMessageDelayed(msg, AUTO_CONNECT_DELAY);
139            }
140      }
141    };
142
143    private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) {
144      // This works only because these broadcast intents are "sticky"
145      Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
146      if (i != null) {
147          int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
148          if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
149              BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
150              if (device != null && autoConnectDevice.equals(device)) {
151                  return true;
152              }
153          }
154      }
155      return false;
156  }
157
158    public BluetoothDeviceProfileState(Context context, String address,
159          BluetoothService service, BluetoothA2dpService a2dpService) {
160        super(address);
161        mContext = context;
162        mDevice = new BluetoothDevice(address);
163        mService = service;
164        mA2dpService = a2dpService;
165
166        addState(mBondedDevice);
167        addState(mOutgoingHandsfree);
168        addState(mIncomingHandsfree);
169        addState(mIncomingA2dp);
170        addState(mOutgoingA2dp);
171        setInitialState(mBondedDevice);
172
173        IntentFilter filter = new IntentFilter();
174        // Fine-grained state broadcasts
175        filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
176        filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
177        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
178
179        mContext.registerReceiver(mBroadcastReceiver, filter);
180
181        HeadsetServiceListener l = new HeadsetServiceListener();
182        PbapServiceListener p = new PbapServiceListener();
183    }
184
185    private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener {
186        public HeadsetServiceListener() {
187            mHeadsetService = new BluetoothHeadset(mContext, this);
188        }
189        public void onServiceConnected() {
190            synchronized(BluetoothDeviceProfileState.this) {
191                mHeadsetServiceConnected = true;
192            }
193        }
194        public void onServiceDisconnected() {
195            synchronized(BluetoothDeviceProfileState.this) {
196                mHeadsetServiceConnected = false;
197            }
198        }
199    }
200
201    private class PbapServiceListener implements BluetoothPbap.ServiceListener {
202        public PbapServiceListener() {
203            mPbapService = new BluetoothPbap(mContext, this);
204        }
205        public void onServiceConnected() {
206            synchronized(BluetoothDeviceProfileState.this) {
207                mPbapServiceConnected = true;
208            }
209        }
210        public void onServiceDisconnected() {
211            synchronized(BluetoothDeviceProfileState.this) {
212                mPbapServiceConnected = false;
213            }
214        }
215    }
216
217    private class BondedDevice extends HierarchicalState {
218        @Override
219        protected void enter() {
220            log("Entering ACL Connected state with: " + getCurrentMessage().what);
221            Message m = new Message();
222            m.copyFrom(getCurrentMessage());
223            sendMessageAtFrontOfQueue(m);
224        }
225        @Override
226        protected boolean processMessage(Message message) {
227            log("ACL Connected State -> Processing Message: " + message.what);
228            switch(message.what) {
229                case CONNECT_HFP_OUTGOING:
230                case DISCONNECT_HFP_OUTGOING:
231                    transitionTo(mOutgoingHandsfree);
232                    break;
233                case CONNECT_HFP_INCOMING:
234                    transitionTo(mIncomingHandsfree);
235                    break;
236                case DISCONNECT_HFP_INCOMING:
237                    transitionTo(mIncomingHandsfree);
238                    break;
239                case CONNECT_A2DP_OUTGOING:
240                case DISCONNECT_A2DP_OUTGOING:
241                    transitionTo(mOutgoingA2dp);
242                    break;
243                case CONNECT_A2DP_INCOMING:
244                case DISCONNECT_A2DP_INCOMING:
245                    transitionTo(mIncomingA2dp);
246                    break;
247                case DISCONNECT_PBAP_OUTGOING:
248                    processCommand(DISCONNECT_PBAP_OUTGOING);
249                    break;
250                case UNPAIR:
251                    if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
252                        sendMessage(DISCONNECT_HFP_OUTGOING);
253                        deferMessage(message);
254                        break;
255                    } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) {
256                        sendMessage(DISCONNECT_A2DP_OUTGOING);
257                        deferMessage(message);
258                        break;
259                    }
260                    processCommand(UNPAIR);
261                    break;
262                case AUTO_CONNECT_PROFILES:
263                    if (isPhoneDocked(mDevice)) {
264                        // Don't auto connect to docks.
265                        break;
266                    } else if (!mHeadsetServiceConnected) {
267                        deferMessage(message);
268                    } else {
269                        if (mHeadsetService.getPriority(mDevice) ==
270                              BluetoothHeadset.PRIORITY_AUTO_CONNECT &&
271                              !mHeadsetService.isConnected(mDevice)) {
272                            mHeadsetService.connectHeadset(mDevice);
273                        }
274                        if (mA2dpService != null &&
275                              mA2dpService.getSinkPriority(mDevice) ==
276                              BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
277                              mA2dpService.getConnectedSinks().length == 0) {
278                            mA2dpService.connectSink(mDevice);
279                        }
280                    }
281                    break;
282                case TRANSITION_TO_STABLE:
283                    // ignore.
284                    break;
285                default:
286                    return NOT_HANDLED;
287            }
288            return HANDLED;
289        }
290    }
291
292    private class OutgoingHandsfree extends HierarchicalState {
293        private boolean mStatus = false;
294        private int mCommand;
295
296        @Override
297        protected void enter() {
298            log("Entering OutgoingHandsfree state with: " + getCurrentMessage().what);
299            mCommand = getCurrentMessage().what;
300            if (mCommand != CONNECT_HFP_OUTGOING &&
301                mCommand != DISCONNECT_HFP_OUTGOING) {
302                Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand);
303            }
304            mStatus = processCommand(mCommand);
305            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
306        }
307
308        @Override
309        protected boolean processMessage(Message message) {
310            log("OutgoingHandsfree State -> Processing Message: " + message.what);
311            Message deferMsg = new Message();
312            int command = message.what;
313            switch(command) {
314                case CONNECT_HFP_OUTGOING:
315                    if (command != mCommand) {
316                        // Disconnect followed by a connect - defer
317                        deferMessage(message);
318                    }
319                    break;
320                case CONNECT_HFP_INCOMING:
321                    if (mCommand == CONNECT_HFP_OUTGOING) {
322                        // Cancel outgoing connect, accept incoming
323                        cancelCommand(CONNECT_HFP_OUTGOING);
324                        transitionTo(mIncomingHandsfree);
325                    } else {
326                        // We have done the disconnect but we are not
327                        // sure which state we are in at this point.
328                        deferMessage(message);
329                    }
330                    break;
331                case CONNECT_A2DP_INCOMING:
332                    // accept incoming A2DP, retry HFP_OUTGOING
333                    transitionTo(mIncomingA2dp);
334
335                    if (mStatus) {
336                        deferMsg.what = mCommand;
337                        deferMessage(deferMsg);
338                    }
339                    break;
340                case CONNECT_A2DP_OUTGOING:
341                    deferMessage(message);
342                    break;
343                case DISCONNECT_HFP_OUTGOING:
344                    if (mCommand == CONNECT_HFP_OUTGOING) {
345                        // Cancel outgoing connect
346                        cancelCommand(CONNECT_HFP_OUTGOING);
347                        processCommand(DISCONNECT_HFP_OUTGOING);
348                    }
349                    // else ignore
350                    break;
351                case DISCONNECT_HFP_INCOMING:
352                    // When this happens the socket would be closed and the headset
353                    // state moved to DISCONNECTED, cancel the outgoing thread.
354                    // if it still is in CONNECTING state
355                    cancelCommand(CONNECT_HFP_OUTGOING);
356                    break;
357                case DISCONNECT_A2DP_OUTGOING:
358                    deferMessage(message);
359                    break;
360                case DISCONNECT_A2DP_INCOMING:
361                    // Bluez will handle the disconnect. If because of this the outgoing
362                    // handsfree connection has failed, then retry.
363                    if (mStatus) {
364                       deferMsg.what = mCommand;
365                       deferMessage(deferMsg);
366                    }
367                    break;
368                case DISCONNECT_PBAP_OUTGOING:
369                case UNPAIR:
370                case AUTO_CONNECT_PROFILES:
371                    deferMessage(message);
372                    break;
373                case TRANSITION_TO_STABLE:
374                    transitionTo(mBondedDevice);
375                    break;
376                default:
377                    return NOT_HANDLED;
378            }
379            return HANDLED;
380        }
381    }
382
383    private class IncomingHandsfree extends HierarchicalState {
384        private boolean mStatus = false;
385        private int mCommand;
386
387        @Override
388        protected void enter() {
389            log("Entering IncomingHandsfree state with: " + getCurrentMessage().what);
390            mCommand = getCurrentMessage().what;
391            if (mCommand != CONNECT_HFP_INCOMING &&
392                mCommand != DISCONNECT_HFP_INCOMING) {
393                Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand);
394            }
395            mStatus = processCommand(mCommand);
396            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
397        }
398
399        @Override
400        protected boolean processMessage(Message message) {
401            log("IncomingHandsfree State -> Processing Message: " + message.what);
402            switch(message.what) {
403                case CONNECT_HFP_OUTGOING:
404                    deferMessage(message);
405                    break;
406                case CONNECT_HFP_INCOMING:
407                    // Ignore
408                    Log.e(TAG, "Error: Incoming connection with a pending incoming connection");
409                    break;
410                case CONNECT_A2DP_INCOMING:
411                    // Serialize the commands.
412                    deferMessage(message);
413                    break;
414                case CONNECT_A2DP_OUTGOING:
415                    deferMessage(message);
416                    break;
417                case DISCONNECT_HFP_OUTGOING:
418                    // We don't know at what state we are in the incoming HFP connection state.
419                    // We can be changing from DISCONNECTED to CONNECTING, or
420                    // from CONNECTING to CONNECTED, so serializing this command is
421                    // the safest option.
422                    deferMessage(message);
423                    break;
424                case DISCONNECT_HFP_INCOMING:
425                    // Nothing to do here, we will already be DISCONNECTED
426                    // by this point.
427                    break;
428                case DISCONNECT_A2DP_OUTGOING:
429                    deferMessage(message);
430                    break;
431                case DISCONNECT_A2DP_INCOMING:
432                    // Bluez handles incoming A2DP disconnect.
433                    // If this causes incoming HFP to fail, it is more of a headset problem
434                    // since both connections are incoming ones.
435                    break;
436                case DISCONNECT_PBAP_OUTGOING:
437                case UNPAIR:
438                case AUTO_CONNECT_PROFILES:
439                    deferMessage(message);
440                    break;
441                case TRANSITION_TO_STABLE:
442                    transitionTo(mBondedDevice);
443                    break;
444                default:
445                    return NOT_HANDLED;
446            }
447            return HANDLED;
448        }
449    }
450
451    private class OutgoingA2dp extends HierarchicalState {
452        private boolean mStatus = false;
453        private int mCommand;
454
455        @Override
456        protected void enter() {
457            log("Entering OutgoingA2dp state with: " + getCurrentMessage().what);
458            mCommand = getCurrentMessage().what;
459            if (mCommand != CONNECT_A2DP_OUTGOING &&
460                mCommand != DISCONNECT_A2DP_OUTGOING) {
461                Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand);
462            }
463            mStatus = processCommand(mCommand);
464            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
465        }
466
467        @Override
468        protected boolean processMessage(Message message) {
469            log("OutgoingA2dp State->Processing Message: " + message.what);
470            Message deferMsg = new Message();
471            switch(message.what) {
472                case CONNECT_HFP_OUTGOING:
473                    processCommand(CONNECT_HFP_OUTGOING);
474
475                    // Don't cancel A2DP outgoing as there is no guarantee it
476                    // will get canceled.
477                    // It might already be connected but we might not have got the
478                    // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here.
479                    // The worst case, the connection will fail, retry.
480                    // The same applies to Disconnecting an A2DP connection.
481                    if (mStatus) {
482                        deferMsg.what = mCommand;
483                        deferMessage(deferMsg);
484                    }
485                    break;
486                case CONNECT_HFP_INCOMING:
487                    processCommand(CONNECT_HFP_INCOMING);
488
489                    // Don't cancel A2DP outgoing as there is no guarantee
490                    // it will get canceled.
491                    // The worst case, the connection will fail, retry.
492                    if (mStatus) {
493                        deferMsg.what = mCommand;
494                        deferMessage(deferMsg);
495                    }
496                    break;
497                case CONNECT_A2DP_INCOMING:
498                    // Bluez will take care of conflicts between incoming and outgoing
499                    // connections.
500                    transitionTo(mIncomingA2dp);
501                    break;
502                case CONNECT_A2DP_OUTGOING:
503                    // Ignore
504                    break;
505                case DISCONNECT_HFP_OUTGOING:
506                    deferMessage(message);
507                    break;
508                case DISCONNECT_HFP_INCOMING:
509                    // At this point, we are already disconnected
510                    // with HFP. Sometimes A2DP connection can
511                    // fail due to the disconnection of HFP. So add a retry
512                    // for the A2DP.
513                    if (mStatus) {
514                        deferMsg.what = mCommand;
515                        deferMessage(deferMsg);
516                    }
517                    break;
518                case DISCONNECT_A2DP_OUTGOING:
519                    deferMessage(message);
520                    break;
521                case DISCONNECT_A2DP_INCOMING:
522                    // Ignore, will be handled by Bluez
523                    break;
524                case DISCONNECT_PBAP_OUTGOING:
525                case UNPAIR:
526                case AUTO_CONNECT_PROFILES:
527                    deferMessage(message);
528                    break;
529                case TRANSITION_TO_STABLE:
530                    transitionTo(mBondedDevice);
531                    break;
532                default:
533                    return NOT_HANDLED;
534            }
535            return HANDLED;
536        }
537    }
538
539    private class IncomingA2dp extends HierarchicalState {
540        private boolean mStatus = false;
541        private int mCommand;
542
543        @Override
544        protected void enter() {
545            log("Entering IncomingA2dp state with: " + getCurrentMessage().what);
546            mCommand = getCurrentMessage().what;
547            if (mCommand != CONNECT_A2DP_INCOMING &&
548                mCommand != DISCONNECT_A2DP_INCOMING) {
549                Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand);
550            }
551            mStatus = processCommand(mCommand);
552            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
553        }
554
555        @Override
556        protected boolean processMessage(Message message) {
557            log("IncomingA2dp State->Processing Message: " + message.what);
558            Message deferMsg = new Message();
559            switch(message.what) {
560                case CONNECT_HFP_OUTGOING:
561                    deferMessage(message);
562                    break;
563                case CONNECT_HFP_INCOMING:
564                    // Shouldn't happen, but serialize the commands.
565                    deferMessage(message);
566                    break;
567                case CONNECT_A2DP_INCOMING:
568                    // ignore
569                    break;
570                case CONNECT_A2DP_OUTGOING:
571                    // Defer message and retry
572                    deferMessage(message);
573                    break;
574                case DISCONNECT_HFP_OUTGOING:
575                    deferMessage(message);
576                    break;
577                case DISCONNECT_HFP_INCOMING:
578                    // Shouldn't happen but if does, we can handle it.
579                    // Depends if the headset can handle it.
580                    // Incoming A2DP will be handled by Bluez, Disconnect HFP
581                    // the socket would have already been closed.
582                    // ignore
583                    break;
584                case DISCONNECT_A2DP_OUTGOING:
585                    deferMessage(message);
586                    break;
587                case DISCONNECT_A2DP_INCOMING:
588                    // Ignore, will be handled by Bluez
589                    break;
590                case DISCONNECT_PBAP_OUTGOING:
591                case UNPAIR:
592                case AUTO_CONNECT_PROFILES:
593                    deferMessage(message);
594                    break;
595                case TRANSITION_TO_STABLE:
596                    transitionTo(mBondedDevice);
597                    break;
598                default:
599                    return NOT_HANDLED;
600            }
601            return HANDLED;
602        }
603    }
604
605
606
607    synchronized void cancelCommand(int command) {
608        if (command == CONNECT_HFP_OUTGOING ) {
609            // Cancel the outgoing thread.
610            if (mHeadsetServiceConnected) {
611                mHeadsetService.cancelConnectThread();
612            }
613            // HeadsetService is down. Phone process most likely crashed.
614            // The thread would have got killed.
615        }
616    }
617
618    synchronized void deferProfileServiceMessage(int command) {
619        Message msg = new Message();
620        msg.what = command;
621        deferMessage(msg);
622    }
623
624    synchronized boolean processCommand(int command) {
625        log("Processing command:" + command);
626        switch(command) {
627            case  CONNECT_HFP_OUTGOING:
628                if (mHeadsetService != null) {
629                    return mHeadsetService.connectHeadsetInternal(mDevice);
630                }
631                break;
632            case CONNECT_HFP_INCOMING:
633                if (!mHeadsetServiceConnected) {
634                    deferProfileServiceMessage(command);
635                } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
636                    return mHeadsetService.acceptIncomingConnect(mDevice);
637                } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
638                    return mHeadsetService.createIncomingConnect(mDevice);
639                }
640                break;
641            case CONNECT_A2DP_OUTGOING:
642                if (mA2dpService != null) {
643                    return mA2dpService.connectSinkInternal(mDevice);
644                }
645                break;
646            case CONNECT_A2DP_INCOMING:
647                // ignore, Bluez takes care
648                return true;
649            case DISCONNECT_HFP_OUTGOING:
650                if (!mHeadsetServiceConnected) {
651                    deferProfileServiceMessage(command);
652                } else {
653                    // Disconnect PBAP
654                    // TODO(): Add PBAP to the state machine.
655                    Message m = new Message();
656                    m.what = DISCONNECT_PBAP_OUTGOING;
657                    deferMessage(m);
658                    if (mHeadsetService.getPriority(mDevice) ==
659                        BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
660                        mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
661                    }
662                    return mHeadsetService.disconnectHeadsetInternal(mDevice);
663                }
664                break;
665            case DISCONNECT_HFP_INCOMING:
666                // ignore
667                return true;
668            case DISCONNECT_A2DP_INCOMING:
669                // ignore
670                return true;
671            case DISCONNECT_A2DP_OUTGOING:
672                if (mA2dpService != null) {
673                    if (mA2dpService.getSinkPriority(mDevice) ==
674                        BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
675                        mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
676                    }
677                    return mA2dpService.disconnectSinkInternal(mDevice);
678                }
679                break;
680            case DISCONNECT_PBAP_OUTGOING:
681                if (!mPbapServiceConnected) {
682                    deferProfileServiceMessage(command);
683                } else {
684                    return mPbapService.disconnect();
685                }
686                break;
687            case UNPAIR:
688                return mService.removeBondInternal(mDevice.getAddress());
689            default:
690                Log.e(TAG, "Error: Unknown Command");
691        }
692        return false;
693    }
694
695    /*package*/ BluetoothDevice getDevice() {
696        return mDevice;
697    }
698
699    private void log(String message) {
700        if (DBG) {
701            Log.i(TAG, "Device:" + mDevice + " Message:" + message);
702        }
703    }
704}
705