Merge "Caching clean up, remove dependency on old shared lib loading/caching logic" into ub-launcher3-master
This commit is contained in:
@@ -19,4 +19,9 @@
|
||||
<string name="overview_callbacks_class" translatable="false"></string>
|
||||
|
||||
<string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
|
||||
|
||||
<!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
|
||||
determines how many thumbnails will be fetched in the background. -->
|
||||
<integer name="recentsThumbnailCacheSize">3</integer>
|
||||
<integer name="recentsIconCacheSize">12</integer>
|
||||
</resources>
|
||||
|
||||
@@ -163,19 +163,12 @@ public class UiFactory {
|
||||
}
|
||||
}
|
||||
|
||||
public static void onStart(Context context) {
|
||||
RecentsModel model = RecentsModel.INSTANCE.get(context);
|
||||
if (model != null) {
|
||||
model.onStart();
|
||||
}
|
||||
}
|
||||
|
||||
public static void onEnterAnimationComplete(Context context) {
|
||||
// After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
|
||||
// as a part of quickstep/scrub, so that high-res thumbnails can load the next time we
|
||||
// enter overview
|
||||
RecentsModel.INSTANCE.get(context).getRecentsTaskLoader()
|
||||
.getHighResThumbnailLoader().setVisible(true);
|
||||
RecentsModel.INSTANCE.get(context).getThumbnailCache()
|
||||
.getHighResLoadingState().setVisible(true);
|
||||
}
|
||||
|
||||
public static void onLauncherStateOrResumeChanged(Launcher launcher) {
|
||||
|
||||
@@ -232,7 +232,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
|
||||
mInputConsumer, mTouchInteractionLog);
|
||||
|
||||
// Preload the plan
|
||||
mRecentsModel.loadTasks(mRunningTask.id, null);
|
||||
mRecentsModel.getTasks(null);
|
||||
mInteractionHandler = handler;
|
||||
handler.setGestureEndCallback(mEventQueue::reset);
|
||||
|
||||
|
||||
@@ -230,7 +230,7 @@ public class OverviewCommandHelper {
|
||||
mRunningTaskId = mAM.getRunningTask().id;
|
||||
|
||||
// Preload the plan
|
||||
mRecentsModel.loadTasks(mRunningTaskId, null);
|
||||
mRecentsModel.getTasks(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.quickstep;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.os.Process;
|
||||
import android.util.SparseBooleanArray;
|
||||
import com.android.launcher3.MainThreadExecutor;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.BackgroundExecutor;
|
||||
import com.android.systemui.shared.system.KeyguardManagerCompat;
|
||||
import com.android.systemui.shared.system.RecentTaskInfoCompat;
|
||||
import com.android.systemui.shared.system.TaskDescriptionCompat;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Manages the recent task list from the system, caching it as necessary.
|
||||
*/
|
||||
public class RecentTasksList extends TaskStackChangeListener {
|
||||
|
||||
private final KeyguardManagerCompat mKeyguardManager;
|
||||
private final MainThreadExecutor mMainThreadExecutor;
|
||||
private final BackgroundExecutor mBgThreadExecutor;
|
||||
|
||||
// The list change id, increments as the task list changes in the system
|
||||
private int mChangeId;
|
||||
// The last change id when the list was last loaded completely, must be <= the list change id
|
||||
private int mLastLoadedId;
|
||||
|
||||
ArrayList<Task> mTasks = new ArrayList<>();
|
||||
|
||||
public RecentTasksList(Context context) {
|
||||
mMainThreadExecutor = new MainThreadExecutor();
|
||||
mBgThreadExecutor = BackgroundExecutor.get();
|
||||
mKeyguardManager = new KeyguardManagerCompat(context);
|
||||
mChangeId = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously fetches the list of recent tasks.
|
||||
*
|
||||
* @param numTasks The maximum number of tasks to fetch
|
||||
* @param loadKeysOnly Whether to load other associated task data, or just the key
|
||||
* @param callback The callback to receive the list of recent tasks
|
||||
* @return The change id of the current task list
|
||||
*/
|
||||
public synchronized int getTasks(int numTasks, boolean loadKeysOnly,
|
||||
Consumer<ArrayList<Task>> callback) {
|
||||
final int requestLoadId = mChangeId;
|
||||
final int numLoadTasks = numTasks > 0
|
||||
? numTasks
|
||||
: Integer.MAX_VALUE;
|
||||
|
||||
if (mLastLoadedId == mChangeId) {
|
||||
// The list is up to date, callback with the same list
|
||||
mMainThreadExecutor.execute(() -> {
|
||||
if (callback != null) {
|
||||
callback.accept(mTasks);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Kick off task loading in the background
|
||||
mBgThreadExecutor.submit(() -> {
|
||||
ArrayList<Task> tasks = loadTasksInBackground(numLoadTasks,
|
||||
loadKeysOnly);
|
||||
|
||||
mMainThreadExecutor.execute(() -> {
|
||||
mTasks = tasks;
|
||||
mLastLoadedId = requestLoadId;
|
||||
|
||||
if (callback != null) {
|
||||
callback.accept(tasks);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return requestLoadId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the provided {@param changeId} is the latest recent tasks list id.
|
||||
*/
|
||||
public synchronized boolean isTaskListValid(int changeId) {
|
||||
return mChangeId == changeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onTaskStackChanged() {
|
||||
mChangeId++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onActivityPinned(String packageName, int userId, int taskId,
|
||||
int stackId) {
|
||||
mChangeId++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onActivityUnpinned() {
|
||||
mChangeId++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and creates a list of all the recent tasks.
|
||||
*/
|
||||
private ArrayList<Task> loadTasksInBackground(int numTasks,
|
||||
boolean loadKeysOnly) {
|
||||
int currentUserId = Process.myUserHandle().getIdentifier();
|
||||
ArrayList<Task> allTasks = new ArrayList<>();
|
||||
List<ActivityManager.RecentTaskInfo> rawTasks =
|
||||
ActivityManagerWrapper.getInstance().getRecentTasks(numTasks, currentUserId);
|
||||
// The raw tasks are given in most-recent to least-recent order, we need to reverse it
|
||||
Collections.reverse(rawTasks);
|
||||
|
||||
SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() {
|
||||
@Override
|
||||
public boolean get(int key) {
|
||||
if (indexOfKey(key) < 0) {
|
||||
// Fill the cached locked state as we fetch
|
||||
put(key, mKeyguardManager.isDeviceLocked(key));
|
||||
}
|
||||
return super.get(key);
|
||||
}
|
||||
};
|
||||
|
||||
int taskCount = rawTasks.size();
|
||||
for (int i = 0; i < taskCount; i++) {
|
||||
ActivityManager.RecentTaskInfo rawTask = rawTasks.get(i);
|
||||
RecentTaskInfoCompat t = new RecentTaskInfoCompat(rawTask);
|
||||
Task.TaskKey taskKey = new Task.TaskKey(rawTask);
|
||||
Task task;
|
||||
if (!loadKeysOnly) {
|
||||
ActivityManager.TaskDescription rawTd = t.getTaskDescription();
|
||||
TaskDescriptionCompat td = new TaskDescriptionCompat(rawTd);
|
||||
boolean isLocked = tmpLockedUsers.get(t.getUserId());
|
||||
task = new Task(taskKey, td.getPrimaryColor(), td.getBackgroundColor(),
|
||||
t.supportsSplitScreenMultiWindow(), isLocked, rawTd, t.getTopActivity());
|
||||
} else {
|
||||
task = new Task(taskKey);
|
||||
}
|
||||
allTasks.add(task);
|
||||
}
|
||||
|
||||
return allTasks;
|
||||
}
|
||||
}
|
||||
@@ -219,7 +219,6 @@ public class RecentsActivity extends BaseDraggingActivity {
|
||||
// onActivityStart callback.
|
||||
mFallbackRecentsView.setContentAlpha(1);
|
||||
super.onStart();
|
||||
UiFactory.onStart(this);
|
||||
mFallbackRecentsView.resetTaskVisuals();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,38 +18,22 @@ package com.android.quickstep;
|
||||
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.util.LruCache;
|
||||
import android.util.SparseArray;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import com.android.launcher3.MainThreadExecutor;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.launcher3.util.UiThreadHelper;
|
||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||
import com.android.systemui.shared.recents.model.IconLoader;
|
||||
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
|
||||
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
|
||||
import com.android.systemui.shared.recents.model.RecentsTaskLoader;
|
||||
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.BackgroundExecutor;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -68,110 +52,102 @@ public class RecentsModel extends TaskStackChangeListener {
|
||||
private final ArrayList<AssistDataListener> mAssistDataListeners = new ArrayList<>();
|
||||
|
||||
private final Context mContext;
|
||||
private final RecentsTaskLoader mRecentsTaskLoader;
|
||||
private final MainThreadExecutor mMainThreadExecutor;
|
||||
private final Handler mBgHandler;
|
||||
|
||||
private RecentsTaskLoadPlan mLastLoadPlan;
|
||||
private int mLastLoadPlanId;
|
||||
private int mTaskChangeId;
|
||||
private ISystemUiProxy mSystemUiProxy;
|
||||
private boolean mClearAssistCacheOnStackChange = true;
|
||||
private final boolean mIsLowRamDevice;
|
||||
private boolean mPreloadTasksInBackground;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
|
||||
private final RecentTasksList mTaskList;
|
||||
private final TaskIconCache mIconCache;
|
||||
private final TaskThumbnailCache mThumbnailCache;
|
||||
|
||||
private RecentsModel(Context context) {
|
||||
mContext = context;
|
||||
|
||||
ActivityManager activityManager =
|
||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
mIsLowRamDevice = activityManager.isLowRamDevice();
|
||||
mMainThreadExecutor = new MainThreadExecutor();
|
||||
mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper());
|
||||
|
||||
Resources res = context.getResources();
|
||||
mRecentsTaskLoader = new RecentsTaskLoader(mContext,
|
||||
res.getInteger(R.integer.config_recentsMaxThumbnailCacheSize),
|
||||
res.getInteger(R.integer.config_recentsMaxIconCacheSize), 0) {
|
||||
|
||||
@Override
|
||||
protected IconLoader createNewIconLoader(Context context,
|
||||
TaskKeyLruCache<Drawable> iconCache,
|
||||
LruCache<ComponentName, ActivityInfo> activityInfoCache) {
|
||||
// Disable finding the dominant color since we don't need to use it
|
||||
return new NormalizedIconLoader(context, iconCache, activityInfoCache,
|
||||
true /* disableColorExtraction */);
|
||||
}
|
||||
};
|
||||
mRecentsTaskLoader.startLoader(mContext);
|
||||
HandlerThread loaderThread = new HandlerThread("TaskThumbnailIconCache",
|
||||
Process.THREAD_PRIORITY_BACKGROUND);
|
||||
loaderThread.start();
|
||||
mTaskList = new RecentTasksList(context);
|
||||
mIconCache = new TaskIconCache(context, loaderThread.getLooper());
|
||||
mThumbnailCache = new TaskThumbnailCache(context, loaderThread.getLooper());
|
||||
ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
|
||||
|
||||
mTaskChangeId = 1;
|
||||
loadTasks(-1, null);
|
||||
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
|
||||
}
|
||||
|
||||
public RecentsTaskLoader getRecentsTaskLoader() {
|
||||
return mRecentsTaskLoader;
|
||||
public TaskIconCache getIconCache() {
|
||||
return mIconCache;
|
||||
}
|
||||
|
||||
public TaskThumbnailCache getThumbnailCache() {
|
||||
return mThumbnailCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preloads the task plan
|
||||
* @param taskId The running task id or -1
|
||||
* Fetches the list of recent tasks.
|
||||
*
|
||||
* @param callback The callback to receive the task plan once its complete or null. This is
|
||||
* always called on the UI thread.
|
||||
* @return the request id associated with this call.
|
||||
*/
|
||||
public int loadTasks(int taskId, Consumer<RecentsTaskLoadPlan> callback) {
|
||||
final int requestId = mTaskChangeId;
|
||||
public int getTasks(Consumer<ArrayList<Task>> callback) {
|
||||
return mTaskList.getTasks(-1, false /* loadKeysOnly */, callback);
|
||||
}
|
||||
|
||||
// Fail fast if nothing has changed.
|
||||
if (mLastLoadPlanId == mTaskChangeId) {
|
||||
if (callback != null) {
|
||||
final RecentsTaskLoadPlan plan = mLastLoadPlan;
|
||||
mMainThreadExecutor.execute(() -> callback.accept(plan));
|
||||
/**
|
||||
* @return Whether the provided {@param changeId} is the latest recent tasks list id.
|
||||
*/
|
||||
public boolean isTaskListValid(int changeId) {
|
||||
return mTaskList.isTaskListValid(changeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the task key associated with the given task id.
|
||||
*
|
||||
* @param callback The callback to receive the task key if it is found or null. This is always
|
||||
* called on the UI thread.
|
||||
*/
|
||||
public void findTaskWithId(int taskId, Consumer<Task.TaskKey> callback) {
|
||||
mTaskList.getTasks(-1, true /* loadKeysOnly */, (tasks) -> {
|
||||
for (Task task : tasks) {
|
||||
if (task.key.id == taskId) {
|
||||
callback.accept(task.key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return requestId;
|
||||
callback.accept(null);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskStackChangedBackground() {
|
||||
if (!mThumbnailCache.isPreloadingEnabled()) {
|
||||
// Skip if we aren't preloading
|
||||
return;
|
||||
}
|
||||
|
||||
BackgroundExecutor.get().submit(() -> {
|
||||
// Preload the plan
|
||||
RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(mContext);
|
||||
PreloadOptions opts = new PreloadOptions();
|
||||
opts.loadTitles = mAccessibilityManager.isEnabled();
|
||||
loadPlan.preloadPlan(opts, mRecentsTaskLoader, taskId, UserHandle.myUserId());
|
||||
// Set the load plan on UI thread
|
||||
mMainThreadExecutor.execute(() -> {
|
||||
mLastLoadPlan = loadPlan;
|
||||
mLastLoadPlanId = requestId;
|
||||
int currentUserId = Process.myUserHandle().getIdentifier();
|
||||
if (!checkCurrentOrManagedUserId(currentUserId, mContext)) {
|
||||
// Skip if we are not the current user
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback != null) {
|
||||
callback.accept(loadPlan);
|
||||
// Keep the cache up to date with the latest thumbnails
|
||||
mTaskList.getTasks(mThumbnailCache.getCacheSize(), true /* keysOnly */, (tasks) -> {
|
||||
int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().id;
|
||||
for (Task task : tasks) {
|
||||
if (task.key.id == runningTaskId) {
|
||||
// Skip the running task, it's not going to have an up-to-date snapshot by the
|
||||
// time the user next enters overview
|
||||
continue;
|
||||
}
|
||||
});
|
||||
mThumbnailCache.updateThumbnailInCache(task);
|
||||
}
|
||||
});
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public void setPreloadTasksInBackground(boolean preloadTasksInBackground) {
|
||||
mPreloadTasksInBackground = preloadTasksInBackground && !mIsLowRamDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
|
||||
mTaskChangeId++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityUnpinned() {
|
||||
mTaskChangeId++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskStackChanged() {
|
||||
mTaskChangeId++;
|
||||
|
||||
Preconditions.assertUIThread();
|
||||
if (mClearAssistCacheOnStackChange) {
|
||||
mCachedAssistData.clear();
|
||||
@@ -180,39 +156,6 @@ public class RecentsModel extends TaskStackChangeListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskStackChangedBackground() {
|
||||
int userId = UserHandle.myUserId();
|
||||
if (!mPreloadTasksInBackground || !checkCurrentOrManagedUserId(userId, mContext)) {
|
||||
// TODO: Only register this for the current user
|
||||
return;
|
||||
}
|
||||
|
||||
// Preload a fixed number of task icons/thumbnails in the background
|
||||
ActivityManager.RunningTaskInfo runningTaskInfo =
|
||||
ActivityManagerWrapper.getInstance().getRunningTask();
|
||||
RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
|
||||
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
|
||||
launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
|
||||
launchOpts.numVisibleTasks = 2;
|
||||
launchOpts.numVisibleTaskThumbnails = 2;
|
||||
launchOpts.onlyLoadForCache = true;
|
||||
launchOpts.onlyLoadPausedActivities = true;
|
||||
launchOpts.loadThumbnails = true;
|
||||
PreloadOptions preloadOpts = new PreloadOptions();
|
||||
preloadOpts.loadTitles = mAccessibilityManager.isEnabled();
|
||||
plan.preloadPlan(preloadOpts, mRecentsTaskLoader, -1, userId);
|
||||
mRecentsTaskLoader.loadTasks(plan, launchOpts);
|
||||
}
|
||||
|
||||
public boolean isLoadPlanValid(int resultId) {
|
||||
return mTaskChangeId == resultId;
|
||||
}
|
||||
|
||||
public RecentsTaskLoadPlan getLastLoadPlan() {
|
||||
return mLastLoadPlan;
|
||||
}
|
||||
|
||||
public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
|
||||
mSystemUiProxy = systemUiProxy;
|
||||
}
|
||||
@@ -221,16 +164,15 @@ public class RecentsModel extends TaskStackChangeListener {
|
||||
return mSystemUiProxy;
|
||||
}
|
||||
|
||||
public void onStart() {
|
||||
mRecentsTaskLoader.startLoader(mContext);
|
||||
}
|
||||
|
||||
public void onTrimMemory(int level) {
|
||||
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
|
||||
// We already stop the loader in UI_HIDDEN, so stop the high res loader as well
|
||||
mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(false);
|
||||
mThumbnailCache.getHighResLoadingState().setVisible(false);
|
||||
}
|
||||
if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
|
||||
// Clear everything once we reach a low-mem situation
|
||||
mThumbnailCache.clear();
|
||||
mIconCache.clear();
|
||||
}
|
||||
mBgHandler.post(() -> mRecentsTaskLoader.onTrimMemory(level));
|
||||
}
|
||||
|
||||
public void onOverviewShown(boolean fromHome, String tag) {
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.quickstep;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.LruCache;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import com.android.launcher3.MainThreadExecutor;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.icons.HandlerRunnable;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Manages the caching of task icons and related data.
|
||||
* TODO: This class should later be merged into IconCache.
|
||||
*/
|
||||
public class TaskIconCache {
|
||||
|
||||
private final Handler mBackgroundHandler;
|
||||
private final MainThreadExecutor mMainThreadExecutor;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
|
||||
private final NormalizedIconLoader mIconLoader;
|
||||
|
||||
private final TaskKeyLruCache<Drawable> mIconCache;
|
||||
private final TaskKeyLruCache<String> mContentDescriptionCache;
|
||||
private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
|
||||
|
||||
private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction =
|
||||
new TaskKeyLruCache.EvictionCallback() {
|
||||
@Override
|
||||
public void onEntryEvicted(Task.TaskKey key) {
|
||||
if (key != null) {
|
||||
mActivityInfoCache.remove(key.getComponent());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public TaskIconCache(Context context, Looper backgroundLooper) {
|
||||
mBackgroundHandler = new Handler(backgroundLooper);
|
||||
mMainThreadExecutor = new MainThreadExecutor();
|
||||
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
|
||||
|
||||
Resources res = context.getResources();
|
||||
int cacheSize = res.getInteger(R.integer.recentsIconCacheSize);
|
||||
mIconCache = new TaskKeyLruCache<>(cacheSize, mClearActivityInfoOnEviction);
|
||||
mContentDescriptionCache = new TaskKeyLruCache<>(cacheSize, mClearActivityInfoOnEviction);
|
||||
mActivityInfoCache = new LruCache<>(cacheSize);
|
||||
mIconLoader = new NormalizedIconLoader(context, mIconCache, mActivityInfoCache,
|
||||
true /* disableColorExtraction */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously fetches the icon and other task data.
|
||||
*
|
||||
* @param task The task to fetch the data for
|
||||
* @param callback The callback to receive the task after its data has been populated.
|
||||
* @return A cancelable handle to the request
|
||||
*/
|
||||
public IconLoadRequest updateIconInBackground(Task task, Consumer<Task> callback) {
|
||||
Preconditions.assertUIThread();
|
||||
if (task.icon != null) {
|
||||
// Nothing to load, the icon is already loaded
|
||||
callback.accept(task);
|
||||
return null;
|
||||
}
|
||||
|
||||
IconLoadRequest request = new IconLoadRequest(mBackgroundHandler) {
|
||||
@Override
|
||||
public void run() {
|
||||
Drawable icon = mIconLoader.getIcon(task);
|
||||
String contentDescription = loadContentDescriptionInBackground(task);
|
||||
if (isCanceled()) {
|
||||
// We don't call back to the provided callback in this case
|
||||
return;
|
||||
}
|
||||
mMainThreadExecutor.execute(() -> {
|
||||
task.icon = icon;
|
||||
task.titleDescription = contentDescription;
|
||||
callback.accept(task);
|
||||
onEnd();
|
||||
});
|
||||
}
|
||||
};
|
||||
Utilities.postAsyncCallback(mBackgroundHandler, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mIconCache.evictAll();
|
||||
mContentDescriptionCache.evictAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the content description for the given {@param task}.
|
||||
*/
|
||||
private String loadContentDescriptionInBackground(Task task) {
|
||||
// Return the cached content description if it exists
|
||||
String label = mContentDescriptionCache.getAndInvalidateIfModified(task.key);
|
||||
if (label != null) {
|
||||
return label;
|
||||
}
|
||||
|
||||
// Skip loading content descriptions if accessibility is not enabled
|
||||
if (!mAccessibilityManager.isEnabled()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Skip loading the content description if the activity no longer exists
|
||||
ActivityInfo activityInfo = mIconLoader.getAndUpdateActivityInfo(task.key);
|
||||
if (activityInfo == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Load the label otherwise
|
||||
label = ActivityManagerWrapper.getInstance().getBadgedContentDescription(activityInfo,
|
||||
task.key.userId, task.taskDescription);
|
||||
mContentDescriptionCache.put(task.key, label);
|
||||
return label;
|
||||
}
|
||||
|
||||
public static abstract class IconLoadRequest extends HandlerRunnable {
|
||||
IconLoadRequest(Handler handler) {
|
||||
super(handler, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.quickstep;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import com.android.launcher3.MainThreadExecutor;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.icons.HandlerRunnable;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class TaskThumbnailCache {
|
||||
|
||||
private final Handler mBackgroundHandler;
|
||||
private final MainThreadExecutor mMainThreadExecutor;
|
||||
|
||||
private final int mCacheSize;
|
||||
private final TaskKeyLruCache<ThumbnailData> mCache;
|
||||
private final HighResLoadingState mHighResLoadingState;
|
||||
|
||||
public static class HighResLoadingState {
|
||||
private boolean mIsLowRamDevice;
|
||||
private boolean mVisible;
|
||||
private boolean mFlingingFast;
|
||||
private boolean mHighResLoadingEnabled;
|
||||
private ArrayList<HighResLoadingStateChangedCallback> mCallbacks = new ArrayList<>();
|
||||
|
||||
public interface HighResLoadingStateChangedCallback {
|
||||
void onHighResLoadingStateChanged(boolean enabled);
|
||||
}
|
||||
|
||||
private HighResLoadingState(Context context) {
|
||||
ActivityManager activityManager =
|
||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
mIsLowRamDevice = activityManager.isLowRamDevice();
|
||||
}
|
||||
|
||||
public void addCallback(HighResLoadingStateChangedCallback callback) {
|
||||
mCallbacks.add(callback);
|
||||
}
|
||||
|
||||
public void removeCallback(HighResLoadingStateChangedCallback callback) {
|
||||
mCallbacks.remove(callback);
|
||||
}
|
||||
|
||||
public void setVisible(boolean visible) {
|
||||
mVisible = visible;
|
||||
updateState();
|
||||
}
|
||||
|
||||
public void setFlingingFast(boolean flingingFast) {
|
||||
mFlingingFast = flingingFast;
|
||||
updateState();
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return mHighResLoadingEnabled;
|
||||
}
|
||||
|
||||
private void updateState() {
|
||||
boolean prevState = mHighResLoadingEnabled;
|
||||
mHighResLoadingEnabled = !mIsLowRamDevice && mVisible && !mFlingingFast;
|
||||
if (prevState != mHighResLoadingEnabled) {
|
||||
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
|
||||
mCallbacks.get(i).onHighResLoadingStateChanged(mHighResLoadingEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TaskThumbnailCache(Context context, Looper backgroundLooper) {
|
||||
mBackgroundHandler = new Handler(backgroundLooper);
|
||||
mMainThreadExecutor = new MainThreadExecutor();
|
||||
mHighResLoadingState = new HighResLoadingState(context);
|
||||
|
||||
Resources res = context.getResources();
|
||||
mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize);
|
||||
mCache = new TaskKeyLruCache<>(mCacheSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously fetches the thumbnail for the given {@param task} and puts it in the cache.
|
||||
*/
|
||||
public void updateThumbnailInCache(Task task) {
|
||||
Preconditions.assertUIThread();
|
||||
|
||||
// Fetch the thumbnail for this task and put it in the cache
|
||||
mCache.put(task.key, ActivityManagerWrapper.getInstance().getTaskThumbnail(
|
||||
task.key.id, true /* reducedResolution */));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asynchronously fetches the icon and other task data for the given {@param task}.
|
||||
*
|
||||
* @param callback The callback to receive the task after its data has been populated.
|
||||
* @return A cancelable handle to the request
|
||||
*/
|
||||
public ThumbnailLoadRequest updateThumbnailInBackground(Task task, boolean reducedResolution,
|
||||
Consumer<Task> callback) {
|
||||
Preconditions.assertUIThread();
|
||||
|
||||
if (task.thumbnail != null && (!task.thumbnail.reducedResolution || reducedResolution)) {
|
||||
// Nothing to load, the thumbnail is already high-resolution or matches what the
|
||||
// request, so just callback
|
||||
callback.accept(task);
|
||||
return null;
|
||||
}
|
||||
|
||||
ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(task.key);
|
||||
if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || reducedResolution)) {
|
||||
// Already cached, lets use that thumbnail
|
||||
task.thumbnail = cachedThumbnail;
|
||||
callback.accept(task);
|
||||
return null;
|
||||
}
|
||||
|
||||
ThumbnailLoadRequest request = new ThumbnailLoadRequest(mBackgroundHandler,
|
||||
reducedResolution) {
|
||||
@Override
|
||||
public void run() {
|
||||
ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail(
|
||||
task.key.id, reducedResolution);
|
||||
if (isCanceled()) {
|
||||
// We don't call back to the provided callback in this case
|
||||
return;
|
||||
}
|
||||
mMainThreadExecutor.execute(() -> {
|
||||
task.thumbnail = thumbnail;
|
||||
callback.accept(task);
|
||||
onEnd();
|
||||
});
|
||||
}
|
||||
};
|
||||
Utilities.postAsyncCallback(mBackgroundHandler, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cache.
|
||||
*/
|
||||
public void clear() {
|
||||
mCache.evictAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The cache size.
|
||||
*/
|
||||
public int getCacheSize() {
|
||||
return mCacheSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The mutable high-res loading state.
|
||||
*/
|
||||
public HighResLoadingState getHighResLoadingState() {
|
||||
return mHighResLoadingState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether to enable background preloading of task thumbnails.
|
||||
*/
|
||||
public boolean isPreloadingEnabled() {
|
||||
return !mHighResLoadingState.mIsLowRamDevice && mHighResLoadingState.mVisible;
|
||||
}
|
||||
|
||||
public static abstract class ThumbnailLoadRequest extends HandlerRunnable {
|
||||
public final boolean reducedResolution;
|
||||
|
||||
ThumbnailLoadRequest(Handler handler, boolean reducedResolution) {
|
||||
super(handler, null);
|
||||
this.reducedResolution = reducedResolution;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,6 @@ public class TouchInteractionService extends Service {
|
||||
super.onCreate();
|
||||
mAM = ActivityManagerWrapper.getInstance();
|
||||
mRecentsModel = RecentsModel.INSTANCE.get(this);
|
||||
mRecentsModel.setPreloadTasksInBackground(true);
|
||||
mMainThreadExecutor = new MainThreadExecutor();
|
||||
mOverviewCommandHelper = new OverviewCommandHelper(this);
|
||||
mMainThreadChoreographer = Choreographer.getInstance();
|
||||
|
||||
@@ -504,8 +504,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
|
||||
// This method is only called when STATE_GESTURE_STARTED_QUICKSTEP/
|
||||
// STATE_GESTURE_STARTED_QUICKSCRUB is set, so we can enable the high-res thumbnail loader
|
||||
// here once we are sure that we will end up in an overview state
|
||||
RecentsModel.INSTANCE.get(mContext).getRecentsTaskLoader()
|
||||
.getHighResThumbnailLoader().setVisible(true);
|
||||
RecentsModel.INSTANCE.get(mContext).getThumbnailCache()
|
||||
.getHighResLoadingState().setVisible(true);
|
||||
}
|
||||
|
||||
private void shiftAnimationDestinationForQuickscrub() {
|
||||
|
||||
@@ -43,7 +43,6 @@ import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.text.Layout;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
@@ -78,13 +77,11 @@ import com.android.launcher3.util.Themes;
|
||||
import com.android.quickstep.OverviewCallbacks;
|
||||
import com.android.quickstep.QuickScrubController;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.TaskThumbnailCache;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.util.ClipAnimationHelper;
|
||||
import com.android.quickstep.util.TaskViewDrawable;
|
||||
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
|
||||
import com.android.systemui.shared.recents.model.RecentsTaskLoader;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.TaskStack;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.BackgroundExecutor;
|
||||
@@ -100,7 +97,8 @@ import androidx.annotation.Nullable;
|
||||
* A list of recent tasks.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable {
|
||||
public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable,
|
||||
TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback {
|
||||
|
||||
private static final String TAG = RecentsView.class.getSimpleName();
|
||||
|
||||
@@ -206,17 +204,13 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
handler.post(() ->
|
||||
dismissTask(taskView, true /* animate */, false /* removeTask */));
|
||||
} else {
|
||||
RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(getContext());
|
||||
RecentsTaskLoadPlan.PreloadOptions opts =
|
||||
new RecentsTaskLoadPlan.PreloadOptions();
|
||||
opts.loadTitles = false;
|
||||
loadPlan.preloadPlan(opts, mModel.getRecentsTaskLoader(), -1,
|
||||
UserHandle.myUserId());
|
||||
if (loadPlan.getTaskStack().findTaskWithId(taskId) == null) {
|
||||
// The task was removed from the recents list
|
||||
handler.post(() ->
|
||||
dismissTask(taskView, true /* animate */, false /* removeTask */));
|
||||
}
|
||||
mModel.findTaskWithId(taskKey.id, (key) -> {
|
||||
if (key == null) {
|
||||
// The task was removed from the recents list
|
||||
handler.post(() -> dismissTask(taskView, true /* animate */,
|
||||
false /* removeTask */));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -229,9 +223,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
}
|
||||
};
|
||||
|
||||
// Used to keep track of the last requested load plan id, so that we do not request to load the
|
||||
// Used to keep track of the last requested task list id, so that we do not request to load the
|
||||
// tasks again if we have already requested it and the task list has not changed
|
||||
private int mRequestedLoadPlanId = -1;
|
||||
private int mTaskListChangeId = -1;
|
||||
|
||||
// Only valid until the launcher state changes to NORMAL
|
||||
private int mRunningTaskId = -1;
|
||||
@@ -285,7 +279,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
mActivity = (T) BaseActivity.fromContext(context);
|
||||
mQuickScrubController = new QuickScrubController(mActivity, this);
|
||||
mModel = RecentsModel.INSTANCE.get(context);
|
||||
|
||||
mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
|
||||
.inflate(R.layout.overview_clear_all_button, this, false);
|
||||
mClearAllButton.setOnClickListener(this::dismissAllTasks);
|
||||
@@ -316,7 +309,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
|
||||
TaskView taskView = getTaskView(taskId);
|
||||
if (taskView != null) {
|
||||
taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
|
||||
taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
|
||||
}
|
||||
return taskView;
|
||||
}
|
||||
@@ -331,6 +324,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
updateTaskStackListenerState();
|
||||
mModel.getThumbnailCache().getHighResLoadingState().addCallback(this);
|
||||
mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
|
||||
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
|
||||
}
|
||||
@@ -339,6 +333,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
updateTaskStackListenerState();
|
||||
mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this);
|
||||
mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
|
||||
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
|
||||
}
|
||||
@@ -349,12 +344,11 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
|
||||
// Clear the task data for the removed child if it was visible
|
||||
if (child != mClearAllButton) {
|
||||
Task task = ((TaskView) child).getTask();
|
||||
TaskView taskView = (TaskView) child;
|
||||
Task task = taskView.getTask();
|
||||
if (mHasVisibleTaskData.get(task.key.id)) {
|
||||
mHasVisibleTaskData.delete(task.key.id);
|
||||
RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
|
||||
loader.unloadTaskData(task);
|
||||
loader.getHighResThumbnailLoader().onTaskInvisible(task);
|
||||
taskView.onTaskListVisibilityChanged(false /* visible */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -444,14 +438,13 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
return true;
|
||||
}
|
||||
|
||||
private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
|
||||
private void applyLoadPlan(ArrayList<Task> tasks) {
|
||||
if (mPendingAnimation != null) {
|
||||
mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(loadPlan));
|
||||
mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(tasks));
|
||||
return;
|
||||
}
|
||||
|
||||
TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
|
||||
if (stack == null) {
|
||||
if (tasks == null || tasks.isEmpty()) {
|
||||
removeAllViews();
|
||||
onTaskStackUpdated();
|
||||
return;
|
||||
@@ -462,7 +455,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
// Ensure there are as many views as there are tasks in the stack (adding and trimming as
|
||||
// necessary)
|
||||
final LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
|
||||
|
||||
// Unload existing visible task data
|
||||
unloadVisibleTaskData();
|
||||
@@ -581,9 +573,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
loadVisibleTaskData();
|
||||
}
|
||||
|
||||
// Update the high res thumbnail loader
|
||||
RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
|
||||
loader.getHighResThumbnailLoader().setFlingingFast(isFlingingFast);
|
||||
// Update the high res thumbnail loader state
|
||||
mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
|
||||
return scrolling;
|
||||
}
|
||||
|
||||
@@ -618,13 +609,12 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
* and unloads the associated task data for tasks that are no longer visible.
|
||||
*/
|
||||
public void loadVisibleTaskData() {
|
||||
if (!mOverviewStateEnabled || mRequestedLoadPlanId == -1) {
|
||||
if (!mOverviewStateEnabled || mTaskListChangeId == -1) {
|
||||
// Skip loading visible task data if we've already left the overview state, or if the
|
||||
// task list hasn't been loaded yet (the task views will not reflect the task list)
|
||||
return;
|
||||
}
|
||||
|
||||
RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
|
||||
int centerPageIndex = getPageNearestToCenterOfScreen();
|
||||
int numChildren = getTaskViewCount();
|
||||
int lower = Math.max(0, centerPageIndex - 2);
|
||||
@@ -641,14 +631,12 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
continue;
|
||||
}
|
||||
if (!mHasVisibleTaskData.get(task.key.id)) {
|
||||
loader.loadTaskData(task);
|
||||
loader.getHighResThumbnailLoader().onTaskVisible(task);
|
||||
taskView.onTaskListVisibilityChanged(true /* visible */);
|
||||
}
|
||||
mHasVisibleTaskData.put(task.key.id, visible);
|
||||
} else {
|
||||
if (mHasVisibleTaskData.get(task.key.id)) {
|
||||
loader.unloadTaskData(task);
|
||||
loader.getHighResThumbnailLoader().onTaskInvisible(task);
|
||||
taskView.onTaskListVisibilityChanged(false /* visible */);
|
||||
}
|
||||
mHasVisibleTaskData.delete(task.key.id);
|
||||
}
|
||||
@@ -659,27 +647,40 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
* Unloads any associated data from the currently visible tasks
|
||||
*/
|
||||
private void unloadVisibleTaskData() {
|
||||
RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
|
||||
for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
|
||||
if (mHasVisibleTaskData.valueAt(i)) {
|
||||
TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
|
||||
if (taskView != null) {
|
||||
Task task = taskView.getTask();
|
||||
loader.unloadTaskData(task);
|
||||
loader.getHighResThumbnailLoader().onTaskInvisible(task);
|
||||
taskView.onTaskListVisibilityChanged(false /* visible */);
|
||||
}
|
||||
}
|
||||
}
|
||||
mHasVisibleTaskData.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHighResLoadingStateChanged(boolean enabled) {
|
||||
// Whenever the high res loading state changes, poke each of the visible tasks to see if
|
||||
// they want to updated their thumbnail state
|
||||
for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
|
||||
if (mHasVisibleTaskData.valueAt(i)) {
|
||||
TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
|
||||
if (taskView != null) {
|
||||
// Poke the view again, which will trigger it to load high res if the state
|
||||
// is enabled
|
||||
taskView.onTaskListVisibilityChanged(true /* visible */);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void startHome();
|
||||
|
||||
public void reset() {
|
||||
mRunningTaskId = -1;
|
||||
mRunningTaskTileHidden = false;
|
||||
mIgnoreResetTaskId = -1;
|
||||
mRequestedLoadPlanId = -1;
|
||||
mTaskListChangeId = -1;
|
||||
|
||||
unloadVisibleTaskData();
|
||||
setCurrentPage(0);
|
||||
@@ -691,8 +692,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
* Reloads the view if anything in recents changed.
|
||||
*/
|
||||
public void reloadIfNeeded() {
|
||||
if (!mModel.isLoadPlanValid(mRequestedLoadPlanId)) {
|
||||
mRequestedLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
|
||||
if (!mModel.isTaskListValid(mTaskListChangeId)) {
|
||||
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,8 +754,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
|
||||
setCurrentPage(0);
|
||||
|
||||
// Load the tasks
|
||||
reloadIfNeeded();
|
||||
// Load the tasks (if the loading is already
|
||||
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
|
||||
}
|
||||
|
||||
public void showNextTask() {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.quickstep.views;
|
||||
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
|
||||
import static com.android.launcher3.BaseActivity.fromContext;
|
||||
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
@@ -30,6 +29,7 @@ import android.app.ActivityOptions;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.AttributeSet;
|
||||
@@ -47,14 +47,15 @@ import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.TaskIconCache;
|
||||
import com.android.quickstep.TaskOverlayFactory;
|
||||
import com.android.quickstep.TaskSystemShortcut;
|
||||
import com.android.quickstep.TaskThumbnailCache;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.views.RecentsView.PageCallbacks;
|
||||
import com.android.quickstep.views.RecentsView.ScrollState;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.ActivityOptionsCompat;
|
||||
|
||||
@@ -64,7 +65,7 @@ import java.util.function.Consumer;
|
||||
/**
|
||||
* A task in the Recents view.
|
||||
*/
|
||||
public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
|
||||
public class TaskView extends FrameLayout implements PageCallbacks {
|
||||
|
||||
private static final String TAG = TaskView.class.getSimpleName();
|
||||
|
||||
@@ -137,6 +138,10 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
|
||||
private Animator mIconAndDimAnimator;
|
||||
private float mFocusTransitionProgress = 1;
|
||||
|
||||
// The current background requests to load the task thumbnail and icon
|
||||
private TaskThumbnailCache.ThumbnailLoadRequest mThumbnailLoadRequest;
|
||||
private TaskIconCache.IconLoadRequest mIconLoadRequest;
|
||||
|
||||
public TaskView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -170,13 +175,8 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
|
||||
* Updates this task view to the given {@param task}.
|
||||
*/
|
||||
public void bind(Task task) {
|
||||
if (mTask != null) {
|
||||
mTask.removeCallback(this);
|
||||
}
|
||||
mTask = task;
|
||||
mSnapshotView.bind();
|
||||
task.addCallback(this);
|
||||
setContentDescription(task.titleDescription);
|
||||
}
|
||||
|
||||
public Task getTask() {
|
||||
@@ -233,15 +233,34 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
|
||||
mSnapshotView.setThumbnail(task, thumbnailData);
|
||||
mIconView.setDrawable(task.icon);
|
||||
mIconView.setOnClickListener(icon -> showTaskMenu());
|
||||
mIconView.setOnLongClickListener(icon -> {
|
||||
requestDisallowInterceptTouchEvent(true);
|
||||
return showTaskMenu();
|
||||
});
|
||||
public void onTaskListVisibilityChanged(boolean visible) {
|
||||
if (mTask == null) {
|
||||
return;
|
||||
}
|
||||
if (visible) {
|
||||
// These calls are no-ops if the data is already loaded, try and load the high
|
||||
// resolution thumbnail if the state permits
|
||||
RecentsModel model = RecentsModel.INSTANCE.get(getContext());
|
||||
TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
|
||||
TaskIconCache iconCache = model.getIconCache();
|
||||
mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(mTask,
|
||||
!thumbnailCache.getHighResLoadingState().isEnabled() /* reducedResolution */,
|
||||
(task) -> mSnapshotView.setThumbnail(task, task.thumbnail));
|
||||
mIconLoadRequest = iconCache.updateIconInBackground(mTask,
|
||||
(task) -> {
|
||||
setContentDescription(task.titleDescription);
|
||||
setIcon(task.icon);
|
||||
});
|
||||
} else {
|
||||
if (mThumbnailLoadRequest != null) {
|
||||
mThumbnailLoadRequest.cancel();
|
||||
}
|
||||
if (mIconLoadRequest != null) {
|
||||
mIconLoadRequest.cancel();
|
||||
}
|
||||
mSnapshotView.setThumbnail(null, null);
|
||||
setIcon(null);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean showTaskMenu() {
|
||||
@@ -253,16 +272,18 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
|
||||
return mMenuView != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskDataUnloaded() {
|
||||
mSnapshotView.setThumbnail(null, null);
|
||||
mIconView.setDrawable(null);
|
||||
mIconView.setOnLongClickListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskWindowingModeChanged() {
|
||||
// Do nothing
|
||||
private void setIcon(Drawable icon) {
|
||||
if (icon != null) {
|
||||
mIconView.setDrawable(icon);
|
||||
mIconView.setOnClickListener(v -> showTaskMenu());
|
||||
mIconView.setOnLongClickListener(v -> {
|
||||
requestDisallowInterceptTouchEvent(true);
|
||||
return showTaskMenu();
|
||||
});
|
||||
} else {
|
||||
mIconView.setDrawable(null);
|
||||
mIconView.setOnLongClickListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void setIconAndDimTransitionProgress(float progress) {
|
||||
|
||||
@@ -132,6 +132,4 @@
|
||||
|
||||
<!-- Recents -->
|
||||
<item type="id" name="overview_panel"/>
|
||||
<integer name="config_recentsMaxThumbnailCacheSize">6</integer>
|
||||
<integer name="config_recentsMaxIconCacheSize">12</integer>
|
||||
</resources>
|
||||
|
||||
@@ -768,7 +768,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
||||
}
|
||||
mAppWidgetHost.setListenIfResumed(true);
|
||||
NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
|
||||
UiFactory.onStart(this);
|
||||
}
|
||||
|
||||
private void logOnDelayedResume() {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.launcher3.icons;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
/**
|
||||
* A runnable that can be posted to a {@link Handler} which can be canceled.
|
||||
*/
|
||||
public abstract class HandlerRunnable implements Runnable {
|
||||
|
||||
private final Handler mHandler;
|
||||
private final Runnable mEndRunnable;
|
||||
|
||||
private boolean mEnded = false;
|
||||
private boolean mCanceled = false;
|
||||
|
||||
public HandlerRunnable(Handler handler, Runnable endRunnable) {
|
||||
mHandler = handler;
|
||||
mEndRunnable = endRunnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels this runnable from being run, only if it has not already run.
|
||||
*/
|
||||
public void cancel() {
|
||||
mHandler.removeCallbacks(this);
|
||||
// TODO: This can actually cause onEnd to be called twice if the handler is already running
|
||||
// this runnable
|
||||
// NOTE: This is currently run on whichever thread the caller is run on.
|
||||
mCanceled = true;
|
||||
onEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this runnable was canceled.
|
||||
*/
|
||||
protected boolean isCanceled() {
|
||||
return mCanceled;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called by the implemention of this runnable. The end callback is done on whichever
|
||||
* thread the caller is calling from.
|
||||
*/
|
||||
public void onEnd() {
|
||||
if (!mEnded) {
|
||||
mEnded = true;
|
||||
if (mEndRunnable != null) {
|
||||
mEndRunnable.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,27 +169,9 @@ public class IconCache extends BaseIconCache {
|
||||
applyCacheEntry(entry, infoInOut);
|
||||
}
|
||||
|
||||
public static abstract class IconLoadRequest implements Runnable {
|
||||
private final Handler mHandler;
|
||||
private final Runnable mEndRunnable;
|
||||
|
||||
private boolean mEnded = false;
|
||||
|
||||
public static abstract class IconLoadRequest extends HandlerRunnable {
|
||||
IconLoadRequest(Handler handler, Runnable endRunnable) {
|
||||
mHandler = handler;
|
||||
mEndRunnable = endRunnable;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
mHandler.removeCallbacks(this);
|
||||
onEnd();
|
||||
}
|
||||
|
||||
public void onEnd() {
|
||||
if (!mEnded) {
|
||||
mEnded = true;
|
||||
mEndRunnable.run();
|
||||
}
|
||||
super(handler, endRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user