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