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.EventLog;
34 import android.util.Log;
35 import android.view.KeyEvent;
36 import android.view.View;
37 import android.widget.Button;
38 import android.widget.CheckBox;
39 import android.widget.CompoundButton;
40 import android.widget.EditText;
41 import android.widget.TextView;
42 import android.os.Handler;
43 import android.os.Message;
45 import com.android.internal.app.AlertActivity;
46 import com.android.internal.app.AlertController;
47 import com.android.settings.R;
48 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
49 import com.android.settingslib.bluetooth.LocalBluetoothManager;
50 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
52 import java.util.Locale;
54 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
57 * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
58 * for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
60 public final class BluetoothPairingDialog extends AlertActivity implements
61 CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
62 private static final String TAG = "BluetoothPairingDialog";
64 private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
65 private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
66 private static final int PAIRING_POPUP_TIMEOUT = 35000;
67 private static final int MESSAGE_DELAYED_DISMISS = 1;
69 private LocalBluetoothManager mBluetoothManager;
70 private CachedBluetoothDeviceManager mCachedDeviceManager;
71 private BluetoothDevice mDevice;
73 private String mPairingKey;
74 private EditText mPairingView;
75 private Button mOkButton;
76 private LocalBluetoothProfile mPbapClientProfile;
77 private boolean mReceiverRegistered;
80 * Dismiss the dialog if the bond state changes to bonded or none,
81 * or if pairing was canceled for {@link #mDevice}.
83 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
85 public void onReceive(Context context, Intent intent) {
86 String action = intent.getAction();
87 if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
88 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
89 BluetoothDevice.ERROR);
90 if (bondState == BluetoothDevice.BOND_BONDED ||
91 bondState == BluetoothDevice.BOND_NONE) {
94 } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
95 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
96 if (device == null || device.equals(mDevice)) {
104 protected void onCreate(Bundle savedInstanceState) {
105 super.onCreate(savedInstanceState);
106 mReceiverRegistered = false;
108 getWindow().addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
110 Intent intent = getIntent();
111 if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
112 Log.e(TAG, "Error: this activity may be started only with intent " +
113 BluetoothDevice.ACTION_PAIRING_REQUEST);
118 mBluetoothManager = Utils.getLocalBtManager(this);
119 if (mBluetoothManager == null) {
120 Log.e(TAG, "Error: BluetoothAdapter not supported by system");
124 mCachedDeviceManager = mBluetoothManager.getCachedDeviceManager();
125 mPbapClientProfile = mBluetoothManager.getProfileManager().getPbapClientProfile();
127 mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
128 mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
131 case BluetoothDevice.PAIRING_VARIANT_PIN:
132 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
133 case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
134 createUserEntryDialog();
138 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
140 intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
141 if (passkey == BluetoothDevice.ERROR) {
142 Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog");
143 mDevice.setPairingConfirmation(false);
147 mPairingKey = String.format(Locale.US, "%06d", passkey);
148 createConfirmationDialog();
152 case BluetoothDevice.PAIRING_VARIANT_CONSENT:
153 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
154 createConsentDialog();
158 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
159 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
161 intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
162 if (pairingKey == BluetoothDevice.ERROR) {
163 Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog");
167 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
168 mPairingKey = String.format("%06d", pairingKey);
170 mPairingKey = String.format("%04d", pairingKey);
172 createDisplayPasskeyOrPinDialog();
177 Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
183 * Leave this registered through pause/resume since we still want to
184 * finish the activity in the background if pairing is canceled.
186 registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_CANCEL));
187 registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
188 mReceiverRegistered = true;
191 private void createUserEntryDialog() {
192 final AlertController.AlertParams p = mAlertParams;
193 p.mTitle = getString(R.string.bluetooth_pairing_request,
194 mCachedDeviceManager.getName(mDevice));
195 p.mView = createPinEntryView();
196 p.mPositiveButtonText = getString(android.R.string.ok);
197 p.mPositiveButtonListener = this;
198 p.mNegativeButtonText = getString(android.R.string.cancel);
199 p.mNegativeButtonListener = this;
202 mOkButton = mAlert.getButton(BUTTON_POSITIVE);
203 mOkButton.setEnabled(false);
206 private View createPinEntryView() {
207 View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
208 TextView messageViewCaptionHint = (TextView) view.findViewById(R.id.pin_values_hint);
209 TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
210 CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
211 CheckBox contactSharing = (CheckBox) view.findViewById(
212 R.id.phonebook_sharing_message_entry_pin);
213 contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook,
214 mCachedDeviceManager.getName(mDevice)));
215 if (mPbapClientProfile != null && mPbapClientProfile.isProfileReady()) {
216 contactSharing.setVisibility(View.GONE);
218 if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_ALLOWED) {
219 contactSharing.setChecked(true);
220 } else if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_REJECTED){
221 contactSharing.setChecked(false);
223 if ((mDevice.getBluetoothClass() != null) && (mDevice.getBluetoothClass().getDeviceClass()
224 == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE)) {
225 contactSharing.setChecked(false);
226 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
227 EventLog.writeEvent(0x534e4554, "73173182", -1, "");
229 contactSharing.setChecked(false);
230 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
234 contactSharing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
236 public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
238 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
240 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
245 mPairingView = (EditText) view.findViewById(R.id.text);
246 mPairingView.addTextChangedListener(this);
247 alphanumericPin.setOnCheckedChangeListener(this);
250 int messageIdHint = R.string.bluetooth_pin_values_hint;
253 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
254 messageIdHint = R.string.bluetooth_pin_values_hint_16_digits;
256 case BluetoothDevice.PAIRING_VARIANT_PIN:
257 messageId = R.string.bluetooth_enter_pin_other_device;
258 // Maximum of 16 characters in a PIN
259 maxLength = BLUETOOTH_PIN_MAX_LENGTH;
262 case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
263 messageId = R.string.bluetooth_enter_passkey_other_device;
264 // Maximum of 6 digits for passkey
265 maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
266 alphanumericPin.setVisibility(View.GONE);
270 Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
274 messageViewCaptionHint.setText(messageIdHint);
275 messageView2.setText(messageId);
276 mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
277 mPairingView.setFilters(new InputFilter[] {
278 new LengthFilter(maxLength) });
283 private View createView() {
284 View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
285 TextView pairingViewCaption = (TextView) view.findViewById(R.id.pairing_caption);
286 TextView pairingViewContent = (TextView) view.findViewById(R.id.pairing_subhead);
287 TextView messagePairing = (TextView) view.findViewById(R.id.pairing_code_message);
288 CheckBox contactSharing = (CheckBox) view.findViewById(
289 R.id.phonebook_sharing_message_confirm_pin);
290 contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook,
291 mCachedDeviceManager.getName(mDevice)));
292 if (mPbapClientProfile != null && mPbapClientProfile.isProfileReady()) {
293 contactSharing.setVisibility(View.GONE);
295 if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_ALLOWED) {
296 contactSharing.setChecked(true);
297 } else if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_REJECTED){
298 contactSharing.setChecked(false);
300 if (mDevice.getBluetoothClass().getDeviceClass()
301 == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
302 contactSharing.setChecked(true);
303 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
305 contactSharing.setChecked(false);
306 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
310 contactSharing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
312 public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
314 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
316 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
321 String messageCaption = null;
322 String pairingContent = null;
324 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
325 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
326 messagePairing.setVisibility(View.VISIBLE);
327 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
328 pairingContent = mPairingKey;
331 case BluetoothDevice.PAIRING_VARIANT_CONSENT:
332 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
333 messagePairing.setVisibility(View.VISIBLE);
337 Log.e(TAG, "Incorrect pairing type received, not creating view");
341 if (pairingContent != null) {
342 pairingViewCaption.setVisibility(View.VISIBLE);
343 pairingViewContent.setVisibility(View.VISIBLE);
344 pairingViewContent.setText(pairingContent);
350 private void createConfirmationDialog() {
351 final AlertController.AlertParams p = mAlertParams;
352 p.mTitle = getString(R.string.bluetooth_pairing_request,
353 mCachedDeviceManager.getName(mDevice));
354 p.mView = createView();
355 p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
356 p.mPositiveButtonListener = this;
357 p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
358 p.mNegativeButtonListener = this;
362 private void createConsentDialog() {
363 final AlertController.AlertParams p = mAlertParams;
364 p.mTitle = getString(R.string.bluetooth_pairing_request,
365 mCachedDeviceManager.getName(mDevice));
366 p.mView = createView();
367 p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
368 p.mPositiveButtonListener = this;
369 p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
370 p.mNegativeButtonListener = this;
374 private void createDisplayPasskeyOrPinDialog() {
375 final AlertController.AlertParams p = mAlertParams;
376 p.mTitle = getString(R.string.bluetooth_pairing_request,
377 mCachedDeviceManager.getName(mDevice));
378 p.mView = createView();
379 p.mNegativeButtonText = getString(android.R.string.cancel);
380 p.mNegativeButtonListener = this;
383 // Since its only a notification, send an OK to the framework,
384 // indicating that the dialog has been displayed.
385 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
386 mDevice.setPairingConfirmation(true);
387 } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
388 byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPairingKey);
389 mDevice.setPin(pinBytes);
394 protected void onDestroy() {
396 if (mReceiverRegistered) {
397 mReceiverRegistered = false;
398 unregisterReceiver(mReceiver);
402 public void afterTextChanged(Editable s) {
403 if (mOkButton != null) {
404 if (mType == BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS) {
405 mOkButton.setEnabled(s.length() >= 16);
407 mOkButton.setEnabled(s.length() > 0);
412 private void onPair(String value) {
413 Log.i(TAG, "Pairing dialog accepted");
415 case BluetoothDevice.PAIRING_VARIANT_PIN:
416 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
417 byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
418 if (pinBytes == null) {
421 mDevice.setPin(pinBytes);
424 case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
425 int passkey = Integer.parseInt(value);
426 mDevice.setPasskey(passkey);
429 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
430 case BluetoothDevice.PAIRING_VARIANT_CONSENT:
431 mDevice.setPairingConfirmation(true);
434 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
435 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
439 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
440 mDevice.setRemoteOutOfBandData();
444 Log.e(TAG, "Incorrect pairing type received");
448 private void onCancel() {
449 Log.i(TAG, "Pairing dialog canceled");
450 mDevice.cancelPairingUserInput();
453 public boolean onKeyDown(int keyCode, KeyEvent event) {
454 if (keyCode == KeyEvent.KEYCODE_BACK) {
457 return super.onKeyDown(keyCode,event);
460 public void onClick(DialogInterface dialog, int which) {
462 case BUTTON_POSITIVE:
463 if (mPairingView != null) {
464 onPair(mPairingView.getText().toString());
470 case BUTTON_NEGATIVE:
478 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
482 public void onTextChanged(CharSequence s, int start, int before, int count) {
485 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
486 // change input type for soft keyboard to numeric or alphanumeric
488 mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
490 mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
494 private void popTimedout() {
496 Message message = mHandler.obtainMessage(MESSAGE_DELAYED_DISMISS);
497 mHandler.sendMessageDelayed(message, PAIRING_POPUP_TIMEOUT);
501 private final Handler mHandler = new Handler() {
503 public void handleMessage(Message msg) {
505 case MESSAGE_DELAYED_DISMISS:
506 Log.v(TAG, "Delayed pairing pop up handler");