Merge "Fixing lock during RecentsModel initialization" into ub-launcher3-master
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 2b10989..b1b9396 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -46,7 +46,6 @@
 
     private final Context mContext;
     private final RecentsAnimationDeviceState mDeviceState;
-    private final RecentsModel mRecentsModel;
     private final OverviewComponentObserver mOverviewComponentObserver;
 
     private long mLastToggleTime;
@@ -55,7 +54,6 @@
             OverviewComponentObserver observer) {
         mContext = context;
         mDeviceState = deviceState;
-        mRecentsModel = RecentsModel.INSTANCE.get(mContext);
         mOverviewComponentObserver = observer;
     }
 
@@ -158,7 +156,7 @@
                     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 6c302ae..d47217b 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -18,7 +18,6 @@
 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.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 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 static final MainThreadInitializedObject<RecentsModel> INSTANCE =
             new MainThreadInitializedObject<>(RecentsModel::new);
 
+    private static final Executor RECENTS_MODEL_EXECUTOR = Executors.newSingleThreadExecutor(
+            new SimpleThreadFactory("TaskThumbnailIconCache-", THREAD_PRIORITY_BACKGROUND));
+
     private final List<TaskVisualsChangeListener> mThumbnailChangeListeners = new ArrayList<>();
     private final Context mContext;
 
@@ -61,12 +65,10 @@
 
     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 7ff799e..65e89cf 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -17,7 +17,6 @@
 
 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.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 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.PackageManagerWrapper;
 import com.android.systemui.shared.system.TaskDescriptionCompat;
 
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -57,7 +54,7 @@
  */
 public class TaskIconCache {
 
-    private final Handler mBackgroundHandler;
+    private final Executor mBgExecutor;
     private final AccessibilityManager mAccessibilityManager;
 
     private final Context mContext;
@@ -65,9 +62,9 @@
     private final SparseArray<BitmapInfo> 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 @@
      * @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) {
+    public CancellableTask 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) {
+        CancellableTask<TaskCacheEntry> request = new CancellableTask<TaskCacheEntry>() {
             @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 @@
     }
 
     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 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 2b7a8ec..a8a0219 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.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<ThumbnailData> mCache;
@@ -94,8 +90,8 @@
         }
     }
 
-    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 @@
      * @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<ThumbnailData> callback) {
         Preconditions.assertUIThread();
 
@@ -142,14 +138,13 @@
             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<ThumbnailData> callback) {
         Preconditions.assertUIThread();
 
@@ -160,26 +155,20 @@
             return null;
         }
 
-        ThumbnailLoadRequest request = new ThumbnailLoadRequest(mBackgroundHandler,
-                lowResolution) {
+        CancellableTask<ThumbnailData> request = new CancellableTask<ThumbnailData>() {
             @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 @@
         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 0000000..a6e2e81
--- /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<T> 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 034d9fb..8fbf44b 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.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 @@
     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 0a32734..a85ae45 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -20,8 +20,10 @@
 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 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;
+        }
+    }
 }