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