OSDN Git Service

bb66da6420b860ed20e9ccd074bac2ec46632ab0
[android-x86/packages-apps-Settings.git] / src / com / android / settings / bluetooth / BluetoothPairingDialog.java
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.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;
41 import android.os.Handler;
42 import android.os.Message;
43
44 import com.android.internal.app.AlertActivity;
45 import com.android.internal.app.AlertController;
46 import com.android.settings.R;
47 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
48 import com.android.settingslib.bluetooth.LocalBluetoothManager;
49 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
50
51 import java.util.Locale;
52
53 /**
54  * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
55  * for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
56  */
57 public final class BluetoothPairingDialog extends AlertActivity implements
58         CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
59     private static final String TAG = "BluetoothPairingDialog";
60
61     private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
62     private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
63     private static final int PAIRING_POPUP_TIMEOUT = 35000;
64     private static final int MESSAGE_DELAYED_DISMISS = 1;
65
66     private LocalBluetoothManager mBluetoothManager;
67     private CachedBluetoothDeviceManager mCachedDeviceManager;
68     private BluetoothDevice mDevice;
69     private int mType;
70     private String mPairingKey;
71     private EditText mPairingView;
72     private Button mOkButton;
73     private LocalBluetoothProfile mPbapClientProfile;
74     private boolean mReceiverRegistered;
75
76     /**
77      * Dismiss the dialog if the bond state changes to bonded or none,
78      * or if pairing was canceled for {@link #mDevice}.
79      */
80     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
81         @Override
82         public void onReceive(Context context, Intent intent) {
83             String action = intent.getAction();
84             if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
85                 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
86                                                    BluetoothDevice.ERROR);
87                 if (bondState == BluetoothDevice.BOND_BONDED ||
88                         bondState == BluetoothDevice.BOND_NONE) {
89                     dismiss();
90                 }
91             } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
92                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
93                 if (device == null || device.equals(mDevice)) {
94                     dismiss();
95                 }
96             }
97         }
98     };
99
100     @Override
101     protected void onCreate(Bundle savedInstanceState) {
102         super.onCreate(savedInstanceState);
103         mReceiverRegistered = false;
104
105         Intent intent = getIntent();
106         if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
107             Log.e(TAG, "Error: this activity may be started only with intent " +
108                   BluetoothDevice.ACTION_PAIRING_REQUEST);
109             finish();
110             return;
111         }
112
113         mBluetoothManager = Utils.getLocalBtManager(this);
114         if (mBluetoothManager == null) {
115             Log.e(TAG, "Error: BluetoothAdapter not supported by system");
116             finish();
117             return;
118         }
119         mCachedDeviceManager = mBluetoothManager.getCachedDeviceManager();
120         mPbapClientProfile = mBluetoothManager.getProfileManager().getPbapClientProfile();
121
122         mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
123         mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
124
125         switch (mType) {
126             case BluetoothDevice.PAIRING_VARIANT_PIN:
127             case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
128             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
129                 createUserEntryDialog();
130                 popTimedout();
131                 break;
132
133             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
134                 int passkey =
135                     intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
136                 if (passkey == BluetoothDevice.ERROR) {
137                     Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog");
138                     mDevice.setPairingConfirmation(false);
139                     finish();
140                     return;
141                 }
142                 mPairingKey = String.format(Locale.US, "%06d", passkey);
143                 createConfirmationDialog();
144                 popTimedout();
145                 break;
146
147             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
148             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
149                 createConsentDialog();
150                 popTimedout();
151                 break;
152
153             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
154             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
155                 int pairingKey =
156                     intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
157                 if (pairingKey == BluetoothDevice.ERROR) {
158                     Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog");
159                     finish();
160                     return;
161                 }
162                 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
163                     mPairingKey = String.format("%06d", pairingKey);
164                 } else {
165                     mPairingKey = String.format("%04d", pairingKey);
166                 }
167                 createDisplayPasskeyOrPinDialog();
168                 popTimedout();
169                 break;
170
171             default:
172                 Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
173                 finish();
174                 return;
175         }
176
177         /*
178          * Leave this registered through pause/resume since we still want to
179          * finish the activity in the background if pairing is canceled.
180          */
181         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_CANCEL));
182         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
183         mReceiverRegistered = true;
184     }
185
186     private void createUserEntryDialog() {
187         final AlertController.AlertParams p = mAlertParams;
188         p.mTitle = getString(R.string.bluetooth_pairing_request,
189                 mCachedDeviceManager.getName(mDevice));
190         p.mView = createPinEntryView();
191         p.mPositiveButtonText = getString(android.R.string.ok);
192         p.mPositiveButtonListener = this;
193         p.mNegativeButtonText = getString(android.R.string.cancel);
194         p.mNegativeButtonListener = this;
195         setupAlert();
196
197         mOkButton = mAlert.getButton(BUTTON_POSITIVE);
198         mOkButton.setEnabled(false);
199     }
200
201     private View createPinEntryView() {
202         View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
203         TextView messageViewCaptionHint = (TextView) view.findViewById(R.id.pin_values_hint);
204         TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
205         CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
206         CheckBox contactSharing = (CheckBox) view.findViewById(
207                 R.id.phonebook_sharing_message_entry_pin);
208         contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook,
209                 mCachedDeviceManager.getName(mDevice)));
210         if (mPbapClientProfile != null && mPbapClientProfile.isProfileReady()) {
211             contactSharing.setVisibility(View.GONE);
212         }
213         if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_ALLOWED) {
214             contactSharing.setChecked(true);
215         } else if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_REJECTED){
216             contactSharing.setChecked(false);
217         } else {
218             if ((mDevice.getBluetoothClass() != null) && (mDevice.getBluetoothClass().getDeviceClass()
219                     == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE)) {
220                 contactSharing.setChecked(true);
221                 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
222             } else {
223                 contactSharing.setChecked(false);
224                 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
225             }
226         }
227
228         contactSharing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
229             @Override
230             public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
231                 if (isChecked) {
232                     mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
233                 } else {
234                     mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
235                 }
236             }
237         });
238
239         mPairingView = (EditText) view.findViewById(R.id.text);
240         mPairingView.addTextChangedListener(this);
241         alphanumericPin.setOnCheckedChangeListener(this);
242
243         int messageId;
244         int messageIdHint = R.string.bluetooth_pin_values_hint;
245         int maxLength;
246         switch (mType) {
247             case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
248                 messageIdHint = R.string.bluetooth_pin_values_hint_16_digits;
249                 // FALLTHROUGH
250             case BluetoothDevice.PAIRING_VARIANT_PIN:
251                 messageId = R.string.bluetooth_enter_pin_other_device;
252                 // Maximum of 16 characters in a PIN
253                 maxLength = BLUETOOTH_PIN_MAX_LENGTH;
254                 break;
255
256             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
257                 messageId = R.string.bluetooth_enter_passkey_other_device;
258                 // Maximum of 6 digits for passkey
259                 maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
260                 alphanumericPin.setVisibility(View.GONE);
261                 break;
262
263             default:
264                 Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
265                 return null;
266         }
267
268         messageViewCaptionHint.setText(messageIdHint);
269         messageView2.setText(messageId);
270         mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
271         mPairingView.setFilters(new InputFilter[] {
272                 new LengthFilter(maxLength) });
273
274         return view;
275     }
276
277     private View createView() {
278         View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
279         TextView pairingViewCaption = (TextView) view.findViewById(R.id.pairing_caption);
280         TextView pairingViewContent = (TextView) view.findViewById(R.id.pairing_subhead);
281         TextView messagePairing = (TextView) view.findViewById(R.id.pairing_code_message);
282         CheckBox contactSharing = (CheckBox) view.findViewById(
283                 R.id.phonebook_sharing_message_confirm_pin);
284         contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook,
285                 mCachedDeviceManager.getName(mDevice)));
286         if (mPbapClientProfile != null && mPbapClientProfile.isProfileReady()) {
287             contactSharing.setVisibility(View.GONE);
288         }
289         if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_ALLOWED) {
290             contactSharing.setChecked(true);
291         } else if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_REJECTED){
292             contactSharing.setChecked(false);
293         } else {
294             if (mDevice.getBluetoothClass().getDeviceClass()
295                     == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
296                 contactSharing.setChecked(true);
297                 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
298             } else {
299                 contactSharing.setChecked(false);
300                 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
301             }
302         }
303
304         contactSharing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
305             @Override
306             public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
307                 if (isChecked) {
308                     mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
309                 } else {
310                     mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
311                 }
312             }
313         });
314
315         String messageCaption = null;
316         String pairingContent = null;
317         switch (mType) {
318             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
319             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
320                 messagePairing.setVisibility(View.VISIBLE);
321             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
322                 pairingContent = mPairingKey;
323                 break;
324
325             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
326             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
327                 messagePairing.setVisibility(View.VISIBLE);
328                 break;
329
330             default:
331                 Log.e(TAG, "Incorrect pairing type received, not creating view");
332                 return null;
333         }
334
335         if (pairingContent != null) {
336             pairingViewCaption.setVisibility(View.VISIBLE);
337             pairingViewContent.setVisibility(View.VISIBLE);
338             pairingViewContent.setText(pairingContent);
339         }
340
341         return view;
342     }
343
344     private void createConfirmationDialog() {
345         final AlertController.AlertParams p = mAlertParams;
346         p.mTitle = getString(R.string.bluetooth_pairing_request,
347                 mCachedDeviceManager.getName(mDevice));
348         p.mView = createView();
349         p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
350         p.mPositiveButtonListener = this;
351         p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
352         p.mNegativeButtonListener = this;
353         setupAlert();
354     }
355
356     private void createConsentDialog() {
357         final AlertController.AlertParams p = mAlertParams;
358         p.mTitle = getString(R.string.bluetooth_pairing_request,
359                 mCachedDeviceManager.getName(mDevice));
360         p.mView = createView();
361         p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
362         p.mPositiveButtonListener = this;
363         p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
364         p.mNegativeButtonListener = this;
365         setupAlert();
366     }
367
368     private void createDisplayPasskeyOrPinDialog() {
369         final AlertController.AlertParams p = mAlertParams;
370         p.mTitle = getString(R.string.bluetooth_pairing_request,
371                 mCachedDeviceManager.getName(mDevice));
372         p.mView = createView();
373         p.mNegativeButtonText = getString(android.R.string.cancel);
374         p.mNegativeButtonListener = this;
375         setupAlert();
376
377         // Since its only a notification, send an OK to the framework,
378         // indicating that the dialog has been displayed.
379         if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
380             mDevice.setPairingConfirmation(true);
381         } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
382             byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPairingKey);
383             mDevice.setPin(pinBytes);
384         }
385     }
386
387     @Override
388     protected void onDestroy() {
389         super.onDestroy();
390         if (mReceiverRegistered) {
391             mReceiverRegistered = false;
392             unregisterReceiver(mReceiver);
393         }
394     }
395
396     public void afterTextChanged(Editable s) {
397         if (mOkButton != null) {
398             if (mType == BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS) {
399                 mOkButton.setEnabled(s.length() >= 16);
400             } else {
401                 mOkButton.setEnabled(s.length() > 0);
402             }
403         }
404     }
405
406     private void onPair(String value) {
407         Log.i(TAG, "Pairing dialog accepted");
408         switch (mType) {
409             case BluetoothDevice.PAIRING_VARIANT_PIN:
410             case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
411                 byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
412                 if (pinBytes == null) {
413                     return;
414                 }
415                 mDevice.setPin(pinBytes);
416                 break;
417
418             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
419                 int passkey = Integer.parseInt(value);
420                 mDevice.setPasskey(passkey);
421                 break;
422
423             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
424             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
425                 mDevice.setPairingConfirmation(true);
426                 break;
427
428             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
429             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
430                 // Do nothing.
431                 break;
432
433             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
434                 mDevice.setRemoteOutOfBandData();
435                 break;
436
437             default:
438                 Log.e(TAG, "Incorrect pairing type received");
439         }
440     }
441
442     private void onCancel() {
443         Log.i(TAG, "Pairing dialog canceled");
444         mDevice.cancelPairingUserInput();
445     }
446
447     public boolean onKeyDown(int keyCode, KeyEvent event) {
448         if (keyCode == KeyEvent.KEYCODE_BACK) {
449             onCancel();
450         }
451         return super.onKeyDown(keyCode,event);
452     }
453
454     public void onClick(DialogInterface dialog, int which) {
455         switch (which) {
456             case BUTTON_POSITIVE:
457                 if (mPairingView != null) {
458                     onPair(mPairingView.getText().toString());
459                 } else {
460                     onPair(null);
461                 }
462                 break;
463
464             case BUTTON_NEGATIVE:
465             default:
466                 onCancel();
467                 break;
468         }
469     }
470
471     /* Not used */
472     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
473     }
474
475     /* Not used */
476     public void onTextChanged(CharSequence s, int start, int before, int count) {
477     }
478
479     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
480         // change input type for soft keyboard to numeric or alphanumeric
481         if (isChecked) {
482             mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
483         } else {
484             mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
485         }
486     }
487
488     private void popTimedout() {
489
490         Message message = mHandler.obtainMessage(MESSAGE_DELAYED_DISMISS);
491         mHandler.sendMessageDelayed(message, PAIRING_POPUP_TIMEOUT);
492
493     }
494
495     private final Handler mHandler = new Handler() {
496         @Override
497         public void handleMessage(Message msg) {
498             switch (msg.what) {
499                 case MESSAGE_DELAYED_DISMISS:
500                     Log.v(TAG, "Delayed pairing pop up handler");
501                     dismiss();
502                     break;
503                 default:
504                     break;
505             }
506         }
507     };
508 }