Show error message when pairing fails in SUW.
Also, prevent multiple dialogs from coming up, dismiss the dialog
when the device goes back into tablet mode and prevent a SysUI crash
when we attempt to stop a scan after the bluetooth adapter turns off.
Bug: 25662777
Change-Id: Ib99eaecb0b60388e1f1efb89199ac4ffdb281536
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index ea7270d..4b775a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -38,7 +38,9 @@
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.text.TextUtils;
+import android.util.Pair;
import android.util.Slog;
+import android.widget.Toast;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -46,6 +48,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.bluetooth.Utils;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
@@ -76,8 +79,9 @@
private static final int STATE_WAITING_FOR_BLUETOOTH = 4;
private static final int STATE_PAIRING = 5;
private static final int STATE_PAIRED = 6;
- private static final int STATE_USER_CANCELLED = 7;
- private static final int STATE_DEVICE_NOT_FOUND = 8;
+ private static final int STATE_PAIRING_FAILED = 7;
+ private static final int STATE_USER_CANCELLED = 8;
+ private static final int STATE_DEVICE_NOT_FOUND = 9;
private static final int MSG_INIT = 0;
private static final int MSG_ON_BOOT_COMPLETED = 1;
@@ -90,6 +94,7 @@
private static final int MSG_SHOW_BLUETOOTH_DIALOG = 8;
private static final int MSG_DISMISS_BLUETOOTH_DIALOG = 9;
private static final int MSG_BLE_ABORT_SCAN = 10;
+ private static final int MSG_SHOW_ERROR = 11;
private volatile KeyboardHandler mHandler;
private volatile KeyboardUIHandler mUIHandler;
@@ -178,6 +183,7 @@
mLocalBluetoothAdapter = bluetoothManager.getBluetoothAdapter();
mProfileManager = bluetoothManager.getProfileManager();
bluetoothManager.getEventManager().registerCallback(new BluetoothCallbackHandler());
+ Utils.setErrorListener(new BluetoothErrorListener());
InputManager im = context.getSystemService(InputManager.class);
im.registerOnTabletModeChangedListener(this, mHandler);
@@ -204,13 +210,15 @@
if (mInTabletMode != InputManager.SWITCH_STATE_OFF) {
if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY) {
stopScanning();
+ } else if (mState == STATE_WAITING_FOR_BLUETOOTH) {
+ mUIHandler.sendEmptyMessage(MSG_DISMISS_BLUETOOTH_DIALOG);
}
mState = STATE_WAITING_FOR_TABLET_MODE_EXIT;
return;
}
final int btState = mLocalBluetoothAdapter.getState();
- if (btState == BluetoothAdapter.STATE_TURNING_ON || btState == BluetoothAdapter.STATE_ON
+ if ((btState == BluetoothAdapter.STATE_TURNING_ON || btState == BluetoothAdapter.STATE_ON)
&& mState == STATE_WAITING_FOR_BLUETOOTH) {
// If we're waiting for bluetooth but it has come on in the meantime, or is coming
// on, just dismiss the dialog. This frequently happens during device startup.
@@ -334,7 +342,10 @@
private void stopScanning() {
if (mScanCallback != null) {
- mLocalBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
+ BluetoothLeScanner scanner = mLocalBluetoothAdapter.getBluetoothLeScanner();
+ if (scanner != null) {
+ scanner.stopScan(mScanCallback);
+ }
mScanCallback = null;
}
}
@@ -370,10 +381,14 @@
// Should only be called on the handler thread
private void onDeviceBondStateChangedInternal(CachedBluetoothDevice d, int bondState) {
- if (d.getName().equals(mKeyboardName) && bondState == BluetoothDevice.BOND_BONDED) {
- // We don't need to manually connect to the device here because it will automatically
- // try to connect after it has been paired.
- mState = STATE_PAIRED;
+ if (mState == STATE_PAIRING && d.getName().equals(mKeyboardName)) {
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ // We don't need to manually connect to the device here because it will
+ // automatically try to connect after it has been paired.
+ mState = STATE_PAIRED;
+ } else if (bondState == BluetoothDevice.BOND_NONE) {
+ mState = STATE_PAIRING_FAILED;
+ }
}
}
@@ -385,6 +400,17 @@
}
}
+ // Should only be called on the handler thread. We want to be careful not to show errors for
+ // pairings not initiated by this UI, so we only pop up the toast when we're at an appropriate
+ // point in our pairing flow and it's the expected device.
+ private void onShowErrorInternal(Context context, String name, int messageResId) {
+ if ((mState == STATE_PAIRING || mState == STATE_PAIRING_FAILED)
+ && mKeyboardName.equals(name)) {
+ String message = context.getString(messageResId, name);
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+ }
+ }
+
private final class KeyboardUIHandler extends Handler {
public KeyboardUIHandler() {
super(Looper.getMainLooper(), null, true /*async*/);
@@ -393,19 +419,27 @@
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_SHOW_BLUETOOTH_DIALOG: {
- DialogInterface.OnClickListener listener = new BluetoothDialogClickListener();
+ if (mDialog != null) {
+ // Don't show another dialog if one is already present
+ break;
+ }
+ DialogInterface.OnClickListener clickListener =
+ new BluetoothDialogClickListener();
+ DialogInterface.OnDismissListener dismissListener =
+ new BluetoothDialogDismissListener();
mDialog = new BluetoothDialog(mContext);
mDialog.setTitle(R.string.enable_bluetooth_title);
mDialog.setMessage(R.string.enable_bluetooth_message);
- mDialog.setPositiveButton(R.string.enable_bluetooth_confirmation_ok, listener);
- mDialog.setNegativeButton(android.R.string.cancel, listener);
+ mDialog.setPositiveButton(
+ R.string.enable_bluetooth_confirmation_ok, clickListener);
+ mDialog.setNegativeButton(android.R.string.cancel, clickListener);
+ mDialog.setOnDismissListener(dismissListener);
mDialog.show();
break;
}
case MSG_DISMISS_BLUETOOTH_DIALOG: {
if (mDialog != null) {
mDialog.dismiss();
- mDialog = null;
}
break;
}
@@ -469,6 +503,10 @@
onBleScanFailedInternal();
break;
}
+ case MSG_SHOW_ERROR: {
+ Pair<Context, String> p = (Pair<Context, String>) msg.obj;
+ onShowErrorInternal(p.first, p.second, msg.arg1);
+ }
}
}
}
@@ -482,6 +520,14 @@
}
}
+ private final class BluetoothDialogDismissListener
+ implements DialogInterface.OnDismissListener {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mDialog = null;
+ }
+ }
+
private final class KeyboardScanCallback extends ScanCallback {
private boolean isDeviceDiscoverable(ScanResult result) {
@@ -564,6 +610,13 @@
public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { }
}
+ private final class BluetoothErrorListener implements Utils.ErrorListener {
+ public void onShowError(Context context, String name, int messageResId) {
+ mHandler.obtainMessage(MSG_SHOW_ERROR, messageResId, 0 /*unused*/,
+ new Pair<>(context, name)).sendToTarget();
+ }
+ }
+
private static String stateToString(int state) {
switch (state) {
case STATE_NOT_ENABLED:
@@ -580,13 +633,15 @@
return "STATE_PAIRING";
case STATE_PAIRED:
return "STATE_PAIRED";
+ case STATE_PAIRING_FAILED:
+ return "STATE_PAIRING_FAILED";
case STATE_USER_CANCELLED:
return "STATE_USER_CANCELLED";
case STATE_DEVICE_NOT_FOUND:
return "STATE_DEVICE_NOT_FOUND";
case STATE_UNKNOWN:
default:
- return "STATE_UNKNOWN";
+ return "STATE_UNKNOWN (" + state + ")";
}
}
}