Merge "Removing tracing for a fixed bug" into ub-launcher3-master
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
index 245561e..d2b9c5e 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
@@ -18,32 +18,45 @@
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 public class BitmapInfo {
 
     public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
+    public static final BitmapInfo LOW_RES_INFO = fromBitmap(LOW_RES_ICON, null);
 
-    public Bitmap icon;
-    public int color;
+    public final Bitmap icon;
+    public final int color;
 
-    public void applyTo(BitmapInfo info) {
-        info.icon = icon;
-        info.color = color;
+    public BitmapInfo(Bitmap icon, int color) {
+        this.icon = icon;
+        this.color = color;
+    }
+
+    /**
+     * Ideally icon should not be null, except in cases when generating hardware bitmap failed
+     */
+    public final boolean isNullOrLowRes() {
+        return icon == null || icon == LOW_RES_ICON;
     }
 
     public final boolean isLowRes() {
         return LOW_RES_ICON == icon;
     }
 
-    public static BitmapInfo fromBitmap(Bitmap bitmap) {
-        return fromBitmap(bitmap, null);
+    public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap) {
+        return of(bitmap, 0);
     }
 
-    public static BitmapInfo fromBitmap(Bitmap bitmap, ColorExtractor dominantColorExtractor) {
-        BitmapInfo info = new BitmapInfo();
-        info.icon = bitmap;
-        info.color = dominantColorExtractor != null
+    public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap,
+            @Nullable ColorExtractor dominantColorExtractor) {
+        return of(bitmap, dominantColorExtractor != null
                 ? dominantColorExtractor.findDominantColorByHue(bitmap)
-                : 0;
-        return info;
+                : 0);
+    }
+
+    public static BitmapInfo of(@NonNull Bitmap bitmap, int color) {
+        return new BitmapInfo(bitmap, color);
     }
 }
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
index 93f0538..8bae94c 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
@@ -71,7 +71,10 @@
     // Empty class name is used for storing package default entry.
     public static final String EMPTY_CLASS_NAME = ".";
 
-    public static class CacheEntry extends BitmapInfo {
+    public static class CacheEntry {
+
+        @NonNull
+        public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;
         public CharSequence title = "";
         public CharSequence contentDescription = "";
     }
@@ -259,23 +262,23 @@
         if (!replaceExisting) {
             entry = mCache.get(key);
             // We can't reuse the entry if the high-res icon is not present.
-            if (entry == null || entry.icon == null || entry.isLowRes()) {
+            if (entry == null || entry.bitmap.isNullOrLowRes()) {
                 entry = null;
             }
         }
         if (entry == null) {
             entry = new CacheEntry();
-            cachingLogic.loadIcon(mContext, object, entry);
+            entry.bitmap = cachingLogic.loadIcon(mContext, object);
         }
         // Icon can't be loaded from cachingLogic, which implies alternative icon was loaded
         // (e.g. fallback icon, default icon). So we drop here since there's no point in caching
         // an empty entry.
-        if (entry.icon == null) return;
+        if (entry.bitmap.isNullOrLowRes()) return;
         entry.title = cachingLogic.getLabel(object);
         entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
         if (cachingLogic.addToMemCache()) mCache.put(key, entry);
 
-        ContentValues values = newContentValues(entry, entry.title.toString(),
+        ContentValues values = newContentValues(entry.bitmap, entry.title.toString(),
                 componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList));
         addIconToDB(values, componentName, info, userSerial);
     }
@@ -300,8 +303,8 @@
         return mDefaultIcons.get(user);
     }
 
-    public boolean isDefaultIcon(Bitmap icon, UserHandle user) {
-        return getDefaultIcon(user).icon == icon;
+    public boolean isDefaultIcon(BitmapInfo icon, UserHandle user) {
+        return getDefaultIcon(user).icon == icon.icon;
     }
 
     /**
@@ -315,7 +318,7 @@
         assertWorkerThread();
         ComponentKey cacheKey = new ComponentKey(componentName, user);
         CacheEntry entry = mCache.get(cacheKey);
-        if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
+        if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) {
             entry = new CacheEntry();
             if (cachingLogic.addToMemCache()) {
                 mCache.put(cacheKey, entry);
@@ -330,7 +333,7 @@
                 providerFetchedOnce = true;
 
                 if (object != null) {
-                    cachingLogic.loadIcon(mContext, object, entry);
+                    entry.bitmap = cachingLogic.loadIcon(mContext, object);
                 } else {
                     if (usePackageIcon) {
                         CacheEntry packageEntry = getEntryForPackageLocked(
@@ -338,15 +341,15 @@
                         if (packageEntry != null) {
                             if (DEBUG) Log.d(TAG, "using package default icon for " +
                                     componentName.toShortString());
-                            packageEntry.applyTo(entry);
+                            entry.bitmap = packageEntry.bitmap;
                             entry.title = packageEntry.title;
                             entry.contentDescription = packageEntry.contentDescription;
                         }
                     }
-                    if (entry.icon == null) {
+                    if (entry.bitmap == null) {
                         if (DEBUG) Log.d(TAG, "using default icon for " +
                                 componentName.toShortString());
-                        getDefaultIcon(user).applyTo(entry);
+                        entry.bitmap = getDefaultIcon(user);
                     }
                 }
             }
@@ -390,10 +393,10 @@
         }
         if (icon != null) {
             BaseIconFactory li = getIconFactory();
-            li.createIconBitmap(icon).applyTo(entry);
+            entry.bitmap = li.createIconBitmap(icon);
             li.close();
         }
-        if (!TextUtils.isEmpty(title) && entry.icon != null) {
+        if (!TextUtils.isEmpty(title) && entry.bitmap.icon != null) {
             mCache.put(cacheKey, entry);
         }
     }
@@ -413,7 +416,7 @@
         ComponentKey cacheKey = getPackageKey(packageName, user);
         CacheEntry entry = mCache.get(cacheKey);
 
-        if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
+        if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) {
             entry = new CacheEntry();
             boolean entryUpdated = true;
 
@@ -438,8 +441,8 @@
 
                     entry.title = appInfo.loadLabel(mPackageManager);
                     entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
-                    entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon;
-                    entry.color = iconInfo.color;
+                    entry.bitmap = BitmapInfo.of(
+                            useLowResIcon ? LOW_RES_ICON : iconInfo.icon, iconInfo.color);
 
                     // Add the icon in the DB here, since these do not get written during
                     // package updates.
@@ -472,7 +475,7 @@
                             Long.toString(getSerialNumberForUser(cacheKey.user))});
             if (c.moveToNext()) {
                 // Set the alpha to be 255, so that we never have a wrong color
-                entry.color = setColorAlphaBound(c.getInt(0), 255);
+                entry.bitmap = BitmapInfo.of(LOW_RES_ICON, setColorAlphaBound(c.getInt(0), 255));
                 entry.title = c.getString(1);
                 if (entry.title == null) {
                     entry.title = "";
@@ -482,13 +485,12 @@
                             entry.title, cacheKey.user);
                 }
 
-                if (lowRes) {
-                    entry.icon = LOW_RES_ICON;
-                } else {
+                if (!lowRes) {
                     byte[] data = c.getBlob(2);
                     try {
-                        entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length,
-                                mDecodeOptions);
+                        entry.bitmap = BitmapInfo.of(
+                                BitmapFactory.decodeByteArray(data, 0, data.length, mDecodeOptions),
+                                entry.bitmap.color);
                     } catch (Exception e) { }
                 }
                 return true;
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
index e40a9c2..ea1ca53 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
@@ -20,6 +20,7 @@
 import android.os.LocaleList;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.icons.BitmapInfo;
@@ -32,7 +33,8 @@
 
     CharSequence getLabel(T object);
 
-    void loadIcon(Context context, T object, BitmapInfo target);
+    @NonNull
+    BitmapInfo loadIcon(Context context, T object);
 
     /**
      * Provides a option list of keywords to associate with this object
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
index 65e69b6..06580b9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
@@ -170,7 +170,7 @@
         if (!details.isEmpty()) {
             WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext);
             try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
-                si.applyFrom(li.createShortcutIcon(details.get(0), true /* badged */, null));
+                si.bitmap = li.createShortcutIcon(details.get(0), true /* badged */, null);
             } catch (Exception e) {
                 if (DEBUG) {
                     Log.e(TAG, "Error loading shortcut icon for " + shortcutKey.toString());
@@ -209,7 +209,7 @@
         InstantAppItemInfo info = new InstantAppItemInfo(intent, pkgName);
         IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
         iconCache.getTitleAndIcon(info, false);
-        if (info.iconBitmap == null || iconCache.isDefaultIcon(info.iconBitmap, info.user)) {
+        if (info.bitmap.icon == null || iconCache.isDefaultIcon(info.bitmap, info.user)) {
             return null;
         }
         return info;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 43cdbdb..4e73a79 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -18,6 +18,8 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.util.NavBarPosition.ROTATION_LANDSCAPE;
+import static com.android.quickstep.util.NavBarPosition.ROTATION_SEASCAPE;
 
 import android.content.Context;
 import android.content.res.Configuration;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 1d00825..4f50e33 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
@@ -25,7 +24,6 @@
 
 import android.animation.Animator;
 import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Point;
@@ -33,8 +31,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -52,7 +48,6 @@
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
-import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AppWindowAnimationHelper;
@@ -93,10 +88,8 @@
     protected final Context mContext;
     protected final RecentsAnimationDeviceState mDeviceState;
     protected final GestureState mGestureState;
-    protected final OverviewComponentObserver mOverviewComponentObserver;
     protected final BaseActivityInterface<T> mActivityInterface;
-    protected final RecentsModel mRecentsModel;
-    protected final int mRunningTaskId;
+    protected final InputConsumerController mInputConsumer;
 
     protected final AppWindowAnimationHelper mAppWindowAnimationHelper;
     protected final TransformParams mTransformParams = new TransformParams();
@@ -108,7 +101,6 @@
     protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
 
     protected final ActivityInitListener mActivityInitListener;
-    protected final InputConsumerController mInputConsumer;
 
     protected RecentsAnimationController mRecentsAnimationController;
     protected RecentsAnimationTargets mRecentsAnimationTargets;
@@ -129,21 +121,18 @@
     protected int mFinishingRecentsAnimationForNewTaskId = -1;
 
     protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
-            GestureState gestureState, OverviewComponentObserver overviewComponentObserver,
-            RecentsModel recentsModel, InputConsumerController inputConsumer, int runningTaskId) {
+            GestureState gestureState, InputConsumerController inputConsumer) {
         mContext = context;
         mDeviceState = deviceState;
         mGestureState = gestureState;
-        mOverviewComponentObserver = overviewComponentObserver;
         mActivityInterface = gestureState.getActivityInterface();
-        mRecentsModel = recentsModel;
         mActivityInitListener =
                 mActivityInterface.createActivityInitListener(this::onActivityInit);
-        mRunningTaskId = runningTaskId;
         mInputConsumer = inputConsumer;
 
         mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
         mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
+
         initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
                 .getDeviceProfile(mContext));
     }
@@ -261,7 +250,8 @@
         mRecentsAnimationTargets = targets;
         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
         final Rect overviewStackBounds;
-        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mRunningTaskId);
+        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
+                mGestureState.getRunningTaskId());
 
         if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
             overviewStackBounds = mActivityInterface
@@ -373,7 +363,7 @@
 
     public void initWhenReady() {
         // Preload the plan
-        mRecentsModel.getTasks(null);
+        RecentsModel.INSTANCE.get(mContext).getTasks(null);
 
         mActivityInitListener.register();
     }
@@ -490,8 +480,8 @@
 
     public interface Factory {
 
-        BaseSwipeUpHandler newHandler(GestureState gestureState, RunningTaskInfo runningTask,
-                long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask);
+        BaseSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs,
+                boolean continuingLastGesture, boolean isLikelyToStartNewTask);
     }
 
     protected interface RunningWindowAnim {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index b2626e5..a8974e5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -248,7 +248,6 @@
             this::createFallbackNoButtonSwipeHandler;
 
     private ActivityManagerWrapper mAM;
-    private RecentsModel mRecentsModel;
     private OverviewCommandHelper mOverviewCommandHelper;
     private OverviewComponentObserver mOverviewComponentObserver;
     private InputConsumerController mInputConsumer;
@@ -327,7 +326,6 @@
     @UiThread
     public void onUserUnlocked() {
         mTaskAnimationManager = new TaskAnimationManager();
-        mRecentsModel = RecentsModel.INSTANCE.get(this);
         mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
         mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
                 mOverviewComponentObserver);
@@ -431,9 +429,10 @@
                 TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
         MotionEvent event = (MotionEvent) ev;
         if (event.getAction() == ACTION_DOWN) {
-            GestureState newGestureState = new GestureState(
-                    mOverviewComponentObserver.getActivityInterface(),
+            GestureState newGestureState = new GestureState(mOverviewComponentObserver,
                     ActiveGestureLog.INSTANCE.generateAndSetLogId());
+            newGestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
+                    () -> mAM.getRunningTask(0)));
 
             if (mDeviceState.isInSwipeUpTouchRegion(event)) {
                 mConsumer.onConsumerAboutToBeSwitched();
@@ -470,8 +469,7 @@
             if (canStartSystemGesture) {
                 // This handles apps launched in direct boot mode (e.g. dialer) as well as apps
                 // launched while device is locked even after exiting direct boot mode (e.g. camera).
-                return createDeviceLockedInputConsumer(newGestureState,
-                        mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
+                return createDeviceLockedInputConsumer(newGestureState);
             } else {
                 return mResetGestureInputConsumer;
             }
@@ -514,25 +512,22 @@
 
     private InputConsumer newBaseConsumer(GestureState previousGestureState,
             GestureState gestureState, MotionEvent event) {
-        RunningTaskInfo runningTaskInfo = TraceHelper.whitelistIpcs("getRunningTask.0",
-                () -> mAM.getRunningTask(0));
         if (mDeviceState.isKeyguardShowingOccluded()) {
             // This handles apps showing over the lockscreen (e.g. camera)
-            return createDeviceLockedInputConsumer(gestureState, runningTaskInfo);
+            return createDeviceLockedInputConsumer(gestureState);
         }
 
         boolean forceOverviewInputConsumer = false;
-        if (isExcludedAssistant(runningTaskInfo)) {
+        if (isExcludedAssistant(gestureState.getRunningTask())) {
             // In the case where we are in the excluded assistant state, ignore it and treat the
             // running activity as the task behind the assistant
-
-            runningTaskInfo = TraceHelper.whitelistIpcs("getRunningTask.assistant",
-                    () -> mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
-            if (!ActivityManagerWrapper.isHomeTask(runningTaskInfo)) {
+            gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.assistant",
+                    () -> mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT)));
+            if (!ActivityManagerWrapper.isHomeTask(gestureState.getRunningTask())) {
                 final ComponentName homeComponent =
                         mOverviewComponentObserver.getHomeIntent().getComponent();
-                forceOverviewInputConsumer =
-                        runningTaskInfo.baseIntent.getComponent().equals(homeComponent);
+                forceOverviewInputConsumer = gestureState.getRunningTask()
+                        .baseIntent.getComponent().equals(homeComponent);
             }
         }
 
@@ -541,9 +536,9 @@
             // consumer but with the next task as the running task
             RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
             info.id = previousGestureState.getFinishingRecentsAnimationTaskId();
-            return createOtherActivityInputConsumer(previousGestureState, gestureState, event,
-                    info);
-        } else if (runningTaskInfo == null) {
+            gestureState.updateRunningTask(info);
+            return createOtherActivityInputConsumer(previousGestureState, gestureState, event);
+        } else if (gestureState.getRunningTask() == null) {
             return mResetGestureInputConsumer;
         } else if (previousGestureState.isRunningAnimationToLauncher()
                 || gestureState.getActivityInterface().isResumed()
@@ -552,11 +547,10 @@
         } else if (ENABLE_QUICKSTEP_LIVE_TILE.get()
                 && gestureState.getActivityInterface().isInLiveTileMode()) {
             return createOverviewInputConsumer(previousGestureState, gestureState, event);
-        } else if (mDeviceState.isGestureBlockedActivity(runningTaskInfo)) {
+        } else if (mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())) {
             return mResetGestureInputConsumer;
         } else {
-            return createOtherActivityInputConsumer(previousGestureState, gestureState, event,
-                    runningTaskInfo);
+            return createOtherActivityInputConsumer(previousGestureState, gestureState, event);
         }
     }
 
@@ -567,8 +561,7 @@
     }
 
     private InputConsumer createOtherActivityInputConsumer(GestureState previousGestureState,
-            GestureState gestureState,
-            MotionEvent event, RunningTaskInfo runningTaskInfo) {
+            GestureState gestureState, MotionEvent event) {
 
         final boolean shouldDefer;
         final BaseSwipeUpHandler.Factory factory;
@@ -585,15 +578,14 @@
 
         final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
         return new OtherActivityInputConsumer(this, mDeviceState, mTaskAnimationManager,
-                gestureState, runningTaskInfo, shouldDefer, this::onConsumerInactive,
+                gestureState, shouldDefer, this::onConsumerInactive,
                 mInputMonitorCompat, disableHorizontalSwipe, factory);
     }
 
-    private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState,
-            RunningTaskInfo taskInfo) {
-        if (mDeviceState.isFullyGesturalNavMode() && taskInfo != null) {
+    private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState) {
+        if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) {
             return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager,
-                    gestureState, mInputMonitorCompat, taskInfo.taskId);
+                    gestureState, mInputMonitorCompat);
         } else {
             return mResetGestureInputConsumer;
         }
@@ -612,7 +604,7 @@
                     false /* startingInActivityBounds */);
         } else {
             final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
-            return new OverviewWithoutFocusInputConsumer(activity, gestureState,
+            return new OverviewWithoutFocusInputConsumer(activity, mDeviceState, gestureState,
                     mInputMonitorCompat, disableHorizontalSwipe);
         }
     }
@@ -727,19 +719,15 @@
     }
 
     private BaseSwipeUpHandler createWindowTransformSwipeHandler(GestureState gestureState,
-            RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture,
-            boolean isLikelyToStartNewTask) {
+            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
         return  new WindowTransformSwipeHandler(this, mDeviceState, mTaskAnimationManager,
-                gestureState, runningTask, touchTimeMs, mOverviewComponentObserver,
-                continuingLastGesture, mInputConsumer, mRecentsModel);
+                gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
     }
 
     private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(GestureState gestureState,
-            RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture,
-            boolean isLikelyToStartNewTask) {
+            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
         return new FallbackNoButtonInputConsumer(this, mDeviceState, gestureState,
-                mOverviewComponentObserver, runningTask, mRecentsModel, mInputConsumer,
-                isLikelyToStartNewTask, continuingLastGesture);
+                mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
     }
 
     protected boolean shouldNotifyBackGesture() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 065f3eb..b55ec20 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -39,7 +39,6 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.PointF;
@@ -192,11 +191,9 @@
 
     public WindowTransformSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
-            RunningTaskInfo runningTaskInfo, long touchTimeMs,
-            OverviewComponentObserver overviewComponentObserver, boolean continuingLastGesture,
-            InputConsumerController inputConsumer, RecentsModel recentsModel) {
-        super(context, deviceState, gestureState, overviewComponentObserver, recentsModel,
-                inputConsumer, runningTaskInfo.id);
+            long touchTimeMs, boolean continuingLastGesture,
+            InputConsumerController inputConsumer) {
+        super(context, deviceState, gestureState, inputConsumer);
         mTaskAnimationManager = taskAnimationManager;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
@@ -290,9 +287,9 @@
 
         mStateCallback.setState(STATE_LAUNCHER_PRESENT);
         if (alreadyOnHome) {
-            onLauncherStart(activity);
+            onLauncherStart();
         } else {
-            activity.setOnStartCallback(this::onLauncherStart);
+            activity.runOnceOnStart(this::onLauncherStart);
         }
 
         setupRecentsViewUi();
@@ -304,7 +301,8 @@
         return mGestureState.getEndTarget() != HOME;
     }
 
-    private void onLauncherStart(final T activity) {
+    private void onLauncherStart() {
+        final T activity = mActivityInterface.getCreatedActivity();
         if (mActivity != activity) {
             return;
         }
@@ -379,7 +377,7 @@
 
     private void onDeferredActivityLaunch() {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mOverviewComponentObserver.getActivityInterface().switchRunningTaskViewToScreenshot(
+            mActivityInterface.switchRunningTaskViewToScreenshot(
                     null, () -> {
                         mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
                     });
@@ -393,7 +391,7 @@
             updateSysUiFlags(mCurrentShift.value);
             return;
         }
-        mRecentsView.onGestureAnimationStart(mRunningTaskId);
+        mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
     }
 
     private void launcherFrameDrawn() {
@@ -442,9 +440,9 @@
         if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
             return;
         }
-        RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets == null
-                ? null
-                : mRecentsAnimationTargets.findTask(mRunningTaskId);
+        RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
+                ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
+                : null;
         final boolean recentsAttachedToAppWindow;
         if (mGestureState.getEndTarget() != null) {
             recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
@@ -521,7 +519,7 @@
 
     @Override
     public Intent getLaunchIntent() {
-        return mOverviewComponentObserver.getOverviewIntent();
+        return mGestureState.getOverviewIntent();
     }
 
     @Override
@@ -1005,7 +1003,9 @@
     @Override
     public void onConsumerAboutToBeSwitched() {
         if (mActivity != null) {
-            mActivity.setOnStartCallback(null);
+            // In the off chance that the gesture ends before Launcher is started, we should clear
+            // the callback here so that it doesn't update with the wrong state
+            mActivity.clearRunOnceOnStartCallback();
         }
         if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
             cancelCurrentAnimation();
@@ -1114,13 +1114,14 @@
     }
 
     private void switchToScreenshot() {
+        final int runningTaskId = mGestureState.getRunningTaskId();
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (mRecentsAnimationController != null) {
                 // Update the screenshot of the task
                 if (mTaskSnapshot == null) {
-                    mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId);
+                    mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
                 }
-                mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot, false /* refreshNow */);
+                mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, false /* refreshNow */);
             }
             mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
         } else if (!hasTargets()) {
@@ -1131,7 +1132,7 @@
             if (mRecentsAnimationController != null) {
                 // Update the screenshot of the task
                 if (mTaskSnapshot == null) {
-                    mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId);
+                    mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
                 }
                 final TaskView taskView;
                 if (mGestureState.getEndTarget() == HOME) {
@@ -1139,7 +1140,7 @@
                     // taken in the correct orientation, but no need to update the thumbnail.
                     taskView = null;
                 } else {
-                    taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot);
+                    taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot);
                 }
                 if (taskView != null && !mCanceled) {
                     // Defer finishing the animation until the next launcher frame with the
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 370f161..e448c5b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -85,7 +85,6 @@
     private final AppWindowAnimationHelper.TransformParams mTransformParams;
     private final Point mDisplaySize;
     private final MultiStateCallback mStateCallback;
-    public final int mRunningTaskId;
 
     private VelocityTracker mVelocityTracker;
     private float mProgress;
@@ -97,7 +96,7 @@
 
     public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
-            InputMonitorCompat inputMonitorCompat, int runningTaskId) {
+            InputMonitorCompat inputMonitorCompat) {
         mContext = context;
         mDeviceState = deviceState;
         mTaskAnimationManager = taskAnimationManager;
@@ -106,7 +105,6 @@
         mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
         mTransformParams = new AppWindowAnimationHelper.TransformParams();
         mInputMonitorCompat = inputMonitorCompat;
-        mRunningTaskId = runningTaskId;
 
         // Do not use DeviceProfile as the user data might be locked
         mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize;
@@ -221,7 +219,8 @@
         mRecentsAnimationTargets = targets;
 
         Rect displaySize = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
-        RemoteAnimationTargetCompat targetCompat = targets.findTask(mRunningTaskId);
+        RemoteAnimationTargetCompat targetCompat = targets.findTask(
+                mGestureState.getRunningTaskId());
         if (targetCompat != null) {
             mAppWindowAnimationHelper.updateSource(displaySize, targetCompat);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
index 7b24bd9..b6cd456 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
@@ -27,7 +27,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
-import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
@@ -47,11 +46,9 @@
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.MultiStateCallback;
-import com.android.quickstep.OverviewComponentObserver;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationDeviceState;
-import com.android.quickstep.RecentsModel;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.RecentsAnimationTargets;
@@ -108,24 +105,18 @@
     private final boolean mRunningOverHome;
     private final boolean mSwipeUpOverHome;
 
-    private final RunningTaskInfo mRunningTaskInfo;
-
     private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
     private RunningWindowAnim mFinishAnimation;
 
     public FallbackNoButtonInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
-            GestureState gestureState, OverviewComponentObserver overviewComponentObserver,
-            RunningTaskInfo runningTaskInfo, RecentsModel recentsModel,
-            InputConsumerController inputConsumer,
+            GestureState gestureState, InputConsumerController inputConsumer,
             boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
-        super(context, deviceState, gestureState, overviewComponentObserver, recentsModel,
-                inputConsumer, runningTaskInfo.id);
+        super(context, deviceState, gestureState, inputConsumer);
         mLauncherAlpha.value = 1;
 
-        mRunningTaskInfo = runningTaskInfo;
         mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
         mContinuingLastGesture = continuingLastGesture;
-        mRunningOverHome = ActivityManagerWrapper.isHomeTask(runningTaskInfo);
+        mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
         mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;
 
         if (mSwipeUpOverHome) {
@@ -182,9 +173,9 @@
 
         if (!mContinuingLastGesture) {
             if (mRunningOverHome) {
-                mRecentsView.onGestureAnimationStart(mRunningTaskInfo);
+                mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
             } else {
-                mRecentsView.onGestureAnimationStart(mRunningTaskId);
+                mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
             }
         }
         mStateCallback.setStateOnUiThread(STATE_RECENTS_PRESENT);
@@ -230,9 +221,9 @@
     @Override
     public Intent getLaunchIntent() {
         if (mInQuickSwitchMode || mSwipeUpOverHome) {
-            return mOverviewComponentObserver.getOverviewIntent();
+            return mGestureState.getOverviewIntent();
         } else {
-            return mOverviewComponentObserver.getHomeIntent();
+            return mGestureState.getHomeIntent();
         }
     }
 
@@ -329,7 +320,7 @@
                 if (mSwipeUpOverHome) {
                     mRecentsAnimationController.finish(false, null, false);
                     // Send a home intent to clear the task stack
-                    mContext.startActivity(mOverviewComponentObserver.getHomeIntent());
+                    mContext.startActivity(mGestureState.getHomeIntent());
                 } else {
                     mRecentsAnimationController.finish(true, null, true);
                 }
@@ -344,7 +335,8 @@
                     break;
                 }
 
-                ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(mRunningTaskId);
+                final int runningTaskId = mGestureState.getRunningTaskId();
+                ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(runningTaskId);
                 mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
                         false /* screenshot */);
 
@@ -353,9 +345,9 @@
 
                 Bundle extras = new Bundle();
                 extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail));
-                extras.putInt(EXTRA_TASK_ID, mRunningTaskId);
+                extras.putInt(EXTRA_TASK_ID, runningTaskId);
 
-                Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent())
+                Intent intent = new Intent(mGestureState.getOverviewIntent())
                         .putExtras(extras);
                 mContext.startActivity(intent, options.toBundle());
                 mRecentsAnimationController.cleanupScreenshot();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index aeab4b5..bf2128d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -30,7 +30,6 @@
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
@@ -58,7 +57,6 @@
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
@@ -81,14 +79,11 @@
     private final GestureState mGestureState;
     private RecentsAnimationCallbacks mActiveCallbacks;
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
-    private final RunningTaskInfo mRunningTask;
     private final InputMonitorCompat mInputMonitorCompat;
     private final BaseActivityInterface mActivityInterface;
 
     private final BaseSwipeUpHandler.Factory mHandlerFactory;
 
-    private final NavBarPosition mNavBarPosition;
-
     private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
     private final MotionPauseDetector mMotionPauseDetector;
     private final float mMotionPauseMinDisplacement;
@@ -124,8 +119,7 @@
 
     public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
-            RunningTaskInfo runningTaskInfo, boolean isDeferredDownTarget,
-            Consumer<OtherActivityInputConsumer> onCompleteCallback,
+            boolean isDeferredDownTarget, Consumer<OtherActivityInputConsumer> onCompleteCallback,
             InputMonitorCompat inputMonitorCompat, boolean disableHorizontalSwipe,
             Factory handlerFactory) {
         super(base);
@@ -133,7 +127,6 @@
         mTaskAnimationManager = taskAnimationManager;
         mGestureState = gestureState;
         mMainThreadHandler = new Handler(Looper.getMainLooper());
-        mRunningTask = runningTaskInfo;
         mHandlerFactory = handlerFactory;
         mActivityInterface = mGestureState.getActivityInterface();
 
@@ -146,8 +139,6 @@
 
         boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning();
         mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
-
-        mNavBarPosition = new NavBarPosition(base);
         mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
 
         float slop = QUICKSTEP_TOUCH_SLOP_RATIO * mTouchSlop;
@@ -179,7 +170,7 @@
         if (mPassedWindowMoveSlop && mInteractionHandler != null
                 && !mRecentsViewDispatcher.hasConsumer()) {
             mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher(
-                    mNavBarPosition.getRotationMode()));
+                    mDeviceState.getNavBarPosition().getRotationMode()));
         }
         int edgeFlags = ev.getEdgeFlags();
         ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
@@ -325,7 +316,7 @@
             long touchTimeMs, boolean isLikelyToStartNewTask) {
         ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
 
-        mInteractionHandler = mHandlerFactory.newHandler(mGestureState, mRunningTask, touchTimeMs,
+        mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs,
                 mTaskAnimationManager.isRecentsAnimationRunning(), isLikelyToStartNewTask);
         mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
         mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler::onMotionPauseChanged);
@@ -360,8 +351,10 @@
                         ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
                 float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
                 float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
-                float velocity = mNavBarPosition.isRightEdge() ? velocityX
-                        : mNavBarPosition.isLeftEdge() ? -velocityX
+                float velocity = mDeviceState.getNavBarPosition().isRightEdge()
+                        ? velocityX
+                        : mDeviceState.getNavBarPosition().isLeftEdge()
+                                ? -velocityX
                                 : velocityY;
 
                 mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
@@ -414,9 +407,9 @@
     }
 
     private float getDisplacement(MotionEvent ev) {
-        if (mNavBarPosition.isRightEdge()) {
+        if (mDeviceState.getNavBarPosition().isRightEdge()) {
             return ev.getX() - mDownPos.x;
-        } else if (mNavBarPosition.isLeftEdge()) {
+        } else if (mDeviceState.getNavBarPosition().isLeftEdge()) {
             return mDownPos.x - ev.getX();
         } else {
             return ev.getY() - mDownPos.y;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 50069ea..d700a37 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -35,36 +35,35 @@
 import com.android.launcher3.logging.StatsLogUtils;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.GestureState;
+import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 public class OverviewWithoutFocusInputConsumer implements InputConsumer {
 
+    private final Context mContext;
+    private final RecentsAnimationDeviceState mDeviceState;
+    private final GestureState mGestureState;
     private final InputMonitorCompat mInputMonitor;
     private final boolean mDisableHorizontalSwipe;
     private final PointF mDownPos = new PointF();
     private final float mSquaredTouchSlop;
-    private final Context mContext;
-    private final NavBarPosition mNavBarPosition;
-    private final BaseActivityInterface mActivityInterface;
 
     private boolean mInterceptedTouch;
     private VelocityTracker mVelocityTracker;
 
-    public OverviewWithoutFocusInputConsumer(Context context, GestureState gestureState,
+    public OverviewWithoutFocusInputConsumer(Context context,
+            RecentsAnimationDeviceState deviceState, GestureState gestureState,
             InputMonitorCompat inputMonitor, boolean disableHorizontalSwipe) {
+        mContext = context;
+        mDeviceState = deviceState;
+        mGestureState = gestureState;
         mInputMonitor = inputMonitor;
         mDisableHorizontalSwipe = disableHorizontalSwipe;
-        mContext = context;
-        mActivityInterface = gestureState.getActivityInterface();
         mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
-        mNavBarPosition = new NavBarPosition(context);
-
         mVelocityTracker = VelocityTracker.obtain();
     }
 
@@ -135,8 +134,11 @@
         mVelocityTracker.computeCurrentVelocity(100);
         float velocityX = mVelocityTracker.getXVelocity();
         float velocityY = mVelocityTracker.getYVelocity();
-        float velocity = mNavBarPosition.isRightEdge()
-                ? -velocityX : (mNavBarPosition.isLeftEdge() ? velocityX : -velocityY);
+        float velocity = mDeviceState.getNavBarPosition().isRightEdge()
+                ? -velocityX
+                : mDeviceState.getNavBarPosition().isLeftEdge()
+                        ? velocityX
+                        : -velocityY;
 
         final boolean triggerQuickstep;
         int touch = Touch.FLING;
@@ -150,7 +152,7 @@
         }
 
         if (triggerQuickstep) {
-            mActivityInterface.closeOverlay();
+            mGestureState.getActivityInterface().closeOverlay();
             ActivityManagerWrapper.getInstance()
                     .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
             ActiveGestureLog.INSTANCE.addLog("startQuickstep");
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
deleted file mode 100644
index e2524b1..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2019 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.uioverrides.QuickstepLauncher.ROTATION_LANDSCAPE;
-import static com.android.launcher3.uioverrides.QuickstepLauncher.ROTATION_SEASCAPE;
-import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
-
-import android.content.Context;
-import android.view.Surface;
-
-import com.android.launcher3.graphics.RotationMode;
-import com.android.launcher3.util.DefaultDisplay;
-import com.android.quickstep.SysUINavigationMode;
-
-/**
- * Utility class to check nav bar position
- */
-public class NavBarPosition {
-
-    private final SysUINavigationMode.Mode mMode;
-    private final int mDisplayRotation;
-
-    public NavBarPosition(Context context) {
-        mMode = SysUINavigationMode.getMode(context);
-        mDisplayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
-    }
-
-    public boolean isRightEdge() {
-        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90;
-    }
-
-    public boolean isLeftEdge() {
-        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270;
-    }
-
-    public RotationMode getRotationMode() {
-        return isLeftEdge() ? ROTATION_SEASCAPE
-                : (isRightEdge() ? ROTATION_LANDSCAPE : RotationMode.NORMAL);
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 5d4665d..c634825 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -1673,8 +1673,8 @@
 
         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
             final int[] visibleTasks = getVisibleChildrenRange();
-            event.setFromIndex(taskViewCount - visibleTasks[1] - 1);
-            event.setToIndex(taskViewCount - visibleTasks[0] - 1);
+            event.setFromIndex(taskViewCount - visibleTasks[1]);
+            event.setToIndex(taskViewCount - visibleTasks[0]);
             event.setItemCount(taskViewCount);
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 0dfc39b..57327f8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -727,8 +727,8 @@
         final RecentsView recentsView = getRecentsView();
         final AccessibilityNodeInfo.CollectionItemInfo itemInfo =
                 AccessibilityNodeInfo.CollectionItemInfo.obtain(
-                        0, 1, recentsView.getChildCount() - recentsView.indexOfChild(this) - 1, 1,
-                        false);
+                        0, 1, recentsView.getTaskViewCount() - recentsView.indexOfChild(this) - 1,
+                        1, false);
         info.setCollectionItemInfo(itemInfo);
     }
 
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 98ff410..ae0886b 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -17,7 +17,10 @@
 
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 
+import android.app.ActivityManager;
+import android.content.Intent;
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import java.util.ArrayList;
@@ -56,6 +59,8 @@
         public final boolean recentsAttachedToAppWindow;
     }
 
+    private static final String TAG = "GestureState";
+
     private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
     private static int FLAG_COUNT = 0;
     private static int getFlagForIndex(String name) {
@@ -98,25 +103,39 @@
 
 
     // Needed to interact with the current activity
+    private final Intent mHomeIntent;
+    private final Intent mOverviewIntent;
     private final BaseActivityInterface mActivityInterface;
     private final MultiStateCallback mStateCallback;
     private final int mGestureId;
 
+    private ActivityManager.RunningTaskInfo mRunningTask;
     private GestureEndTarget mEndTarget;
     // TODO: This can be removed once we stop finishing the animation when starting a new task
     private int mFinishingRecentsAnimationTaskId = -1;
 
-    public GestureState(BaseActivityInterface activityInterface, int gestureId) {
-        mActivityInterface = activityInterface;
-        mGestureId = gestureId;
+    public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
+        mHomeIntent = componentObserver.getHomeIntent();
+        mOverviewIntent = componentObserver.getOverviewIntent();
+        mActivityInterface = componentObserver.getActivityInterface();
         mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0]));
+        mGestureId = gestureId;
     }
 
     public GestureState() {
         // Do nothing, only used for initializing the gesture state prior to user unlock
+        mHomeIntent = new Intent();
+        mOverviewIntent = new Intent();
         mActivityInterface = null;
-        mGestureId = -1;
         mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0]));
+        mGestureId = -1;
+    }
+
+    /**
+     * @return whether the gesture state has the provided {@param stateMask} flags set.
+     */
+    public boolean hasState(int stateMask) {
+        return mStateCallback.hasStates(stateMask);
     }
 
     /**
@@ -134,6 +153,20 @@
     }
 
     /**
+     * @return the intent for the Home component.
+     */
+    public Intent getHomeIntent() {
+        return mHomeIntent;
+    }
+
+    /**
+     * @return the intent for the Overview component.
+     */
+    public Intent getOverviewIntent() {
+        return mOverviewIntent;
+    }
+
+    /**
      * @return the interface to the activity handing the UI updates for this gesture.
      */
     public <T extends BaseDraggingActivity> BaseActivityInterface<T> getActivityInterface() {
@@ -148,6 +181,27 @@
     }
 
     /**
+     * @return the running task for this gesture.
+     */
+    public ActivityManager.RunningTaskInfo getRunningTask() {
+        return mRunningTask;
+    }
+
+    /**
+     * @return the running task id for this gesture.
+     */
+    public int getRunningTaskId() {
+        return mRunningTask != null ? mRunningTask.taskId : -1;
+    }
+
+    /**
+     * Updates the running task for the gesture to be the given {@param runningTask}.
+     */
+    public void updateRunningTask(ActivityManager.RunningTaskInfo runningTask) {
+        mRunningTask = runningTask;
+    }
+
+    /**
      * @return the end target for this gesture (if known).
      */
     public GestureEndTarget getEndTarget() {
@@ -155,14 +209,6 @@
     }
 
     /**
-     * @return whether the current gesture is still running a recents animation to a state in the
-     *         Launcher or Recents activity.
-     */
-    public boolean isRunningAnimationToLauncher() {
-        return isRecentsAnimationRunning() && mEndTarget != null && mEndTarget.isLauncher;
-    }
-
-    /**
      * Sets the end target of this gesture and immediately notifies the state changes.
      */
     public void setEndTarget(GestureEndTarget target) {
@@ -202,6 +248,15 @@
     }
 
     /**
+     * @return whether the current gesture is still running a recents animation to a state in the
+     *         Launcher or Recents activity.
+     * Updates the running task for the gesture to be the given {@param runningTask}.
+     */
+    public boolean isRunningAnimationToLauncher() {
+        return isRecentsAnimationRunning() && mEndTarget != null && mEndTarget.isLauncher;
+    }
+
+    /**
      * @return whether the recents animation is started but not yet ended
      */
     public boolean isRecentsAnimationRunning() {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 9d5120d..333e179 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -48,11 +48,10 @@
     private final Consumer<RecentsAnimationController> mOnFinishedListener;
     private final boolean mShouldMinimizeSplitScreen;
 
-    private boolean mWindowThresholdCrossed = false;
-
     private InputConsumerController mInputConsumerController;
     private Supplier<InputConsumer> mInputProxySupplier;
     private InputConsumer mInputConsumer;
+    private boolean mWindowThresholdCrossed = false;
     private boolean mTouchInProgress;
     private boolean mFinishPending;
 
@@ -62,8 +61,6 @@
         mController = controller;
         mOnFinishedListener = onFinishedListener;
         mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
-
-        setWindowThresholdCrossed(mWindowThresholdCrossed);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 1855e64..81f411e 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,8 +17,10 @@
 
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 
+import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
 import static com.android.launcher3.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE;
 import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@@ -35,14 +37,17 @@
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.graphics.Point;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.Process;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -57,6 +62,7 @@
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
@@ -72,16 +78,17 @@
         NavigationModeChangeListener,
         DefaultDisplay.DisplayInfoChangeListener {
 
-    private Context mContext;
-    private UserManagerCompat mUserManager;
-    private SysUINavigationMode mSysUiNavMode;
-    private DefaultDisplay mDefaultDisplay;
-    private int mDisplayId;
+    private final Context mContext;
+    private final UserManagerCompat mUserManager;
+    private final SysUINavigationMode mSysUiNavMode;
+    private final DefaultDisplay mDefaultDisplay;
+    private final int mDisplayId;
 
     private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
 
     private @SystemUiStateFlags int mSystemUiStateFlags;
     private SysUINavigationMode.Mode mMode = THREE_BUTTONS;
+    private NavBarPosition mNavBarPosition;
 
     private final RectF mSwipeUpTouchRegion = new RectF();
     private final Region mDeferredGestureRegion = new Region();
@@ -108,11 +115,13 @@
     private ComponentName mGestureBlockedActivity;
 
     public RecentsAnimationDeviceState(Context context) {
+        final ContentResolver resolver = context.getContentResolver();
         mContext = context;
         mUserManager = UserManagerCompat.getInstance(context);
         mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
         mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
         mDisplayId = mDefaultDisplay.getInfo().id;
+        runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this));
 
         // Register for user unlocked if necessary
         mIsUserUnlocked = mUserManager.isUserUnlocked(Process.myUserHandle());
@@ -155,7 +164,6 @@
         for (Runnable r : mOnDestroyActions) {
             r.run();
         }
-        mDefaultDisplay.removeChangeListener(this);
     }
 
     /**
@@ -183,6 +191,7 @@
             mExclusionListener.unregister();
         }
         mMode = newMode;
+        mNavBarPosition = new NavBarPosition(mMode, mDefaultDisplay.getInfo());
     }
 
     @Override
@@ -191,6 +200,7 @@
             return;
         }
 
+        mNavBarPosition = new NavBarPosition(mMode, info);
         updateGestureTouchRegions();
     }
 
@@ -202,6 +212,13 @@
     }
 
     /**
+     * @return the nav bar position for the current nav bar mode and display rotation.
+     */
+    public NavBarPosition getNavBarPosition() {
+        return mNavBarPosition;
+    }
+
+    /**
      * @return whether the current nav mode is fully gestural.
      */
     public boolean isFullyGesturalNavMode() {
diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
new file mode 100644
index 0000000..a4614de
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 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.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.Surface;
+
+import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.SysUINavigationMode;
+
+/**
+ * Utility class to check nav bar position.
+ */
+public class NavBarPosition {
+
+    public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
+        @Override
+        public void mapRect(int left, int top, int right, int bottom, Rect out) {
+            out.left = top;
+            out.top = right;
+            out.right = bottom;
+            out.bottom = left;
+        }
+
+        @Override
+        public void mapInsets(Context context, Rect insets, Rect out) {
+            // If there is a display cutout, the top insets in portrait would also include the
+            // cutout, which we will get as the left inset in landscape. Using the max of left and
+            // top allows us to cover both cases (with or without cutout).
+            if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
+                out.top = Math.max(insets.top, insets.left);
+                out.bottom = Math.max(insets.right, insets.bottom);
+                out.left = out.right = 0;
+            } else {
+                out.top = Math.max(insets.top, insets.left);
+                out.bottom = insets.right;
+                out.left = insets.bottom;
+                out.right = 0;
+            }
+        }
+    };
+
+    public static RotationMode ROTATION_SEASCAPE = new RotationMode(90) {
+        @Override
+        public void mapRect(int left, int top, int right, int bottom, Rect out) {
+            out.left = bottom;
+            out.top = left;
+            out.right = top;
+            out.bottom = right;
+        }
+
+        @Override
+        public void mapInsets(Context context, Rect insets, Rect out) {
+            if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
+                out.top = Math.max(insets.top, insets.right);
+                out.bottom = Math.max(insets.left, insets.bottom);
+                out.left = out.right = 0;
+            } else {
+                out.top = Math.max(insets.top, insets.right);
+                out.bottom = insets.left;
+                out.right = insets.bottom;
+                out.left = 0;
+            }
+        }
+
+        @Override
+        public int toNaturalGravity(int absoluteGravity) {
+            int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+            int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+            if (horizontalGravity == Gravity.RIGHT) {
+                horizontalGravity = Gravity.LEFT;
+            } else if (horizontalGravity == Gravity.LEFT) {
+                horizontalGravity = Gravity.RIGHT;
+            }
+
+            if (verticalGravity == Gravity.TOP) {
+                verticalGravity = Gravity.BOTTOM;
+            } else if (verticalGravity == Gravity.BOTTOM) {
+                verticalGravity = Gravity.TOP;
+            }
+
+            return ((absoluteGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK)
+                    & ~Gravity.VERTICAL_GRAVITY_MASK)
+                    | horizontalGravity | verticalGravity;
+        }
+    };
+
+    private final SysUINavigationMode.Mode mMode;
+    private final int mDisplayRotation;
+
+    public NavBarPosition(SysUINavigationMode.Mode mode, DefaultDisplay.Info info) {
+        mMode = mode;
+        mDisplayRotation = info.rotation;
+    }
+
+    public boolean isRightEdge() {
+        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90;
+    }
+
+    public boolean isLeftEdge() {
+        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270;
+    }
+
+    public RotationMode getRotationMode() {
+        return isLeftEdge() ? ROTATION_SEASCAPE
+                : (isRightEdge() ? ROTATION_LANDSCAPE : RotationMode.NORMAL);
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index 32eb2ec..5b6d94d 100644
--- a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -202,15 +202,14 @@
             CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
             if (entry == null) {
                 entry = new CacheEntry();
-                getDefaultIcon(user).applyTo(entry);
+                entry.bitmap = getDefaultIcon(user);
             }
             return entry;
         }
 
         public void addCache(ComponentName key, String title) {
             CacheEntry entry = new CacheEntry();
-            entry.icon = newIcon();
-            entry.color = Color.RED;
+            entry.bitmap = BitmapInfo.of(newIcon(), Color.RED);
             entry.title = title;
             mCache.put(new ComponentKey(key, Process.myUserHandle()), entry);
         }
diff --git a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 81b9043..69c5b00 100644
--- a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -2,13 +2,13 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.icons.BitmapInfo;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,7 +43,7 @@
     public void testCacheUpdate_update_apps() throws Exception {
         // Clear all icons from apps list so that its easy to check what was updated
         for (AppInfo info : allAppsList.data) {
-            info.iconBitmap = null;
+            info.bitmap = BitmapInfo.LOW_RES_INFO;
         }
 
         executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
@@ -56,9 +56,9 @@
         assertFalse(allAppsList.data.isEmpty());
         for (AppInfo info : allAppsList.data) {
             if (info.componentName.getPackageName().equals("app1")) {
-                assertNotNull(info.iconBitmap);
+                assertFalse(info.bitmap.isNullOrLowRes());
             } else {
-                assertNull(info.iconBitmap);
+                assertTrue(info.bitmap.isNullOrLowRes());
             }
         }
     }
@@ -85,10 +85,10 @@
         for (ItemInfo info : bgDataModel.itemsIdMap) {
             if (updates.contains(info.id)) {
                 assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
-                assertNotNull(((WorkspaceItemInfo) info).iconBitmap);
+                assertFalse(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes());
             } else {
                 assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
-                assertNull(((WorkspaceItemInfo) info).iconBitmap);
+                assertTrue(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes());
             }
         }
     }
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index d24de8e..772eb00 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -57,7 +57,7 @@
     private ActionMode mCurrentActionMode;
     protected boolean mIsSafeModeEnabled;
 
-    private OnStartCallback mOnStartCallback;
+    private Runnable mOnStartCallback;
 
     private int mThemeRes = R.style.AppTheme;
 
@@ -226,7 +226,7 @@
         super.onStart();
 
         if (mOnStartCallback != null) {
-            mOnStartCallback.onActivityStart(this);
+            mOnStartCallback.run();
             mOnStartCallback = null;
         }
     }
@@ -238,8 +238,12 @@
         mRotationListener.disable();
     }
 
-    public <T extends BaseDraggingActivity> void setOnStartCallback(OnStartCallback<T> callback) {
-        mOnStartCallback = callback;
+    public void runOnceOnStart(Runnable action) {
+        mOnStartCallback = action;
+    }
+
+    public void clearRunOnceOnStartCallback() {
+        mOnStartCallback = null;
     }
 
     protected void onDeviceProfileInitiated() {
@@ -258,12 +262,4 @@
     }
 
     protected abstract void reapplyUi();
-
-    /**
-     * Callback for listening for onStart
-     */
-    public interface OnStartCallback<T extends BaseDraggingActivity> {
-
-        void onActivityStart(T activity);
-    }
 }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 7adb6a4..e8aa83f 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -289,7 +289,7 @@
     private void applyIconAndLabel(ItemInfoWithIcon info) {
         FastBitmapDrawable iconDrawable = DrawableFactory.INSTANCE.get(getContext())
                 .newIcon(getContext(), info);
-        mDotParams.color = IconPalette.getMutedColor(info.iconColor, 0.54f);
+        mDotParams.color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
 
         setIcon(iconDrawable);
         setText(info.title);
@@ -665,7 +665,7 @@
             mDisableRelayout = true;
 
             // Optimization: Starting in N, pre-uploads the bitmap to RenderThread.
-            info.iconBitmap.prepareToDraw();
+            info.bitmap.icon.prepareToDraw();
 
             if (info instanceof AppInfo) {
                 applyFromApplicationInfo((AppInfo) info);
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index a90025e..2e57f71 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -98,10 +98,6 @@
         this(info.icon, info.color);
     }
 
-    public FastBitmapDrawable(ItemInfoWithIcon info) {
-        this(info.iconBitmap, info.iconColor);
-    }
-
     protected FastBitmapDrawable(Bitmap b, int iconColor) {
         this(b, iconColor, false);
     }
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 93def50..2bf1a85 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -485,7 +485,7 @@
             } else if (shortcutInfo != null) {
                 WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext);
                 LauncherIcons li = LauncherIcons.obtain(mContext);
-                itemInfo.applyFrom(li.createShortcutIcon(shortcutInfo));
+                itemInfo.bitmap = li.createShortcutIcon(shortcutInfo);
                 li.recycle();
                 return Pair.create(itemInfo, shortcutInfo);
             } else if (providerInfo != null) {
@@ -656,7 +656,7 @@
         if (iconInfo == null) {
             iconInfo = app.getIconCache().getDefaultIcon(info.user);
         }
-        info.applyFrom(iconInfo);
+        info.bitmap = iconInfo;
 
         info.title = Utilities.trim(name);
         info.contentDescription = app.getContext().getPackageManager()
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index 1550bb0..1941455 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -16,10 +16,6 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON;
-
-import android.graphics.Bitmap;
-
 import com.android.launcher3.icons.BitmapInfo;
 
 /**
@@ -30,14 +26,9 @@
     public static final String TAG = "ItemInfoDebug";
 
     /**
-     * A bitmap version of the application icon.
+     * The bitmap for the application icon
      */
-    public Bitmap iconBitmap;
-
-    /**
-     * Dominant color in the {@link #iconBitmap}.
-     */
-    public int iconColor;
+    public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;
 
     /**
      * Indicates that the icon is disabled due to safe mode restrictions.
@@ -106,8 +97,7 @@
 
     protected ItemInfoWithIcon(ItemInfoWithIcon info) {
         super(info);
-        iconBitmap = info.iconBitmap;
-        iconColor = info.iconColor;
+        bitmap = info.bitmap;
         runtimeStatusFlags = info.runtimeStatusFlags;
     }
 
@@ -120,12 +110,7 @@
      * Indicates whether we're using a low res icon
      */
     public boolean usingLowResIcon() {
-        return iconBitmap == LOW_RES_ICON;
-    }
-
-    public void applyFrom(BitmapInfo info) {
-        iconBitmap = info.icon;
-        iconColor = info.color;
+        return bitmap.isLowRes();
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b0b213c..93304a1 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -510,7 +510,7 @@
         updateAndBindWorkspaceItem(() -> {
             si.updateFromDeepShortcutInfo(info, mApp.getContext());
             LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
-            si.applyFrom(li.createShortcutIcon(info));
+            si.bitmap = li.createShortcutIcon(info);
             li.recycle();
             return si;
         });
@@ -546,7 +546,8 @@
         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
             writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
             for (AppInfo info : mBgAllAppsList.data) {
-                writer.println(prefix + "   title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
+                writer.println(prefix + "   title=\"" + info.title
+                        + "\" bitmapIcon=" + info.bitmap.icon
                         + " componentName=" + info.componentName.getPackageName());
             }
         }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 92f8069..aa6e61c 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -582,7 +582,7 @@
             }
             ShortcutInfo si = (ShortcutInfo) obj;
             LauncherIcons li = LauncherIcons.obtain(appState.getContext());
-            Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap;
+            Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).bitmap.icon;
             li.recycle();
             float badgeSize = iconSize * LauncherIcons.getBadgeSizeForIconSize(iconSize);
             float insetFraction = (iconSize - badgeSize) / iconSize;
diff --git a/src/com/android/launcher3/WorkspaceItemInfo.java b/src/com/android/launcher3/WorkspaceItemInfo.java
index 71bd92f..be907e5 100644
--- a/src/com/android/launcher3/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/WorkspaceItemInfo.java
@@ -140,7 +140,7 @@
                 .put(Favorites.RESTORED, status);
 
         if (!usingLowResIcon()) {
-            writer.putIcon(iconBitmap, user);
+            writer.putIcon(bitmap, user);
         }
         if (iconResource != null) {
             writer.put(Favorites.ICON_PACKAGE, iconResource.packageName)
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 837301f..13dbab5 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -57,8 +57,8 @@
      */
     public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
         FastBitmapDrawable drawable = info.usingLowResIcon()
-                ? new PlaceHolderIconDrawable(info, getShapePath(), context)
-                : new FastBitmapDrawable(info);
+                ? new PlaceHolderIconDrawable(info.bitmap, getShapePath(), context)
+                : new FastBitmapDrawable(info.bitmap);
         drawable.setIsDisabled(info.isDisabled());
         return drawable;
     }
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index d7b845b..2badb6e 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -50,8 +50,8 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.WorkspaceLayoutManager;
 import com.android.launcher3.allapps.SearchUiManager;
 import com.android.launcher3.config.FeatureFlags;
@@ -105,7 +105,7 @@
                 Build.VERSION.SDK_INT);
 
         mWorkspaceItemInfo = new WorkspaceItemInfo();
-        mWorkspaceItemInfo.applyFrom(iconInfo);
+        mWorkspaceItemInfo.bitmap = iconInfo;
         mWorkspaceItemInfo.intent = new Intent();
         mWorkspaceItemInfo.contentDescription = mWorkspaceItemInfo.title =
                 context.getString(R.string.label_application);
diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
index 23745cb..c6f807f 100644
--- a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
@@ -24,7 +24,6 @@
 import android.graphics.Rect;
 
 import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.R;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.util.Themes;
@@ -41,10 +40,6 @@
         this(info.icon, info.color, progressPath, context);
     }
 
-    public PlaceHolderIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) {
-        this(info.iconBitmap, info.iconColor, progressPath, context);
-    }
-
     protected PlaceHolderIconDrawable(Bitmap b, int iconColor, Path progressPath, Context context) {
         super(b, iconColor);
 
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index cc4c2ef..acdf942 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -105,7 +105,7 @@
      * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar.
      */
     public PreloadIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) {
-        super(info);
+        super(info.bitmap);
         mItem = info;
         mProgressPath = progressPath;
         mScaledTrackPath = new Path();
diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java
index 832956d..f7ee5f9 100644
--- a/src/com/android/launcher3/icons/ComponentWithLabel.java
+++ b/src/com/android/launcher3/icons/ComponentWithLabel.java
@@ -57,10 +57,8 @@
         }
 
         @Override
-        public void loadIcon(Context context,
-                ComponentWithLabel object, BitmapInfo target) {
-            // Do not load icon.
-            target.icon = BitmapInfo.LOW_RES_ICON;
+        public BitmapInfo loadIcon(Context context, ComponentWithLabel object) {
+            return BitmapInfo.LOW_RES_INFO;
         }
 
         @Override
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 9886f53..13dc08f 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -161,7 +161,7 @@
         CacheEntry entry = cacheLocked(application.componentName,
                 application.user, () -> null, mLauncherActivityInfoCachingLogic,
                 false, application.usingLowResIcon());
-        if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
+        if (entry.bitmap != null && !isDefaultIcon(entry.bitmap, application.user)) {
             applyCacheEntry(entry, application);
         }
     }
@@ -183,7 +183,7 @@
         // null info means not installed, but if we have a component from the intent then
         // we should still look in the cache for restored app icons.
         if (info.getTargetComponent() == null) {
-            info.applyFrom(getDefaultIcon(info.user));
+            info.bitmap = getDefaultIcon(info.user);
             info.title = "";
             info.contentDescription = "";
         } else {
@@ -226,7 +226,7 @@
     protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
         info.title = Utilities.trim(entry.title);
         info.contentDescription = entry.contentDescription;
-        info.applyFrom((entry.icon == null) ? getDefaultIcon(info.user) : entry);
+        info.bitmap = (entry.bitmap == null) ? getDefaultIcon(info.user) : entry.bitmap;
     }
 
     public Drawable getFullResIcon(LauncherActivityInfo info) {
diff --git a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
index f9a94da..a29d4fe 100644
--- a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
+++ b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
@@ -55,13 +55,12 @@
     }
 
     @Override
-    public void loadIcon(Context context, LauncherActivityInfo object,
-            BitmapInfo target) {
-        LauncherIcons li = LauncherIcons.obtain(context);
-        li.createBadgedIconBitmap(
-                IconProvider.INSTANCE.get(context)
-                        .getIcon(object, li.mFillResIconDpi, true /* flattenDrawable */),
-                object.getUser(), object.getApplicationInfo().targetSdkVersion).applyTo(target);
-        li.recycle();
+    public BitmapInfo loadIcon(Context context, LauncherActivityInfo object) {
+        try (LauncherIcons li = LauncherIcons.obtain(context)) {
+            return li.createBadgedIconBitmap(
+                    IconProvider.INSTANCE.get(context)
+                            .getIcon(object, li.mFillResIconDpi, true /* flattenDrawable */),
+                    object.getUser(), object.getApplicationInfo().targetSdkVersion);
+        }
     }
 }
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index adc92c4..1c34dc4 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -137,32 +137,25 @@
             if (fallbackIconProvider != null) {
                 // Fallback icons are already badged and with appropriate shadow
                 ItemInfoWithIcon fullIcon = fallbackIconProvider.get();
-                if (fullIcon != null && fullIcon.iconBitmap != null) {
-                    BitmapInfo result = new BitmapInfo();
-                    result.icon = fullIcon.iconBitmap;
-                    result.color = fullIcon.iconColor;
-                    return result;
+                if (fullIcon != null && fullIcon.bitmap != null) {
+                    return fullIcon.bitmap;
                 }
             }
             unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon;
         }
 
-        BitmapInfo result = new BitmapInfo();
         if (!badged) {
-            result.color = Themes.getColorAccent(mContext);
-            result.icon = unbadgedBitmap;
-            return result;
+            return BitmapInfo.of(unbadgedBitmap, Themes.getColorAccent(mContext));
         }
 
         final Bitmap unbadgedfinal = unbadgedBitmap;
         final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
 
-        result.color = badge.iconColor;
-        result.icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
+        Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
             getShadowGenerator().recreateIcon(unbadgedfinal, c);
-            badgeWithDrawable(c, new FastBitmapDrawable(badge));
+            badgeWithDrawable(c, new FastBitmapDrawable(badge.bitmap));
         });
-        return result;
+        return BitmapInfo.of(icon, badge.bitmap.color);
     }
 
     public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfo shortcutInfo, IconCache cache) {
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 2d62c9e..21c73e9 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -137,7 +137,7 @@
                                 .makeWorkspaceItem();
                         WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo;
                         wii.title = "";
-                        wii.applyFrom(app.getIconCache().getDefaultIcon(item.user));
+                        wii.bitmap = app.getIconCache().getDefaultIcon(item.user);
                         app.getIconCache().getTitleAndIcon(wii,
                                 ((WorkspaceItemInfo) itemInfo).usingLowResIcon());
                     }
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 6154e7e..95268d0 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -153,7 +153,7 @@
         info.title = getTitle();
         // the fallback icon
         if (!loadIcon(info)) {
-            info.applyFrom(mIconCache.getDefaultIcon(info.user));
+            info.bitmap = mIconCache.getDefaultIcon(info.user);
         }
 
         // TODO: If there's an explicit component and we can't install that, delete it.
@@ -183,7 +183,7 @@
                 info.iconResource.resourceName = resourceName;
                 BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
                 if (iconInfo != null) {
-                    info.applyFrom(iconInfo);
+                    info.bitmap = iconInfo;
                     return true;
                 }
             }
@@ -192,7 +192,7 @@
         // Failed to load from resource, try loading from DB.
         byte[] data = getBlob(iconIndex);
         try {
-            info.applyFrom(li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)));
+            info.bitmap = li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length));
             return true;
         } catch (Exception e) {
             Log.e(TAG, "Failed to decode byte array for info " + info, e);
@@ -273,7 +273,7 @@
         info.intent = newIntent;
 
         mIconCache.getTitleAndIcon(info, lai, useLowResIcon);
-        if (mIconCache.isDefaultIcon(info.iconBitmap, user)) {
+        if (mIconCache.isDefaultIcon(info.bitmap, user)) {
             loadIcon(info);
         }
 
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 353a0d2..6383a5f 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -503,8 +503,9 @@
                                     // use the last saved icon instead of the default.
                                     Supplier<ItemInfoWithIcon> fallbackIconProvider = () ->
                                             c.loadIcon(finalInfo, li) ? finalInfo : null;
-                                    info.applyFrom(li.createShortcutIcon(pinnedShortcut,
-                                            true /* badged */, fallbackIconProvider));
+                                    info.bitmap = li.createShortcutIcon(
+                                            pinnedShortcut, true /* badged */,
+                                            fallbackIconProvider);
                                     li.recycle();
                                     if (pmHelper.isAppSuspended(
                                             pinnedShortcut.getPackage(), info.user)) {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index db63b7c..1e614bd 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -189,7 +189,7 @@
                             BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
                             li.recycle();
                             if (iconInfo != null) {
-                                si.applyFrom(iconInfo);
+                                si.bitmap = iconInfo;
                                 infoUpdated = true;
                             }
                         }
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 6c358b1..05225d4 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -93,8 +93,8 @@
                     // If the shortcut is pinned but no longer has an icon in the system,
                     // keep the current icon instead of reverting to the default icon.
                     LauncherIcons li = LauncherIcons.obtain(context);
-                    workspaceItemInfo.applyFrom(li.createShortcutIcon(fullDetails, true,
-                            () -> workspaceItemInfo));
+                    workspaceItemInfo.bitmap = li.createShortcutIcon(
+                            fullDetails, true, () -> workspaceItemInfo);
                     li.recycle();
                     updatedWorkspaceItemInfos.add(workspaceItemInfo);
                 }
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 4b773d7..db1c307 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -92,7 +92,7 @@
                     // If the shortcut is pinned but no longer has an icon in the system,
                     // keep the current icon instead of reverting to the default icon.
                     LauncherIcons li = LauncherIcons.obtain(context);
-                    si.applyFrom(li.createShortcutIcon(shortcut, true, () -> si));
+                    si.bitmap = li.createShortcutIcon(shortcut, true, () -> si);
                     li.recycle();
                 } else {
                     si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java
index 68ea6c4..e13645c 100644
--- a/src/com/android/launcher3/pm/PinRequestHelper.java
+++ b/src/com/android/launcher3/pm/PinRequestHelper.java
@@ -82,7 +82,7 @@
             WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
             // Apply the unbadged icon and fetch the actual icon asynchronously.
             LauncherIcons li = LauncherIcons.obtain(context);
-            info.applyFrom(li.createShortcutIcon(si, false /* badged */));
+            info.bitmap = li.createShortcutIcon(si, false /* badged */);
             li.recycle();
             LauncherAppState.getInstance(context).getModel()
                     .updateAndBindWorkspaceItem(info, si);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 835782a..79b41c1 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -501,7 +501,7 @@
         DotInfo dotInfo = mLauncher.getDotInfoForItem(itemInfo);
         if (mNotificationItemView != null && dotInfo != null) {
             mNotificationItemView.updateHeader(
-                    dotInfo.getNotificationCount(), itemInfo.iconColor);
+                    dotInfo.getNotificationCount(), itemInfo.bitmap.color);
         }
     }
 
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 9618927..80c6683 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -154,7 +154,7 @@
                 final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, launcher);
                 // Use unbadged icon for the menu.
                 LauncherIcons li = LauncherIcons.obtain(launcher);
-                si.applyFrom(li.createShortcutIcon(shortcut, false /* badged */));
+                si.bitmap = li.createShortcutIcon(shortcut, false /* badged */);
                 li.recycle();
                 si.rank = i;
 
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 140a06a..64df384 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -24,9 +24,10 @@
 import android.graphics.Color;
 import android.os.Bundle;
 import android.os.Debug;
-import android.util.Log;
 import android.view.View;
 
+import androidx.annotation.Keep;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
@@ -186,6 +187,22 @@
         Runtime.getRuntime().runFinalization();
 
         final CountDownLatch fence = new CountDownLatch(1);
+        createFinalizationObserver(fence);
+        try {
+            do {
+                Runtime.getRuntime().gc();
+                Runtime.getRuntime().runFinalization();
+            } while (!fence.await(100, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    // Create the observer in the scope of a method to minimize the chance that
+    // it remains live in a DEX/machine register at the point of the fence guard.
+    // This must be kept to avoid R8 inlining it.
+    @Keep
+    private static void createFinalizationObserver(CountDownLatch fence) {
         new Object() {
             @Override
             protected void finalize() throws Throwable {
@@ -196,13 +213,5 @@
                 }
             }
         };
-        try {
-            do {
-                Runtime.getRuntime().gc();
-                Runtime.getRuntime().runFinalization();
-            } while (!fence.await(100, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException ex) {
-            throw new RuntimeException(ex);
-        }
     }
 }
diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java
index 00adf10..2d64353 100644
--- a/src/com/android/launcher3/util/ContentWriter.java
+++ b/src/com/android/launcher3/util/ContentWriter.java
@@ -26,6 +26,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.GraphicsUtils;
 
 /**
@@ -37,7 +38,7 @@
     private final Context mContext;
 
     private CommitParams mCommitParams;
-    private Bitmap mIcon;
+    private BitmapInfo mIcon;
     private UserHandle mUser;
 
     public ContentWriter(Context context, CommitParams commitParams) {
@@ -79,7 +80,7 @@
         return this;
     }
 
-    public ContentWriter putIcon(Bitmap value, UserHandle user) {
+    public ContentWriter putIcon(BitmapInfo value, UserHandle user) {
         mIcon = value;
         mUser = user;
         return this;
@@ -97,7 +98,7 @@
         Preconditions.assertNonUiThread();
         if (mIcon != null && !LauncherAppState.getInstance(context).getIconCache()
                 .isDefaultIcon(mIcon, mUser)) {
-            mValues.put(LauncherSettings.Favorites.ICON, GraphicsUtils.flattenBitmap(mIcon));
+            mValues.put(LauncherSettings.Favorites.ICON, GraphicsUtils.flattenBitmap(mIcon.icon));
             mIcon = null;
         }
         return mValues;
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 50db40f..3ece5f4 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -33,12 +33,12 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Themes;
@@ -128,7 +128,7 @@
             mCenterDrawable.setCallback(null);
             mCenterDrawable = null;
         }
-        if (info.iconBitmap != null) {
+        if (info.bitmap.icon != null) {
             // The view displays three modes,
             //   1) App icon in the center
             //   2) Preload icon in the center
@@ -142,7 +142,7 @@
             } else if (isReadyForClickSetup()) {
                 mCenterDrawable = drawableFactory.newIcon(getContext(), info);
                 mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
-                updateSettingColor(info.iconColor);
+                updateSettingColor(info.bitmap.color);
             } else {
                 mCenterDrawable = DrawableFactory.INSTANCE.get(getContext())
                         .newPendingIcon(getContext(), info);
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
index 435125b..f3b325d 100644
--- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
@@ -18,6 +18,8 @@
 
 import android.util.Log;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator;
@@ -25,8 +27,6 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 
-import androidx.recyclerview.widget.RecyclerView;
-
 /**
  * Do diff on widget's tray list items and call the {@link RecyclerView.Adapter}
  * methods accordingly.
@@ -137,7 +137,7 @@
     }
 
     private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) {
-        return curInfo.iconBitmap.equals(newInfo.iconBitmap) &&
-                !mIconCache.isDefaultIcon(curInfo.iconBitmap, curInfo.user);
+        return curInfo.bitmap.icon.equals(newInfo.bitmap.icon)
+                && !mIconCache.isDefaultIcon(curInfo.bitmap, curInfo.user);
     }
 }
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 7029ad5..0dcfaa8 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -142,7 +142,7 @@
         when(mMockIconCache.getDefaultIcon(eq(mLoaderCursor.user)))
                 .thenReturn(BitmapInfo.fromBitmap(icon));
         WorkspaceItemInfo info = mLoaderCursor.loadSimpleWorkspaceItem();
-        assertEquals(icon, info.iconBitmap);
+        assertEquals(icon, info.bitmap.icon);
         assertEquals("my-shortcut", info.title);
         assertEquals(ITEM_TYPE_SHORTCUT, info.itemType);
     }
diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
index 08fa098..858cb38 100644
--- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -21,6 +21,7 @@
 import android.os.Build;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.UiDevice;
 
 import org.junit.rules.TestRule;
@@ -86,6 +87,19 @@
     }
 
     private static int getRunFlavor() {
+        final String flavorOverride = InstrumentationRegistry.getArguments().getString("flavor");
+
+        if (flavorOverride != null) {
+            Log.d(TAG, "Flavor override: " + flavorOverride);
+            try {
+                return (int) TestStabilityRule.class.getField(flavorOverride).get(null);
+            } catch (NoSuchFieldException e) {
+                throw new AssertionError("Unrecognized run flavor override: " + flavorOverride);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
         final String launcherVersion;
         try {
             launcherVersion = getInstrumentation().
diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
index a31d8a6..c7f7cd6 100644
--- a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
+++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
@@ -23,16 +23,19 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.view.LayoutInflater;
+
+import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
-import android.view.LayoutInflater;
 
-import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.MultiHashMap;
@@ -46,8 +49,6 @@
 import java.util.ArrayList;
 import java.util.Map;
 
-import androidx.recyclerview.widget.RecyclerView;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class WidgetsListAdapterTest {
@@ -136,7 +137,7 @@
             PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName());
             pInfo.title = pInfo.packageName;
             pInfo.user = wi.user;
-            pInfo.iconBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8);
+            pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
             newMap.addToList(pInfo, wi);
             if (newMap.size() == num) {
                 break;