Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2008 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.settings.bluetooth;
     18 
     19 import android.bluetooth.BluetoothDevice;
     20 import android.content.BroadcastReceiver;
     21 import android.content.Context;
     22 import android.content.DialogInterface;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.os.Bundle;
     26 import android.text.Editable;
     27 import android.text.Html;
     28 import android.text.InputFilter;
     29 import android.text.InputType;
     30 import android.text.Spanned;
     31 import android.text.TextWatcher;
     32 import android.text.InputFilter.LengthFilter;
     33 import android.util.Log;
     34 import android.view.View;
     35 import android.widget.Button;
     36 import android.widget.CheckBox;
     37 import android.widget.CompoundButton;
     38 import android.widget.EditText;
     39 import android.widget.TextView;
     40 
     41 import com.android.internal.app.AlertActivity;
     42 import com.android.internal.app.AlertController;
     43 import com.android.settings.R;
     44 import android.view.KeyEvent;
     45 
     46 import java.util.Locale;
     47 
     48 /**
     49  * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
     50  * for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
     51  */
     52 public final class BluetoothPairingDialog extends AlertActivity implements
     53         CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
     54     private static final String TAG = "BluetoothPairingDialog";
     55 
     56     private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
     57     private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
     58 
     59     private LocalBluetoothManager mBluetoothManager;
     60     private CachedBluetoothDeviceManager mCachedDeviceManager;
     61     private BluetoothDevice mDevice;
     62     private int mType;
     63     private String mPairingKey;
     64     private EditText mPairingView;
     65     private Button mOkButton;
     66 
     67     /**
     68      * Dismiss the dialog if the bond state changes to bonded or none,
     69      * or if pairing was canceled for {@link #mDevice}.
     70      */
     71     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
     72         @Override
     73         public void onReceive(Context context, Intent intent) {
     74             String action = intent.getAction();
     75             if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
     76                 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
     77                                                    BluetoothDevice.ERROR);
     78                 if (bondState == BluetoothDevice.BOND_BONDED ||
     79                         bondState == BluetoothDevice.BOND_NONE) {
     80                     dismiss();
     81                 }
     82             } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
     83                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
     84                 if (device == null || device.equals(mDevice)) {
     85                     dismiss();
     86                 }
     87             }
     88         }
     89     };
     90 
     91     @Override
     92     protected void onCreate(Bundle savedInstanceState) {
     93         super.onCreate(savedInstanceState);
     94 
     95         Intent intent = getIntent();
     96         if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST))
     97         {
     98             Log.e(TAG, "Error: this activity may be started only with intent " +
     99                   BluetoothDevice.ACTION_PAIRING_REQUEST);
    100             finish();
    101             return;
    102         }
    103 
    104         mBluetoothManager = LocalBluetoothManager.getInstance(this);
    105         if (mBluetoothManager == null) {
    106             Log.e(TAG, "Error: BluetoothAdapter not supported by system");
    107             finish();
    108             return;
    109         }
    110         mCachedDeviceManager = mBluetoothManager.getCachedDeviceManager();
    111 
    112         mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    113         mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
    114 
    115         switch (mType) {
    116             case BluetoothDevice.PAIRING_VARIANT_PIN:
    117             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    118                 createUserEntryDialog();
    119                 break;
    120 
    121             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    122                 int passkey =
    123                     intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
    124                 if (passkey == BluetoothDevice.ERROR) {
    125                     Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog");
    126                     return;
    127                 }
    128                 mPairingKey = String.format(Locale.US, "%06d", passkey);
    129                 createConfirmationDialog();
    130                 break;
    131 
    132             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
    133             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    134                 createConsentDialog();
    135                 break;
    136 
    137             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    138             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    139                 int pairingKey =
    140                     intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
    141                 if (pairingKey == BluetoothDevice.ERROR) {
    142                     Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog");
    143                     return;
    144                 }
    145                 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
    146                     mPairingKey = String.format("%06d", pairingKey);
    147                 } else {
    148                     mPairingKey = String.format("%04d", pairingKey);
    149                 }
    150                 createDisplayPasskeyOrPinDialog();
    151                 break;
    152 
    153             default:
    154                 Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
    155         }
    156 
    157         /*
    158          * Leave this registered through pause/resume since we still want to
    159          * finish the activity in the background if pairing is canceled.
    160          */
    161         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_CANCEL));
    162         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
    163     }
    164 
    165     private void createUserEntryDialog() {
    166         final AlertController.AlertParams p = mAlertParams;
    167         p.mTitle = getString(R.string.bluetooth_pairing_request);
    168         p.mView = createPinEntryView();
    169         p.mPositiveButtonText = getString(android.R.string.ok);
    170         p.mPositiveButtonListener = this;
    171         p.mNegativeButtonText = getString(android.R.string.cancel);
    172         p.mNegativeButtonListener = this;
    173         setupAlert();
    174 
    175         mOkButton = mAlert.getButton(BUTTON_POSITIVE);
    176         mOkButton.setEnabled(false);
    177     }
    178 
    179     private View createPinEntryView() {
    180         View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
    181         TextView messageViewCaption = (TextView) view.findViewById(R.id.message_caption);
    182         TextView messageViewContent = (TextView) view.findViewById(R.id.message_subhead);
    183         TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
    184         CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
    185         mPairingView = (EditText) view.findViewById(R.id.text);
    186         mPairingView.addTextChangedListener(this);
    187         alphanumericPin.setOnCheckedChangeListener(this);
    188 
    189         int messageId1;
    190         int messageId2;
    191         int maxLength;
    192         switch (mType) {
    193             case BluetoothDevice.PAIRING_VARIANT_PIN:
    194                 messageId1 = R.string.bluetooth_enter_pin_msg;
    195                 messageId2 = R.string.bluetooth_enter_pin_other_device;
    196                 // Maximum of 16 characters in a PIN
    197                 maxLength = BLUETOOTH_PIN_MAX_LENGTH;
    198                 break;
    199 
    200             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    201                 messageId1 = R.string.bluetooth_enter_pin_msg;
    202                 messageId2 = R.string.bluetooth_enter_passkey_other_device;
    203                 // Maximum of 6 digits for passkey
    204                 maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
    205                 alphanumericPin.setVisibility(View.GONE);
    206                 break;
    207 
    208             default:
    209                 Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
    210                 return null;
    211         }
    212 
    213         messageViewCaption.setText(messageId1);
    214         messageViewContent.setText(mCachedDeviceManager.getName(mDevice));
    215         messageView2.setText(messageId2);
    216         mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
    217         mPairingView.setFilters(new InputFilter[] {
    218                 new LengthFilter(maxLength) });
    219 
    220         return view;
    221     }
    222 
    223     private View createView() {
    224         View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
    225         // Escape device name to avoid HTML injection.
    226         String name = Html.escapeHtml(mCachedDeviceManager.getName(mDevice));
    227         TextView messageViewCaption = (TextView) view.findViewById(R.id.message_caption);
    228         TextView messageViewContent = (TextView) view.findViewById(R.id.message_subhead);
    229         TextView pairingViewCaption = (TextView) view.findViewById(R.id.pairing_caption);
    230         TextView pairingViewContent = (TextView) view.findViewById(R.id.pairing_subhead);
    231         TextView messagePairing = (TextView) view.findViewById(R.id.pairing_code_message);
    232 
    233         String messageCaption = null;
    234         String pairingContent = null;
    235         switch (mType) {
    236             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    237             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    238                 messagePairing.setVisibility(View.VISIBLE);
    239             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    240                 messageCaption = getString(R.string.bluetooth_enter_pin_msg);
    241                 pairingContent = mPairingKey;
    242                 break;
    243 
    244             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
    245             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    246                 messagePairing.setVisibility(View.VISIBLE);
    247                 messageCaption = getString(R.string.bluetooth_enter_pin_msg);
    248                 break;
    249 
    250             default:
    251                 Log.e(TAG, "Incorrect pairing type received, not creating view");
    252                 return null;
    253         }
    254 
    255         if (messageViewCaption != null) {
    256             messageViewCaption.setText(messageCaption);
    257             messageViewContent.setText(name);
    258         }
    259 
    260         if (pairingContent != null) {
    261             pairingViewCaption.setVisibility(View.VISIBLE);
    262             pairingViewContent.setVisibility(View.VISIBLE);
    263             pairingViewContent.setText(pairingContent);
    264         }
    265 
    266         return view;
    267     }
    268 
    269     private void createConfirmationDialog() {
    270         final AlertController.AlertParams p = mAlertParams;
    271         p.mTitle = getString(R.string.bluetooth_pairing_request);
    272         p.mView = createView();
    273         p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
    274         p.mPositiveButtonListener = this;
    275         p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
    276         p.mNegativeButtonListener = this;
    277         setupAlert();
    278     }
    279 
    280     private void createConsentDialog() {
    281         final AlertController.AlertParams p = mAlertParams;
    282         p.mTitle = getString(R.string.bluetooth_pairing_request);
    283         p.mView = createView();
    284         p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
    285         p.mPositiveButtonListener = this;
    286         p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
    287         p.mNegativeButtonListener = this;
    288         setupAlert();
    289     }
    290 
    291     private void createDisplayPasskeyOrPinDialog() {
    292         final AlertController.AlertParams p = mAlertParams;
    293         p.mTitle = getString(R.string.bluetooth_pairing_request);
    294         p.mView = createView();
    295         p.mNegativeButtonText = getString(android.R.string.cancel);
    296         p.mNegativeButtonListener = this;
    297         setupAlert();
    298 
    299         // Since its only a notification, send an OK to the framework,
    300         // indicating that the dialog has been displayed.
    301         if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
    302             mDevice.setPairingConfirmation(true);
    303         } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
    304             byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPairingKey);
    305             mDevice.setPin(pinBytes);
    306         }
    307     }
    308 
    309     @Override
    310     protected void onDestroy() {
    311         super.onDestroy();
    312         unregisterReceiver(mReceiver);
    313     }
    314 
    315     public void afterTextChanged(Editable s) {
    316         if (mOkButton != null) {
    317             mOkButton.setEnabled(s.length() > 0);
    318         }
    319     }
    320 
    321     private void allowPhonebookAccess() {
    322         CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(mDevice);
    323         if (cachedDevice == null) {
    324             cachedDevice = mCachedDeviceManager.addDevice(
    325                     mBluetoothManager.getBluetoothAdapter(),
    326                     mBluetoothManager.getProfileManager(),
    327                     mDevice);
    328         }
    329         cachedDevice.setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
    330     }
    331 
    332     private void onPair(String value) {
    333         allowPhonebookAccess();
    334 
    335         switch (mType) {
    336             case BluetoothDevice.PAIRING_VARIANT_PIN:
    337                 byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
    338                 if (pinBytes == null) {
    339                     return;
    340                 }
    341                 mDevice.setPin(pinBytes);
    342                 break;
    343 
    344             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    345                 int passkey = Integer.parseInt(value);
    346                 mDevice.setPasskey(passkey);
    347                 break;
    348 
    349             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    350             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
    351                 mDevice.setPairingConfirmation(true);
    352                 break;
    353 
    354             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    355             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    356                 // Do nothing.
    357                 break;
    358 
    359             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    360                 mDevice.setRemoteOutOfBandData();
    361                 break;
    362 
    363             default:
    364                 Log.e(TAG, "Incorrect pairing type received");
    365         }
    366     }
    367 
    368     private void onCancel() {
    369         mDevice.cancelPairingUserInput();
    370     }
    371 
    372     public boolean onKeyDown(int keyCode, KeyEvent event) {
    373         if (keyCode == KeyEvent.KEYCODE_BACK) {
    374             onCancel();
    375         }
    376         return super.onKeyDown(keyCode,event);
    377     }
    378 
    379     public void onClick(DialogInterface dialog, int which) {
    380         switch (which) {
    381             case BUTTON_POSITIVE:
    382                 if (mPairingView != null) {
    383                     onPair(mPairingView.getText().toString());
    384                 } else {
    385                     onPair(null);
    386                 }
    387                 break;
    388 
    389             case BUTTON_NEGATIVE:
    390             default:
    391                 onCancel();
    392                 break;
    393         }
    394     }
    395 
    396     /* Not used */
    397     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    398     }
    399 
    400     /* Not used */
    401     public void onTextChanged(CharSequence s, int start, int before, int count) {
    402     }
    403 
    404     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    405         // change input type for soft keyboard to numeric or alphanumeric
    406         if (isChecked) {
    407             mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
    408         } else {
    409             mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
    410         }
    411     }
    412 }
    413