Handle process die event of a bubble activity gracefully.
When the process behind a bubble activity died (e.g. killed by lmkd,
or app crash), collapse the bubble and restart the activity when it's
expanded next time.
Bug: 126945401
Test: atest TaskStackChangedListenerTest
Change-Id: Ia2ee21f9ced51fe33756b1e12159f445d6ed083d
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 74c3069..d902454 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -5441,6 +5441,7 @@
task.cleanUpResourcesForDestroy();
}
+ final ActivityDisplay display = getDisplay();
if (mTaskHistory.isEmpty()) {
if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this);
// We only need to adjust focused stack if this stack is in focus and we are not in the
@@ -5449,11 +5450,11 @@
&& mRootActivityContainer.isTopDisplayFocusedStack(this)) {
String myReason = reason + " leftTaskHistoryEmpty";
if (!inMultiWindowMode() || adjustFocusToNextFocusableStack(myReason) == null) {
- getDisplay().moveHomeStackToFront(myReason);
+ display.moveHomeStackToFront(myReason);
}
}
if (isAttached()) {
- getDisplay().positionChildAtBottom(this);
+ display.positionChildAtBottom(this);
}
if (!isActivityTypeHome() || !isAttached()) {
remove();
@@ -5466,6 +5467,9 @@
if (inPinnedWindowingMode()) {
mService.getTaskChangeNotificationController().notifyActivityUnpinned();
}
+ if (display.isSingleTaskInstance()) {
+ mService.notifySingleTaskDisplayEmpty(display.mDisplayId);
+ }
}
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 9fc278e..277da06 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6040,6 +6040,10 @@
return allUids.contains(uid);
}
+ void notifySingleTaskDisplayEmpty(int displayId) {
+ mTaskChangeNotificationController.notifySingleTaskDisplayEmpty(displayId);
+ }
+
final class H extends Handler {
static final int REPORT_TIME_TRACKER_MSG = 1;
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 27175c7..80470c6 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -55,6 +55,7 @@
private static final int NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG = 20;
private static final int NOTIFY_BACK_PRESSED_ON_TASK_ROOT = 21;
private static final int NOTIFY_SINGLE_TASK_DISPLAY_DRAWN = 22;
+ private static final int NOTIFY_SINGLE_TASK_DISPLAY_EMPTY = 23;
// Delay in notifying task stack change listeners (in millis)
private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -159,6 +160,10 @@
l.onSingleTaskDisplayDrawn(m.arg1);
};
+ private final TaskStackConsumer mNotifySingleTaskDisplayEmpty = (l, m) -> {
+ l.onSingleTaskDisplayEmpty(m.arg1);
+ };
+
@FunctionalInterface
public interface TaskStackConsumer {
void accept(ITaskStackListener t, Message m) throws RemoteException;
@@ -241,6 +246,9 @@
case NOTIFY_SINGLE_TASK_DISPLAY_DRAWN:
forAllRemoteListeners(mNotifySingleTaskDisplayDrawn, msg);
break;
+ case NOTIFY_SINGLE_TASK_DISPLAY_EMPTY:
+ forAllRemoteListeners(mNotifySingleTaskDisplayEmpty, msg);
+ break;
}
}
}
@@ -495,4 +503,15 @@
forAllLocalListeners(mNotifySingleTaskDisplayDrawn, msg);
msg.sendToTarget();
}
+
+ /**
+ * Notify listeners that the last task is removed from a single task display.
+ */
+ void notifySingleTaskDisplayEmpty(int displayId) {
+ final Message msg = mHandler.obtainMessage(
+ NOTIFY_SINGLE_TASK_DISPLAY_EMPTY,
+ displayId, 0 /* unused */);
+ forAllLocalListeners(mNotifySingleTaskDisplayEmpty, msg);
+ msg.sendToTarget();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index 19fd93fe..6e41118 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -271,6 +271,54 @@
waitForCallback(singleTaskDisplayDrawnLatch);
}
+ @Test
+ public void testSingleTaskDisplayEmpty() throws Exception {
+ final Instrumentation instrumentation = getInstrumentation();
+
+ final CountDownLatch activityViewReadyLatch = new CountDownLatch(1);
+ final CountDownLatch activityViewDestroyedLatch = new CountDownLatch(1);
+ final CountDownLatch singleTaskDisplayDrawnLatch = new CountDownLatch(1);
+ final CountDownLatch singleTaskDisplayEmptyLatch = new CountDownLatch(1);
+
+ registerTaskStackChangedListener(new TaskStackListener() {
+ @Override
+ public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
+ singleTaskDisplayDrawnLatch.countDown();
+ }
+ @Override
+ public void onSingleTaskDisplayEmpty(int displayId)
+ throws RemoteException {
+ singleTaskDisplayEmptyLatch.countDown();
+ }
+ });
+ final ActivityViewTestActivity activity =
+ (ActivityViewTestActivity) startTestActivity(ActivityViewTestActivity.class);
+ final ActivityView activityView = activity.getActivityView();
+ activityView.setCallback(new ActivityView.StateCallback() {
+ @Override
+ public void onActivityViewReady(ActivityView view) {
+ activityViewReadyLatch.countDown();
+ }
+
+ @Override
+ public void onActivityViewDestroyed(ActivityView view) {
+ activityViewDestroyedLatch.countDown();
+ }
+ });
+ waitForCallback(activityViewReadyLatch);
+
+ final Context context = instrumentation.getContext();
+ Intent intent = new Intent(context, ActivityInActivityView.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ activityView.startActivity(intent);
+ waitForCallback(singleTaskDisplayDrawnLatch);
+ assertEquals(1, singleTaskDisplayEmptyLatch.getCount());
+
+ activityView.release();
+ waitForCallback(activityViewDestroyedLatch);
+ waitForCallback(singleTaskDisplayEmptyLatch);
+ }
+
/**
* Starts the provided activity and returns the started instance.
*/