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.os.PowerManager;
     25 import android.server.BluetoothA2dpService;
     26 import android.server.BluetoothService;
     27 import android.util.Log;
     28 import android.util.Pair;
     29 
     30 import com.android.internal.util.HierarchicalState;
     31 import com.android.internal.util.HierarchicalStateMachine;
     32 
     33 /**
     34  * This class is the Profile connection state machine associated with a remote
     35  * device. When the device bonds an instance of this class is created.
     36  * This tracks incoming and outgoing connections of all the profiles. Incoming
     37  * connections are preferred over outgoing connections and HFP preferred over
     38  * A2DP. When the device is unbonded, the instance is removed.
     39  *
     40  * States:
     41  * {@link BondedDevice}: This state represents a bonded device. When in this
     42  * state none of the profiles are in transition states.
     43  *
     44  * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition
     45  * state because of a outgoing Connect or Disconnect.
     46  *
     47  * {@link IncomingHandsfree}: Handsfree profile connection is in a transition
     48  * state because of a incoming Connect or Disconnect.
     49  *
     50  * {@link IncomingA2dp}: A2dp profile connection is in a transition
     51  * state because of a incoming Connect or Disconnect.
     52  *
     53  * {@link OutgoingA2dp}: A2dp profile connection is in a transition
     54  * state because of a outgoing Connect or Disconnect.
     55  *
     56  * Todo(): Write tests for this class, when the Android Mock support is completed.
     57  * @hide
     58  */
     59 public final class BluetoothDeviceProfileState extends HierarchicalStateMachine {
     60     private static final String TAG = "BluetoothDeviceProfileState";
     61     private static final boolean DBG = false;
     62 
     63     public static final int CONNECT_HFP_OUTGOING = 1;
     64     public static final int CONNECT_HFP_INCOMING = 2;
     65     public static final int CONNECT_A2DP_OUTGOING = 3;
     66     public static final int CONNECT_A2DP_INCOMING = 4;
     67 
     68     public static final int DISCONNECT_HFP_OUTGOING = 5;
     69     private static final int DISCONNECT_HFP_INCOMING = 6;
     70     public static final int DISCONNECT_A2DP_OUTGOING = 7;
     71     public static final int DISCONNECT_A2DP_INCOMING = 8;
     72     public static final int DISCONNECT_PBAP_OUTGOING = 9;
     73 
     74     public static final int UNPAIR = 100;
     75     public static final int AUTO_CONNECT_PROFILES = 101;
     76     public static final int TRANSITION_TO_STABLE = 102;
     77     public static final int CONNECT_OTHER_PROFILES = 103;
     78     private static final int CONNECTION_ACCESS_REQUEST_REPLY = 104;
     79     private static final int CONNECTION_ACCESS_REQUEST_EXPIRY = 105;
     80 
     81     private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs
     82     private static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs
     83     private static final int CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT = 7000; // 7 secs
     84     private static final int CONNECTION_ACCESS_UNDEFINED = -1;
     85     private static final long INIT_INCOMING_REJECT_TIMER = 1000; // 1 sec
     86     private static final long MAX_INCOMING_REJECT_TIMER = 3600 * 1000 * 4; // 4 hours
     87 
     88     private static final String PREFS_NAME = "ConnectionAccess";
     89 
     90     private BondedDevice mBondedDevice = new BondedDevice();
     91     private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree();
     92     private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
     93     private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
     94     private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
     95 
     96     private Context mContext;
     97     private BluetoothService mService;
     98     private BluetoothA2dpService mA2dpService;
     99     private BluetoothHeadset  mHeadsetService;
    100     private BluetoothPbap     mPbapService;
    101     private boolean mHeadsetServiceConnected;
    102     private boolean mPbapServiceConnected;
    103     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
    104 
    105     private BluetoothDevice mDevice;
    106     private int mHeadsetState;
    107     private int mA2dpState;
    108     private long mIncomingRejectTimer;
    109     private boolean mConnectionAccessReplyReceived = false;
    110     private Pair<Integer, String> mIncomingConnections;
    111     private PowerManager.WakeLock mWakeLock;
    112     private PowerManager mPowerManager;
    113 
    114     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    115         @Override
    116         public void onReceive(Context context, Intent intent) {
    117             String action = intent.getAction();
    118             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    119             if (!device.equals(mDevice)) return;
    120 
    121             if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
    122                 int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
    123                 int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0);
    124                 int initiator = intent.getIntExtra(
    125                     BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR,
    126                     BluetoothHeadset.LOCAL_DISCONNECT);
    127                 // We trust this device now
    128                 if (newState == BluetoothHeadset.STATE_CONNECTED) {
    129                     setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
    130                 }
    131                 mHeadsetState = newState;
    132                 if (newState == BluetoothHeadset.STATE_DISCONNECTED &&
    133                     initiator == BluetoothHeadset.REMOTE_DISCONNECT) {
    134                     sendMessage(DISCONNECT_HFP_INCOMING);
    135                 }
    136                 if (newState == BluetoothHeadset.STATE_CONNECTED ||
    137                     newState == BluetoothHeadset.STATE_DISCONNECTED) {
    138                     sendMessage(TRANSITION_TO_STABLE);
    139                 }
    140             } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
    141                 int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0);
    142                 int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0);
    143                 mA2dpState = newState;
    144                 // We trust this device now
    145                 if (newState == BluetoothA2dp.STATE_CONNECTED) {
    146                     setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
    147                 }
    148                 if ((oldState == BluetoothA2dp.STATE_CONNECTED ||
    149                            oldState == BluetoothA2dp.STATE_PLAYING) &&
    150                            newState == BluetoothA2dp.STATE_DISCONNECTED) {
    151                     sendMessage(DISCONNECT_A2DP_INCOMING);
    152                 }
    153                 if (newState == BluetoothA2dp.STATE_CONNECTED ||
    154                     newState == BluetoothA2dp.STATE_DISCONNECTED) {
    155                     sendMessage(TRANSITION_TO_STABLE);
    156                 }
    157             } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
    158                 // This is technically not needed, but we can get stuck sometimes.
    159                 // For example, if incoming A2DP fails, we are not informed by Bluez
    160                 sendMessage(TRANSITION_TO_STABLE);
    161             } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
    162                 mWakeLock.release();
    163                 int val = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
    164                                              BluetoothDevice.CONNECTION_ACCESS_NO);
    165                 Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_REPLY);
    166                 msg.arg1 = val;
    167                 sendMessage(msg);
    168             }
    169       }
    170     };
    171 
    172     private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) {
    173       // This works only because these broadcast intents are "sticky"
    174       Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
    175       if (i != null) {
    176           int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
    177           if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
    178               BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    179               if (device != null && autoConnectDevice.equals(device)) {
    180                   return true;
    181               }
    182           }
    183       }
    184       return false;
    185   }
    186 
    187     public BluetoothDeviceProfileState(Context context, String address,
    188           BluetoothService service, BluetoothA2dpService a2dpService) {
    189         super(address);
    190         mContext = context;
    191         mDevice = new BluetoothDevice(address);
    192         mService = service;
    193         mA2dpService = a2dpService;
    194 
    195         addState(mBondedDevice);
    196         addState(mOutgoingHandsfree);
    197         addState(mIncomingHandsfree);
    198         addState(mIncomingA2dp);
    199         addState(mOutgoingA2dp);
    200         setInitialState(mBondedDevice);
    201 
    202         IntentFilter filter = new IntentFilter();
    203         // Fine-grained state broadcasts
    204         filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
    205         filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
    206         filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
    207         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
    208         filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
    209 
    210         mContext.registerReceiver(mBroadcastReceiver, filter);
    211 
    212         HeadsetServiceListener l = new HeadsetServiceListener();
    213         PbapServiceListener p = new PbapServiceListener();
    214 
    215         mIncomingConnections = mService.getIncomingState(address);
    216         mIncomingRejectTimer = readTimerValue();
    217         mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
    218         mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
    219                                               PowerManager.ACQUIRE_CAUSES_WAKEUP |
    220                                               PowerManager.ON_AFTER_RELEASE, TAG);
    221         mWakeLock.setReferenceCounted(false);
    222     }
    223 
    224     private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener {
    225         public HeadsetServiceListener() {
    226             mHeadsetService = new BluetoothHeadset(mContext, this);
    227         }
    228         public void onServiceConnected() {
    229             synchronized(BluetoothDeviceProfileState.this) {
    230                 mHeadsetServiceConnected = true;
    231             }
    232         }
    233         public void onServiceDisconnected() {
    234             synchronized(BluetoothDeviceProfileState.this) {
    235                 mHeadsetServiceConnected = false;
    236             }
    237         }
    238     }
    239 
    240     private class PbapServiceListener implements BluetoothPbap.ServiceListener {
    241         public PbapServiceListener() {
    242             mPbapService = new BluetoothPbap(mContext, this);
    243         }
    244         public void onServiceConnected() {
    245             synchronized(BluetoothDeviceProfileState.this) {
    246                 mPbapServiceConnected = true;
    247             }
    248         }
    249         public void onServiceDisconnected() {
    250             synchronized(BluetoothDeviceProfileState.this) {
    251                 mPbapServiceConnected = false;
    252             }
    253         }
    254     }
    255 
    256     private class BondedDevice extends HierarchicalState {
    257         @Override
    258         protected void enter() {
    259             Log.i(TAG, "Entering ACL Connected state with: " + getCurrentMessage().what);
    260             Message m = new Message();
    261             m.copyFrom(getCurrentMessage());
    262             sendMessageAtFrontOfQueue(m);
    263         }
    264         @Override
    265         protected boolean processMessage(Message message) {
    266             log("ACL Connected State -> Processing Message: " + message.what);
    267             switch(message.what) {
    268                 case CONNECT_HFP_OUTGOING:
    269                 case DISCONNECT_HFP_OUTGOING:
    270                     transitionTo(mOutgoingHandsfree);
    271                     break;
    272                 case CONNECT_HFP_INCOMING:
    273                     transitionTo(mIncomingHandsfree);
    274                     break;
    275                 case DISCONNECT_HFP_INCOMING:
    276                     transitionTo(mIncomingHandsfree);
    277                     break;
    278                 case CONNECT_A2DP_OUTGOING:
    279                 case DISCONNECT_A2DP_OUTGOING:
    280                     transitionTo(mOutgoingA2dp);
    281                     break;
    282                 case CONNECT_A2DP_INCOMING:
    283                 case DISCONNECT_A2DP_INCOMING:
    284                     transitionTo(mIncomingA2dp);
    285                     break;
    286                 case DISCONNECT_PBAP_OUTGOING:
    287                     processCommand(DISCONNECT_PBAP_OUTGOING);
    288                     break;
    289                 case UNPAIR:
    290                     if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
    291                         sendMessage(DISCONNECT_HFP_OUTGOING);
    292                         deferMessage(message);
    293                         break;
    294                     } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) {
    295                         sendMessage(DISCONNECT_A2DP_OUTGOING);
    296                         deferMessage(message);
    297                         break;
    298                     }
    299                     processCommand(UNPAIR);
    300                     break;
    301                 case AUTO_CONNECT_PROFILES:
    302                     if (isPhoneDocked(mDevice)) {
    303                         // Don't auto connect to docks.
    304                         break;
    305                     } else if (!mHeadsetServiceConnected) {
    306                         deferMessage(message);
    307                     } else {
    308                         if (mHeadsetService.getPriority(mDevice) ==
    309                               BluetoothHeadset.PRIORITY_AUTO_CONNECT &&
    310                               !mHeadsetService.isConnected(mDevice)) {
    311                             Log.i(TAG, "Headset:Auto Connect Profiles");
    312                             mHeadsetService.connectHeadset(mDevice);
    313                         }
    314                         if (mA2dpService != null &&
    315                               mA2dpService.getSinkPriority(mDevice) ==
    316                               BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
    317                               mA2dpService.getConnectedSinks().length == 0) {
    318                             Log.i(TAG, "A2dp:Auto Connect Profiles");
    319                             mA2dpService.connectSink(mDevice);
    320                         }
    321                     }
    322                     break;
    323                 case CONNECT_OTHER_PROFILES:
    324                     if (isPhoneDocked(mDevice)) {
    325                        break;
    326                     }
    327                     if (message.arg1 == CONNECT_A2DP_OUTGOING) {
    328                         if (mA2dpService != null &&
    329                             mA2dpService.getConnectedSinks().length == 0) {
    330                             Log.i(TAG, "A2dp:Connect Other Profiles");
    331                             mA2dpService.connectSink(mDevice);
    332                         }
    333                     } else if (message.arg1 == CONNECT_HFP_OUTGOING) {
    334                         if (!mHeadsetServiceConnected) {
    335                             deferMessage(message);
    336                         } else {
    337                             if (!mHeadsetService.isConnected(mDevice)) {
    338                                 Log.i(TAG, "Headset:Connect Other Profiles");
    339                                 mHeadsetService.connectHeadset(mDevice);
    340                             }
    341                         }
    342                     }
    343                     break;
    344                 case TRANSITION_TO_STABLE:
    345                     // ignore.
    346                     break;
    347                 default:
    348                     return NOT_HANDLED;
    349             }
    350             return HANDLED;
    351         }
    352     }
    353 
    354     private class OutgoingHandsfree extends HierarchicalState {
    355         private boolean mStatus = false;
    356         private int mCommand;
    357 
    358         @Override
    359         protected void enter() {
    360             Log.i(TAG, "Entering OutgoingHandsfree state with: " + getCurrentMessage().what);
    361             mCommand = getCurrentMessage().what;
    362             if (mCommand != CONNECT_HFP_OUTGOING &&
    363                 mCommand != DISCONNECT_HFP_OUTGOING) {
    364                 Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand);
    365             }
    366             mStatus = processCommand(mCommand);
    367             if (!mStatus) {
    368                 sendMessage(TRANSITION_TO_STABLE);
    369                 mService.sendProfileStateMessage(BluetoothProfileState.HFP,
    370                                                  BluetoothProfileState.TRANSITION_TO_STABLE);
    371             }
    372         }
    373 
    374         @Override
    375         protected boolean processMessage(Message message) {
    376             log("OutgoingHandsfree State -> Processing Message: " + message.what);
    377             Message deferMsg = new Message();
    378             int command = message.what;
    379             switch(command) {
    380                 case CONNECT_HFP_OUTGOING:
    381                     if (command != mCommand) {
    382                         // Disconnect followed by a connect - defer
    383                         deferMessage(message);
    384                     }
    385                     break;
    386                 case CONNECT_HFP_INCOMING:
    387                     if (mCommand == CONNECT_HFP_OUTGOING) {
    388                         // Cancel outgoing connect, accept incoming
    389                         cancelCommand(CONNECT_HFP_OUTGOING);
    390                         transitionTo(mIncomingHandsfree);
    391                     } else {
    392                         // We have done the disconnect but we are not
    393                         // sure which state we are in at this point.
    394                         deferMessage(message);
    395                     }
    396                     break;
    397                 case CONNECT_A2DP_INCOMING:
    398                     // accept incoming A2DP, retry HFP_OUTGOING
    399                     transitionTo(mIncomingA2dp);
    400 
    401                     if (mStatus) {
    402                         deferMsg.what = mCommand;
    403                         deferMessage(deferMsg);
    404                     }
    405                     break;
    406                 case CONNECT_A2DP_OUTGOING:
    407                     deferMessage(message);
    408                     break;
    409                 case DISCONNECT_HFP_OUTGOING:
    410                     if (mCommand == CONNECT_HFP_OUTGOING) {
    411                         // Cancel outgoing connect
    412                         cancelCommand(CONNECT_HFP_OUTGOING);
    413                         processCommand(DISCONNECT_HFP_OUTGOING);
    414                     }
    415                     // else ignore
    416                     break;
    417                 case DISCONNECT_HFP_INCOMING:
    418                     // When this happens the socket would be closed and the headset
    419                     // state moved to DISCONNECTED, cancel the outgoing thread.
    420                     // if it still is in CONNECTING state
    421                     cancelCommand(CONNECT_HFP_OUTGOING);
    422                     break;
    423                 case DISCONNECT_A2DP_OUTGOING:
    424                     deferMessage(message);
    425                     break;
    426                 case DISCONNECT_A2DP_INCOMING:
    427                     // Bluez will handle the disconnect. If because of this the outgoing
    428                     // handsfree connection has failed, then retry.
    429                     if (mStatus) {
    430                        deferMsg.what = mCommand;
    431                        deferMessage(deferMsg);
    432                     }
    433                     break;
    434                 case DISCONNECT_PBAP_OUTGOING:
    435                 case UNPAIR:
    436                 case AUTO_CONNECT_PROFILES:
    437                 case CONNECT_OTHER_PROFILES:
    438                     deferMessage(message);
    439                     break;
    440                 case TRANSITION_TO_STABLE:
    441                     transitionTo(mBondedDevice);
    442                     break;
    443                 default:
    444                     return NOT_HANDLED;
    445             }
    446             return HANDLED;
    447         }
    448     }
    449 
    450     private class IncomingHandsfree extends HierarchicalState {
    451         private boolean mStatus = false;
    452         private int mCommand;
    453 
    454         @Override
    455         protected void enter() {
    456             Log.i(TAG, "Entering IncomingHandsfree state with: " + getCurrentMessage().what);
    457             mCommand = getCurrentMessage().what;
    458             if (mCommand != CONNECT_HFP_INCOMING &&
    459                 mCommand != DISCONNECT_HFP_INCOMING) {
    460                 Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand);
    461             }
    462             mStatus = processCommand(mCommand);
    463             if (!mStatus) {
    464                 sendMessage(TRANSITION_TO_STABLE);
    465                 mService.sendProfileStateMessage(BluetoothProfileState.HFP,
    466                                                  BluetoothProfileState.TRANSITION_TO_STABLE);
    467             }
    468         }
    469 
    470         @Override
    471         protected boolean processMessage(Message message) {
    472             log("IncomingHandsfree State -> Processing Message: " + message.what);
    473             switch(message.what) {
    474                 case CONNECT_HFP_OUTGOING:
    475                     deferMessage(message);
    476                     break;
    477                 case CONNECT_HFP_INCOMING:
    478                     // Ignore
    479                     Log.e(TAG, "Error: Incoming connection with a pending incoming connection");
    480                     break;
    481                 case CONNECTION_ACCESS_REQUEST_REPLY:
    482                     int val = message.arg1;
    483                     mConnectionAccessReplyReceived = true;
    484                     boolean value = false;
    485                     if (val == BluetoothDevice.CONNECTION_ACCESS_YES) {
    486                         value = true;
    487                     }
    488                     setTrust(val);
    489 
    490                     handleIncomingConnection(CONNECT_HFP_INCOMING, value);
    491                     break;
    492                 case CONNECTION_ACCESS_REQUEST_EXPIRY:
    493                     if (!mConnectionAccessReplyReceived) {
    494                         handleIncomingConnection(CONNECT_HFP_INCOMING, false);
    495                         sendConnectionAccessRemovalIntent();
    496                         sendMessage(TRANSITION_TO_STABLE);
    497                     }
    498                     break;
    499                 case CONNECT_A2DP_INCOMING:
    500                     // Serialize the commands.
    501                     deferMessage(message);
    502                     break;
    503                 case CONNECT_A2DP_OUTGOING:
    504                     deferMessage(message);
    505                     break;
    506                 case DISCONNECT_HFP_OUTGOING:
    507                     // We don't know at what state we are in the incoming HFP connection state.
    508                     // We can be changing from DISCONNECTED to CONNECTING, or
    509                     // from CONNECTING to CONNECTED, so serializing this command is
    510                     // the safest option.
    511                     deferMessage(message);
    512                     break;
    513                 case DISCONNECT_HFP_INCOMING:
    514                     // Nothing to do here, we will already be DISCONNECTED
    515                     // by this point.
    516                     break;
    517                 case DISCONNECT_A2DP_OUTGOING:
    518                     deferMessage(message);
    519                     break;
    520                 case DISCONNECT_A2DP_INCOMING:
    521                     // Bluez handles incoming A2DP disconnect.
    522                     // If this causes incoming HFP to fail, it is more of a headset problem
    523                     // since both connections are incoming ones.
    524                     break;
    525                 case DISCONNECT_PBAP_OUTGOING:
    526                 case UNPAIR:
    527                 case AUTO_CONNECT_PROFILES:
    528                 case CONNECT_OTHER_PROFILES:
    529                     deferMessage(message);
    530                     break;
    531                 case TRANSITION_TO_STABLE:
    532                     transitionTo(mBondedDevice);
    533                     break;
    534                 default:
    535                     return NOT_HANDLED;
    536             }
    537             return HANDLED;
    538         }
    539     }
    540 
    541     private class OutgoingA2dp extends HierarchicalState {
    542         private boolean mStatus = false;
    543         private int mCommand;
    544 
    545         @Override
    546         protected void enter() {
    547             Log.i(TAG, "Entering OutgoingA2dp state with: " + getCurrentMessage().what);
    548             mCommand = getCurrentMessage().what;
    549             if (mCommand != CONNECT_A2DP_OUTGOING &&
    550                 mCommand != DISCONNECT_A2DP_OUTGOING) {
    551                 Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand);
    552             }
    553             mStatus = processCommand(mCommand);
    554             if (!mStatus) {
    555                 sendMessage(TRANSITION_TO_STABLE);
    556                 mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
    557                                                  BluetoothProfileState.TRANSITION_TO_STABLE);
    558             }
    559         }
    560 
    561         @Override
    562         protected boolean processMessage(Message message) {
    563             log("OutgoingA2dp State->Processing Message: " + message.what);
    564             Message deferMsg = new Message();
    565             switch(message.what) {
    566                 case CONNECT_HFP_OUTGOING:
    567                     processCommand(CONNECT_HFP_OUTGOING);
    568 
    569                     // Don't cancel A2DP outgoing as there is no guarantee it
    570                     // will get canceled.
    571                     // It might already be connected but we might not have got the
    572                     // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here.
    573                     // The worst case, the connection will fail, retry.
    574                     // The same applies to Disconnecting an A2DP connection.
    575                     if (mStatus) {
    576                         deferMsg.what = mCommand;
    577                         deferMessage(deferMsg);
    578                     }
    579                     break;
    580                 case CONNECT_HFP_INCOMING:
    581                     processCommand(CONNECT_HFP_INCOMING);
    582 
    583                     // Don't cancel A2DP outgoing as there is no guarantee
    584                     // it will get canceled.
    585                     // The worst case, the connection will fail, retry.
    586                     if (mStatus) {
    587                         deferMsg.what = mCommand;
    588                         deferMessage(deferMsg);
    589                     }
    590                     break;
    591                 case CONNECT_A2DP_INCOMING:
    592                     // Bluez will take care of conflicts between incoming and outgoing
    593                     // connections.
    594                     transitionTo(mIncomingA2dp);
    595                     break;
    596                 case CONNECT_A2DP_OUTGOING:
    597                     // Ignore
    598                     break;
    599                 case DISCONNECT_HFP_OUTGOING:
    600                     deferMessage(message);
    601                     break;
    602                 case DISCONNECT_HFP_INCOMING:
    603                     // At this point, we are already disconnected
    604                     // with HFP. Sometimes A2DP connection can
    605                     // fail due to the disconnection of HFP. So add a retry
    606                     // for the A2DP.
    607                     if (mStatus) {
    608                         deferMsg.what = mCommand;
    609                         deferMessage(deferMsg);
    610                     }
    611                     break;
    612                 case DISCONNECT_A2DP_OUTGOING:
    613                     deferMessage(message);
    614                     break;
    615                 case DISCONNECT_A2DP_INCOMING:
    616                     // Ignore, will be handled by Bluez
    617                     break;
    618                 case DISCONNECT_PBAP_OUTGOING:
    619                 case UNPAIR:
    620                 case AUTO_CONNECT_PROFILES:
    621                 case CONNECT_OTHER_PROFILES:
    622                     deferMessage(message);
    623                     break;
    624                 case TRANSITION_TO_STABLE:
    625                     transitionTo(mBondedDevice);
    626                     break;
    627                 default:
    628                     return NOT_HANDLED;
    629             }
    630             return HANDLED;
    631         }
    632     }
    633 
    634     private class IncomingA2dp extends HierarchicalState {
    635         private boolean mStatus = false;
    636         private int mCommand;
    637 
    638         @Override
    639         protected void enter() {
    640             Log.i(TAG, "Entering IncomingA2dp state with: " + getCurrentMessage().what);
    641             mCommand = getCurrentMessage().what;
    642             if (mCommand != CONNECT_A2DP_INCOMING &&
    643                 mCommand != DISCONNECT_A2DP_INCOMING) {
    644                 Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand);
    645             }
    646             mStatus = processCommand(mCommand);
    647             if (!mStatus) {
    648                 sendMessage(TRANSITION_TO_STABLE);
    649                 mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
    650                                                  BluetoothProfileState.TRANSITION_TO_STABLE);
    651             }
    652         }
    653 
    654         @Override
    655         protected boolean processMessage(Message message) {
    656             log("IncomingA2dp State->Processing Message: " + message.what);
    657             Message deferMsg = new Message();
    658             switch(message.what) {
    659                 case CONNECT_HFP_OUTGOING:
    660                     deferMessage(message);
    661                     break;
    662                 case CONNECT_HFP_INCOMING:
    663                     // Shouldn't happen, but serialize the commands.
    664                     deferMessage(message);
    665                     break;
    666                 case CONNECT_A2DP_INCOMING:
    667                     // ignore
    668                     break;
    669                 case CONNECTION_ACCESS_REQUEST_REPLY:
    670                     int val = message.arg1;
    671                     mConnectionAccessReplyReceived = true;
    672                     boolean value = false;
    673                     if (val == BluetoothDevice.CONNECTION_ACCESS_YES) {
    674                         value = true;
    675                     }
    676                     setTrust(val);
    677                     handleIncomingConnection(CONNECT_A2DP_INCOMING, value);
    678                     break;
    679                 case CONNECTION_ACCESS_REQUEST_EXPIRY:
    680                     // The check protects the race condition between REQUEST_REPLY
    681                     // and the timer expiry.
    682                     if (!mConnectionAccessReplyReceived) {
    683                         handleIncomingConnection(CONNECT_A2DP_INCOMING, false);
    684                         sendConnectionAccessRemovalIntent();
    685                         sendMessage(TRANSITION_TO_STABLE);
    686                     }
    687                     break;
    688                 case CONNECT_A2DP_OUTGOING:
    689                     // Defer message and retry
    690                     deferMessage(message);
    691                     break;
    692                 case DISCONNECT_HFP_OUTGOING:
    693                     deferMessage(message);
    694                     break;
    695                 case DISCONNECT_HFP_INCOMING:
    696                     // Shouldn't happen but if does, we can handle it.
    697                     // Depends if the headset can handle it.
    698                     // Incoming A2DP will be handled by Bluez, Disconnect HFP
    699                     // the socket would have already been closed.
    700                     // ignore
    701                     break;
    702                 case DISCONNECT_A2DP_OUTGOING:
    703                     deferMessage(message);
    704                     break;
    705                 case DISCONNECT_A2DP_INCOMING:
    706                     // Ignore, will be handled by Bluez
    707                     break;
    708                 case DISCONNECT_PBAP_OUTGOING:
    709                 case UNPAIR:
    710                 case AUTO_CONNECT_PROFILES:
    711                 case CONNECT_OTHER_PROFILES:
    712                     deferMessage(message);
    713                     break;
    714                 case TRANSITION_TO_STABLE:
    715                     transitionTo(mBondedDevice);
    716                     break;
    717                 default:
    718                     return NOT_HANDLED;
    719             }
    720             return HANDLED;
    721         }
    722     }
    723 
    724 
    725 
    726     synchronized void cancelCommand(int command) {
    727         if (command == CONNECT_HFP_OUTGOING ) {
    728             // Cancel the outgoing thread.
    729             if (mHeadsetServiceConnected) {
    730                 mHeadsetService.cancelConnectThread();
    731             }
    732             // HeadsetService is down. Phone process most likely crashed.
    733             // The thread would have got killed.
    734         }
    735     }
    736 
    737     synchronized void deferProfileServiceMessage(int command) {
    738         Message msg = new Message();
    739         msg.what = command;
    740         deferMessage(msg);
    741     }
    742 
    743     private void updateIncomingAllowedTimer() {
    744         // Not doing a perfect exponential backoff because
    745         // we want two different rates. For all practical
    746         // purposes, this is good enough.
    747         if (mIncomingRejectTimer == 0) mIncomingRejectTimer = INIT_INCOMING_REJECT_TIMER;
    748 
    749         mIncomingRejectTimer *= 5;
    750         if (mIncomingRejectTimer > MAX_INCOMING_REJECT_TIMER) {
    751             mIncomingRejectTimer = MAX_INCOMING_REJECT_TIMER;
    752         }
    753         writeTimerValue(mIncomingRejectTimer);
    754     }
    755 
    756     private boolean handleIncomingConnection(int command, boolean accept) {
    757         boolean ret = false;
    758         Log.i(TAG, "handleIncomingConnection:" + command + ":" + accept);
    759         switch (command) {
    760             case CONNECT_HFP_INCOMING:
    761                 if (!accept) {
    762                     ret = mHeadsetService.rejectIncomingConnect(mDevice);
    763                     sendMessage(TRANSITION_TO_STABLE);
    764                     updateIncomingAllowedTimer();
    765                 } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
    766                     writeTimerValue(0);
    767                     ret =  mHeadsetService.acceptIncomingConnect(mDevice);
    768                 } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
    769                     writeTimerValue(0);
    770                     handleConnectionOfOtherProfiles(command);
    771                     ret = mHeadsetService.createIncomingConnect(mDevice);
    772                 }
    773                 break;
    774             case CONNECT_A2DP_INCOMING:
    775                 if (!accept) {
    776                     ret = mA2dpService.allowIncomingConnect(mDevice, false);
    777                     sendMessage(TRANSITION_TO_STABLE);
    778                     updateIncomingAllowedTimer();
    779                 } else {
    780                     writeTimerValue(0);
    781                     ret = mA2dpService.allowIncomingConnect(mDevice, true);
    782                     handleConnectionOfOtherProfiles(command);
    783                 }
    784                 break;
    785             default:
    786                 Log.e(TAG, "Waiting for incoming connection but state changed to:" + command);
    787                 break;
    788        }
    789        return ret;
    790     }
    791 
    792     private void sendConnectionAccessIntent() {
    793         mConnectionAccessReplyReceived = false;
    794 
    795         if (!mPowerManager.isScreenOn()) mWakeLock.acquire();
    796 
    797         Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
    798         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
    799         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    800     }
    801 
    802     private void sendConnectionAccessRemovalIntent() {
    803         mWakeLock.release();
    804         Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
    805         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
    806         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    807     }
    808 
    809     private int getTrust() {
    810         String address = mDevice.getAddress();
    811         if (mIncomingConnections != null) return mIncomingConnections.first;
    812         return CONNECTION_ACCESS_UNDEFINED;
    813     }
    814 
    815 
    816     private String getStringValue(long value) {
    817         StringBuilder sbr = new StringBuilder();
    818         sbr.append(Long.toString(System.currentTimeMillis()));
    819         sbr.append("-");
    820         sbr.append(Long.toString(value));
    821         return sbr.toString();
    822     }
    823 
    824     private void setTrust(int value) {
    825         String second;
    826         if (mIncomingConnections == null) {
    827             second = getStringValue(INIT_INCOMING_REJECT_TIMER);
    828         } else {
    829             second = mIncomingConnections.second;
    830         }
    831 
    832         mIncomingConnections = new Pair(value, second);
    833         mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections);
    834     }
    835 
    836     private void writeTimerValue(long value) {
    837         Integer first;
    838         if (mIncomingConnections == null) {
    839             first = CONNECTION_ACCESS_UNDEFINED;
    840         } else {
    841             first = mIncomingConnections.first;
    842         }
    843         mIncomingConnections = new Pair(first, getStringValue(value));
    844         mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections);
    845     }
    846 
    847     private long readTimerValue() {
    848         if (mIncomingConnections == null)
    849             return 0;
    850         String value = mIncomingConnections.second;
    851         String[] splits = value.split("-");
    852         if (splits != null && splits.length == 2) {
    853             return Long.parseLong(splits[1]);
    854         }
    855         return 0;
    856     }
    857 
    858     private boolean readIncomingAllowedValue() {
    859         if (readTimerValue() == 0) return true;
    860         String value = mIncomingConnections.second;
    861         String[] splits = value.split("-");
    862         if (splits != null && splits.length == 2) {
    863             long val1 = Long.parseLong(splits[0]);
    864             long val2 = Long.parseLong(splits[1]);
    865             if (val1 + val2 <= System.currentTimeMillis()) {
    866                 return true;
    867             }
    868         }
    869         return false;
    870     }
    871 
    872     synchronized boolean processCommand(int command) {
    873         Log.e(TAG, "Processing command:" + command);
    874         Message msg;
    875         switch(command) {
    876             case  CONNECT_HFP_OUTGOING:
    877                 if (mHeadsetService != null) {
    878                     return mHeadsetService.connectHeadsetInternal(mDevice);
    879                 }
    880                 break;
    881             case CONNECT_HFP_INCOMING:
    882                 if (!mHeadsetServiceConnected) {
    883                     deferProfileServiceMessage(command);
    884                 } else {
    885                     // Check if device is already trusted
    886                     int access = getTrust();
    887                     if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
    888                         handleIncomingConnection(command, true);
    889                     } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO &&
    890                                !readIncomingAllowedValue()) {
    891                         handleIncomingConnection(command, false);
    892                     } else {
    893                         sendConnectionAccessIntent();
    894                         msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY);
    895                         sendMessageDelayed(msg,
    896                                 CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT);
    897                     }
    898                     return true;
    899                 }
    900                 break;
    901             case CONNECT_A2DP_OUTGOING:
    902                 if (mA2dpService != null) {
    903                     return mA2dpService.connectSinkInternal(mDevice);
    904                 }
    905                 break;
    906             case CONNECT_A2DP_INCOMING:
    907                 // Check if device is already trusted
    908                 int access = getTrust();
    909                 if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
    910                     handleIncomingConnection(command, true);
    911                 } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO &&
    912                            !readIncomingAllowedValue()) {
    913                     handleIncomingConnection(command, false);
    914                 } else {
    915                     sendConnectionAccessIntent();
    916                     msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY);
    917                     sendMessageDelayed(msg,
    918                             CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT);
    919                 }
    920                 return true;
    921             case DISCONNECT_HFP_OUTGOING:
    922                 if (!mHeadsetServiceConnected) {
    923                     deferProfileServiceMessage(command);
    924                 } else {
    925                     // Disconnect PBAP
    926                     // TODO(): Add PBAP to the state machine.
    927                     Message m = new Message();
    928                     m.what = DISCONNECT_PBAP_OUTGOING;
    929                     deferMessage(m);
    930                     if (mHeadsetService.getPriority(mDevice) ==
    931                         BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
    932                         mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
    933                     }
    934                     return mHeadsetService.disconnectHeadsetInternal(mDevice);
    935                 }
    936                 break;
    937             case DISCONNECT_HFP_INCOMING:
    938                 // ignore
    939                 return true;
    940             case DISCONNECT_A2DP_INCOMING:
    941                 // ignore
    942                 return true;
    943             case DISCONNECT_A2DP_OUTGOING:
    944                 if (mA2dpService != null) {
    945                     if (mA2dpService.getSinkPriority(mDevice) ==
    946                         BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
    947                         mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
    948                     }
    949                     return mA2dpService.disconnectSinkInternal(mDevice);
    950                 }
    951                 break;
    952             case DISCONNECT_PBAP_OUTGOING:
    953                 if (!mPbapServiceConnected) {
    954                     deferProfileServiceMessage(command);
    955                 } else {
    956                     return mPbapService.disconnect();
    957                 }
    958                 break;
    959             case UNPAIR:
    960                 writeTimerValue(INIT_INCOMING_REJECT_TIMER);
    961                 setTrust(CONNECTION_ACCESS_UNDEFINED);
    962                 return mService.removeBondInternal(mDevice.getAddress());
    963             default:
    964                 Log.e(TAG, "Error: Unknown Command");
    965         }
    966         return false;
    967     }
    968 
    969     private void handleConnectionOfOtherProfiles(int command) {
    970         // The white paper recommendations mentions that when there is a
    971         // link loss, it is the responsibility of the remote device to connect.
    972         // Many connect only 1 profile - and they connect the second profile on
    973         // some user action (like play being pressed) and so we need this code.
    974         // Auto Connect code only connects to the last connected device - which
    975         // is useful in cases like when the phone reboots. But consider the
    976         // following case:
    977         // User is connected to the car's phone and  A2DP profile.
    978         // User comes to the desk  and places the phone in the dock
    979         // (or any speaker or music system or even another headset) and thus
    980         // gets connected to the A2DP profile.  User goes back to the car.
    981         // Ideally the car's system is supposed to send incoming connections
    982         // from both Handsfree and A2DP profile. But they don't. The Auto
    983         // connect code, will not work here because we only auto connect to the
    984         // last connected device for that profile which in this case is the dock.
    985         // Now suppose a user is using 2 headsets simultaneously, one for the
    986         // phone profile one for the A2DP profile. If this is the use case, we
    987         // expect the user to use the preference to turn off the A2DP profile in
    988         // the Settings screen for the first headset. Else, after link loss,
    989         // there can be an incoming connection from the first headset which
    990         // might result in the connection of the A2DP profile (if the second
    991         // headset is slower) and thus the A2DP profile on the second headset
    992         // will never get connected.
    993         //
    994         // TODO(): Handle other profiles here.
    995         switch (command) {
    996             case CONNECT_HFP_INCOMING:
    997                 // Connect A2DP if there is no incoming connection
    998                 // If the priority is OFF - don't auto connect.
    999                 if (mA2dpService.getSinkPriority(mDevice) == BluetoothA2dp.PRIORITY_ON ||
   1000                         mA2dpService.getSinkPriority(mDevice) ==
   1001                             BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
   1002                     Message msg = new Message();
   1003                     msg.what = CONNECT_OTHER_PROFILES;
   1004                     msg.arg1 = CONNECT_A2DP_OUTGOING;
   1005                     sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY);
   1006                 }
   1007                 break;
   1008             case CONNECT_A2DP_INCOMING:
   1009                 // This is again against spec. HFP incoming connections should be made
   1010                 // before A2DP, so we should not hit this case. But many devices
   1011                 // don't follow this.
   1012                 if (mHeadsetService.getPriority(mDevice) == BluetoothHeadset.PRIORITY_ON
   1013                         || mHeadsetService.getPriority(mDevice) ==
   1014                             BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
   1015                     Message msg = new Message();
   1016                     msg.what = CONNECT_OTHER_PROFILES;
   1017                     msg.arg1 = CONNECT_HFP_OUTGOING;
   1018                     sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY);
   1019                 }
   1020                 break;
   1021             default:
   1022                 break;
   1023         }
   1024 
   1025     }
   1026 
   1027     /*package*/ BluetoothDevice getDevice() {
   1028         return mDevice;
   1029     }
   1030 
   1031     private void log(String message) {
   1032         if (DBG) {
   1033             Log.i(TAG, "Device:" + mDevice + " Message:" + message);
   1034         }
   1035     }
   1036 }
   1037