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
This commit is contained in:
@@ -2031,10 +2031,8 @@
|
|||||||
<!-- Bluetooth stuff -->
|
<!-- Bluetooth stuff -->
|
||||||
|
|
||||||
<activity android:name=".bluetooth.BluetoothPairingDialog"
|
<activity android:name=".bluetooth.BluetoothPairingDialog"
|
||||||
android:label="@string/bluetooth_pairing_request"
|
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize"
|
android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar">
|
||||||
android:theme="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert">
|
|
||||||
<intent-filter android:priority="1">
|
<intent-filter android:priority="1">
|
||||||
<action android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
|
<action android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
428
src/com/android/settings/bluetooth/BluetoothPairingDialog.java
Executable file → Normal file
428
src/com/android/settings/bluetooth/BluetoothPairingDialog.java
Executable file → Normal file
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,65 +16,28 @@
|
|||||||
|
|
||||||
package com.android.settings.bluetooth;
|
package com.android.settings.bluetooth;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothClass;
|
import android.annotation.Nullable;
|
||||||
|
import android.app.Activity;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.bluetooth.BluetoothUuid;
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
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
|
* 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.
|
* for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
|
||||||
*/
|
*/
|
||||||
public final class BluetoothPairingDialog extends AlertActivity implements
|
public final class BluetoothPairingDialog extends Activity {
|
||||||
CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
|
public static final String FRAGMENT_TAG = "bluetooth.pairing.fragment";
|
||||||
private static final String TAG = "BluetoothPairingDialog";
|
|
||||||
|
|
||||||
private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
|
private BluetoothPairingController mBluetoothPairingController;
|
||||||
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 boolean mReceiverRegistered;
|
private boolean mReceiverRegistered;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dismiss the dialog if the bond state changes to bonded or none,
|
* 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() {
|
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
@@ -82,14 +45,14 @@ public final class BluetoothPairingDialog extends AlertActivity implements
|
|||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
|
if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
|
||||||
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
|
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
|
||||||
BluetoothDevice.ERROR);
|
BluetoothDevice.ERROR);
|
||||||
if (bondState == BluetoothDevice.BOND_BONDED ||
|
if (bondState == BluetoothDevice.BOND_BONDED ||
|
||||||
bondState == BluetoothDevice.BOND_NONE) {
|
bondState == BluetoothDevice.BOND_NONE) {
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
} else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
|
} else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
|
||||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||||
if (device == null || device.equals(mDevice)) {
|
if (device == null || mBluetoothPairingController.deviceEquals(device)) {
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,72 +60,29 @@ public final class BluetoothPairingDialog extends AlertActivity implements
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
boolean fragmentFound = true;
|
||||||
|
|
||||||
|
BluetoothPairingDialogFragment bluetoothFragment =
|
||||||
|
(BluetoothPairingDialogFragment) getFragmentManager()
|
||||||
|
.findFragmentByTag(FRAGMENT_TAG);
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
|
mBluetoothPairingController = new BluetoothPairingController(intent, this);
|
||||||
Log.e(TAG, "Error: this activity may be started only with intent " +
|
|
||||||
BluetoothDevice.ACTION_PAIRING_REQUEST);
|
// check if the fragment exists already
|
||||||
finish();
|
if (bluetoothFragment == null) {
|
||||||
return;
|
fragmentFound = false;
|
||||||
|
bluetoothFragment = new BluetoothPairingDialogFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
mBluetoothManager = Utils.getLocalBtManager(this);
|
// set the controller
|
||||||
if (mBluetoothManager == null) {
|
bluetoothFragment.setPairingController(mBluetoothPairingController);
|
||||||
Log.e(TAG, "Error: BluetoothAdapter not supported by system");
|
|
||||||
finish();
|
// pass the fragment to the manager when it is created from scratch
|
||||||
return;
|
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
|
* Leave this registered through pause/resume since we still want to
|
||||||
* finish the activity in the background if pairing is canceled.
|
* finish the activity in the background if pairing is canceled.
|
||||||
@@ -172,210 +92,6 @@ public final class BluetoothPairingDialog extends AlertActivity implements
|
|||||||
mReceiverRegistered = true;
|
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
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
@@ -385,95 +101,9 @@ public final class BluetoothPairingDialog extends AlertActivity implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void afterTextChanged(Editable s) {
|
private void dismiss() {
|
||||||
if (mOkButton != null) {
|
if (!isFinishing()) {
|
||||||
if (mType == BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS) {
|
finish();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user