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.BluetoothDevice;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.os.Bundle;
26 import android.text.Editable;
27 import android.text.Html;
28 import android.text.InputFilter;
29 import android.text.InputType;
30 import android.text.Spanned;
31 import android.text.TextWatcher;
32 import android.text.InputFilter.LengthFilter;
33 import android.util.Log;
34 import android.view.View;
35 import android.widget.Button;
36 import android.widget.CheckBox;
37 import android.widget.CompoundButton;
38 import android.widget.EditText;
39 import android.widget.TextView;
41 import com.android.internal.app.AlertActivity;
42 import com.android.internal.app.AlertController;
43 import com.android.settings.R;
44 import android.view.KeyEvent;
46 import java.util.Locale;
49 * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
50 * for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
52 public final class BluetoothPairingDialog extends AlertActivity implements
53 CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
54 private static final String TAG = "BluetoothPairingDialog";
56 private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
57 private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
59 private LocalBluetoothManager mBluetoothManager;
60 private CachedBluetoothDeviceManager mCachedDeviceManager;
61 private BluetoothDevice mDevice;
63 private String mPairingKey;
64 private EditText mPairingView;
65 private Button mOkButton;
68 * Dismiss the dialog if the bond state changes to bonded or none,
69 * or if pairing was canceled for {@link #mDevice}.
71 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
73 public void onReceive(Context context, Intent intent) {
74 String action = intent.getAction();
75 if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
76 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
77 BluetoothDevice.ERROR);
78 if (bondState == BluetoothDevice.BOND_BONDED ||
79 bondState == BluetoothDevice.BOND_NONE) {
82 } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
83 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
84 if (device == null || device.equals(mDevice)) {
92 protected void onCreate(Bundle savedInstanceState) {
93 super.onCreate(savedInstanceState);
95 Intent intent = getIntent();
96 if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST))
98 Log.e(TAG, "Error: this activity may be started only with intent " +
99 BluetoothDevice.ACTION_PAIRING_REQUEST);
104 mBluetoothManager = LocalBluetoothManager.getInstance(this);
105 if (mBluetoothManager == null) {
106 Log.e(TAG, "Error: BluetoothAdapter not supported by system");
110 mCachedDeviceManager = mBluetoothManager.getCachedDeviceManager();
112 mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
113 mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
116 case BluetoothDevice.PAIRING_VARIANT_PIN:
117 case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
118 createUserEntryDialog();
121 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
123 intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
124 if (passkey == BluetoothDevice.ERROR) {
125 Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog");
128 mPairingKey = String.format(Locale.US, "%06d", passkey);
129 createConfirmationDialog();
132 case BluetoothDevice.PAIRING_VARIANT_CONSENT:
133 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
134 createConsentDialog();
137 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
138 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
140 intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
141 if (pairingKey == BluetoothDevice.ERROR) {
142 Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog");
145 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
146 mPairingKey = String.format("%06d", pairingKey);
148 mPairingKey = String.format("%04d", pairingKey);
150 createDisplayPasskeyOrPinDialog();
154 Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
158 * Leave this registered through pause/resume since we still want to
159 * finish the activity in the background if pairing is canceled.
161 registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_CANCEL));
162 registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
165 private void createUserEntryDialog() {
166 final AlertController.AlertParams p = mAlertParams;
167 p.mTitle = getString(R.string.bluetooth_pairing_request);
168 p.mView = createPinEntryView();
169 p.mPositiveButtonText = getString(android.R.string.ok);
170 p.mPositiveButtonListener = this;
171 p.mNegativeButtonText = getString(android.R.string.cancel);
172 p.mNegativeButtonListener = this;
175 mOkButton = mAlert.getButton(BUTTON_POSITIVE);
176 mOkButton.setEnabled(false);
179 private View createPinEntryView() {
180 View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
181 TextView messageViewCaption = (TextView) view.findViewById(R.id.message_caption);
182 TextView messageViewContent = (TextView) view.findViewById(R.id.message_subhead);
183 TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
184 CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
185 mPairingView = (EditText) view.findViewById(R.id.text);
186 mPairingView.addTextChangedListener(this);
187 alphanumericPin.setOnCheckedChangeListener(this);
193 case BluetoothDevice.PAIRING_VARIANT_PIN:
194 messageId1 = R.string.bluetooth_enter_pin_msg;
195 messageId2 = R.string.bluetooth_enter_pin_other_device;
196 // Maximum of 16 characters in a PIN
197 maxLength = BLUETOOTH_PIN_MAX_LENGTH;
200 case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
201 messageId1 = R.string.bluetooth_enter_pin_msg;
202 messageId2 = R.string.bluetooth_enter_passkey_other_device;
203 // Maximum of 6 digits for passkey
204 maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
205 alphanumericPin.setVisibility(View.GONE);
209 Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
213 messageViewCaption.setText(messageId1);
214 messageViewContent.setText(mCachedDeviceManager.getName(mDevice));
215 messageView2.setText(messageId2);
216 mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
217 mPairingView.setFilters(new InputFilter[] {
218 new LengthFilter(maxLength) });
223 private View createView() {
224 View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
225 // Escape device name to avoid HTML injection.
226 String name = Html.escapeHtml(mCachedDeviceManager.getName(mDevice));
227 TextView messageViewCaption = (TextView) view.findViewById(R.id.message_caption);
228 TextView messageViewContent = (TextView) view.findViewById(R.id.message_subhead);
229 TextView pairingViewCaption = (TextView) view.findViewById(R.id.pairing_caption);
230 TextView pairingViewContent = (TextView) view.findViewById(R.id.pairing_subhead);
231 TextView messagePairing = (TextView) view.findViewById(R.id.pairing_code_message);
233 String messageCaption = null;
234 String pairingContent = null;
236 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
237 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
238 messagePairing.setVisibility(View.VISIBLE);
239 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
240 messageCaption = getString(R.string.bluetooth_enter_pin_msg);
241 pairingContent = mPairingKey;
244 case BluetoothDevice.PAIRING_VARIANT_CONSENT:
245 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
246 messagePairing.setVisibility(View.VISIBLE);
247 messageCaption = getString(R.string.bluetooth_enter_pin_msg);
251 Log.e(TAG, "Incorrect pairing type received, not creating view");
255 if (messageViewCaption != null) {
256 messageViewCaption.setText(messageCaption);
257 messageViewContent.setText(name);
260 if (pairingContent != null) {
261 pairingViewCaption.setVisibility(View.VISIBLE);
262 pairingViewContent.setVisibility(View.VISIBLE);
263 pairingViewContent.setText(pairingContent);
269 private void createConfirmationDialog() {
270 final AlertController.AlertParams p = mAlertParams;
271 p.mTitle = getString(R.string.bluetooth_pairing_request);
272 p.mView = createView();
273 p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
274 p.mPositiveButtonListener = this;
275 p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
276 p.mNegativeButtonListener = this;
280 private void createConsentDialog() {
281 final AlertController.AlertParams p = mAlertParams;
282 p.mTitle = getString(R.string.bluetooth_pairing_request);
283 p.mView = createView();
284 p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
285 p.mPositiveButtonListener = this;
286 p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
287 p.mNegativeButtonListener = this;
291 private void createDisplayPasskeyOrPinDialog() {
292 final AlertController.AlertParams p = mAlertParams;
293 p.mTitle = getString(R.string.bluetooth_pairing_request);
294 p.mView = createView();
295 p.mNegativeButtonText = getString(android.R.string.cancel);
296 p.mNegativeButtonListener = this;
299 // Since its only a notification, send an OK to the framework,
300 // indicating that the dialog has been displayed.
301 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
302 mDevice.setPairingConfirmation(true);
303 } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
304 byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPairingKey);
305 mDevice.setPin(pinBytes);
310 protected void onDestroy() {
312 unregisterReceiver(mReceiver);
315 public void afterTextChanged(Editable s) {
316 if (mOkButton != null) {
317 mOkButton.setEnabled(s.length() > 0);
321 private void allowPhonebookAccess() {
322 CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(mDevice);
323 if (cachedDevice == null) {
324 cachedDevice = mCachedDeviceManager.addDevice(
325 mBluetoothManager.getBluetoothAdapter(),
326 mBluetoothManager.getProfileManager(),
329 cachedDevice.setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
332 private void onPair(String value) {
333 allowPhonebookAccess();
336 case BluetoothDevice.PAIRING_VARIANT_PIN:
337 byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
338 if (pinBytes == null) {
341 mDevice.setPin(pinBytes);
344 case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
345 int passkey = Integer.parseInt(value);
346 mDevice.setPasskey(passkey);
349 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
350 case BluetoothDevice.PAIRING_VARIANT_CONSENT:
351 mDevice.setPairingConfirmation(true);
354 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
355 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
359 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
360 mDevice.setRemoteOutOfBandData();
364 Log.e(TAG, "Incorrect pairing type received");
368 private void onCancel() {
369 mDevice.cancelPairingUserInput();
372 public boolean onKeyDown(int keyCode, KeyEvent event) {
373 if (keyCode == KeyEvent.KEYCODE_BACK) {
376 return super.onKeyDown(keyCode,event);
379 public void onClick(DialogInterface dialog, int which) {
381 case BUTTON_POSITIVE:
382 if (mPairingView != null) {
383 onPair(mPairingView.getText().toString());
389 case BUTTON_NEGATIVE:
397 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
401 public void onTextChanged(CharSequence s, int start, int before, int count) {
404 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
405 // change input type for soft keyboard to numeric or alphanumeric
407 mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
409 mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);