Refactoring for bluetooth dialogs
Dialogs displayed when attempting to connect
to a bluetooth device have been refactored so
that they use a proper dialog fragment. Hardware
dependencies have also been refactored into a
controller class to make it possible to test
these dialogs as well as the controller
independently of one another.
Test: RoboTests
Bug: 32180625
Change-Id: I5447f8299bc13e139052635fc63546cbfc997f33
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 44d4c19..6ce38c7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2031,10 +2031,8 @@
<!-- Bluetooth stuff -->
<activity android:name=".bluetooth.BluetoothPairingDialog"
- android:label="@string/bluetooth_pairing_request"
android:excludeFromRecents="true"
- android:windowSoftInputMode="stateVisible|adjustResize"
- android:theme="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert">
+ android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar">
<intent-filter android:priority="1">
<action android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingController.java b/src/com/android/settings/bluetooth/BluetoothPairingController.java
new file mode 100644
index 0000000..38b30a8
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothPairingController.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.text.Editable;
+import android.util.Log;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import com.android.settings.R;
+import com.android.settings.bluetooth.BluetoothPairingDialogFragment.BluetoothPairingDialogListener;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
+import java.util.Locale;
+
+/**
+ * A controller used by {@link BluetoothPairingDialog} to manage connection state while we try to
+ * pair with a bluetooth device. It includes methods that allow the
+ * {@link BluetoothPairingDialogFragment} to interrogate the current state as well.
+ */
+public class BluetoothPairingController implements OnCheckedChangeListener,
+ BluetoothPairingDialogListener {
+
+ private static final String TAG = "BTPairingController";
+
+ // Different types of dialogs we can map to
+ public static final int INVALID_DIALOG_TYPE = -1;
+ public static final int USER_ENTRY_DIALOG = 0;
+ public static final int CONFIRMATION_DIALOG = 1;
+ public static final int DISPLAY_PASSKEY_DIALOG = 2;
+
+ private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
+ private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
+
+ // Bluetooth dependencies for the connection we are trying to establish
+ private LocalBluetoothManager mBluetoothManager;
+ private BluetoothDevice mDevice;
+ private int mType;
+ private String mUserInput;
+ private String mPasskeyFormatted;
+ private int mPasskey;
+ private String mDeviceName;
+ private LocalBluetoothProfile mPbapClientProfile;
+
+ /**
+ * Creates an instance of a BluetoothPairingController.
+ *
+ * @param intent - must contain {@link BluetoothDevice#EXTRA_PAIRING_VARIANT}, {@link
+ * BluetoothDevice#EXTRA_PAIRING_KEY}, and {@link BluetoothDevice#EXTRA_DEVICE}. Missing extra
+ * will lead to undefined behavior.
+ */
+ public BluetoothPairingController(Intent intent, Context context) {
+ mBluetoothManager = Utils.getLocalBtManager(context);
+ mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+ String message = "";
+ if (mBluetoothManager == null) {
+ throw new IllegalStateException("Could not obtain LocalBluetoothManager");
+ } else if (mDevice == null) {
+ throw new IllegalStateException("Could not find BluetoothDevice");
+ }
+
+ mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
+ mPasskey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
+ mDeviceName = mBluetoothManager.getCachedDeviceManager().getName(mDevice);
+ mPbapClientProfile = mBluetoothManager.getProfileManager().getPbapClientProfile();
+ mPasskeyFormatted = formatKey(mPasskey);
+
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+ } else {
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+ }
+ }
+
+ @Override
+ public void onDialogPositiveClick(BluetoothPairingDialogFragment dialog) {
+ if (getDialogType() == USER_ENTRY_DIALOG) {
+ onPair(mUserInput);
+ } else {
+ onPair(null);
+ }
+ }
+
+ @Override
+ public void onDialogNegativeClick(BluetoothPairingDialogFragment dialog) {
+ onCancel();
+ }
+
+ /**
+ * A method for querying which bluetooth pairing dialog fragment variant this device requires.
+ *
+ * @return - The dialog view variant needed for this device.
+ */
+ public int getDialogType() {
+ switch (mType) {
+ case BluetoothDevice.PAIRING_VARIANT_PIN:
+ case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
+ case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
+ return USER_ENTRY_DIALOG;
+
+ case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
+ case BluetoothDevice.PAIRING_VARIANT_CONSENT:
+ case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
+ return CONFIRMATION_DIALOG;
+
+ case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
+ case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
+ return DISPLAY_PASSKEY_DIALOG;
+
+ default:
+ return INVALID_DIALOG_TYPE;
+ }
+ }
+
+ /**
+ * @return - A string containing the name provided by the device.
+ */
+ public String getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * A method for querying if the bluetooth device has a profile already set up on this device.
+ *
+ * @return - A boolean indicating if the device has previous knowledge of a profile for this
+ * device.
+ */
+ public boolean isProfileReady() {
+ return mPbapClientProfile != null && mPbapClientProfile.isProfileReady();
+ }
+
+ /**
+ * A method for querying if the bluetooth device has access to contacts on the device.
+ *
+ * @return - A boolean indicating if the bluetooth device has permission to access the device
+ * contacts
+ */
+ public boolean getContactSharingState() {
+ switch (mDevice.getPhonebookAccessPermission()) {
+ case BluetoothDevice.ACCESS_ALLOWED:
+ return true;
+ case BluetoothDevice.ACCESS_REJECTED:
+ return false;
+ default:
+ if (mDevice.getBluetoothClass().getDeviceClass()
+ == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * A method for querying if the provided editable is a valid passkey/pin format for this device.
+ *
+ * @param s - The passkey/pin
+ * @return - A boolean indicating if the passkey/pin is of the correct format.
+ */
+ public boolean isPasskeyValid(Editable s) {
+ boolean requires16Digits = mType == BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS;
+ return s.length() >= 16 && requires16Digits || s.length() > 0 && !requires16Digits;
+ }
+
+ /**
+ * A method for querying what message should be shown to the user as additional text in the
+ * dialog for this device. Returns -1 to indicate a device type that does not use this message.
+ *
+ * @return - The message ID to show the user.
+ */
+ public int getDeviceVariantMessageID() {
+ switch (mType) {
+ case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
+ case BluetoothDevice.PAIRING_VARIANT_PIN:
+ return R.string.bluetooth_enter_pin_other_device;
+
+ case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
+ return R.string.bluetooth_enter_passkey_other_device;
+
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * A method for querying what message hint should be shown to the user as additional text in the
+ * dialog for this device. Returns -1 to indicate a device type that does not use this message.
+ *
+ * @return - The message ID to show the user.
+ */
+ public int getDeviceVariantMessageHint() {
+ switch (mType) {
+ case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
+ return R.string.bluetooth_pin_values_hint_16_digits;
+
+ case BluetoothDevice.PAIRING_VARIANT_PIN:
+ case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
+ return R.string.bluetooth_pin_values_hint;
+
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * A method for querying the maximum passkey/pin length for this device.
+ *
+ * @return - An int indicating the maximum length
+ */
+ public int getDeviceMaxPasskeyLength() {
+ switch (mType) {
+ case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
+ case BluetoothDevice.PAIRING_VARIANT_PIN:
+ return BLUETOOTH_PIN_MAX_LENGTH;
+
+ case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
+ return BLUETOOTH_PASSKEY_MAX_LENGTH;
+
+ default:
+ return 0;
+ }
+
+ }
+
+ /**
+ * A method for querying if the device uses an alphanumeric passkey.
+ *
+ * @return - a boolean indicating if the passkey can be alphanumeric.
+ */
+ public boolean pairingCodeIsAlphanumeric() {
+ switch (mType) {
+ case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
+ return false;
+
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * A method used by the dialogfragment to notify the controller that the dialog has been
+ * displayed for bluetooth device types that just care about it being displayed.
+ */
+ protected void notifyDialogDisplayed() {
+ // send an OK to the framework, indicating that the dialog has been displayed.
+ if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
+ mDevice.setPairingConfirmation(true);
+ } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
+ byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPasskeyFormatted);
+ mDevice.setPin(pinBytes);
+ }
+ }
+
+ /**
+ * A method for querying if this bluetooth device type has a key it would like displayed
+ * to the user.
+ *
+ * @return - A boolean indicating if a key exists which should be displayed to the user.
+ */
+ public boolean isDisplayPairingKeyVariant() {
+ switch (mType) {
+ case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
+ case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
+ case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * A method for querying if this bluetooth device type has other content it would like displayed
+ * to the user.
+ *
+ * @return - A boolean indicating if content exists which should be displayed to the user.
+ */
+ public boolean hasPairingContent() {
+ switch (mType) {
+ case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
+ case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
+ case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * A method for obtaining any additional content this bluetooth device has for displaying to the
+ * user.
+ *
+ * @return - A string containing the additional content, null if none exists.
+ * @see {@link BluetoothPairingController#hasPairingContent()}
+ */
+ public String getPairingContent() {
+ if (hasPairingContent()) {
+ return mPasskeyFormatted;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * A method that exists to allow the fragment to update the controller with input the user has
+ * provided in the fragment.
+ *
+ * @param input - A string containing the user input.
+ */
+ protected void updateUserInput(String input) {
+ mUserInput = input;
+ }
+
+ /**
+ * Returns the provided passkey in a format that this device expects. Only works for numeric
+ * passkeys/pins.
+ *
+ * @param passkey - An integer containing the passkey to format.
+ * @return - A string containing the formatted passkey/pin
+ */
+ private String formatKey(int passkey) {
+ switch (mType) {
+ case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
+ case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
+ return String.format(Locale.US, "%06d", passkey);
+
+ case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
+ return String.format("%04d", passkey);
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * handles the necessary communication with the bluetooth device to establish a successful
+ * pairing
+ *
+ * @param passkey - The passkey we will attempt to pair to the device with.
+ */
+ private void onPair(String passkey) {
+ Log.d(TAG, "Pairing dialog accepted");
+ switch (mType) {
+ case BluetoothDevice.PAIRING_VARIANT_PIN:
+ case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
+ byte[] pinBytes = BluetoothDevice.convertPinToBytes(passkey);
+ if (pinBytes == null) {
+ return;
+ }
+ mDevice.setPin(pinBytes);
+ break;
+
+ case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
+ int pass = Integer.parseInt(passkey);
+ mDevice.setPasskey(pass);
+ break;
+
+ case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
+ case BluetoothDevice.PAIRING_VARIANT_CONSENT:
+ mDevice.setPairingConfirmation(true);
+ break;
+
+ case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
+ case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
+ // Do nothing.
+ break;
+
+ case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
+ mDevice.setRemoteOutOfBandData();
+ break;
+
+ default:
+ Log.e(TAG, "Incorrect pairing type received");
+ }
+ }
+
+ /**
+ * A method for properly ending communication with the bluetooth device. Will be called by the
+ * {@link BluetoothPairingDialogFragment} when it is dismissed.
+ */
+ public void onCancel() {
+ Log.d(TAG, "Pairing dialog canceled");
+ mDevice.cancelPairingUserInput();
+ }
+
+ /**
+ * A method for checking if this device is equal to another device.
+ *
+ * @param device - The other device being compared to this device.
+ * @return - A boolean indicating if the devices were equal.
+ */
+ public boolean deviceEquals(BluetoothDevice device) {
+ return mDevice == device;
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
old mode 100755
new mode 100644
index 27cd532..ed63fcb
--- a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,65 +16,28 @@
package com.android.settings.bluetooth;
-import android.bluetooth.BluetoothClass;
+import android.annotation.Nullable;
+import android.app.Activity;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothUuid;
import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
-import android.text.Editable;
-import android.text.InputFilter;
-import android.text.InputFilter.LengthFilter;
-import android.text.InputType;
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.text.TextWatcher;
-import android.text.style.TtsSpan;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
-import com.android.settings.R;
-import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.LocalBluetoothProfile;
-
-import java.util.Locale;
/**
* BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
* for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
*/
-public final class BluetoothPairingDialog extends AlertActivity implements
- CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
- private static final String TAG = "BluetoothPairingDialog";
+public final class BluetoothPairingDialog extends Activity {
+ public static final String FRAGMENT_TAG = "bluetooth.pairing.fragment";
- private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
- private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
-
- private LocalBluetoothManager mBluetoothManager;
- private CachedBluetoothDeviceManager mCachedDeviceManager;
- private BluetoothDevice mDevice;
- private int mType;
- private String mPairingKey;
- private EditText mPairingView;
- private Button mOkButton;
- private LocalBluetoothProfile mPbapClientProfile;
+ private BluetoothPairingController mBluetoothPairingController;
private boolean mReceiverRegistered;
/**
* Dismiss the dialog if the bond state changes to bonded or none,
- * or if pairing was canceled for {@link #mDevice}.
+ * or if pairing was canceled for {@link BluetoothPairingController#mDevice}.
*/
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@@ -82,14 +45,14 @@
String action = intent.getAction();
if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
- BluetoothDevice.ERROR);
+ BluetoothDevice.ERROR);
if (bondState == BluetoothDevice.BOND_BONDED ||
bondState == BluetoothDevice.BOND_NONE) {
dismiss();
}
} else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (device == null || device.equals(mDevice)) {
+ if (device == null || mBluetoothPairingController.deviceEquals(device)) {
dismiss();
}
}
@@ -97,72 +60,29 @@
};
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ boolean fragmentFound = true;
+ BluetoothPairingDialogFragment bluetoothFragment =
+ (BluetoothPairingDialogFragment) getFragmentManager()
+ .findFragmentByTag(FRAGMENT_TAG);
Intent intent = getIntent();
- if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
- Log.e(TAG, "Error: this activity may be started only with intent " +
- BluetoothDevice.ACTION_PAIRING_REQUEST);
- finish();
- return;
+ mBluetoothPairingController = new BluetoothPairingController(intent, this);
+
+ // check if the fragment exists already
+ if (bluetoothFragment == null) {
+ fragmentFound = false;
+ bluetoothFragment = new BluetoothPairingDialogFragment();
}
- mBluetoothManager = Utils.getLocalBtManager(this);
- if (mBluetoothManager == null) {
- Log.e(TAG, "Error: BluetoothAdapter not supported by system");
- finish();
- return;
+ // set the controller
+ bluetoothFragment.setPairingController(mBluetoothPairingController);
+
+ // pass the fragment to the manager when it is created from scratch
+ if (!fragmentFound) {
+ bluetoothFragment.show(getFragmentManager(), FRAGMENT_TAG);
}
- mCachedDeviceManager = mBluetoothManager.getCachedDeviceManager();
- mPbapClientProfile = mBluetoothManager.getProfileManager().getPbapClientProfile();
-
- mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
-
- switch (mType) {
- case BluetoothDevice.PAIRING_VARIANT_PIN:
- case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
- case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
- createUserEntryDialog();
- break;
-
- case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
- int passkey =
- intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
- if (passkey == BluetoothDevice.ERROR) {
- Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog");
- return;
- }
- mPairingKey = String.format(Locale.US, "%06d", passkey);
- createConfirmationDialog();
- break;
-
- case BluetoothDevice.PAIRING_VARIANT_CONSENT:
- case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
- createConsentDialog();
- break;
-
- case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
- case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
- int pairingKey =
- intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
- if (pairingKey == BluetoothDevice.ERROR) {
- Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog");
- return;
- }
- if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
- mPairingKey = String.format("%06d", pairingKey);
- } else {
- mPairingKey = String.format("%04d", pairingKey);
- }
- createDisplayPasskeyOrPinDialog();
- break;
-
- default:
- Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
- }
-
/*
* Leave this registered through pause/resume since we still want to
* finish the activity in the background if pairing is canceled.
@@ -172,210 +92,6 @@
mReceiverRegistered = true;
}
- private void createUserEntryDialog() {
- final AlertController.AlertParams p = mAlertParams;
- p.mTitle = getString(R.string.bluetooth_pairing_request,
- mCachedDeviceManager.getName(mDevice));
- p.mView = createPinEntryView();
- p.mPositiveButtonText = getString(android.R.string.ok);
- p.mPositiveButtonListener = this;
- p.mNegativeButtonText = getString(android.R.string.cancel);
- p.mNegativeButtonListener = this;
- setupAlert();
-
- mOkButton = mAlert.getButton(BUTTON_POSITIVE);
- mOkButton.setEnabled(false);
- }
-
- private View createPinEntryView() {
- View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
- TextView messageViewCaptionHint = (TextView) view.findViewById(R.id.pin_values_hint);
- TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
- CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
- CheckBox contactSharing = (CheckBox) view.findViewById(
- R.id.phonebook_sharing_message_entry_pin);
- contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook,
- mCachedDeviceManager.getName(mDevice)));
- if (mPbapClientProfile != null && mPbapClientProfile.isProfileReady()) {
- contactSharing.setVisibility(View.GONE);
- }
- if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_ALLOWED) {
- contactSharing.setChecked(true);
- } else if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_REJECTED){
- contactSharing.setChecked(false);
- } else {
- if (mDevice.getBluetoothClass().getDeviceClass()
- == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
- contactSharing.setChecked(true);
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
- } else {
- contactSharing.setChecked(false);
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
- }
- }
-
- contactSharing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
- if (isChecked) {
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
- } else {
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
- }
- }
- });
-
- mPairingView = (EditText) view.findViewById(R.id.text);
- mPairingView.addTextChangedListener(this);
- alphanumericPin.setOnCheckedChangeListener(this);
-
- int messageId;
- int messageIdHint = R.string.bluetooth_pin_values_hint;
- int maxLength;
- switch (mType) {
- case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
- messageIdHint = R.string.bluetooth_pin_values_hint_16_digits;
- // FALLTHROUGH
- case BluetoothDevice.PAIRING_VARIANT_PIN:
- messageId = R.string.bluetooth_enter_pin_other_device;
- // Maximum of 16 characters in a PIN
- maxLength = BLUETOOTH_PIN_MAX_LENGTH;
- break;
-
- case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
- messageId = R.string.bluetooth_enter_passkey_other_device;
- // Maximum of 6 digits for passkey
- maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
- alphanumericPin.setVisibility(View.GONE);
- break;
-
- default:
- Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
- return null;
- }
-
- messageViewCaptionHint.setText(messageIdHint);
- messageView2.setText(messageId);
- mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
- mPairingView.setFilters(new InputFilter[] {
- new LengthFilter(maxLength) });
-
- return view;
- }
-
- private View createView() {
- View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
- TextView pairingViewCaption = (TextView) view.findViewById(R.id.pairing_caption);
- TextView pairingViewContent = (TextView) view.findViewById(R.id.pairing_subhead);
- TextView messagePairing = (TextView) view.findViewById(R.id.pairing_code_message);
- CheckBox contactSharing = (CheckBox) view.findViewById(
- R.id.phonebook_sharing_message_confirm_pin);
- contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook,
- mCachedDeviceManager.getName(mDevice)));
- if (mPbapClientProfile != null && mPbapClientProfile.isProfileReady()) {
- contactSharing.setVisibility(View.GONE);
- }
- if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_ALLOWED) {
- contactSharing.setChecked(true);
- } else if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_REJECTED){
- contactSharing.setChecked(false);
- } else {
- if (mDevice.getBluetoothClass().getDeviceClass()
- == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
- contactSharing.setChecked(true);
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
- } else {
- contactSharing.setChecked(false);
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
- }
- }
-
- contactSharing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
- if (isChecked) {
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
- } else {
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
- }
- }
- });
-
- SpannableString pairingContent = null;
- switch (mType) {
- case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
- case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
- messagePairing.setVisibility(View.VISIBLE);
- case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: {
- pairingContent = new SpannableString(mPairingKey);
- pairingContent.setSpan(new TtsSpan.DigitsBuilder(mPairingKey).build(),
- 0, mPairingKey.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- break;
- }
- case BluetoothDevice.PAIRING_VARIANT_CONSENT:
- messagePairing.setVisibility(view.GONE);
- break;
- case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
- messagePairing.setVisibility(View.VISIBLE);
- break;
-
- default:
- Log.e(TAG, "Incorrect pairing type received, not creating view");
- return null;
- }
-
- if (pairingContent != null) {
- pairingViewCaption.setVisibility(View.VISIBLE);
- pairingViewContent.setVisibility(View.VISIBLE);
- pairingViewContent.setText(pairingContent);
- }
-
- return view;
- }
-
- private void createConfirmationDialog() {
- final AlertController.AlertParams p = mAlertParams;
- p.mTitle = getString(R.string.bluetooth_pairing_request,
- mCachedDeviceManager.getName(mDevice));
- p.mView = createView();
- p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
- p.mPositiveButtonListener = this;
- p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
- p.mNegativeButtonListener = this;
- setupAlert();
- }
-
- private void createConsentDialog() {
- final AlertController.AlertParams p = mAlertParams;
- p.mTitle = getString(R.string.bluetooth_pairing_request,
- mCachedDeviceManager.getName(mDevice));
- p.mView = createView();
- p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
- p.mPositiveButtonListener = this;
- p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
- p.mNegativeButtonListener = this;
- setupAlert();
- }
-
- private void createDisplayPasskeyOrPinDialog() {
- final AlertController.AlertParams p = mAlertParams;
- p.mTitle = getString(R.string.bluetooth_pairing_request,
- mCachedDeviceManager.getName(mDevice));
- p.mView = createView();
- p.mNegativeButtonText = getString(android.R.string.cancel);
- p.mNegativeButtonListener = this;
- setupAlert();
-
- // Since its only a notification, send an OK to the framework,
- // indicating that the dialog has been displayed.
- if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
- mDevice.setPairingConfirmation(true);
- } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
- byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPairingKey);
- mDevice.setPin(pinBytes);
- }
- }
-
@Override
protected void onDestroy() {
super.onDestroy();
@@ -385,95 +101,9 @@
}
}
- public void afterTextChanged(Editable s) {
- if (mOkButton != null) {
- if (mType == BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS) {
- mOkButton.setEnabled(s.length() >= 16);
- } else {
- mOkButton.setEnabled(s.length() > 0);
- }
- }
- }
-
- private void onPair(String value) {
- Log.i(TAG, "Pairing dialog accepted");
- switch (mType) {
- case BluetoothDevice.PAIRING_VARIANT_PIN:
- case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
- byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
- if (pinBytes == null) {
- return;
- }
- mDevice.setPin(pinBytes);
- break;
-
- case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
- int passkey = Integer.parseInt(value);
- mDevice.setPasskey(passkey);
- break;
-
- case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
- case BluetoothDevice.PAIRING_VARIANT_CONSENT:
- mDevice.setPairingConfirmation(true);
- break;
-
- case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
- case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
- // Do nothing.
- break;
-
- case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
- mDevice.setRemoteOutOfBandData();
- break;
-
- default:
- Log.e(TAG, "Incorrect pairing type received");
- }
- }
-
- private void onCancel() {
- Log.i(TAG, "Pairing dialog canceled");
- mDevice.cancelPairingUserInput();
- }
-
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- onCancel();
- }
- return super.onKeyDown(keyCode,event);
- }
-
- public void onClick(DialogInterface dialog, int which) {
- switch (which) {
- case BUTTON_POSITIVE:
- if (mPairingView != null) {
- onPair(mPairingView.getText().toString());
- } else {
- onPair(null);
- }
- break;
-
- case BUTTON_NEGATIVE:
- default:
- onCancel();
- break;
- }
- }
-
- /* Not used */
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- /* Not used */
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
-
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- // change input type for soft keyboard to numeric or alphanumeric
- if (isChecked) {
- mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
- } else {
- mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
+ private void dismiss() {
+ if (!isFinishing()) {
+ finish();
}
}
}
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java
new file mode 100644
index 0000000..d4247c0
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.bluetooth;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.InputFilter.LengthFilter;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.TextView;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+/**
+ * A dialogFragment used by {@link BluetoothPairingDialog} to create an appropriately styled dialog
+ * for the bluetooth device.
+ */
+public class BluetoothPairingDialogFragment extends InstrumentedDialogFragment implements
+ TextWatcher, OnClickListener {
+
+ private static final String TAG = "BTPairingDialogFragment";
+
+ private AlertDialog.Builder mBuilder;
+ private BluetoothPairingController mPairingController;
+ private AlertDialog mDialog;
+ private EditText mPairingView;
+
+ /**
+ * The interface we expect a listener to implement. Typically this should be done by
+ * the controller.
+ */
+ public interface BluetoothPairingDialogListener {
+
+ void onDialogNegativeClick(BluetoothPairingDialogFragment dialog);
+
+ void onDialogPositiveClick(BluetoothPairingDialogFragment dialog);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ if (mPairingController == null) {
+ throw new IllegalStateException(
+ "Must call setPairingController() before showing dialog");
+ }
+ mBuilder = new AlertDialog.Builder(getActivity());
+ mDialog = setupDialog();
+ mDialog.setCanceledOnTouchOutside(false);
+ return mDialog;
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ // enable the positive button when we detect potentially valid input
+ Button positiveButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ if (positiveButton != null) {
+ positiveButton.setEnabled(mPairingController.isPasskeyValid(s));
+ }
+ // notify the controller about user input
+ mPairingController.updateUserInput(s.toString());
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ mPairingController.onDialogPositiveClick(this);
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ mPairingController.onDialogNegativeClick(this);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.BLUETOOTH_DIALOG_FRAGMENT;
+ }
+
+ /**
+ * Sets the controller that the fragment should use. this method MUST be called
+ * before you try to show the dialog or an error will be thrown. An implementation
+ * of a pairing controller can be found at {@link BluetoothPairingController}.
+ */
+ public void setPairingController(BluetoothPairingController pairingController) {
+ mPairingController = pairingController;
+ }
+
+ /**
+ * Creates the appropriate type of dialog and returns it.
+ */
+ private AlertDialog setupDialog() {
+ AlertDialog dialog;
+ switch (mPairingController.getDialogType()) {
+ case BluetoothPairingController.USER_ENTRY_DIALOG:
+ dialog = createUserEntryDialog();
+ break;
+ case BluetoothPairingController.CONFIRMATION_DIALOG:
+ dialog = createConsentDialog();
+ break;
+ case BluetoothPairingController.DISPLAY_PASSKEY_DIALOG:
+ dialog = createDisplayPasskeyOrPinDialog();
+ break;
+ default:
+ dialog = null;
+ Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
+ }
+ return dialog;
+ }
+
+ /**
+ * Returns a dialog with UI elements that allow a user to provide input.
+ */
+ private AlertDialog createUserEntryDialog() {
+ mBuilder.setTitle(getString(R.string.bluetooth_pairing_request,
+ mPairingController.getDeviceName()));
+ mBuilder.setView(createPinEntryView());
+ mBuilder.setPositiveButton(getString(android.R.string.ok), this);
+ mBuilder.setNegativeButton(getString(android.R.string.cancel), this);
+ AlertDialog dialog = mBuilder.create();
+ dialog.getButton(Dialog.BUTTON_POSITIVE).setEnabled(false);
+ return dialog;
+ }
+
+ /**
+ * Creates the custom view with UI elements for user input.
+ */
+ private View createPinEntryView() {
+ View view = getActivity().getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
+ TextView messageViewCaptionHint = (TextView) view.findViewById(R.id.pin_values_hint);
+ TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
+ CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
+ CheckBox contactSharing = (CheckBox) view.findViewById(
+ R.id.phonebook_sharing_message_entry_pin);
+ contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook,
+ mPairingController.getDeviceName()));
+ EditText pairingView = (EditText) view.findViewById(R.id.text);
+
+ contactSharing.setVisibility(mPairingController.isProfileReady()
+ ? View.GONE : View.VISIBLE);
+ contactSharing.setOnCheckedChangeListener(mPairingController);
+ contactSharing.setChecked(mPairingController.getContactSharingState());
+
+ mPairingView = pairingView;
+
+ pairingView.addTextChangedListener(this);
+ alphanumericPin.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ // change input type for soft keyboard to numeric or alphanumeric
+ if (isChecked) {
+ mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
+ } else {
+ mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
+ }
+ });
+
+ int messageId = mPairingController.getDeviceVariantMessageID();
+ int messageIdHint = mPairingController.getDeviceVariantMessageHint();
+ int maxLength = mPairingController.getDeviceMaxPasskeyLength();
+ alphanumericPin.setVisibility(mPairingController.pairingCodeIsAlphanumeric()
+ ? View.VISIBLE : View.GONE);
+
+ messageViewCaptionHint.setText(messageIdHint);
+ messageView2.setText(messageId);
+ pairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
+ pairingView.setFilters(new InputFilter[]{
+ new LengthFilter(maxLength)});
+
+ return view;
+ }
+
+ /**
+ * Creates a dialog with UI elements that allow the user to confirm a pairing request.
+ */
+ private AlertDialog createConfirmationDialog() {
+ mBuilder.setTitle(getString(R.string.bluetooth_pairing_request,
+ mPairingController.getDeviceName()));
+ mBuilder.setView(createView());
+ mBuilder.setPositiveButton(getString(R.string.bluetooth_pairing_accept),
+ this);
+ mBuilder.setNegativeButton(getString(R.string.bluetooth_pairing_decline),
+ this);
+ AlertDialog dialog = mBuilder.create();
+ return dialog;
+ }
+
+ /**
+ * Creates a dialog with UI elements that allow the user to consent to a pairing request.
+ */
+ private AlertDialog createConsentDialog() {
+ return createConfirmationDialog();
+ }
+
+ /**
+ * Creates a dialog that informs users of a pairing request and shows them the passkey/pin
+ * of the device.
+ */
+ private AlertDialog createDisplayPasskeyOrPinDialog() {
+ mBuilder.setTitle(getString(R.string.bluetooth_pairing_request,
+ mPairingController.getDeviceName()));
+ mBuilder.setView(createView());
+ mBuilder.setNegativeButton(getString(android.R.string.cancel), this);
+ AlertDialog dialog = mBuilder.create();
+
+ // Tell the controller the dialog has been created.
+ mPairingController.notifyDialogDisplayed();
+
+ return dialog;
+ }
+
+ /**
+ * Creates a custom view for dialogs which need to show users additional information but do
+ * not require user input.
+ */
+ private View createView() {
+ View view = getActivity().getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
+ TextView pairingViewCaption = (TextView) view.findViewById(R.id.pairing_caption);
+ TextView pairingViewContent = (TextView) view.findViewById(R.id.pairing_subhead);
+ TextView messagePairing = (TextView) view.findViewById(R.id.pairing_code_message);
+ CheckBox contactSharing = (CheckBox) view.findViewById(
+ R.id.phonebook_sharing_message_confirm_pin);
+ contactSharing.setText(getString(R.string.bluetooth_pairing_shares_phonebook,
+ mPairingController.getDeviceName()));
+
+ contactSharing.setVisibility(
+ mPairingController.isProfileReady() ? View.GONE : View.VISIBLE);
+ contactSharing.setChecked(mPairingController.getContactSharingState());
+ contactSharing.setOnCheckedChangeListener(mPairingController);
+
+ messagePairing.setVisibility(mPairingController.isDisplayPairingKeyVariant()
+ ? View.VISIBLE : View.GONE);
+ if (mPairingController.hasPairingContent()) {
+ pairingViewCaption.setVisibility(View.VISIBLE);
+ pairingViewContent.setVisibility(View.VISIBLE);
+ pairingViewContent.setText(mPairingController.getPairingContent());
+ }
+ return view;
+ }
+
+}