diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 72bdbf124c181..5ffc8f941aa4e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -726,6 +726,10 @@ 0.5 + + 600dp + 48dp diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index d3e939f8b0b46..6da827240f3a8 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 c9debb2068b75..f0a9bc3f80be2 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 752eb2aaf81ce..25eea951c5858 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(); } 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 0000000000000..be8da9f384781 --- /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 mLoadQueue = new ArrayDeque<>(); + @GuardedBy("mLoadQueue") + private final ArraySet mLoadingTasks = new ArraySet<>(); + @GuardedBy("mLoadQueue") + private boolean mLoaderIdling; + + private final ArrayList 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 12c10dff0ab38..f8d123b8fdde1 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 40a4a2b6a123f..e378d0ae51dee 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 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 2f2e866cd159a..29d0a2372769a 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 09a37129c7e49..33ff1b634d64f 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 40aad45733544..b7cedf79e0886 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 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 1fa73c6ef61cc..8cd3d156b1b89 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 e0dac090ad6e6..5989b33f38084 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 0000000000000..4d632af4c9354 --- /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); + } +}