Merge "Special handling of processes with recent tasks."
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 064e978..02b7f8c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -519,11 +519,15 @@
      * process that contains activities. */
     public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 16;
 
+    /** @hide Process is being cached for later use and has an activity that corresponds
+     * to an existing recent task. */
+    public static final int PROCESS_STATE_CACHED_RECENT = 17;
+
     /** @hide Process is being cached for later use and is empty. */
-    public static final int PROCESS_STATE_CACHED_EMPTY = 17;
+    public static final int PROCESS_STATE_CACHED_EMPTY = 18;
 
     /** @hide Process does not exist. */
-    public static final int PROCESS_STATE_NONEXISTENT = 18;
+    public static final int PROCESS_STATE_NONEXISTENT = 19;
 
     // NOTE: If PROCESS_STATEs are added or changed, then new fields must be added
     // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 7519fce..fbdf17d 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -102,6 +102,7 @@
         STATE_LAST_ACTIVITY,            // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         STATE_CACHED_ACTIVITY,          // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
         STATE_CACHED_ACTIVITY_CLIENT,   // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        STATE_CACHED_ACTIVITY,          // ActivityManager.PROCESS_STATE_CACHED_RECENT
         STATE_CACHED_EMPTY,             // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 26d871f..0a6f976 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3325,7 +3325,7 @@
             String what, Object obj, ProcessRecord srcApp) {
         app.lastActivityTime = now;
 
-        if (app.activities.size() > 0) {
+        if (app.activities.size() > 0 || app.recentTasks.size() > 0) {
             // Don't want to touch dependent processes that are hosting activities.
             return index;
         }
@@ -3389,7 +3389,7 @@
     final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
             ProcessRecord client) {
         final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities
-                || app.treatLikeActivity;
+                || app.treatLikeActivity || app.recentTasks.size() > 0;
         final boolean hasService = false; // not impl yet. app.services.size() > 0;
         if (!activityChange && hasActivity) {
             // The process has activities, so we are only allowing activity-based adjustments
@@ -3493,7 +3493,8 @@
         int nextIndex;
         if (hasActivity) {
             final int N = mLruProcesses.size();
-            if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) {
+            if ((app.activities.size() == 0 || app.recentTasks.size() > 0)
+                    && mLruProcessActivityStart < (N - 1)) {
                 // Process doesn't have activities, but has clients with
                 // activities...  move it up, but one below the top (the top
                 // should always have a real activity).
@@ -5330,6 +5331,8 @@
         // Remove this application's activities from active lists.
         boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);
 
+        app.clearRecentTasks();
+
         app.activities.clear();
 
         if (app.instr != null) {
@@ -21022,7 +21025,7 @@
                             + " instead of expected " + app);
                     if (r.app == null || (r.app.uid == app.uid)) {
                         // Only fix things up when they look sane
-                        r.app = app;
+                        r.setProcess(app);
                     } else {
                         continue;
                     }
@@ -21101,6 +21104,11 @@
                 adj += minLayer;
             }
         }
+        if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.recentTasks.size() > 0) {
+            procState = ActivityManager.PROCESS_STATE_CACHED_RECENT;
+            app.adjType = "cch-rec";
+            if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Raise to cached recent: " + app);
+        }
 
         if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
                 || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -22661,6 +22669,7 @@
                     switch (app.curProcState) {
                         case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
                         case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+                        case ActivityManager.PROCESS_STATE_CACHED_RECENT:
                             // This process is a cached process holding activities...
                             // assign it the next cached value for that type, and then
                             // step that cached level.
@@ -23385,7 +23394,7 @@
             // has been removed.
             for (i=mRemovedProcesses.size()-1; i>=0; i--) {
                 final ProcessRecord app = mRemovedProcesses.get(i);
-                if (app.activities.size() == 0
+                if (app.activities.size() == 0 && app.recentTasks.size() == 0
                         && app.curReceivers.isEmpty() && app.services.size() == 0) {
                     Slog.i(
                         TAG, "Exiting empty application process "
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 9a16745..2e76471 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -934,6 +934,14 @@
         }
     }
 
+    void setProcess(ProcessRecord proc) {
+        app = proc;
+        final ActivityRecord root = task != null ? task.getRootActivity() : null;
+        if (root == this) {
+            task.setRootProcess(proc);
+        }
+    }
+
     AppWindowContainerController getWindowContainerController() {
         return mWindowContainerController;
     }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 745e9fb..b015bcf 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1270,7 +1270,7 @@
             // schedule launch ticks to collect information about slow apps.
             r.startLaunchTickingLocked();
 
-            r.app = app;
+            r.setProcess(app);
 
             if (mKeyguardController.isKeyguardLocked()) {
                 r.notifyUnknownVisibilityLaunched();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 7810c5e..1a19601 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -400,6 +400,9 @@
             case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                 procState = "CACC";
                 break;
+            case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+                procState = "CRE ";
+                break;
             case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
                 procState = "CEM ";
                 break;
@@ -494,6 +497,7 @@
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_RECENT
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
@@ -515,6 +519,7 @@
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_RECENT
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
@@ -536,6 +541,7 @@
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_RECENT
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
@@ -557,6 +563,7 @@
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
@@ -578,6 +585,7 @@
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_CACHED_RECENT
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e847723..9d3c2ae 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -164,6 +164,8 @@
 
     // all activities running in the process
     final ArrayList<ActivityRecord> activities = new ArrayList<>();
+    // any tasks this process had run root activities in
+    final ArrayList<TaskRecord> recentTasks = new ArrayList<>();
     // all ServiceRecord running in this process
     final ArraySet<ServiceRecord> services = new ArraySet<>();
     // services that are currently executing code (need to remain foreground).
@@ -396,6 +398,12 @@
                 pw.print(prefix); pw.print("  - "); pw.println(activities.get(i));
             }
         }
+        if (recentTasks.size() > 0) {
+            pw.print(prefix); pw.println("Recent Tasks:");
+            for (int i=0; i<recentTasks.size(); i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(recentTasks.get(i));
+            }
+        }
         if (services.size() > 0) {
             pw.print(prefix); pw.println("Services:");
             for (int i=0; i<services.size(); i++) {
@@ -512,6 +520,13 @@
         }
     }
 
+    public void clearRecentTasks() {
+        for (int i = recentTasks.size() - 1; i >= 0; i--) {
+            recentTasks.get(i).clearRootProcess();
+        }
+        recentTasks.clear();
+    }
+
     /**
      * This method returns true if any of the activities within the process record are interesting
      * to the user. See HistoryRecord.isInterestingToUserLocked()
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 949f51f..a9c6eee 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -257,6 +257,11 @@
     /** Current stack. Setter must always be used to update the value. */
     private ActivityStack mStack;
 
+    /** The process that had previously hosted the root activity of this task.
+     * Used to know that we should try harder to keep this process around, in case the
+     * user wants to return to it. */
+    private ProcessRecord mRootProcess;
+
     /** Takes on same value as first root activity */
     boolean isPersistable = false;
     int maxRecents;
@@ -962,6 +967,8 @@
             mService.notifyTaskPersisterLocked(this, false);
         }
 
+        clearRootProcess();
+
         // TODO: Use window container controller once tasks are better synced between AM and WM
         mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId);
     }
@@ -2114,6 +2121,22 @@
         }
     }
 
+    void setRootProcess(ProcessRecord proc) {
+        clearRootProcess();
+        if (intent != null &&
+                (intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
+            mRootProcess = proc;
+            proc.recentTasks.add(this);
+        }
+    }
+
+    void clearRootProcess() {
+        if (mRootProcess != null) {
+            mRootProcess.recentTasks.remove(this);
+            mRootProcess = null;
+        }
+    }
+
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("userId="); pw.print(userId);
                 pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
@@ -2198,6 +2221,9 @@
         if (lastDescription != null) {
             pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription);
         }
+        if (mRootProcess != null) {
+            pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
+        }
         pw.print(prefix); pw.print("stackId="); pw.println(getStackId());
         pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
                 pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));