Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2016 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 package com.android.settings.bluetooth;
     17 
     18 import android.bluetooth.BluetoothClass;
     19 import android.bluetooth.BluetoothDevice;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.text.Editable;
     23 import android.util.Log;
     24 import android.widget.CompoundButton;
     25 import android.widget.CompoundButton.OnCheckedChangeListener;
     26 import com.android.settings.R;
     27 import com.android.settings.bluetooth.BluetoothPairingDialogFragment.BluetoothPairingDialogListener;
     28 import com.android.settingslib.bluetooth.LocalBluetoothManager;
     29 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
     30 import java.util.Locale;
     31 
     32 /**
     33  * A controller used by {@link BluetoothPairingDialog} to manage connection state while we try to
     34  * pair with a bluetooth device. It includes methods that allow the
     35  * {@link BluetoothPairingDialogFragment} to interrogate the current state as well.
     36  */
     37 public class BluetoothPairingController implements OnCheckedChangeListener,
     38         BluetoothPairingDialogListener {
     39 
     40     private static final String TAG = "BTPairingController";
     41 
     42     // Different types of dialogs we can map to
     43     public static final int INVALID_DIALOG_TYPE = -1;
     44     public static final int USER_ENTRY_DIALOG = 0;
     45     public static final int CONFIRMATION_DIALOG = 1;
     46     public static final int DISPLAY_PASSKEY_DIALOG = 2;
     47 
     48     private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
     49     private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
     50 
     51     // Bluetooth dependencies for the connection we are trying to establish
     52     private LocalBluetoothManager mBluetoothManager;
     53     private BluetoothDevice mDevice;
     54     private int mType;
     55     private String mUserInput;
     56     private String mPasskeyFormatted;
     57     private int mPasskey;
     58     private String mDeviceName;
     59     private LocalBluetoothProfile mPbapClientProfile;
     60     private boolean mPbapAllowed;
     61 
     62     /**
     63      * Creates an instance of a BluetoothPairingController.
     64      *
     65      * @param intent - must contain {@link BluetoothDevice#EXTRA_PAIRING_VARIANT}, {@link
     66      * BluetoothDevice#EXTRA_PAIRING_KEY}, and {@link BluetoothDevice#EXTRA_DEVICE}. Missing extra
     67      * will lead to undefined behavior.
     68      */
     69     public BluetoothPairingController(Intent intent, Context context) {
     70         mBluetoothManager = Utils.getLocalBtManager(context);
     71         mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
     72 
     73         String message = "";
     74         if (mBluetoothManager == null) {
     75             throw new IllegalStateException("Could not obtain LocalBluetoothManager");
     76         } else if (mDevice == null) {
     77             throw new IllegalStateException("Could not find BluetoothDevice");
     78         }
     79 
     80         mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
     81         mPasskey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
     82         mDeviceName = mBluetoothManager.getCachedDeviceManager().getName(mDevice);
     83         mPbapClientProfile = mBluetoothManager.getProfileManager().getPbapClientProfile();
     84         mPasskeyFormatted = formatKey(mPasskey);
     85 
     86     }
     87 
     88     @Override
     89     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
     90         if (isChecked) {
     91             mPbapAllowed = true;
     92         } else {
     93             mPbapAllowed = false;
     94         }
     95     }
     96 
     97     @Override
     98     public void onDialogPositiveClick(BluetoothPairingDialogFragment dialog) {
     99         if (getDialogType() == USER_ENTRY_DIALOG) {
    100             if (mPbapAllowed) {
    101                 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
    102             } else {
    103                 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
    104             }
    105             onPair(mUserInput);
    106         } else {
    107             onPair(null);
    108         }
    109     }
    110 
    111     @Override
    112     public void onDialogNegativeClick(BluetoothPairingDialogFragment dialog) {
    113         mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
    114         onCancel();
    115     }
    116 
    117     /**
    118      * A method for querying which bluetooth pairing dialog fragment variant this device requires.
    119      *
    120      * @return - The dialog view variant needed for this device.
    121      */
    122     public int getDialogType() {
    123         switch (mType) {
    124             case BluetoothDevice.PAIRING_VARIANT_PIN:
    125             case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
    126             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    127                 return USER_ENTRY_DIALOG;
    128 
    129             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    130             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
    131             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    132                 return CONFIRMATION_DIALOG;
    133 
    134             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    135             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    136                 return DISPLAY_PASSKEY_DIALOG;
    137 
    138             default:
    139                 return INVALID_DIALOG_TYPE;
    140         }
    141     }
    142 
    143     /**
    144      * @return - A string containing the name provided by the device.
    145      */
    146     public String getDeviceName() {
    147         return mDeviceName;
    148     }
    149 
    150     /**
    151      * A method for querying if the bluetooth device has a profile already set up on this device.
    152      *
    153      * @return - A boolean indicating if the device has previous knowledge of a profile for this
    154      * device.
    155      */
    156     public boolean isProfileReady() {
    157         return mPbapClientProfile != null && mPbapClientProfile.isProfileReady();
    158     }
    159 
    160     /**
    161      * A method for querying if the bluetooth device has access to contacts on the device.
    162      *
    163      * @return - A boolean indicating if the bluetooth device has permission to access the device
    164      * contacts
    165      */
    166     public boolean getContactSharingState() {
    167         switch (mDevice.getPhonebookAccessPermission()) {
    168             case BluetoothDevice.ACCESS_ALLOWED:
    169                 return true;
    170             case BluetoothDevice.ACCESS_REJECTED:
    171                 return false;
    172             default:
    173                 if (mDevice.getBluetoothClass().getDeviceClass()
    174                         == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
    175                     return true;
    176                 }
    177                 return false;
    178         }
    179     }
    180 
    181     /**
    182      * Update Phone book permission
    183      *
    184      */
    185      public void  setContactSharingState() {
    186         if ((mDevice.getPhonebookAccessPermission() != BluetoothDevice.ACCESS_ALLOWED)
    187                 && (mDevice.getPhonebookAccessPermission() != BluetoothDevice.ACCESS_REJECTED)) {
    188                  if (mDevice.getBluetoothClass().getDeviceClass()
    189                         == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
    190                     onCheckedChanged(null, true);
    191             } else {
    192                 onCheckedChanged(null, false);
    193             }
    194         }
    195     }
    196 
    197     /**
    198      * A method for querying if the provided editable is a valid passkey/pin format for this device.
    199      *
    200      * @param s - The passkey/pin
    201      * @return - A boolean indicating if the passkey/pin is of the correct format.
    202      */
    203     public boolean isPasskeyValid(Editable s) {
    204         boolean requires16Digits = mType == BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS;
    205         return s.length() >= 16 && requires16Digits || s.length() > 0 && !requires16Digits;
    206     }
    207 
    208     /**
    209      * A method for querying what message should be shown to the user as additional text in the
    210      * dialog for this device. Returns -1 to indicate a device type that does not use this message.
    211      *
    212      * @return - The message ID to show the user.
    213      */
    214     public int getDeviceVariantMessageId() {
    215         switch (mType) {
    216             case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
    217             case BluetoothDevice.PAIRING_VARIANT_PIN:
    218                 return R.string.bluetooth_enter_pin_other_device;
    219 
    220             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    221                 return R.string.bluetooth_enter_passkey_other_device;
    222 
    223             default:
    224                 return INVALID_DIALOG_TYPE;
    225         }
    226     }
    227 
    228     /**
    229      * A method for querying what message hint should be shown to the user as additional text in the
    230      * dialog for this device. Returns -1 to indicate a device type that does not use this message.
    231      *
    232      * @return - The message ID to show the user.
    233      */
    234     public int getDeviceVariantMessageHintId() {
    235         switch (mType) {
    236             case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
    237                 return R.string.bluetooth_pin_values_hint_16_digits;
    238 
    239             case BluetoothDevice.PAIRING_VARIANT_PIN:
    240             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    241                 return R.string.bluetooth_pin_values_hint;
    242 
    243             default:
    244                 return INVALID_DIALOG_TYPE;
    245         }
    246     }
    247 
    248     /**
    249      * A method for querying the maximum passkey/pin length for this device.
    250      *
    251      * @return - An int indicating the maximum length
    252      */
    253     public int getDeviceMaxPasskeyLength() {
    254         switch (mType) {
    255             case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
    256             case BluetoothDevice.PAIRING_VARIANT_PIN:
    257                 return BLUETOOTH_PIN_MAX_LENGTH;
    258 
    259             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    260                 return BLUETOOTH_PASSKEY_MAX_LENGTH;
    261 
    262             default:
    263                 return 0;
    264         }
    265 
    266     }
    267 
    268     /**
    269      * A method for querying if the device uses an alphanumeric passkey.
    270      *
    271      * @return - a boolean indicating if the passkey can be alphanumeric.
    272      */
    273     public boolean pairingCodeIsAlphanumeric() {
    274         switch (mType) {
    275             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    276                 return false;
    277 
    278             default:
    279                 return true;
    280         }
    281     }
    282 
    283     /**
    284      * A method used by the dialogfragment to notify the controller that the dialog has been
    285      * displayed for bluetooth device types that just care about it being displayed.
    286      */
    287     protected void notifyDialogDisplayed() {
    288         // send an OK to the framework, indicating that the dialog has been displayed.
    289         if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
    290             mDevice.setPairingConfirmation(true);
    291         } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
    292             byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPasskeyFormatted);
    293             mDevice.setPin(pinBytes);
    294         }
    295     }
    296 
    297     /**
    298      * A method for querying if this bluetooth device type has a key it would like displayed
    299      * to the user.
    300      *
    301      * @return - A boolean indicating if a key exists which should be displayed to the user.
    302      */
    303     public boolean isDisplayPairingKeyVariant() {
    304         switch (mType) {
    305             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    306             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    307             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    308                 return true;
    309             default:
    310                 return false;
    311         }
    312     }
    313 
    314     /**
    315      * A method for querying if this bluetooth device type has other content it would like displayed
    316      * to the user.
    317      *
    318      * @return - A boolean indicating if content exists which should be displayed to the user.
    319      */
    320     public boolean hasPairingContent() {
    321         switch (mType) {
    322             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    323             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    324             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    325                 return true;
    326 
    327             default:
    328                 return false;
    329         }
    330     }
    331 
    332     /**
    333      * A method for obtaining any additional content this bluetooth device has for displaying to the
    334      * user.
    335      *
    336      * @return - A string containing the additional content, null if none exists.
    337      * @see {@link BluetoothPairingController#hasPairingContent()}
    338      */
    339     public String getPairingContent() {
    340         if (hasPairingContent()) {
    341             return mPasskeyFormatted;
    342         } else {
    343             return null;
    344         }
    345     }
    346 
    347     /**
    348      * A method that exists to allow the fragment to update the controller with input the user has
    349      * provided in the fragment.
    350      *
    351      * @param input - A string containing the user input.
    352      */
    353     protected void updateUserInput(String input) {
    354         mUserInput = input;
    355     }
    356 
    357     /**
    358      * Returns the provided passkey in a format that this device expects. Only works for numeric
    359      * passkeys/pins.
    360      *
    361      * @param passkey - An integer containing the passkey to format.
    362      * @return - A string containing the formatted passkey/pin
    363      */
    364     private String formatKey(int passkey) {
    365         switch (mType) {
    366             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    367             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    368                 return String.format(Locale.US, "%06d", passkey);
    369 
    370             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    371                 return String.format("%04d", passkey);
    372 
    373             default:
    374                 return null;
    375         }
    376     }
    377 
    378     /**
    379      * handles the necessary communication with the bluetooth device to establish a successful
    380      * pairing
    381      *
    382      * @param passkey - The passkey we will attempt to pair to the device with.
    383      */
    384     private void onPair(String passkey) {
    385         Log.d(TAG, "Pairing dialog accepted");
    386         switch (mType) {
    387             case BluetoothDevice.PAIRING_VARIANT_PIN:
    388             case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
    389                 byte[] pinBytes = BluetoothDevice.convertPinToBytes(passkey);
    390                 if (pinBytes == null) {
    391                     return;
    392                 }
    393                 mDevice.setPin(pinBytes);
    394                 break;
    395 
    396             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    397                 int pass = Integer.parseInt(passkey);
    398                 mDevice.setPasskey(pass);
    399                 break;
    400 
    401             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    402             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
    403                 mDevice.setPairingConfirmation(true);
    404                 break;
    405 
    406             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    407             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    408                 // Do nothing.
    409                 break;
    410 
    411             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    412                 mDevice.setRemoteOutOfBandData();
    413                 break;
    414 
    415             default:
    416                 Log.e(TAG, "Incorrect pairing type received");
    417         }
    418     }
    419 
    420     /**
    421      * A method for properly ending communication with the bluetooth device. Will be called by the
    422      * {@link BluetoothPairingDialogFragment} when it is dismissed.
    423      */
    424     public void onCancel() {
    425         Log.d(TAG, "Pairing dialog canceled");
    426         mDevice.cancelPairingUserInput();
    427     }
    428 
    429     /**
    430      * A method for checking if this device is equal to another device.
    431      *
    432      * @param device - The other device being compared to this device.
    433      * @return - A boolean indicating if the devices were equal.
    434      */
    435     public boolean deviceEquals(BluetoothDevice device) {
    436         return mDevice == device;
    437     }
    438 }
    439