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