diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 44d4c191e66..6ce38c70474 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2031,10 +2031,8 @@ + android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar"> diff --git a/src/com/android/settings/bluetooth/BluetoothPairingController.java b/src/com/android/settings/bluetooth/BluetoothPairingController.java new file mode 100644 index 00000000000..38b30a88cdb --- /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 27cd5329667..ed63fcbb4a2 --- 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 @@ public final class BluetoothPairingDialog extends AlertActivity implements 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 @@ public final class BluetoothPairingDialog extends AlertActivity implements }; @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 @@ public final class BluetoothPairingDialog extends AlertActivity implements 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 final class BluetoothPairingDialog extends AlertActivity implements } } - 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 00000000000..d4247c0e5d9 --- /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; + } + +}