BluetoothPairingDialog.java revision ca9812a8521fcc483e821fd5a88ec421de0b8f66
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;
44
45/**
46 * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
47 * for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
48 */
49public final class BluetoothPairingDialog extends AlertActivity implements
50        CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
51    private static final String TAG = "BluetoothPairingDialog";
52
53    private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
54    private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
55    private BluetoothDevice mDevice;
56    private int mType;
57    private String mPairingKey;
58    private EditText mPairingView;
59    private Button mOkButton;
60
61    /**
62     * Dismiss the dialog if the bond state changes to bonded or none,
63     * or if pairing was canceled for {@link #mDevice}.
64     */
65    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
66        @Override
67        public void onReceive(Context context, Intent intent) {
68            String action = intent.getAction();
69            if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
70                int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
71                                                   BluetoothDevice.ERROR);
72                if (bondState == BluetoothDevice.BOND_BONDED ||
73                        bondState == BluetoothDevice.BOND_NONE) {
74                    dismiss();
75                }
76            } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
77                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
78                if (device == null || device.equals(mDevice)) {
79                    dismiss();
80                }
81            }
82        }
83    };
84
85    @Override
86    protected void onCreate(Bundle savedInstanceState) {
87        super.onCreate(savedInstanceState);
88
89        Intent intent = getIntent();
90        if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST))
91        {
92            Log.e(TAG, "Error: this activity may be started only with intent " +
93                  BluetoothDevice.ACTION_PAIRING_REQUEST);
94            finish();
95            return;
96        }
97
98        LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this);
99        if (manager == null) {
100            Log.e(TAG, "Error: BluetoothAdapter not supported by system");
101            finish();
102            return;
103        }
104        CachedBluetoothDeviceManager deviceManager = manager.getCachedDeviceManager();
105
106        mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
107        mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
108
109        switch (mType) {
110            case BluetoothDevice.PAIRING_VARIANT_PIN:
111            case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
112                createUserEntryDialog(deviceManager);
113                break;
114
115            case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
116                int passkey =
117                    intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
118                if (passkey == BluetoothDevice.ERROR) {
119                    Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog");
120                    return;
121                }
122                mPairingKey = String.format("%06d", passkey);
123                createConfirmationDialog(deviceManager);
124                break;
125
126            case BluetoothDevice.PAIRING_VARIANT_CONSENT:
127            case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
128                createConsentDialog(deviceManager);
129                break;
130
131            case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
132            case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
133                int pairingKey =
134                    intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
135                if (pairingKey == BluetoothDevice.ERROR) {
136                    Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog");
137                    return;
138                }
139                if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
140                    mPairingKey = String.format("%06d", pairingKey);
141                } else {
142                    mPairingKey = String.format("%04d", pairingKey);
143                }
144                createDisplayPasskeyOrPinDialog(deviceManager);
145                break;
146
147            default:
148                Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
149        }
150
151        /*
152         * Leave this registered through pause/resume since we still want to
153         * finish the activity in the background if pairing is canceled.
154         */
155        registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_CANCEL));
156        registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
157    }
158
159    private void createUserEntryDialog(CachedBluetoothDeviceManager deviceManager) {
160        final AlertController.AlertParams p = mAlertParams;
161        p.mIconId = android.R.drawable.ic_dialog_info;
162        p.mTitle = getString(R.string.bluetooth_pairing_request);
163        p.mView = createPinEntryView(deviceManager.getName(mDevice));
164        p.mPositiveButtonText = getString(android.R.string.ok);
165        p.mPositiveButtonListener = this;
166        p.mNegativeButtonText = getString(android.R.string.cancel);
167        p.mNegativeButtonListener = this;
168        setupAlert();
169
170        mOkButton = mAlert.getButton(BUTTON_POSITIVE);
171        mOkButton.setEnabled(false);
172    }
173
174    private View createPinEntryView(String deviceName) {
175        View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
176        TextView messageView = (TextView) view.findViewById(R.id.message);
177        TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
178        CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
179        mPairingView = (EditText) view.findViewById(R.id.text);
180        mPairingView.addTextChangedListener(this);
181        alphanumericPin.setOnCheckedChangeListener(this);
182
183        int messageId1;
184        int messageId2;
185        int maxLength;
186        switch (mType) {
187            case BluetoothDevice.PAIRING_VARIANT_PIN:
188                messageId1 = R.string.bluetooth_enter_pin_msg;
189                messageId2 = R.string.bluetooth_enter_pin_other_device;
190                // Maximum of 16 characters in a PIN
191                maxLength = BLUETOOTH_PIN_MAX_LENGTH;
192                break;
193
194            case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
195                messageId1 = R.string.bluetooth_enter_passkey_msg;
196                messageId2 = R.string.bluetooth_enter_passkey_other_device;
197                // Maximum of 6 digits for passkey
198                maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
199                alphanumericPin.setVisibility(View.GONE);
200                break;
201
202            default:
203                Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
204                return null;
205        }
206
207        // Format the message string, then parse HTML style tags
208        String messageText = getString(messageId1, deviceName);
209        messageView.setText(Html.fromHtml(messageText));
210        messageView2.setText(messageId2);
211        mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
212        mPairingView.setFilters(new InputFilter[] {
213                new LengthFilter(maxLength) });
214
215        return view;
216    }
217
218    private View createView(CachedBluetoothDeviceManager deviceManager) {
219        View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
220        String name = deviceManager.getName(mDevice);
221        TextView messageView = (TextView) view.findViewById(R.id.message);
222
223        String messageText; // formatted string containing HTML style tags
224        switch (mType) {
225            case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
226                messageText = getString(R.string.bluetooth_confirm_passkey_msg,
227                        name, mPairingKey);
228                break;
229
230            case BluetoothDevice.PAIRING_VARIANT_CONSENT:
231            case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
232                messageText = getString(R.string.bluetooth_incoming_pairing_msg, name);
233                break;
234
235            case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
236            case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
237                messageText = getString(R.string.bluetooth_display_passkey_pin_msg, name,
238                        mPairingKey);
239                break;
240
241            default:
242                Log.e(TAG, "Incorrect pairing type received, not creating view");
243                return null;
244        }
245        messageView.setText(Html.fromHtml(messageText));
246        return view;
247    }
248
249    private void createConfirmationDialog(CachedBluetoothDeviceManager deviceManager) {
250        final AlertController.AlertParams p = mAlertParams;
251        p.mIconId = android.R.drawable.ic_dialog_info;
252        p.mTitle = getString(R.string.bluetooth_pairing_request);
253        p.mView = createView(deviceManager);
254        p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
255        p.mPositiveButtonListener = this;
256        p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
257        p.mNegativeButtonListener = this;
258        setupAlert();
259    }
260
261    private void createConsentDialog(CachedBluetoothDeviceManager deviceManager) {
262        final AlertController.AlertParams p = mAlertParams;
263        p.mIconId = android.R.drawable.ic_dialog_info;
264        p.mTitle = getString(R.string.bluetooth_pairing_request);
265        p.mView = createView(deviceManager);
266        p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
267        p.mPositiveButtonListener = this;
268        p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
269        p.mNegativeButtonListener = this;
270        setupAlert();
271    }
272
273    private void createDisplayPasskeyOrPinDialog(
274            CachedBluetoothDeviceManager deviceManager) {
275        final AlertController.AlertParams p = mAlertParams;
276        p.mIconId = android.R.drawable.ic_dialog_info;
277        p.mTitle = getString(R.string.bluetooth_pairing_request);
278        p.mView = createView(deviceManager);
279        p.mNegativeButtonText = getString(android.R.string.cancel);
280        p.mNegativeButtonListener = this;
281        setupAlert();
282
283        // Since its only a notification, send an OK to the framework,
284        // indicating that the dialog has been displayed.
285        if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
286            mDevice.setPairingConfirmation(true);
287        } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
288            byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPairingKey);
289            mDevice.setPin(pinBytes);
290        }
291    }
292
293    @Override
294    protected void onDestroy() {
295        super.onDestroy();
296        unregisterReceiver(mReceiver);
297    }
298
299    public void afterTextChanged(Editable s) {
300        if (s.length() > 0) {
301            mOkButton.setEnabled(true);
302        }
303    }
304
305    private void onPair(String value) {
306        switch (mType) {
307            case BluetoothDevice.PAIRING_VARIANT_PIN:
308                byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
309                if (pinBytes == null) {
310                    return;
311                }
312                mDevice.setPin(pinBytes);
313                break;
314
315            case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
316                int passkey = Integer.parseInt(value);
317                mDevice.setPasskey(passkey);
318                break;
319
320            case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
321            case BluetoothDevice.PAIRING_VARIANT_CONSENT:
322                mDevice.setPairingConfirmation(true);
323                break;
324
325            case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
326            case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
327                // Do nothing.
328                break;
329
330            case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
331                mDevice.setRemoteOutOfBandData();
332                break;
333
334            default:
335                Log.e(TAG, "Incorrect pairing type received");
336        }
337    }
338
339    private void onCancel() {
340        mDevice.cancelPairingUserInput();
341    }
342
343    public void onClick(DialogInterface dialog, int which) {
344        switch (which) {
345            case BUTTON_POSITIVE:
346                if (mPairingView != null) {
347                    onPair(mPairingView.getText().toString());
348                } else {
349                    onPair(null);
350                }
351                break;
352
353            case BUTTON_NEGATIVE:
354            default:
355                onCancel();
356                break;
357        }
358    }
359
360    /* Not used */
361    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
362    }
363
364    /* Not used */
365    public void onTextChanged(CharSequence s, int start, int before, int count) {
366    }
367
368    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
369        // change input type for soft keyboard to numeric or alphanumeric
370        if (isChecked) {
371            mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
372        } else {
373            mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
374        }
375    }
376}
377