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