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 com.android.bluetooth.a2dpsink.A2dpSinkService;
     27 import com.android.bluetooth.hfpclient.HeadsetClientService;
     28 import com.android.bluetooth.pbapclient.PbapClientService;
     29 
     30 import android.bluetooth.OobData;
     31 import android.content.Intent;
     32 import android.os.Message;
     33 import android.os.UserHandle;
     34 import android.util.Log;
     35 
     36 import com.android.bluetooth.R;
     37 import com.android.bluetooth.Utils;
     38 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
     39 import com.android.internal.util.State;
     40 import com.android.internal.util.StateMachine;
     41 
     42 import java.util.ArrayList;
     43 
     44 /**
     45  * This state machine handles Bluetooth Adapter State.
     46  * States:
     47  *      {@link StableState} :  No device is in bonding / unbonding state.
     48  *      {@link PendingCommandState} : Some device is in bonding / unbonding state.
     49  * TODO(BT) This class can be removed and this logic moved to the stack.
     50  */
     51 
     52 final class BondStateMachine extends StateMachine {
     53     private static final boolean DBG = false;
     54     private static final String TAG = "BluetoothBondStateMachine";
     55 
     56     static final int CREATE_BOND = 1;
     57     static final int CANCEL_BOND = 2;
     58     static final int REMOVE_BOND = 3;
     59     static final int BONDING_STATE_CHANGE = 4;
     60     static final int SSP_REQUEST = 5;
     61     static final int PIN_REQUEST = 6;
     62     static final int BOND_STATE_NONE = 0;
     63     static final int BOND_STATE_BONDING = 1;
     64     static final int BOND_STATE_BONDED = 2;
     65 
     66     private AdapterService mAdapterService;
     67     private AdapterProperties mAdapterProperties;
     68     private RemoteDevices mRemoteDevices;
     69     private BluetoothAdapter mAdapter;
     70 
     71     private PendingCommandState mPendingCommandState = new PendingCommandState();
     72     private StableState mStableState = new StableState();
     73 
     74     public static final String OOBDATA = "oobdata";
     75 
     76     private BondStateMachine(AdapterService service,
     77             AdapterProperties prop, RemoteDevices remoteDevices) {
     78         super("BondStateMachine:");
     79         addState(mStableState);
     80         addState(mPendingCommandState);
     81         mRemoteDevices = remoteDevices;
     82         mAdapterService = service;
     83         mAdapterProperties = prop;
     84         mAdapter = BluetoothAdapter.getDefaultAdapter();
     85         setInitialState(mStableState);
     86     }
     87 
     88     public static BondStateMachine make(AdapterService service,
     89             AdapterProperties prop, RemoteDevices remoteDevices) {
     90         Log.d(TAG, "make");
     91         BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices);
     92         bsm.start();
     93         return bsm;
     94     }
     95 
     96     public void doQuit() {
     97         quitNow();
     98     }
     99 
    100     public void cleanup() {
    101         mAdapterService = null;
    102         mRemoteDevices = null;
    103         mAdapterProperties = null;
    104     }
    105 
    106     private class StableState extends State {
    107         @Override
    108         public void enter() {
    109             infoLog("StableState(): Entering Off State");
    110         }
    111 
    112         @Override
    113         public boolean processMessage(Message msg) {
    114 
    115             BluetoothDevice dev = (BluetoothDevice)msg.obj;
    116 
    117             switch(msg.what) {
    118 
    119               case CREATE_BOND:
    120                   OobData oobData = null;
    121                   if (msg.getData() != null)
    122                       oobData = msg.getData().getParcelable(OOBDATA);
    123 
    124                   createBond(dev, msg.arg1, oobData, true);
    125                   break;
    126               case REMOVE_BOND:
    127                   removeBond(dev, true);
    128                   break;
    129               case BONDING_STATE_CHANGE:
    130                 int newState = msg.arg1;
    131                 /* if incoming pairing, transition to pending state */
    132                 if (newState == BluetoothDevice.BOND_BONDING)
    133                 {
    134                     sendIntent(dev, newState, 0);
    135                     transitionTo(mPendingCommandState);
    136                 }
    137                 else if (newState == BluetoothDevice.BOND_NONE)
    138                 {
    139                     /* if the link key was deleted by the stack */
    140                     sendIntent(dev, newState, 0);
    141                 }
    142                 else
    143                 {
    144                     Log.e(TAG, "In stable state, received invalid newState: " + 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 =
    160             new ArrayList<BluetoothDevice>();
    161 
    162         @Override
    163         public void enter() {
    164             infoLog("Entering PendingCommandState State");
    165             BluetoothDevice dev = (BluetoothDevice)getCurrentMessage().obj;
    166         }
    167 
    168         @Override
    169         public boolean processMessage(Message msg) {
    170             BluetoothDevice dev = (BluetoothDevice)msg.obj;
    171             DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
    172             boolean result = false;
    173              if (mDevices.contains(dev) && msg.what != CANCEL_BOND &&
    174                    msg.what != BONDING_STATE_CHANGE && msg.what != SSP_REQUEST &&
    175                    msg.what != PIN_REQUEST) {
    176                  deferMessage(msg);
    177                  return true;
    178              }
    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         // Workaround for Android Auto until pre-accepting pairing requests is added.
    328         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
    329         mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
    330     }
    331 
    332     private void sendIntent(BluetoothDevice device, int newState, int reason) {
    333         DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
    334         int oldState = BluetoothDevice.BOND_NONE;
    335         if (devProp != null) {
    336             oldState = devProp.getBondState();
    337         }
    338         if (oldState == newState) return;
    339         mAdapterProperties.onBondStateChanged(device, newState);
    340 
    341         Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    342         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    343         intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
    344         intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
    345         if (newState == BluetoothDevice.BOND_NONE)
    346             intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
    347         mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL,
    348                 AdapterService.BLUETOOTH_PERM);
    349         infoLog("Bond State Change Intent:" + device + " OldState: " + oldState
    350                 + " NewState: " + newState);
    351     }
    352 
    353     void bondStateChangeCallback(int status, byte[] address, int newState) {
    354         BluetoothDevice device = mRemoteDevices.getDevice(address);
    355 
    356         if (device == null) {
    357             infoLog("No record of the device:" + device);
    358             // This device will be added as part of the BONDING_STATE_CHANGE intent processing
    359             // in sendIntent above
    360             device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
    361         }
    362 
    363         infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device
    364                 + " newState: " + newState);
    365 
    366         Message msg = obtainMessage(BONDING_STATE_CHANGE);
    367         msg.obj = device;
    368 
    369         if (newState == BOND_STATE_BONDED)
    370             msg.arg1 = BluetoothDevice.BOND_BONDED;
    371         else if (newState == BOND_STATE_BONDING)
    372             msg.arg1 = BluetoothDevice.BOND_BONDING;
    373         else
    374             msg.arg1 = BluetoothDevice.BOND_NONE;
    375         msg.arg2 = status;
    376 
    377         sendMessage(msg);
    378     }
    379 
    380     void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
    381             int passkey) {
    382         //TODO(BT): Get wakelock and update name and cod
    383         BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
    384         if (bdDevice == null) {
    385             mRemoteDevices.addDeviceProperties(address);
    386         }
    387         infoLog("sspRequestCallback: " + address + " name: " + name + " cod: " +
    388                 cod + " pairingVariant " + pairingVariant + " passkey: " + passkey);
    389         int variant;
    390         boolean displayPasskey = false;
    391         switch(pairingVariant) {
    392 
    393             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION :
    394                 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION;
    395                 displayPasskey = true;
    396             break;
    397 
    398             case AbstractionLayer.BT_SSP_VARIANT_CONSENT :
    399                 variant = BluetoothDevice.PAIRING_VARIANT_CONSENT;
    400             break;
    401 
    402             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY :
    403                 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY;
    404             break;
    405 
    406             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_NOTIFICATION :
    407                 variant = BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY;
    408                 displayPasskey = true;
    409             break;
    410 
    411             default:
    412                 errorLog("SSP Pairing variant not present");
    413                 return;
    414         }
    415         BluetoothDevice device = mRemoteDevices.getDevice(address);
    416         if (device == null) {
    417            warnLog("Device is not known for:" + Utils.getAddressStringFromByte(address));
    418            mRemoteDevices.addDeviceProperties(address);
    419            device = mRemoteDevices.getDevice(address);
    420         }
    421 
    422         Message msg = obtainMessage(SSP_REQUEST);
    423         msg.obj = device;
    424         if(displayPasskey)
    425             msg.arg1 = passkey;
    426         msg.arg2 = variant;
    427         sendMessage(msg);
    428     }
    429 
    430     void pinRequestCallback(byte[] address, byte[] name, int cod, boolean min16Digits) {
    431         //TODO(BT): Get wakelock and update name and cod
    432 
    433         BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
    434         if (bdDevice == null) {
    435             mRemoteDevices.addDeviceProperties(address);
    436         }
    437         infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" +
    438                 cod);
    439 
    440         Message msg = obtainMessage(PIN_REQUEST);
    441         msg.obj = bdDevice;
    442         msg.arg2 = min16Digits ? 1 : 0; // Use arg2 to pass the min16Digit boolean
    443 
    444         sendMessage(msg);
    445     }
    446 
    447     private void clearProfilePriority(BluetoothDevice device) {
    448         HidService hidService = HidService.getHidService();
    449         A2dpService a2dpService = A2dpService.getA2dpService();
    450         HeadsetService headsetService = HeadsetService.getHeadsetService();
    451         HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
    452         A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
    453         PbapClientService pbapClientService = PbapClientService.getPbapClientService();
    454 
    455         if (hidService != null)
    456             hidService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
    457         if (a2dpService != null)
    458             a2dpService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
    459         if (headsetService != null)
    460             headsetService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
    461         if (headsetClientService != null)
    462             headsetClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
    463         if (a2dpSinkService != null)
    464             a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
    465         if (pbapClientService != null)
    466             pbapClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
    467 
    468         // Clear Absolute Volume black list
    469         if(a2dpService != null)
    470             a2dpService.resetAvrcpBlacklist(device);
    471     }
    472 
    473     private void infoLog(String msg) {
    474         Log.i(TAG, msg);
    475     }
    476 
    477     private void errorLog(String msg) {
    478         Log.e(TAG, msg);
    479     }
    480 
    481     private void warnLog(String msg) {
    482         Log.w(TAG, msg);
    483     }
    484 
    485     private int getUnbondReasonFromHALCode (int reason) {
    486         if (reason == AbstractionLayer.BT_STATUS_SUCCESS)
    487             return BluetoothDevice.BOND_SUCCESS;
    488         else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN)
    489             return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
    490         else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE)
    491             return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
    492         else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED)
    493             return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED;
    494         else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT)
    495             return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT;
    496 
    497         /* default */
    498         return BluetoothDevice.UNBOND_REASON_REMOVED;
    499     }
    500 }
    501