• 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 /**
47  * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
48  * for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
49  */
50 public final class BluetoothPairingDialog extends AlertActivity implements
51         CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
52     private static final String TAG = "BluetoothPairingDialog";
53 
54     private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
55     private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
56     private BluetoothDevice mDevice;
57     private int mType;
58     private String mPairingKey;
59     private EditText mPairingView;
60     private Button mOkButton;
61 
62     /**
63      * Dismiss the dialog if the bond state changes to bonded or none,
64      * or if pairing was canceled for {@link #mDevice}.
65      */
66     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
67         @Override
68         public void onReceive(Context context, Intent intent) {
69             String action = intent.getAction();
70             if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
71                 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
72                                                    BluetoothDevice.ERROR);
73                 if (bondState == BluetoothDevice.BOND_BONDED ||
74                         bondState == BluetoothDevice.BOND_NONE) {
75                     dismiss();
76                 }
77             } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
78                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
79                 if (device == null || device.equals(mDevice)) {
80                     dismiss();
81                 }
82             }
83         }
84     };
85 
86     @Override
onCreate(Bundle savedInstanceState)87     protected void onCreate(Bundle savedInstanceState) {
88         super.onCreate(savedInstanceState);
89 
90         Intent intent = getIntent();
91         if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST))
92         {
93             Log.e(TAG, "Error: this activity may be started only with intent " +
94                   BluetoothDevice.ACTION_PAIRING_REQUEST);
95             finish();
96             return;
97         }
98 
99         LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this);
100         if (manager == null) {
101             Log.e(TAG, "Error: BluetoothAdapter not supported by system");
102             finish();
103             return;
104         }
105         CachedBluetoothDeviceManager deviceManager = manager.getCachedDeviceManager();
106 
107         mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
108         mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
109 
110         switch (mType) {
111             case BluetoothDevice.PAIRING_VARIANT_PIN:
112             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
113                 createUserEntryDialog(deviceManager);
114                 break;
115 
116             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
117                 int passkey =
118                     intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
119                 if (passkey == BluetoothDevice.ERROR) {
120                     Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog");
121                     return;
122                 }
123                 mPairingKey = String.format("%06d", passkey);
124                 createConfirmationDialog(deviceManager);
125                 break;
126 
127             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
128             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
129                 createConsentDialog(deviceManager);
130                 break;
131 
132             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
133             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
134                 int pairingKey =
135                     intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
136                 if (pairingKey == BluetoothDevice.ERROR) {
137                     Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog");
138                     return;
139                 }
140                 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
141                     mPairingKey = String.format("%06d", pairingKey);
142                 } else {
143                     mPairingKey = String.format("%04d", pairingKey);
144                 }
145                 createDisplayPasskeyOrPinDialog(deviceManager);
146                 break;
147 
148             default:
149                 Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
150         }
151 
152         /*
153          * Leave this registered through pause/resume since we still want to
154          * finish the activity in the background if pairing is canceled.
155          */
156         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_CANCEL));
157         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
158     }
159 
createUserEntryDialog(CachedBluetoothDeviceManager deviceManager)160     private void createUserEntryDialog(CachedBluetoothDeviceManager deviceManager) {
161         final AlertController.AlertParams p = mAlertParams;
162         p.mIconId = android.R.drawable.ic_dialog_info;
163         p.mTitle = getString(R.string.bluetooth_pairing_request);
164         p.mView = createPinEntryView(deviceManager.getName(mDevice));
165         p.mPositiveButtonText = getString(android.R.string.ok);
166         p.mPositiveButtonListener = this;
167         p.mNegativeButtonText = getString(android.R.string.cancel);
168         p.mNegativeButtonListener = this;
169         setupAlert();
170 
171         mOkButton = mAlert.getButton(BUTTON_POSITIVE);
172         mOkButton.setEnabled(false);
173     }
174 
createPinEntryView(String deviceName)175     private View createPinEntryView(String deviceName) {
176         View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
177         TextView messageView = (TextView) view.findViewById(R.id.message);
178         TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
179         CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
180         mPairingView = (EditText) view.findViewById(R.id.text);
181         mPairingView.addTextChangedListener(this);
182         alphanumericPin.setOnCheckedChangeListener(this);
183 
184         int messageId1;
185         int messageId2;
186         int maxLength;
187         switch (mType) {
188             case BluetoothDevice.PAIRING_VARIANT_PIN:
189                 messageId1 = R.string.bluetooth_enter_pin_msg;
190                 messageId2 = R.string.bluetooth_enter_pin_other_device;
191                 // Maximum of 16 characters in a PIN
192                 maxLength = BLUETOOTH_PIN_MAX_LENGTH;
193                 break;
194 
195             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
196                 messageId1 = R.string.bluetooth_enter_passkey_msg;
197                 messageId2 = R.string.bluetooth_enter_passkey_other_device;
198                 // Maximum of 6 digits for passkey
199                 maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
200                 alphanumericPin.setVisibility(View.GONE);
201                 break;
202 
203             default:
204                 Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
205                 return null;
206         }
207 
208         // Format the message string, then parse HTML style tags
209         String messageText = getString(messageId1, deviceName);
210         messageView.setText(Html.fromHtml(messageText));
211         messageView2.setText(messageId2);
212         mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
213         mPairingView.setFilters(new InputFilter[] {
214                 new LengthFilter(maxLength) });
215 
216         return view;
217     }
218 
createView(CachedBluetoothDeviceManager deviceManager)219     private View createView(CachedBluetoothDeviceManager deviceManager) {
220         View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
221         String name = deviceManager.getName(mDevice);
222         TextView messageView = (TextView) view.findViewById(R.id.message);
223 
224         String messageText; // formatted string containing HTML style tags
225         switch (mType) {
226             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
227                 messageText = getString(R.string.bluetooth_confirm_passkey_msg,
228                         name, mPairingKey);
229                 break;
230 
231             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
232             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
233                 messageText = getString(R.string.bluetooth_incoming_pairing_msg, name);
234                 break;
235 
236             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
237             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
238                 messageText = getString(R.string.bluetooth_display_passkey_pin_msg, name,
239                         mPairingKey);
240                 break;
241 
242             default:
243                 Log.e(TAG, "Incorrect pairing type received, not creating view");
244                 return null;
245         }
246         messageView.setText(Html.fromHtml(messageText));
247         return view;
248     }
249 
createConfirmationDialog(CachedBluetoothDeviceManager deviceManager)250     private void createConfirmationDialog(CachedBluetoothDeviceManager deviceManager) {
251         final AlertController.AlertParams p = mAlertParams;
252         p.mIconId = android.R.drawable.ic_dialog_info;
253         p.mTitle = getString(R.string.bluetooth_pairing_request);
254         p.mView = createView(deviceManager);
255         p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
256         p.mPositiveButtonListener = this;
257         p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
258         p.mNegativeButtonListener = this;
259         setupAlert();
260     }
261 
createConsentDialog(CachedBluetoothDeviceManager deviceManager)262     private void createConsentDialog(CachedBluetoothDeviceManager deviceManager) {
263         final AlertController.AlertParams p = mAlertParams;
264         p.mIconId = android.R.drawable.ic_dialog_info;
265         p.mTitle = getString(R.string.bluetooth_pairing_request);
266         p.mView = createView(deviceManager);
267         p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
268         p.mPositiveButtonListener = this;
269         p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
270         p.mNegativeButtonListener = this;
271         setupAlert();
272     }
273 
createDisplayPasskeyOrPinDialog( CachedBluetoothDeviceManager deviceManager)274     private void createDisplayPasskeyOrPinDialog(
275             CachedBluetoothDeviceManager deviceManager) {
276         final AlertController.AlertParams p = mAlertParams;
277         p.mIconId = android.R.drawable.ic_dialog_info;
278         p.mTitle = getString(R.string.bluetooth_pairing_request);
279         p.mView = createView(deviceManager);
280         p.mNegativeButtonText = getString(android.R.string.cancel);
281         p.mNegativeButtonListener = this;
282         setupAlert();
283 
284         // Since its only a notification, send an OK to the framework,
285         // indicating that the dialog has been displayed.
286         if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
287             mDevice.setPairingConfirmation(true);
288         } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
289             byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPairingKey);
290             mDevice.setPin(pinBytes);
291         }
292     }
293 
294     @Override
onDestroy()295     protected void onDestroy() {
296         super.onDestroy();
297         unregisterReceiver(mReceiver);
298     }
299 
afterTextChanged(Editable s)300     public void afterTextChanged(Editable s) {
301         if (mOkButton != null) {
302             mOkButton.setEnabled(s.length() > 0);
303         }
304     }
305 
onPair(String value)306     private void onPair(String value) {
307         switch (mType) {
308             case BluetoothDevice.PAIRING_VARIANT_PIN:
309                 byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
310                 if (pinBytes == null) {
311                     return;
312                 }
313                 mDevice.setPin(pinBytes);
314                 break;
315 
316             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
317                 int passkey = Integer.parseInt(value);
318                 mDevice.setPasskey(passkey);
319                 break;
320 
321             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
322             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
323                 mDevice.setPairingConfirmation(true);
324                 break;
325 
326             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
327             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
328                 // Do nothing.
329                 break;
330 
331             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
332                 mDevice.setRemoteOutOfBandData();
333                 break;
334 
335             default:
336                 Log.e(TAG, "Incorrect pairing type received");
337         }
338     }
339 
onCancel()340     private void onCancel() {
341         mDevice.cancelPairingUserInput();
342     }
343 
onKeyDown(int keyCode, KeyEvent event)344     public boolean onKeyDown(int keyCode, KeyEvent event) {
345         if (keyCode == KeyEvent.KEYCODE_BACK) {
346             onCancel();
347         }
348         return super.onKeyDown(keyCode,event);
349     }
350 
onClick(DialogInterface dialog, int which)351     public void onClick(DialogInterface dialog, int which) {
352         switch (which) {
353             case BUTTON_POSITIVE:
354                 if (mPairingView != null) {
355                     onPair(mPairingView.getText().toString());
356                 } else {
357                     onPair(null);
358                 }
359                 break;
360 
361             case BUTTON_NEGATIVE:
362             default:
363                 onCancel();
364                 break;
365         }
366     }
367 
368     /* Not used */
beforeTextChanged(CharSequence s, int start, int count, int after)369     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
370     }
371 
372     /* Not used */
onTextChanged(CharSequence s, int start, int before, int count)373     public void onTextChanged(CharSequence s, int start, int before, int count) {
374     }
375 
onCheckedChanged(CompoundButton buttonView, boolean isChecked)376     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
377         // change input type for soft keyboard to numeric or alphanumeric
378         if (isChecked) {
379             mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
380         } else {
381             mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
382         }
383     }
384 }
385