BluetoothPairingDialog.java revision e49c30e533be90280b4a6e9cb0065adb1b43b104
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 17package com.android.settings.bluetooth; 18 19import android.bluetooth.BluetoothDevice; 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.DialogInterface; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.os.Bundle; 26import android.text.Editable; 27import android.text.Html; 28import android.text.InputFilter; 29import android.text.InputType; 30import android.text.Spanned; 31import android.text.TextWatcher; 32import android.text.InputFilter.LengthFilter; 33import android.util.Log; 34import android.view.View; 35import android.widget.Button; 36import android.widget.CheckBox; 37import android.widget.CompoundButton; 38import android.widget.EditText; 39import android.widget.TextView; 40 41import com.android.internal.app.AlertActivity; 42import com.android.internal.app.AlertController; 43import com.android.settings.R; 44import android.view.KeyEvent; 45 46import 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 */ 52public 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.mTitle = getString(R.string.bluetooth_pairing_request); 165 p.mView = createPinEntryView(deviceManager.getName(mDevice)); 166 p.mPositiveButtonText = getString(android.R.string.ok); 167 p.mPositiveButtonListener = this; 168 p.mNegativeButtonText = getString(android.R.string.cancel); 169 p.mNegativeButtonListener = this; 170 setupAlert(); 171 172 mOkButton = mAlert.getButton(BUTTON_POSITIVE); 173 mOkButton.setEnabled(false); 174 } 175 176 private View createPinEntryView(String deviceName) { 177 View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null); 178 TextView messageViewCaption = (TextView) view.findViewById(R.id.message_caption); 179 TextView messageViewContent = (TextView) view.findViewById(R.id.message_subhead); 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_pin_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 messageViewCaption.setText(messageId1); 211 messageViewContent.setText(deviceName); 212 messageView2.setText(messageId2); 213 mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER); 214 mPairingView.setFilters(new InputFilter[] { 215 new LengthFilter(maxLength) }); 216 217 return view; 218 } 219 220 private View createView(CachedBluetoothDeviceManager deviceManager) { 221 View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null); 222 // Escape device name to avoid HTML injection. 223 String name = Html.escapeHtml(deviceManager.getName(mDevice)); 224 TextView messageViewCaption = (TextView) view.findViewById(R.id.message_caption); 225 TextView messageViewContent = (TextView) view.findViewById(R.id.message_subhead); 226 TextView pairingViewCaption = (TextView) view.findViewById(R.id.pairing_caption); 227 TextView pairingViewContent = (TextView) view.findViewById(R.id.pairing_subhead); 228 TextView messagePairing = (TextView) view.findViewById(R.id.pairing_code_message); 229 230 String messageCaption = null; 231 String pairingContent = null; 232 switch (mType) { 233 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: 234 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: 235 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: 236 messageCaption = getString(R.string.bluetooth_enter_pin_msg); 237 pairingContent = mPairingKey; 238 break; 239 240 case BluetoothDevice.PAIRING_VARIANT_CONSENT: 241 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: 242 messageCaption = getString(R.string.bluetooth_enter_pin_msg); 243 break; 244 245 default: 246 Log.e(TAG, "Incorrect pairing type received, not creating view"); 247 return null; 248 } 249 250 if (messageViewCaption != null) { 251 messageViewCaption.setText(messageCaption); 252 messageViewContent.setText(name); 253 } 254 255 if (pairingContent != null) { 256 pairingViewCaption.setVisibility(View.VISIBLE); 257 pairingViewContent.setVisibility(View.VISIBLE); 258 pairingViewContent.setText(pairingContent); 259 messagePairing.setVisibility(View.VISIBLE); 260 } 261 262 return view; 263 } 264 265 private void createConfirmationDialog(CachedBluetoothDeviceManager deviceManager) { 266 final AlertController.AlertParams p = mAlertParams; 267 p.mTitle = getString(R.string.bluetooth_pairing_request); 268 p.mView = createView(deviceManager); 269 p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept); 270 p.mPositiveButtonListener = this; 271 p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline); 272 p.mNegativeButtonListener = this; 273 setupAlert(); 274 } 275 276 private void createConsentDialog(CachedBluetoothDeviceManager deviceManager) { 277 final AlertController.AlertParams p = mAlertParams; 278 p.mTitle = getString(R.string.bluetooth_pairing_request); 279 p.mView = createView(deviceManager); 280 p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept); 281 p.mPositiveButtonListener = this; 282 p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline); 283 p.mNegativeButtonListener = this; 284 setupAlert(); 285 } 286 287 private void createDisplayPasskeyOrPinDialog( 288 CachedBluetoothDeviceManager deviceManager) { 289 final AlertController.AlertParams p = mAlertParams; 290 p.mTitle = getString(R.string.bluetooth_pairing_request); 291 p.mView = createView(deviceManager); 292 p.mNegativeButtonText = getString(android.R.string.cancel); 293 p.mNegativeButtonListener = this; 294 setupAlert(); 295 296 // Since its only a notification, send an OK to the framework, 297 // indicating that the dialog has been displayed. 298 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) { 299 mDevice.setPairingConfirmation(true); 300 } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) { 301 byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPairingKey); 302 mDevice.setPin(pinBytes); 303 } 304 } 305 306 @Override 307 protected void onDestroy() { 308 super.onDestroy(); 309 unregisterReceiver(mReceiver); 310 } 311 312 public void afterTextChanged(Editable s) { 313 if (mOkButton != null) { 314 mOkButton.setEnabled(s.length() > 0); 315 } 316 } 317 318 private void onPair(String value) { 319 switch (mType) { 320 case BluetoothDevice.PAIRING_VARIANT_PIN: 321 byte[] pinBytes = BluetoothDevice.convertPinToBytes(value); 322 if (pinBytes == null) { 323 return; 324 } 325 mDevice.setPin(pinBytes); 326 break; 327 328 case BluetoothDevice.PAIRING_VARIANT_PASSKEY: 329 int passkey = Integer.parseInt(value); 330 mDevice.setPasskey(passkey); 331 break; 332 333 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: 334 case BluetoothDevice.PAIRING_VARIANT_CONSENT: 335 mDevice.setPairingConfirmation(true); 336 break; 337 338 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: 339 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: 340 // Do nothing. 341 break; 342 343 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: 344 mDevice.setRemoteOutOfBandData(); 345 break; 346 347 default: 348 Log.e(TAG, "Incorrect pairing type received"); 349 } 350 } 351 352 private void onCancel() { 353 mDevice.cancelPairingUserInput(); 354 } 355 356 public boolean onKeyDown(int keyCode, KeyEvent event) { 357 if (keyCode == KeyEvent.KEYCODE_BACK) { 358 onCancel(); 359 } 360 return super.onKeyDown(keyCode,event); 361 } 362 363 public void onClick(DialogInterface dialog, int which) { 364 switch (which) { 365 case BUTTON_POSITIVE: 366 if (mPairingView != null) { 367 onPair(mPairingView.getText().toString()); 368 } else { 369 onPair(null); 370 } 371 break; 372 373 case BUTTON_NEGATIVE: 374 default: 375 onCancel(); 376 break; 377 } 378 } 379 380 /* Not used */ 381 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 382 } 383 384 /* Not used */ 385 public void onTextChanged(CharSequence s, int start, int before, int count) { 386 } 387 388 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 389 // change input type for soft keyboard to numeric or alphanumeric 390 if (isChecked) { 391 mPairingView.setInputType(InputType.TYPE_CLASS_TEXT); 392 } else { 393 mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER); 394 } 395 } 396} 397