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