Merge "Allow user to tap on stashed app to launch task in fullscreen" into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 1b47939..dcad873 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -194,6 +194,7 @@
     public static final int SPLIT_DIVIDER_ANIM_DURATION = 100;
 
     public static final int CONTENT_ALPHA_DURATION = 217;
+    public static final int TRANSIENT_TASKBAR_TRANSITION_DURATION = 417;
     public static final int TASKBAR_TO_APP_DURATION = 600;
     // TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with unlock animation
     // is solved.
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index 68ed682..fb2d0dc 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -18,8 +18,6 @@
 
 import static android.content.ContentResolver.SCHEME_CONTENT;
 
-import static com.android.launcher3.Utilities.newContentObserver;
-
 import android.annotation.TargetApi;
 import android.app.RemoteAction;
 import android.content.ContentProviderClient;
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index beab56c..0945bf2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -17,6 +17,7 @@
 
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 
+import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION;
 import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 
@@ -157,9 +158,11 @@
                 isResumed,
                 fromInit,
                 /* startAnimation= */ true,
-                !isResumed
-                        ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
-                        : QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
+                DisplayController.isTransientTaskbar(mLauncher)
+                        ? TRANSIENT_TASKBAR_TRANSITION_DURATION
+                        : (!isResumed
+                                ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
+                                : QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION));
     }
 
     @Nullable
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 729eea9..379a6cd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -24,7 +24,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.widget.LauncherAppWidgetHost;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 
 /**
  * A wrapper for the hidden API calls
@@ -44,7 +44,7 @@
      * @param handler InteractionHandler for the views in the host
      */
     public static void setHostInteractionHandler(@NonNull AppWidgetHost host,
-            @Nullable LauncherAppWidgetHost.LauncherWidgetInteractionHandler handler) {
+            @Nullable LauncherWidgetHolder.LauncherWidgetInteractionHandler handler) {
         host.setInteractionHandler(handler::onInteraction);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index 2dde6b6..353d817 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -33,12 +33,12 @@
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.widget.LauncherAppWidgetHost;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 
 /** Provides a Quickstep specific animation when launching an activity from an app widget. */
-class QuickstepInteractionHandler implements
-        LauncherAppWidgetHost.LauncherWidgetInteractionHandler {
+class QuickstepInteractionHandler
+        implements LauncherWidgetHolder.LauncherWidgetInteractionHandler {
 
     private static final String TAG = "QuickstepInteractionHandler";
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 9813c8a..f90feb8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -78,7 +78,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherWidgetHolder;
 import com.android.launcher3.QuickstepAccessibilityDelegate;
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
@@ -125,6 +124,7 @@
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SystemUiProxy;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 918b3c1..f343f52 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.ObjectAnimator;
@@ -86,6 +87,11 @@
     @Override
     protected boolean canInterceptTouch(MotionEvent ev) {
         mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+        boolean isOneHandedModeActive = (SystemUiProxy.INSTANCE.get(mLauncher)
+                .getLastSystemUiStateFlags() & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
+        // Reset touch slop multiplier to default 1.0f if one-handed-mode is not active
+        mDetector.setTouchSlopMultiplier(
+                isOneHandedModeActive ? ONE_HANDED_ACTIVATED_SLOP_MULTIPLIER : 1f /* default */);
         return super.canInterceptTouch(ev) && !mLauncher.isInState(HINT_STATE);
     }
 
@@ -277,14 +283,4 @@
     private float dpiFromPx(float pixels) {
         return Utilities.dpiFromPx(pixels, mLauncher.getResources().getDisplayMetrics().densityDpi);
     }
-
-    @Override
-    public void onOneHandedModeStateChanged(boolean activated) {
-        if (activated) {
-            mDetector.setTouchSlopMultiplier(ONE_HANDED_ACTIVATED_SLOP_MULTIPLIER);
-        } else {
-            // Reset touch slop multiplier to default 1.0f
-            mDetector.setTouchSlopMultiplier(1f /* default */);
-        }
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index a622326..2c3ba85 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -121,9 +121,6 @@
 
     public abstract void onAssistantVisibilityChanged(float visibility);
 
-    /** Called when one handed mode activated or deactivated. */
-    public abstract void onOneHandedModeStateChanged(boolean activated);
-
     public abstract AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
             boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback);
 
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index c62220f..ae9fb0b 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -77,11 +77,6 @@
         // set to zero prior to this class becoming active.
     }
 
-    @Override
-    public void onOneHandedModeStateChanged(boolean activated) {
-        // Do nothing for FallbackActivityInterface
-    }
-
     /** 6 */
     @Override
     public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index ecb3747..9ff9416 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -109,15 +109,6 @@
     }
 
     @Override
-    public void onOneHandedModeStateChanged(boolean activated) {
-        Launcher launcher = getCreatedActivity();
-        if (launcher == null) {
-            return;
-        }
-        launcher.onOneHandedStateChanged(activated);
-    }
-
-    @Override
     public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
             boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
         notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 9e3173c..8e07376 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -115,11 +115,6 @@
         if (mDeviceState.isHomeDisabled() != mIsHomeDisabled) {
             updateOverviewTargets();
         }
-
-        // Notify ALL_APPS touch controller when one handed mode state activated or deactivated
-        if (mDeviceState.isOneHandedModeEnabled()) {
-            mActivityInterface.onOneHandedModeStateChanged(mDeviceState.isOneHandedModeActive());
-        }
     }
 
     private void updateOverviewTargets(Intent unused) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index e87fdad..9e25555 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -46,10 +46,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
 
 import android.app.ActivityTaskManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.graphics.Region;
 import android.inputmethodservice.InputMethodService;
 import android.net.Uri;
@@ -63,12 +60,12 @@
 import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.Utilities;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -114,15 +111,12 @@
 
     private boolean mIsUserUnlocked;
     private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
-    private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (ACTION_USER_UNLOCKED.equals(intent.getAction())) {
-                mIsUserUnlocked = true;
-                notifyUserUnlocked();
-            }
+    private final SimpleBroadcastReceiver mUserUnlockedReceiver = new SimpleBroadcastReceiver(i -> {
+        if (ACTION_USER_UNLOCKED.equals(i.getAction())) {
+            mIsUserUnlocked = true;
+            notifyUserUnlocked();
         }
-    };
+    });
 
     private int mGestureBlockingTaskId = -1;
     private @NonNull Region mExclusionRegion = new Region();
@@ -153,10 +147,9 @@
         mIsUserUnlocked = context.getSystemService(UserManager.class)
                 .isUserUnlocked(Process.myUserHandle());
         if (!mIsUserUnlocked) {
-            mContext.registerReceiver(mUserUnlockedReceiver,
-                    new IntentFilter(ACTION_USER_UNLOCKED));
+            mUserUnlockedReceiver.register(mContext, ACTION_USER_UNLOCKED);
         }
-        runOnDestroy(() -> Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver));
+        runOnDestroy(() -> mUserUnlockedReceiver.unregisterReceiverSafely(mContext));
 
         // Register for exclusion updates
         mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
@@ -347,7 +340,7 @@
             action.run();
         }
         mUserUnlockedActions.clear();
-        Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver);
+        mUserUnlockedReceiver.unregisterReceiverSafely(mContext);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index f12074b..170c622 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -18,7 +18,6 @@
 import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_UNFOLD_ANIMATION;
 import static com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY;
-import static com.android.launcher3.Utilities.comp;
 
 import android.annotation.Nullable;
 import android.util.FloatProperty;
@@ -135,7 +134,7 @@
         @Override
         public void onTransitionProgress(float progress) {
             if (mQsbInsettable != null) {
-                float insetPercentage = comp(progress) * MAX_WIDTH_INSET_FRACTION;
+                float insetPercentage = (1 - progress) * MAX_WIDTH_INSET_FRACTION;
                 mQsbInsettable.setHorizontalInsets(insetPercentage);
             }
         }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index c428c64..4de69cd 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -20,7 +20,6 @@
 import static android.widget.Toast.LENGTH_SHORT;
 
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.Utilities.comp;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
@@ -501,7 +500,7 @@
             return;
         }
         mModalness = modalness;
-        mIconView.setAlpha(comp(modalness));
+        mIconView.setAlpha(1 - modalness);
         mDigitalWellBeingToast.updateBannerOffset(modalness,
                 mCurrentFullscreenParams.mCurrentDrawnInsets.top
                         + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom);
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index 75e89b2..eb6d096 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -2,7 +2,6 @@
 
 import static android.os.Process.myUserHandle;
 
-import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
@@ -12,6 +11,7 @@
 import android.database.Cursor;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -21,7 +21,7 @@
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.util.ContentWriter;
-import com.android.launcher3.widget.LauncherAppWidgetHost;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 
 public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
 
@@ -32,7 +32,7 @@
         if (AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED.equals(intent.getAction())) {
             int hostId = intent.getIntExtra(AppWidgetManager.EXTRA_HOST_ID, 0);
             Log.d(TAG, "Widget ID map received for host:" + hostId);
-            if (hostId != LauncherAppWidgetHost.APPWIDGET_HOST_ID) {
+            if (hostId != LauncherWidgetHolder.APPWIDGET_HOST_ID) {
                 return;
             }
 
@@ -50,11 +50,11 @@
      * Updates the app widgets whose id has changed during the restore process.
      */
     @WorkerThread
-    public static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
-        AppWidgetHost appWidgetHost = new LauncherAppWidgetHost(context);
+    public static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds,
+            @NonNull LauncherWidgetHolder holder) {
         if (WidgetsModel.GO_DISABLE_WIDGETS) {
             Log.e(TAG, "Skipping widget ID remap as widgets not supported");
-            appWidgetHost.deleteHost();
+            holder.deleteHost();
             return;
         }
         if (!RestoreDbTask.isPending(context)) {
@@ -63,7 +63,7 @@
             Log.e(TAG, "Skipping widget ID remap as DB already in use");
             for (int widgetId : newWidgetIds) {
                 Log.d(TAG, "Deleting widgetId: " + widgetId);
-                appWidgetHost.deleteAppWidgetId(widgetId);
+                holder.deleteAppWidgetId(widgetId);
             }
             return;
         }
@@ -100,7 +100,7 @@
                 try {
                     if (!cursor.moveToFirst()) {
                         // The widget no long exists.
-                        appWidgetHost.deleteAppWidgetId(newWidgetIds[i]);
+                        holder.deleteAppWidgetId(newWidgetIds[i]);
                     }
                 } finally {
                     cursor.close();
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 64666b0..efdd5e1 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import android.appwidget.AppWidgetHost;
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
@@ -48,6 +47,7 @@
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -74,7 +74,7 @@
     private static final String FORMATTED_LAYOUT_RES = "default_layout_%dx%d";
     private static final String LAYOUT_RES = "default_layout";
 
-    static AutoInstallsLayout get(Context context, AppWidgetHost appWidgetHost,
+    static AutoInstallsLayout get(Context context, LauncherWidgetHolder appWidgetHolder,
             LayoutParserCallback callback) {
         Pair<String, Resources> customizationApkInfo = PackageManagerHelper.findSystemApk(
                 ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager());
@@ -109,7 +109,7 @@
             Log.e(TAG, "Layout definition not found in package: " + pkg);
             return null;
         }
-        return new AutoInstallsLayout(context, appWidgetHost, callback, targetRes, layoutId,
+        return new AutoInstallsLayout(context, appWidgetHolder, callback, targetRes, layoutId,
                 TAG_WORKSPACE);
     }
 
@@ -156,7 +156,7 @@
     @Thunk
     final Context mContext;
     @Thunk
-    final AppWidgetHost mAppWidgetHost;
+    final LauncherWidgetHolder mAppWidgetHolder;
     protected final LayoutParserCallback mCallback;
 
     protected final PackageManager mPackageManager;
@@ -174,17 +174,17 @@
 
     protected SQLiteDatabase mDb;
 
-    public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost,
+    public AutoInstallsLayout(Context context, LauncherWidgetHolder appWidgetHolder,
             LayoutParserCallback callback, Resources res,
             int layoutId, String rootTag) {
-        this(context, appWidgetHost, callback, res, () -> res.getXml(layoutId), rootTag);
+        this(context, appWidgetHolder, callback, res, () -> res.getXml(layoutId), rootTag);
     }
 
-    public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost,
+    public AutoInstallsLayout(Context context, LauncherWidgetHolder appWidgetHolder,
             LayoutParserCallback callback, Resources res,
             Supplier<XmlPullParser> initialLayoutSupplier, String rootTag) {
         mContext = context;
-        mAppWidgetHost = appWidgetHost;
+        mAppWidgetHolder = appWidgetHolder;
         mCallback = callback;
 
         mPackageManager = context.getPackageManager();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index c0a00c2..9f54f09 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -607,15 +607,16 @@
      * Get the icon bounds on the view depending on the layout type.
      */
     public void getIconBounds(int iconSize, Rect outBounds) {
-        Utilities.setRectToViewCenter(this, iconSize, outBounds);
+        outBounds.set(0, 0, iconSize, iconSize);
         if (mLayoutHorizontal) {
+            int top = (getHeight() - iconSize) / 2;
             if (mIsRtl) {
-                outBounds.offsetTo(getWidth() - iconSize - getPaddingRight(), outBounds.top);
+                outBounds.offsetTo(getWidth() - iconSize - getPaddingRight(), top);
             } else {
-                outBounds.offsetTo(getPaddingLeft(), outBounds.top);
+                outBounds.offsetTo(getPaddingLeft(), top);
             }
         } else {
-            outBounds.offsetTo(outBounds.left, getPaddingTop());
+            outBounds.offset((getWidth() - iconSize) / 2, getPaddingTop());
         }
     }
 
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java
index 4daca8b..af13bea 100644
--- a/src/com/android/launcher3/DefaultLayoutParser.java
+++ b/src/com/android/launcher3/DefaultLayoutParser.java
@@ -1,6 +1,5 @@
 package com.android.launcher3;
 
-import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -21,6 +20,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -55,9 +55,9 @@
     private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
             "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
 
-    public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
+    public DefaultLayoutParser(Context context, LauncherWidgetHolder appWidgetHolder,
             LayoutParserCallback callback, Resources sourceRes, int layoutId) {
-        super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES);
+        super(context, appWidgetHolder, callback, sourceRes, layoutId, TAG_FAVORITES);
     }
 
     @Override
@@ -336,11 +336,11 @@
             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
             int insertedId = -1;
             try {
-                int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
+                int appWidgetId = mAppWidgetHolder.allocateAppWidgetId();
 
                 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) {
                     Log.e(TAG, "Unable to bind app widget id " + cn);
-                    mAppWidgetHost.deleteAppWidgetId(appWidgetId);
+                    mAppWidgetHolder.deleteAppWidgetId(appWidgetId);
                     return -1;
                 }
 
@@ -349,7 +349,7 @@
                 mValues.put(Favorites._ID, mCallback.generateNewItemId());
                 insertedId = mCallback.insertAndCheck(mDb, mValues);
                 if (insertedId < 0) {
-                    mAppWidgetHost.deleteAppWidgetId(appWidgetId);
+                    mAppWidgetHolder.deleteAppWidgetId(appWidgetId);
                     return insertedId;
                 }
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 3d7d14f..9c5ec38 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -308,8 +308,7 @@
     }
 
     public static String getCurrentGridName(Context context) {
-        return Utilities.isGridOptionsEnabled(context)
-                ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) : null;
+        return Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null);
     }
 
     private String initGrid(Context context, String gridName) {
@@ -384,10 +383,6 @@
         allAppsBorderSpaces = displayOption.allAppsBorderSpaces;
         allAppsIconSize = displayOption.allAppsIconSizes;
         allAppsIconTextSize = displayOption.allAppsIconTextSizes;
-        if (!Utilities.isGridOptionsEnabled(context)) {
-            allAppsIconSize = iconSize;
-            allAppsIconTextSize = iconTextSize;
-        }
 
         if (devicePaddingId != 0) {
             devicePaddings = new DevicePaddings(context, devicePaddingId);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ab7f622..578efdf 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -210,6 +210,7 @@
 import com.android.launcher3.views.ScrimView;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.PendingAppWidgetHostView;
@@ -648,14 +649,6 @@
     }
 
     /**
-     * Called when one handed mode activated and deactivated.
-     * @param activated true if one handed mode activated, false otherwise.
-     */
-    public void onOneHandedStateChanged(boolean activated) {
-        mDragLayer.onOneHandedModeStateChanged(activated);
-    }
-
-    /**
      * Returns {@code true} if a new DeviceProfile is initialized, and {@code false} otherwise.
      */
     protected boolean initDeviceProfile(InvariantDeviceProfile idp) {
@@ -1720,6 +1713,7 @@
         } catch (NullPointerException ex) {
             Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
         }
+        mAppWidgetHolder.destroy();
 
         TextKeyListener.getInstance().release();
         clearPendingBinds();
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 58e85fe..457e126 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -22,7 +22,6 @@
 
 import android.annotation.TargetApi;
 import android.app.backup.BackupManager;
-import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.ContentProvider;
@@ -55,6 +54,8 @@
 import android.util.Log;
 import android.util.Xml;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.config.FeatureFlags;
@@ -70,7 +71,7 @@
 import com.android.launcher3.util.NoLocaleSQLiteHelper;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.widget.LauncherAppWidgetHost;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 
 import org.xmlpull.v1.XmlPullParser;
 
@@ -255,17 +256,20 @@
                     values.getAsString(Favorites.APPWIDGET_PROVIDER));
 
             if (cn != null) {
+                LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
                 try {
-                    AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
-                    int appWidgetId = widgetHost.allocateAppWidgetId();
+                    int appWidgetId = widgetHolder.allocateAppWidgetId();
                     values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
                     if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
-                        widgetHost.deleteAppWidgetId(appWidgetId);
+                        widgetHolder.deleteAppWidgetId(appWidgetId);
                         return false;
                     }
                 } catch (RuntimeException e) {
                     Log.e(TAG, "Failed to initialize external widget", e);
                     return false;
+                } finally {
+                    // Necessary to destroy the holder to free up possible activity context
+                    widgetHolder.destroy();
                 }
             } else {
                 return false;
@@ -533,10 +537,10 @@
         if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) {
             Log.d(TAG, "loading default workspace");
 
-            AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
-            AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);
+            LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
+            AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder);
             if (loader == null) {
-                loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);
+                loader = AutoInstallsLayout.get(getContext(), widgetHolder, mOpenHelper);
             }
             if (loader == null) {
                 final Partner partner = Partner.get(getContext().getPackageManager());
@@ -545,7 +549,7 @@
                     int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
                             "xml", partner.getPackageName());
                     if (workspaceResId != 0) {
-                        loader = new DefaultLayoutParser(getContext(), widgetHost,
+                        loader = new DefaultLayoutParser(getContext(), widgetHolder,
                                 mOpenHelper, partnerRes, workspaceResId);
                     }
                 }
@@ -553,7 +557,7 @@
 
             final boolean usingExternallyProvidedLayout = loader != null;
             if (loader == null) {
-                loader = getDefaultLayoutParser(widgetHost);
+                loader = getDefaultLayoutParser(widgetHolder);
             }
 
             // There might be some partially restored DB items, due to buggy restore logic in
@@ -565,9 +569,10 @@
                 // Unable to load external layout. Cleanup and load the internal layout.
                 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
-                        getDefaultLayoutParser(widgetHost));
+                        getDefaultLayoutParser(widgetHolder));
             }
             clearFlagEmptyDbCreated();
+            widgetHolder.destroy();
         }
     }
 
@@ -576,7 +581,8 @@
      *
      * @return the loader if the restrictions are set and the resource exists; null otherwise.
      */
-    private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
+    private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(
+            LauncherWidgetHolder widgetHolder) {
         Context ctx = getContext();
         final String authority;
         if (!TextUtils.isEmpty(mProviderAuthority)) {
@@ -602,7 +608,7 @@
             parser.setInput(new StringReader(layout));
 
             Log.d(TAG, "Loading layout from " + authority);
-            return new AutoInstallsLayout(ctx, widgetHost, mOpenHelper,
+            return new AutoInstallsLayout(ctx, widgetHolder, mOpenHelper,
                     ctx.getPackageManager().getResourcesForApplication(pi.applicationInfo),
                     () -> parser, AutoInstallsLayout.TAG_WORKSPACE);
         } catch (Exception e) {
@@ -621,7 +627,7 @@
                 .build();
     }
 
-    private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) {
+    private DefaultLayoutParser getDefaultLayoutParser(LauncherWidgetHolder widgetHolder) {
         InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
         int defaultLayout = mUseTestWorkspaceLayout
                 ? TEST_WORKSPACE_LAYOUT_RES_XML : idp.defaultLayoutId;
@@ -631,7 +637,7 @@
             defaultLayout = idp.demoModeLayoutId;
         }
 
-        return new DefaultLayoutParser(getContext(), widgetHost,
+        return new DefaultLayoutParser(getContext(), widgetHolder,
                 mOpenHelper, getContext().getResources(), defaultLayout);
     }
 
@@ -932,40 +938,46 @@
          */
         public void removeGhostWidgets(SQLiteDatabase db) {
             // Get all existing widget ids.
-            final AppWidgetHost host = newLauncherWidgetHost();
-            final int[] allWidgets;
+            final LauncherWidgetHolder holder = newLauncherWidgetHolder();
             try {
-                // Although the method was defined in O, it has existed since the beginning of time,
-                // so it might work on older platforms as well.
-                allWidgets = host.getAppWidgetIds();
-            } catch (IncompatibleClassChangeError e) {
-                Log.e(TAG, "getAppWidgetIds not supported", e);
-                return;
-            }
-            final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
-                    Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
-                    "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null));
-            boolean isAnyWidgetRemoved = false;
-            for (int widgetId : allWidgets) {
-                if (!validWidgets.contains(widgetId)) {
-                    try {
-                        FileLog.d(TAG, "Deleting invalid widget " + widgetId);
-                        host.deleteAppWidgetId(widgetId);
-                        isAnyWidgetRemoved = true;
-                    } catch (RuntimeException e) {
-                        // Ignore
+                final int[] allWidgets;
+                try {
+                    // Although the method was defined in O, it has existed since the beginning of
+                    // time, so it might work on older platforms as well.
+                    allWidgets = holder.getAppWidgetIds();
+                } catch (IncompatibleClassChangeError e) {
+                    Log.e(TAG, "getAppWidgetIds not supported", e);
+                    // Necessary to destroy the holder to free up possible activity context
+                    holder.destroy();
+                    return;
+                }
+                final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
+                        Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
+                        "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null));
+                boolean isAnyWidgetRemoved = false;
+                for (int widgetId : allWidgets) {
+                    if (!validWidgets.contains(widgetId)) {
+                        try {
+                            FileLog.d(TAG, "Deleting invalid widget " + widgetId);
+                            holder.deleteAppWidgetId(widgetId);
+                            isAnyWidgetRemoved = true;
+                        } catch (RuntimeException e) {
+                            // Ignore
+                        }
                     }
                 }
-            }
-            if (isAnyWidgetRemoved) {
-                final String allWidgetsIds = Arrays.stream(allWidgets).mapToObj(String::valueOf)
-                        .collect(Collectors.joining(",", "[", "]"));
-                final String validWidgetsIds = Arrays.stream(
-                        validWidgets.getArray().toArray()).mapToObj(String::valueOf)
-                        .collect(Collectors.joining(",", "[", "]"));
-                FileLog.d(TAG, "One or more widgets was removed. db_path=" + db.getPath()
-                        + " allWidgetsIds=" + allWidgetsIds
-                        + ", validWidgetsIds=" + validWidgetsIds);
+                if (isAnyWidgetRemoved) {
+                    final String allWidgetsIds = Arrays.stream(allWidgets).mapToObj(String::valueOf)
+                            .collect(Collectors.joining(",", "[", "]"));
+                    final String validWidgetsIds = Arrays.stream(
+                                    validWidgets.getArray().toArray()).mapToObj(String::valueOf)
+                            .collect(Collectors.joining(",", "[", "]"));
+                    FileLog.d(TAG, "One or more widgets was removed. db_path=" + db.getPath()
+                            + " allWidgetsIds=" + allWidgetsIds
+                            + ", validWidgetsIds=" + validWidgetsIds);
+                }
+            } finally {
+                holder.destroy();
             }
         }
 
@@ -1066,8 +1078,12 @@
             return mMaxItemId;
         }
 
-        public AppWidgetHost newLauncherWidgetHost() {
-            return new LauncherAppWidgetHost(mContext);
+        /**
+         * @return A new {@link LauncherWidgetHolder} based on the current context
+         */
+        @NonNull
+        public LauncherWidgetHolder newLauncherWidgetHolder() {
+            return new LauncherWidgetHolder(mContext);
         }
 
         @Override
diff --git a/src/com/android/launcher3/LauncherWidgetHolder.java b/src/com/android/launcher3/LauncherWidgetHolder.java
deleted file mode 100644
index 5fcd46f..0000000
--- a/src/com/android/launcher3/LauncherWidgetHolder.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/**
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3;
-
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.uioverrides.ApiWrapper;
-import com.android.launcher3.widget.LauncherAppWidgetHost;
-import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
-
-import java.util.function.IntConsumer;
-
-/**
- * A wrapper for LauncherAppWidgetHost. This class is created so the AppWidgetHost could run in
- * background.
- */
-public class LauncherWidgetHolder {
-    @NonNull
-    private final LauncherAppWidgetHost mWidgetHost;
-
-    public LauncherWidgetHolder(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public LauncherWidgetHolder(@NonNull Context context,
-            @Nullable IntConsumer appWidgetRemovedCallback) {
-        mWidgetHost = new LauncherAppWidgetHost(context, appWidgetRemovedCallback);
-    }
-
-    /**
-     * Starts listening to the widget updates from the server side
-     */
-    public void startListening() {
-        mWidgetHost.startListening();
-    }
-
-    /**
-     * Set the STARTED state of the widget host
-     * @param isStarted True if setting the host as started, false otherwise
-     */
-    public void setActivityStarted(boolean isStarted) {
-        mWidgetHost.setActivityStarted(isStarted);
-    }
-
-    /**
-     * Set the RESUMED state of the widget host
-     * @param isResumed True if setting the host as resumed, false otherwise
-     */
-    public void setActivityResumed(boolean isResumed) {
-        mWidgetHost.setActivityResumed(isResumed);
-    }
-
-    /**
-     * Set the NORMAL state of the widget host
-     * @param isNormal True if setting the host to be in normal state, false otherwise
-     */
-    public void setStateIsNormal(boolean isNormal) {
-        mWidgetHost.setStateIsNormal(isNormal);
-    }
-
-    /**
-     * Delete the specified app widget from the host
-     * @param appWidgetId The ID of the app widget to be deleted
-     */
-    public void deleteAppWidgetId(int appWidgetId) {
-        mWidgetHost.deleteAppWidgetId(appWidgetId);
-    }
-
-    /**
-     * Add the pending view to the host for complete configuration in further steps
-     * @param appWidgetId The ID of the specified app widget
-     * @param view The {@link PendingAppWidgetHostView} of the app widget
-     */
-    public void addPendingView(int appWidgetId, @NonNull PendingAppWidgetHostView view) {
-        mWidgetHost.addPendingView(appWidgetId, view);
-    }
-
-    /**
-     * @return True if the host is listening to the widget updates, false otherwise
-     */
-    public boolean isListening() {
-        return mWidgetHost.isListening();
-    }
-
-    /**
-     * @return The allocated app widget id if allocation is successful, returns -1 otherwise
-     */
-    public int allocateAppWidgetId() {
-        return mWidgetHost.allocateAppWidgetId();
-    }
-
-    /**
-     * Add a listener that is triggered when the providers of the widgets are changed
-     * @param listener The listener that notifies when the providers changed
-     */
-    public void addProviderChangeListener(
-            @NonNull LauncherAppWidgetHost.ProviderChangedListener listener) {
-        mWidgetHost.addProviderChangeListener(listener);
-    }
-
-    /**
-     * Remove the specified listener from the host
-     * @param listener The listener that is to be removed from the host
-     */
-    public void removeProviderChangeListener(
-            LauncherAppWidgetHost.ProviderChangedListener listener) {
-        mWidgetHost.removeProviderChangeListener(listener);
-    }
-
-    /**
-     * Starts the configuration activity for the widget
-     * @param activity The activity in which to start the configuration page
-     * @param widgetId The ID of the widget
-     * @param requestCode The request code
-     */
-    public void startConfigActivity(@NonNull BaseDraggingActivity activity, int widgetId,
-            int requestCode) {
-        mWidgetHost.startConfigActivity(activity, widgetId, requestCode);
-    }
-
-    /**
-     * Starts the binding flow for the widget
-     * @param activity The activity for which to bind the widget
-     * @param appWidgetId The ID of the widget
-     * @param info The {@link AppWidgetProviderInfo} of the widget
-     * @param requestCode The request code
-     */
-    public void startBindFlow(@NonNull BaseActivity activity,
-            int appWidgetId, @NonNull AppWidgetProviderInfo info, int requestCode) {
-        mWidgetHost.startBindFlow(activity, appWidgetId, info, requestCode);
-    }
-
-    /**
-     * Stop the host from listening to the widget updates
-     */
-    public void stopListening() {
-        mWidgetHost.stopListening();
-    }
-
-    /**
-     * Create a view for the specified app widget
-     * @param context The activity context for which the view is created
-     * @param appWidgetId The ID of the widget
-     * @param info The {@link LauncherAppWidgetProviderInfo} of the widget
-     * @return A view for the widget
-     */
-    @NonNull
-    public AppWidgetHostView createView(@NonNull Context context, int appWidgetId,
-            @NonNull LauncherAppWidgetProviderInfo info) {
-        return mWidgetHost.createView(context, appWidgetId, info);
-    }
-
-    /**
-     * Set the interaction handler for the widget host
-     * @param handler The interaction handler
-     */
-    public void setInteractionHandler(
-            @Nullable LauncherAppWidgetHost.LauncherWidgetInteractionHandler handler) {
-        ApiWrapper.setHostInteractionHandler(mWidgetHost, handler);
-    }
-
-    /**
-     * Clears all the views from the host
-     */
-    public void clearViews() {
-        mWidgetHost.clearViews();
-    }
-}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index a507532..743e3ae 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -26,20 +26,13 @@
 import android.app.ActivityManager;
 import android.app.Person;
 import android.app.WallpaperManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.LightingColorFilter;
@@ -51,7 +44,6 @@
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.net.Uri;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.DeadObjectException;
@@ -71,15 +63,12 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.animation.Interpolator;
-import android.widget.LinearLayout;
 
 import androidx.annotation.ChecksSdkIntAtLeast;
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
-import com.android.launcher3.graphics.GridCustomizationsProvider;
 import com.android.launcher3.graphics.TintedDrawableSpan;
 import com.android.launcher3.icons.ShortcutCachingLogic;
 import com.android.launcher3.icons.ThemedIconDrawable;
@@ -91,7 +80,6 @@
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
@@ -100,10 +88,8 @@
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
-import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -117,8 +103,6 @@
     private static final Pattern sTrimPattern =
             Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
 
-    private static final int[] sLoc0 = new int[2];
-    private static final int[] sLoc1 = new int[2];
     private static final Matrix sMatrix = new Matrix();
     private static final Matrix sInverseMatrix = new Matrix();
 
@@ -167,14 +151,6 @@
                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
     }
 
-    // An intent extra to indicate the horizontal scroll of the wallpaper.
-    public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
-    public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
-
-    // An intent extra to indicate the launch source by launcher.
-    public static final String EXTRA_WALLPAPER_LAUNCH_SOURCE =
-            "com.android.wallpaper.LAUNCH_SOURCE";
-
     public static boolean IS_RUNNING_IN_TEST_HARNESS =
                     ActivityManager.isRunningInTestHarness();
 
@@ -186,12 +162,6 @@
         return Log.isLoggable(propertyName, Log.VERBOSE);
     }
 
-    public static boolean existsStyleWallpapers(Context context) {
-        ResolveInfo ri = context.getPackageManager().resolveActivity(
-                PackageManagerHelper.getStyleWallpapersIntent(context), 0);
-        return ri != null;
-    }
-
     /**
      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
      * coordinates.
@@ -305,9 +275,9 @@
      * Sets {@param out} to be same as {@param in} by rounding individual values
      */
     public static void roundArray(float[] in, int[] out) {
-       for (int i = 0; i < in.length; i++) {
-           out[i] = Math.round(in[i]);
-       }
+        for (int i = 0; i < in.length; i++) {
+            out[i] = Math.round(in[i]);
+        }
     }
 
     public static void offsetPoints(float[] points, float offsetX, float offsetY) {
@@ -328,80 +298,8 @@
                 localY < (v.getHeight() + slop);
     }
 
-    public static int[] getCenterDeltaInScreenSpace(View v0, View v1) {
-        v0.getLocationInWindow(sLoc0);
-        v1.getLocationInWindow(sLoc1);
-
-        sLoc0[0] += (v0.getMeasuredWidth() * v0.getScaleX()) / 2;
-        sLoc0[1] += (v0.getMeasuredHeight() * v0.getScaleY()) / 2;
-        sLoc1[0] += (v1.getMeasuredWidth() * v1.getScaleX()) / 2;
-        sLoc1[1] += (v1.getMeasuredHeight() * v1.getScaleY()) / 2;
-        return new int[] {sLoc1[0] - sLoc0[0], sLoc1[1] - sLoc0[1]};
-    }
-
-    /**
-     * Helper method to set rectOut with rectFSrc.
-     */
-    public static void setRect(RectF rectFSrc, Rect rectOut) {
-        rectOut.left = (int) rectFSrc.left;
-        rectOut.top = (int) rectFSrc.top;
-        rectOut.right = (int) rectFSrc.right;
-        rectOut.bottom = (int) rectFSrc.bottom;
-    }
-
     public static void scaleRectFAboutCenter(RectF r, float scale) {
-        scaleRectFAboutPivot(r, scale, r.centerX(), r.centerY());
-    }
-
-    public static void scaleRectFAboutPivot(RectF r, float scale, float px, float py) {
-        if (scale != 1.0f) {
-            r.offset(-px, -py);
-            r.left = r.left * scale;
-            r.top = r.top * scale ;
-            r.right = r.right * scale;
-            r.bottom = r.bottom * scale;
-            r.offset(px, py);
-        }
-    }
-
-    public static void scaleRectAboutCenter(Rect r, float scale) {
-        if (scale != 1.0f) {
-            int cx = r.centerX();
-            int cy = r.centerY();
-            r.offset(-cx, -cy);
-            scaleRect(r, scale);
-            r.offset(cx, cy);
-        }
-    }
-
-    public static void scaleRect(Rect r, float scale) {
-        if (scale != 1.0f) {
-            r.left = (int) (r.left * scale + 0.5f);
-            r.top = (int) (r.top * scale + 0.5f);
-            r.right = (int) (r.right * scale + 0.5f);
-            r.bottom = (int) (r.bottom * scale + 0.5f);
-        }
-    }
-
-    public static void insetRect(Rect r, Rect insets) {
-        r.left = Math.min(r.right, r.left + insets.left);
-        r.top = Math.min(r.bottom, r.top + insets.top);
-        r.right = Math.max(r.left, r.right - insets.right);
-        r.bottom = Math.max(r.top, r.bottom - insets.bottom);
-    }
-
-    public static float shrinkRect(Rect r, float scaleX, float scaleY) {
-        float scale = Math.min(Math.min(scaleX, scaleY), 1.0f);
-        if (scale < 1.0f) {
-            int deltaX = (int) (r.width() * (scaleX - scale) * 0.5f);
-            r.left += deltaX;
-            r.right -= deltaX;
-
-            int deltaY = (int) (r.height() * (scaleY - scale) * 0.5f);
-            r.top += deltaY;
-            r.bottom -= deltaY;
-        }
-        return scale;
+        scaleRectFAboutCenter(r, scale, scale);
     }
 
     /**
@@ -419,6 +317,33 @@
         r.offset(px, py);
     }
 
+    public static void scaleRectAboutCenter(Rect r, float scale) {
+        if (scale != 1.0f) {
+            int cx = r.centerX();
+            int cy = r.centerY();
+            r.offset(-cx, -cy);
+            r.left = (int) (r.left * scale + 0.5f);
+            r.top = (int) (r.top * scale + 0.5f);
+            r.right = (int) (r.right * scale + 0.5f);
+            r.bottom = (int) (r.bottom * scale + 0.5f);
+            r.offset(cx, cy);
+        }
+    }
+
+    public static float shrinkRect(Rect r, float scaleX, float scaleY) {
+        float scale = Math.min(Math.min(scaleX, scaleY), 1.0f);
+        if (scale < 1.0f) {
+            int deltaX = (int) (r.width() * (scaleX - scale) * 0.5f);
+            r.left += deltaX;
+            r.right -= deltaX;
+
+            int deltaY = (int) (r.height() * (scaleY - scale) * 0.5f);
+            r.top += deltaY;
+            r.bottom -= deltaY;
+        }
+        return scale;
+    }
+
     /**
      * Maps t from one range to another range.
      * @param t The value to map.
@@ -454,30 +379,6 @@
     }
 
     /**
-     * Bounds parameter to the range [0, 1]
-     */
-    public static float saturate(float a) {
-        return boundToRange(a, 0, 1.0f);
-    }
-
-    /**
-     * Returns the compliment (1 - a) of the parameter.
-     */
-    public static float comp(float a) {
-        return 1 - a;
-    }
-
-    /**
-     * Returns the "probabilistic or" of a and b. (a + b - ab).
-     * Useful beyond probability, can be used to combine two unit progresses for example.
-     */
-    public static float or(float a, float b) {
-        float satA = saturate(a);
-        float satB = saturate(b);
-        return satA + satB - (satA * satB);
-    }
-
-    /**
      * Trims the string, removing all whitespace at the beginning and end of the string.
      * Non-breaking whitespaces are also removed.
      */
@@ -536,7 +437,6 @@
         return ResourceUtils.roundPxValueFromFloat(value);
     }
 
-
     public static String createDbSelectionQuery(String columnName, IntArray values) {
         return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, values.toConcatString());
     }
@@ -560,18 +460,6 @@
     }
 
     /**
-     * Using the view's bounds and icon size, calculate where the icon bounds will
-     * be if it was positioned at the center of the view.
-     */
-    public static void setRectToViewCenter(View iconView, int iconSize, Rect outBounds) {
-        int top = (iconView.getHeight() - iconSize) / 2;
-        int left = (iconView.getWidth() - iconSize) / 2;
-        int right = left + iconSize;
-        int bottom = top + iconSize;
-        outBounds.set(left, top, right, bottom);
-    }
-
-    /**
      * Ensures that a value is within given bounds. Specifically:
      * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound,
      * return upperBound; else return value unchanged.
@@ -646,42 +534,6 @@
                 || e.getCause() instanceof DeadObjectException;
     }
 
-    public static boolean isGridOptionsEnabled(Context context) {
-        return isComponentEnabled(context.getPackageManager(),
-                context.getPackageName(),
-                GridCustomizationsProvider.class.getName());
-    }
-
-    private static boolean isComponentEnabled(PackageManager pm, String pkgName, String clsName) {
-        ComponentName componentName = new ComponentName(pkgName, clsName);
-        int componentEnabledSetting = pm.getComponentEnabledSetting(componentName);
-
-        switch (componentEnabledSetting) {
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
-                return false;
-            case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
-                return true;
-            case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
-            default:
-                // We need to get the application info to get the component's default state
-                try {
-                    PackageInfo packageInfo = pm.getPackageInfo(pkgName,
-                            PackageManager.GET_PROVIDERS | PackageManager.GET_DISABLED_COMPONENTS);
-
-                    if (packageInfo.providers != null) {
-                        return Arrays.stream(packageInfo.providers).anyMatch(
-                                pi -> pi.name.equals(clsName) && pi.isEnabled());
-                    }
-
-                    // the component is not declared in the AndroidManifest
-                    return false;
-                } catch (PackageManager.NameNotFoundException e) {
-                    // the package isn't installed on the device
-                    return false;
-                }
-        }
-    }
-
     /**
      * Utility method to post a runnable on the handler, skipping the synchronization barriers.
      */
@@ -691,12 +543,6 @@
         handler.sendMessage(msg);
     }
 
-    public static void unregisterReceiverSafely(Context context, BroadcastReceiver receiver) {
-        try {
-            context.unregisterReceiver(receiver);
-        } catch (IllegalArgumentException e) {}
-    }
-
     /**
      * Returns the full drawable for info without any flattening or pre-processing.
      *
@@ -794,14 +640,6 @@
         }
     }
 
-    /**
-     * @return true is the extra is either null or is of type {@param type}
-     */
-    public static boolean isValidExtraType(Intent intent, String key, Class type) {
-        Object extra = intent.getParcelableExtra(key);
-        return extra == null || type.isInstance(extra);
-    }
-
     public static float squaredHypot(float x, float y) {
         return x * x + y * y;
     }
@@ -812,18 +650,6 @@
     }
 
     /**
-     * Helper method to create a content provider
-     */
-    public static ContentObserver newContentObserver(Handler handler, Consumer<Uri> command) {
-        return new ContentObserver(handler) {
-            @Override
-            public void onChange(boolean selfChange, Uri uri) {
-                command.accept(uri);
-            }
-        };
-    }
-
-    /**
      * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CCW. Parent
      * sizes represent the "space" that will rotate carrying inOutBounds along with it to determine
      * the final bounds.
@@ -871,16 +697,6 @@
                 ColorUtils.blendARGB(0, color, tintAmount));
     }
 
-    /**
-     * Sets start margin on the provided {@param view} to be {@param margin}.
-     * Assumes {@param view} is a child of {@link LinearLayout}
-     */
-    public static void setStartMarginForView(View view, int margin) {
-        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) view.getLayoutParams();
-        lp.setMarginStart(margin);
-        view.setLayoutParams(lp);
-    }
-
     public static Rect getViewBounds(@NonNull View v) {
         int[] pos = new int[2];
         v.getLocationOnScreen(pos);
@@ -921,12 +737,4 @@
         }
         return options;
     }
-
-    public static boolean bothNull(@Nullable Object a, @Nullable Object b) {
-        return a == null && b == null;
-    }
-
-    public static boolean bothNonNull(@Nullable Object a, @Nullable Object b) {
-        return a != null && b != null;
-    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a8def69..517bbf8 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -107,8 +107,9 @@
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.WallpaperOffsetInterpolator;
-import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.LauncherWidgetHolder;
+import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
 import com.android.launcher3.widget.NavigableAppWidgetHostView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 4c461aa..4d58eb0 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -15,9 +15,6 @@
  */
 package com.android.launcher3.allapps.search;
 
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_FOCUSED_ITEM_SELECTED_WITH_IME;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_QUICK_SEARCH_WITH_IME;
-
 import android.text.Editable;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
@@ -125,10 +122,6 @@
     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
 
         if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
-            mLauncher.getStatsLogManager().logger()
-                    .log(actionId == EditorInfo.IME_ACTION_SEARCH
-                            ? LAUNCHER_ALLAPPS_QUICK_SEARCH_WITH_IME
-                            : LAUNCHER_ALLAPPS_FOCUSED_ITEM_SELECTED_WITH_IME);
             // selectFocusedView should return SearchTargetEvent that is passed onto onClick
             return mLauncher.getAppsView().getMainAdapterProvider().launchHighlightedItem();
         }
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 05b1984..5a49f4a 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -53,6 +53,8 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
@@ -69,8 +71,8 @@
 import com.android.launcher3.views.AbstractSlideInView;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.widget.AddItemWidgetsBottomSheet;
-import com.android.launcher3.widget.LauncherAppWidgetHost;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 import com.android.launcher3.widget.NavigableAppWidgetHostView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -105,7 +107,8 @@
     private WidgetCell mWidgetCell;
 
     // Widget request specific options.
-    private LauncherAppWidgetHost mAppWidgetHost;
+    @Nullable
+    private LauncherWidgetHolder mAppWidgetHolder = null;
     private WidgetManagerHelper mAppWidgetManager;
     private int mPendingBindWidgetId;
     private Bundle mWidgetOptions;
@@ -284,7 +287,7 @@
         mWidgetCell.setRemoteViewsPreview(PinItemDragListener.getPreview(mRequest));
 
         mAppWidgetManager = new WidgetManagerHelper(this);
-        mAppWidgetHost = new LauncherAppWidgetHost(this);
+        mAppWidgetHolder = new LauncherWidgetHolder(this);
 
         PendingAddWidgetInfo pendingInfo =
                 new PendingAddWidgetInfo(widgetInfo, CONTAINER_PIN_WIDGETS);
@@ -338,7 +341,7 @@
             return;
         }
 
-        mPendingBindWidgetId = mAppWidgetHost.allocateAppWidgetId();
+        mPendingBindWidgetId = mAppWidgetHolder.allocateAppWidgetId();
         AppWidgetProviderInfo widgetProviderInfo = mRequest.getAppWidgetProviderInfo(this);
         boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
                 mPendingBindWidgetId, widgetProviderInfo, mWidgetOptions);
@@ -349,7 +352,7 @@
         }
 
         // request bind widget
-        mAppWidgetHost.startBindFlow(this, mPendingBindWidgetId,
+        mAppWidgetHolder.startBindFlow(this, mPendingBindWidgetId,
                 mRequest.getAppWidgetProviderInfo(this), REQUEST_BIND_APPWIDGET);
     }
 
@@ -363,6 +366,15 @@
     }
 
     @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mAppWidgetHolder != null) {
+            // Necessary to destroy the holder to free up possible activity context
+            mAppWidgetHolder.destroy();
+        }
+    }
+
+    @Override
     public void onBackPressed() {
         logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_BACK);
         mSlideInView.close(/* animate= */ true);
@@ -378,7 +390,7 @@
                 acceptWidget(widgetId);
             } else {
                 // Simply wait it out.
-                mAppWidgetHost.deleteAppWidgetId(widgetId);
+                mAppWidgetHolder.deleteAppWidgetId(widgetId);
                 mPendingBindWidgetId = -1;
             }
             return;
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 1ee7fc1..366870b 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -56,7 +56,6 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.Scrim;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
-import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks;
 
@@ -474,16 +473,6 @@
         return mWorkspaceDragScrim;
     }
 
-    /**
-     * Called when one handed mode state changed.
-     * @param activated true if one handed mode activated, false otherwise.
-     */
-    public void onOneHandedModeStateChanged(boolean activated) {
-        for (TouchController controller : mControllers) {
-            controller.onOneHandedModeStateChanged(activated);
-        }
-    }
-
     @Override
     public void onOverlayScrollChanged(float progress) {
         float alpha = 1 - Interpolators.DEACCEL_3.getInterpolation(progress);
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index e8759da..dd00f07 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -616,11 +616,14 @@
     public void drawDot(Canvas canvas) {
         if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) {
             Rect iconBounds = mDotParams.iconBounds;
+            // FolderIcon draws the icon to be top-aligned (with padding) & horizontally-centered
+            int iconSize = mActivity.getDeviceProfile().iconSizePx;
+            iconBounds.left = (getWidth() - iconSize) / 2;
+            iconBounds.right = iconBounds.left + iconSize;
+            iconBounds.top = getPaddingTop();
+            iconBounds.bottom = iconBounds.top + iconSize;
 
-            Utilities.setRectToViewCenter(this, mActivity.getDeviceProfile().iconSizePx,
-                    iconBounds);
-            iconBounds.offsetTo(iconBounds.left, getPaddingTop());
-            float iconScale = (float) mBackground.previewSize / iconBounds.width();
+            float iconScale = (float) mBackground.previewSize / iconSize;
             Utilities.scaleRectAboutCenter(iconBounds, iconScale);
 
             // If we are animating to the accepting state, animate the dot out.
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 482e923..2361907 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -99,8 +99,8 @@
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.widget.BaseLauncherAppWidgetHostView;
-import com.android.launcher3.widget.LauncherAppWidgetHost;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 import com.android.launcher3.widget.LocalColorExtractor;
 import com.android.launcher3.widget.NavigableAppWidgetHostView;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
@@ -554,7 +554,7 @@
     private class LauncherPreviewAppWidgetHost extends AppWidgetHost {
 
         private LauncherPreviewAppWidgetHost(Context context) {
-            super(context, LauncherAppWidgetHost.APPWIDGET_HOST_ID);
+            super(context, LauncherWidgetHolder.APPWIDGET_HOST_ID);
         }
 
         @Override
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 1ced0b1..c21fc38 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.model;
 
-import static com.android.launcher3.Utilities.isValidExtraType;
-
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -149,4 +147,12 @@
         info.intent = launchIntent;
         return info;
     }
+
+    /**
+     * @return true if the extra is either null or is of type {@param type}
+     */
+    private static boolean isValidExtraType(Intent intent, String key, Class type) {
+        Object extra = intent.getParcelableExtra(key);
+        return extra == null || type.isInstance(extra);
+    }
 }
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 514e7b2..f444bd5 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherSettings.Settings;
-import com.android.launcher3.LauncherWidgetHolder;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.FileLog;
@@ -49,6 +48,7 @@
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LooperExecutor;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 48b3acf..95ac7b0 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -47,6 +47,7 @@
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.LogConfig;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 
 import java.io.InvalidObjectException;
 import java.util.Arrays;
@@ -353,9 +354,12 @@
     private void restoreAppWidgetIdsIfExists(Context context) {
         SharedPreferences prefs = Utilities.getPrefs(context);
         if (prefs.contains(APPWIDGET_OLD_IDS) && prefs.contains(APPWIDGET_IDS)) {
+            LauncherWidgetHolder holder = new LauncherWidgetHolder(context);
             AppWidgetsRestoredReceiver.restoreAppWidgetIds(context,
                     IntArray.fromConcatString(prefs.getString(APPWIDGET_OLD_IDS, "")).toArray(),
-                    IntArray.fromConcatString(prefs.getString(APPWIDGET_IDS, "")).toArray());
+                    IntArray.fromConcatString(prefs.getString(APPWIDGET_IDS, "")).toArray(),
+                    holder);
+            holder.destroy();
         } else {
             FileLog.d(TAG, "No app widget ids to restore.");
         }
diff --git a/src/com/android/launcher3/util/BgObjectWithLooper.java b/src/com/android/launcher3/util/BgObjectWithLooper.java
index 1483c43..adc3c7d 100644
--- a/src/com/android/launcher3/util/BgObjectWithLooper.java
+++ b/src/com/android/launcher3/util/BgObjectWithLooper.java
@@ -15,10 +15,15 @@
  */
 package com.android.launcher3.util;
 
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
 import android.os.Looper;
 
 import androidx.annotation.WorkerThread;
 
+import java.util.function.Consumer;
+
 /**
  * Utility class to define an object which does most of it's processing on a
  * dedicated background thread.
@@ -43,4 +48,16 @@
      */
     @WorkerThread
     protected abstract void onInitialized(Looper looper);
+
+    /**
+     * Helper method to create a content provider
+     */
+    protected static ContentObserver newContentObserver(Handler handler, Consumer<Uri> command) {
+        return new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                command.accept(uri);
+            }
+        };
+    }
 }
diff --git a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
index 4dfa5cc..0a23506 100644
--- a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
+++ b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
@@ -52,4 +52,15 @@
         }
         context.registerReceiver(this, filter, flags);
     }
+
+    /**
+     * Unregisters the receiver ignoring any errors
+     */
+    public void unregisterReceiverSafely(Context context) {
+        try {
+            context.unregisterReceiver(this);
+        } catch (IllegalArgumentException e) {
+            // It was probably never registered or already unregistered. Ignore.
+        }
+    }
 }
diff --git a/src/com/android/launcher3/util/TouchController.java b/src/com/android/launcher3/util/TouchController.java
index 9c397c0..fc1d819 100644
--- a/src/com/android/launcher3/util/TouchController.java
+++ b/src/com/android/launcher3/util/TouchController.java
@@ -32,10 +32,5 @@
      */
     boolean onControllerInterceptTouchEvent(MotionEvent ev);
 
-    /**
-     * Called when one handed mode state changed
-     */
-    default void onOneHandedModeStateChanged(boolean activated) { }
-
     default void dump(String prefix, PrintWriter writer) { }
 }
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index d301925..b3c376f 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -15,9 +15,6 @@
  */
 package com.android.launcher3.views;
 
-import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR;
-import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_LAUNCH_SOURCE;
-import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS;
@@ -51,6 +48,7 @@
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
 
 import java.util.ArrayList;
@@ -62,6 +60,13 @@
 public class OptionsPopupView extends ArrowPopup<Launcher>
         implements OnClickListener, OnLongClickListener {
 
+    // An intent extra to indicate the horizontal scroll of the wallpaper.
+    private static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
+    private static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
+    // An intent extra to indicate the launch source by launcher.
+    private static final String EXTRA_WALLPAPER_LAUNCH_SOURCE =
+            "com.android.wallpaper.LAUNCH_SOURCE";
+
     private final ArrayMap<View, OptionItem> mItemMap = new ArrayMap<>();
     private RectF mTargetRect;
     private boolean mShouldAddArrow;
@@ -180,10 +185,11 @@
      */
     public static ArrayList<OptionItem> getOptions(Launcher launcher) {
         ArrayList<OptionItem> options = new ArrayList<>();
-        int resString = Utilities.existsStyleWallpapers(launcher) ?
-                R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
-        int resDrawable = Utilities.existsStyleWallpapers(launcher) ?
-                R.drawable.ic_palette : R.drawable.ic_wallpaper;
+        boolean styleWallpaperExists = styleWallpapersExists(launcher);
+        int resString = styleWallpaperExists
+                ? R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
+        int resDrawable = styleWallpaperExists
+                ? R.drawable.ic_palette : R.drawable.ic_wallpaper;
         options.add(new OptionItem(launcher,
                 resString,
                 resDrawable,
@@ -251,7 +257,7 @@
                 .putExtra(EXTRA_WALLPAPER_OFFSET,
                         launcher.getWorkspace().getWallpaperOffsetForCenterPage())
                 .putExtra(EXTRA_WALLPAPER_LAUNCH_SOURCE, "app_launched_launcher");
-        if (!Utilities.existsStyleWallpapers(launcher)) {
+        if (!styleWallpapersExists(launcher)) {
             intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "wallpaper_only");
         } else {
             intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "focus_wallpaper");
@@ -299,4 +305,9 @@
             this.clickListener = clickListener;
         }
     }
+
+    private static boolean styleWallpapersExists(Context context) {
+        return context.getPackageManager().resolveActivity(
+                PackageManagerHelper.getStyleWallpapersIntent(context), 0) != null;
+    }
 }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index fff8fbb..9c21ea2 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -16,301 +16,87 @@
 
 package com.android.launcher3.widget;
 
-import static android.app.Activity.RESULT_CANCELED;
+import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
 
-import android.app.PendingIntent;
 import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
-import android.content.ActivityNotFoundException;
 import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.util.SparseArray;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.Toast;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.WidgetsModel;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.ArrayList;
 import java.util.function.IntConsumer;
 
-
 /**
  * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView}
  * which correctly captures all long-press events. This ensures that users can
  * always pick up and move widgets.
  */
-public class LauncherAppWidgetHost extends AppWidgetHost {
+class LauncherAppWidgetHost extends AppWidgetHost {
+    @NonNull
+    private final ArrayList<LauncherWidgetHolder.ProviderChangedListener>
+            mProviderChangeListeners = new ArrayList<>();
 
-    private static final int FLAG_LISTENING = 1;
-    private static final int FLAG_STATE_IS_NORMAL = 1 << 1;
-    private static final int FLAG_ACTIVITY_STARTED = 1 << 2;
-    private static final int FLAG_ACTIVITY_RESUMED = 1 << 3;
-    private static final int FLAGS_SHOULD_LISTEN =
-            FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED;
-    // TODO(b/191735836): Replace with ActivityOptions.KEY_SPLASH_SCREEN_STYLE when un-hidden
-    private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle";
-    // TODO(b/191735836): Replace with SplashScreen.SPLASH_SCREEN_STYLE_EMPTY when un-hidden
-    private static final int SPLASH_SCREEN_STYLE_EMPTY = 0;
-
-    public static final int APPWIDGET_HOST_ID = 1024;
-
-    private final ArrayList<ProviderChangedListener> mProviderChangeListeners = new ArrayList<>();
-    private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
-    private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
-    private final SparseArray<LauncherAppWidgetHostView> mDeferredViews = new SparseArray<>();
-    private final SparseArray<RemoteViews> mCachedRemoteViews = new SparseArray<>();
-
+    @NonNull
     private final Context mContext;
-    private int mFlags = FLAG_STATE_IS_NORMAL;
 
-    private IntConsumer mAppWidgetRemovedCallback = null;
+    @Nullable
+    private final IntConsumer mAppWidgetRemovedCallback;
 
-    /**
-     * This serves for the purpose of getting rid of the hidden API calling of InteractionHandler
-     */
-    public interface LauncherWidgetInteractionHandler {
-        /**
-         * Invoked when the user performs an interaction on the View.
-         *
-         * @param view the View with which the user interacted
-         * @param pendingIntent the base PendingIntent associated with the view
-         * @param response the response to the interaction, which knows how to fill in the
-         *                 attached PendingIntent
-         */
-        boolean onInteraction(
-                View view,
-                PendingIntent pendingIntent,
-                RemoteViews.RemoteResponse response);
-    }
+    @NonNull
+    private final LauncherWidgetHolder mHolder;
 
-    public LauncherAppWidgetHost(Context context) {
-        this(context, null);
-    }
-
-    public LauncherAppWidgetHost(Context context,
-            IntConsumer appWidgetRemovedCallback) {
+    public LauncherAppWidgetHost(@NonNull Context context,
+            @Nullable IntConsumer appWidgetRemovedCallback, @NonNull LauncherWidgetHolder holder) {
         super(context, APPWIDGET_HOST_ID);
         mContext = context;
         mAppWidgetRemovedCallback = appWidgetRemovedCallback;
-    }
-
-    @Override
-    protected LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
-            AppWidgetProviderInfo appWidget) {
-        final LauncherAppWidgetHostView view;
-        if (mPendingViews.get(appWidgetId) != null) {
-            view = mPendingViews.get(appWidgetId);
-            mPendingViews.remove(appWidgetId);
-        } else if (mDeferredViews.get(appWidgetId) != null) {
-            // In case the widget view is deferred, we will simply return the deferred view as
-            // opposed to instantiate a new instance of LauncherAppWidgetHostView since launcher
-            // already added the former to the workspace.
-            view = mDeferredViews.get(appWidgetId);
-        } else {
-            view = new LauncherAppWidgetHostView(context);
-        }
-        mViews.put(appWidgetId, view);
-        return view;
-    }
-
-    @Override
-    public void startListening() {
-        if (WidgetsModel.GO_DISABLE_WIDGETS) {
-            return;
-        }
-        mFlags |= FLAG_LISTENING;
-        try {
-            super.startListening();
-        } catch (Exception e) {
-            if (!Utilities.isBinderSizeError(e)) {
-                throw new RuntimeException(e);
-            }
-            // We're willing to let this slide. The exception is being caused by the list of
-            // RemoteViews which is being passed back. The startListening relationship will
-            // have been established by this point, and we will end up populating the
-            // widgets upon bind anyway. See issue 14255011 for more context.
-        }
-
-        // We go in reverse order and inflate any deferred or cached widget
-        for (int i = mViews.size() - 1; i >= 0; i--) {
-            LauncherAppWidgetHostView view = mViews.valueAt(i);
-            if (view instanceof DeferredAppWidgetHostView) {
-                view.reInflate();
-            }
-            if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
-                final int appWidgetId = mViews.keyAt(i);
-                if (view == mDeferredViews.get(appWidgetId)) {
-                    // If the widget view was deferred, we'll need to call super.createView here
-                    // to make the binder call to system process to fetch cumulative updates to this
-                    // widget, as well as setting up this view for future updates.
-                    super.createView(view.mLauncher, appWidgetId, view.getAppWidgetInfo());
-                    // At this point #onCreateView should have been called, which in turn returned
-                    // the deferred view. There's no reason to keep the reference anymore, so we
-                    // removed it here.
-                    mDeferredViews.remove(appWidgetId);
-                }
-            }
-        }
-    }
-
-    @Override
-    public void stopListening() {
-        if (WidgetsModel.GO_DISABLE_WIDGETS) {
-            return;
-        }
-        mFlags &= ~FLAG_LISTENING;
-        super.stopListening();
-    }
-
-    public boolean isListening() {
-        return (mFlags & FLAG_LISTENING) != 0;
+        mHolder = holder;
     }
 
     /**
-     * Sets or unsets a flag the can change whether the widget host should be in the listening
-     * state.
+     * Add a listener that is triggered when the providers of the widgets are changed
+     * @param listener The listener that notifies when the providers changed
      */
-    private void setShouldListenFlag(int flag, boolean on) {
-        if (on) {
-            mFlags |= flag;
-        } else {
-            mFlags &= ~flag;
-        }
-
-        final boolean listening = isListening();
-        if (!listening && (mFlags & FLAGS_SHOULD_LISTEN) == FLAGS_SHOULD_LISTEN) {
-            // Postpone starting listening until all flags are on.
-            startListening();
-        } else if (listening && (mFlags & FLAG_ACTIVITY_STARTED) == 0) {
-            // Postpone stopping listening until the activity is stopped.
-            stopListening();
-        }
+    public void addProviderChangeListener(
+            @NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
+        mProviderChangeListeners.add(listener);
     }
 
     /**
-     * Registers an "entering/leaving Normal state" event.
+     * Remove the specified listener from the host
+     * @param listener The listener that is to be removed from the host
      */
-    public void setStateIsNormal(boolean isNormal) {
-        setShouldListenFlag(FLAG_STATE_IS_NORMAL, isNormal);
-    }
-
-    /**
-     * Registers an "activity started/stopped" event.
-     */
-    public void setActivityStarted(boolean isStarted) {
-        setShouldListenFlag(FLAG_ACTIVITY_STARTED, isStarted);
-    }
-
-    /**
-     * Registers an "activity paused/resumed" event.
-     */
-    public void setActivityResumed(boolean isResumed) {
-        setShouldListenFlag(FLAG_ACTIVITY_RESUMED, isResumed);
+    public void removeProviderChangeListener(
+            LauncherWidgetHolder.ProviderChangedListener listener) {
+        mProviderChangeListeners.remove(listener);
     }
 
     @Override
-    public int allocateAppWidgetId() {
-        if (WidgetsModel.GO_DISABLE_WIDGETS) {
-            return AppWidgetManager.INVALID_APPWIDGET_ID;
-        }
-
-        return super.allocateAppWidgetId();
-    }
-
-    public void addProviderChangeListener(ProviderChangedListener callback) {
-        mProviderChangeListeners.add(callback);
-    }
-
-    public void removeProviderChangeListener(ProviderChangedListener callback) {
-        mProviderChangeListeners.remove(callback);
-    }
-
     protected void onProvidersChanged() {
         if (!mProviderChangeListeners.isEmpty()) {
-            for (ProviderChangedListener callback : new ArrayList<>(mProviderChangeListeners)) {
+            for (LauncherWidgetHolder.ProviderChangedListener callback :
+                    new ArrayList<>(mProviderChangeListeners)) {
                 callback.notifyWidgetProvidersChanged();
             }
         }
     }
 
-    public void addPendingView(int appWidgetId, PendingAppWidgetHostView view) {
-        mPendingViews.put(appWidgetId, view);
-    }
-
-    public AppWidgetHostView createView(Context context, int appWidgetId,
-            LauncherAppWidgetProviderInfo appWidget) {
-        if (appWidget.isCustomWidget()) {
-            LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
-            lahv.setAppWidget(0, appWidget);
-            CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
-            return lahv;
-        } else if ((mFlags & FLAG_LISTENING) == 0) {
-            // Since the launcher hasn't started listening to widget updates, we can't simply call
-            // super.createView here because the later will make a binder call to retrieve
-            // RemoteViews from system process.
-            // TODO: have launcher always listens to widget updates in background so that this
-            //  check can be removed altogether.
-            if (FeatureFlags.ENABLE_CACHED_WIDGET.get()
-                    && mCachedRemoteViews.get(appWidgetId) != null) {
-                // We've found RemoteViews from cache for this widget, so we will instantiate a
-                // widget host view and populate it with the cached RemoteViews.
-                final LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
-                view.setAppWidget(appWidgetId, appWidget);
-                view.updateAppWidget(mCachedRemoteViews.get(appWidgetId));
-                mDeferredViews.put(appWidgetId, view);
-                mViews.put(appWidgetId, view);
-                return view;
-            } else {
-                // When cache misses, a placeholder for the widget will be returned instead.
-                DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
-                view.setAppWidget(appWidgetId, appWidget);
-                mViews.put(appWidgetId, view);
-                return view;
-            }
-        } else {
-            try {
-                return super.createView(context, appWidgetId, appWidget);
-            } catch (Exception e) {
-                if (!Utilities.isBinderSizeError(e)) {
-                    throw new RuntimeException(e);
-                }
-
-                // If the exception was thrown while fetching the remote views, let the view stay.
-                // This will ensure that if the widget posts a valid update later, the view
-                // will update.
-                LauncherAppWidgetHostView view = mViews.get(appWidgetId);
-                if (view == null) {
-                    view = onCreateView(mContext, appWidgetId, appWidget);
-                }
-                view.setAppWidget(appWidgetId, appWidget);
-                view.switchToErrorView();
-                return view;
-            }
-        }
+    @Override
+    @NonNull
+    public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
+            AppWidgetProviderInfo appWidget) {
+        return mHolder.onCreateView(context, appWidgetId, appWidget);
     }
 
     /**
      * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
      */
     @Override
-    protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
+    protected void onProviderChanged(int appWidgetId, @NonNull AppWidgetProviderInfo appWidget) {
         LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo.fromProviderInfo(
                 mContext, appWidget);
         super.onProviderChanged(appWidgetId, info);
@@ -324,6 +110,7 @@
      *
      * @param appWidgetId TODO: make this override when SDK is updated
      */
+    @Override
     public void onAppWidgetRemoved(int appWidgetId) {
         if (mAppWidgetRemovedCallback == null) {
             return;
@@ -331,92 +118,12 @@
         mAppWidgetRemovedCallback.accept(appWidgetId);
     }
 
-    @Override
-    public void deleteAppWidgetId(int appWidgetId) {
-        super.deleteAppWidgetId(appWidgetId);
-        mViews.remove(appWidgetId);
-    }
-
+    /**
+     * The same as super.clearViews(), except with the scope exposed
+     */
     @Override
     public void clearViews() {
         super.clearViews();
-        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
-            // First, we clear any previously cached content from existing widgets
-            mCachedRemoteViews.clear();
-            mDeferredViews.clear();
-            // Then we proceed to cache the content from the widgets
-            for (int i = 0; i < mViews.size(); i++) {
-                final int appWidgetId = mViews.keyAt(i);
-                final LauncherAppWidgetHostView view = mViews.get(appWidgetId);
-                mCachedRemoteViews.put(appWidgetId, view.mLastRemoteViews);
-            }
-        }
-        mViews.clear();
     }
 
-    public void startBindFlow(BaseActivity activity,
-            int appWidgetId, AppWidgetProviderInfo info, int requestCode) {
-
-        if (WidgetsModel.GO_DISABLE_WIDGETS) {
-            sendActionCancelled(activity, requestCode);
-            return;
-        }
-
-        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND)
-                .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
-                .putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.provider)
-                .putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE, info.getProfile());
-        // TODO: we need to make sure that this accounts for the options bundle.
-        // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
-        activity.startActivityForResult(intent, requestCode);
-    }
-
-    /**
-     * Launches an app widget's configuration activity.
-     * @param activity The activity from which to launch the configuration activity
-     * @param widgetId The id of the bound app widget to be configured
-     * @param requestCode An optional request code to be returned with the result
-     */
-    public void startConfigActivity(BaseDraggingActivity activity, int widgetId, int requestCode) {
-        if (WidgetsModel.GO_DISABLE_WIDGETS) {
-            sendActionCancelled(activity, requestCode);
-            return;
-        }
-
-        try {
-            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: startConfigActivity");
-            startAppWidgetConfigureActivityForResult(activity, widgetId, 0, requestCode,
-                    getConfigurationActivityOptions(activity, widgetId));
-        } catch (ActivityNotFoundException | SecurityException e) {
-            Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
-            sendActionCancelled(activity, requestCode);
-        }
-    }
-
-    /**
-     * Returns an {@link android.app.ActivityOptions} bundle from the {code activity} for launching
-     * the configuration of the {@code widgetId} app widget, or null of options cannot be produced.
-     */
-    @Nullable
-    private Bundle getConfigurationActivityOptions(BaseDraggingActivity activity, int widgetId) {
-        LauncherAppWidgetHostView view = mViews.get(widgetId);
-        if (view == null) return null;
-        Object tag = view.getTag();
-        if (!(tag instanceof ItemInfo)) return null;
-        Bundle bundle = activity.getActivityLaunchOptions(view, (ItemInfo) tag).toBundle();
-        bundle.putInt(KEY_SPLASH_SCREEN_STYLE, SPLASH_SCREEN_STYLE_EMPTY);
-        return bundle;
-    }
-
-    private void sendActionCancelled(final BaseActivity activity, final int requestCode) {
-        new Handler().post(() -> activity.onActivityResult(requestCode, RESULT_CANCELED, null));
-    }
-
-    /**
-     * Listener for getting notifications on provider changes.
-     */
-    public interface ProviderChangedListener {
-
-        void notifyWidgetProvidersChanged();
-    }
 }
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
new file mode 100644
index 0000000..5497729
--- /dev/null
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -0,0 +1,508 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget;
+
+import static android.app.Activity.RESULT_CANCELED;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
+
+import java.util.function.IntConsumer;
+
+/**
+ * A wrapper for LauncherAppWidgetHost. This class is created so the AppWidgetHost could run in
+ * background.
+ */
+public class LauncherWidgetHolder {
+    public static final int APPWIDGET_HOST_ID = 1024;
+
+    private static final int FLAG_LISTENING = 1;
+    private static final int FLAG_STATE_IS_NORMAL = 1 << 1;
+    private static final int FLAG_ACTIVITY_STARTED = 1 << 2;
+    private static final int FLAG_ACTIVITY_RESUMED = 1 << 3;
+    private static final int FLAGS_SHOULD_LISTEN =
+            FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED;
+
+    @NonNull
+    private final Context mContext;
+
+    @NonNull
+    private final AppWidgetHost mWidgetHost;
+
+    @NonNull
+    private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
+    @NonNull
+    private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
+    @NonNull
+    private final SparseArray<LauncherAppWidgetHostView> mDeferredViews = new SparseArray<>();
+    @NonNull
+    private final SparseArray<RemoteViews> mCachedRemoteViews = new SparseArray<>();
+
+    private int mFlags = FLAG_STATE_IS_NORMAL;
+
+    // TODO(b/191735836): Replace with ActivityOptions.KEY_SPLASH_SCREEN_STYLE when un-hidden
+    private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle";
+    // TODO(b/191735836): Replace with SplashScreen.SPLASH_SCREEN_STYLE_EMPTY when un-hidden
+    private static final int SPLASH_SCREEN_STYLE_EMPTY = 0;
+
+    public LauncherWidgetHolder(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public LauncherWidgetHolder(@NonNull Context context,
+            @Nullable IntConsumer appWidgetRemovedCallback) {
+        mContext = context;
+        mWidgetHost = createHost(context, appWidgetRemovedCallback);
+    }
+
+    protected AppWidgetHost createHost(
+            Context context, @Nullable IntConsumer appWidgetRemovedCallback) {
+        return new LauncherAppWidgetHost(context, appWidgetRemovedCallback, this);
+    }
+
+    /**
+     * Starts listening to the widget updates from the server side
+     */
+    public void startListening() {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
+            return;
+        }
+        setListeningFlag(true);
+        try {
+            mWidgetHost.startListening();
+        } catch (Exception e) {
+            if (!Utilities.isBinderSizeError(e)) {
+                throw new RuntimeException(e);
+            }
+            // We're willing to let this slide. The exception is being caused by the list of
+            // RemoteViews which is being passed back. The startListening relationship will
+            // have been established by this point, and we will end up populating the
+            // widgets upon bind anyway. See issue 14255011 for more context.
+        }
+
+        // We go in reverse order and inflate any deferred or cached widget
+        for (int i = mViews.size() - 1; i >= 0; i--) {
+            LauncherAppWidgetHostView view = mViews.valueAt(i);
+            if (view instanceof DeferredAppWidgetHostView) {
+                view.reInflate();
+            }
+            if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
+                final int appWidgetId = mViews.keyAt(i);
+                if (view == mDeferredViews.get(appWidgetId)) {
+                    // If the widget view was deferred, we'll need to call super.createView here
+                    // to make the binder call to system process to fetch cumulative updates to this
+                    // widget, as well as setting up this view for future updates.
+                    mWidgetHost.createView(view.mLauncher, appWidgetId,
+                            view.getAppWidgetInfo());
+                    // At this point #onCreateView should have been called, which in turn returned
+                    // the deferred view. There's no reason to keep the reference anymore, so we
+                    // removed it here.
+                    mDeferredViews.remove(appWidgetId);
+                }
+            }
+        }
+    }
+
+    /**
+     * Registers an "activity started/stopped" event.
+     */
+    public void setActivityStarted(boolean isStarted) {
+        setShouldListenFlag(FLAG_ACTIVITY_STARTED, isStarted);
+    }
+
+    /**
+     * Registers an "activity paused/resumed" event.
+     */
+    public void setActivityResumed(boolean isResumed) {
+        setShouldListenFlag(FLAG_ACTIVITY_RESUMED, isResumed);
+    }
+
+    /**
+     * Set the NORMAL state of the widget host
+     * @param isNormal True if setting the host to be in normal state, false otherwise
+     */
+    public void setStateIsNormal(boolean isNormal) {
+        setShouldListenFlag(FLAG_STATE_IS_NORMAL, isNormal);
+    }
+
+    /**
+     * Delete the specified app widget from the host
+     * @param appWidgetId The ID of the app widget to be deleted
+     */
+    public void deleteAppWidgetId(int appWidgetId) {
+        mWidgetHost.deleteAppWidgetId(appWidgetId);
+        mViews.remove(appWidgetId);
+    }
+
+    /**
+     * Add the pending view to the host for complete configuration in further steps
+     * @param appWidgetId The ID of the specified app widget
+     * @param view The {@link PendingAppWidgetHostView} of the app widget
+     */
+    public void addPendingView(int appWidgetId, @NonNull PendingAppWidgetHostView view) {
+        mPendingViews.put(appWidgetId, view);
+    }
+
+    /**
+     * @param appWidgetId The app widget id of the specified widget
+     * @return The {@link PendingAppWidgetHostView} of the widget if it exists, null otherwise
+     */
+    @Nullable
+    protected PendingAppWidgetHostView getPendingView(int appWidgetId) {
+        return mPendingViews.get(appWidgetId);
+    }
+
+    protected void removePendingView(int appWidgetId) {
+        mPendingViews.remove(appWidgetId);
+    }
+
+    /**
+     * Called when the launcher is destroyed
+     */
+    public void destroy() {
+        // No-op
+    }
+
+    /**
+     * @return The allocated app widget id if allocation is successful, returns -1 otherwise
+     */
+    public int allocateAppWidgetId() {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
+            return AppWidgetManager.INVALID_APPWIDGET_ID;
+        }
+
+        return mWidgetHost.allocateAppWidgetId();
+    }
+
+    /**
+     * Add a listener that is triggered when the providers of the widgets are changed
+     * @param listener The listener that notifies when the providers changed
+     */
+    public void addProviderChangeListener(@NonNull ProviderChangedListener listener) {
+        LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
+        tempHost.addProviderChangeListener(listener);
+    }
+
+    /**
+     * Remove the specified listener from the host
+     * @param listener The listener that is to be removed from the host
+     */
+    public void removeProviderChangeListener(ProviderChangedListener listener) {
+        LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
+        tempHost.removeProviderChangeListener(listener);
+    }
+
+    /**
+     * Starts the configuration activity for the widget
+     * @param activity The activity in which to start the configuration page
+     * @param widgetId The ID of the widget
+     * @param requestCode The request code
+     */
+    public void startConfigActivity(@NonNull BaseDraggingActivity activity, int widgetId,
+            int requestCode) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
+            sendActionCancelled(activity, requestCode);
+            return;
+        }
+
+        try {
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: startConfigActivity");
+            mWidgetHost.startAppWidgetConfigureActivityForResult(activity, widgetId, 0, requestCode,
+                    getConfigurationActivityOptions(activity, widgetId));
+        } catch (ActivityNotFoundException | SecurityException e) {
+            Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+            sendActionCancelled(activity, requestCode);
+        }
+    }
+
+    private void sendActionCancelled(final BaseActivity activity, final int requestCode) {
+        MAIN_EXECUTOR.execute(
+                () -> activity.onActivityResult(requestCode, RESULT_CANCELED, null));
+    }
+
+    /**
+     * Returns an {@link android.app.ActivityOptions} bundle from the {code activity} for launching
+     * the configuration of the {@code widgetId} app widget, or null of options cannot be produced.
+     */
+    @Nullable
+    protected Bundle getConfigurationActivityOptions(@NonNull BaseDraggingActivity activity,
+            int widgetId) {
+        LauncherAppWidgetHostView view = mViews.get(widgetId);
+        if (view == null) return null;
+        Object tag = view.getTag();
+        if (!(tag instanceof ItemInfo)) return null;
+        Bundle bundle = activity.getActivityLaunchOptions(view, (ItemInfo) tag).toBundle();
+        bundle.putInt(KEY_SPLASH_SCREEN_STYLE, SPLASH_SCREEN_STYLE_EMPTY);
+        return bundle;
+    }
+
+    /**
+     * Starts the binding flow for the widget
+     * @param activity The activity for which to bind the widget
+     * @param appWidgetId The ID of the widget
+     * @param info The {@link AppWidgetProviderInfo} of the widget
+     * @param requestCode The request code
+     */
+    public void startBindFlow(@NonNull BaseActivity activity,
+            int appWidgetId, @NonNull AppWidgetProviderInfo info, int requestCode) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
+            sendActionCancelled(activity, requestCode);
+            return;
+        }
+
+        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND)
+                .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
+                .putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.provider)
+                .putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE, info.getProfile());
+        // TODO: we need to make sure that this accounts for the options bundle.
+        // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
+        activity.startActivityForResult(intent, requestCode);
+    }
+
+    /**
+     * Stop the host from listening to the widget updates
+     */
+    public void stopListening() {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
+            return;
+        }
+
+        mWidgetHost.stopListening();
+        setListeningFlag(false);
+    }
+
+    protected void setListeningFlag(final boolean isListening) {
+        if (isListening) {
+            mFlags |= FLAG_LISTENING;
+            return;
+        }
+        mFlags &= ~FLAG_LISTENING;
+    }
+
+    /**
+     * Set the interaction handler for the widget host
+     * @param handler The interaction handler
+     */
+    public void setInteractionHandler(
+            @Nullable LauncherWidgetInteractionHandler handler) {
+        ApiWrapper.setHostInteractionHandler(mWidgetHost, handler);
+    }
+
+    /**
+     * Delete the host
+     */
+    public void deleteHost() {
+        mWidgetHost.deleteHost();
+    }
+
+    /**
+     * @return The app widget ids
+     */
+    @NonNull
+    public int[] getAppWidgetIds() {
+        return mWidgetHost.getAppWidgetIds();
+    }
+
+    /**
+     * Create a view for the specified app widget
+     * @param context The activity context for which the view is created
+     * @param appWidgetId The ID of the widget
+     * @param appWidget The {@link LauncherAppWidgetProviderInfo} of the widget
+     * @return A view for the widget
+     */
+    @NonNull
+    public AppWidgetHostView createView(@NonNull Context context, int appWidgetId,
+            @NonNull LauncherAppWidgetProviderInfo appWidget) {
+        if (appWidget.isCustomWidget()) {
+            LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
+            lahv.setAppWidget(0, appWidget);
+            CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
+            return lahv;
+        } else if ((mFlags & FLAG_LISTENING) == 0) {
+            // Since the launcher hasn't started listening to widget updates, we can't simply call
+            // super.createView here because the later will make a binder call to retrieve
+            // RemoteViews from system process.
+            // TODO: have launcher always listens to widget updates in background so that this
+            //  check can be removed altogether.
+            if (FeatureFlags.ENABLE_CACHED_WIDGET.get()
+                    && mCachedRemoteViews.get(appWidgetId) != null) {
+                // We've found RemoteViews from cache for this widget, so we will instantiate a
+                // widget host view and populate it with the cached RemoteViews.
+                final LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
+                view.setAppWidget(appWidgetId, appWidget);
+                view.updateAppWidget(mCachedRemoteViews.get(appWidgetId));
+                mDeferredViews.put(appWidgetId, view);
+                mViews.put(appWidgetId, view);
+                return view;
+            } else {
+                // When cache misses, a placeholder for the widget will be returned instead.
+                DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
+                view.setAppWidget(appWidgetId, appWidget);
+                mViews.put(appWidgetId, view);
+                return view;
+            }
+        } else {
+            try {
+                return mWidgetHost.createView(context, appWidgetId, appWidget);
+            } catch (Exception e) {
+                if (!Utilities.isBinderSizeError(e)) {
+                    throw new RuntimeException(e);
+                }
+
+                // If the exception was thrown while fetching the remote views, let the view stay.
+                // This will ensure that if the widget posts a valid update later, the view
+                // will update.
+                LauncherAppWidgetHostView view = mViews.get(appWidgetId);
+                if (view == null) {
+                    view = onCreateView(mContext, appWidgetId, appWidget);
+                }
+                view.setAppWidget(appWidgetId, appWidget);
+                view.switchToErrorView();
+                return view;
+            }
+        }
+    }
+
+    /**
+     * Listener for getting notifications on provider changes.
+     */
+    public interface ProviderChangedListener {
+        /**
+         * Notify the listener that the providers have changed
+         */
+        void notifyWidgetProvidersChanged();
+    }
+
+    /**
+     * Called to return a proper view when creating a view
+     * @param context The context for which the widget view is created
+     * @param appWidgetId The ID of the added widget
+     * @param appWidget The provider info of the added widget
+     * @return A view for the specified app widget
+     */
+    @NonNull
+    public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
+            AppWidgetProviderInfo appWidget) {
+        final LauncherAppWidgetHostView view;
+        if (getPendingView(appWidgetId) != null) {
+            view = getPendingView(appWidgetId);
+            removePendingView(appWidgetId);
+        } else if (mDeferredViews.get(appWidgetId) != null) {
+            // In case the widget view is deferred, we will simply return the deferred view as
+            // opposed to instantiate a new instance of LauncherAppWidgetHostView since launcher
+            // already added the former to the workspace.
+            view = mDeferredViews.get(appWidgetId);
+        } else {
+            view = new LauncherAppWidgetHostView(context);
+        }
+        mViews.put(appWidgetId, view);
+        return view;
+    }
+
+    /**
+     * Clears all the views from the host
+     */
+    public void clearViews() {
+        LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
+        tempHost.clearViews();
+        if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
+            // First, we clear any previously cached content from existing widgets
+            mCachedRemoteViews.clear();
+            mDeferredViews.clear();
+            // Then we proceed to cache the content from the widgets
+            for (int i = 0; i < mViews.size(); i++) {
+                final int appWidgetId = mViews.keyAt(i);
+                final LauncherAppWidgetHostView view = mViews.get(appWidgetId);
+                mCachedRemoteViews.put(appWidgetId, view.mLastRemoteViews);
+            }
+        }
+        mViews.clear();
+    }
+
+    /**
+     * @return True if the host is listening to the updates, false otherwise
+     */
+    public boolean isListening() {
+        return (mFlags & FLAG_LISTENING) != 0;
+    }
+
+    /**
+     * Sets or unsets a flag the can change whether the widget host should be in the listening
+     * state.
+     */
+    private void setShouldListenFlag(int flag, boolean on) {
+        if (on) {
+            mFlags |= flag;
+        } else {
+            mFlags &= ~flag;
+        }
+
+        final boolean listening = isListening();
+        if (!listening && (mFlags & FLAGS_SHOULD_LISTEN) == FLAGS_SHOULD_LISTEN) {
+            // Postpone starting listening until all flags are on.
+            startListening();
+        } else if (listening && (mFlags & FLAG_ACTIVITY_STARTED) == 0) {
+            // Postpone stopping listening until the activity is stopped.
+            stopListening();
+        }
+    }
+
+    /**
+     * Set as a substitution for the hidden interaction handler in RemoteViews
+     */
+    public interface LauncherWidgetInteractionHandler {
+        /**
+         * Invoked when the user performs an interaction on the View.
+         *
+         * @param view the View with which the user interacted
+         * @param pendingIntent the base PendingIntent associated with the view
+         * @param response the response to the interaction, which knows how to fill in the
+         *                 attached PendingIntent
+         */
+        boolean onInteraction(
+                View view,
+                PendingIntent pendingIntent,
+                RemoteViews.RemoteResponse response);
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 21b2647..72ec629 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -65,7 +65,7 @@
 import com.android.launcher3.views.StickyHeaderLayout;
 import com.android.launcher3.views.WidgetsEduView;
 import com.android.launcher3.widget.BaseWidgetSheet;
-import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
+import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.picker.search.SearchModeListener;
 import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index ea0f5a3..02f4ece 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -24,7 +24,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.widget.LauncherAppWidgetHost;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 
 /**
  * A wrapper for the hidden API calls
@@ -43,7 +43,7 @@
      * @param handler InteractionHandler for the views in the host
      */
     public static void setHostInteractionHandler(@NonNull AppWidgetHost host,
-            @Nullable LauncherAppWidgetHost.LauncherWidgetInteractionHandler handler) {
+            @Nullable LauncherWidgetHolder.LauncherWidgetInteractionHandler handler) {
         // No-op
     }
 }
diff --git a/tests/src/com/android/launcher3/util/WidgetUtils.java b/tests/src/com/android/launcher3/util/WidgetUtils.java
index 6fc8491..e514142 100644
--- a/tests/src/com/android/launcher3/util/WidgetUtils.java
+++ b/tests/src/com/android/launcher3/util/WidgetUtils.java
@@ -20,7 +20,6 @@
 
 import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
 
-import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
@@ -32,8 +31,8 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.widget.LauncherAppWidgetHost;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.LauncherWidgetHolder;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetManagerHelper;
 
@@ -71,14 +70,19 @@
             pendingInfo.minSpanY = item.minSpanY;
             Bundle options = pendingInfo.getDefaultSizeOptions(targetContext);
 
-            AppWidgetHost host = new LauncherAppWidgetHost(targetContext);
-            int widgetId = host.allocateAppWidgetId();
-            if (!new WidgetManagerHelper(targetContext)
-                    .bindAppWidgetIdIfAllowed(widgetId, info, options)) {
-                host.deleteAppWidgetId(widgetId);
-                throw new IllegalArgumentException("Unable to bind widget id");
+            LauncherWidgetHolder holder = new LauncherWidgetHolder(targetContext);
+            try {
+                int widgetId = holder.allocateAppWidgetId();
+                if (!new WidgetManagerHelper(targetContext)
+                        .bindAppWidgetIdIfAllowed(widgetId, info, options)) {
+                    holder.deleteAppWidgetId(widgetId);
+                    throw new IllegalArgumentException("Unable to bind widget id");
+                }
+                item.appWidgetId = widgetId;
+            } finally {
+                // Necessary to destroy the holder to free up possible activity context
+                holder.destroy();
             }
-            item.appWidgetId = widgetId;
         }
         return item;
     }