Remove activity icon bitmaps from system process.

When a TaskDescription is sent up to the system process, the Bitmap
contained in the mIcon member is immediately flushed to disk and the
name of the file replaces it in the TaskDescription. Thereby saving
mucho RAM for better uses.

Fixes bug 17527308.

Change-Id: Ifac63ea5d12ed091d1eb03e178b8b847a827f940
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 701ab1d..5f3ed61 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5066,7 +5066,7 @@
     public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
         ActivityManager.TaskDescription td;
         // Scale the icon down to something reasonable if it is provided
-        if (taskDescription.getIcon() != null) {
+        if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) {
             final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
             final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size, true);
             td = new ActivityManager.TaskDescription(taskDescription.getLabel(), icon,
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 9486a72..85d4839 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -56,9 +56,11 @@
 import android.util.DisplayMetrics;
 import android.util.Size;
 import android.util.Slog;
+import org.xmlpull.v1.XmlSerializer;
 
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -508,8 +510,18 @@
      * Information you can set and retrieve about the current activity within the recent task list.
      */
     public static class TaskDescription implements Parcelable {
+        /** @hide */
+        public static final String ATTR_TASKDESCRIPTION_PREFIX = "task_description_";
+        private static final String ATTR_TASKDESCRIPTIONLABEL =
+                ATTR_TASKDESCRIPTION_PREFIX + "label";
+        private static final String ATTR_TASKDESCRIPTIONCOLOR =
+                ATTR_TASKDESCRIPTION_PREFIX + "color";
+        private static final String ATTR_TASKDESCRIPTIONICONFILENAME =
+                ATTR_TASKDESCRIPTION_PREFIX + "icon_filename";
+
         private String mLabel;
         private Bitmap mIcon;
+        private String mIconFilename;
         private int mColorPrimary;
 
         /**
@@ -529,6 +541,12 @@
             mColorPrimary = colorPrimary;
         }
 
+        /** @hide */
+        public TaskDescription(String label, int colorPrimary, String iconFilename) {
+            this(label, null, colorPrimary);
+            mIconFilename = iconFilename;
+        }
+
         /**
          * Creates the TaskDescription to the specified values.
          *
@@ -559,7 +577,10 @@
          * Creates a copy of another TaskDescription.
          */
         public TaskDescription(TaskDescription td) {
-            this(td.getLabel(), td.getIcon(), td.getPrimaryColor());
+            mLabel = td.mLabel;
+            mIcon = td.mIcon;
+            setPrimaryColor(td.mColorPrimary);
+            mIconFilename = td.mIconFilename;
         }
 
         private TaskDescription(Parcel source) {
@@ -579,7 +600,7 @@
          * @hide
          */
         public void setPrimaryColor(int primaryColor) {
-            mColorPrimary = primaryColor;
+            mColorPrimary = 0xFF000000 | primaryColor;
         }
 
         /**
@@ -591,6 +612,16 @@
         }
 
         /**
+         * Moves the icon bitmap reference from an actual Bitmap to a file containing the
+         * bitmap.
+         * @hide
+         */
+        public void setIconFilename(String iconFilename) {
+            mIconFilename = iconFilename;
+            mIcon = null;
+        }
+
+        /**
          * @return The label and description of the current state of this task.
          */
         public String getLabel() {
@@ -601,7 +632,22 @@
          * @return The icon that represents the current state of this task.
          */
         public Bitmap getIcon() {
-            return mIcon;
+            if (mIcon != null) {
+                return mIcon;
+            }
+            if (mIconFilename != null) {
+                try {
+                    return ActivityManagerNative.getDefault().
+                            getTaskDescriptionIcon(mIconFilename);
+                } catch (RemoteException e) {
+                }
+            }
+            return null;
+        }
+
+        /** @hide */
+        public String getIconFilename() {
+            return mIconFilename;
         }
 
         /**
@@ -611,6 +657,30 @@
             return mColorPrimary;
         }
 
+        /** @hide */
+        public void saveToXml(XmlSerializer out) throws IOException {
+            if (mLabel != null) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, mLabel);
+            }
+            if (mColorPrimary != 0) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(mColorPrimary));
+            }
+            if (mIconFilename != null) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONICONFILENAME, mIconFilename);
+            }
+        }
+
+        /** @hide */
+        public void restoreFromXml(String attrName, String attrValue) {
+            if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) {
+                setLabel(attrValue);
+            } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) {
+                setPrimaryColor((int) Long.parseLong(attrValue, 16));
+            } else if (ATTR_TASKDESCRIPTIONICONFILENAME.equals(attrName)) {
+                setIconFilename(attrValue);
+            }
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -631,12 +701,19 @@
                 mIcon.writeToParcel(dest, 0);
             }
             dest.writeInt(mColorPrimary);
+            if (mIconFilename == null) {
+                dest.writeInt(0);
+            } else {
+                dest.writeInt(1);
+                dest.writeString(mIconFilename);
+            }
         }
 
         public void readFromParcel(Parcel source) {
             mLabel = source.readInt() > 0 ? source.readString() : null;
             mIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
             mColorPrimary = source.readInt();
+            mIconFilename = source.readInt() > 0 ? source.readString() : null;
         }
 
         public static final Creator<TaskDescription> CREATOR
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 677fcef..11470e3 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2253,6 +2253,20 @@
             return true;
         }
 
+        case GET_TASK_DESCRIPTION_ICON_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            String filename = data.readString();
+            Bitmap icon = getTaskDescriptionIcon(filename);
+            reply.writeNoException();
+            if (icon == null) {
+                reply.writeInt(0);
+            } else {
+                reply.writeInt(1);
+                icon.writeToParcel(reply, 0);
+            }
+            return true;
+        }
+
         case REQUEST_VISIBLE_BEHIND_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             IBinder token = data.readStrongBinder();
@@ -5241,6 +5255,20 @@
     }
 
     @Override
+    public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(filename);
+        mRemote.transact(GET_TASK_DESCRIPTION_ICON_TRANSACTION, data, reply, 0);
+        reply.readException();
+        final Bitmap icon = reply.readInt() == 0 ? null : Bitmap.CREATOR.createFromParcel(reply);
+        data.recycle();
+        reply.recycle();
+        return icon;
+    }
+
+    @Override
     public boolean requestVisibleBehind(IBinder token, boolean visible) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 8fa1fd53..aa5fea0 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -451,6 +451,7 @@
 
     public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
             throws RemoteException;
+    public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
 
     public boolean requestVisibleBehind(IBinder token, boolean visible) throws RemoteException;
     public boolean isBackgroundVisibleBehind(IBinder token) throws RemoteException;
@@ -775,4 +776,5 @@
     int RELEASE_ACTIVITY_INSTANCE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+235;
     int RELEASE_SOME_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+236;
     int BOOT_ANIMATION_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+237;
+    int GET_TASK_DESCRIPTION_ICON_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+238;
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6d8e105..741cffe 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8375,12 +8375,17 @@
         synchronized (this) {
             ActivityRecord r = ActivityRecord.isInStackLocked(token);
             if (r != null) {
-                r.taskDescription = td;
+                r.setTaskDescription(td);
                 r.task.updateTaskDescription();
             }
         }
     }
 
+    @Override
+    public Bitmap getTaskDescriptionIcon(String filename) {
+        return mTaskPersister.getTaskDescriptionIcon(filename);
+    }
+
     private void cleanUpRemovedTaskLocked(TaskRecord tr, int flags) {
         mRecentTasks.remove(tr);
         tr.removedFromRecents(mTaskPersister);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index e043f03..5d9dfa0 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -205,14 +205,18 @@
                     pw.print(" resultWho="); pw.print(resultWho);
                     pw.print(" resultCode="); pw.println(requestCode);
         }
-        if (taskDescription.getIcon() != null || taskDescription.getLabel() != null ||
+        final String iconFilename = taskDescription.getIconFilename();
+        if (iconFilename != null || taskDescription.getLabel() != null ||
                 taskDescription.getPrimaryColor() != 0) {
             pw.print(prefix); pw.print("taskDescription:");
-                    pw.print(" icon="); pw.print(taskDescription.getIcon());
+                    pw.print(" iconFilename="); pw.print(taskDescription.getIconFilename());
                     pw.print(" label=\""); pw.print(taskDescription.getLabel()); pw.print("\"");
                     pw.print(" color=");
                     pw.println(Integer.toHexString(taskDescription.getPrimaryColor()));
         }
+        if (iconFilename == null && taskDescription.getIcon() != null) {
+            pw.print(prefix); pw.println("taskDescription contains Bitmap");
+        }
         if (results != null) {
             pw.print(prefix); pw.print("results="); pw.println(results);
         }
@@ -1092,6 +1096,17 @@
                 TaskPersister.IMAGE_EXTENSION;
     }
 
+    void setTaskDescription(TaskDescription _taskDescription) {
+        Bitmap icon;
+        if (_taskDescription.getIconFilename() == null &&
+                (icon = _taskDescription.getIcon()) != null) {
+            final String iconFilename = createImageFilename(createTime, task.taskId);
+            mStackSupervisor.mService.mTaskPersister.saveImage(icon, iconFilename);
+            _taskDescription.setIconFilename(iconFilename);
+        }
+        taskDescription = _taskDescription;
+    }
+
     void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
         out.attribute(null, ATTR_ID, String.valueOf(createTime));
         out.attribute(null, ATTR_LAUNCHEDFROMUID, String.valueOf(launchedFromUid));
@@ -1105,8 +1120,7 @@
         out.attribute(null, ATTR_USERID, String.valueOf(userId));
 
         if (taskDescription != null) {
-            task.saveTaskDescription(taskDescription, createImageFilename(createTime, task.taskId),
-                    out);
+            taskDescription.saveToXml(out);
         }
 
         out.startTag(null, TAG_INTENT);
@@ -1150,9 +1164,8 @@
                 componentSpecified = Boolean.valueOf(attrValue);
             } else if (ATTR_USERID.equals(attrName)) {
                 userId = Integer.valueOf(attrValue);
-            } else if (TaskRecord.readTaskDescriptionAttribute(taskDescription, attrName,
-                    attrValue)) {
-                // Completed in TaskRecord.readTaskDescriptionAttribute()
+            } else if (attrName.startsWith(TaskDescription.ATTR_TASKDESCRIPTION_PREFIX)) {
+                taskDescription.restoreFromXml(attrName, attrValue);
             } else {
                 Log.d(TAG, "Unknown ActivityRecord attribute=" + attrName);
             }
@@ -1196,11 +1209,6 @@
                 null, null, 0, componentSpecified, stackSupervisor, null, null);
 
         r.persistentState = persistentState;
-
-        if (createTime >= 0) {
-            taskDescription.setIcon(TaskPersister.restoreImage(createImageFilename(createTime,
-                    taskId)));
-        }
         r.taskDescription = taskDescription;
         r.createTime = createTime;
 
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index b21af48..4e6c5d6 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -176,7 +176,16 @@
         }
     }
 
-    Bitmap getThumbnail(String filename) {
+    Bitmap getTaskDescriptionIcon(String filename) {
+        // See if it is in the write queue
+        final Bitmap icon = getImageFromWriteQueue(filename);
+        if (icon != null) {
+            return icon;
+        }
+        return restoreImage(filename);
+    }
+
+    Bitmap getImageFromWriteQueue(String filename) {
         synchronized (this) {
             for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
                 final WriteQueueItem item = mWriteQueue.get(queueNdx);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 4de7367..3f0d450 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -25,6 +25,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskThumbnail;
+import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.content.ComponentName;
@@ -70,15 +71,12 @@
     private static final String ATTR_LASTDESCRIPTION = "last_description";
     private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
     private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
-    private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label";
-    private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color";
     private static final String ATTR_TASK_AFFILIATION = "task_affiliation";
     private static final String ATTR_PREV_AFFILIATION = "prev_affiliation";
     private static final String ATTR_NEXT_AFFILIATION = "next_affiliation";
     private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
     private static final String ATTR_CALLING_UID = "calling_uid";
     private static final String ATTR_CALLING_PACKAGE = "calling_package";
-    private static final String LAST_ACTIVITY_ICON_SUFFIX = "_last_activity_icon_";
 
     private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
 
@@ -113,8 +111,7 @@
 
     // This represents the last resolved activity values for this task
     // NOTE: This value needs to be persisted with each task
-    ActivityManager.TaskDescription lastTaskDescription =
-            new ActivityManager.TaskDescription();
+    TaskDescription lastTaskDescription = new TaskDescription();
 
     /** List of all activities in the task arranged in history order */
     final ArrayList<ActivityRecord> mActivities;
@@ -180,7 +177,7 @@
     }
 
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
-            ActivityManager.TaskDescription _taskDescription) {
+            TaskDescription _taskDescription) {
         mService = service;
         mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
                 TaskPersister.IMAGE_EXTENSION;
@@ -215,7 +212,7 @@
             boolean _askedCompatMode, int _taskType, int _userId, int _effectiveUid,
             String _lastDescription, ArrayList<ActivityRecord> activities, long _firstActiveTime,
             long _lastActiveTime, long lastTimeMoved, boolean neverRelinquishIdentity,
-            ActivityManager.TaskDescription _lastTaskDescription, int taskAffiliation,
+            TaskDescription _lastTaskDescription, int taskAffiliation,
             int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid,
             String callingPackage) {
         mService = service;
@@ -441,7 +438,7 @@
         thumbs.mainThumbnail = mLastThumbnail;
         thumbs.thumbnailFileDescriptor = null;
         if (mLastThumbnail == null) {
-            thumbs.mainThumbnail = mService.mTaskPersister.getThumbnail(mFilename);
+            thumbs.mainThumbnail = mService.mTaskPersister.getImageFromWriteQueue(mFilename);
         }
         // Only load the thumbnail file if we don't have a thumbnail
         if (thumbs.mainThumbnail == null && mLastThumbnailFile.exists()) {
@@ -759,7 +756,7 @@
             // recent activity values, then we do not fall back to the last set
             // values in the TaskRecord.
             String label = null;
-            Bitmap icon = null;
+            String iconFilename = null;
             int colorPrimary = 0;
             for (--activityNdx; activityNdx >= 0; --activityNdx) {
                 final ActivityRecord r = mActivities.get(activityNdx);
@@ -767,15 +764,15 @@
                     if (label == null) {
                         label = r.taskDescription.getLabel();
                     }
-                    if (icon == null) {
-                        icon = r.taskDescription.getIcon();
+                    if (iconFilename == null) {
+                        iconFilename = r.taskDescription.getIconFilename();
                     }
                     if (colorPrimary == 0) {
                         colorPrimary = r.taskDescription.getPrimaryColor();
                     }
                 }
             }
-            lastTaskDescription = new ActivityManager.TaskDescription(label, icon, colorPrimary);
+            lastTaskDescription = new TaskDescription(label, colorPrimary, iconFilename);
             // Update the task affiliation color if we are the parent of the group
             if (taskId == mAffiliatedTaskId) {
                 mAffiliatedTaskColor = lastTaskDescription.getPrimaryColor();
@@ -804,41 +801,6 @@
         setIntent(r);
     }
 
-    void saveTaskDescription(ActivityManager.TaskDescription taskDescription,
-            String iconFilename, XmlSerializer out) throws IOException {
-        if (taskDescription != null) {
-            final String label = taskDescription.getLabel();
-            if (label != null) {
-                out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, label);
-            }
-            final int colorPrimary = taskDescription.getPrimaryColor();
-            if (colorPrimary != 0) {
-                out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(colorPrimary));
-            }
-            final Bitmap icon = taskDescription.getIcon();
-            if (icon != null) {
-                mService.mTaskPersister.saveImage(icon, iconFilename);
-            }
-        }
-    }
-
-    static boolean readTaskDescriptionAttribute(ActivityManager.TaskDescription taskDescription,
-            String attrName, String attrValue) {
-        if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) {
-            taskDescription.setLabel(attrValue);
-        } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) {
-            taskDescription.setPrimaryColor((int) Long.parseLong(attrValue, 16));
-        } else {
-            return false;
-        }
-        return true;
-    }
-
-    private static String createLastTaskDescriptionIconFilename(int taskId, long lastActiveTime) {
-        return String.valueOf(taskId) + LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime +
-                TaskPersister.IMAGE_EXTENSION;
-    }
-
     void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
         if (ActivityManagerService.DEBUG_RECENTS) Slog.i(TAG, "Saving task=" + this);
 
@@ -875,8 +837,7 @@
             out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
         }
         if (lastTaskDescription != null) {
-            saveTaskDescription(lastTaskDescription, createLastTaskDescriptionIconFilename(taskId,
-                    lastActiveTime), out);
+            lastTaskDescription.saveToXml(out);
         }
         out.attribute(null, ATTR_TASK_AFFILIATION_COLOR, String.valueOf(mAffiliatedTaskColor));
         out.attribute(null, ATTR_TASK_AFFILIATION, String.valueOf(mAffiliatedTaskId));
@@ -934,7 +895,7 @@
         boolean neverRelinquishIdentity = true;
         int taskId = -1;
         final int outerDepth = in.getDepth();
-        ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription();
+        TaskDescription taskDescription = new TaskDescription();
         int taskAffiliation = -1;
         int taskAffiliationColor = 0;
         int prevTaskId = -1;
@@ -980,8 +941,8 @@
                 lastTimeOnTop = Long.valueOf(attrValue);
             } else if (ATTR_NEVERRELINQUISH.equals(attrName)) {
                 neverRelinquishIdentity = Boolean.valueOf(attrValue);
-            } else if (readTaskDescriptionAttribute(taskDescription, attrName, attrValue)) {
-                // Completed in TaskPersister.readTaskDescriptionAttribute()
+            } else if (attrName.startsWith(TaskDescription.ATTR_TASKDESCRIPTION_PREFIX)) {
+                taskDescription.restoreFromXml(attrName, attrValue);
             } else if (ATTR_TASK_AFFILIATION.equals(attrName)) {
                 taskAffiliation = Integer.valueOf(attrValue);
             } else if (ATTR_PREV_AFFILIATION.equals(attrName)) {
@@ -1025,11 +986,6 @@
             }
         }
 
-        if (lastActiveTime >= 0) {
-            taskDescription.setIcon(TaskPersister.restoreImage(
-                    createLastTaskDescriptionIconFilename(taskId, lastActiveTime)));
-        }
-
         if (!hasRootAffinity) {
             rootAffinity = affinity;
         } else if ("@".equals(rootAffinity)) {