2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.bluetooth;
19 import android.bluetooth.BluetoothClass;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothUuid;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.os.Bundle;
28 import android.text.Editable;
29 import android.text.InputFilter;
30 import android.text.InputFilter.LengthFilter;
31 import android.text.InputType;
32 import android.text.TextWatcher;
33 import android.util.Log;
34 import android.view.KeyEvent;
35 import android.view.View;
36 import android.widget.Button;
37 import android.widget.CheckBox;
38 import android.widget.CompoundButton;
39 import android.widget.EditText;
40 import android.widget.TextView;
41 import android.os.Handler;
42 import android.os.Message;
44 import com.android.internal.app.AlertActivity;
45 import com.android.internal.app.AlertController;
46 import com.android.settings.R;
47 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
48 import com.android.settingslib.bluetooth.LocalBluetoothManager;
49 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
51 import java.util.Locale;
54 * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
55 * for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
57 public final class BluetoothPairingDialog extends AlertActivity implements
58 CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
59 private static final String TAG = "BluetoothPairingDialog";
61 private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
62 private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
63 private static final int PAIRING_POPUP_TIMEOUT = 35000;
64 private static final int MESSAGE_DELAYED_DISMISS = 1;
66 private LocalBluetoothManager mBluetoothManager;
67 private CachedBluetoothDeviceManager mCachedDeviceManager;
68 private BluetoothDevice mDevice;
70 private String mPairingKey;
71 private EditText mPairingView;
72 private Button mOkButton;
73 private LocalBluetoothProfile mPbapClientProfile;
74 private boolean mReceiverRegistered;
77 * Dismiss the dialog if the bond state changes to bonded or none,
78 * or if pairing was canceled for {@link #mDevice}.
80 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
82 public void onReceive(Context context, Intent intent) {
83 String action = intent.getAction();
84 if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
85 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
86 BluetoothDevice.ERROR);
87 if (bondState == BluetoothDevice.BOND_BONDED ||
88 bondState == BluetoothDevice.BOND_NONE) {
91 } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
92 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
93 if (device == null || device.equals(mDevice)) {
101 protected void onCreate(Bundle savedInstanceState) {
102 super.onCreate(savedInstanceState);
103 mReceiverRegistered = false;
105 Intent intent = getIntent();
106 if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
107 Log.e(TAG, "Error: this activity may be started only with intent " +
108 BluetoothDevice.ACTION_PAIRING_REQUEST);
113 mBluetoothManager = Utils.getLocalBtManager(this);
114 if (mBluetoothManager == null) {
115 Log.e(TAG, "Error: BluetoothAdapter not supported by system");
119 mCachedDeviceManager = mBluetoothManager.getCachedDeviceManager();
120 mPbapClientProfile = mBluetoothManager.getProfileManager().getPbapClientProfile();
122 mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
123 mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
126 case BluetoothDevice.PAIRING_VARIANT_PIN:
127 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
128 case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
129 createUserEntryDialog();
133 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
135 intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
136 if (passkey == BluetoothDevice.ERROR) {
137 Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog");
138 mDevice.setPairingConfirmation(false);
142 mPairingKey = String.format(Locale.US, "%06d", passkey);
143 createConfirmationDialog();
147 case BluetoothDevice.PAIRING_VARIANT_CONSENT:
148 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
149 createConsentDialog();
153 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
154 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
156 intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
157 if (pairingKey == BluetoothDevice.ERROR) {
158 Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog");
162 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
163 mPairingKey = String.format("%06d", pairingKey);
165 mPairingKey = String.format("%04d", pairingKey);
167 createDisplayPasskeyOrPinDialog();
172 Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
178 * Leave this registered through pause/resume since we still want to
179 * finish the activity in the background if pairing is canceled.
181 registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_CANCEL));
182 registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
183 mReceiverRegistered = true;
186 private void createUserEntryDialog() {
187 final AlertController.AlertParams p = mAlertParams;
188 p.mTitle = getString(R.string.bluetooth_pairing_request,
189 mCachedDeviceManager.getName(mDevice));
190 p.mView = createPinEntryView();
191 p.mPositiveButtonText = getString(android.R.string.ok);
192 p.mPositiveButtonListener = this;
193 p.mNegativeButtonText = getString(android.R.string.cancel);
194 p.mNegativeButtonListener = this;
197 mOkButton = mAlert.getButton(BUTTON_POSITIVE);
198 mOkButton.setEnabled(false);
201 private View createPinEntryView() {
202 View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
203 TextView messageViewCaptionHint = (TextView) view.findViewById(R.id.pin_values_hint);
204 TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
205 CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
206 CheckBox contactSharing = (CheckBox) view.findViewById(
207 R.id.phonebook_sharing_message_entry_pin);
208 contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook,
209 mCachedDeviceManager.getName(mDevice)));
210 if (mPbapClientProfile != null && mPbapClientProfile.isProfileReady()) {
211 contactSharing.setVisibility(View.GONE);
213 if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_ALLOWED) {
214 contactSharing.setChecked(true);
215 } else if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_REJECTED){
216 contactSharing.setChecked(false);
218 if ((mDevice.getBluetoothClass() != null) && (mDevice.getBluetoothClass().getDeviceClass()
219 == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE)) {
220 contactSharing.setChecked(true);
221 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
223 contactSharing.setChecked(false);
224 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
228 contactSharing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
230 public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
232 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
234 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
239 mPairingView = (EditText) view.findViewById(R.id.text);
240 mPairingView.addTextChangedListener(this);
241 alphanumericPin.setOnCheckedChangeListener(this);
244 int messageIdHint = R.string.bluetooth_pin_values_hint;
247 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
248 messageIdHint = R.string.bluetooth_pin_values_hint_16_digits;
250 case BluetoothDevice.PAIRING_VARIANT_PIN:
251 messageId = R.string.bluetooth_enter_pin_other_device;
252 // Maximum of 16 characters in a PIN
253 maxLength = BLUETOOTH_PIN_MAX_LENGTH;
256 case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
257 messageId = R.string.bluetooth_enter_passkey_other_device;
258 // Maximum of 6 digits for passkey
259 maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
260 alphanumericPin.setVisibility(View.GONE);
264 Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
268 messageViewCaptionHint.setText(messageIdHint);
269 messageView2.setText(messageId);
270 mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
271 mPairingView.setFilters(new InputFilter[] {
272 new LengthFilter(maxLength) });
277 private View createView() {
278 View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
279 TextView pairingViewCaption = (TextView) view.findViewById(R.id.pairing_caption);
280 TextView pairingViewContent = (TextView) view.findViewById(R.id.pairing_subhead);
281 TextView messagePairing = (TextView) view.findViewById(R.id.pairing_code_message);
282 CheckBox contactSharing = (CheckBox) view.findViewById(
283 R.id.phonebook_sharing_message_confirm_pin);
284 contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook,
285 mCachedDeviceManager.getName(mDevice)));
286 if (mPbapClientProfile != null && mPbapClientProfile.isProfileReady()) {
287 contactSharing.setVisibility(View.GONE);
289 if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_ALLOWED) {
290 contactSharing.setChecked(true);
291 } else if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_REJECTED){
292 contactSharing.setChecked(false);
294 if (mDevice.getBluetoothClass().getDeviceClass()
295 == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
296 contactSharing.setChecked(true);
297 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
299 contactSharing.setChecked(false);
300 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
304 contactSharing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
306 public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
308 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
310 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
315 String messageCaption = null;
316 String pairingContent = null;
318 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
319 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
320 messagePairing.setVisibility(View.VISIBLE);
321 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
322 pairingContent = mPairingKey;
325 case BluetoothDevice.PAIRING_VARIANT_CONSENT:
326 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
327 messagePairing.setVisibility(View.VISIBLE);
331 Log.e(TAG, "Incorrect pairing type received, not creating view");
335 if (pairingContent != null) {
336 pairingViewCaption.setVisibility(View.VISIBLE);
337 pairingViewContent.setVisibility(View.VISIBLE);
338 pairingViewContent.setText(pairingContent);
344 private void createConfirmationDialog() {
345 final AlertController.AlertParams p = mAlertParams;
346 p.mTitle = getString(R.string.bluetooth_pairing_request,
347 mCachedDeviceManager.getName(mDevice));
348 p.mView = createView();
349 p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
350 p.mPositiveButtonListener = this;
351 p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
352 p.mNegativeButtonListener = this;
356 private void createConsentDialog() {
357 final AlertController.AlertParams p = mAlertParams;
358 p.mTitle = getString(R.string.bluetooth_pairing_request,
359 mCachedDeviceManager.getName(mDevice));
360 p.mView = createView();
361 p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
362 p.mPositiveButtonListener = this;
363 p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
364 p.mNegativeButtonListener = this;
368 private void createDisplayPasskeyOrPinDialog() {
369 final AlertController.AlertParams p = mAlertParams;
370 p.mTitle = getString(R.string.bluetooth_pairing_request,
371 mCachedDeviceManager.getName(mDevice));
372 p.mView = createView();
373 p.mNegativeButtonText = getString(android.R.string.cancel);
374 p.mNegativeButtonListener = this;
377 // Since its only a notification, send an OK to the framework,
378 // indicating that the dialog has been displayed.
379 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
380 mDevice.setPairingConfirmation(true);
381 } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
382 byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPairingKey);
383 mDevice.setPin(pinBytes);
388 protected void onDestroy() {
390 if (mReceiverRegistered) {
391 mReceiverRegistered = false;
392 unregisterReceiver(mReceiver);
396 public void afterTextChanged(Editable s) {
397 if (mOkButton != null) {
398 if (mType == BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS) {
399 mOkButton.setEnabled(s.length() >= 16);
401 mOkButton.setEnabled(s.length() > 0);
406 private void onPair(String value) {
407 Log.i(TAG, "Pairing dialog accepted");
409 case BluetoothDevice.PAIRING_VARIANT_PIN:
410 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
411 byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
412 if (pinBytes == null) {
415 mDevice.setPin(pinBytes);
418 case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
419 int passkey = Integer.parseInt(value);
420 mDevice.setPasskey(passkey);
423 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
424 case BluetoothDevice.PAIRING_VARIANT_CONSENT:
425 mDevice.setPairingConfirmation(true);
428 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
429 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
433 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
434 mDevice.setRemoteOutOfBandData();
438 Log.e(TAG, "Incorrect pairing type received");
442 private void onCancel() {
443 Log.i(TAG, "Pairing dialog canceled");
444 mDevice.cancelPairingUserInput();
447 public boolean onKeyDown(int keyCode, KeyEvent event) {
448 if (keyCode == KeyEvent.KEYCODE_BACK) {
451 return super.onKeyDown(keyCode,event);
454 public void onClick(DialogInterface dialog, int which) {
456 case BUTTON_POSITIVE:
457 if (mPairingView != null) {
458 onPair(mPairingView.getText().toString());
464 case BUTTON_NEGATIVE:
472 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
476 public void onTextChanged(CharSequence s, int start, int before, int count) {
479 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
480 // change input type for soft keyboard to numeric or alphanumeric
482 mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
484 mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
488 private void popTimedout() {
490 Message message = mHandler.obtainMessage(MESSAGE_DELAYED_DISMISS);
491 mHandler.sendMessageDelayed(message, PAIRING_POPUP_TIMEOUT);
495 private final Handler mHandler = new Handler() {
497 public void handleMessage(Message msg) {
499 case MESSAGE_DELAYED_DISMISS:
500 Log.v(TAG, "Delayed pairing pop up handler");