diff options
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 */); } } |