Home | History | Annotate | Download | only in btservice
      1 /*
      2  * Copyright (C) 2012 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 com.android.bluetooth.btservice;
     18 
     19 import android.bluetooth.BluetoothAdapter;
     20 import android.bluetooth.BluetoothProfile;
     21 import android.bluetooth.BluetoothDevice;
     22 import com.android.bluetooth.a2dp.A2dpService;
     23 import com.android.bluetooth.hid.HidService;
     24 import com.android.bluetooth.hfp.HeadsetService;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.os.Message;
     28 import android.os.UserHandle;
     29 import android.util.Log;
     30 
     31 import com.android.bluetooth.Utils;
     32 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
     33 import com.android.internal.util.State;
     34 import com.android.internal.util.StateMachine;
     35 
     36 import java.util.ArrayList;
     37 
     38 /**
     39  * This state machine handles Bluetooth Adapter State.
     40  * States:
     41  *      {@link StableState} :  No device is in bonding / unbonding state.
     42  *      {@link PendingCommandState} : Some device is in bonding / unbonding state.
     43  * TODO(BT) This class can be removed and this logic moved to the stack.
     44  */
     45 
     46 final class BondStateMachine extends StateMachine {
     47     private static final boolean DBG = false;
     48     private static final String TAG = "BluetoothBondStateMachine";
     49 
     50     static final int CREATE_BOND = 1;
     51     static final int CANCEL_BOND = 2;
     52     static final int REMOVE_BOND = 3;
     53     static final int BONDING_STATE_CHANGE = 4;
     54 
     55     static final int BOND_STATE_NONE = 0;
     56     static final int BOND_STATE_BONDING = 1;
     57     static final int BOND_STATE_BONDED = 2;
     58 
     59     private AdapterService mAdapterService;
     60     private AdapterProperties mAdapterProperties;
     61     private RemoteDevices mRemoteDevices;
     62     private BluetoothAdapter mAdapter;
     63 
     64     private PendingCommandState mPendingCommandState = new PendingCommandState();
     65     private StableState mStableState = new StableState();
     66 
     67     private BondStateMachine(AdapterService service,
     68             AdapterProperties prop, RemoteDevices remoteDevices) {
     69         super("BondStateMachine:");
     70         addState(mStableState);
     71         addState(mPendingCommandState);
     72         mRemoteDevices = remoteDevices;
     73         mAdapterService = service;
     74         mAdapterProperties = prop;
     75         mAdapter = BluetoothAdapter.getDefaultAdapter();
     76         setInitialState(mStableState);
     77     }
     78 
     79     public static BondStateMachine make(AdapterService service,
     80             AdapterProperties prop, RemoteDevices remoteDevices) {
     81         Log.d(TAG, "make");
     82         BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices);
     83         bsm.start();
     84         return bsm;
     85     }
     86 
     87     public void doQuit() {
     88         quitNow();
     89     }
     90 
     91     public void cleanup() {
     92         mAdapterService = null;
     93         mRemoteDevices = null;
     94         mAdapterProperties = null;
     95     }
     96 
     97     private class StableState extends State {
     98         @Override
     99         public void enter() {
    100             infoLog("StableState(): Entering Off State");
    101         }
    102 
    103         @Override
    104         public boolean processMessage(Message msg) {
    105 
    106             BluetoothDevice dev = (BluetoothDevice)msg.obj;
    107 
    108             switch(msg.what) {
    109 
    110               case CREATE_BOND:
    111                   createBond(dev, true);
    112                   break;
    113               case REMOVE_BOND:
    114                   removeBond(dev, true);
    115                   break;
    116               case BONDING_STATE_CHANGE:
    117                 int newState = msg.arg1;
    118                 /* if incoming pairing, transition to pending state */
    119                 if (newState == BluetoothDevice.BOND_BONDING)
    120                 {
    121                     sendIntent(dev, newState, 0);
    122                     transitionTo(mPendingCommandState);
    123                 }
    124                 else
    125                 {
    126                     Log.e(TAG, "In stable state, received invalid newState: " + newState);
    127                 }
    128                 break;
    129 
    130               case CANCEL_BOND:
    131               default:
    132                    Log.e(TAG, "Received unhandled state: " + msg.what);
    133                    return false;
    134             }
    135             return true;
    136         }
    137     }
    138 
    139 
    140     private class PendingCommandState extends State {
    141         private final ArrayList<BluetoothDevice> mDevices =
    142             new ArrayList<BluetoothDevice>();
    143 
    144         @Override
    145         public void enter() {
    146             infoLog("Entering PendingCommandState State");
    147             BluetoothDevice dev = (BluetoothDevice)getCurrentMessage().obj;
    148         }
    149 
    150         @Override
    151         public boolean processMessage(Message msg) {
    152 
    153             BluetoothDevice dev = (BluetoothDevice)msg.obj;
    154             boolean result = false;
    155             if (mDevices.contains(dev) &&
    156                     msg.what != CANCEL_BOND && msg.what != BONDING_STATE_CHANGE) {
    157                 deferMessage(msg);
    158                 return true;
    159             }
    160 
    161             switch (msg.what) {
    162                 case CREATE_BOND:
    163                     result = createBond(dev, false);
    164                     break;
    165                 case REMOVE_BOND:
    166                     result = removeBond(dev, false);
    167                     break;
    168                 case CANCEL_BOND:
    169                     result = cancelBond(dev);
    170                     break;
    171                 case BONDING_STATE_CHANGE:
    172                     int newState = msg.arg1;
    173                     int reason = getUnbondReasonFromHALCode(msg.arg2);
    174                     sendIntent(dev, newState, reason);
    175                     if(newState != BluetoothDevice.BOND_BONDING )
    176                     {
    177                         /* this is either none/bonded, remove and transition */
    178                         result = !mDevices.remove(dev);
    179                         if (mDevices.isEmpty()) {
    180                             // Whenever mDevices is empty, then we need to
    181                             // set result=false. Else, we will end up adding
    182                             // the device to the list again. This prevents us
    183                             // from pairing with a device that we just unpaired
    184                             result = false;
    185                             transitionTo(mStableState);
    186                         }
    187                         if (newState == BluetoothDevice.BOND_NONE)
    188                         {
    189                             // Set the profile Priorities to undefined
    190                             clearProfilePriorty(dev);
    191                         }
    192                         else if (newState == BluetoothDevice.BOND_BONDED)
    193                         {
    194                            // Restore the profile priorty settings
    195                            setProfilePriorty(dev);
    196                         }
    197                     }
    198                     else if(!mDevices.contains(dev))
    199                         result=true;
    200                     break;
    201                 default:
    202                     Log.e(TAG, "Received unhandled event:" + msg.what);
    203                     return false;
    204             }
    205             if (result) mDevices.add(dev);
    206 
    207             return true;
    208         }
    209     }
    210 
    211     private boolean cancelBond(BluetoothDevice dev) {
    212         if (dev.getBondState() == BluetoothDevice.BOND_BONDING) {
    213             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
    214             if (!mAdapterService.cancelBondNative(addr)) {
    215                Log.e(TAG, "Unexpected error while cancelling bond:");
    216             } else {
    217                 return true;
    218             }
    219         }
    220         return false;
    221     }
    222 
    223     private boolean removeBond(BluetoothDevice dev, boolean transition) {
    224         if (dev.getBondState() == BluetoothDevice.BOND_BONDED) {
    225             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
    226             if (!mAdapterService.removeBondNative(addr)) {
    227                Log.e(TAG, "Unexpected error while removing bond:");
    228             } else {
    229                 if (transition) transitionTo(mPendingCommandState);
    230                 return true;
    231             }
    232 
    233         }
    234         return false;
    235     }
    236 
    237     private boolean createBond(BluetoothDevice dev, boolean transition) {
    238         if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
    239             infoLog("Bond address is:" + dev);
    240             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
    241             if (!mAdapterService.createBondNative(addr)) {
    242                 sendIntent(dev, BluetoothDevice.BOND_NONE,
    243                            BluetoothDevice.UNBOND_REASON_REMOVED);
    244                 return false;
    245             } else if (transition) {
    246                 transitionTo(mPendingCommandState);
    247             }
    248             return true;
    249         }
    250         return false;
    251     }
    252 
    253     private void sendIntent(BluetoothDevice device, int newState, int reason) {
    254         DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
    255         int oldState = BluetoothDevice.BOND_NONE;
    256         if (devProp != null) {
    257             oldState = devProp.getBondState();
    258         }
    259         if (oldState == newState) return;
    260         mAdapterProperties.onBondStateChanged(device, newState);
    261 
    262         Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    263         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    264         intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
    265         intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
    266         if (newState == BluetoothDevice.BOND_NONE)
    267             intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
    268         mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL,
    269                 AdapterService.BLUETOOTH_PERM);
    270         infoLog("Bond State Change Intent:" + device + " OldState: " + oldState
    271                 + " NewState: " + newState);
    272     }
    273 
    274     void bondStateChangeCallback(int status, byte[] address, int newState) {
    275         BluetoothDevice device = mRemoteDevices.getDevice(address);
    276 
    277         if (device == null) {
    278             infoLog("No record of the device:" + device);
    279             // This device will be added as part of the BONDING_STATE_CHANGE intent processing
    280             // in sendIntent above
    281             device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
    282         }
    283 
    284         infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device
    285                 + " newState: " + newState);
    286 
    287         Message msg = obtainMessage(BONDING_STATE_CHANGE);
    288         msg.obj = device;
    289 
    290         if (newState == BOND_STATE_BONDED)
    291             msg.arg1 = BluetoothDevice.BOND_BONDED;
    292         else if (newState == BOND_STATE_BONDING)
    293             msg.arg1 = BluetoothDevice.BOND_BONDING;
    294         else
    295             msg.arg1 = BluetoothDevice.BOND_NONE;
    296         msg.arg2 = status;
    297 
    298         sendMessage(msg);
    299     }
    300 
    301     private void setProfilePriorty (BluetoothDevice device){
    302         HidService hidService = HidService.getHidService();
    303         A2dpService a2dpService = A2dpService.getA2dpService();
    304         HeadsetService headsetService = HeadsetService.getHeadsetService();
    305 
    306         if ((hidService != null) &&
    307             (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
    308             hidService.setPriority(device,BluetoothProfile.PRIORITY_ON);
    309         }
    310 
    311         if ((a2dpService != null) &&
    312             (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
    313             a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON);
    314         }
    315 
    316         if ((headsetService != null) &&
    317             (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
    318             headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON);
    319         }
    320     }
    321 
    322     private void clearProfilePriorty (BluetoothDevice device){
    323         HidService hidService = HidService.getHidService();
    324         A2dpService a2dpService = A2dpService.getA2dpService();
    325         HeadsetService headsetService = HeadsetService.getHeadsetService();
    326 
    327         if (hidService != null)
    328             hidService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
    329         if(a2dpService != null)
    330             a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
    331         if(headsetService != null)
    332             headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
    333     }
    334 
    335     private void infoLog(String msg) {
    336         Log.i(TAG, msg);
    337     }
    338 
    339     private void errorLog(String msg) {
    340         Log.e(TAG, msg);
    341     }
    342 
    343     private int getUnbondReasonFromHALCode (int reason) {
    344         if (reason == AbstractionLayer.BT_STATUS_SUCCESS)
    345             return BluetoothDevice.BOND_SUCCESS;
    346         else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN)
    347             return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
    348         else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE)
    349             return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
    350         else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED)
    351             return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED;
    352         else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT)
    353             return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT;
    354 
    355         /* default */
    356         return BluetoothDevice.UNBOND_REASON_REMOVED;
    357     }
    358 }
    359