• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.settings.bluetooth;
18 
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;
40 
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;
45 
46 import java.util.Locale;
47 
48 /**
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.
51  */
52 public final class BluetoothPairingDialog extends AlertActivity implements
53         CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
54     private static final String TAG = "BluetoothPairingDialog";
55 
56     private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
57     private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
58 
59     private LocalBluetoothManager mBluetoothManager;
60     private CachedBluetoothDeviceManager mCachedDeviceManager;
61     private BluetoothDevice mDevice;
62     private int mType;
63     private String mPairingKey;
64     private EditText mPairingView;
65     private Button mOkButton;
66 
67     /**
68      * Dismiss the dialog if the bond state changes to bonded or none,
69      * or if pairing was canceled for {@link #mDevice}.
70      */
71     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
72         @Override
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) {
80                     dismiss();
81                 }
82             } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
83                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
84                 if (device == null || device.equals(mDevice)) {
85                     dismiss();
86                 }
87             }
88         }
89     };
90 
91     @Override
onCreate(Bundle savedInstanceState)92     protected void onCreate(Bundle savedInstanceState) {
93         super.onCreate(savedInstanceState);
94 
95         Intent intent = getIntent();
96         if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST))
97         {
98             Log.e(TAG, "Error: this activity may be started only with intent " +
99                   BluetoothDevice.ACTION_PAIRING_REQUEST);
100             finish();
101             return;
102         }
103 
104         mBluetoothManager = LocalBluetoothManager.getInstance(this);
105         if (mBluetoothManager == null) {
106             Log.e(TAG, "Error: BluetoothAdapter not supported by system");
107             finish();
108             return;
109         }
110         mCachedDeviceManager = mBluetoothManager.getCachedDeviceManager();
111 
112         mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
113         mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
114 
115         switch (mType) {
116             case BluetoothDevice.PAIRING_VARIANT_PIN:
117             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
118                 createUserEntryDialog();
119                 break;
120 
121             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
122                 int passkey =
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");
126                     return;
127                 }
128                 mPairingKey = String.format(Locale.US, "%06d", passkey);
129                 createConfirmationDialog();
130                 break;
131 
132             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
133             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
134                 createConsentDialog();
135                 break;
136 
137             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
138             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
139                 int pairingKey =
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");
143                     return;
144                 }
145                 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
146                     mPairingKey = String.format("%06d", pairingKey);
147                 } else {
148                     mPairingKey = String.format("%04d", pairingKey);
149                 }
150                 createDisplayPasskeyOrPinDialog();
151                 break;
152 
153             default:
154                 Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
155         }
156 
157         /*
158          * Leave this registered through pause/resume since we still want to
159          * finish the activity in the background if pairing is canceled.
160          */
161         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_CANCEL));
162         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
163     }
164 
createUserEntryDialog()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;
173         setupAlert();
174 
175         mOkButton = mAlert.getButton(BUTTON_POSITIVE);
176         mOkButton.setEnabled(false);
177     }
178 
createPinEntryView()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);
188 
189         int messageId1;
190         int messageId2;
191         int maxLength;
192         switch (mType) {
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;
198                 break;
199 
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);
206                 break;
207 
208             default:
209                 Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
210                 return null;
211         }
212 
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) });
219 
220         return view;
221     }
222 
createView()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);
232 
233         String messageCaption = null;
234         String pairingContent = null;
235         switch (mType) {
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;
242                 break;
243 
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);
248                 break;
249 
250             default:
251                 Log.e(TAG, "Incorrect pairing type received, not creating view");
252                 return null;
253         }
254 
255         if (messageViewCaption != null) {
256             messageViewCaption.setText(messageCaption);
257             messageViewContent.setText(name);
258         }
259 
260         if (pairingContent != null) {
261             pairingViewCaption.setVisibility(View.VISIBLE);
262             pairingViewContent.setVisibility(View.VISIBLE);
263             pairingViewContent.setText(pairingContent);
264         }
265 
266         return view;
267     }
268 
createConfirmationDialog()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;
277         setupAlert();
278     }
279 
createConsentDialog()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;
288         setupAlert();
289     }
290 
createDisplayPasskeyOrPinDialog()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;
297         setupAlert();
298 
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);
306         }
307     }
308 
309     @Override
onDestroy()310     protected void onDestroy() {
311         super.onDestroy();
312         unregisterReceiver(mReceiver);
313     }
314 
afterTextChanged(Editable s)315     public void afterTextChanged(Editable s) {
316         if (mOkButton != null) {
317             mOkButton.setEnabled(s.length() > 0);
318         }
319     }
320 
allowPhonebookAccess()321     private void allowPhonebookAccess() {
322         CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(mDevice);
323         if (cachedDevice == null) {
324             cachedDevice = mCachedDeviceManager.addDevice(
325                     mBluetoothManager.getBluetoothAdapter(),
326                     mBluetoothManager.getProfileManager(),
327                     mDevice);
328         }
329         cachedDevice.setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
330     }
331 
onPair(String value)332     private void onPair(String value) {
333         allowPhonebookAccess();
334 
335         switch (mType) {
336             case BluetoothDevice.PAIRING_VARIANT_PIN:
337                 byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
338                 if (pinBytes == null) {
339                     return;
340                 }
341                 mDevice.setPin(pinBytes);
342                 break;
343 
344             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
345                 int passkey = Integer.parseInt(value);
346                 mDevice.setPasskey(passkey);
347                 break;
348 
349             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
350             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
351                 mDevice.setPairingConfirmation(true);
352                 break;
353 
354             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
355             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
356                 // Do nothing.
357                 break;
358 
359             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
360                 mDevice.setRemoteOutOfBandData();
361                 break;
362 
363             default:
364                 Log.e(TAG, "Incorrect pairing type received");
365         }
366     }
367 
onCancel()368     private void onCancel() {
369         mDevice.cancelPairingUserInput();
370     }
371 
onKeyDown(int keyCode, KeyEvent event)372     public boolean onKeyDown(int keyCode, KeyEvent event) {
373         if (keyCode == KeyEvent.KEYCODE_BACK) {
374             onCancel();
375         }
376         return super.onKeyDown(keyCode,event);
377     }
378 
onClick(DialogInterface dialog, int which)379     public void onClick(DialogInterface dialog, int which) {
380         switch (which) {
381             case BUTTON_POSITIVE:
382                 if (mPairingView != null) {
383                     onPair(mPairingView.getText().toString());
384                 } else {
385                     onPair(null);
386                 }
387                 break;
388 
389             case BUTTON_NEGATIVE:
390             default:
391                 onCancel();
392                 break;
393         }
394     }
395 
396     /* Not used */
beforeTextChanged(CharSequence s, int start, int count, int after)397     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
398     }
399 
400     /* Not used */
onTextChanged(CharSequence s, int start, int before, int count)401     public void onTextChanged(CharSequence s, int start, int before, int count) {
402     }
403 
onCheckedChanged(CompoundButton buttonView, boolean isChecked)404     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
405         // change input type for soft keyboard to numeric or alphanumeric
406         if (isChecked) {
407             mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
408         } else {
409             mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
410         }
411     }
412 }
413