diff options
author | Winson Chung <winsonc@google.com> | 2018-10-23 19:59:21 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2018-10-23 19:59:21 +0000 |
commit | 445ab9d8cd3bb63d653cf9d0e0cdb5fae952beaf (patch) | |
tree | 875190189e4e71ce28e2e8a55d5a63c655e1fff9 | |
parent | 02b83db128c133636d7a89ddebbdfcaf2d31f1b1 (diff) | |
parent | cc8dbf31db320c6d9513516603c9dcb2c3d01aa5 (diff) | |
download | android_packages_apps_Trebuchet-445ab9d8cd3bb63d653cf9d0e0cdb5fae952beaf.tar.gz android_packages_apps_Trebuchet-445ab9d8cd3bb63d653cf9d0e0cdb5fae952beaf.tar.bz2 android_packages_apps_Trebuchet-445ab9d8cd3bb63d653cf9d0e0cdb5fae952beaf.zip |
Merge "Caching clean up, remove dependency on old shared lib loading/caching logic" into ub-launcher3-master
17 files changed, 767 insertions, 245 deletions
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml index c29437634..d8ca1c47c 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -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> diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java index b406b3078..4e79fed59 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java +++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java @@ -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) { diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java index 94ec69a0c..b11260ea5 100644 --- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java +++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java @@ -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); diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index d9626c4bd..5b488ca6d 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -230,7 +230,7 @@ public class OverviewCommandHelper { mRunningTaskId = mAM.getRunningTask().id; // Preload the plan - mRecentsModel.loadTasks(mRunningTaskId, null); + mRecentsModel.getTasks(null); } @Override diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java new file mode 100644 index 000000000..fec38bf14 --- /dev/null +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -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; + } +}
\ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index b93a54b89..ef735e1ce 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -219,7 +219,6 @@ public class RecentsActivity extends BaseDraggingActivity { // onActivityStart callback. mFallbackRecentsView.setContentAlpha(1); super.onStart(); - UiFactory.onStart(this); mFallbackRecentsView.resetTaskVisuals(); } diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index 517f7599d..a9ce5ccf2 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -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 TaskIconCache getIconCache() { + return mIconCache; } - public RecentsTaskLoader getRecentsTaskLoader() { - return mRecentsTaskLoader; + 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; - - // Fail fast if nothing has changed. - if (mLastLoadPlanId == mTaskChangeId) { - if (callback != null) { - final RecentsTaskLoadPlan plan = mLastLoadPlan; - mMainThreadExecutor.execute(() -> callback.accept(plan)); - } - return requestId; - } - - 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; - - if (callback != null) { - callback.accept(loadPlan); - } - }); - }); - return requestId; + public int getTasks(Consumer<ArrayList<Task>> callback) { + return mTaskList.getTasks(-1, false /* loadKeysOnly */, callback); } - public void setPreloadTasksInBackground(boolean preloadTasksInBackground) { - mPreloadTasksInBackground = preloadTasksInBackground && !mIsLowRamDevice; + /** + * @return Whether the provided {@param changeId} is the latest recent tasks list id. + */ + public boolean isTaskListValid(int changeId) { + return mTaskList.isTaskListValid(changeId); } - @Override - public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { - mTaskChangeId++; + /** + * 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; + } + } + callback.accept(null); + }); } @Override - public void onActivityUnpinned() { - mTaskChangeId++; + public void onTaskStackChangedBackground() { + if (!mThumbnailCache.isPreloadingEnabled()) { + // Skip if we aren't preloading + return; + } + + int currentUserId = Process.myUserHandle().getIdentifier(); + if (!checkCurrentOrManagedUserId(currentUserId, mContext)) { + // Skip if we are not the current user + return; + } + + // 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); + } + }); } @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) { diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java new file mode 100644 index 000000000..afa58fab0 --- /dev/null +++ b/quickstep/src/com/android/quickstep/TaskIconCache.java @@ -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); + } + } +} diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java new file mode 100644 index 000000000..c47101b82 --- /dev/null +++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java @@ -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; + } + } +} diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 9371a4c1c..b1a214d9d 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -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(); diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index 1c79f4475..6908b8978 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -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() { diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 1205bdc3b..cbbd181e2 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -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() { diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 56074f0f5..da5b79a9d 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -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) { diff --git a/res/values/config.xml b/res/values/config.xml index 85c2e65c5..946afecf8 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -132,6 +132,4 @@ <!-- Recents --> <item type="id" name="overview_panel"/> - <integer name="config_recentsMaxThumbnailCacheSize">6</integer> - <integer name="config_recentsMaxIconCacheSize">12</integer> </resources> diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index e714a0bf8..3ae9a49e7 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -768,7 +768,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } mAppWidgetHost.setListenIfResumed(true); NotificationListener.setNotificationsChangedListener(mPopupDataProvider); - UiFactory.onStart(this); } private void logOnDelayedResume() { diff --git a/src/com/android/launcher3/icons/HandlerRunnable.java b/src/com/android/launcher3/icons/HandlerRunnable.java new file mode 100644 index 000000000..e7132cd69 --- /dev/null +++ b/src/com/android/launcher3/icons/HandlerRunnable.java @@ -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(); + } + } + } +} diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 41a53e572..6e2ca28ec 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -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); } } |