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     private BluetoothDevice mDevice;
     59     private int mType;
     60     private String mPairingKey;
     61     private EditText mPairingView;
     62     private Button mOkButton;
     63 
     64     /**
     65      * Dismiss the dialog if the bond state changes to bonded or none,
     66      * or if pairing was canceled for {@link #mDevice}.
     67      */
     68     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
     69         @Override
     70         public void onReceive(Context context, Intent intent) {
     71             String action = intent.getAction();
     72             if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
     73                 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
     74                                                    BluetoothDevice.ERROR);
     75                 if (bondState == BluetoothDevice.BOND_BONDED ||
     76                         bondState == BluetoothDevice.BOND_NONE) {
     77                     dismiss();
     78                 }
     79             } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
     80                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
     81                 if (device == null || device.equals(mDevice)) {
     82                     dismiss();
     83                 }
     84             }
     85         }
     86     };
     87 
     88     @Override
     89     protected void onCreate(Bundle savedInstanceState) {
     90         super.onCreate(savedInstanceState);
     91 
     92         Intent intent = getIntent();
     93         if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST))
     94         {
     95             Log.e(TAG, "Error: this activity may be started only with intent " +
     96                   BluetoothDevice.ACTION_PAIRING_REQUEST);
     97             finish();
     98             return;
     99         }
    100 
    101         LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this);
    102         if (manager == null) {
    103             Log.e(TAG, "Error: BluetoothAdapter not supported by system");
    104             finish();
    105             return;
    106         }
    107         CachedBluetoothDeviceManager deviceManager = manager.getCachedDeviceManager();
    108 
    109         mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    110         mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
    111 
    112         switch (mType) {
    113             case BluetoothDevice.PAIRING_VARIANT_PIN:
    114             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    115                 createUserEntryDialog(deviceManager);
    116                 break;
    117 
    118             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    119                 int passkey =
    120                     intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
    121                 if (passkey == BluetoothDevice.ERROR) {
    122                     Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog");
    123                     return;
    124                 }
    125                 mPairingKey = String.format(Locale.US, "%06d", passkey);
    126                 createConfirmationDialog(deviceManager);
    127                 break;
    128 
    129             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
    130             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    131                 createConsentDialog(deviceManager);
    132                 break;
    133 
    134             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    135             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    136                 int pairingKey =
    137                     intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
    138                 if (pairingKey == BluetoothDevice.ERROR) {
    139                     Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog");
    140                     return;
    141                 }
    142                 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
    143                     mPairingKey = String.format("%06d", pairingKey);
    144                 } else {
    145                     mPairingKey = String.format("%04d", pairingKey);
    146                 }
    147                 createDisplayPasskeyOrPinDialog(deviceManager);
    148                 break;
    149 
    150             default:
    151                 Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
    152         }
    153 
    154         /*
    155          * Leave this registered through pause/resume since we still want to
    156          * finish the activity in the background if pairing is canceled.
    157          */
    158         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_CANCEL));
    159         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
    160     }
    161 
    162     private void createUserEntryDialog(CachedBluetoothDeviceManager deviceManager) {
    163         final AlertController.AlertParams p = mAlertParams;
    164         p.mIconId = android.R.drawable.ic_dialog_info;
    165         p.mTitle = getString(R.string.bluetooth_pairing_request);
    166         p.mView = createPinEntryView(deviceManager.getName(mDevice));
    167         p.mPositiveButtonText = getString(android.R.string.ok);
    168         p.mPositiveButtonListener = this;
    169         p.mNegativeButtonText = getString(android.R.string.cancel);
    170         p.mNegativeButtonListener = this;
    171         setupAlert();
    172 
    173         mOkButton = mAlert.getButton(BUTTON_POSITIVE);
    174         mOkButton.setEnabled(false);
    175     }
    176 
    177     private View createPinEntryView(String deviceName) {
    178         View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
    179         TextView messageView = (TextView) view.findViewById(R.id.message);
    180         TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
    181         CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
    182         mPairingView = (EditText) view.findViewById(R.id.text);
    183         mPairingView.addTextChangedListener(this);
    184         alphanumericPin.setOnCheckedChangeListener(this);
    185 
    186         int messageId1;
    187         int messageId2;
    188         int maxLength;
    189         switch (mType) {
    190             case BluetoothDevice.PAIRING_VARIANT_PIN:
    191                 messageId1 = R.string.bluetooth_enter_pin_msg;
    192                 messageId2 = R.string.bluetooth_enter_pin_other_device;
    193                 // Maximum of 16 characters in a PIN
    194                 maxLength = BLUETOOTH_PIN_MAX_LENGTH;
    195                 break;
    196 
    197             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    198                 messageId1 = R.string.bluetooth_enter_passkey_msg;
    199                 messageId2 = R.string.bluetooth_enter_passkey_other_device;
    200                 // Maximum of 6 digits for passkey
    201                 maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
    202                 alphanumericPin.setVisibility(View.GONE);
    203                 break;
    204 
    205             default:
    206                 Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
    207                 return null;
    208         }
    209 
    210         // HTML escape deviceName, Format the message string, then parse HTML style tags
    211         String messageText = getString(messageId1, Html.escapeHtml(deviceName));
    212         messageView.setText(Html.fromHtml(messageText));
    213         messageView2.setText(messageId2);
    214         mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
    215         mPairingView.setFilters(new InputFilter[] {
    216                 new LengthFilter(maxLength) });
    217 
    218         return view;
    219     }
    220 
    221     private View createView(CachedBluetoothDeviceManager deviceManager) {
    222         View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
    223 	// Escape device name to avoid HTML injection.
    224         String name = Html.escapeHtml(deviceManager.getName(mDevice));
    225         TextView messageView = (TextView) view.findViewById(R.id.message);
    226 
    227         String messageText; // formatted string containing HTML style tags
    228         switch (mType) {
    229             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    230                 messageText = getString(R.string.bluetooth_confirm_passkey_msg,
    231                         name, mPairingKey);
    232                 break;
    233 
    234             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
    235             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    236                 messageText = getString(R.string.bluetooth_incoming_pairing_msg, name);
    237                 break;
    238 
    239             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    240             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    241                 messageText = getString(R.string.bluetooth_display_passkey_pin_msg, name,
    242                         mPairingKey);
    243                 break;
    244 
    245             default:
    246                 Log.e(TAG, "Incorrect pairing type received, not creating view");
    247                 return null;
    248         }
    249         messageView.setText(Html.fromHtml(messageText));
    250         return view;
    251     }
    252 
    253     private void createConfirmationDialog(CachedBluetoothDeviceManager deviceManager) {
    254         final AlertController.AlertParams p = mAlertParams;
    255         p.mIconId = android.R.drawable.ic_dialog_info;
    256         p.mTitle = getString(R.string.bluetooth_pairing_request);
    257         p.mView = createView(deviceManager);
    258         p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
    259         p.mPositiveButtonListener = this;
    260         p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
    261         p.mNegativeButtonListener = this;
    262         setupAlert();
    263     }
    264 
    265     private void createConsentDialog(CachedBluetoothDeviceManager deviceManager) {
    266         final AlertController.AlertParams p = mAlertParams;
    267         p.mIconId = android.R.drawable.ic_dialog_info;
    268         p.mTitle = getString(R.string.bluetooth_pairing_request);
    269         p.mView = createView(deviceManager);
    270         p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
    271         p.mPositiveButtonListener = this;
    272         p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
    273         p.mNegativeButtonListener = this;
    274         setupAlert();
    275     }
    276 
    277     private void createDisplayPasskeyOrPinDialog(
    278             CachedBluetoothDeviceManager deviceManager) {
    279         final AlertController.AlertParams p = mAlertParams;
    280         p.mIconId = android.R.drawable.ic_dialog_info;
    281         p.mTitle = getString(R.string.bluetooth_pairing_request);
    282         p.mView = createView(deviceManager);
    283         p.mNegativeButtonText = getString(android.R.string.cancel);
    284         p.mNegativeButtonListener = this;
    285         setupAlert();
    286 
    287         // Since its only a notification, send an OK to the framework,
    288         // 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(mPairingKey);
    293             mDevice.setPin(pinBytes);
    294         }
    295     }
    296 
    297     @Override
    298     protected void onDestroy() {
    299         super.onDestroy();
    300         unregisterReceiver(mReceiver);
    301     }
    302 
    303     public void afterTextChanged(Editable s) {
    304         if (mOkButton != null) {
    305             mOkButton.setEnabled(s.length() > 0);
    306         }
    307     }
    308 
    309     private void onPair(String value) {
    310         switch (mType) {
    311             case BluetoothDevice.PAIRING_VARIANT_PIN:
    312                 byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
    313                 if (pinBytes == null) {
    314                     return;
    315                 }
    316                 mDevice.setPin(pinBytes);
    317                 break;
    318 
    319             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    320                 int passkey = Integer.parseInt(value);
    321                 mDevice.setPasskey(passkey);
    322                 break;
    323 
    324             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    325             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
    326                 mDevice.setPairingConfirmation(true);
    327                 break;
    328 
    329             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    330             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    331                 // Do nothing.
    332                 break;
    333 
    334             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    335                 mDevice.setRemoteOutOfBandData();
    336                 break;
    337 
    338             default:
    339                 Log.e(TAG, "Incorrect pairing type received");
    340         }
    341     }
    342 
    343     private void onCancel() {
    344         mDevice.cancelPairingUserInput();
    345     }
    346 
    347     public boolean onKeyDown(int keyCode, KeyEvent event) {
    348         if (keyCode == KeyEvent.KEYCODE_BACK) {
    349             onCancel();
    350         }
    351         return super.onKeyDown(keyCode,event);
    352     }
    353 
    354     public void onClick(DialogInterface dialog, int which) {
    355         switch (which) {
    356             case BUTTON_POSITIVE:
    357                 if (mPairingView != null) {
    358                     onPair(mPairingView.getText().toString());
    359                 } else {
    360                     onPair(null);
    361                 }
    362                 break;
    363 
    364             case BUTTON_NEGATIVE:
    365             default:
    366                 onCancel();
    367                 break;
    368         }
    369     }
    370 
    371     /* Not used */
    372     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    373     }
    374 
    375     /* Not used */
    376     public void onTextChanged(CharSequence s, int start, int before, int count) {
    377     }
    378 
    379     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    380         // change input type for soft keyboard to numeric or alphanumeric
    381         if (isChecked) {
    382             mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
    383         } else {
    384             mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
    385         }
    386     }
    387 }
    388