BluetoothPairingDialog.java revision aaf8ffff79a73c71d3fb77a31950e344f4c7eab5
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.BluetoothDevice;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.os.Bundle;
26import android.text.Editable;
27import android.text.Html;
28import android.text.InputFilter;
29import android.text.InputType;
30import android.text.Spanned;
31import android.text.TextWatcher;
32import android.text.InputFilter.LengthFilter;
33import android.util.Log;
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 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 */
50public 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
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
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
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
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
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
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
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
295    protected void onDestroy() {
296        super.onDestroy();
297        unregisterReceiver(mReceiver);
298    }
299
300    public void afterTextChanged(Editable s) {
301        if (mOkButton != null) {
302            mOkButton.setEnabled(s.length() > 0);
303        }
304    }
305
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
340    private void onCancel() {
341        mDevice.cancelPairingUserInput();
342    }
343
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
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 */
369    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
370    }
371
372    /* Not used */
373    public void onTextChanged(CharSequence s, int start, int before, int count) {
374    }
375
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