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