Merge "Fix binder death and disconnection." into main
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 789a1e4..e08eb37 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -66,7 +66,8 @@
*/
public class TileLifecycleManager extends BroadcastReceiver implements
IQSTileService, ServiceConnection, IBinder.DeathRecipient {
- public static final boolean DEBUG = false;
+
+ private final boolean mDebug = Log.isLoggable(TAG, Log.DEBUG);
private static final String TAG = "TileLifecycleManager";
@@ -101,7 +102,7 @@
private Set<Integer> mQueuedMessages = new ArraySet<>();
@Nullable
- private QSTileServiceWrapper mWrapper;
+ private volatile QSTileServiceWrapper mWrapper;
private boolean mListening;
private IBinder mClickBinder;
@@ -132,7 +133,7 @@
mPackageManagerAdapter = packageManagerAdapter;
mBroadcastDispatcher = broadcastDispatcher;
mActivityManager = activityManager;
- if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser);
+ if (mDebug) Log.d(TAG, "Creating " + mIntent + " " + mUser);
}
/** Injectable factory for TileLifecycleManager. */
@@ -215,10 +216,11 @@
if (!checkComponentState()) {
return;
}
- if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
+ if (mDebug) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
mBindTryCount++;
try {
- mIsBound.set(bindServices());
+ // Only try a new binding if we are not currently bound.
+ mIsBound.compareAndSet(false, bindServices());
if (!mIsBound.get()) {
mContext.unbindService(this);
}
@@ -227,19 +229,7 @@
mIsBound.set(false);
}
} else {
- if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
- // Give it another chance next time it needs to be bound, out of kindness.
- mBindTryCount = 0;
- freeWrapper();
- if (mIsBound.get()) {
- try {
- mContext.unbindService(this);
- } catch (Exception e) {
- Log.e(TAG, "Failed to unbind service "
- + mIntent.getComponent().flattenToShortString(), e);
- }
- mIsBound.set(false);
- }
+ unbindService();
}
}
@@ -264,9 +254,26 @@
mUser);
}
+ @WorkerThread
+ private void unbindService() {
+ if (mDebug) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
+ // Give it another chance next time it needs to be bound, out of kindness.
+ mBindTryCount = 0;
+ freeWrapper();
+ if (mIsBound.get()) {
+ try {
+ mContext.unbindService(this);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to unbind service "
+ + mIntent.getComponent().flattenToShortString(), e);
+ }
+ mIsBound.set(false);
+ }
+ }
+
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG) Log.d(TAG, "onServiceConnected " + name);
+ if (mDebug) Log.d(TAG, "onServiceConnected " + name);
// Got a connection, set the binding count to 0.
mBindTryCount = 0;
final QSTileServiceWrapper wrapper = new QSTileServiceWrapper(Stub.asInterface(service));
@@ -284,11 +291,17 @@
}
@Override
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
+ public void onBindingDied(ComponentName name) {
+ if (mDebug) Log.d(TAG, "onBindingDied " + name);
handleDeath();
}
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (mDebug) Log.d(TAG, "onServiceDisconnected " + name);
+ freeWrapper();
+ }
+
private void handlePendingMessages() {
// This ordering is laid out manually to make sure we preserve the TileService
// lifecycle.
@@ -298,35 +311,36 @@
mQueuedMessages.clear();
}
if (queue.contains(MSG_ON_ADDED)) {
- if (DEBUG) Log.d(TAG, "Handling pending onAdded");
+ if (mDebug) Log.d(TAG, "Handling pending onAdded " + getComponent());
onTileAdded();
}
if (mListening) {
- if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
+ if (mDebug) Log.d(TAG, "Handling pending onStartListening " + getComponent());
onStartListening();
}
if (queue.contains(MSG_ON_CLICK)) {
- if (DEBUG) Log.d(TAG, "Handling pending onClick");
+ if (mDebug) Log.d(TAG, "Handling pending onClick " + getComponent());
if (!mListening) {
- Log.w(TAG, "Managed to get click on non-listening state...");
+ Log.w(TAG, "Managed to get click on non-listening state... " + getComponent());
// Skipping click since lost click privileges.
} else {
onClick(mClickBinder);
}
}
if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) {
- if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete");
+ if (mDebug) Log.d(TAG, "Handling pending onUnlockComplete " + getComponent());
if (!mListening) {
- Log.w(TAG, "Managed to get unlock on non-listening state...");
+ Log.w(TAG,
+ "Managed to get unlock on non-listening state... " + getComponent());
// Skipping unlock since lost click privileges.
} else {
onUnlockComplete();
}
}
if (queue.contains(MSG_ON_REMOVED)) {
- if (DEBUG) Log.d(TAG, "Handling pending onRemoved");
+ if (mDebug) Log.d(TAG, "Handling pending onRemoved " + getComponent());
if (mListening) {
- Log.w(TAG, "Managed to get remove in listening state...");
+ Log.w(TAG, "Managed to get remove in listening state... " + getComponent());
onStopListening();
}
onTileRemoved();
@@ -340,28 +354,44 @@
}
public void handleDestroy() {
- if (DEBUG) Log.d(TAG, "handleDestroy");
+ if (mDebug) Log.d(TAG, "handleDestroy");
if (mPackageReceiverRegistered.get() || mUserReceiverRegistered.get()) {
stopPackageListening();
}
mChangeListener = null;
}
+ /**
+ * Handles a dead binder.
+ *
+ * It means that we need to clean up the binding (calling unbindService). After that, if we
+ * are supposed to be bound, we will try to bind after some amount of time.
+ */
private void handleDeath() {
- if (mWrapper == null) return;
- freeWrapper();
- // Clearly not bound anymore
- mIsBound.set(false);
- if (!mBound.get()) return;
- if (DEBUG) Log.d(TAG, "handleDeath");
- if (checkComponentState()) {
- if (isDeathRebindScheduled.compareAndSet(false, true)) {
- mExecutor.executeDelayed(() -> {
- setBindService(true);
- isDeathRebindScheduled.set(false);
- }, getRebindDelay());
+ mExecutor.execute(() -> {
+ if (!mIsBound.get()) {
+ // If we are already not bound, don't do anything else.
+ return;
}
- }
+ // Clearly we shouldn't be bound anymore
+ if (mDebug) Log.d(TAG, "handleDeath " + getComponent());
+ // Binder died, make sure that we unbind. However, we don't want to call setBindService
+ // as we still may want to rebind.
+ unbindService();
+ // If mBound is true (meaning that we should be bound), then reschedule binding for
+ // later.
+ if (mBound.get() && checkComponentState()) {
+ if (isDeathRebindScheduled.compareAndSet(false, true)) {
+ mExecutor.executeDelayed(() -> {
+ // Only rebind if we are supposed to, but remove the scheduling anyway.
+ if (mBound.get()) {
+ setBindService(true);
+ }
+ isDeathRebindScheduled.set(false);
+ }, getRebindDelay());
+ }
+ }
+ });
}
/**
@@ -379,7 +409,7 @@
} else {
delay = mBindRetryDelay;
}
- Log.i(TAG, "Rebinding with a delay=" + delay);
+ if (mDebug) Log.i(TAG, "Rebinding with a delay=" + delay + " - " + getComponent());
return delay;
}
@@ -392,7 +422,7 @@
}
private void startPackageListening() {
- if (DEBUG) Log.d(TAG, "startPackageListening");
+ if (mDebug) Log.d(TAG, "startPackageListening " + getComponent());
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
@@ -402,7 +432,7 @@
this, mUser, filter, null, mHandler, Context.RECEIVER_EXPORTED);
} catch (Exception ex) {
mPackageReceiverRegistered.set(false);
- Log.e(TAG, "Could not register package receiver", ex);
+ Log.e(TAG, "Could not register package receiver " + getComponent(), ex);
}
filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
try {
@@ -410,12 +440,12 @@
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser);
} catch (Exception ex) {
mUserReceiverRegistered.set(false);
- Log.e(TAG, "Could not register unlock receiver", ex);
+ Log.e(TAG, "Could not register unlock receiver " + getComponent(), ex);
}
}
private void stopPackageListening() {
- if (DEBUG) Log.d(TAG, "stopPackageListening");
+ if (mDebug) Log.d(TAG, "stopPackageListening " + getComponent());
if (mUserReceiverRegistered.compareAndSet(true, false)) {
mBroadcastDispatcher.unregisterReceiver(this);
}
@@ -430,7 +460,7 @@
@Override
public void onReceive(Context context, Intent intent) {
- if (DEBUG) Log.d(TAG, "onReceive: " + intent);
+ if (mDebug) Log.d(TAG, "onReceive: " + intent);
if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
Uri data = intent.getData();
String pkgName = data.getEncodedSchemeSpecificPart();
@@ -446,7 +476,7 @@
if (mBound.get()) {
// Trying to bind again will check the state of the package before bothering to
// bind.
- if (DEBUG) Log.d(TAG, "Trying to rebind");
+ if (mDebug) Log.d(TAG, "Trying to rebind " + getComponent());
setBindService(true);
}
@@ -458,7 +488,9 @@
try {
ServiceInfo si = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
0, mUser.getIdentifier());
- if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent());
+ if (mDebug && si == null) {
+ Log.d(TAG, "Can't find component " + mIntent.getComponent());
+ }
return si != null;
} catch (RemoteException e) {
// Shouldn't happen.
@@ -472,7 +504,7 @@
mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier());
return true;
} catch (PackageManager.NameNotFoundException e) {
- if (DEBUG) {
+ if (mDebug) {
Log.d(TAG, "Package not available: " + packageName, e);
} else {
Log.d(TAG, "Package not available: " + packageName);
@@ -489,7 +521,7 @@
@Override
public void onTileAdded() {
- if (DEBUG) Log.d(TAG, "onTileAdded");
+ if (mDebug) Log.d(TAG, "onTileAdded " + getComponent());
if (mWrapper == null || !mWrapper.onTileAdded()) {
queueMessage(MSG_ON_ADDED);
handleDeath();
@@ -498,7 +530,7 @@
@Override
public void onTileRemoved() {
- if (DEBUG) Log.d(TAG, "onTileRemoved");
+ if (mDebug) Log.d(TAG, "onTileRemoved " + getComponent());
if (mWrapper == null || !mWrapper.onTileRemoved()) {
queueMessage(MSG_ON_REMOVED);
handleDeath();
@@ -507,7 +539,7 @@
@Override
public void onStartListening() {
- if (DEBUG) Log.d(TAG, "onStartListening");
+ if (mDebug) Log.d(TAG, "onStartListening " + getComponent());
mListening = true;
if (mWrapper != null && !mWrapper.onStartListening()) {
handleDeath();
@@ -516,7 +548,7 @@
@Override
public void onStopListening() {
- if (DEBUG) Log.d(TAG, "onStopListening");
+ if (mDebug) Log.d(TAG, "onStopListening " + getComponent());
mListening = false;
if (mWrapper != null && !mWrapper.onStopListening()) {
handleDeath();
@@ -525,7 +557,7 @@
@Override
public void onClick(IBinder iBinder) {
- if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser);
+ if (mDebug) Log.d(TAG, "onClick " + iBinder + " " + getComponent() + " " + mUser);
if (mWrapper == null || !mWrapper.onClick(iBinder)) {
mClickBinder = iBinder;
queueMessage(MSG_ON_CLICK);
@@ -535,7 +567,7 @@
@Override
public void onUnlockComplete() {
- if (DEBUG) Log.d(TAG, "onUnlockComplete");
+ if (mDebug) Log.d(TAG, "onUnlockComplete " + getComponent());
if (mWrapper == null || !mWrapper.onUnlockComplete()) {
queueMessage(MSG_ON_UNLOCK_COMPLETE);
handleDeath();
@@ -550,7 +582,7 @@
@Override
public void binderDied() {
- if (DEBUG) Log.d(TAG, "binderDeath");
+ if (mDebug) Log.d(TAG, "binderDeath " + getComponent());
handleDeath();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index fbd63c6..8142456 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -300,8 +301,10 @@
mStateManager.onStartListening();
mStateManager.executeSetBindService(true);
mExecutor.runAllReady();
- mStateManager.onServiceDisconnected(mTileServiceComponentName);
+ mStateManager.onBindingDied(mTileServiceComponentName);
+ mExecutor.runAllReady();
mClock.advanceTime(5000);
+ mExecutor.runAllReady();
// Two calls: one for the first bind, one for the restart.
verifyBind(2);
@@ -318,20 +321,66 @@
mStateManager.onStartListening();
mStateManager.executeSetBindService(true);
mExecutor.runAllReady();
- mStateManager.onServiceDisconnected(mTileServiceComponentName);
+ verify(mMockTileService, times(1)).onStartListening();
+ mStateManager.onBindingDied(mTileServiceComponentName);
+ mExecutor.runAllReady();
// Longer delay than a regular one
mClock.advanceTime(5000);
- verifyBind(1);
- verify(mMockTileService, times(1)).onStartListening();
+ mExecutor.runAllReady();
+
+ assertFalse(mContext.isBound(mTileServiceComponentName));
mClock.advanceTime(20000);
+ mExecutor.runAllReady();
// Two calls: one for the first bind, one for the restart.
verifyBind(2);
verify(mMockTileService, times(2)).onStartListening();
}
@Test
+ public void testOnServiceDisconnectedDoesnUnbind_doesntForwardToBinder() throws Exception {
+ mStateManager.executeSetBindService(true);
+ mExecutor.runAllReady();
+
+ mStateManager.onStartListening();
+ verify(mMockTileService).onStartListening();
+
+ clearInvocations(mMockTileService);
+ mStateManager.onServiceDisconnected(mTileServiceComponentName);
+ mExecutor.runAllReady();
+
+ mStateManager.onStartListening();
+ verify(mMockTileService, never()).onStartListening();
+ }
+
+ @Test
+ public void testKillProcessLowMemory_unbound_doesntBindAgain() throws Exception {
+ doAnswer(invocation -> {
+ ActivityManager.MemoryInfo memoryInfo = invocation.getArgument(0);
+ memoryInfo.lowMemory = true;
+ return null;
+ }).when(mActivityManager).getMemoryInfo(any());
+ mStateManager.onStartListening();
+ mStateManager.executeSetBindService(true);
+ mExecutor.runAllReady();
+ verifyBind(1);
+ verify(mMockTileService, times(1)).onStartListening();
+
+ mStateManager.onBindingDied(mTileServiceComponentName);
+ mExecutor.runAllReady();
+
+ clearInvocations(mMockTileService);
+ mStateManager.executeSetBindService(false);
+ mExecutor.runAllReady();
+ mClock.advanceTime(30000);
+ mExecutor.runAllReady();
+
+ verifyBind(0);
+ verify(mMockTileService, never()).onStartListening();
+ }
+
+ @Test
public void testToggleableTile() throws Exception {
assertTrue(mStateManager.isToggleableTile());
}