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