Do not depend on task activities decrementing with finish.
It is possible for a task to be cleared when a single activity is
finished. For example, if only task overlays are present after an
activity is removed, the task can be cleared under certain
conditions. As a result, we can not rely on only a single activity
being removed from a task when it is finished.
This changelist addresses this issue by caching the list of actvities
under a task that will not be modified as activities are finished.
Change-Id: Id3b0813deebd0bc31b2ff7ae7f69a2833dcb0a61
Fixes: 64291682
Test: atest FrameworksServicesTests:com.android.server.am.ActivityStackTests#testFinishDisabledPackageActivities
Test: atest FrameworksServicesTests:com.android.server.am.ActivityStackTests#testHandleAppDied
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index f4a4af2..666f3a2 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -347,6 +347,9 @@
private final Rect mTmpRect2 = new Rect();
private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic();
+ /** List for processing through a set of activities */
+ private final ArrayList<ActivityRecord> mTmpActivities = new ArrayList<>();
+
/** Run all ActivityStacks through this */
protected final ActivityStackSupervisor mStackSupervisor;
@@ -4405,11 +4408,15 @@
"Removing app " + app + " from history with " + i + " entries");
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
- --i;
+ mTmpActivities.clear();
+ mTmpActivities.addAll(activities);
+
+ while (!mTmpActivities.isEmpty()) {
+ final int targetIndex = mTmpActivities.size() - 1;
+ final ActivityRecord r = mTmpActivities.remove(targetIndex);
if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP,
- "Record #" + i + " " + r + ": app=" + r.app);
+ "Record #" + targetIndex + " " + r + ": app=" + r.app);
+
if (r.app == app) {
if (r.visible) {
hasVisibleActivities = true;
@@ -4827,9 +4834,11 @@
ComponentName homeActivity = null;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- int numActivities = activities.size();
- for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
- ActivityRecord r = activities.get(activityNdx);
+ mTmpActivities.clear();
+ mTmpActivities.addAll(activities);
+
+ while (!mTmpActivities.isEmpty()) {
+ ActivityRecord r = mTmpActivities.remove(0);
final boolean sameComponent =
(r.packageName.equals(packageName) && (filterByClasses == null
|| filterByClasses.contains(r.realActivity.getClassName())))
@@ -4862,12 +4871,8 @@
r.app = null;
}
lastTask = r.getTask();
- if (finishActivityLocked(r, Activity.RESULT_CANCELED, null, "force-stop",
- true)) {
- // r has been deleted from mActivities, accommodate.
- --numActivities;
- --activityNdx;
- }
+ finishActivityLocked(r, Activity.RESULT_CANCELED, null, "force-stop",
+ true);
}
}
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 6f6e0d9..8174f40 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -2002,7 +2002,7 @@
} else if (intent != null) {
sb.append(" I=");
sb.append(intent.getComponent().flattenToShortString());
- } else if (affinityIntent != null) {
+ } else if (affinityIntent != null && affinityIntent.getComponent() != null) {
sb.append(" aI=");
sb.append(affinityIntent.getComponent().flattenToShortString());
} else {
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index 32a29a2..d60623d 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -486,4 +486,45 @@
verify(lifecycleManager, times(1)).scheduleTransaction(eq(app.thread),
eq(r.appToken), any(DestroyActivityItem.class));
}
+
+ @Test
+ public void testFinishDisabledPackageActivities() throws Exception {
+ final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build();
+ final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build();
+
+ // Making the second activity a task overlay without an app means it will be removed from
+ // the task's activities as well once first activity is removed.
+ secondActivity.mTaskOverlay = true;
+ secondActivity.app = null;
+
+ assertEquals(mTask.mActivities.size(), 2);
+
+ mStack.finishDisabledPackageActivitiesLocked(firstActivity.packageName, null,
+ true /* doit */, true /* evenPersistent */, UserHandle.USER_ALL);
+
+ assertTrue(mTask.mActivities.isEmpty());
+ assertTrue(mStack.getAllTasks().isEmpty());
+ }
+
+ @Test
+ public void testHandleAppDied() throws Exception {
+ final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build();
+ final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build();
+
+ // Making the first activity a task overlay means it will be removed from the task's
+ // activities as well once second activity is removed as handleAppDied processes the
+ // activity list in reverse.
+ firstActivity.mTaskOverlay = true;
+ firstActivity.app = null;
+
+ // second activity will be immediately removed as it has no state.
+ secondActivity.haveState = false;
+
+ assertEquals(mTask.mActivities.size(), 2);
+
+ mStack.handleAppDiedLocked(secondActivity.app);
+
+ assertTrue(mTask.mActivities.isEmpty());
+ assertTrue(mStack.getAllTasks().isEmpty());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index b58c700..fdabfb4 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -197,11 +197,6 @@
final ActivityInfo aInfo = containsConditions(preconditions, PRECONDITION_NO_ACTIVITY_INFO)
? null : new ActivityInfo();
- if (aInfo != null) {
- aInfo.applicationInfo = new ApplicationInfo();
- aInfo.applicationInfo.packageName = ActivityBuilder.DEFAULT_PACKAGE;
- }
-
IVoiceInteractionSession voiceSession =
containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION)
? mock(IVoiceInteractionSession.class) : null;
@@ -210,6 +205,11 @@
final ActivityBuilder builder = new ActivityBuilder(service).setTask(
new TaskBuilder(service.mStackSupervisor).setVoiceSession(voiceSession).build());
+ if (aInfo != null) {
+ aInfo.applicationInfo = new ApplicationInfo();
+ aInfo.applicationInfo.packageName = builder.getDefaultComponentPackageName();
+ }
+
// Offset uid by one from {@link ActivityInfo} to simulate different uids.
if (containsConditions(preconditions, PRECONDITION_DIFFERENT_UID)) {
builder.setUid(aInfo.applicationInfo.uid + 1);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index ff7b1d0..1195188 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -67,6 +67,12 @@
private final Context mContext = InstrumentationRegistry.getContext();
private HandlerThread mHandlerThread;
+ // Default package name
+ static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo";
+
+ // Default base activity name
+ private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity";
+
@Before
public void setUp() throws Exception {
if (!sOneTimeSetupDone) {
@@ -106,11 +112,7 @@
// An id appended to the end of the component name to make it unique
private static int sCurrentActivityId = 0;
- // Default package name
- static final String DEFAULT_PACKAGE = "com.foo";
- // Default base activity name
- private static final String DEFAULT_BASE_ACTIVITY_NAME = ".BarActivity";
private final ActivityManagerService mService;
@@ -149,11 +151,15 @@
return this;
}
+ String getDefaultComponentPackageName() {
+ return DEFAULT_COMPONENT_PACKAGE_NAME;
+ }
+
ActivityRecord build() {
if (mComponent == null) {
final int id = sCurrentActivityId++;
- mComponent = ComponentName.createRelative(DEFAULT_PACKAGE,
- DEFAULT_BASE_ACTIVITY_NAME + id);
+ mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+ DEFAULT_COMPONENT_CLASS_NAME + id);
}
if (mCreateTask) {
@@ -191,6 +197,9 @@
* Builder for creating new tasks.
*/
protected static class TaskBuilder {
+ // Default package name
+ static final String DEFAULT_PACKAGE = "com.bar";
+
private final ActivityStackSupervisor mSupervisor;
private ComponentName mComponent;
@@ -252,6 +261,11 @@
aInfo.applicationInfo.packageName = mPackage;
Intent intent = new Intent();
+ if (mComponent == null) {
+ mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+ DEFAULT_COMPONENT_CLASS_NAME);
+ }
+
intent.setComponent(mComponent);
intent.setFlags(mFlags);
@@ -312,6 +326,8 @@
doNothing().when(supervisor).ensureActivitiesVisibleLocked(any(), anyInt(), anyBoolean());
// Do not schedule idle timeouts
doNothing().when(supervisor).scheduleIdleTimeoutLocked(any());
+ // unit test version does not handle launch wake lock
+ doNothing().when(supervisor).acquireLaunchWakelock();
supervisor.initialize();