Implement maxRecents and fix TaskPersister bug

Activities can now set the maximum number of times that they will
appear in the recent task lists when using DocCentric mode. The
default number is 15, the min 1, and the max 100.

Also a bug in TaskPersister that deleted files because it did not
properly parse their task ids is fixed.

Fixes bug 13736052.

Change-Id: I7ccb4e6f89d6202ff31c8577bb7b9d8d1b7e5e8d
diff --git a/api/current.txt b/api/current.txt
index 79fb8df..4498507 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -310,7 +310,7 @@
     field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
     field public static final int autoLink = 16842928; // 0x10100b0
     field public static final int autoMirrored = 16843754; // 0x10103ea
-    field public static final int autoRemoveFromRecents = 16843849; // 0x1010449
+    field public static final int autoRemoveFromRecents = 16843850; // 0x101044a
     field public static final int autoStart = 16843445; // 0x10102b5
     field public static final deprecated int autoText = 16843114; // 0x101016a
     field public static final int autoUrlDetect = 16843404; // 0x101028c
@@ -413,10 +413,10 @@
     field public static final int content = 16843355; // 0x101025b
     field public static final int contentAuthority = 16843408; // 0x1010290
     field public static final int contentDescription = 16843379; // 0x1010273
-    field public static final int contentInsetEnd = 16843862; // 0x1010456
-    field public static final int contentInsetLeft = 16843863; // 0x1010457
-    field public static final int contentInsetRight = 16843864; // 0x1010458
-    field public static final int contentInsetStart = 16843861; // 0x1010455
+    field public static final int contentInsetEnd = 16843863; // 0x1010457
+    field public static final int contentInsetLeft = 16843864; // 0x1010458
+    field public static final int contentInsetRight = 16843865; // 0x1010459
+    field public static final int contentInsetStart = 16843862; // 0x1010456
     field public static final int controlX1 = 16843798; // 0x1010416
     field public static final int controlX2 = 16843800; // 0x1010418
     field public static final int controlY1 = 16843799; // 0x1010417
@@ -503,7 +503,7 @@
     field public static final int excludeClass = 16843845; // 0x1010445
     field public static final int excludeFromRecents = 16842775; // 0x1010017
     field public static final int excludeId = 16843844; // 0x1010444
-    field public static final int excludeViewName = 16843856; // 0x1010450
+    field public static final int excludeViewName = 16843857; // 0x1010451
     field public static final int exitFadeDuration = 16843533; // 0x101030d
     field public static final int expandableListPreferredChildIndicatorLeft = 16842834; // 0x1010052
     field public static final int expandableListPreferredChildIndicatorRight = 16842835; // 0x1010053
@@ -565,7 +565,7 @@
     field public static final int freezesText = 16843116; // 0x101016c
     field public static final int fromAlpha = 16843210; // 0x10101ca
     field public static final int fromDegrees = 16843187; // 0x10101b3
-    field public static final int fromId = 16843852; // 0x101044c
+    field public static final int fromId = 16843853; // 0x101044d
     field public static final int fromScene = 16843741; // 0x10103dd
     field public static final int fromXDelta = 16843206; // 0x10101c6
     field public static final int fromXScale = 16843202; // 0x10101c2
@@ -794,7 +794,7 @@
     field public static final int manageSpaceActivity = 16842756; // 0x1010004
     field public static final int mapViewStyle = 16842890; // 0x101008a
     field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
-    field public static final int matchOrder = 16843857; // 0x1010451
+    field public static final int matchOrder = 16843858; // 0x1010452
     field public static final int max = 16843062; // 0x1010136
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
@@ -803,6 +803,7 @@
     field public static final int maxLength = 16843104; // 0x1010160
     field public static final int maxLevel = 16843186; // 0x10101b2
     field public static final int maxLines = 16843091; // 0x1010153
+    field public static final int maxRecents = 16843849; // 0x1010449
     field public static final int maxRows = 16843059; // 0x1010133
     field public static final int maxSdkVersion = 16843377; // 0x1010271
     field public static final int maxWidth = 16843039; // 0x101011f
@@ -827,7 +828,7 @@
     field public static final int moreIcon = 16843061; // 0x1010135
     field public static final int multiprocess = 16842771; // 0x1010013
     field public static final int name = 16842755; // 0x1010003
-    field public static final int navigationBarColor = 16843860; // 0x1010454
+    field public static final int navigationBarColor = 16843861; // 0x1010455
     field public static final int navigationMode = 16843471; // 0x10102cf
     field public static final int negativeButtonText = 16843254; // 0x10101f6
     field public static final int nestedScrollingEnabled = 16843833; // 0x1010439
@@ -861,7 +862,7 @@
     field public static final int paddingBottom = 16842969; // 0x10100d9
     field public static final int paddingEnd = 16843700; // 0x10103b4
     field public static final int paddingLeft = 16842966; // 0x10100d6
-    field public static final int paddingMode = 16843865; // 0x1010459
+    field public static final int paddingMode = 16843866; // 0x101045a
     field public static final int paddingRight = 16842968; // 0x10100d8
     field public static final int paddingStart = 16843699; // 0x10103b3
     field public static final int paddingTop = 16842967; // 0x10100d7
@@ -957,7 +958,7 @@
     field public static final int restoreAnyVersion = 16843450; // 0x10102ba
     field public static final deprecated int restoreNeedsApplication = 16843421; // 0x101029d
     field public static final int restrictedAccountType = 16843733; // 0x10103d5
-    field public static final int reversible = 16843853; // 0x101044d
+    field public static final int reversible = 16843854; // 0x101044e
     field public static final int right = 16843183; // 0x10101af
     field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093
     field public static final int ringtoneType = 16843257; // 0x10101f9
@@ -1011,7 +1012,7 @@
     field public static final int selectAllOnFocus = 16843102; // 0x101015e
     field public static final int selectable = 16843238; // 0x10101e6
     field public static final int selectableItemBackground = 16843534; // 0x101030e
-    field public static final int selectableItemBackgroundBorderless = 16843866; // 0x101045a
+    field public static final int selectableItemBackgroundBorderless = 16843867; // 0x101045b
     field public static final int selectedDateVerticalBar = 16843591; // 0x1010347
     field public static final int selectedWeekBackgroundColor = 16843586; // 0x1010342
     field public static final int sessionService = 16843840; // 0x1010440
@@ -1048,7 +1049,7 @@
     field public static final int spinnerStyle = 16842881; // 0x1010081
     field public static final int spinnersShown = 16843595; // 0x101034b
     field public static final int splitMotionEvents = 16843503; // 0x10102ef
-    field public static final int splitTrack = 16843854; // 0x101044e
+    field public static final int splitTrack = 16843855; // 0x101044f
     field public static final int src = 16843033; // 0x1010119
     field public static final int ssp = 16843747; // 0x10103e3
     field public static final int sspPattern = 16843749; // 0x10103e5
@@ -1060,7 +1061,7 @@
     field public static final int startDelay = 16843746; // 0x10103e2
     field public static final int startOffset = 16843198; // 0x10101be
     field public static final deprecated int startYear = 16843132; // 0x101017c
-    field public static final int stateListAnimator = 16843850; // 0x101044a
+    field public static final int stateListAnimator = 16843851; // 0x101044b
     field public static final int stateNotNeeded = 16842774; // 0x1010016
     field public static final int state_above_anchor = 16842922; // 0x10100aa
     field public static final int state_accelerated = 16843547; // 0x101031b
@@ -1085,7 +1086,7 @@
     field public static final int state_single = 16842915; // 0x10100a3
     field public static final int state_window_focused = 16842909; // 0x101009d
     field public static final int staticWallpaperPreview = 16843569; // 0x1010331
-    field public static final int statusBarColor = 16843859; // 0x1010453
+    field public static final int statusBarColor = 16843860; // 0x1010454
     field public static final int stepSize = 16843078; // 0x1010146
     field public static final int stopWithTask = 16843626; // 0x101036a
     field public static final int streamType = 16843273; // 0x1010209
@@ -1130,7 +1131,7 @@
     field public static final int targetId = 16843740; // 0x10103dc
     field public static final int targetPackage = 16842785; // 0x1010021
     field public static final int targetSdkVersion = 16843376; // 0x1010270
-    field public static final int targetViewName = 16843855; // 0x101044f
+    field public static final int targetViewName = 16843856; // 0x1010450
     field public static final int taskAffinity = 16842770; // 0x1010012
     field public static final int taskCloseEnterAnimation = 16842942; // 0x10100be
     field public static final int taskCloseExitAnimation = 16842943; // 0x10100bf
@@ -1220,7 +1221,7 @@
     field public static final int titleTextStyle = 16843512; // 0x10102f8
     field public static final int toAlpha = 16843211; // 0x10101cb
     field public static final int toDegrees = 16843188; // 0x10101b4
-    field public static final int toId = 16843851; // 0x101044b
+    field public static final int toId = 16843852; // 0x101044c
     field public static final int toScene = 16843742; // 0x10103de
     field public static final int toXDelta = 16843207; // 0x10101c7
     field public static final int toXScale = 16843203; // 0x10101c3
@@ -1309,7 +1310,7 @@
     field public static final int windowContentTransitionManager = 16843795; // 0x1010413
     field public static final int windowContentTransitions = 16843794; // 0x1010412
     field public static final int windowDisablePreview = 16843298; // 0x1010222
-    field public static final int windowDrawsSystemBarBackgrounds = 16843858; // 0x1010452
+    field public static final int windowDrawsSystemBarBackgrounds = 16843859; // 0x1010453
     field public static final int windowEnableSplitTouch = 16843543; // 0x1010317
     field public static final int windowEnterAnimation = 16842932; // 0x10100b4
     field public static final int windowEnterTransition = 16843834; // 0x101043a
@@ -8029,6 +8030,7 @@
     field public int documentLaunchMode;
     field public int flags;
     field public int launchMode;
+    field public int maxRecents;
     field public java.lang.String parentActivityName;
     field public java.lang.String permission;
     field public int screenOrientation;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index c2fe3a2..cfe4712 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -99,6 +99,12 @@
     public int documentLaunchMode;
 
     /**
+     * The maximum number of tasks rooted at this activity that can be in the recent task list.
+     * Refer to {@link android.R.attr#maxRecents}.
+     */
+    public int maxRecents;
+
+    /**
      * Optional name of a permission required to be able to access this
      * Activity.  From the "permission" attribute.
      */
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1c838c3..8965faa 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2512,6 +2512,9 @@
             a.info.documentLaunchMode = sa.getInt(
                     com.android.internal.R.styleable.AndroidManifestActivity_documentLaunchMode,
                     ActivityInfo.DOCUMENT_LAUNCH_NONE);
+            a.info.maxRecents = sa.getInt(
+                    com.android.internal.R.styleable.AndroidManifestActivity_maxRecents,
+                    15);
             a.info.screenOrientation = sa.getInt(
                     com.android.internal.R.styleable.AndroidManifestActivity_screenOrientation,
                     ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index acfbe2d..3a0f767 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -933,6 +933,12 @@
         <enum name="always" value="2" />
     </attr>
 
+    <!-- The maximum number of entries of tasks rooted at this activity in the recent task list.
+         When this number of entries is reached the least recently used instance of this activity
+         will be removed from recents. The value will be clamped between 1 and 100 inclusive.
+         The default value for this if it is not specified is 15. -->
+    <attr name="maxRecents" format="integer" />
+
     <!-- Tasks launched by activities with this attribute will remain in the recent tasks
          list until the last activity in the task is completed. When that happens the task
          will be automatically removed from the recent tasks list.
@@ -1607,6 +1613,7 @@
         <attr name="persistable" />
         <attr name="allowEmbedded" />
         <attr name="documentLaunchMode" />
+        <attr name="maxRecents" />
         <attr name="autoRemoveFromRecents" />
     </declare-styleable>
     
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 88e1cda..ce97035 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2161,6 +2161,7 @@
   <public type="attr" name="hideOnContentScroll" />
   <public type="attr" name="actionOverflowMenuStyle" />
   <public type="attr" name="documentLaunchMode" />
+  <public type="attr" name="maxRecents" />
   <public type="attr" name="autoRemoveFromRecents" />
   <public type="attr" name="stateListAnimator" />
   <public type="attr" name="toId" />
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index fc808ec..70327a6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -269,7 +269,7 @@
     static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
 
     // Maximum number of recent tasks that we can remember.
-    static final int MAX_RECENT_TASKS = ActivityManager.isLowRamDeviceStatic() ? 10 : 20;
+    static final int MAX_RECENT_TASKS = ActivityManager.isLowRamDeviceStatic() ? 10 : 200;
 
     // Amount of time after a call to stopAppSwitches() during which we will
     // prevent further untrusted switches from happening.
@@ -3532,6 +3532,9 @@
         // Remove any existing entries that are the same kind of task.
         final Intent intent = task.intent;
         final boolean document = intent != null && intent.isDocument();
+        final ComponentName comp = intent.getComponent();
+
+        int maxRecents = task.maxRecents - 1;
         for (int i=0; i<N; i++) {
             TaskRecord tr = mRecentTasks.get(i);
             if (task != tr) {
@@ -3543,14 +3546,24 @@
                     (intent == null || !intent.filterEquals(trIntent))) {
                     continue;
                 }
-                if (document || trIntent != null && trIntent.isDocument()) {
-                    // Document tasks do not match other tasks.
+                final boolean trIsDocument = trIntent != null && trIntent.isDocument();
+                if (document && trIsDocument) {
+                    // These are the same document activity (not necessarily the same doc).
+                    if (maxRecents > 0) {
+                        --maxRecents;
+                        continue;
+                    }
+                    // Hit the maximum number of documents for this task. Fall through
+                    // and remove this document from recents.
+                } else if (document || trIsDocument) {
+                    // Only one of these is a document. Not the droid we're looking for.
                     continue;
                 }
             }
 
             // Either task and tr are the same or, their affinities match or their intents match
-            // and neither of them is a document.
+            // and neither of them is a document, or they are documents using the same activity
+            // and their maxRecents has been reached.
             tr.disposeThumbnail();
             mRecentTasks.remove(i);
             i--;
@@ -3560,6 +3573,7 @@
                 // specified, then replace it with the existing recent task.
                 task = tr;
             }
+            mTaskPersister.notify(tr, false);
         }
         if (N >= MAX_RECENT_TASKS) {
             mRecentTasks.remove(N-1).disposeThumbnail();
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 1804d03..d5a50e7 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -549,14 +549,16 @@
                     if (DEBUG_TASKS) Slog.d(TAG, "Found matching affinity!");
                     return r;
                 }
-            } else if (taskIntent != null && taskIntent.getComponent().equals(cls) &&
+            } else if (taskIntent != null && taskIntent.getComponent() != null &&
+                    taskIntent.getComponent().compareTo(cls) == 0 &&
                     Objects.equals(documentData, taskDocumentData)) {
                 if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!");
                 //dump();
                 if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: "
                         + r.intent);
                 return r;
-            } else if (affinityIntent != null && affinityIntent.getComponent().equals(cls) &&
+            } else if (affinityIntent != null && affinityIntent.getComponent() != null &&
+                    affinityIntent.getComponent().compareTo(cls) == 0 &&
                     Objects.equals(documentData, taskDocumentData)) {
                 if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!");
                 //dump();
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 3bfaca9..bb289fa 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -227,7 +227,7 @@
         for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
             File file = files[fileNdx];
             String filename = file.getName();
-            final int taskIdEnd = filename.indexOf('_') + 1;
+            final int taskIdEnd = filename.indexOf('_');
             if (taskIdEnd > 0) {
                 final int taskId;
                 try {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index ce83ae6..1df230e 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -96,6 +96,7 @@
 
     /** Takes on same value as first root activity */
     boolean isPersistable = false;
+    int maxRecents;
 
     /** Only used for persistable tasks, otherwise 0. The last time this task was moved. Used for
      * determining the order when restoring. Sign indicates whether last task movement was to front
@@ -104,6 +105,7 @@
 
     /** True if persistable, has changed, and has not yet been persisted */
     boolean needsPersisting = false;
+
     /** Launch the home activity when leaving this task. Will be false for tasks that are not on
      * Display.DEFAULT_DISPLAY. */
     boolean mOnTopOfHome = false;
@@ -301,6 +303,8 @@
         if (mActivities.isEmpty()) {
             taskType = r.mActivityType;
             isPersistable = r.isPersistable();
+            // Clamp to [1, 100].
+            maxRecents = Math.min(Math.max(r.info.maxRecents, 1), 100);
         } else {
             // Otherwise make all added activities match this one.
             r.mActivityType = taskType;