From 648f69b95ce7fc95f551f6e08a2408d6e57dbab9 Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Thu, 18 Sep 2014 14:16:26 -0700 Subject: [PATCH] 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 --- core/java/android/app/Activity.java | 2 +- core/java/android/app/ActivityManager.java | 83 ++++++++++++++++++- .../android/app/ActivityManagerNative.java | 28 +++++++ core/java/android/app/IActivityManager.java | 2 + .../server/am/ActivityManagerService.java | 7 +- .../com/android/server/am/ActivityRecord.java | 32 ++++--- .../com/android/server/am/TaskPersister.java | 11 ++- .../com/android/server/am/TaskRecord.java | 70 +++------------- 8 files changed, 160 insertions(+), 75 deletions(-) diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 701ab1d809dcd..5f3ed61fee204 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5066,7 +5066,7 @@ public class Activity extends ContextThemeWrapper 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 9486a72f91d48..85d4839d0c6e9 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -56,9 +56,11 @@ import android.text.TextUtils; 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 @@ public class ActivityManager { * 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 @@ public class ActivityManager { 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 @@ public class ActivityManager { * 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 @@ public class ActivityManager { * @hide */ public void setPrimaryColor(int primaryColor) { - mColorPrimary = primaryColor; + mColorPrimary = 0xFF000000 | primaryColor; } /** @@ -590,6 +611,16 @@ public class ActivityManager { mIcon = icon; } + /** + * 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. */ @@ -601,7 +632,22 @@ public class ActivityManager { * @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 @@ public class ActivityManager { 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 @@ public class ActivityManager { 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 CREATOR diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 677fcef8d01a9..11470e336b65d 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -2253,6 +2253,20 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM 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(); @@ -5240,6 +5254,20 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + @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(); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 8fa1fd5399736..aa5fea0a9e1ea 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -451,6 +451,7 @@ public interface IActivityManager extends IInterface { 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 @@ public interface IActivityManager extends IInterface { 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 6d8e10574fe57..741cffe8685ad 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 e043f03fc51d6..5d9dfa0d2abf5 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 @@ final class ActivityRecord { 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 @@ final class ActivityRecord { 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 @@ final class ActivityRecord { 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 @@ final class ActivityRecord { 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 @@ final class ActivityRecord { 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 b21af48c4d3b3..4e6c5d6e6c5bd 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 @@ public class TaskPersister { } } - 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 4de73675db02e..3f0d450bf814d 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 static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE; 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 @@ final class TaskRecord { 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 @@ final class TaskRecord { // 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 mActivities; @@ -180,7 +177,7 @@ final class TaskRecord { } 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 @@ final class TaskRecord { boolean _askedCompatMode, int _taskType, int _userId, int _effectiveUid, String _lastDescription, ArrayList 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 @@ final class TaskRecord { 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 @@ final class TaskRecord { // 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 @@ final class TaskRecord { 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 @@ final class TaskRecord { 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 @@ final class TaskRecord { 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 @@ final class TaskRecord { 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 @@ final class TaskRecord { 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 @@ final class TaskRecord { } } - if (lastActiveTime >= 0) { - taskDescription.setIcon(TaskPersister.restoreImage( - createLastTaskDescriptionIconFilename(taskId, lastActiveTime))); - } - if (!hasRootAffinity) { rootAffinity = affinity; } else if ("@".equals(rootAffinity)) {