Implement new thumbnail loading strategy
- By default, we load only the reduced resolution screenshots. - As soon as the user stops scrolling fast, we also start loading full resolution screenshots. - We prefetch reduced resolution screenshots when scrolling from back to front, as the other direction is automatically prefetched because the thumbnails aren't immediately visible. Test: Open many apps, adb restart, scroll fast and slow in recents Test: runtest systemui -c com.android.systemui.recents.model.HighResThumbnailLoaderTest Bug: 34829962 Change-Id: I7f7a9842eb28a09a18573426fa9677cee2877124
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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) &&
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ****/
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user