Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 10759887 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Fixing lock during RecentsModel initialization

HandlerThread.getLooper blocks the current thread until.
the final thread is initialized. Updating RecentsModel to
use an executor instead of a handler thread to avoid this.

Change-Id: I3b2e82a375ea65bea1526af4a32c6a2488102541
parent 011452a4
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -47,7 +47,6 @@ public class OverviewCommandHelper {

    private final Context mContext;
    private final RecentsAnimationDeviceState mDeviceState;
    private final RecentsModel mRecentsModel;
    private final OverviewComponentObserver mOverviewComponentObserver;

    private long mLastToggleTime;
@@ -56,7 +55,6 @@ public class OverviewCommandHelper {
            OverviewComponentObserver observer) {
        mContext = context;
        mDeviceState = deviceState;
        mRecentsModel = RecentsModel.INSTANCE.get(mContext);
        mOverviewComponentObserver = observer;
    }

@@ -160,7 +158,7 @@ public class OverviewCommandHelper {
                    ActivityManagerWrapper.getInstance().getRunningTask(), mDeviceState);

            // Preload the plan
            mRecentsModel.getTasks(null);
            RecentsModel.INSTANCE.get(mContext).getTasks(null);
        }

        @Override
+8 −6
Original line number Diff line number Diff line
@@ -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<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 @@ 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,
+18 −32
Original line number Diff line number Diff line
@@ -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<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 @@ 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<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;
        }
        CancellableTask<TaskCacheEntry> request = new CancellableTask<TaskCacheEntry>() {
            @Override
            public TaskCacheEntry getResultOnBg() {
                return getCacheEntry(task);
            }

        IconLoadRequest request = new IconLoadRequest(mBackgroundHandler) {
            @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;
            public void handleResult(TaskCacheEntry result) {
                task.icon = result.icon;
                task.titleDescription = result.contentDescription;
                callback.accept(task);
                    onEnd();
                });
            }
        };
        Utilities.postAsyncCallback(mBackgroundHandler, request);
        mBgExecutor.execute(request);
        return request;
    }

@@ -120,8 +113,7 @@ public class TaskIconCache {
    }

    void invalidateCacheEntries(String pkg, UserHandle handle) {
        Utilities.postAsyncCallback(mBackgroundHandler,
                () -> mIconCache.removeAll(key ->
        mBgExecutor.execute(() -> mIconCache.removeAll(key ->
                pkg.equals(key.getPackageName()) && handle.getIdentifier() == key.userId));
    }

@@ -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 = "";
+16 −36
Original line number Diff line number Diff line
@@ -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<ThumbnailData> 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<ThumbnailData> 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<ThumbnailData> callback) {
        Preconditions.assertUIThread();

@@ -160,26 +155,20 @@ public class TaskThumbnailCache {
            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 @@ 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
+68 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
Loading