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