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.BluetoothClass;
     21 import android.bluetooth.BluetoothProfile;
     22 import android.bluetooth.BluetoothDevice;
     23 import com.android.bluetooth.a2dp.A2dpService;
     24 import com.android.bluetooth.hid.HidService;
     25 import com.android.bluetooth.hfp.HeadsetService;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.os.Message;
     29 import android.os.UserHandle;
     30 import android.util.Log;
     31 
     32 import com.android.bluetooth.Utils;
     33 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
     34 import com.android.internal.util.State;
     35 import com.android.internal.util.StateMachine;
     36 
     37 import java.util.ArrayList;
     38 
     39 /**
     40  * This state machine handles Bluetooth Adapter State.
     41  * States:
     42  *      {@link StableState} :  No device is in bonding / unbonding state.
     43  *      {@link PendingCommandState} : Some device is in bonding / unbonding state.
     44  * TODO(BT) This class can be removed and this logic moved to the stack.
     45  */
     46 
     47 final class BondStateMachine extends StateMachine {
     48     private static final boolean DBG = false;
     49     private static final String TAG = "BluetoothBondStateMachine";
     50 
     51     static final int CREATE_BOND = 1;
     52     static final int CANCEL_BOND = 2;
     53     static final int REMOVE_BOND = 3;
     54     static final int BONDING_STATE_CHANGE = 4;
     55     static final int SSP_REQUEST = 5;
     56     static final int PIN_REQUEST = 6;
     57     static final int BOND_STATE_NONE = 0;
     58     static final int BOND_STATE_BONDING = 1;
     59     static final int BOND_STATE_BONDED = 2;
     60 
     61     private AdapterService mAdapterService;
     62     private AdapterProperties mAdapterProperties;
     63     private RemoteDevices mRemoteDevices;
     64     private BluetoothAdapter mAdapter;
     65 
     66     private PendingCommandState mPendingCommandState = new PendingCommandState();
     67     private StableState mStableState = new StableState();
     68 
     69     private BondStateMachine(AdapterService service,
     70             AdapterProperties prop, RemoteDevices remoteDevices) {
     71         super("BondStateMachine:");
     72         addState(mStableState);
     73         addState(mPendingCommandState);
     74         mRemoteDevices = remoteDevices;
     75         mAdapterService = service;
     76         mAdapterProperties = prop;
     77         mAdapter = BluetoothAdapter.getDefaultAdapter();
     78         setInitialState(mStableState);
     79     }
     80 
     81     public static BondStateMachine make(AdapterService service,
     82             AdapterProperties prop, RemoteDevices remoteDevices) {
     83         Log.d(TAG, "make");
     84         BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices);
     85         bsm.start();
     86         return bsm;
     87     }
     88 
     89     public void doQuit() {
     90         quitNow();
     91     }
     92 
     93     public void cleanup() {
     94         mAdapterService = null;
     95         mRemoteDevices = null;
     96         mAdapterProperties = null;
     97     }
     98 
     99     private class StableState extends State {
    100         @Override
    101         public void enter() {
    102             infoLog("StableState(): Entering Off State");
    103         }
    104 
    105         @Override
    106         public boolean processMessage(Message msg) {
    107 
    108             BluetoothDevice dev = (BluetoothDevice)msg.obj;
    109 
    110             switch(msg.what) {
    111 
    112               case CREATE_BOND:
    113                   createBond(dev, msg.arg1, true);
    114                   break;
    115               case REMOVE_BOND:
    116                   removeBond(dev, true);
    117                   break;
    118               case BONDING_STATE_CHANGE:
    119                 int newState = msg.arg1;
    120                 /* if incoming pairing, transition to pending state */
    121                 if (newState == BluetoothDevice.BOND_BONDING)
    122                 {
    123                     sendIntent(dev, newState, 0);
    124                     transitionTo(mPendingCommandState);
    125                 }
    126                 else
    127                 {
    128                     Log.e(TAG, "In stable state, received invalid newState: " + newState);
    129                 }
    130                 break;
    131 
    132               case CANCEL_BOND:
    133               default:
    134                    Log.e(TAG, "Received unhandled state: " + msg.what);
    135                    return false;
    136             }
    137             return true;
    138         }
    139     }
    140 
    141 
    142     private class PendingCommandState extends State {
    143         private final ArrayList<BluetoothDevice> mDevices =
    144             new ArrayList<BluetoothDevice>();
    145 
    146         @Override
    147         public void enter() {
    148             infoLog("Entering PendingCommandState State");
    149             BluetoothDevice dev = (BluetoothDevice)getCurrentMessage().obj;
    150         }
    151 
    152         @Override
    153         public boolean processMessage(Message msg) {
    154 
    155             BluetoothDevice dev = (BluetoothDevice)msg.obj;
    156             DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
    157             boolean result = false;
    158              if (mDevices.contains(dev) && msg.what != CANCEL_BOND &&
    159                    msg.what != BONDING_STATE_CHANGE && msg.what != SSP_REQUEST &&
    160                    msg.what != PIN_REQUEST) {
    161                  deferMessage(msg);
    162                  return true;
    163              }
    164 
    165             Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    166 
    167             switch (msg.what) {
    168                 case CREATE_BOND:
    169                     result = createBond(dev, msg.arg1, false);
    170                     break;
    171                 case REMOVE_BOND:
    172                     result = removeBond(dev, false);
    173                     break;
    174                 case CANCEL_BOND:
    175                     result = cancelBond(dev);
    176                     break;
    177                 case BONDING_STATE_CHANGE:
    178                     int newState = msg.arg1;
    179                     int reason = getUnbondReasonFromHALCode(msg.arg2);
    180                     sendIntent(dev, newState, reason);
    181                     if(newState != BluetoothDevice.BOND_BONDING )
    182                     {
    183                         /* this is either none/bonded, remove and transition */
    184                         result = !mDevices.remove(dev);
    185                         if (mDevices.isEmpty()) {
    186                             // Whenever mDevices is empty, then we need to
    187                             // set result=false. Else, we will end up adding
    188                             // the device to the list again. This prevents us
    189                             // from pairing with a device that we just unpaired
    190                             result = false;
    191                             transitionTo(mStableState);
    192                         }
    193                         if (newState == BluetoothDevice.BOND_NONE)
    194                         {
    195                             mAdapterService.setPhonebookAccessPermission(dev,
    196                                     BluetoothDevice.ACCESS_UNKNOWN);
    197                             mAdapterService.setMessageAccessPermission(dev,
    198                                     BluetoothDevice.ACCESS_UNKNOWN);
    199                             // Set the profile Priorities to undefined
    200                             clearProfilePriorty(dev);
    201                         }
    202                         else if (newState == BluetoothDevice.BOND_BONDED)
    203                         {
    204                            // Do not set profile priority
    205                            // Profile priority should be set after SDP completion
    206 
    207                            // Restore the profile priorty settings
    208                            //setProfilePriorty(dev);
    209                         }
    210                     }
    211                     else if(!mDevices.contains(dev))
    212                         result=true;
    213                     break;
    214                 case SSP_REQUEST:
    215                     int passkey = msg.arg1;
    216                     int variant = msg.arg2;
    217                     sendDisplayPinIntent(devProp.getAddress(), passkey, variant);
    218                     break;
    219                 case PIN_REQUEST:
    220                     BluetoothClass btClass = dev.getBluetoothClass();
    221                     int btDeviceClass = btClass.getDeviceClass();
    222                     if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD ||
    223                          btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) {
    224                         // Its a keyboard. Follow the HID spec recommendation of creating the
    225                         // passkey and displaying it to the user. If the keyboard doesn't follow
    226                         // the spec recommendation, check if the keyboard has a fixed PIN zero
    227                         // and pair.
    228                         //TODO: Maintain list of devices that have fixed pin
    229                         // Generate a variable 6-digit PIN in range of 100000-999999
    230                         // This is not truly random but good enough.
    231                         int pin = 100000 + (int)Math.floor((Math.random() * (999999 - 100000)));
    232                         sendDisplayPinIntent(devProp.getAddress(), pin,
    233                                  BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN);
    234                         break;
    235                     }
    236                     //In PIN_REQUEST, there is no passkey to display.So do not send the
    237                     //EXTRA_PAIRING_KEY type in the intent( 0 in SendDisplayPinIntent() )
    238                     sendDisplayPinIntent(devProp.getAddress(), 0,
    239                                           BluetoothDevice.PAIRING_VARIANT_PIN);
    240 
    241                     break;
    242                 default:
    243                     Log.e(TAG, "Received unhandled event:" + msg.what);
    244                     return false;
    245             }
    246             if (result) mDevices.add(dev);
    247 
    248             return true;
    249         }
    250     }
    251 
    252     private boolean cancelBond(BluetoothDevice dev) {
    253         if (dev.getBondState() == BluetoothDevice.BOND_BONDING) {
    254             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
    255             if (!mAdapterService.cancelBondNative(addr)) {
    256                Log.e(TAG, "Unexpected error while cancelling bond:");
    257             } else {
    258                 return true;
    259             }
    260         }
    261         return false;
    262     }
    263 
    264     private boolean removeBond(BluetoothDevice dev, boolean transition) {
    265         if (dev.getBondState() == BluetoothDevice.BOND_BONDED) {
    266             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
    267             if (!mAdapterService.removeBondNative(addr)) {
    268                Log.e(TAG, "Unexpected error while removing bond:");
    269             } else {
    270                 if (transition) transitionTo(mPendingCommandState);
    271                 return true;
    272             }
    273 
    274         }
    275         return false;
    276     }
    277 
    278     private boolean createBond(BluetoothDevice dev, int transport, boolean transition) {
    279         if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
    280             infoLog("Bond address is:" + dev);
    281             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
    282             if (!mAdapterService.createBondNative(addr, transport)) {
    283                 sendIntent(dev, BluetoothDevice.BOND_NONE,
    284                            BluetoothDevice.UNBOND_REASON_REMOVED);
    285                 return false;
    286             } else if (transition) {
    287                 transitionTo(mPendingCommandState);
    288             }
    289             return true;
    290         }
    291         return false;
    292     }
    293 
    294     private void sendDisplayPinIntent(byte[] address, int pin, int variant) {
    295         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    296         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevices.getDevice(address));
    297         if (pin != 0) {
    298             intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin);
    299         }
    300         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, variant);
    301         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    302         mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
    303     }
    304 
    305     private void sendIntent(BluetoothDevice device, int newState, int reason) {
    306         DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
    307         int oldState = BluetoothDevice.BOND_NONE;
    308         if (devProp != null) {
    309             oldState = devProp.getBondState();
    310         }
    311         if (oldState == newState) return;
    312         mAdapterProperties.onBondStateChanged(device, newState);
    313 
    314         Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    315         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    316         intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
    317         intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
    318         if (newState == BluetoothDevice.BOND_NONE)
    319             intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
    320         mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL,
    321                 AdapterService.BLUETOOTH_PERM);
    322         infoLog("Bond State Change Intent:" + device + " OldState: " + oldState
    323                 + " NewState: " + newState);
    324     }
    325 
    326     void bondStateChangeCallback(int status, byte[] address, int newState) {
    327         BluetoothDevice device = mRemoteDevices.getDevice(address);
    328 
    329         if (device == null) {
    330             infoLog("No record of the device:" + device);
    331             // This device will be added as part of the BONDING_STATE_CHANGE intent processing
    332             // in sendIntent above
    333             device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
    334         }
    335 
    336         infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device
    337                 + " newState: " + newState);
    338 
    339         Message msg = obtainMessage(BONDING_STATE_CHANGE);
    340         msg.obj = device;
    341 
    342         if (newState == BOND_STATE_BONDED)
    343             msg.arg1 = BluetoothDevice.BOND_BONDED;
    344         else if (newState == BOND_STATE_BONDING)
    345             msg.arg1 = BluetoothDevice.BOND_BONDING;
    346         else
    347             msg.arg1 = BluetoothDevice.BOND_NONE;
    348         msg.arg2 = status;
    349 
    350         sendMessage(msg);
    351     }
    352 
    353     void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
    354             int passkey) {
    355         //TODO(BT): Get wakelock and update name and cod
    356         BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
    357         if (bdDevice == null) {
    358             mRemoteDevices.addDeviceProperties(address);
    359         }
    360         infoLog("sspRequestCallback: " + address + " name: " + name + " cod: " +
    361                 cod + " pairingVariant " + pairingVariant + " passkey: " + passkey);
    362         int variant;
    363         boolean displayPasskey = false;
    364         switch(pairingVariant) {
    365 
    366             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION :
    367                 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION;
    368                 displayPasskey = true;
    369             break;
    370 
    371             case AbstractionLayer.BT_SSP_VARIANT_CONSENT :
    372                 variant = BluetoothDevice.PAIRING_VARIANT_CONSENT;
    373             break;
    374 
    375             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY :
    376                 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY;
    377             break;
    378 
    379             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_NOTIFICATION :
    380                 variant = BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY;
    381                 displayPasskey = true;
    382             break;
    383 
    384             default:
    385                 errorLog("SSP Pairing variant not present");
    386                 return;
    387         }
    388         BluetoothDevice device = mRemoteDevices.getDevice(address);
    389         if (device == null) {
    390            warnLog("Device is not known for:" + Utils.getAddressStringFromByte(address));
    391            mRemoteDevices.addDeviceProperties(address);
    392            device = mRemoteDevices.getDevice(address);
    393         }
    394 
    395         Message msg = obtainMessage(SSP_REQUEST);
    396         msg.obj = device;
    397         if(displayPasskey)
    398             msg.arg1 = passkey;
    399         msg.arg2 = variant;
    400         sendMessage(msg);
    401     }
    402 
    403     void pinRequestCallback(byte[] address, byte[] name, int cod) {
    404         //TODO(BT): Get wakelock and update name and cod
    405         BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
    406         if (bdDevice == null) {
    407             mRemoteDevices.addDeviceProperties(address);
    408         }
    409         infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" +
    410                 cod);
    411 
    412         Message msg = obtainMessage(PIN_REQUEST);
    413         msg.obj = bdDevice;
    414 
    415         sendMessage(msg);
    416     }
    417 
    418     private void setProfilePriorty (BluetoothDevice device){
    419         HidService hidService = HidService.getHidService();
    420         A2dpService a2dpService = A2dpService.getA2dpService();
    421         HeadsetService headsetService = HeadsetService.getHeadsetService();
    422 
    423         if ((hidService != null) &&
    424             (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
    425             hidService.setPriority(device,BluetoothProfile.PRIORITY_ON);
    426         }
    427 
    428         if ((a2dpService != null) &&
    429             (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
    430             a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON);
    431         }
    432 
    433         if ((headsetService != null) &&
    434             (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
    435             headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON);
    436         }
    437     }
    438 
    439     private void clearProfilePriorty (BluetoothDevice device){
    440         HidService hidService = HidService.getHidService();
    441         A2dpService a2dpService = A2dpService.getA2dpService();
    442         HeadsetService headsetService = HeadsetService.getHeadsetService();
    443 
    444         if (hidService != null)
    445             hidService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
    446         if(a2dpService != null)
    447             a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
    448         if(headsetService != null)
    449             headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
    450     }
    451 
    452     private void infoLog(String msg) {
    453         Log.i(TAG, msg);
    454     }
    455 
    456     private void errorLog(String msg) {
    457         Log.e(TAG, msg);
    458     }
    459 
    460     private void warnLog(String msg) {
    461         Log.w(TAG, msg);
    462     }
    463 
    464     private int getUnbondReasonFromHALCode (int reason) {
    465         if (reason == AbstractionLayer.BT_STATUS_SUCCESS)
    466             return BluetoothDevice.BOND_SUCCESS;
    467         else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN)
    468             return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
    469         else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE)
    470             return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
    471         else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED)
    472             return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED;
    473         else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT)
    474             return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT;
    475 
    476         /* default */
    477         return BluetoothDevice.UNBOND_REASON_REMOVED;
    478     }
    479 }
    480