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;
42 import com.android.internal.app.AlertActivity;
43 import com.android.internal.app.AlertController;
44 import com.android.settings.R;
45 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
46 import com.android.settingslib.bluetooth.LocalBluetoothManager;
47 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
49 import java.util.Locale;
52 * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
53 * for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
55 public final class BluetoothPairingDialog extends AlertActivity implements
56 CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
57 private static final String TAG = "BluetoothPairingDialog";
59 private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
60 private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
62 private LocalBluetoothManager mBluetoothManager;
63 private CachedBluetoothDeviceManager mCachedDeviceManager;
64 private BluetoothDevice mDevice;
66 private String mPairingKey;
67 private EditText mPairingView;
68 private Button mOkButton;
69 private LocalBluetoothProfile mPbapClientProfile;
70 private boolean mReceiverRegistered;
73 * Dismiss the dialog if the bond state changes to bonded or none,
74 * or if pairing was canceled for {@link #mDevice}.
76 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
78 public void onReceive(Context context, Intent intent) {
79 String action = intent.getAction();
80 if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
81 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
82 BluetoothDevice.ERROR);
83 if (bondState == BluetoothDevice.BOND_BONDED ||
84 bondState == BluetoothDevice.BOND_NONE) {
87 } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
88 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
89 if (device == null || device.equals(mDevice)) {
97 protected void onCreate(Bundle savedInstanceState) {
98 super.onCreate(savedInstanceState);
99 mReceiverRegistered = false;
101 Intent intent = getIntent();
102 if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
103 Log.e(TAG, "Error: this activity may be started only with intent " +
104 BluetoothDevice.ACTION_PAIRING_REQUEST);
109 mBluetoothManager = Utils.getLocalBtManager(this);
110 if (mBluetoothManager == null) {
111 Log.e(TAG, "Error: BluetoothAdapter not supported by system");
115 mCachedDeviceManager = mBluetoothManager.getCachedDeviceManager();
116 mPbapClientProfile = mBluetoothManager.getProfileManager().getPbapClientProfile();
118 mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
119 mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
122 case BluetoothDevice.PAIRING_VARIANT_PIN:
123 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
124 case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
125 createUserEntryDialog();
128 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
130 intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
131 if (passkey == BluetoothDevice.ERROR) {
132 Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog");
133 mDevice.setPairingConfirmation(false);
137 mPairingKey = String.format(Locale.US, "%06d", passkey);
138 createConfirmationDialog();
141 case BluetoothDevice.PAIRING_VARIANT_CONSENT:
142 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
143 createConsentDialog();
146 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
147 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
149 intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
150 if (pairingKey == BluetoothDevice.ERROR) {
151 Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog");
155 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
156 mPairingKey = String.format("%06d", pairingKey);
158 mPairingKey = String.format("%04d", pairingKey);
160 createDisplayPasskeyOrPinDialog();
164 Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
170 * Leave this registered through pause/resume since we still want to
171 * finish the activity in the background if pairing is canceled.
173 registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_CANCEL));
174 registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
175 mReceiverRegistered = true;
178 private void createUserEntryDialog() {
179 final AlertController.AlertParams p = mAlertParams;
180 p.mTitle = getString(R.string.bluetooth_pairing_request,
181 mCachedDeviceManager.getName(mDevice));
182 p.mView = createPinEntryView();
183 p.mPositiveButtonText = getString(android.R.string.ok);
184 p.mPositiveButtonListener = this;
185 p.mNegativeButtonText = getString(android.R.string.cancel);
186 p.mNegativeButtonListener = this;
189 mOkButton = mAlert.getButton(BUTTON_POSITIVE);
190 mOkButton.setEnabled(false);
193 private View createPinEntryView() {
194 View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
195 TextView messageViewCaptionHint = (TextView) view.findViewById(R.id.pin_values_hint);
196 TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
197 CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
198 CheckBox contactSharing = (CheckBox) view.findViewById(
199 R.id.phonebook_sharing_message_entry_pin);
200 contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook,
201 mCachedDeviceManager.getName(mDevice)));
202 if (mPbapClientProfile != null && mPbapClientProfile.isProfileReady()) {
203 contactSharing.setVisibility(View.GONE);
205 if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_ALLOWED) {
206 contactSharing.setChecked(true);
207 } else if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_REJECTED){
208 contactSharing.setChecked(false);
210 if (mDevice.getBluetoothClass().getDeviceClass()
211 == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
212 contactSharing.setChecked(true);
213 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
215 contactSharing.setChecked(false);
216 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
220 contactSharing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
222 public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
224 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
226 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
231 mPairingView = (EditText) view.findViewById(R.id.text);
232 mPairingView.addTextChangedListener(this);
233 alphanumericPin.setOnCheckedChangeListener(this);
236 int messageIdHint = R.string.bluetooth_pin_values_hint;
239 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
240 messageIdHint = R.string.bluetooth_pin_values_hint_16_digits;
242 case BluetoothDevice.PAIRING_VARIANT_PIN:
243 messageId = R.string.bluetooth_enter_pin_other_device;
244 // Maximum of 16 characters in a PIN
245 maxLength = BLUETOOTH_PIN_MAX_LENGTH;
248 case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
249 messageId = R.string.bluetooth_enter_passkey_other_device;
250 // Maximum of 6 digits for passkey
251 maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
252 alphanumericPin.setVisibility(View.GONE);
256 Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
260 messageViewCaptionHint.setText(messageIdHint);
261 messageView2.setText(messageId);
262 mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
263 mPairingView.setFilters(new InputFilter[] {
264 new LengthFilter(maxLength) });
269 private View createView() {
270 View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
271 TextView pairingViewCaption = (TextView) view.findViewById(R.id.pairing_caption);
272 TextView pairingViewContent = (TextView) view.findViewById(R.id.pairing_subhead);
273 TextView messagePairing = (TextView) view.findViewById(R.id.pairing_code_message);
274 CheckBox contactSharing = (CheckBox) view.findViewById(
275 R.id.phonebook_sharing_message_confirm_pin);
276 contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook,
277 mCachedDeviceManager.getName(mDevice)));
278 if (mPbapClientProfile != null && mPbapClientProfile.isProfileReady()) {
279 contactSharing.setVisibility(View.GONE);
281 if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_ALLOWED) {
282 contactSharing.setChecked(true);
283 } else if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_REJECTED){
284 contactSharing.setChecked(false);
286 if (mDevice.getBluetoothClass().getDeviceClass()
287 == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
288 contactSharing.setChecked(true);
289 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
291 contactSharing.setChecked(false);
292 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
296 contactSharing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
298 public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
300 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
302 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
307 String messageCaption = null;
308 String pairingContent = null;
310 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
311 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
312 messagePairing.setVisibility(View.VISIBLE);
313 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
314 pairingContent = mPairingKey;
317 case BluetoothDevice.PAIRING_VARIANT_CONSENT:
318 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
319 messagePairing.setVisibility(View.VISIBLE);
323 Log.e(TAG, "Incorrect pairing type received, not creating view");
327 if (pairingContent != null) {
328 pairingViewCaption.setVisibility(View.VISIBLE);
329 pairingViewContent.setVisibility(View.VISIBLE);
330 pairingViewContent.setText(pairingContent);
336 private void createConfirmationDialog() {
337 final AlertController.AlertParams p = mAlertParams;
338 p.mTitle = getString(R.string.bluetooth_pairing_request,
339 mCachedDeviceManager.getName(mDevice));
340 p.mView = createView();
341 p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
342 p.mPositiveButtonListener = this;
343 p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
344 p.mNegativeButtonListener = this;
348 private void createConsentDialog() {
349 final AlertController.AlertParams p = mAlertParams;
350 p.mTitle = getString(R.string.bluetooth_pairing_request,
351 mCachedDeviceManager.getName(mDevice));
352 p.mView = createView();
353 p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
354 p.mPositiveButtonListener = this;
355 p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
356 p.mNegativeButtonListener = this;
360 private void createDisplayPasskeyOrPinDialog() {
361 final AlertController.AlertParams p = mAlertParams;
362 p.mTitle = getString(R.string.bluetooth_pairing_request,
363 mCachedDeviceManager.getName(mDevice));
364 p.mView = createView();
365 p.mNegativeButtonText = getString(android.R.string.cancel);
366 p.mNegativeButtonListener = this;
369 // Since its only a notification, send an OK to the framework,
370 // indicating that the dialog has been displayed.
371 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
372 mDevice.setPairingConfirmation(true);
373 } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
374 byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPairingKey);
375 mDevice.setPin(pinBytes);
380 protected void onDestroy() {
382 if (mReceiverRegistered) {
383 mReceiverRegistered = false;
384 unregisterReceiver(mReceiver);
388 public void afterTextChanged(Editable s) {
389 if (mOkButton != null) {
390 if (mType == BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS) {
391 mOkButton.setEnabled(s.length() >= 16);
393 mOkButton.setEnabled(s.length() > 0);
398 private void onPair(String value) {
399 Log.i(TAG, "Pairing dialog accepted");
401 case BluetoothDevice.PAIRING_VARIANT_PIN:
402 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
403 byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
404 if (pinBytes == null) {
407 mDevice.setPin(pinBytes);
410 case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
411 int passkey = Integer.parseInt(value);
412 mDevice.setPasskey(passkey);
415 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
416 case BluetoothDevice.PAIRING_VARIANT_CONSENT:
417 mDevice.setPairingConfirmation(true);
420 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
421 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
425 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
426 mDevice.setRemoteOutOfBandData();
430 Log.e(TAG, "Incorrect pairing type received");
434 private void onCancel() {
435 Log.i(TAG, "Pairing dialog canceled");
436 mDevice.cancelPairingUserInput();
439 public boolean onKeyDown(int keyCode, KeyEvent event) {
440 if (keyCode == KeyEvent.KEYCODE_BACK) {
443 return super.onKeyDown(keyCode,event);
446 public void onClick(DialogInterface dialog, int which) {
448 case BUTTON_POSITIVE:
449 if (mPairingView != null) {
450 onPair(mPairingView.getText().toString());
456 case BUTTON_NEGATIVE:
464 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
468 public void onTextChanged(CharSequence s, int start, int before, int count) {
471 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
472 // change input type for soft keyboard to numeric or alphanumeric
474 mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
476 mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);