Fix sysui depending on slow bt calls
Move calls to a background thread and cache the results to avoid
slowing down sysui when there are too many bluetooth devices.
Test: runtest systemui
Change-Id: Ica10e86ca6b2783c30ed9de68f1a91dc5cfdf068
Fixes: 38118549
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 9b20a7a..79fb5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -179,12 +179,6 @@
@Override
public void onBluetoothDevicesChanged() {
- mUiHandler.post(new Runnable() {
- @Override
- public void run() {
- mDetailAdapter.updateItems();
- }
- });
refreshState();
if (isShowingDetail()) {
mDetailAdapter.updateItems();
@@ -198,6 +192,9 @@
}
protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
+ // We probably won't ever have space in the UI for more than 20 devices, so don't
+ // get info for them.
+ private static final int MAX_DEVICES = 20;
private QSDetailItems mItems;
@Override
@@ -260,13 +257,14 @@
final Collection<CachedBluetoothDevice> devices = mController.getDevices();
if (devices != null) {
int connectedDevices = 0;
+ int count = 0;
for (CachedBluetoothDevice device : devices) {
- if (device.getBondState() == BluetoothDevice.BOND_NONE) continue;
+ if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
final Item item = new Item();
item.icon = R.drawable.ic_qs_bluetooth_on;
item.line1 = device.getName();
item.tag = device;
- int state = device.getMaxConnectionState();
+ int state = mController.getMaxConnectionState(device);
if (state == BluetoothProfile.STATE_CONNECTED) {
item.icon = R.drawable.ic_qs_bluetooth_connected;
item.line2 = mContext.getString(R.string.quick_settings_connected);
@@ -280,6 +278,9 @@
} else {
items.add(item);
}
+ if (++count == MAX_DEVICES) {
+ break;
+ }
}
}
mItems.setItems(items.toArray(new Item[items.size()]));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index df30e20..9daa199 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -37,6 +37,9 @@
void disconnect(CachedBluetoothDevice device);
boolean canConfigBluetooth();
+ int getMaxConnectionState(CachedBluetoothDevice device);
+ int getBondState(CachedBluetoothDevice device);
+
public interface Callback {
void onBluetoothStateChange(boolean enabled);
void onBluetoothDevicesChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 36d24b3..dc4b115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -18,6 +18,8 @@
import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
@@ -33,8 +35,10 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.WeakHashMap;
public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
CachedBluetoothDevice.Callback {
@@ -44,18 +48,22 @@
private final LocalBluetoothManager mLocalBluetoothManager;
private final UserManager mUserManager;
private final int mCurrentUser;
+ private final WeakHashMap<CachedBluetoothDevice, ActuallyCachedState> mCachedState =
+ new WeakHashMap<>();
+ private final Handler mBgHandler;
private boolean mEnabled;
private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
private CachedBluetoothDevice mLastDevice;
- private final H mHandler = new H();
+ private final H mHandler = new H(Looper.getMainLooper());
private int mState;
public BluetoothControllerImpl(Context context, Looper bgLooper) {
mLocalBluetoothManager = Dependency.get(LocalBluetoothManager.class);
+ mBgHandler = new Handler(bgLooper);
if (mLocalBluetoothManager != null) {
- mLocalBluetoothManager.getEventManager().setReceiverHandler(new Handler(bgLooper));
+ mLocalBluetoothManager.getEventManager().setReceiverHandler(mBgHandler);
mLocalBluetoothManager.getEventManager().registerCallback(this);
onBluetoothStateChanged(
mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
@@ -106,6 +114,16 @@
}
@Override
+ public int getBondState(CachedBluetoothDevice device) {
+ return getCachedState(device).mBondState;
+ }
+
+ @Override
+ public int getMaxConnectionState(CachedBluetoothDevice device) {
+ return getCachedState(device).mMaxConnectionState;
+ }
+
+ @Override
public void addCallback(Callback cb) {
mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget();
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
@@ -225,12 +243,14 @@
@Override
public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+ mCachedState.remove(cachedDevice);
updateConnected();
mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
@Override
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+ mCachedState.remove(cachedDevice);
updateConnected();
mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
@@ -243,11 +263,44 @@
@Override
public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+ mCachedState.remove(cachedDevice);
mLastDevice = cachedDevice;
updateConnected();
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
+ private ActuallyCachedState getCachedState(CachedBluetoothDevice device) {
+ ActuallyCachedState state = mCachedState.get(device);
+ if (state == null) {
+ state = new ActuallyCachedState(device, mHandler);
+ mBgHandler.post(state);
+ mCachedState.put(device, state);
+ return state;
+ }
+ return state;
+ }
+
+ private static class ActuallyCachedState implements Runnable {
+
+ private final WeakReference<CachedBluetoothDevice> mDevice;
+ private final Handler mUiHandler;
+ private int mBondState = BluetoothDevice.BOND_NONE;
+ private int mMaxConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+
+ private ActuallyCachedState(CachedBluetoothDevice device, Handler uiHandler) {
+ mDevice = new WeakReference<>(device);
+ mUiHandler = uiHandler;
+ }
+
+ @Override
+ public void run() {
+ mBondState = mDevice.get().getBondState();
+ mMaxConnectionState = mDevice.get().getMaxConnectionState();
+ mUiHandler.removeMessages(H.MSG_PAIRED_DEVICES_CHANGED);
+ mUiHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
+ }
+ }
+
private final class H extends Handler {
private final ArrayList<BluetoothController.Callback> mCallbacks = new ArrayList<>();
@@ -256,6 +309,10 @@
private static final int MSG_ADD_CALLBACK = 3;
private static final int MSG_REMOVE_CALLBACK = 4;
+ public H(Looper looper) {
+ super(looper);
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 2eb9560..4cc8bca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -14,16 +14,21 @@
package com.android.systemui.statusbar.policy;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.os.Looper;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import android.util.Log;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -80,4 +85,56 @@
BluetoothAdapter.STATE_DISCONNECTED);
assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
}
+
+ @Test
+ public void testDefaultConnectionState() {
+ CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device));
+ assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+ mBluetoothControllerImpl.getMaxConnectionState(device));
+ }
+
+ @Test
+ public void testAsyncBondState() throws Exception {
+ CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ when(device.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
+ mBluetoothControllerImpl.addCallback(callback);
+
+ // Grab the main looper, we'll need it later.
+ TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
+
+ // Trigger the state getting.
+ assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device));
+
+ mTestableLooper.processMessages(1);
+ mainLooper.processAllMessages();
+
+ assertEquals(BluetoothDevice.BOND_BONDED, mBluetoothControllerImpl.getBondState(device));
+ verify(callback).onBluetoothDevicesChanged();
+ mainLooper.destroy();
+ }
+
+ @Test
+ public void testAsyncConnectionState() throws Exception {
+ CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
+ BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
+ mBluetoothControllerImpl.addCallback(callback);
+
+ // Grab the main looper, we'll need it later.
+ TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
+
+ // Trigger the state getting.
+ assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+ mBluetoothControllerImpl.getMaxConnectionState(device));
+
+ mTestableLooper.processMessages(1);
+ mainLooper.processAllMessages();
+
+ assertEquals(BluetoothProfile.STATE_CONNECTED,
+ mBluetoothControllerImpl.getMaxConnectionState(device));
+ verify(callback).onBluetoothDevicesChanged();
+ mainLooper.destroy();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
index 0ba0319..9ec096a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
@@ -83,4 +83,14 @@
public boolean canConfigBluetooth() {
return false;
}
+
+ @Override
+ public int getMaxConnectionState(CachedBluetoothDevice device) {
+ return 0;
+ }
+
+ @Override
+ public int getBondState(CachedBluetoothDevice device) {
+ return 0;
+ }
}