summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityManager.java38
-rw-r--r--core/java/android/app/IActivityManager.aidl5
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java215
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/Task.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java119
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java4
-rw-r--r--services/core/java/com/android/server/am/TaskRecord.java4
-rw-r--r--services/core/java/com/android/server/wm/AppWindowContainerController.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotCache.java17
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java7
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotLoader.java11
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotPersister.java29
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java37
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java3
26 files changed, 573 insertions, 82 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 8a0af9a54056..9f2f669b64c8 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2177,31 +2177,62 @@ public class ActivityManager {
private final GraphicBuffer mSnapshot;
private final int mOrientation;
private final Rect mContentInsets;
+ private final boolean mReducedResolution;
+ private final float mScale;
- public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets) {
+ public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets,
+ boolean reducedResolution, float scale) {
mSnapshot = snapshot;
mOrientation = orientation;
mContentInsets = new Rect(contentInsets);
+ mReducedResolution = reducedResolution;
+ mScale = scale;
}
private TaskSnapshot(Parcel source) {
mSnapshot = source.readParcelable(null /* classLoader */);
mOrientation = source.readInt();
mContentInsets = source.readParcelable(null /* classLoader */);
+ mReducedResolution = source.readBoolean();
+ mScale = source.readFloat();
}
+ /**
+ * @return The graphic buffer representing the screenshot.
+ */
public GraphicBuffer getSnapshot() {
return mSnapshot;
}
+ /**
+ * @return The screen orientation the screenshot was taken in.
+ */
public int getOrientation() {
return mOrientation;
}
+ /**
+ * @return The system/content insets on the snapshot. These can be clipped off in order to
+ * remove any areas behind system bars in the snapshot.
+ */
public Rect getContentInsets() {
return mContentInsets;
}
+ /**
+ * @return Whether this snapshot is a down-sampled version of the full resolution.
+ */
+ public boolean isReducedResolution() {
+ return mReducedResolution;
+ }
+
+ /**
+ * @return The scale this snapshot was taken in.
+ */
+ public float getScale() {
+ return mScale;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -2212,12 +2243,15 @@ public class ActivityManager {
dest.writeParcelable(mSnapshot, 0);
dest.writeInt(mOrientation);
dest.writeParcelable(mContentInsets, 0);
+ dest.writeBoolean(mReducedResolution);
+ dest.writeFloat(mScale);
}
@Override
public String toString() {
return "TaskSnapshot{mSnapshot=" + mSnapshot + " mOrientation=" + mOrientation
- + " mContentInsets=" + mContentInsets.toShortString();
+ + " mContentInsets=" + mContentInsets.toShortString()
+ + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale;
}
public static final Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index c8546676baef..d9408574b2a5 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -600,9 +600,12 @@ interface IActivityManager {
void cancelTaskThumbnailTransition(int taskId);
/**
+ * @param taskId the id of the task to retrieve the snapshots for
+ * @param reducedResolution if set, if the snapshot needs to be loaded from disk, this will load
+ * a reduced resolution of it, which is much faster
* @return a graphic buffer representing a screenshot of a task
*/
- ActivityManager.TaskSnapshot getTaskSnapshot(int taskId);
+ ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution);
void scheduleApplicationInfoChanged(in List<String> packageNames, int userId);
void setPersistentVrThread(int tid);
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 72bdbf124c18..5ffc8f941aa4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -726,6 +726,10 @@
<!-- The alpha to apply to the recents row when it doesn't have focus -->
<item name="recents_recents_row_dim_alpha" format="float" type="dimen">0.5</item>
+ <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
+ loading full resolution screenshots. -->
+ <dimen name="recents_fast_fling_velocity">600dp</dimen>
+
<!-- The size of the PIP drag-to-dismiss target. -->
<dimen name="pip_dismiss_target_size">48dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index d3e939f8b0b4..6da827240f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -33,6 +33,7 @@ import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -59,6 +60,7 @@ import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.events.component.ShowUserToastEvent;
import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.HighResThumbnailLoader;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
@@ -184,6 +186,7 @@ public class Recents extends SystemUI
return sTaskLoader;
}
+
public static SystemServicesProxy getSystemServices() {
return sSystemServicesProxy;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index c9debb2068b7..f0a9bc3f80be 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -375,6 +375,8 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
// Notify of the next draw
mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
+
+ Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(true);
}
@Override
@@ -529,6 +531,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
mReceivedNewIntent = false;
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
+ Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(false);
if (!isChangingConfigurations()) {
// Workaround for b/22542869, if the RecentsActivity is started again, but without going
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index d7c1391af897..25eea951c585 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -637,7 +637,7 @@ public class SystemServicesProxy {
}
/** Returns the top task thumbnail for the given task id */
- public ThumbnailData getTaskThumbnail(int taskId) {
+ public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) {
if (mAm == null) return null;
// If we are mocking, then just return a dummy thumbnail
@@ -649,7 +649,7 @@ public class SystemServicesProxy {
return thumbnailData;
}
- ThumbnailData thumbnailData = getThumbnail(taskId);
+ ThumbnailData thumbnailData = getThumbnail(taskId, reduced);
if (thumbnailData.thumbnail != null && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
thumbnailData.thumbnail.setHasAlpha(false);
// We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
@@ -669,7 +669,7 @@ public class SystemServicesProxy {
/**
* Returns a task thumbnail from the activity manager
*/
- public @NonNull ThumbnailData getThumbnail(int taskId) {
+ public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) {
if (mAm == null) {
return new ThumbnailData();
}
@@ -678,7 +678,8 @@ public class SystemServicesProxy {
if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
ActivityManager.TaskSnapshot snapshot = null;
try {
- snapshot = ActivityManager.getService().getTaskSnapshot(taskId);
+ snapshot = ActivityManager.getService().getTaskSnapshot(taskId,
+ false /* reducedResolution */);
} catch (RemoteException e) {
Log.w(TAG, "Failed to retrieve snapshot", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
new file mode 100644
index 000000000000..be8da9f38478
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.recents.model;
+
+import static android.os.Process.setThreadPriority;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task.TaskCallbacks;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+/**
+ * Loader class that loads full-resolution thumbnails when appropriate.
+ */
+public class HighResThumbnailLoader implements TaskCallbacks {
+
+ @GuardedBy("mLoadQueue")
+ private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>();
+ @GuardedBy("mLoadQueue")
+ private final ArraySet<Task> mLoadingTasks = new ArraySet<>();
+ @GuardedBy("mLoadQueue")
+ private boolean mLoaderIdling;
+
+ private final ArrayList<Task> mVisibleTasks = new ArrayList<>();
+ private final Thread mLoadThread;
+ private final Handler mMainThreadHandler;
+ private final SystemServicesProxy mSystemServicesProxy;
+ private boolean mLoading;
+ private boolean mVisible;
+ private boolean mFlingingFast;
+
+ public HighResThumbnailLoader(SystemServicesProxy ssp, Looper looper) {
+ mMainThreadHandler = new Handler(looper);
+ mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader");
+ mLoadThread.start();
+ mSystemServicesProxy = ssp;
+ }
+
+ public void setVisible(boolean visible) {
+ mVisible = visible;
+ updateLoading();
+ }
+
+ public void setFlingingFast(boolean flingingFast) {
+ if (mFlingingFast == flingingFast) {
+ return;
+ }
+ mFlingingFast = flingingFast;
+ updateLoading();
+ }
+
+ @VisibleForTesting
+ boolean isLoading() {
+ return mLoading;
+ }
+
+ private void updateLoading() {
+ setLoading(mVisible && !mFlingingFast);
+ }
+
+ private void setLoading(boolean loading) {
+ if (loading == mLoading) {
+ return;
+ }
+ synchronized (mLoadQueue) {
+ mLoading = loading;
+ if (!loading) {
+ stopLoading();
+ } else {
+ startLoading();
+ }
+ }
+ }
+
+ @GuardedBy("mLoadQueue")
+ private void startLoading() {
+ for (int i = mVisibleTasks.size() - 1; i >= 0; i--) {
+ Task t = mVisibleTasks.get(i);
+ if ((t.thumbnail == null || t.thumbnail.reducedResolution)
+ && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) {
+ mLoadQueue.add(t);
+ }
+ }
+ mLoadQueue.notifyAll();
+ }
+
+ @GuardedBy("mLoadQueue")
+ private void stopLoading() {
+ mLoadQueue.clear();
+ mLoadQueue.notifyAll();
+ }
+
+ /**
+ * Needs to be called when a task becomes visible. Note that this is different from
+ * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it
+ * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data
+ * has been updated.
+ */
+ public void onTaskVisible(Task t) {
+ t.addCallback(this);
+ mVisibleTasks.add(t);
+ if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) {
+ synchronized (mLoadQueue) {
+ mLoadQueue.add(t);
+ mLoadQueue.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is
+ * different from {@link TaskCallbacks#onTaskDataUnloaded()}
+ */
+ public void onTaskInvisible(Task t) {
+ t.removeCallback(this);
+ mVisibleTasks.remove(t);
+ synchronized (mLoadQueue) {
+ mLoadQueue.remove(t);
+ }
+ }
+
+ @VisibleForTesting
+ void waitForLoaderIdle() {
+ while (true) {
+ synchronized (mLoadQueue) {
+ if (mLoadQueue.isEmpty() && mLoaderIdling) {
+ return;
+ }
+ }
+ SystemClock.sleep(100);
+ }
+ }
+
+ @Override
+ public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
+ if (thumbnailData != null && !thumbnailData.reducedResolution) {
+ synchronized (mLoadQueue) {
+ mLoadQueue.remove(task);
+ }
+ }
+ }
+
+ @Override
+ public void onTaskDataUnloaded() {
+ }
+
+ @Override
+ public void onTaskStackIdChanged() {
+ }
+
+ private final Runnable mLoader = new Runnable() {
+
+ @Override
+ public void run() {
+ setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1);
+ while (true) {
+ Task next = null;
+ synchronized (mLoadQueue) {
+ if (!mLoading || mLoadQueue.isEmpty()) {
+ try {
+ mLoaderIdling = true;
+ mLoadQueue.wait();
+ mLoaderIdling = false;
+ } catch (InterruptedException e) {
+ // Don't care.
+ }
+ } else {
+ next = mLoadQueue.poll();
+ if (next != null) {
+ mLoadingTasks.add(next);
+ }
+ }
+ }
+ if (next != null) {
+ loadTask(next);
+ }
+ }
+ }
+
+ private void loadTask(Task t) {
+ ThumbnailData thumbnail = mSystemServicesProxy.getTaskThumbnail(t.key.id,
+ false /* reducedResolution */);
+ mMainThreadHandler.post(() -> {
+ synchronized (mLoadQueue) {
+ mLoadingTasks.remove(t);
+ }
+ if (mVisibleTasks.contains(t)) {
+ t.notifyTaskDataLoaded(thumbnail, t.icon);
+ }
+ });
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 12c10dff0ab3..f8d123b8fdde 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -188,7 +188,8 @@ public class RecentsTaskLoadPlan {
Drawable icon = isStackTask
? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
: null;
- Bitmap thumbnail = loader.getAndUpdateThumbnail(taskKey, false /* loadIfNotCached */);
+ ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
+ false /* loadIfNotCached */);
int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
boolean isSystemApp = (info != null) &&
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 40a4a2b6a123..e378d0ae51de 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -27,6 +27,7 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
import android.util.Log;
import android.util.LruCache;
@@ -37,6 +38,7 @@ import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.model.Task.TaskKey;
import java.io.PrintWriter;
import java.util.Map;
@@ -156,7 +158,6 @@ class BackgroundTaskLoader implements Runnable {
}
}
} else {
- RecentsConfiguration config = Recents.getConfiguration();
SystemServicesProxy ssp = Recents.getSystemServices();
// If we've stopped the loader, then fall through to the above logic to wait on
// the load thread
@@ -190,7 +191,8 @@ class BackgroundTaskLoader implements Runnable {
}
if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
- ThumbnailData cachedThumbnailData = ssp.getTaskThumbnail(t.key.id);
+ ThumbnailData cachedThumbnailData = ssp.getTaskThumbnail(t.key.id,
+ true /* reducedResolution */);
if (cachedThumbnailData.thumbnail == null) {
cachedThumbnailData.thumbnail = mDefaultThumbnail;
@@ -242,6 +244,7 @@ public class RecentsTaskLoader {
private final TaskKeyLruCache<String> mContentDescriptionCache;
private final TaskResourceLoadQueue mLoadQueue;
private final BackgroundTaskLoader mLoader;
+ private final HighResThumbnailLoader mHighResThumbnailLoader;
private final int mMaxThumbnailCacheSize;
private final int mMaxIconCacheSize;
@@ -293,6 +296,8 @@ public class RecentsTaskLoader {
mClearActivityInfoOnEviction);
mActivityInfoCache = new LruCache(numRecentTasks);
mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultThumbnail, mDefaultIcon);
+ mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(),
+ Looper.getMainLooper());
}
/** Returns the size of the app icon cache. */
@@ -305,6 +310,10 @@ public class RecentsTaskLoader {
return mMaxThumbnailCacheSize;
}
+ public HighResThumbnailLoader getHighResThumbnailLoader() {
+ return mHighResThumbnailLoader;
+ }
+
/** Creates a new plan for loading the recent tasks. */
public RecentsTaskLoadPlan createLoadPlan(Context context) {
RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
@@ -346,7 +355,7 @@ public class RecentsTaskLoader {
/** Releases the task resource data back into the pool. */
public void unloadTaskData(Task t) {
mLoadQueue.removeTask(t);
- t.notifyTaskDataUnloaded(null, mDefaultIcon);
+ t.notifyTaskDataUnloaded(mDefaultIcon);
}
/** Completely removes the resource data from the pool. */
@@ -356,7 +365,7 @@ public class RecentsTaskLoader {
mActivityLabelCache.remove(t.key);
mContentDescriptionCache.remove(t.key);
if (notifyTaskDataUnloaded) {
- t.notifyTaskDataUnloaded(null, mDefaultIcon);
+ t.notifyTaskDataUnloaded(mDefaultIcon);
}
}
@@ -491,16 +500,16 @@ public class RecentsTaskLoader {
/**
* Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
*/
- Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
+ ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
SystemServicesProxy ssp = Recents.getSystemServices();
if (loadIfNotCached) {
RecentsConfiguration config = Recents.getConfiguration();
if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
// Load the thumbnail from the system
- ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id);
+ ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id, true /* reducedResolution */);
if (thumbnailData.thumbnail != null) {
- return thumbnailData.thumbnail;
+ return thumbnailData;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 2f2e866cd159..29d0a2372769 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.model;
import android.app.ActivityManager;
+import android.app.ActivityManager.TaskThumbnail;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -141,7 +142,7 @@ public class Task {
* which can then fall back to the application icon.
*/
public Drawable icon;
- public Bitmap thumbnail;
+ public ThumbnailData thumbnail;
@ViewDebug.ExportedProperty(category="recents")
public String title;
@ViewDebug.ExportedProperty(category="recents")
@@ -199,11 +200,11 @@ public class Task {
}
public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
- Bitmap thumbnail, String title, String titleDescription, String dismissDescription,
- String appInfoDescription, int colorPrimary, int colorBackground,
- boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
- boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
- int resizeMode, ComponentName topActivity, boolean isLocked) {
+ ThumbnailData thumbnail, String title, String titleDescription,
+ String dismissDescription, String appInfoDescription, int colorPrimary,
+ int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
+ boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
+ int resizeMode, ComponentName topActivity, boolean isLocked) {
boolean isInAffiliationGroup = (affiliationTaskId != key.id);
boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
this.key = key;
@@ -301,7 +302,7 @@ public class Task {
/** Notifies the callback listeners that this task has been loaded */
public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
this.icon = applicationIcon;
- this.thumbnail = thumbnailData != null ? thumbnailData.thumbnail : null;
+ this.thumbnail = thumbnailData;
int callbackCount = mCallbacks.size();
for (int i = 0; i < callbackCount; i++) {
mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData);
@@ -309,9 +310,9 @@ public class Task {
}
/** Notifies the callback listeners that this task has been unloaded */
- public void notifyTaskDataUnloaded(Bitmap defaultThumbnail, Drawable defaultApplicationIcon) {
+ public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) {
icon = defaultApplicationIcon;
- thumbnail = defaultThumbnail;
+ thumbnail = null;
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
mCallbacks.get(i).onTaskDataUnloaded();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
index 09a37129c7e4..33ff1b634d64 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
@@ -29,12 +29,16 @@ public class ThumbnailData {
public Bitmap thumbnail;
public int orientation;
public final Rect insets = new Rect();
+ public boolean reducedResolution;
+ public float scale;
public static ThumbnailData createFromTaskSnapshot(TaskSnapshot snapshot) {
ThumbnailData out = new ThumbnailData();
out.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
out.insets.set(snapshot.getContentInsets());
out.orientation = snapshot.getOrientation();
+ out.reducedResolution = snapshot.isReducedResolution();
+ out.scale = snapshot.getScale();
return out;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 40aad4573354..b7cedf79e088 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -83,8 +83,8 @@ import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
@@ -96,10 +96,10 @@ import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
-
import com.android.systemui.recents.views.grid.GridTaskView;
import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -217,6 +217,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// grid layout.
private TaskViewFocusFrame mTaskViewFocusFrame;
+ private Task mPrefetchingTask;
+ private final float mFastFlingVelocity;
+
// A convenience update listener to request updating clipping of tasks
private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
new ValueAnimator.AnimatorUpdateListener() {
@@ -273,6 +276,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mTaskCornerRadiusPx = Recents.getConfiguration().isGridEnabled ?
res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
+ mFastFlingVelocity = res.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
mDividerSize = ssp.getDockedDividerSize(context);
mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
mDisplayRect = ssp.getDisplayRect();
@@ -663,6 +667,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
+ updatePrefetchingTask(tasks, visibleTaskRange[0], visibleTaskRange[1]);
+
// Update the focus if the previous focused task was returned to the view pool
if (lastFocusedTaskIndex != -1) {
int newFocusedTaskIndex = (lastFocusedTaskIndex < visibleTaskRange[1])
@@ -1200,6 +1206,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (mStackScroller.computeScroll()) {
// Notify accessibility
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ Recents.getTaskLoader().getHighResThumbnailLoader().setFlingingFast(
+ mStackScroller.getScrollVelocity() > mFastFlingVelocity);
}
if (mDeferredTaskViewLayoutAnimation != null) {
relayoutTaskViews(mDeferredTaskViewLayoutAnimation);
@@ -1657,13 +1665,41 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
tv.setNoUserInteractionState();
}
- // Load the task data
- Recents.getTaskLoader().loadTaskData(task);
+ if (task == mPrefetchingTask) {
+ task.notifyTaskDataLoaded(task.thumbnail, task.icon);
+ } else {
+ // Load the task data
+ Recents.getTaskLoader().loadTaskData(task);
+ }
+ Recents.getTaskLoader().getHighResThumbnailLoader().onTaskVisible(task);
}
private void unbindTaskView(TaskView tv, Task task) {
- // Report that this task's data is no longer being used
- Recents.getTaskLoader().unloadTaskData(task);
+ if (task != mPrefetchingTask) {
+ // Report that this task's data is no longer being used
+ Recents.getTaskLoader().unloadTaskData(task);
+ }
+ Recents.getTaskLoader().getHighResThumbnailLoader().onTaskInvisible(task);
+ }
+
+ private void updatePrefetchingTask(ArrayList<Task> tasks, int frontIndex, int backIndex) {
+ Task t = null;
+ boolean somethingVisible = frontIndex != -1 && backIndex != -1;
+ if (somethingVisible && frontIndex < tasks.size() - 1) {
+ t = tasks.get(frontIndex + 1);
+ }
+ if (mPrefetchingTask != t) {
+ if (mPrefetchingTask != null) {
+ int index = tasks.indexOf(mPrefetchingTask);
+ if (index < backIndex || index > frontIndex) {
+ Recents.getTaskLoader().unloadTaskData(mPrefetchingTask);
+ }
+ }
+ mPrefetchingTask = t;
+ if (t != null) {
+ Recents.getTaskLoader().loadTaskData(t);
+ }
+ }
}
/**** TaskViewCallbacks Implementation ****/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 1fa73c6ef61c..8cd3d156b1b8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -262,6 +262,10 @@ public class TaskStackViewScroller {
return !mScroller.isFinished();
}
+ float getScrollVelocity() {
+ return mScroller.getCurrVelocity();
+ }
+
/** Stops the scroller and any current fling. */
void stopScroller() {
if (!mScroller.isFinished()) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index e0dac090ad6e..5989b33f3808 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -66,7 +66,7 @@ public class TaskViewThumbnail extends View {
protected Rect mThumbnailRect = new Rect();
@ViewDebug.ExportedProperty(category="recents")
protected float mThumbnailScale;
- private float mFullscreenThumbnailScale;
+ private float mFullscreenThumbnailScale = 1f;
/** The height, in pixels, of the task view's title bar. */
private int mTitleBarHeight;
private boolean mSizeToFit = false;
@@ -116,12 +116,6 @@ public class TaskViewThumbnail extends View {
mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
mBgFillPaint.setColor(Color.WHITE);
mLockedPaint.setColor(Color.WHITE);
- if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
- mFullscreenThumbnailScale = 1f;
- } else {
- mFullscreenThumbnailScale = res.getFraction(
- com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
- }
mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
}
@@ -190,6 +184,7 @@ public class TaskViewThumbnail extends View {
if (thumbnailData != null && thumbnailData.thumbnail != null) {
Bitmap bm = thumbnailData.thumbnail;
bm.prepareToDraw();
+ mFullscreenThumbnailScale = thumbnailData.scale;
mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mDrawPaint.setShader(mBitmapShader);
mThumbnailRect.set(0, 0,
@@ -297,7 +292,8 @@ public class TaskViewThumbnail extends View {
(float) mTaskViewRect.width() / mThumbnailRect.width(),
(float) mTaskViewRect.height() / mThumbnailRect.height());
}
- mMatrix.setTranslate(-mThumbnailData.insets.left, -mThumbnailData.insets.top);
+ mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale,
+ -mThumbnailData.insets.top * mFullscreenThumbnailScale);
mMatrix.postScale(mThumbnailScale, mThumbnailScale);
mBitmapShader.setLocalMatrix(mMatrix);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java
new file mode 100644
index 000000000000..4d632af4c935
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.recents.model;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task.TaskKey;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * runtest systemui -c com.android.systemui.recents.model.HighResThumbnailLoaderTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HighResThumbnailLoaderTest extends SysuiTestCase {
+
+ private HighResThumbnailLoader mLoader;
+
+ @Mock
+ private SystemServicesProxy mMockSystemServicesProxy;
+ @Mock
+ private Task mTask;
+
+ private ThumbnailData mThumbnailData = new ThumbnailData();
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mLoader = new HighResThumbnailLoader(mMockSystemServicesProxy, Looper.getMainLooper());
+ mTask.key = new TaskKey(0, 0, null, 0, 0, 0);
+ when(mMockSystemServicesProxy.getTaskThumbnail(anyInt(), anyBoolean()))
+ .thenReturn(mThumbnailData);
+ mLoader.setVisible(true);
+ }
+
+ @Test
+ public void testLoading() throws Exception {
+ mLoader.setVisible(true);
+ assertTrue(mLoader.isLoading());
+ mLoader.setVisible(false);
+ assertFalse(mLoader.isLoading());
+ mLoader.setVisible(true);
+ mLoader.setFlingingFast(true);
+ assertFalse(mLoader.isLoading());
+ mLoader.setFlingingFast(false);
+ assertTrue(mLoader.isLoading());
+ }
+
+ @Test
+ public void testLoad() throws Exception {
+ mLoader.onTaskVisible(mTask);
+ mLoader.waitForLoaderIdle();
+ waitForIdleSync();
+ verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
+ }
+
+ @Test
+ public void testFlinging_notLoaded() throws Exception {
+ mLoader.setFlingingFast(true);
+ mLoader.onTaskVisible(mTask);
+ mLoader.waitForLoaderIdle();
+ waitForIdleSync();
+ verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
+ }
+
+ /**
+ * Tests whether task is loaded after stopping to fling
+ */
+ @Test
+ public void testAfterFlinging() throws Exception {
+ mLoader.setFlingingFast(true);
+ mLoader.onTaskVisible(mTask);
+ mLoader.setFlingingFast(false);
+ mLoader.waitForLoaderIdle();
+ waitForIdleSync();
+ verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
+ }
+
+ @Test
+ public void testAlreadyLoaded() throws Exception {
+ mTask.thumbnail = new ThumbnailData();
+ mTask.thumbnail.reducedResolution = false;
+ mLoader.onTaskVisible(mTask);
+ mLoader.waitForLoaderIdle();
+ waitForIdleSync();
+ verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 46552e2a9a72..18befef4772a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10028,7 +10028,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public TaskSnapshot getTaskSnapshot(int taskId) {
+ public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
final long ident = Binder.clearCallingIdentity();
try {
@@ -10042,7 +10042,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
// Don't call this while holding the lock as this operation might hit the disk.
- return task.getSnapshot();
+ return task.getSnapshot(reducedResolution);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 99fe418e66e1..13c8865525ef 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -737,11 +737,11 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta
/**
* DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD!
*/
- TaskSnapshot getSnapshot() {
+ TaskSnapshot getSnapshot(boolean reducedResolution) {
// TODO: Move this to {@link TaskWindowContainerController} once recent tasks are more
// synchronized between AM and WM.
- return mService.mWindowManager.getTaskSnapshot(taskId, userId);
+ return mService.mWindowManager.getTaskSnapshot(taskId, userId, reducedResolution);
}
void touchActiveTime() {
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index b90a82a88641..bd38be4c090c 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -535,7 +535,7 @@ public class AppWindowContainerController
private boolean createSnapshot() {
final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
- false /* restoreFromDisk */);
+ false /* restoreFromDisk */, false /* reducedResolution */);
if (snapshot == null) {
return false;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 40283360dcfc..1ec020180aa2 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -61,7 +61,8 @@ class TaskSnapshotCache {
/**
* If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
*/
- @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
+ @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+ boolean reducedResolution) {
synchronized (mService.mWindowMap) {
// Try the running cache.
@@ -81,19 +82,23 @@ class TaskSnapshotCache {
if (!restoreFromDisk) {
return null;
}
- return tryRestoreFromDisk(taskId, userId);
+ return tryRestoreFromDisk(taskId, userId, reducedResolution);
}
/**
* DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
*/
- private TaskSnapshot tryRestoreFromDisk(int taskId, int userId) {
- final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId);
+ private TaskSnapshot tryRestoreFromDisk(int taskId, int userId, boolean reducedResolution) {
+ final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId, reducedResolution);
if (snapshot == null) {
return null;
}
- synchronized (mService.mWindowMap) {
- mRetrievalCache.put(taskId, snapshot);
+
+ // Only cache non-reduced snapshots.
+ if (!reducedResolution) {
+ synchronized (mService.mWindowMap) {
+ mRetrievalCache.put(taskId, snapshot);
+ }
}
return snapshot;
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 5995bba2c4ff..469a8a710f07 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -106,8 +106,9 @@ class TaskSnapshotController {
* Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
* MANAGER LOCK WHEN CALLING THIS METHOD!
*/
- @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
- return mCache.getSnapshot(taskId, userId, restoreFromDisk);
+ @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+ boolean reducedResolution) {
+ return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
}
/**
@@ -130,7 +131,7 @@ class TaskSnapshotController {
return null;
}
return new TaskSnapshot(buffer, top.getConfiguration().orientation,
- top.findMainWindow().mStableInsets);
+ top.findMainWindow().mStableInsets, false /* reduced */, 1f /* scale */);
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
index 43408229289e..ec21d259f70e 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static com.android.server.wm.TaskSnapshotPersister.*;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -59,11 +60,14 @@ class TaskSnapshotLoader {
*
* @param taskId The id of the task to load.
* @param userId The id of the user the task belonged to.
+ * @param reducedResolution Whether to load a reduced resolution version of the snapshot.
* @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
*/
- TaskSnapshot loadTask(int taskId, int userId) {
+ TaskSnapshot loadTask(int taskId, int userId, boolean reducedResolution) {
final File protoFile = mPersister.getProtoFile(taskId, userId);
- final File bitmapFile = mPersister.getBitmapFile(taskId, userId);
+ final File bitmapFile = reducedResolution
+ ? mPersister.getReducedResolutionBitmapFile(taskId, userId)
+ : mPersister.getBitmapFile(taskId, userId);
if (!protoFile.exists() || !bitmapFile.exists()) {
return null;
}
@@ -84,7 +88,8 @@ class TaskSnapshotLoader {
return null;
}
return new TaskSnapshot(buffer, proto.orientation,
- new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom));
+ new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
+ reducedResolution, reducedResolution ? REDUCED_SCALE : 1f);
} catch (IOException e) {
Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
return null;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 3a06c3859ca5..f2a92df2b820 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.graphics.Bitmap.CompressFormat.*;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -47,9 +48,12 @@ class TaskSnapshotPersister {
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
private static final String SNAPSHOTS_DIRNAME = "snapshots";
+ private static final String REDUCED_POSTFIX = "_reduced";
+ static final float REDUCED_SCALE = 0.5f;
private static final long DELAY_MS = 100;
+ private static final int QUALITY = 95;
private static final String PROTO_EXTENSION = ".proto";
- private static final String BITMAP_EXTENSION = ".png";
+ private static final String BITMAP_EXTENSION = ".jpg";
@GuardedBy("mLock")
private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
@@ -152,6 +156,10 @@ class TaskSnapshotPersister {
return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
}
+ File getReducedResolutionBitmapFile(int taskId, int userId) {
+ return new File(getDirectory(userId), taskId + REDUCED_POSTFIX + BITMAP_EXTENSION);
+ }
+
private boolean createDirectory(int userId) {
final File dir = getDirectory(userId);
return dir.exists() || dir.mkdirs();
@@ -160,8 +168,10 @@ class TaskSnapshotPersister {
private void deleteSnapshot(int taskId, int userId) {
final File protoFile = getProtoFile(taskId, userId);
final File bitmapFile = getBitmapFile(taskId, userId);
+ final File bitmapReducedFile = getReducedResolutionBitmapFile(taskId, userId);
protoFile.delete();
bitmapFile.delete();
+ bitmapReducedFile.delete();
}
interface DirectoryResolver {
@@ -254,13 +264,20 @@ class TaskSnapshotPersister {
boolean writeBuffer() {
final File file = getBitmapFile(mTaskId, mUserId);
+ final File reducedFile = getReducedResolutionBitmapFile(mTaskId, mUserId);
final Bitmap bitmap = Bitmap.createHardwareBitmap(mSnapshot.getSnapshot());
+ final Bitmap reduced = Bitmap.createScaledBitmap(bitmap,
+ (int) (bitmap.getWidth() * REDUCED_SCALE),
+ (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */);
try {
FileOutputStream fos = new FileOutputStream(file);
- bitmap.compress(CompressFormat.PNG, 0 /* quality */, fos);
+ bitmap.compress(JPEG, QUALITY, fos);
fos.close();
+ FileOutputStream reducedFos = new FileOutputStream(reducedFile);
+ reduced.compress(JPEG, QUALITY, reducedFos);
+ reducedFos.close();
} catch (IOException e) {
- Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
+ Slog.e(TAG, "Unable to open " + file + " or " + reducedFile +" for persisting.", e);
return false;
}
return true;
@@ -325,8 +342,12 @@ class TaskSnapshotPersister {
if (end == -1) {
return -1;
}
+ String name = fileName.substring(0, end);
+ if (name.endsWith(REDUCED_POSTFIX)) {
+ name = name.substring(0, name.length() - REDUCED_POSTFIX.length());
+ }
try {
- return Integer.parseInt(fileName.substring(0, end));
+ return Integer.parseInt(name);
} catch (NumberFormatException e) {
return -1;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5edb82cfb1bb..5844b0b6f583 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3621,8 +3621,9 @@ public class WindowManagerService extends IWindowManager.Stub
return true;
}
- public TaskSnapshot getTaskSnapshot(int taskId, int userId) {
- return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */);
+ public TaskSnapshot getTaskSnapshot(int taskId, int userId, boolean reducedResolution) {
+ return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */,
+ reducedResolution);
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index f1fcba3aac24..290f69a7a7e7 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -66,10 +66,10 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
mCache.onAppRemoved(window.mAppToken);
assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
}
@Test
@@ -77,12 +77,12 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
mCache.onAppDied(window.mAppToken);
// Should still be in the retrieval cache.
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
// Trash retrieval cache.
for (int i = 0; i < 20; i++) {
@@ -92,7 +92,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
// Should not be in cache anymore
assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
}
@Test
@@ -100,10 +100,27 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
mCache.onTaskRemoved(window.getTask().mTaskId);
assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
+ }
+
+ @Test
+ public void testReduced_notCached() throws Exception {
+ final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
+ mPersister.waitForQueueEmpty();
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ false /* restoreFromDisk */, false /* reducedResolution */));
+
+ // Load it from disk
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ true /* restoreFromDisk */, true /* reducedResolution */));
+
+ // Make sure it's not in the cache now.
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ false /* restoreFromDisk */, false /* reducedResolution */));
}
@Test
@@ -112,14 +129,14 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
mPersister.waitForQueueEmpty();
assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
// Load it from disk
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
- true /* restoreFromDisk */));
+ true /* restoreFromDisk */, false /* reducedResolution */));
// Make sure it's in the cache now.
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index dc008b52c3b3..4121447c156f 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -26,6 +26,7 @@ import android.app.ActivityManager.TaskSnapshot;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Rect;
+import android.os.Debug;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.MediumTest;
@@ -50,7 +51,6 @@ import java.io.File;
@RunWith(AndroidJUnit4.class)
public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase {
- private static final String TEST_USER_NAME = "TaskSnapshotPersisterTest User";
private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
@Test
@@ -58,9 +58,10 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
mPersister.persistSnapshot(1 , sTestUserId, createSnapshot());
mPersister.waitForQueueEmpty();
final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"),
- new File(sFilesDir.getPath() + "/snapshots/1.png") };
+ new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg")};
assertTrueForFiles(files, File::exists, " must exist");
- final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId);
+ final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId, false /* reduced */);
assertNotNull(snapshot);
assertEquals(TEST_INSETS, snapshot.getContentInsets());
assertNotNull(snapshot.getSnapshot());
@@ -79,7 +80,8 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
mPersister.onTaskRemovedFromRecents(1, sTestUserId);
mPersister.waitForQueueEmpty();
assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.proto").exists());
- assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.png").exists());
+ assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.jpg").exists());
+ assertFalse(new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg").exists());
}
/**
@@ -105,9 +107,10 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp"));
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err"));
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/"));
- assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.png"));
+ assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.jpg"));
assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.proto"));
- assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.png"));
+ assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.jpg"));
+ assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1_reduced.jpg"));
}
@Test
@@ -120,10 +123,12 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
mPersister.waitForQueueEmpty();
final File[] existsFiles = new File[] {
new File(sFilesDir.getPath() + "/snapshots/1.proto"),
- new File(sFilesDir.getPath() + "/snapshots/1.png") };
+ new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg") };
final File[] nonExistsFiles = new File[] {
new File(sFilesDir.getPath() + "/snapshots/2.proto"),
- new File(sFilesDir.getPath() + "/snapshots/2.png") };
+ new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
assertTrueForFiles(existsFiles, File::exists, " must exist");
assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
}
@@ -138,9 +143,11 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
mPersister.waitForQueueEmpty();
final File[] existsFiles = new File[] {
new File(sFilesDir.getPath() + "/snapshots/1.proto"),
- new File(sFilesDir.getPath() + "/snapshots/1.png"),
+ new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg"),
new File(sFilesDir.getPath() + "/snapshots/2.proto"),
- new File(sFilesDir.getPath() + "/snapshots/2.png") };
+ new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
assertTrueForFiles(existsFiles, File::exists, " must exist");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 6fc6edb0812b..5e7389d24330 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -106,6 +106,7 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase {
Canvas c = buffer.lockCanvas();
c.drawColor(Color.RED);
buffer.unlockCanvasAndPost(c);
- return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS);
+ return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS,
+ false /* reducedResolution */, 1f /* scale */);
}
}