diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index 2b10989ec5..b1b9396658 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -46,7 +46,6 @@ public class OverviewCommandHelper { private final Context mContext; private final RecentsAnimationDeviceState mDeviceState; - private final RecentsModel mRecentsModel; private final OverviewComponentObserver mOverviewComponentObserver; private long mLastToggleTime; @@ -55,7 +54,6 @@ public class OverviewCommandHelper { OverviewComponentObserver observer) { mContext = context; mDeviceState = deviceState; - mRecentsModel = RecentsModel.INSTANCE.get(mContext); mOverviewComponentObserver = observer; } @@ -158,7 +156,7 @@ public class OverviewCommandHelper { ActivityManagerWrapper.getInstance().getRunningTask(), mDeviceState); // Preload the plan - mRecentsModel.getTasks(null); + RecentsModel.INSTANCE.get(mContext).getTasks(null); } @Override diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index 6c302ae5d7..d47217b770 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -18,7 +18,6 @@ package com.android.quickstep; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.createAndStartNewLooper; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; import android.annotation.TargetApi; @@ -26,11 +25,11 @@ import android.app.ActivityManager; import android.content.ComponentCallbacks2; import android.content.Context; import android.os.Build; -import android.os.Looper; import android.os.Process; import android.os.UserHandle; import com.android.launcher3.icons.IconProvider; +import com.android.launcher3.util.Executors.SimpleThreadFactory; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -40,6 +39,8 @@ import com.android.systemui.shared.system.TaskStackChangeListener; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.function.Consumer; /** @@ -52,6 +53,9 @@ public class RecentsModel extends TaskStackChangeListener { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(RecentsModel::new); + private static final Executor RECENTS_MODEL_EXECUTOR = Executors.newSingleThreadExecutor( + new SimpleThreadFactory("TaskThumbnailIconCache-", THREAD_PRIORITY_BACKGROUND)); + private final List mThumbnailChangeListeners = new ArrayList<>(); private final Context mContext; @@ -61,12 +65,10 @@ public class RecentsModel extends TaskStackChangeListener { private RecentsModel(Context context) { mContext = context; - Looper looper = - createAndStartNewLooper("TaskThumbnailIconCache", THREAD_PRIORITY_BACKGROUND); mTaskList = new RecentTasksList(MAIN_EXECUTOR, new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance()); - mIconCache = new TaskIconCache(context, looper); - mThumbnailCache = new TaskThumbnailCache(context, looper); + mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR); + mThumbnailCache = new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR); ActivityManagerWrapper.getInstance().registerTaskStackListener(this); IconProvider.registerIconChangeListener(context, diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java index 7ff799ec55..65e89cfa2f 100644 --- a/quickstep/src/com/android/quickstep/TaskIconCache.java +++ b/quickstep/src/com/android/quickstep/TaskIconCache.java @@ -17,7 +17,6 @@ package com.android.quickstep; import static com.android.launcher3.FastBitmapDrawable.newIcon; import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.app.ActivityManager.TaskDescription; import android.content.Context; @@ -27,8 +26,6 @@ import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; -import android.os.Handler; -import android.os.Looper; import android.os.UserHandle; import android.util.SparseArray; import android.view.accessibility.AccessibilityManager; @@ -37,12 +34,11 @@ import androidx.annotation.WorkerThread; import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.R; -import com.android.launcher3.Utilities; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.IconProvider; import com.android.launcher3.icons.LauncherIcons; -import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.util.Preconditions; +import com.android.quickstep.util.CancellableTask; import com.android.quickstep.util.TaskKeyLruCache; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; @@ -50,6 +46,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.PackageManagerWrapper; import com.android.systemui.shared.system.TaskDescriptionCompat; +import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -57,7 +54,7 @@ import java.util.function.Consumer; */ public class TaskIconCache { - private final Handler mBackgroundHandler; + private final Executor mBgExecutor; private final AccessibilityManager mAccessibilityManager; private final Context mContext; @@ -65,9 +62,9 @@ public class TaskIconCache { private final SparseArray mDefaultIcons = new SparseArray<>(); private final IconProvider mIconProvider; - public TaskIconCache(Context context, Looper backgroundLooper) { + public TaskIconCache(Context context, Executor bgExecutor) { mContext = context; - mBackgroundHandler = new Handler(backgroundLooper); + mBgExecutor = bgExecutor; mAccessibilityManager = context.getSystemService(AccessibilityManager.class); Resources res = context.getResources(); @@ -83,31 +80,27 @@ public class TaskIconCache { * @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 callback) { + public CancellableTask updateIconInBackground(Task task, Consumer 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) { + CancellableTask request = new CancellableTask() { @Override - public void run() { - TaskCacheEntry entry = getCacheEntry(task); - if (isCanceled()) { - // We don't call back to the provided callback in this case - return; - } - MAIN_EXECUTOR.execute(() -> { - task.icon = entry.icon; - task.titleDescription = entry.contentDescription; - callback.accept(task); - onEnd(); - }); + public TaskCacheEntry getResultOnBg() { + return getCacheEntry(task); + } + + @Override + public void handleResult(TaskCacheEntry result) { + task.icon = result.icon; + task.titleDescription = result.contentDescription; + callback.accept(task); } }; - Utilities.postAsyncCallback(mBackgroundHandler, request); + mBgExecutor.execute(request); return request; } @@ -120,9 +113,8 @@ public class TaskIconCache { } void invalidateCacheEntries(String pkg, UserHandle handle) { - Utilities.postAsyncCallback(mBackgroundHandler, - () -> mIconCache.removeAll(key -> - pkg.equals(key.getPackageName()) && handle.getIdentifier() == key.userId)); + mBgExecutor.execute(() -> mIconCache.removeAll(key -> + pkg.equals(key.getPackageName()) && handle.getIdentifier() == key.userId)); } @WorkerThread @@ -208,12 +200,6 @@ public class TaskIconCache { } } - public static abstract class IconLoadRequest extends HandlerRunnable { - IconLoadRequest(Handler handler) { - super(handler, null); - } - } - private static class TaskCacheEntry { public Drawable icon; public String contentDescription = ""; diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java index 2b7a8ec250..a8a0219594 100644 --- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java +++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java @@ -15,17 +15,12 @@ */ package com.android.quickstep; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; - import android.content.Context; import android.content.res.Resources; -import android.os.Handler; -import android.os.Looper; import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.util.Preconditions; +import com.android.quickstep.util.CancellableTask; import com.android.quickstep.util.TaskKeyLruCache; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; @@ -33,11 +28,12 @@ import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; import java.util.ArrayList; +import java.util.concurrent.Executor; import java.util.function.Consumer; public class TaskThumbnailCache { - private final Handler mBackgroundHandler; + private final Executor mBgExecutor; private final int mCacheSize; private final TaskKeyLruCache mCache; @@ -94,8 +90,8 @@ public class TaskThumbnailCache { } } - public TaskThumbnailCache(Context context, Looper backgroundLooper) { - mBackgroundHandler = new Handler(backgroundLooper); + public TaskThumbnailCache(Context context, Executor bgExecutor) { + mBgExecutor = bgExecutor; mHighResLoadingState = new HighResLoadingState(context); Resources res = context.getResources(); @@ -130,7 +126,7 @@ public class TaskThumbnailCache { * @param callback The callback to receive the task after its data has been populated. * @return A cancelable handle to the request */ - public ThumbnailLoadRequest updateThumbnailInBackground( + public CancellableTask updateThumbnailInBackground( Task task, Consumer callback) { Preconditions.assertUIThread(); @@ -142,14 +138,13 @@ public class TaskThumbnailCache { return null; } - return updateThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), t -> { task.thumbnail = t; callback.accept(t); }); } - private ThumbnailLoadRequest updateThumbnailInBackground(TaskKey key, boolean lowResolution, + private CancellableTask updateThumbnailInBackground(TaskKey key, boolean lowResolution, Consumer callback) { Preconditions.assertUIThread(); @@ -160,26 +155,20 @@ public class TaskThumbnailCache { return null; } - ThumbnailLoadRequest request = new ThumbnailLoadRequest(mBackgroundHandler, - lowResolution) { + CancellableTask request = new CancellableTask() { @Override - public void run() { - ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail( + public ThumbnailData getResultOnBg() { + return ActivityManagerWrapper.getInstance().getTaskThumbnail( key.id, lowResolution); + } - MAIN_EXECUTOR.execute(() -> { - if (isCanceled()) { - // We don't call back to the provided callback in this case - return; - } - - mCache.put(key, thumbnail); - callback.accept(thumbnail); - onEnd(); - }); + @Override + public void handleResult(ThumbnailData result) { + mCache.put(key, result); + callback.accept(result); } }; - Utilities.postAsyncCallback(mBackgroundHandler, request); + mBgExecutor.execute(request); return request; } @@ -218,15 +207,6 @@ public class TaskThumbnailCache { return mEnableTaskSnapshotPreloading && mHighResLoadingState.mVisible; } - public static abstract class ThumbnailLoadRequest extends HandlerRunnable { - public final boolean mLowResolution; - - ThumbnailLoadRequest(Handler handler, boolean lowResolution) { - super(handler, null); - mLowResolution = lowResolution; - } - } - /** * @return Whether device supports low-res thumbnails. Low-res files are an optimization * for faster load times of snapshots. Devices can optionally disable low-res files so that diff --git a/quickstep/src/com/android/quickstep/util/CancellableTask.java b/quickstep/src/com/android/quickstep/util/CancellableTask.java new file mode 100644 index 0000000000..a6e2e81589 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/CancellableTask.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 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.util; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + +import androidx.annotation.UiThread; +import androidx.annotation.WorkerThread; + +/** + * Utility class to executore a task on background and post the result on UI thread + */ +public abstract class CancellableTask implements Runnable { + + private boolean mCancelled = false; + + @Override + public final void run() { + if (mCancelled) { + return; + } + T result = getResultOnBg(); + if (mCancelled) { + return; + } + MAIN_EXECUTOR.execute(() -> { + if (mCancelled) { + return; + } + handleResult(result); + }); + } + + /** + * Called on the worker thread to process the request. The return object is passed to + * {@link #handleResult(Object)} + */ + @WorkerThread + public abstract T getResultOnBg(); + + /** + * Called on the UI thread to handle the final result. + * @param result + */ + @UiThread + public abstract void handleResult(T result); + + /** + * Cancels the request. If it is called before {@link #handleResult(Object)}, that method + * will not be called + */ + public void cancel() { + mCancelled = true; + } +} diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 034d9fb7f7..8fbf44b572 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -86,6 +86,7 @@ import com.android.quickstep.TaskIconCache; import com.android.quickstep.TaskOverlayFactory; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; +import com.android.quickstep.util.CancellableTask; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.util.TaskCornerRadius; import com.android.quickstep.views.RecentsView.PageCallbacks; @@ -185,8 +186,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private boolean mShowScreenshot; // The current background requests to load the task thumbnail and icon - private TaskThumbnailCache.ThumbnailLoadRequest mThumbnailLoadRequest; - private TaskIconCache.IconLoadRequest mIconLoadRequest; + private CancellableTask mThumbnailLoadRequest; + private CancellableTask mIconLoadRequest; // Order in which the footers appear. Lower order appear below higher order. public static final int INDEX_DIGITAL_WELLBEING_TOAST = 0; diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java index 0a32734fca..a85ae458ed 100644 --- a/src/com/android/launcher3/util/Executors.java +++ b/src/com/android/launcher3/util/Executors.java @@ -20,8 +20,10 @@ import android.os.Looper; import android.os.Process; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; /** * Various different executors used in Launcher @@ -83,4 +85,29 @@ public class Executors { */ public static final LooperExecutor MODEL_EXECUTOR = new LooperExecutor(createAndStartNewLooper("launcher-loader")); + + /** + * A simple ThreadFactory to set the thread name and priority when used with executors. + */ + public static class SimpleThreadFactory implements ThreadFactory { + + private final int mPriority; + private final String mNamePrefix; + + private final AtomicInteger mCount = new AtomicInteger(0); + + public SimpleThreadFactory(String namePrefix, int priority) { + mNamePrefix = namePrefix; + mPriority = priority; + } + + @Override + public Thread newThread(Runnable runnable) { + Thread t = new Thread(() -> { + Process.setThreadPriority(mPriority); + runnable.run(); + }, mNamePrefix + mCount.incrementAndGet()); + return t; + } + } }