OSDN Git Service

Merge remote-tracking branch 'cm/cm-14.1' into cm-14.1-x86
[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.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;
44
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;
51
52 import java.util.Locale;
53
54 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
55
56 /**
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.
59  */
60 public final class BluetoothPairingDialog extends AlertActivity implements
61         CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
62     private static final String TAG = "BluetoothPairingDialog";
63
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;
68
69     private LocalBluetoothManager mBluetoothManager;
70     private CachedBluetoothDeviceManager mCachedDeviceManager;
71     private BluetoothDevice mDevice;
72     private int mType;
73     private String mPairingKey;
74     private EditText mPairingView;
75     private Button mOkButton;
76     private LocalBluetoothProfile mPbapClientProfile;
77     private boolean mReceiverRegistered;
78
79     /**
80      * Dismiss the dialog if the bond state changes to bonded or none,
81      * or if pairing was canceled for {@link #mDevice}.
82      */
83     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
84         @Override
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) {
92                     dismiss();
93                 }
94             } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
95                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
96                 if (device == null || device.equals(mDevice)) {
97                     dismiss();
98                 }
99             }
100         }
101     };
102
103     @Override
104     protected void onCreate(Bundle savedInstanceState) {
105         super.onCreate(savedInstanceState);
106         mReceiverRegistered = false;
107
108         getWindow().addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
109
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);
114             finish();
115             return;
116         }
117
118         mBluetoothManager = Utils.getLocalBtManager(this);
119         if (mBluetoothManager == null) {
120             Log.e(TAG, "Error: BluetoothAdapter not supported by system");
121             finish();
122             return;
123         }
124         mCachedDeviceManager = mBluetoothManager.getCachedDeviceManager();
125         mPbapClientProfile = mBluetoothManager.getProfileManager().getPbapClientProfile();
126
127         mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
128         mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
129
130         switch (mType) {
131             case BluetoothDevice.PAIRING_VARIANT_PIN:
132             case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
133             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
134                 createUserEntryDialog();
135                 popTimedout();
136                 break;
137
138             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
139                 int passkey =
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);
144                     finish();
145                     return;
146                 }
147                 mPairingKey = String.format(Locale.US, "%06d", passkey);
148                 createConfirmationDialog();
149                 popTimedout();
150                 break;
151
152             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
153             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
154                 createConsentDialog();
155                 popTimedout();
156                 break;
157
158             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
159             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
160                 int pairingKey =
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");
164                     finish();
165                     return;
166                 }
167                 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
168                     mPairingKey = String.format("%06d", pairingKey);
169                 } else {
170                     mPairingKey = String.format("%04d", pairingKey);
171                 }
172                 createDisplayPasskeyOrPinDialog();
173                 popTimedout();
174                 break;
175
176             default:
177                 Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
178                 finish();
179                 return;
180         }
181
182         /*
183          * Leave this registered through pause/resume since we still want to
184          * finish the activity in the background if pairing is canceled.
185          */
186         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_CANCEL));
187         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
188         mReceiverRegistered = true;
189     }
190
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;
200         setupAlert();
201
202         mOkButton = mAlert.getButton(BUTTON_POSITIVE);
203         mOkButton.setEnabled(false);
204     }
205
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);
217         }
218         if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_ALLOWED) {
219             contactSharing.setChecked(true);
220         } else if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_REJECTED){
221             contactSharing.setChecked(false);
222         } else {
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, "");
228             } else {
229                 contactSharing.setChecked(false);
230                 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
231             }
232         }
233
234         contactSharing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
235             @Override
236             public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
237                 if (isChecked) {
238                     mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
239                 } else {
240                     mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
241                 }
242             }
243         });
244
245         mPairingView = (EditText) view.findViewById(R.id.text);
246         mPairingView.addTextChangedListener(this);
247         alphanumericPin.setOnCheckedChangeListener(this);
248
249         int messageId;
250         int messageIdHint = R.string.bluetooth_pin_values_hint;
251         int maxLength;
252         switch (mType) {
253             case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
254                 messageIdHint = R.string.bluetooth_pin_values_hint_16_digits;
255                 // FALLTHROUGH
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;
260                 break;
261
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);
267                 break;
268
269             default:
270                 Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
271                 return null;
272         }
273
274         messageViewCaptionHint.setText(messageIdHint);
275         messageView2.setText(messageId);
276         mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
277         mPairingView.setFilters(new InputFilter[] {
278                 new LengthFilter(maxLength) });
279
280         return view;
281     }
282
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);
294         }
295         if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_ALLOWED) {
296             contactSharing.setChecked(true);
297         } else if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_REJECTED){
298             contactSharing.setChecked(false);
299         } else {
300             if (mDevice.getBluetoothClass().getDeviceClass()
301                     == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
302                 contactSharing.setChecked(true);
303                 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
304             } else {
305                 contactSharing.setChecked(false);
306                 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
307             }
308         }
309
310         contactSharing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
311             @Override
312             public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
313                 if (isChecked) {
314                     mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
315                 } else {
316                     mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
317                 }
318             }
319         });
320
321         String messageCaption = null;
322         String pairingContent = null;
323         switch (mType) {
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;
329                 break;
330
331             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
332             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
333                 messagePairing.setVisibility(View.VISIBLE);
334                 break;
335
336             default:
337                 Log.e(TAG, "Incorrect pairing type received, not creating view");
338                 return null;
339         }
340
341         if (pairingContent != null) {
342             pairingViewCaption.setVisibility(View.VISIBLE);
343             pairingViewContent.setVisibility(View.VISIBLE);
344             pairingViewContent.setText(pairingContent);
345         }
346
347         return view;
348     }
349
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;
359         setupAlert();
360     }
361
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;
371         setupAlert();
372     }
373
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;
381         setupAlert();
382
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);
390         }
391     }
392
393     @Override
394     protected void onDestroy() {
395         super.onDestroy();
396         if (mReceiverRegistered) {
397             mReceiverRegistered = false;
398             unregisterReceiver(mReceiver);
399         }
400     }
401
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);
406             } else {
407                 mOkButton.setEnabled(s.length() > 0);
408             }
409         }
410     }
411
412     private void onPair(String value) {
413         Log.i(TAG, "Pairing dialog accepted");
414         switch (mType) {
415             case BluetoothDevice.PAIRING_VARIANT_PIN:
416             case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
417                 byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
418                 if (pinBytes == null) {
419                     return;
420                 }
421                 mDevice.setPin(pinBytes);
422                 break;
423
424             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
425                 int passkey = Integer.parseInt(value);
426                 mDevice.setPasskey(passkey);
427                 break;
428
429             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
430             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
431                 mDevice.setPairingConfirmation(true);
432                 break;
433
434             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
435             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
436                 // Do nothing.
437                 break;
438
439             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
440                 mDevice.setRemoteOutOfBandData();
441                 break;
442
443             default:
444                 Log.e(TAG, "Incorrect pairing type received");
445         }
446     }
447
448     private void onCancel() {
449         Log.i(TAG, "Pairing dialog canceled");
450         mDevice.cancelPairingUserInput();
451     }
452
453     public boolean onKeyDown(int keyCode, KeyEvent event) {
454         if (keyCode == KeyEvent.KEYCODE_BACK) {
455             onCancel();
456         }
457         return super.onKeyDown(keyCode,event);
458     }
459
460     public void onClick(DialogInterface dialog, int which) {
461         switch (which) {
462             case BUTTON_POSITIVE:
463                 if (mPairingView != null) {
464                     onPair(mPairingView.getText().toString());
465                 } else {
466                     onPair(null);
467                 }
468                 break;
469
470             case BUTTON_NEGATIVE:
471             default:
472                 onCancel();
473                 break;
474         }
475     }
476
477     /* Not used */
478     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
479     }
480
481     /* Not used */
482     public void onTextChanged(CharSequence s, int start, int before, int count) {
483     }
484
485     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
486         // change input type for soft keyboard to numeric or alphanumeric
487         if (isChecked) {
488             mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
489         } else {
490             mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
491         }
492     }
493
494     private void popTimedout() {
495
496         Message message = mHandler.obtainMessage(MESSAGE_DELAYED_DISMISS);
497         mHandler.sendMessageDelayed(message, PAIRING_POPUP_TIMEOUT);
498
499     }
500
501     private final Handler mHandler = new Handler() {
502         @Override
503         public void handleMessage(Message msg) {
504             switch (msg.what) {
505                 case MESSAGE_DELAYED_DISMISS:
506                     Log.v(TAG, "Delayed pairing pop up handler");
507                     dismiss();
508                     break;
509                 default:
510                     break;
511             }
512         }
513     };
514 }