OSDN Git Service

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