Home | History | Annotate | Download | only in bluetooth
      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 
     17 package android.bluetooth;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.os.Message;
     24 import android.server.BluetoothA2dpService;
     25 import android.server.BluetoothService;
     26 import android.util.Log;
     27 
     28 import com.android.internal.util.HierarchicalState;
     29 import 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  */
     57 public final class BluetoothDeviceProfileState extends HierarchicalStateMachine {
     58     private static final String TAG = "BluetoothDeviceProfileState";
     59     private static final boolean DBG = 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     public static final int CONNECT_OTHER_PROFILES = 103;
     76 
     77     private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs
     78 
     79     private BondedDevice mBondedDevice = new BondedDevice();
     80     private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree();
     81     private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
     82     private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
     83     private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
     84 
     85     private Context mContext;
     86     private BluetoothService mService;
     87     private BluetoothA2dpService mA2dpService;
     88     private BluetoothHeadset  mHeadsetService;
     89     private BluetoothPbap     mPbapService;
     90     private boolean mHeadsetServiceConnected;
     91     private boolean mPbapServiceConnected;
     92 
     93     private BluetoothDevice mDevice;
     94     private int mHeadsetState;
     95     private int mA2dpState;
     96 
     97     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
     98         @Override
     99         public void onReceive(Context context, Intent intent) {
    100             String action = intent.getAction();
    101             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    102             if (!device.equals(mDevice)) return;
    103 
    104             if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
    105                 int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
    106                 int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0);
    107                 int initiator = intent.getIntExtra(
    108                     BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR,
    109                     BluetoothHeadset.LOCAL_DISCONNECT);
    110                 mHeadsetState = newState;
    111                 if (newState == BluetoothHeadset.STATE_DISCONNECTED &&
    112                     initiator == BluetoothHeadset.REMOTE_DISCONNECT) {
    113                     sendMessage(DISCONNECT_HFP_INCOMING);
    114                 }
    115                 if (newState == BluetoothHeadset.STATE_CONNECTED ||
    116                     newState == BluetoothHeadset.STATE_DISCONNECTED) {
    117                     sendMessage(TRANSITION_TO_STABLE);
    118                 }
    119             } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
    120                 int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0);
    121                 int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0);
    122                 mA2dpState = newState;
    123                 if ((oldState == BluetoothA2dp.STATE_CONNECTED ||
    124                            oldState == BluetoothA2dp.STATE_PLAYING) &&
    125                            newState == BluetoothA2dp.STATE_DISCONNECTED) {
    126                     sendMessage(DISCONNECT_A2DP_INCOMING);
    127                 }
    128                 if (newState == BluetoothA2dp.STATE_CONNECTED ||
    129                     newState == BluetoothA2dp.STATE_DISCONNECTED) {
    130                     sendMessage(TRANSITION_TO_STABLE);
    131                 }
    132             } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
    133                 Message msg = new Message();
    134                 msg.what = AUTO_CONNECT_PROFILES;
    135                 sendMessageDelayed(msg, AUTO_CONNECT_DELAY);
    136             } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
    137                 // This is technically not needed, but we can get stuck sometimes.
    138                 // For example, if incoming A2DP fails, we are not informed by Bluez
    139                 sendMessage(TRANSITION_TO_STABLE);
    140             }
    141       }
    142     };
    143 
    144     private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) {
    145       // This works only because these broadcast intents are "sticky"
    146       Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
    147       if (i != null) {
    148           int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
    149           if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
    150               BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    151               if (device != null && autoConnectDevice.equals(device)) {
    152                   return true;
    153               }
    154           }
    155       }
    156       return false;
    157   }
    158 
    159     public BluetoothDeviceProfileState(Context context, String address,
    160           BluetoothService service, BluetoothA2dpService a2dpService) {
    161         super(address);
    162         mContext = context;
    163         mDevice = new BluetoothDevice(address);
    164         mService = service;
    165         mA2dpService = a2dpService;
    166 
    167         addState(mBondedDevice);
    168         addState(mOutgoingHandsfree);
    169         addState(mIncomingHandsfree);
    170         addState(mIncomingA2dp);
    171         addState(mOutgoingA2dp);
    172         setInitialState(mBondedDevice);
    173 
    174         IntentFilter filter = new IntentFilter();
    175         // Fine-grained state broadcasts
    176         filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
    177         filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
    178         filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
    179         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
    180 
    181         mContext.registerReceiver(mBroadcastReceiver, filter);
    182 
    183         HeadsetServiceListener l = new HeadsetServiceListener();
    184         PbapServiceListener p = new PbapServiceListener();
    185     }
    186 
    187     private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener {
    188         public HeadsetServiceListener() {
    189             mHeadsetService = new BluetoothHeadset(mContext, this);
    190         }
    191         public void onServiceConnected() {
    192             synchronized(BluetoothDeviceProfileState.this) {
    193                 mHeadsetServiceConnected = true;
    194             }
    195         }
    196         public void onServiceDisconnected() {
    197             synchronized(BluetoothDeviceProfileState.this) {
    198                 mHeadsetServiceConnected = false;
    199             }
    200         }
    201     }
    202 
    203     private class PbapServiceListener implements BluetoothPbap.ServiceListener {
    204         public PbapServiceListener() {
    205             mPbapService = new BluetoothPbap(mContext, this);
    206         }
    207         public void onServiceConnected() {
    208             synchronized(BluetoothDeviceProfileState.this) {
    209                 mPbapServiceConnected = true;
    210             }
    211         }
    212         public void onServiceDisconnected() {
    213             synchronized(BluetoothDeviceProfileState.this) {
    214                 mPbapServiceConnected = false;
    215             }
    216         }
    217     }
    218 
    219     private class BondedDevice extends HierarchicalState {
    220         @Override
    221         protected void enter() {
    222             Log.i(TAG, "Entering ACL Connected state with: " + getCurrentMessage().what);
    223             Message m = new Message();
    224             m.copyFrom(getCurrentMessage());
    225             sendMessageAtFrontOfQueue(m);
    226         }
    227         @Override
    228         protected boolean processMessage(Message message) {
    229             log("ACL Connected State -> Processing Message: " + message.what);
    230             switch(message.what) {
    231                 case CONNECT_HFP_OUTGOING:
    232                 case DISCONNECT_HFP_OUTGOING:
    233                     transitionTo(mOutgoingHandsfree);
    234                     break;
    235                 case CONNECT_HFP_INCOMING:
    236                     transitionTo(mIncomingHandsfree);
    237                     break;
    238                 case DISCONNECT_HFP_INCOMING:
    239                     transitionTo(mIncomingHandsfree);
    240                     break;
    241                 case CONNECT_A2DP_OUTGOING:
    242                 case DISCONNECT_A2DP_OUTGOING:
    243                     transitionTo(mOutgoingA2dp);
    244                     break;
    245                 case CONNECT_A2DP_INCOMING:
    246                 case DISCONNECT_A2DP_INCOMING:
    247                     transitionTo(mIncomingA2dp);
    248                     break;
    249                 case DISCONNECT_PBAP_OUTGOING:
    250                     processCommand(DISCONNECT_PBAP_OUTGOING);
    251                     break;
    252                 case UNPAIR:
    253                     if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
    254                         sendMessage(DISCONNECT_HFP_OUTGOING);
    255                         deferMessage(message);
    256                         break;
    257                     } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) {
    258                         sendMessage(DISCONNECT_A2DP_OUTGOING);
    259                         deferMessage(message);
    260                         break;
    261                     }
    262                     processCommand(UNPAIR);
    263                     break;
    264                 case AUTO_CONNECT_PROFILES:
    265                     if (isPhoneDocked(mDevice)) {
    266                         // Don't auto connect to docks.
    267                         break;
    268                     } else if (!mHeadsetServiceConnected) {
    269                         deferMessage(message);
    270                     } else {
    271                         if (mHeadsetService.getPriority(mDevice) ==
    272                               BluetoothHeadset.PRIORITY_AUTO_CONNECT &&
    273                               !mHeadsetService.isConnected(mDevice)) {
    274                             Log.i(TAG, "Headset:Auto Connect Profiles");
    275                             mHeadsetService.connectHeadset(mDevice);
    276                         }
    277                         if (mA2dpService != null &&
    278                               mA2dpService.getSinkPriority(mDevice) ==
    279                               BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
    280                               mA2dpService.getConnectedSinks().length == 0) {
    281                             Log.i(TAG, "A2dp:Auto Connect Profiles");
    282                             mA2dpService.connectSink(mDevice);
    283                         }
    284                     }
    285                     break;
    286                 case CONNECT_OTHER_PROFILES:
    287                     if (isPhoneDocked(mDevice)) {
    288                        break;
    289                     }
    290                     if (message.arg1 == CONNECT_A2DP_OUTGOING) {
    291                         if (mA2dpService != null &&
    292                             mA2dpService.getConnectedSinks().length == 0) {
    293                             Log.i(TAG, "A2dp:Connect Other Profiles");
    294                             mA2dpService.connectSink(mDevice);
    295                         }
    296                     } else if (message.arg1 == CONNECT_HFP_OUTGOING) {
    297                         if (!mHeadsetServiceConnected) {
    298                             deferMessage(message);
    299                         } else {
    300                             if (!mHeadsetService.isConnected(mDevice)) {
    301                                 Log.i(TAG, "Headset:Connect Other Profiles");
    302                                 mHeadsetService.connectHeadset(mDevice);
    303                             }
    304                         }
    305                     }
    306                     break;
    307                 case TRANSITION_TO_STABLE:
    308                     // ignore.
    309                     break;
    310                 default:
    311                     return NOT_HANDLED;
    312             }
    313             return HANDLED;
    314         }
    315     }
    316 
    317     private class OutgoingHandsfree extends HierarchicalState {
    318         private boolean mStatus = false;
    319         private int mCommand;
    320 
    321         @Override
    322         protected void enter() {
    323             Log.i(TAG, "Entering OutgoingHandsfree state with: " + getCurrentMessage().what);
    324             mCommand = getCurrentMessage().what;
    325             if (mCommand != CONNECT_HFP_OUTGOING &&
    326                 mCommand != DISCONNECT_HFP_OUTGOING) {
    327                 Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand);
    328             }
    329             mStatus = processCommand(mCommand);
    330             if (!mStatus) {
    331                 sendMessage(TRANSITION_TO_STABLE);
    332                 mService.sendProfileStateMessage(BluetoothProfileState.HFP,
    333                                                  BluetoothProfileState.TRANSITION_TO_STABLE);
    334             }
    335         }
    336 
    337         @Override
    338         protected boolean processMessage(Message message) {
    339             log("OutgoingHandsfree State -> Processing Message: " + message.what);
    340             Message deferMsg = new Message();
    341             int command = message.what;
    342             switch(command) {
    343                 case CONNECT_HFP_OUTGOING:
    344                     if (command != mCommand) {
    345                         // Disconnect followed by a connect - defer
    346                         deferMessage(message);
    347                     }
    348                     break;
    349                 case CONNECT_HFP_INCOMING:
    350                     if (mCommand == CONNECT_HFP_OUTGOING) {
    351                         // Cancel outgoing connect, accept incoming
    352                         cancelCommand(CONNECT_HFP_OUTGOING);
    353                         transitionTo(mIncomingHandsfree);
    354                     } else {
    355                         // We have done the disconnect but we are not
    356                         // sure which state we are in at this point.
    357                         deferMessage(message);
    358                     }
    359                     break;
    360                 case CONNECT_A2DP_INCOMING:
    361                     // accept incoming A2DP, retry HFP_OUTGOING
    362                     transitionTo(mIncomingA2dp);
    363 
    364                     if (mStatus) {
    365                         deferMsg.what = mCommand;
    366                         deferMessage(deferMsg);
    367                     }
    368                     break;
    369                 case CONNECT_A2DP_OUTGOING:
    370                     deferMessage(message);
    371                     break;
    372                 case DISCONNECT_HFP_OUTGOING:
    373                     if (mCommand == CONNECT_HFP_OUTGOING) {
    374                         // Cancel outgoing connect
    375                         cancelCommand(CONNECT_HFP_OUTGOING);
    376                         processCommand(DISCONNECT_HFP_OUTGOING);
    377                     }
    378                     // else ignore
    379                     break;
    380                 case DISCONNECT_HFP_INCOMING:
    381                     // When this happens the socket would be closed and the headset
    382                     // state moved to DISCONNECTED, cancel the outgoing thread.
    383                     // if it still is in CONNECTING state
    384                     cancelCommand(CONNECT_HFP_OUTGOING);
    385                     break;
    386                 case DISCONNECT_A2DP_OUTGOING:
    387                     deferMessage(message);
    388                     break;
    389                 case DISCONNECT_A2DP_INCOMING:
    390                     // Bluez will handle the disconnect. If because of this the outgoing
    391                     // handsfree connection has failed, then retry.
    392                     if (mStatus) {
    393                        deferMsg.what = mCommand;
    394                        deferMessage(deferMsg);
    395                     }
    396                     break;
    397                 case DISCONNECT_PBAP_OUTGOING:
    398                 case UNPAIR:
    399                 case AUTO_CONNECT_PROFILES:
    400                 case CONNECT_OTHER_PROFILES:
    401                     deferMessage(message);
    402                     break;
    403                 case TRANSITION_TO_STABLE:
    404                     transitionTo(mBondedDevice);
    405                     break;
    406                 default:
    407                     return NOT_HANDLED;
    408             }
    409             return HANDLED;
    410         }
    411     }
    412 
    413     private class IncomingHandsfree extends HierarchicalState {
    414         private boolean mStatus = false;
    415         private int mCommand;
    416 
    417         @Override
    418         protected void enter() {
    419             Log.i(TAG, "Entering IncomingHandsfree state with: " + getCurrentMessage().what);
    420             mCommand = getCurrentMessage().what;
    421             if (mCommand != CONNECT_HFP_INCOMING &&
    422                 mCommand != DISCONNECT_HFP_INCOMING) {
    423                 Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand);
    424             }
    425             mStatus = processCommand(mCommand);
    426             if (!mStatus) {
    427                 sendMessage(TRANSITION_TO_STABLE);
    428                 mService.sendProfileStateMessage(BluetoothProfileState.HFP,
    429                                                  BluetoothProfileState.TRANSITION_TO_STABLE);
    430             }
    431         }
    432 
    433         @Override
    434         protected boolean processMessage(Message message) {
    435             log("IncomingHandsfree State -> Processing Message: " + message.what);
    436             switch(message.what) {
    437                 case CONNECT_HFP_OUTGOING:
    438                     deferMessage(message);
    439                     break;
    440                 case CONNECT_HFP_INCOMING:
    441                     // Ignore
    442                     Log.e(TAG, "Error: Incoming connection with a pending incoming connection");
    443                     break;
    444                 case CONNECT_A2DP_INCOMING:
    445                     // Serialize the commands.
    446                     deferMessage(message);
    447                     break;
    448                 case CONNECT_A2DP_OUTGOING:
    449                     deferMessage(message);
    450                     break;
    451                 case DISCONNECT_HFP_OUTGOING:
    452                     // We don't know at what state we are in the incoming HFP connection state.
    453                     // We can be changing from DISCONNECTED to CONNECTING, or
    454                     // from CONNECTING to CONNECTED, so serializing this command is
    455                     // the safest option.
    456                     deferMessage(message);
    457                     break;
    458                 case DISCONNECT_HFP_INCOMING:
    459                     // Nothing to do here, we will already be DISCONNECTED
    460                     // by this point.
    461                     break;
    462                 case DISCONNECT_A2DP_OUTGOING:
    463                     deferMessage(message);
    464                     break;
    465                 case DISCONNECT_A2DP_INCOMING:
    466                     // Bluez handles incoming A2DP disconnect.
    467                     // If this causes incoming HFP to fail, it is more of a headset problem
    468                     // since both connections are incoming ones.
    469                     break;
    470                 case DISCONNECT_PBAP_OUTGOING:
    471                 case UNPAIR:
    472                 case AUTO_CONNECT_PROFILES:
    473                 case CONNECT_OTHER_PROFILES:
    474                     deferMessage(message);
    475                     break;
    476                 case TRANSITION_TO_STABLE:
    477                     transitionTo(mBondedDevice);
    478                     break;
    479                 default:
    480                     return NOT_HANDLED;
    481             }
    482             return HANDLED;
    483         }
    484     }
    485 
    486     private class OutgoingA2dp extends HierarchicalState {
    487         private boolean mStatus = false;
    488         private int mCommand;
    489 
    490         @Override
    491         protected void enter() {
    492             Log.i(TAG, "Entering OutgoingA2dp state with: " + getCurrentMessage().what);
    493             mCommand = getCurrentMessage().what;
    494             if (mCommand != CONNECT_A2DP_OUTGOING &&
    495                 mCommand != DISCONNECT_A2DP_OUTGOING) {
    496                 Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand);
    497             }
    498             mStatus = processCommand(mCommand);
    499             if (!mStatus) {
    500                 sendMessage(TRANSITION_TO_STABLE);
    501                 mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
    502                                                  BluetoothProfileState.TRANSITION_TO_STABLE);
    503             }
    504         }
    505 
    506         @Override
    507         protected boolean processMessage(Message message) {
    508             log("OutgoingA2dp State->Processing Message: " + message.what);
    509             Message deferMsg = new Message();
    510             switch(message.what) {
    511                 case CONNECT_HFP_OUTGOING:
    512                     processCommand(CONNECT_HFP_OUTGOING);
    513 
    514                     // Don't cancel A2DP outgoing as there is no guarantee it
    515                     // will get canceled.
    516                     // It might already be connected but we might not have got the
    517                     // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here.
    518                     // The worst case, the connection will fail, retry.
    519                     // The same applies to Disconnecting an A2DP connection.
    520                     if (mStatus) {
    521                         deferMsg.what = mCommand;
    522                         deferMessage(deferMsg);
    523                     }
    524                     break;
    525                 case CONNECT_HFP_INCOMING:
    526                     processCommand(CONNECT_HFP_INCOMING);
    527 
    528                     // Don't cancel A2DP outgoing as there is no guarantee
    529                     // it will get canceled.
    530                     // The worst case, the connection will fail, retry.
    531                     if (mStatus) {
    532                         deferMsg.what = mCommand;
    533                         deferMessage(deferMsg);
    534                     }
    535                     break;
    536                 case CONNECT_A2DP_INCOMING:
    537                     // Bluez will take care of conflicts between incoming and outgoing
    538                     // connections.
    539                     transitionTo(mIncomingA2dp);
    540                     break;
    541                 case CONNECT_A2DP_OUTGOING:
    542                     // Ignore
    543                     break;
    544                 case DISCONNECT_HFP_OUTGOING:
    545                     deferMessage(message);
    546                     break;
    547                 case DISCONNECT_HFP_INCOMING:
    548                     // At this point, we are already disconnected
    549                     // with HFP. Sometimes A2DP connection can
    550                     // fail due to the disconnection of HFP. So add a retry
    551                     // for the A2DP.
    552                     if (mStatus) {
    553                         deferMsg.what = mCommand;
    554                         deferMessage(deferMsg);
    555                     }
    556                     break;
    557                 case DISCONNECT_A2DP_OUTGOING:
    558                     deferMessage(message);
    559                     break;
    560                 case DISCONNECT_A2DP_INCOMING:
    561                     // Ignore, will be handled by Bluez
    562                     break;
    563                 case DISCONNECT_PBAP_OUTGOING:
    564                 case UNPAIR:
    565                 case AUTO_CONNECT_PROFILES:
    566                 case CONNECT_OTHER_PROFILES:
    567                     deferMessage(message);
    568                     break;
    569                 case TRANSITION_TO_STABLE:
    570                     transitionTo(mBondedDevice);
    571                     break;
    572                 default:
    573                     return NOT_HANDLED;
    574             }
    575             return HANDLED;
    576         }
    577     }
    578 
    579     private class IncomingA2dp extends HierarchicalState {
    580         private boolean mStatus = false;
    581         private int mCommand;
    582 
    583         @Override
    584         protected void enter() {
    585             Log.i(TAG, "Entering IncomingA2dp state with: " + getCurrentMessage().what);
    586             mCommand = getCurrentMessage().what;
    587             if (mCommand != CONNECT_A2DP_INCOMING &&
    588                 mCommand != DISCONNECT_A2DP_INCOMING) {
    589                 Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand);
    590             }
    591             mStatus = processCommand(mCommand);
    592             if (!mStatus) {
    593                 sendMessage(TRANSITION_TO_STABLE);
    594                 mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
    595                                                  BluetoothProfileState.TRANSITION_TO_STABLE);
    596             }
    597         }
    598 
    599         @Override
    600         protected boolean processMessage(Message message) {
    601             log("IncomingA2dp State->Processing Message: " + message.what);
    602             Message deferMsg = new Message();
    603             switch(message.what) {
    604                 case CONNECT_HFP_OUTGOING:
    605                     deferMessage(message);
    606                     break;
    607                 case CONNECT_HFP_INCOMING:
    608                     // Shouldn't happen, but serialize the commands.
    609                     deferMessage(message);
    610                     break;
    611                 case CONNECT_A2DP_INCOMING:
    612                     // ignore
    613                     break;
    614                 case CONNECT_A2DP_OUTGOING:
    615                     // Defer message and retry
    616                     deferMessage(message);
    617                     break;
    618                 case DISCONNECT_HFP_OUTGOING:
    619                     deferMessage(message);
    620                     break;
    621                 case DISCONNECT_HFP_INCOMING:
    622                     // Shouldn't happen but if does, we can handle it.
    623                     // Depends if the headset can handle it.
    624                     // Incoming A2DP will be handled by Bluez, Disconnect HFP
    625                     // the socket would have already been closed.
    626                     // ignore
    627                     break;
    628                 case DISCONNECT_A2DP_OUTGOING:
    629                     deferMessage(message);
    630                     break;
    631                 case DISCONNECT_A2DP_INCOMING:
    632                     // Ignore, will be handled by Bluez
    633                     break;
    634                 case DISCONNECT_PBAP_OUTGOING:
    635                 case UNPAIR:
    636                 case AUTO_CONNECT_PROFILES:
    637                 case CONNECT_OTHER_PROFILES:
    638                     deferMessage(message);
    639                     break;
    640                 case TRANSITION_TO_STABLE:
    641                     transitionTo(mBondedDevice);
    642                     break;
    643                 default:
    644                     return NOT_HANDLED;
    645             }
    646             return HANDLED;
    647         }
    648     }
    649 
    650 
    651 
    652     synchronized void cancelCommand(int command) {
    653         if (command == CONNECT_HFP_OUTGOING ) {
    654             // Cancel the outgoing thread.
    655             if (mHeadsetServiceConnected) {
    656                 mHeadsetService.cancelConnectThread();
    657             }
    658             // HeadsetService is down. Phone process most likely crashed.
    659             // The thread would have got killed.
    660         }
    661     }
    662 
    663     synchronized void deferProfileServiceMessage(int command) {
    664         Message msg = new Message();
    665         msg.what = command;
    666         deferMessage(msg);
    667     }
    668 
    669     synchronized boolean processCommand(int command) {
    670         Log.i(TAG, "Processing command:" + command);
    671         switch(command) {
    672             case  CONNECT_HFP_OUTGOING:
    673                 if (mHeadsetService != null) {
    674                     return mHeadsetService.connectHeadsetInternal(mDevice);
    675                 }
    676                 break;
    677             case CONNECT_HFP_INCOMING:
    678                 if (!mHeadsetServiceConnected) {
    679                     deferProfileServiceMessage(command);
    680                 } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
    681                     return mHeadsetService.acceptIncomingConnect(mDevice);
    682                 } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
    683                     handleConnectionOfOtherProfiles(command);
    684                     return mHeadsetService.createIncomingConnect(mDevice);
    685                 }
    686                 break;
    687             case CONNECT_A2DP_OUTGOING:
    688                 if (mA2dpService != null) {
    689                     return mA2dpService.connectSinkInternal(mDevice);
    690                 }
    691                 break;
    692             case CONNECT_A2DP_INCOMING:
    693                 handleConnectionOfOtherProfiles(command);
    694                 // ignore, Bluez takes care
    695                 return true;
    696             case DISCONNECT_HFP_OUTGOING:
    697                 if (!mHeadsetServiceConnected) {
    698                     deferProfileServiceMessage(command);
    699                 } else {
    700                     // Disconnect PBAP
    701                     // TODO(): Add PBAP to the state machine.
    702                     Message m = new Message();
    703                     m.what = DISCONNECT_PBAP_OUTGOING;
    704                     deferMessage(m);
    705                     if (mHeadsetService.getPriority(mDevice) ==
    706                         BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
    707                         mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
    708                     }
    709                     return mHeadsetService.disconnectHeadsetInternal(mDevice);
    710                 }
    711                 break;
    712             case DISCONNECT_HFP_INCOMING:
    713                 // ignore
    714                 return true;
    715             case DISCONNECT_A2DP_INCOMING:
    716                 // ignore
    717                 return true;
    718             case DISCONNECT_A2DP_OUTGOING:
    719                 if (mA2dpService != null) {
    720                     if (mA2dpService.getSinkPriority(mDevice) ==
    721                         BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
    722                         mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
    723                     }
    724                     return mA2dpService.disconnectSinkInternal(mDevice);
    725                 }
    726                 break;
    727             case DISCONNECT_PBAP_OUTGOING:
    728                 if (!mPbapServiceConnected) {
    729                     deferProfileServiceMessage(command);
    730                 } else {
    731                     return mPbapService.disconnect();
    732                 }
    733                 break;
    734             case UNPAIR:
    735                 return mService.removeBondInternal(mDevice.getAddress());
    736             default:
    737                 Log.e(TAG, "Error: Unknown Command");
    738         }
    739         return false;
    740     }
    741 
    742     private void handleConnectionOfOtherProfiles(int command) {
    743         // The white paper recommendations mentions that when there is a
    744         // link loss, it is the responsibility of the remote device to connect.
    745         // Many connect only 1 profile - and they connect the second profile on
    746         // some user action (like play being pressed) and so we need this code.
    747         // Auto Connect code only connects to the last connected device - which
    748         // is useful in cases like when the phone reboots. But consider the
    749         // following case:
    750         // User is connected to the car's phone and  A2DP profile.
    751         // User comes to the desk  and places the phone in the dock
    752         // (or any speaker or music system or even another headset) and thus
    753         // gets connected to the A2DP profile.  User goes back to the car.
    754         // Ideally the car's system is supposed to send incoming connections
    755         // from both Handsfree and A2DP profile. But they don't. The Auto
    756         // connect code, will not work here because we only auto connect to the
    757         // last connected device for that profile which in this case is the dock.
    758         // Now suppose a user is using 2 headsets simultaneously, one for the
    759         // phone profile one for the A2DP profile. If this is the use case, we
    760         // expect the user to use the preference to turn off the A2DP profile in
    761         // the Settings screen for the first headset. Else, after link loss,
    762         // there can be an incoming connection from the first headset which
    763         // might result in the connection of the A2DP profile (if the second
    764         // headset is slower) and thus the A2DP profile on the second headset
    765         // will never get connected.
    766         //
    767         // TODO(): Handle other profiles here.
    768         switch (command) {
    769             case CONNECT_HFP_INCOMING:
    770                 // Connect A2DP if there is no incoming connection
    771                 // If the priority is OFF - don't auto connect.
    772                 // If the priority is AUTO_CONNECT, auto connect code takes care.
    773                 if (mA2dpService.getSinkPriority(mDevice) == BluetoothA2dp.PRIORITY_ON) {
    774                     Message msg = new Message();
    775                     msg.what = CONNECT_OTHER_PROFILES;
    776                     msg.arg1 = CONNECT_A2DP_OUTGOING;
    777                     sendMessageDelayed(msg, AUTO_CONNECT_DELAY);
    778                 }
    779                 break;
    780             case CONNECT_A2DP_INCOMING:
    781                 // This is again against spec. HFP incoming connections should be made
    782                 // before A2DP, so we should not hit this case. But many devices
    783                 // don't follow this.
    784                 if (mHeadsetService.getPriority(mDevice) == BluetoothHeadset.PRIORITY_ON) {
    785                     Message msg = new Message();
    786                     msg.what = CONNECT_OTHER_PROFILES;
    787                     msg.arg1 = CONNECT_HFP_OUTGOING;
    788                     sendMessageDelayed(msg, AUTO_CONNECT_DELAY);
    789                 }
    790                 break;
    791             default:
    792                 break;
    793         }
    794 
    795     }
    796 
    797     /*package*/ BluetoothDevice getDevice() {
    798         return mDevice;
    799     }
    800 
    801     private void log(String message) {
    802         if (DBG) {
    803             Log.i(TAG, "Device:" + mDevice + " Message:" + message);
    804         }
    805     }
    806 }
    807