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