Removing widget preview caching

> All previews are generated on demand when the corresponding
  header expands
> Using ItemAnimator to animate layout changes when preview loads

Bug: 196238313
Test: Manual
Change-Id: I0cb859c8443c2c536399e4063f58baecfc7416ad
diff --git a/robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java b/robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java
deleted file mode 100644
index 1090d1e..0000000
--- a/robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * Copyright (C) 2021 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 com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.graphics.Bitmap;
-import android.os.CancellationSignal;
-import android.os.UserHandle;
-import android.util.Size;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.testing.TestActivity;
-import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-@RunWith(RobolectricTestRunner.class)
-public class CachingWidgetPreviewLoaderTest {
-    private final Size SIZE_10_10 = new Size(10, 10);
-    private final Size SIZE_20_20 = new Size(20, 20);
-    private static final String TEST_PACKAGE = "com.example.test";
-    private final ComponentName TEST_PROVIDER =
-            new ComponentName(TEST_PACKAGE, ".WidgetProvider");
-    private final ComponentName TEST_PROVIDER2 =
-            new ComponentName(TEST_PACKAGE, ".WidgetProvider2");
-    private final Bitmap BITMAP = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
-    private final Bitmap BITMAP2 = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888);
-
-
-    @Mock private CancellationSignal mCancellationSignal;
-    @Mock private WidgetPreviewLoader mDelegate;
-    @Mock private IconCache mIconCache;
-    @Mock private DeviceProfile mDeviceProfile;
-    @Mock private LauncherAppWidgetProviderInfo mProviderInfo;
-    @Mock private LauncherAppWidgetProviderInfo mProviderInfo2;
-    @Mock private WidgetPreviewLoadedCallback mPreviewLoadedCallback;
-    @Mock private WidgetPreviewLoadedCallback mPreviewLoadedCallback2;
-    @Captor private ArgumentCaptor<WidgetPreviewLoadedCallback> mCallbackCaptor;
-
-    private TestActivity mTestActivity;
-    private CachingWidgetPreviewLoader mLoader;
-    private WidgetItem mWidgetItem;
-    private WidgetItem mWidgetItem2;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mLoader = new CachingWidgetPreviewLoader(mDelegate);
-
-        mTestActivity = Robolectric.buildActivity(TestActivity.class).setup().get();
-        mTestActivity.setDeviceProfile(mDeviceProfile);
-
-        when(mDelegate.loadPreview(any(), any(), any(), any())).thenReturn(mCancellationSignal);
-
-        mProviderInfo.provider = TEST_PROVIDER;
-        when(mProviderInfo.getProfile()).thenReturn(new UserHandle(0));
-
-        mProviderInfo2.provider = TEST_PROVIDER2;
-        when(mProviderInfo2.getProfile()).thenReturn(new UserHandle(0));
-
-        InvariantDeviceProfile testProfile = new InvariantDeviceProfile();
-        testProfile.numRows = 5;
-        testProfile.numColumns = 5;
-
-        mWidgetItem = new WidgetItem(mProviderInfo, testProfile, mIconCache);
-        mWidgetItem2 = new WidgetItem(mProviderInfo2, testProfile, mIconCache);
-    }
-
-    @Test
-    public void getPreview_notInCache_shouldReturnNull() {
-        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
-    }
-
-    @Test
-    public void getPreview_notInCache_shouldNotCallDelegate() {
-        mLoader.getPreview(mWidgetItem, SIZE_10_10);
-
-        verifyZeroInteractions(mDelegate);
-    }
-
-    @Test
-    public void getPreview_inCache_shouldReturnCachedBitmap() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
-        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
-    }
-
-    @Test
-    public void getPreview_otherSizeInCache_shouldReturnNull() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
-        assertThat(mLoader.getPreview(mWidgetItem, SIZE_20_20)).isNull();
-    }
-
-    @Test
-    public void getPreview_otherItemInCache_shouldReturnNull() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
-        assertThat(mLoader.getPreview(mWidgetItem2, SIZE_10_10)).isNull();
-    }
-
-    @Test
-    public void getPreview_shouldStoreMultipleSizesPerItem() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-        loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP2);
-
-        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
-        assertThat(mLoader.getPreview(mWidgetItem, SIZE_20_20)).isEqualTo(BITMAP2);
-    }
-
-    @Test
-    public void loadPreview_notInCache_shouldStartLoading() {
-        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
-        verify(mDelegate).loadPreview(eq(mTestActivity), eq(mWidgetItem), eq(SIZE_10_10), any());
-        verifyZeroInteractions(mPreviewLoadedCallback);
-    }
-
-    @Test
-    public void loadPreview_thenLoaded_shouldCallBack() {
-        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-        verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
-        WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-
-        loaderCallback.onPreviewLoaded(BITMAP);
-
-        verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
-    }
-
-    @Test
-    public void loadPreview_thenCancelled_shouldCancelDelegateRequest() {
-        CancellationSignal cancellationSignal =
-                mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
-        cancellationSignal.cancel();
-
-        verify(mCancellationSignal).cancel();
-        verifyZeroInteractions(mPreviewLoadedCallback);
-        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
-    }
-
-    @Test
-    public void loadPreview_thenCancelled_otherCallListening_shouldNotCancelDelegateRequest() {
-        CancellationSignal cancellationSignal1 =
-                mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-
-        cancellationSignal1.cancel();
-
-        verifyZeroInteractions(mCancellationSignal);
-    }
-
-    @Test
-    public void loadPreview_thenCancelled_otherCallListening_loaded_shouldCallBackToNonCancelled() {
-        CancellationSignal cancellationSignal1 =
-                mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-        verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
-        WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-
-        cancellationSignal1.cancel();
-        loaderCallback.onPreviewLoaded(BITMAP);
-
-        verifyZeroInteractions(mPreviewLoadedCallback);
-        verify(mPreviewLoadedCallback2).onPreviewLoaded(BITMAP);
-        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
-    }
-
-    @Test
-    public void loadPreview_thenCancelled_bothCallsCancelled_shouldCancelDelegateRequest() {
-        CancellationSignal cancellationSignal1 =
-                mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-        CancellationSignal cancellationSignal2 =
-                mLoader.loadPreview(
-                        mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-
-        cancellationSignal1.cancel();
-        cancellationSignal2.cancel();
-
-        verify(mCancellationSignal).cancel();
-        verifyZeroInteractions(mPreviewLoadedCallback);
-        verifyZeroInteractions(mPreviewLoadedCallback2);
-        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
-    }
-
-    @Test
-    public void loadPreview_multipleCallbacks_shouldOnlyCallDelegateOnce() {
-        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-
-        verify(mDelegate).loadPreview(any(), any(), any(), any());
-    }
-
-    @Test
-    public void loadPreview_multipleCallbacks_shouldForwardResultToEachCallback() {
-        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-
-        verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
-        WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-
-        loaderCallback.onPreviewLoaded(BITMAP);
-
-        verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
-        verify(mPreviewLoadedCallback2).onPreviewLoaded(BITMAP);
-    }
-
-    @Test
-    public void loadPreview_inCache_shouldCallBackImmediately() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-        reset(mDelegate);
-
-        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
-        verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
-        verifyZeroInteractions(mDelegate);
-    }
-
-    @Test
-    public void loadPreview_thenLoaded_thenCancelled_shouldNotRemovePreviewFromCache() {
-        CancellationSignal cancellationSignal =
-                mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-        verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
-        WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-        loaderCallback.onPreviewLoaded(BITMAP);
-
-        cancellationSignal.cancel();
-
-        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
-    }
-
-    @Test
-    public void isPreviewLoaded_notLoaded_shouldReturnFalse() {
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
-    }
-
-    @Test
-    public void isPreviewLoaded_otherSizeLoaded_shouldReturnFalse() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
-
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
-    }
-
-    @Test
-    public void isPreviewLoaded_otherItemLoaded_shouldReturnFalse() {
-        loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);
-
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
-    }
-
-    @Test
-    public void isPreviewLoaded_loaded_shouldReturnTrue() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isTrue();
-    }
-
-    @Test
-    public void clearPreviews_notInCache_shouldBeNoOp() {
-        mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
-    }
-
-    @Test
-    public void clearPreviews_inCache_shouldRemovePreview() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
-        mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
-    }
-
-    @Test
-    public void clearPreviews_inCache_multipleSizes_shouldRemoveAllSizes() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-        loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
-
-        mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
-    }
-
-    @Test
-    public void clearPreviews_inCache_otherItems_shouldOnlyRemoveSpecifiedItems() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-        loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);
-
-        mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_10_10)).isTrue();
-    }
-
-    @Test
-    public void clearPreviews_inCache_otherItems_shouldRemoveAllSpecifiedItems() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-        loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);
-
-        mLoader.clearPreviews(Arrays.asList(mWidgetItem, mWidgetItem2));
-
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_10_10)).isFalse();
-    }
-
-    @Test
-    public void clearPreviews_loading_shouldCancelLoad() {
-        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
-        mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
-        verify(mCancellationSignal).cancel();
-    }
-
-    @Test
-    public void clearAll_cacheEmpty_shouldBeNoOp() {
-        mLoader.clearAll();
-
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
-    }
-
-    @Test
-    public void clearAll_inCache_shouldRemovePreview() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
-        mLoader.clearAll();
-
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
-    }
-
-    @Test
-    public void clearAll_inCache_multipleSizes_shouldRemoveAllSizes() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-        loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
-
-        mLoader.clearAll();
-
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
-    }
-
-    @Test
-    public void clearAll_inCache_multipleItems_shouldRemoveAll() {
-        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-        loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
-        loadPreviewIntoCache(mWidgetItem2, SIZE_20_20, BITMAP);
-
-        mLoader.clearAll();
-
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
-        assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_20_20)).isFalse();
-    }
-
-    @Test
-    public void clearAll_loading_shouldCancelLoad() {
-        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
-        mLoader.clearAll();
-
-        verify(mCancellationSignal).cancel();
-    }
-
-    private void loadPreviewIntoCache(WidgetItem widgetItem, Size size, Bitmap bitmap) {
-        reset(mDelegate);
-        mLoader.loadPreview(mTestActivity, widgetItem, size, ignored -> {});
-        verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
-        WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-
-        loaderCallback.onPreviewLoaded(bitmap);
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
index fb44ca1..12aac8b 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
@@ -39,7 +39,6 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -64,7 +63,6 @@
     private static final String TEST_PACKAGE_PLACEHOLDER = "com.google.test";
 
     @Mock private LayoutInflater mMockLayoutInflater;
-    @Mock private DatabaseWidgetPreviewLoader mMockWidgetCache;
     @Mock private RecyclerView.AdapterDataObserver mListener;
     @Mock private IconCache mIconCache;
 
@@ -81,7 +79,7 @@
         mTestProfile.numRows = 5;
         mTestProfile.numColumns = 5;
         mUserHandle = Process.myUserHandle();
-        mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
+        mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater,
                 mIconCache, () -> 0, null, null);
         mAdapter.registerAdapterDataObserver(mListener);
 
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index b7d7788..fa000c0 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -23,6 +23,8 @@
 import static org.mockito.Mockito.verify;
 import static org.robolectric.Shadows.shadowOf;
 
+import static java.util.Collections.EMPTY_LIST;
+
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
@@ -116,7 +118,7 @@
                 APP_NAME,
                 TEST_PACKAGE,
                 /* numOfWidgets= */ 3);
-        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
 
         TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
         TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
@@ -134,7 +136,7 @@
                 TEST_PACKAGE,
                 /* numOfWidgets= */ 3);
 
-        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
         widgetsListHeader.callOnClick();
 
         verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
index 2b4cea0..b18c8b7 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
@@ -23,6 +23,8 @@
 import static org.mockito.Mockito.verify;
 import static org.robolectric.Shadows.shadowOf;
 
+import static java.util.Collections.EMPTY_LIST;
+
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
@@ -116,7 +118,7 @@
                 APP_NAME,
                 TEST_PACKAGE,
                 /* numOfWidgets= */ 3);
-        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
 
         TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
         TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
@@ -135,7 +137,7 @@
                 TEST_PACKAGE,
                 /* numOfWidgets= */ 3);
 
-        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
         widgetsListHeader.callOnClick();
 
         verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 9f66fb7..cb38c6f 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -23,6 +23,8 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.robolectric.Shadows.shadowOf;
 
+import static java.util.Collections.EMPTY_LIST;
+
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
@@ -44,7 +46,6 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.testing.TestActivity;
-import com.android.launcher3.widget.CachingWidgetPreviewLoader;
 import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.WidgetCell;
@@ -111,7 +112,6 @@
                 LayoutInflater.from(mTestActivity),
                 mOnIconClickListener,
                 mOnLongClickListener,
-                new CachingWidgetPreviewLoader(mWidgetPreviewLoader),
                 new WidgetsListDrawableFactory(mTestActivity));
     }
 
@@ -128,13 +128,13 @@
                 APP_NAME,
                 TEST_PACKAGE,
                 /* numOfWidgets= */ 3);
-        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
         shadowOf(getMainLooper()).idle();
 
         // THEN the table container has one row, which contains 3 widgets.
         // View:  .SampleWidget0 | .SampleWidget1 | .SampleWidget2
-        assertThat(viewHolder.mTableContainer.getChildCount()).isEqualTo(1);
-        TableRow row = (TableRow) viewHolder.mTableContainer.getChildAt(0);
+        assertThat(viewHolder.tableContainer.getChildCount()).isEqualTo(1);
+        TableRow row = (TableRow) viewHolder.tableContainer.getChildAt(0);
         assertThat(row.getChildCount()).isEqualTo(3);
         // Widget 0 label is .SampleWidget0.
         assertWidgetCellWithLabel(row.getChildAt(0), ".SampleWidget0");
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 3d6be69..702b73a 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -48,7 +48,6 @@
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.launcher3.util.Themes;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 public class LauncherAppState {
@@ -64,7 +63,6 @@
     private final LauncherModel mModel;
     private final IconProvider mIconProvider;
     private final IconCache mIconCache;
-    private final DatabaseWidgetPreviewLoader mWidgetCache;
     private final InvariantDeviceProfile mInvariantDeviceProfile;
     private final RunnableList mOnTerminateCallback = new RunnableList();
 
@@ -139,7 +137,6 @@
         mIconProvider =  new IconProvider(context, Themes.isThemedIconEnabled(context));
         mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
                 iconCacheFileName, mIconProvider);
-        mWidgetCache = new DatabaseWidgetPreviewLoader(mContext, mIconCache);
         mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext));
         mOnTerminateCallback.add(mIconCache::close);
     }
@@ -155,7 +152,6 @@
         LauncherIcons.clearPool();
         mIconCache.updateIconParams(
                 mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize);
-        mWidgetCache.refresh();
         mModel.forceReload();
     }
 
@@ -181,10 +177,6 @@
         return mModel;
     }
 
-    public DatabaseWidgetPreviewLoader getWidgetCache() {
-        return mWidgetCache;
-    }
-
     public InvariantDeviceProfile getInvariantDeviceProfile() {
         return mInvariantDeviceProfile;
     }
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 92ed18a..466b268 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -286,9 +286,7 @@
 
             @Override
             protected void onPostExecute(WidgetItem item) {
-                mWidgetCell.setPreviewSize(item);
-                mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
-                mWidgetCell.ensurePreview();
+                mWidgetCell.applyFromCellItem(item);
             }
         }.executeOnExecutor(MODEL_EXECUTOR);
         // TODO: Create a worker looper executor and reuse that everywhere.
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 82b0f7c..83fb3d1 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -123,7 +123,6 @@
                         iconCache.updateIconsForPkg(packages[i], mUser);
                         activitiesLists.put(
                                 packages[i], appsList.updatePackage(context, packages[i], mUser));
-                        app.getWidgetCache().removePackage(packages[i], mUser);
 
                         // The update may have changed which shortcuts/widgets are available.
                         // Refresh the widgets for the package if we have an activity running.
@@ -148,7 +147,6 @@
                 for (int i = 0; i < N; i++) {
                     if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
                     appsList.removePackage(packages[i], mUser);
-                    app.getWidgetCache().removePackage(packages[i], mUser);
                 }
                 flagOp = FlagOp.addFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
                 break;
diff --git a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
index 6215827..31436c4 100644
--- a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
+++ b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
@@ -22,6 +22,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.List;
 
 /**
  * Creates and populates views with data
@@ -46,7 +47,7 @@
     V newViewHolder(ViewGroup parent);
 
     /** Populate UI references in {@link ViewHolder} with data. */
-    void bindViewHolder(V viewHolder, T data, @ListPosition int position);
+    void bindViewHolder(V viewHolder, T data, @ListPosition int position, List<Object> payloads);
 
     /**
      * Called when the view is recycled. Views are recycled in batches once they are sufficiently
diff --git a/src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java b/src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java
deleted file mode 100644
index afceadd..0000000
--- a/src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2021 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 android.graphics.Bitmap;
-import android.os.CancellationSignal;
-import android.util.Size;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.collection.ArrayMap;
-import androidx.collection.ArraySet;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.util.ComponentKey;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/** Wrapper around {@link DatabaseWidgetPreviewLoader} that contains caching logic. */
-public class CachingWidgetPreviewLoader implements WidgetPreviewLoader {
-
-    @NonNull private final WidgetPreviewLoader mDelegate;
-    @NonNull private final Map<ComponentKey, Map<Size, CacheResult>> mCache = new ArrayMap<>();
-
-    public CachingWidgetPreviewLoader(@NonNull WidgetPreviewLoader delegate) {
-        mDelegate = delegate;
-    }
-
-    /** Returns whether the preview is loaded for the item and size. */
-    public boolean isPreviewLoaded(@NonNull WidgetItem item, @NonNull Size previewSize) {
-        return getPreview(item, previewSize) != null;
-    }
-
-    /** Returns the cached preview for the item and size, or null if there is none. */
-    @Nullable
-    public Bitmap getPreview(@NonNull WidgetItem item, @NonNull Size previewSize) {
-        CacheResult cacheResult = getCacheResult(item, previewSize);
-        if (cacheResult instanceof CacheResult.Loaded) {
-            return ((CacheResult.Loaded) cacheResult).mBitmap;
-        } else {
-            return null;
-        }
-    }
-
-    @NonNull
-    private CacheResult getCacheResult(@NonNull WidgetItem item, @NonNull Size previewSize) {
-        synchronized (mCache) {
-            Map<Size, CacheResult> cacheResults = mCache.get(toComponentKey(item));
-            if (cacheResults == null) {
-                return CacheResult.MISS;
-            }
-
-            return cacheResults.getOrDefault(previewSize, CacheResult.MISS);
-        }
-    }
-
-    /**
-     * Puts the result in the cache for the item and size. Returns the value previously in the
-     * cache, or null if there was none.
-     */
-    @Nullable
-    private CacheResult putCacheResult(
-            @NonNull WidgetItem item,
-            @NonNull Size previewSize,
-            @Nullable CacheResult cacheResult) {
-        ComponentKey key = toComponentKey(item);
-        synchronized (mCache) {
-            Map<Size, CacheResult> cacheResults = mCache.getOrDefault(key, new ArrayMap<>());
-            CacheResult previous;
-            if (cacheResult == null) {
-                previous = cacheResults.remove(previewSize);
-                if (cacheResults.isEmpty()) {
-                    mCache.remove(key);
-                } else {
-                    previous = cacheResults.put(previewSize, cacheResult);
-                    mCache.put(key, cacheResults);
-                }
-            } else {
-                previous = cacheResults.put(previewSize, cacheResult);
-                mCache.put(key, cacheResults);
-            }
-            return previous;
-        }
-    }
-
-    private void removeCacheResult(@NonNull WidgetItem item, @NonNull Size previewSize) {
-        ComponentKey key = toComponentKey(item);
-        synchronized (mCache) {
-            Map<Size, CacheResult> cacheResults = mCache.getOrDefault(key, new ArrayMap<>());
-            cacheResults.remove(previewSize);
-            mCache.put(key, cacheResults);
-        }
-    }
-
-    /**
-     * Gets the preview for the widget item and size, using the value in the cache if stored.
-     *
-     * @return a {@link CancellationSignal}, which can cancel the request before it loads
-     */
-    @Override
-    @UiThread
-    @NonNull
-    public CancellationSignal loadPreview(
-            @NonNull BaseActivity activity, @NonNull WidgetItem item, @NonNull Size previewSize,
-            @NonNull WidgetPreviewLoadedCallback callback) {
-        CancellationSignal signal = new CancellationSignal();
-        signal.setOnCancelListener(() -> {
-            synchronized (mCache) {
-                CacheResult cacheResult = getCacheResult(item, previewSize);
-                if (!(cacheResult instanceof CacheResult.Loading)) {
-                    // If the key isn't actively loading, then this is a no-op. Cancelling loading
-                    // shouldn't clear the cache if we've already loaded.
-                    return;
-                }
-
-                CacheResult.Loading prev = (CacheResult.Loading) cacheResult;
-                CacheResult.Loading updated = prev.withoutCallback(callback);
-
-                if (updated.mCallbacks.isEmpty()) {
-                    // If the last callback was removed, then cancel the underlying request in the
-                    // delegate.
-                    prev.mCancellationSignal.cancel();
-                    removeCacheResult(item, previewSize);
-                } else {
-                    // If there are other callbacks still active, then don't cancel the delegate's
-                    // request, just remove this callback from the set.
-                    putCacheResult(item, previewSize, updated);
-                }
-            }
-        });
-
-        synchronized (mCache) {
-            CacheResult cacheResult = getCacheResult(item, previewSize);
-            if (cacheResult instanceof CacheResult.Loaded) {
-                // If the bitmap is already present in the cache, invoke the callback immediately.
-                callback.onPreviewLoaded(((CacheResult.Loaded) cacheResult).mBitmap);
-                return signal;
-            }
-
-            if (cacheResult instanceof CacheResult.Loading) {
-                // If we're already loading the preview for this key, then just add the callback
-                // to the set we'll call after it loads.
-                CacheResult.Loading prev = (CacheResult.Loading) cacheResult;
-                putCacheResult(item, previewSize, prev.withCallback(callback));
-                return signal;
-            }
-
-            CancellationSignal delegateCancellationSignal =
-                    mDelegate.loadPreview(
-                            activity,
-                            item,
-                            previewSize,
-                            preview -> {
-                                CacheResult prev;
-                                synchronized (mCache) {
-                                    prev = putCacheResult(
-                                            item, previewSize, new CacheResult.Loaded(preview));
-                                }
-                                if (prev instanceof CacheResult.Loading) {
-                                    // Notify each stored callback that the preview has loaded.
-                                    ((CacheResult.Loading) prev).mCallbacks
-                                            .forEach(c -> c.onPreviewLoaded(preview));
-                                } else {
-                                    // If there isn't a loading object in the cache, then we were
-                                    // notified before adding this signal to the cache. Just
-                                    // call back to the provided callback, there can't be others.
-                                    callback.onPreviewLoaded(preview);
-                                }
-                            });
-            ArraySet<WidgetPreviewLoadedCallback> callbacks = new ArraySet<>();
-            callbacks.add(callback);
-            putCacheResult(
-                    item,
-                    previewSize,
-                    new CacheResult.Loading(delegateCancellationSignal, callbacks));
-        }
-
-        return signal;
-    }
-
-    /** Clears all cached previews for {@code items}, cancelling any in-progress preview loading. */
-    public void clearPreviews(Iterable<WidgetItem> items) {
-        List<CacheResult> previousCacheResults = new ArrayList<>();
-        synchronized (mCache) {
-            for (WidgetItem item : items) {
-                Map<Size, CacheResult> previousMap = mCache.remove(toComponentKey(item));
-                if (previousMap != null) {
-                    previousCacheResults.addAll(previousMap.values());
-                }
-            }
-        }
-
-        for (CacheResult previousCacheResult : previousCacheResults) {
-            if (previousCacheResult instanceof CacheResult.Loading) {
-                ((CacheResult.Loading) previousCacheResult).mCancellationSignal.cancel();
-            }
-        }
-    }
-
-    /** Clears all cached previews, cancelling any in-progress preview loading. */
-    public void clearAll() {
-        List<CacheResult> previousCacheResults;
-        synchronized (mCache) {
-            previousCacheResults =
-                    mCache
-                    .values()
-                    .stream()
-                    .flatMap(sizeToResult -> sizeToResult.values().stream())
-                    .collect(Collectors.toList());
-            mCache.clear();
-        }
-
-        for (CacheResult previousCacheResult : previousCacheResults) {
-            if (previousCacheResult instanceof CacheResult.Loading) {
-                ((CacheResult.Loading) previousCacheResult).mCancellationSignal.cancel();
-            }
-        }
-    }
-
-    private abstract static class CacheResult {
-        static final CacheResult MISS = new CacheResult() {};
-
-        static final class Loading extends CacheResult {
-            @NonNull final CancellationSignal mCancellationSignal;
-            @NonNull final Set<WidgetPreviewLoadedCallback> mCallbacks;
-
-            Loading(@NonNull CancellationSignal cancellationSignal,
-                    @NonNull Set<WidgetPreviewLoadedCallback> callbacks) {
-                mCancellationSignal = cancellationSignal;
-                mCallbacks = callbacks;
-            }
-
-            @NonNull
-            Loading withCallback(@NonNull WidgetPreviewLoadedCallback callback) {
-                if (mCallbacks.contains(callback)) return this;
-                Set<WidgetPreviewLoadedCallback> newCallbacks =
-                        new ArraySet<>(mCallbacks.size() + 1);
-                newCallbacks.addAll(mCallbacks);
-                newCallbacks.add(callback);
-                return new Loading(mCancellationSignal, newCallbacks);
-            }
-
-            @NonNull
-            Loading withoutCallback(@NonNull WidgetPreviewLoadedCallback callback) {
-                if (!mCallbacks.contains(callback)) return this;
-                Set<WidgetPreviewLoadedCallback> newCallbacks =
-                        new ArraySet<>(mCallbacks.size() - 1);
-                for (WidgetPreviewLoadedCallback existingCallback : mCallbacks) {
-                    if (!existingCallback.equals(callback)) {
-                        newCallbacks.add(existingCallback);
-                    }
-                }
-                return new Loading(mCancellationSignal, newCallbacks);
-            }
-        }
-
-        static final class Loaded extends CacheResult {
-            @NonNull final Bitmap mBitmap;
-
-            Loaded(@NonNull Bitmap bitmap) {
-                mBitmap = bitmap;
-            }
-        }
-    }
-
-    @NonNull
-    private static ComponentKey toComponentKey(@NonNull WidgetItem item) {
-        return new ComponentKey(item.componentName, item.user);
-    }
-}
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index 4ec7e60..95c3e1e 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -16,21 +16,9 @@
 package com.android.launcher3.widget;
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -39,72 +27,40 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
-import android.os.CancellationSignal;
+import android.os.Handler;
 import android.os.Process;
-import android.os.UserHandle;
 import android.util.Log;
-import android.util.LongSparseArray;
-import android.util.Pair;
 import android.util.Size;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.icons.ShadowGenerator;
+import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.SQLiteCacheHelper;
-import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.util.WidgetSizes;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.WeakHashMap;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
 
-/** {@link WidgetPreviewLoader} that loads preview images from a {@link CacheDb}. */
-public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader {
+/** Utility class to load widget previews */
+public class DatabaseWidgetPreviewLoader {
 
     private static final String TAG = "WidgetPreviewLoader";
-    private static final boolean DEBUG = false;
 
-    private final HashMap<String, long[]> mPackageVersions = new HashMap<>();
-
-    /**
-     * Weak reference objects, do not prevent their referents from being made finalizable,
-     * finalized, and then reclaimed.
-     * Note: synchronized block used for this variable is expensive and the block should always
-     * be posted to a background thread.
-     */
-    @Thunk final Set<Bitmap> mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap<>());
-
-    private final Context mContext;
-    private final IconCache mIconCache;
-    private final UserCache mUserCache;
-    private final CacheDb mDb;
+    private final BaseActivity mContext;
     private final float mPreviewBoxCornerRadius;
 
-    public DatabaseWidgetPreviewLoader(Context context, IconCache iconCache) {
+    public DatabaseWidgetPreviewLoader(BaseActivity context) {
         mContext = context;
-        mIconCache = iconCache;
-        mUserCache = UserCache.INSTANCE.get(context);
-        mDb = new CacheDb(context);
         float previewCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context);
         mPreviewBoxCornerRadius = previewCornerRadius > 0
                 ? previewCornerRadius
@@ -117,251 +73,29 @@
      *
      * @return a request id which can be used to cancel the request.
      */
-    @Override
     @NonNull
-    public CancellationSignal loadPreview(
-            @NonNull BaseActivity activity,
+    public HandlerRunnable loadPreview(
             @NonNull WidgetItem item,
             @NonNull Size previewSize,
-            @NonNull WidgetPreviewLoadedCallback callback) {
-        int previewWidth = previewSize.getWidth();
-        int previewHeight = previewSize.getHeight();
-        String size = previewWidth + "x" + previewHeight;
-        WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
-
-        PreviewLoadTask task =
-                new PreviewLoadTask(activity, key, item, previewWidth, previewHeight, callback);
-        task.executeOnExecutor(Executors.THREAD_POOL_EXECUTOR);
-
-        CancellationSignal signal = new CancellationSignal();
-        signal.setOnCancelListener(task);
-        return signal;
-    }
-
-    /** Clears the database storing previews. */
-    public void refresh() {
-        mDb.clear();
-    }
-
-    /**
-     * The DB holds the generated previews for various components. Previews can also have different
-     * sizes (landscape vs portrait).
-     */
-    private static class CacheDb extends SQLiteCacheHelper {
-        private static final int DB_VERSION = 9;
-
-        private static final String TABLE_NAME = "shortcut_and_widget_previews";
-        private static final String COLUMN_COMPONENT = "componentName";
-        private static final String COLUMN_USER = "profileId";
-        private static final String COLUMN_SIZE = "size";
-        private static final String COLUMN_PACKAGE = "packageName";
-        private static final String COLUMN_LAST_UPDATED = "lastUpdated";
-        private static final String COLUMN_VERSION = "version";
-        private static final String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
-
-        CacheDb(Context context) {
-            super(context, LauncherFiles.WIDGET_PREVIEWS_DB, DB_VERSION, TABLE_NAME);
-        }
-
-        @Override
-        public void onCreateTable(SQLiteDatabase database) {
-            database.execSQL("CREATE TABLE IF NOT EXISTS "
-                    + TABLE_NAME
-                    + " ("
-                    + COLUMN_COMPONENT
-                    + " TEXT NOT NULL, "
-                    + COLUMN_USER
-                    + " INTEGER NOT NULL, "
-                    + COLUMN_SIZE
-                    + " TEXT NOT NULL, "
-                    + COLUMN_PACKAGE
-                    + " TEXT NOT NULL, "
-                    + COLUMN_LAST_UPDATED
-                    + " INTEGER NOT NULL DEFAULT 0, "
-                    + COLUMN_VERSION
-                    + " INTEGER NOT NULL DEFAULT 0, "
-                    + COLUMN_PREVIEW_BITMAP
-                    + " BLOB, "
-                    + "PRIMARY KEY ("
-                    + COLUMN_COMPONENT
-                    + ", "
-                    + COLUMN_USER
-                    + ", "
-                    + COLUMN_SIZE
-                    + ") "
-                    +
-                    ");");
-        }
-    }
-
-    @Thunk void writeToDb(WidgetCacheKey key, long[] versions, Bitmap preview) {
-        ContentValues values = new ContentValues();
-        values.put(CacheDb.COLUMN_COMPONENT, key.componentName.flattenToShortString());
-        values.put(CacheDb.COLUMN_USER, mUserCache.getSerialNumberForUser(key.user));
-        values.put(CacheDb.COLUMN_SIZE, key.mSize);
-        values.put(CacheDb.COLUMN_PACKAGE, key.componentName.getPackageName());
-        values.put(CacheDb.COLUMN_VERSION, versions[0]);
-        values.put(CacheDb.COLUMN_LAST_UPDATED, versions[1]);
-        values.put(CacheDb.COLUMN_PREVIEW_BITMAP, GraphicsUtils.flattenBitmap(preview));
-        mDb.insertOrReplace(values);
-    }
-
-    /** Removes the package from the preview database. */
-    public void removePackage(String packageName, UserHandle user) {
-        removePackage(packageName, user, mUserCache.getSerialNumberForUser(user));
-    }
-
-    /** Removes the package from the preview database. */
-    public void removePackage(String packageName, UserHandle user, long userSerial) {
-        synchronized (mPackageVersions) {
-            mPackageVersions.remove(packageName);
-        }
-
-        mDb.delete(
-                CacheDb.COLUMN_PACKAGE + " = ? AND " + CacheDb.COLUMN_USER + " = ?",
-                new String[]{packageName, Long.toString(userSerial)});
-    }
-
-    /**
-     * Updates the persistent DB:
-     *   1. Any preview generated for an old package version is removed
-     *   2. Any preview for an absent package is removed
-     * This ensures that we remove entries for packages which changed while the launcher was dead.
-     *
-     * @param packageUser if provided, specifies that list only contains previews for the
-     *                    given package/user, otherwise the list contains all previews
-     */
-    public void removeObsoletePreviews(ArrayList<? extends ComponentKey> list,
-            @Nullable PackageUserKey packageUser) {
-        Preconditions.assertWorkerThread();
-
-        LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
-
-        for (ComponentKey key : list) {
-            final long userId = mUserCache.getSerialNumberForUser(key.user);
-            HashSet<String> packages = validPackages.get(userId);
-            if (packages == null) {
-                packages = new HashSet<>();
-                validPackages.put(userId, packages);
-            }
-            packages.add(key.componentName.getPackageName());
-        }
-
-        LongSparseArray<HashSet<String>> packagesToDelete = new LongSparseArray<>();
-        long passedUserId = packageUser == null ? 0
-                : mUserCache.getSerialNumberForUser(packageUser.mUser);
-        Cursor c = null;
-        try {
-            c = mDb.query(
-                    new String[]{CacheDb.COLUMN_USER, CacheDb.COLUMN_PACKAGE,
-                            CacheDb.COLUMN_LAST_UPDATED, CacheDb.COLUMN_VERSION},
-                    null, null);
-            while (c.moveToNext()) {
-                long userId = c.getLong(0);
-                String pkg = c.getString(1);
-                long lastUpdated = c.getLong(2);
-                long version = c.getLong(3);
-
-                if (packageUser != null && (!pkg.equals(packageUser.mPackageName)
-                        || userId != passedUserId)) {
-                    // This preview is associated with a different package/user, no need to remove.
-                    continue;
-                }
-
-                HashSet<String> packages = validPackages.get(userId);
-                if (packages != null && packages.contains(pkg)) {
-                    long[] versions = getPackageVersion(pkg);
-                    if (versions[0] == version && versions[1] == lastUpdated) {
-                        // Every thing checks out
-                        continue;
-                    }
-                }
-
-                // We need to delete this package.
-                packages = packagesToDelete.get(userId);
-                if (packages == null) {
-                    packages = new HashSet<>();
-                    packagesToDelete.put(userId, packages);
-                }
-                packages.add(pkg);
-            }
-
-            for (int i = 0; i < packagesToDelete.size(); i++) {
-                long userId = packagesToDelete.keyAt(i);
-                UserHandle user = mUserCache.getUserForSerialNumber(userId);
-                for (String pkg : packagesToDelete.valueAt(i)) {
-                    removePackage(pkg, user, userId);
-                }
-            }
-        } catch (SQLException e) {
-            Log.e(TAG, "Error updating widget previews", e);
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-    }
-
-    /**
-     * Reads the preview bitmap from the DB or null if the preview is not in the DB.
-     */
-    @Thunk Bitmap readFromDb(WidgetCacheKey key, Bitmap recycle, PreviewLoadTask loadTask) {
-        Cursor cursor = null;
-        try {
-            cursor = mDb.query(
-                    new String[]{CacheDb.COLUMN_PREVIEW_BITMAP},
-                    CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND "
-                            + CacheDb.COLUMN_SIZE + " = ?",
-                    new String[]{
-                            key.componentName.flattenToShortString(),
-                            Long.toString(mUserCache.getSerialNumberForUser(key.user)),
-                            key.mSize
-                    });
-            // If cancelled, skip getting the blob and decoding it into a bitmap
-            if (loadTask.isCancelled()) {
-                return null;
-            }
-            if (cursor.moveToNext()) {
-                byte[] blob = cursor.getBlob(0);
-                BitmapFactory.Options opts = new BitmapFactory.Options();
-                opts.inBitmap = recycle;
-                try {
-                    if (!loadTask.isCancelled()) {
-                        return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
-                    }
-                } catch (Exception e) {
-                    return null;
-                }
-            }
-        } catch (SQLException e) {
-            Log.w(TAG, "Error loading preview from DB", e);
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-        return null;
+            @NonNull Consumer<Bitmap> callback) {
+        Handler handler = Executors.UI_HELPER_EXECUTOR.getHandler();
+        HandlerRunnable<Bitmap> request = new HandlerRunnable<>(handler,
+                () -> generatePreview(item, previewSize.getWidth(), previewSize.getHeight()),
+                MAIN_EXECUTOR,
+                callback);
+        Utilities.postAsyncCallback(handler, request);
+        return request;
     }
 
     /**
      * Returns a generated preview for a widget and if the preview should be saved in persistent
      * storage.
-     * @param launcher
-     * @param item
-     * @param recycle
-     * @param previewWidth
-     * @param previewHeight
-     * @return Pair<Bitmap, Boolean>
      */
-    private Pair<Bitmap, Boolean> generatePreview(BaseActivity launcher, WidgetItem item,
-            Bitmap recycle,
-            int previewWidth, int previewHeight) {
+    private Bitmap generatePreview(WidgetItem item, int previewWidth, int previewHeight) {
         if (item.widgetInfo != null) {
-            return generateWidgetPreview(launcher, item.widgetInfo,
-                    previewWidth, recycle, null);
+            return generateWidgetPreview(item.widgetInfo, previewWidth, null);
         } else {
-            return new Pair<>(generateShortcutPreview(launcher, item.activityInfo,
-                    previewWidth, previewHeight, recycle), false);
+            return generateShortcutPreview(item.activityInfo, previewWidth, previewHeight);
         }
     }
 
@@ -369,16 +103,12 @@
      * Generates the widget preview from either the {@link WidgetManagerHelper} or cache
      * and add badge at the bottom right corner.
      *
-     * @param launcher
      * @param info                        information about the widget
      * @param maxPreviewWidth             width of the preview on either workspace or tray
-     * @param preview                     bitmap that can be recycled
      * @param preScaledWidthOut           return the width of the returned bitmap
-     * @return Pair<Bitmap (the preview) , Boolean (should be stored in db)>
      */
-    public Pair<Bitmap, Boolean> generateWidgetPreview(BaseActivity launcher,
-            LauncherAppWidgetProviderInfo info,
-            int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) {
+    public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info,
+            int maxPreviewWidth, int[] preScaledWidthOut) {
         // Load the preview image if possible
         if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
 
@@ -409,117 +139,96 @@
         int previewWidth;
         int previewHeight;
 
-        boolean savePreviewImage = widgetPreviewExists || info.previewImage == 0;
-
         if (widgetPreviewExists && drawable.getIntrinsicWidth() > 0
                 && drawable.getIntrinsicHeight() > 0) {
             previewWidth = drawable.getIntrinsicWidth();
             previewHeight = drawable.getIntrinsicHeight();
         } else {
-            DeviceProfile dp = launcher.getDeviceProfile();
+            DeviceProfile dp = mContext.getDeviceProfile();
             Size widgetSize = WidgetSizes.getWidgetPaddedSizePx(mContext, info.provider, dp, spanX,
                     spanY);
             previewWidth = widgetSize.getWidth();
             previewHeight = widgetSize.getHeight();
         }
 
-        // Scale to fit width only - let the widget preview be clipped in the
-        // vertical dimension
-        float scale = 1f;
         if (preScaledWidthOut != null) {
             preScaledWidthOut[0] = previewWidth;
         }
-        if (previewWidth > maxPreviewWidth) {
-            scale = maxPreviewWidth / (float) (previewWidth);
-        }
+        // Scale to fit width only - let the widget preview be clipped in the
+        // vertical dimension
+        final float scale = previewWidth > maxPreviewWidth
+                ? (maxPreviewWidth / (float) (previewWidth)) : 1f;
         if (scale != 1f) {
             previewWidth = Math.max((int) (scale * previewWidth), 1);
             previewHeight = Math.max((int) (scale * previewHeight), 1);
         }
 
-        final Canvas c = new Canvas();
-        if (preview == null) {
-            // If no bitmap was provided, then allocate a new one with the right size.
-            preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
-            c.setBitmap(preview);
-        } else {
-            // If a bitmap was passed in, attempt to reconfigure the bitmap to the same dimensions
-            // as the preview.
-            try {
-                preview.reconfigure(previewWidth, previewHeight, preview.getConfig());
-            } catch (IllegalArgumentException e) {
-                // This occurs if the preview can't be reconfigured for any reason. In this case,
-                // allocate a new bitmap with the right size.
-                preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
-            }
+        final int previewWidthF = previewWidth;
+        final int previewHeightF = previewHeight;
+        final Drawable drawableF = drawable;
 
-            c.setBitmap(preview);
-            c.drawColor(0, PorterDuff.Mode.CLEAR);
-        }
-
-        // Draw the scaled preview into the final bitmap
-        if (widgetPreviewExists) {
-            drawable.setBounds(0, 0, previewWidth, previewHeight);
-            drawable.draw(c);
-        } else {
-            RectF boxRect;
-
-            // Draw horizontal and vertical lines to represent individual columns.
-            final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
-
-            if (Utilities.ATLEAST_S) {
-                boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */
-                        previewWidth, /* bottom= */ previewHeight);
-
-                p.setStyle(Paint.Style.FILL);
-                p.setColor(Color.WHITE);
-                float roundedCorner = mContext.getResources().getDimension(
-                        android.R.dimen.system_app_widget_background_radius);
-                c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p);
+        return BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight, c -> {
+            // Draw the scaled preview into the final bitmap
+            if (widgetPreviewExists) {
+                drawableF.setBounds(0, 0, previewWidthF, previewHeightF);
+                drawableF.draw(c);
             } else {
-                boxRect = drawBoxWithShadow(c, previewWidth, previewHeight);
-            }
+                RectF boxRect;
 
-            p.setStyle(Paint.Style.STROKE);
-            p.setStrokeWidth(mContext.getResources()
-                    .getDimension(R.dimen.widget_preview_cell_divider_width));
-            p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+                // Draw horizontal and vertical lines to represent individual columns.
+                final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
 
-            float t = boxRect.left;
-            float tileSize = boxRect.width() / spanX;
-            for (int i = 1; i < spanX; i++) {
-                t += tileSize;
-                c.drawLine(t, 0, t, previewHeight, p);
-            }
+                if (Utilities.ATLEAST_S) {
+                    boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */
+                            previewWidthF, /* bottom= */ previewHeightF);
 
-            t = boxRect.top;
-            tileSize = boxRect.height() / spanY;
-            for (int i = 1; i < spanY; i++) {
-                t += tileSize;
-                c.drawLine(0, t, previewWidth, t, p);
-            }
-
-            // Draw icon in the center.
-            try {
-                Drawable icon =
-                        mIconCache.getFullResIcon(info.provider.getPackageName(), info.icon);
-                if (icon != null) {
-                    int appIconSize = launcher.getDeviceProfile().iconSizePx;
-                    int iconSize = (int) Math.min(appIconSize * scale,
-                            Math.min(boxRect.width(), boxRect.height()));
-
-                    icon = mutateOnMainThread(icon);
-                    int hoffset = (previewWidth - iconSize) / 2;
-                    int yoffset = (previewHeight - iconSize) / 2;
-                    icon.setBounds(hoffset, yoffset, hoffset + iconSize, yoffset + iconSize);
-                    icon.draw(c);
+                    p.setStyle(Paint.Style.FILL);
+                    p.setColor(Color.WHITE);
+                    float roundedCorner = mContext.getResources().getDimension(
+                            android.R.dimen.system_app_widget_background_radius);
+                    c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p);
+                } else {
+                    boxRect = drawBoxWithShadow(c, previewWidthF, previewHeightF);
                 }
-            } catch (Resources.NotFoundException e) {
-                savePreviewImage = false;
+
+                p.setStyle(Paint.Style.STROKE);
+                p.setStrokeWidth(mContext.getResources()
+                        .getDimension(R.dimen.widget_preview_cell_divider_width));
+                p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+                float t = boxRect.left;
+                float tileSize = boxRect.width() / spanX;
+                for (int i = 1; i < spanX; i++) {
+                    t += tileSize;
+                    c.drawLine(t, 0, t, previewHeightF, p);
+                }
+
+                t = boxRect.top;
+                tileSize = boxRect.height() / spanY;
+                for (int i = 1; i < spanY; i++) {
+                    t += tileSize;
+                    c.drawLine(0, t, previewWidthF, t, p);
+                }
+
+                // Draw icon in the center.
+                try {
+                    Drawable icon = LauncherAppState.getInstance(mContext).getIconCache()
+                            .getFullResIcon(info.provider.getPackageName(), info.icon);
+                    if (icon != null) {
+                        int appIconSize = mContext.getDeviceProfile().iconSizePx;
+                        int iconSize = (int) Math.min(appIconSize * scale,
+                                Math.min(boxRect.width(), boxRect.height()));
+
+                        icon = mutateOnMainThread(icon);
+                        int hoffset = (previewWidthF - iconSize) / 2;
+                        int yoffset = (previewHeightF - iconSize) / 2;
+                        icon.setBounds(hoffset, yoffset, hoffset + iconSize, yoffset + iconSize);
+                        icon.draw(c);
+                    }
+                } catch (Resources.NotFoundException e) {
+                }
             }
-            c.setBitmap(null);
-        }
-        return new Pair<>(preview, savePreviewImage);
+        });
     }
 
     private RectF drawBoxWithShadow(Canvas c, int width, int height) {
@@ -537,42 +246,29 @@
         return builder.bounds;
     }
 
-    private Bitmap generateShortcutPreview(BaseActivity launcher, ShortcutConfigActivityInfo info,
-            int maxWidth, int maxHeight, Bitmap preview) {
-        int iconSize = launcher.getDeviceProfile().allAppsIconSizePx;
-        int padding = launcher.getResources()
+    private Bitmap generateShortcutPreview(
+            ShortcutConfigActivityInfo info, int maxWidth, int maxHeight) {
+        int iconSize = mContext.getDeviceProfile().allAppsIconSizePx;
+        int padding = mContext.getResources()
                 .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
 
         int size = iconSize + 2 * padding;
         if (maxHeight < size || maxWidth < size) {
             throw new RuntimeException("Max size is too small for preview");
         }
-        final Canvas c = new Canvas();
-        if (preview == null || preview.getWidth() < size || preview.getHeight() < size) {
-            preview = Bitmap.createBitmap(size, size, Config.ARGB_8888);
-            c.setBitmap(preview);
-        } else {
-            if (preview.getWidth() > size || preview.getHeight() > size) {
-                preview.reconfigure(size, size, preview.getConfig());
-            }
+        return BitmapRenderer.createHardwareBitmap(size, size, c -> {
+            drawBoxWithShadow(c, size, size);
 
-            // Reusing bitmap. Clear it.
-            c.setBitmap(preview);
-            c.drawColor(0, PorterDuff.Mode.CLEAR);
-        }
+            LauncherIcons li = LauncherIcons.obtain(mContext);
+            Drawable icon = li.createBadgedIconBitmap(
+                    mutateOnMainThread(info.getFullResIcon(
+                            LauncherAppState.getInstance(mContext).getIconCache())),
+                    Process.myUserHandle(), 0).newIcon(mContext);
+            li.recycle();
 
-        drawBoxWithShadow(c, size, size);
-
-        LauncherIcons li = LauncherIcons.obtain(mContext);
-        Drawable icon = li.createBadgedIconBitmap(
-                mutateOnMainThread(info.getFullResIcon(mIconCache)),
-                Process.myUserHandle(), 0).newIcon(launcher);
-        li.recycle();
-
-        icon.setBounds(padding, padding, padding + iconSize, padding + iconSize);
-        icon.draw(c);
-        c.setBitmap(null);
-        return preview;
+            icon.setBounds(padding, padding, padding + iconSize, padding + iconSize);
+            icon.draw(c);
+        });
     }
 
     private Drawable mutateOnMainThread(final Drawable drawable) {
@@ -585,206 +281,4 @@
             throw new RuntimeException(e);
         }
     }
-
-    /**
-     * @return an array of containing versionCode and lastUpdatedTime for the package.
-     */
-    @Thunk long[] getPackageVersion(String packageName) {
-        synchronized (mPackageVersions) {
-            long[] versions = mPackageVersions.get(packageName);
-            if (versions == null) {
-                versions = new long[2];
-                try {
-                    PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName,
-                            PackageManager.GET_UNINSTALLED_PACKAGES);
-                    versions[0] = info.versionCode;
-                    versions[1] = info.lastUpdateTime;
-                } catch (NameNotFoundException e) {
-                    Log.e(TAG, "PackageInfo not found", e);
-                }
-                mPackageVersions.put(packageName, versions);
-            }
-            return versions;
-        }
-    }
-
-    private class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap>
-            implements CancellationSignal.OnCancelListener {
-        @Thunk final WidgetCacheKey mKey;
-        private final WidgetItem mInfo;
-        private final int mPreviewHeight;
-        private final int mPreviewWidth;
-        private final WidgetPreviewLoadedCallback mCallback;
-        private final BaseActivity mActivity;
-        @Thunk long[] mVersions;
-        @Thunk Bitmap mBitmapToRecycle;
-
-        @Nullable private Bitmap mUnusedPreviewBitmap;
-        private boolean mSaveToDB = false;
-
-        PreviewLoadTask(BaseActivity activity, WidgetCacheKey key, WidgetItem info,
-                int previewWidth, int previewHeight, WidgetPreviewLoadedCallback callback) {
-            mActivity = activity;
-            mKey = key;
-            mInfo = info;
-            mPreviewHeight = previewHeight;
-            mPreviewWidth = previewWidth;
-            mCallback = callback;
-            if (DEBUG) {
-                Log.d(TAG, String.format("%s, %s, %d, %d",
-                        mKey, mInfo, mPreviewHeight, mPreviewWidth));
-            }
-        }
-
-        @Override
-        protected Bitmap doInBackground(Void... params) {
-            Bitmap unusedBitmap = null;
-
-            // If already cancelled before this gets to run in the background, then return early
-            if (isCancelled()) {
-                return null;
-            }
-            synchronized (mUnusedBitmaps) {
-                // Check if we can re-use a bitmap
-                for (Bitmap candidate : mUnusedBitmaps) {
-                    if (candidate != null && candidate.isMutable()
-                            && candidate.getWidth() == mPreviewWidth
-                            && candidate.getHeight() == mPreviewHeight) {
-                        unusedBitmap = candidate;
-                        mUnusedBitmaps.remove(unusedBitmap);
-                        break;
-                    }
-                }
-            }
-
-            // creating a bitmap is expensive. Do not do this inside synchronized block.
-            if (unusedBitmap == null) {
-                unusedBitmap = Bitmap.createBitmap(mPreviewWidth, mPreviewHeight, Config.ARGB_8888);
-            }
-            // If cancelled now, don't bother reading the preview from the DB
-            if (isCancelled()) {
-                return unusedBitmap;
-            }
-            Bitmap preview = readFromDb(mKey, unusedBitmap, this);
-            // Only consider generating the preview if we have not cancelled the task already
-            if (!isCancelled() && preview == null) {
-                // Fetch the version info before we generate the preview, so that, in-case the
-                // app was updated while we are generating the preview, we use the old version info,
-                // which would gets re-written next time.
-                boolean persistable = mInfo.activityInfo == null
-                        || mInfo.activityInfo.isPersistable();
-                mVersions = persistable ? getPackageVersion(mKey.componentName.getPackageName())
-                        : null;
-
-                // it's not in the db... we need to generate it
-                Pair<Bitmap, Boolean> pair = generatePreview(mActivity, mInfo, unusedBitmap,
-                        mPreviewWidth, mPreviewHeight);
-                preview = pair.first;
-
-                if (preview != unusedBitmap) {
-                    mUnusedPreviewBitmap = unusedBitmap;
-                }
-
-                this.mSaveToDB = pair.second;
-            }
-            return preview;
-        }
-
-        @Override
-        protected void onPostExecute(final Bitmap preview) {
-            mCallback.onPreviewLoaded(preview);
-
-            // Write the generated preview to the DB in the worker thread
-            if (mVersions != null) {
-                MODEL_EXECUTOR.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mUnusedPreviewBitmap != null) {
-                            // If we didn't end up using the bitmap, it can be added back into the
-                            // recycled set.
-                            synchronized (mUnusedBitmaps) {
-                                mUnusedBitmaps.add(mUnusedPreviewBitmap);
-                            }
-                        }
-
-                        if (!isCancelled() && mSaveToDB) {
-                            // If we are still using this preview, then write it to the DB and then
-                            // let the normal clear mechanism recycle the bitmap
-                            writeToDb(mKey, mVersions, preview);
-                            mBitmapToRecycle = preview;
-                        } else {
-                            // If we've already cancelled, then skip writing the bitmap to the DB
-                            // and manually add the bitmap back to the recycled set
-                            synchronized (mUnusedBitmaps) {
-                                mUnusedBitmaps.add(preview);
-                            }
-                        }
-                    }
-                });
-            } else {
-                // If we don't need to write to disk, then ensure the preview gets recycled by
-                // the normal clear mechanism
-                mBitmapToRecycle = preview;
-            }
-        }
-
-        @Override
-        protected void onCancelled(final Bitmap preview) {
-            // If we've cancelled while the task is running, then can return the bitmap to the
-            // recycled set immediately. Otherwise, it will be recycled after the preview is written
-            // to disk.
-            if (preview != null) {
-                MODEL_EXECUTOR.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        synchronized (mUnusedBitmaps) {
-                            mUnusedBitmaps.add(preview);
-                        }
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void onCancel() {
-            cancel(true);
-
-            // This only handles the case where the PreviewLoadTask is cancelled after the task has
-            // successfully completed (including having written to disk when necessary).  In the
-            // other cases where it is cancelled while the task is running, it will be cleaned up
-            // in the tasks's onCancelled() call, and if cancelled while the task is writing to
-            // disk, it will be cancelled in the task's onPostExecute() call.
-            if (mBitmapToRecycle != null) {
-                MODEL_EXECUTOR.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        synchronized (mUnusedBitmaps) {
-                            mUnusedBitmaps.add(mBitmapToRecycle);
-                        }
-                        mBitmapToRecycle = null;
-                    }
-                });
-            }
-        }
-    }
-
-    private static final class WidgetCacheKey extends ComponentKey {
-
-        @Thunk final String mSize;
-
-        WidgetCacheKey(ComponentName componentName, UserHandle user, String size) {
-            super(componentName, user);
-            this.mSize = size;
-        }
-
-        @Override
-        public int hashCode() {
-            return super.hashCode() ^ mSize.hashCode();
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            return super.equals(o) && ((WidgetCacheKey) o).mSize.equals(mSize);
-        }
-    }
 }
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 991910d..2347d28 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -140,10 +140,9 @@
                         .addDragListener(new AppWidgetHostViewDragListener(launcher));
             }
             if (preview == null && mAppWidgetHostViewPreview == null) {
-                Drawable p = new FastBitmapDrawable(
-                        app.getWidgetCache().generateWidgetPreview(launcher,
-                                createWidgetInfo.info, maxWidth, null,
-                                previewSizeBeforeScale).first);
+                Drawable p = new FastBitmapDrawable(new DatabaseWidgetPreviewLoader(launcher)
+                        .generateWidgetPreview(
+                                createWidgetInfo.info, maxWidth, previewSizeBeforeScale));
                 if (RoundedCornerEnforcement.isRoundedCornerEnabled()) {
                     p = new RoundDrawableWrapper(p, mEnforcedRoundedCornersForWidget);
                 }
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index bd444db..423c66a 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -26,14 +26,12 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
-import android.os.CancellationSignal;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Size;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnLayoutChangeListener;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -42,6 +40,7 @@
 import android.widget.RemoteViews;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseActivity;
@@ -51,9 +50,13 @@
 import com.android.launcher3.R;
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.RoundDrawableWrapper;
+import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.util.WidgetSizes;
 
+import java.util.function.Consumer;
+
 /**
  * Represents the individual cell of the widget inside the widget tray. The preview is drawn
  * horizontally centered, and scaled down if needed.
@@ -63,7 +66,7 @@
  * transition from the view to drag view, so when adding padding support, DnD would need to
  * consider the appropriate scaling factor.
  */
-public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
+public class WidgetCell extends LinearLayout {
 
     private static final String TAG = "WidgetCell";
     private static final boolean DEBUG = false;
@@ -115,14 +118,11 @@
 
     protected WidgetItem mItem;
 
-    private WidgetPreviewLoader mWidgetPreviewLoader;
+    private final DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
 
-    protected CancellationSignal mActiveRequest;
+    protected HandlerRunnable mActiveRequest;
     private boolean mAnimatePreview = true;
 
-    private boolean mApplyBitmapDeferred = false;
-    private Drawable mDeferredDrawable;
-
     protected final BaseActivity mActivity;
     private final CheckLongPressHelper mLongPressHelper;
     private final float mEnforcedCornerRadius;
@@ -144,6 +144,7 @@
         super(context, attrs, defStyle);
 
         mActivity = BaseActivity.fromContext(context);
+        mWidgetPreviewLoader = new DatabaseWidgetPreviewLoader(mActivity);
         mLongPressHelper = new CheckLongPressHelper(this);
         mLongPressHelper.setLongPressTimeoutFactor(1);
 
@@ -218,7 +219,36 @@
         this.mSourceContainer = sourceContainer;
     }
 
-    public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
+    /**
+     * Applies the item to this view
+     */
+    public void applyFromCellItem(WidgetItem item) {
+        applyFromCellItem(item, 1f);
+    }
+
+    /**
+     * Applies the item to this view
+     */
+    public void applyFromCellItem(WidgetItem item, float previewScale) {
+        applyFromCellItem(item, previewScale, this::applyPreview, null);
+    }
+
+    /**
+     * Applies the item to this view
+     * @param item item to apply
+     * @param previewScale factor to scale the preview
+     * @param callback callback when preview is loaded in case the preview is being loaded or cached
+     * @param cachedPreview previously cached preview bitmap is present
+     */
+    public void applyFromCellItem(WidgetItem item, float previewScale,
+            @NonNull Consumer<Bitmap> callback, @Nullable Bitmap cachedPreview) {
+        // setPreviewSize
+        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+        Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile, item);
+        mTargetPreviewWidth = widgetSize.getWidth();
+        mTargetPreviewHeight = widgetSize.getHeight();
+        mPreviewContainerScale = previewScale;
+
         applyPreviewOnAppWidgetHostView(item);
 
         Context context = getContext();
@@ -240,14 +270,14 @@
             }
         }
 
-        mWidgetPreviewLoader = loader;
         if (item.activityInfo != null) {
             setTag(new PendingAddShortcutInfo(item.activityInfo));
         } else {
             setTag(new PendingAddWidgetInfo(item.widgetInfo, mSourceContainer));
         }
-    }
 
+        ensurePreviewWithCallback(callback, cachedPreview);
+    }
 
     private void applyPreviewOnAppWidgetHostView(WidgetItem item) {
         if (mRemoteViewsPreview != null) {
@@ -294,37 +324,15 @@
         return mAppWidgetHostViewPreview;
     }
 
-    /**
-     * Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but
-     * will not cause invalidate, so that when deferring is disabled later, all the bitmaps are
-     * ready.
-     * This prevents invalidates while the animation is running.
-     */
-    public void setApplyBitmapDeferred(boolean isDeferred) {
-        if (mApplyBitmapDeferred != isDeferred) {
-            mApplyBitmapDeferred = isDeferred;
-            if (!mApplyBitmapDeferred && mDeferredDrawable != null) {
-                applyPreview(mDeferredDrawable);
-                mDeferredDrawable = null;
-            }
-        }
-    }
-
     public void setAnimatePreview(boolean shouldAnimate) {
         mAnimatePreview = shouldAnimate;
     }
 
-    public void applyPreview(Bitmap bitmap) {
-        FastBitmapDrawable drawable = new FastBitmapDrawable(bitmap);
-        applyPreview(new RoundDrawableWrapper(drawable, mEnforcedCornerRadius));
-    }
+    private void applyPreview(Bitmap bitmap) {
+        if (bitmap != null) {
+            Drawable drawable = new RoundDrawableWrapper(
+                    new FastBitmapDrawable(bitmap), mEnforcedCornerRadius);
 
-    private void applyPreview(Drawable drawable) {
-        if (mApplyBitmapDeferred) {
-            mDeferredDrawable = drawable;
-            return;
-        }
-        if (drawable != null) {
             // Scale down the preview size if it's wider than the cell.
             float scale = 1f;
             if (mTargetPreviewWidth > 0) {
@@ -349,6 +357,10 @@
         } else {
             mWidgetImageContainer.setAlpha(1f);
         }
+        if (mActiveRequest != null) {
+            mActiveRequest.cancel();
+            mActiveRequest = null;
+        }
     }
 
     private void setContainerSize(int width, int height) {
@@ -358,7 +370,13 @@
         mWidgetImageContainer.setLayoutParams(layoutParams);
     }
 
-    public void ensurePreview() {
+    /**
+     * Ensures that the preview is already loaded or being loaded. If the preview is not loaded,
+     * it applies the provided cachedPreview. If that is null, it starts a loader and notifies the
+     * callback on successful load.
+     */
+    private void ensurePreviewWithCallback(Consumer<Bitmap> callback,
+            @Nullable Bitmap cachedPreview) {
         if (mAppWidgetHostViewPreview != null) {
             int containerWidth = (int) (mTargetPreviewWidth * mPreviewContainerScale);
             int containerHeight = (int) (mTargetPreviewHeight * mPreviewContainerScale);
@@ -382,38 +400,18 @@
             mAppWidgetHostViewPreview.setLayoutParams(params);
             mWidgetImageContainer.addView(mAppWidgetHostViewPreview, /* index= */ 0);
             mWidgetImage.setVisibility(View.GONE);
-            applyPreview((Drawable) null);
+            applyPreview(null);
+            return;
+        }
+        if (cachedPreview != null) {
+            applyPreview(cachedPreview);
             return;
         }
         if (mActiveRequest != null) {
             return;
         }
         mActiveRequest = mWidgetPreviewLoader.loadPreview(
-                BaseActivity.fromContext(getContext()), mItem,
-                new Size(mTargetPreviewWidth, mTargetPreviewHeight),
-                this::applyPreview);
-    }
-
-    /** Sets the widget preview image size in number of cells. */
-    public Size setPreviewSize(WidgetItem widgetItem) {
-        return setPreviewSize(widgetItem, 1f);
-    }
-
-    /** Sets the widget preview image size, in number of cells, and preview scale. */
-    public Size setPreviewSize(WidgetItem widgetItem, float previewScale) {
-        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
-        Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile, widgetItem);
-        mTargetPreviewWidth = widgetSize.getWidth();
-        mTargetPreviewHeight = widgetSize.getHeight();
-        mPreviewContainerScale = previewScale;
-        return widgetSize;
-    }
-
-    @Override
-    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
-            int oldTop, int oldRight, int oldBottom) {
-        removeOnLayoutChangeListener(this);
-        ensurePreview();
+                mItem, new Size(mTargetPreviewWidth, mTargetPreviewHeight), callback);
     }
 
     @Override
@@ -429,17 +427,6 @@
         mLongPressHelper.cancelLongPress();
     }
 
-    /**
-     * Helper method to get the string info of the tag.
-     */
-    private String getTagToString() {
-        if (getTag() instanceof PendingAddWidgetInfo ||
-                getTag() instanceof PendingAddShortcutInfo) {
-            return getTag().toString();
-        }
-        return "";
-    }
-
     private static NavigableAppWidgetHostView createAppWidgetHostView(Context context) {
         return new NavigableAppWidgetHostView(context) {
             @Override
@@ -450,12 +437,7 @@
     }
 
     private static boolean isLauncherContext(Context context) {
-        try {
-            Launcher.getLauncher(context);
-            return true;
-        } catch (Exception e) {
-            return false;
-        }
+        return ActivityContext.lookupContext(context) instanceof Launcher;
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/WidgetPreviewLoader.java b/src/com/android/launcher3/widget/WidgetPreviewLoader.java
deleted file mode 100644
index ff5c82f..0000000
--- a/src/com/android/launcher3/widget/WidgetPreviewLoader.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2021 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 android.graphics.Bitmap;
-import android.os.CancellationSignal;
-import android.util.Size;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.model.WidgetItem;
-
-/** Asynchronous loader of preview bitmaps for {@link WidgetItem}s. */
-public interface WidgetPreviewLoader {
-    /**
-     * Loads a widget preview and calls back to {@code callback} when complete.
-     *
-     * @return a {@link CancellationSignal} which can be used to cancel the request.
-     */
-    @NonNull
-    @UiThread
-    CancellationSignal loadPreview(
-            @NonNull BaseActivity activity,
-            @NonNull WidgetItem item,
-            @NonNull Size previewSize,
-            @NonNull WidgetPreviewLoadedCallback callback);
-
-    /** Callback class for requests to {@link WidgetPreviewLoader}. */
-    interface WidgetPreviewLoadedCallback {
-        void onPreviewLoaded(@NonNull Bitmap preview);
-    }
-}
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 6beff3a..bb4638a 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -37,7 +37,6 @@
 import android.widget.TextView;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.model.WidgetItem;
@@ -199,11 +198,7 @@
             tableRow.setGravity(Gravity.TOP);
             row.forEach(widgetItem -> {
                 WidgetCell widget = addItemCell(tableRow);
-                widget.setPreviewSize(widgetItem);
-                widget.applyFromCellItem(widgetItem, LauncherAppState.getInstance(mActivityContext)
-                        .getWidgetCache());
-                widget.ensurePreview();
-                widget.setVisibility(View.VISIBLE);
+                widget.applyFromCellItem(widgetItem);
             });
             widgetsTable.addView(tableRow);
         });
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 9dbfa87..09f0299 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -687,7 +687,7 @@
                         .findFirst()
                         .orElse(null);
         if (viewHolderForTip != null) {
-            return ((ViewGroup) viewHolderForTip.mTableContainer.getChildAt(0)).getChildAt(0);
+            return ((ViewGroup) viewHolderForTip.tableContainer.getChildAt(0)).getChildAt(0);
         }
 
         return null;
@@ -745,7 +745,6 @@
             mWidgetsListAdapter = new WidgetsListAdapter(
                     context,
                     LayoutInflater.from(context),
-                    apps.getWidgetCache(),
                     apps.getIconCache(),
                     this::getEmptySpaceHeight,
                     /* iconClickListener= */ WidgetsFullSheet.this,
@@ -784,7 +783,6 @@
             if (mAdapterType == PRIMARY || mAdapterType == WORK) {
                 mWidgetsRecyclerView.addOnAttachStateChangeListener(mBindScrollbarInSearchMode);
             }
-            mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
             mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
         }
     }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 1ad1f7a..de0d8b8 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -24,14 +24,12 @@
 import android.graphics.Rect;
 import android.os.Process;
 import android.util.Log;
-import android.util.Size;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
-import android.widget.TableRow;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -41,29 +39,22 @@
 import androidx.recyclerview.widget.RecyclerView.LayoutParams;
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.recyclerview.ViewHolderBinder;
 import com.android.launcher3.util.LabelComparator;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.CachingWidgetPreviewLoader;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
-import com.android.launcher3.widget.WidgetCell;
-import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback;
 import com.android.launcher3.widget.model.WidgetListSpaceEntry;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
 import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
-import com.android.launcher3.widget.util.WidgetSizes;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
@@ -94,12 +85,9 @@
     private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
     private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
 
-    private final Context mContext;
     private final Launcher mLauncher;
-    private final CachingWidgetPreviewLoader mCachingPreviewLoader;
     private final WidgetsDiffReporter mDiffReporter;
     private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
-    private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
     private final WidgetListBaseRowEntryComparator mRowComparator =
             new WidgetListBaseRowEntryComparator();
 
@@ -115,26 +103,21 @@
     @Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
     @Nullable private RecyclerView mRecyclerView;
     @Nullable private PackageUserKey mPendingClickHeader;
-    private final int mShortcutPreviewPadding;
     private final int mSpacingBetweenEntries;
     private int mMaxSpanSize = 4;
 
-    private final WidgetPreviewLoadedCallback mPreviewLoadedCallback =
-            ignored -> updateVisibleEntries();
-
     public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
-            DatabaseWidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
-            IntSupplier emptySpaceHeightProvider,
+            IconCache iconCache, IntSupplier emptySpaceHeightProvider,
             OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
-        mContext = context;
         mLauncher = Launcher.getLauncher(context);
-        mCachingPreviewLoader = new CachingWidgetPreviewLoader(widgetPreviewLoader);
         mDiffReporter = new WidgetsDiffReporter(iconCache, this);
         WidgetsListDrawableFactory listDrawableFactory = new WidgetsListDrawableFactory(context);
-        mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(
-                layoutInflater, iconClickListener, iconLongClickListener,
-                mCachingPreviewLoader, listDrawableFactory);
-        mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder);
+
+        mViewHolderBinders.put(
+                VIEW_TYPE_WIDGETS_LIST,
+                new WidgetsListTableViewHolderBinder(
+                        layoutInflater, iconClickListener, iconLongClickListener,
+                        listDrawableFactory));
         mViewHolderBinders.put(
                 VIEW_TYPE_WIDGETS_HEADER,
                 new WidgetsListHeaderViewHolderBinder(
@@ -150,9 +133,6 @@
         mViewHolderBinders.put(
                 VIEW_TYPE_WIDGETS_SPACE,
                 new WidgetsSpaceViewHolderBinder(emptySpaceHeightProvider));
-        mShortcutPreviewPadding =
-                2 * context.getResources()
-                        .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
         mSpacingBetweenEntries =
                 context.getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
     }
@@ -186,28 +166,6 @@
         mFilter = filter;
     }
 
-    /**
-     * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv}.
-     *
-     * @see WidgetCell#setApplyBitmapDeferred(boolean)
-     */
-    public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) {
-        mWidgetsListTableViewHolderBinder.setApplyBitmapDeferred(isDeferred);
-
-        for (int i = rv.getChildCount() - 1; i >= 0; i--) {
-            ViewHolder viewHolder = rv.getChildViewHolder(rv.getChildAt(i));
-            if (viewHolder.getItemViewType() == VIEW_TYPE_WIDGETS_LIST) {
-                WidgetsRowViewHolder holder = (WidgetsRowViewHolder) viewHolder;
-                for (int j = holder.mTableContainer.getChildCount() - 1; j >= 0; j--) {
-                    TableRow row =  (TableRow) holder.mTableContainer.getChildAt(j);
-                    for (int k = row.getChildCount() - 1; k >= 0; k--) {
-                        ((WidgetCell) row.getChildAt(k)).setApplyBitmapDeferred(isDeferred);
-                    }
-                }
-            }
-        }
-    }
-
     @Override
     public int getItemCount() {
         return mVisibleEntries.size();
@@ -233,7 +191,6 @@
 
     /** Updates the widget list based on {@code tempEntries}. */
     public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
-        mCachingPreviewLoader.clearAll();
         mAllEntries.clear();
         mAllEntries.add(new WidgetListSpaceEntry());
         tempEntries.stream().sorted(mRowComparator).forEach(mAllEntries::add);
@@ -247,15 +204,10 @@
     public void setWidgetsOnSearch(List<WidgetsListBaseEntry> searchResults) {
         // Forget the expanded package every time widget list is refreshed in search mode.
         mWidgetsContentVisiblePackageUserKey = null;
-        cancelLoadingPreviews();
         setWidgets(searchResults);
     }
 
     private void updateVisibleEntries() {
-        // If not all previews are ready, then defer this update and try again after the preview
-        // loads.
-        if (!ensureAllPreviewsReady()) return;
-
         // Get the current top of the header with the matching key before adjusting the visible
         // entries.
         OptionalInt previousPositionForPackageUserKey =
@@ -293,54 +245,6 @@
         }
     }
 
-    /**
-     * Checks that all preview images are loaded and starts loading for those that aren't ready.
-     *
-     * @return true if all previews are ready and the data can be updated, false otherwise.
-     */
-    private boolean ensureAllPreviewsReady() {
-        boolean allReady = true;
-        BaseActivity activity = BaseActivity.fromContext(mContext);
-        for (WidgetsListBaseEntry entry : mAllEntries) {
-            if (!(entry instanceof WidgetsListContentEntry)) continue;
-
-            WidgetsListContentEntry contentEntry = (WidgetsListContentEntry) entry;
-            if (!matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) {
-                // If the entry isn't visible, clear any loaded previews.
-                mCachingPreviewLoader.clearPreviews(contentEntry.mWidgets);
-                continue;
-            }
-
-            for (int i = 0; i < entry.mWidgets.size(); i++) {
-                WidgetItem widgetItem = entry.mWidgets.get(i);
-                DeviceProfile deviceProfile = activity.getDeviceProfile();
-                Size widgetSize = WidgetSizes.getWidgetItemSizePx(mContext, deviceProfile,
-                        widgetItem);
-                if (widgetItem.isShortcut()) {
-                    widgetSize =
-                            new Size(
-                                    widgetSize.getWidth() + mShortcutPreviewPadding,
-                                    widgetSize.getHeight() + mShortcutPreviewPadding);
-                }
-
-                if (widgetItem.hasPreviewLayout()
-                        || mCachingPreviewLoader.isPreviewLoaded(widgetItem, widgetSize)) {
-                    // The widget is ready if it can be rendered with a preview layout or if its
-                    // preview bitmap is in the cache.
-                    continue;
-                }
-
-                // If we've reached this point, we should load the preview for the widget.
-                allReady = false;
-                mCachingPreviewLoader.loadPreview(
-                        activity,
-                        widgetItem,
-                        widgetSize,
-                        mPreviewLoadedCallback);
-            }
-        }
-        return allReady;
-    }
 
     /** Returns whether {@code entry} matches {@code key}. */
     private static boolean isHeaderForPackageUserKey(
@@ -361,13 +265,17 @@
     public void resetExpandedHeader() {
         if (mWidgetsContentVisiblePackageUserKey != null) {
             mWidgetsContentVisiblePackageUserKey = null;
-            cancelLoadingPreviews();
             updateVisibleEntries();
         }
     }
 
     @Override
-    public void onBindViewHolder(ViewHolder holder, int pos) {
+    public void onBindViewHolder(ViewHolder holder, int position) {
+        onBindViewHolder(holder, position, Collections.EMPTY_LIST);
+    }
+
+    @Override
+    public void onBindViewHolder(ViewHolder holder, int pos, List<Object> payloads) {
         ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
         WidgetsListBaseEntry entry = mVisibleEntries.get(pos);
 
@@ -376,7 +284,7 @@
         if (pos == (getItemCount() - 1)) {
             listPos |= POSITION_LAST;
         }
-        viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), listPos);
+        viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), listPos, payloads);
         holder.itemView.setTag(R.id.tag_widget_entry, entry);
     }
 
@@ -430,8 +338,6 @@
         // Ignore invalid clicks, such as collapsing a package that isn't currently expanded.
         if (!showWidgets && !packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) return;
 
-        cancelLoadingPreviews();
-
         if (showWidgets) {
             mWidgetsContentVisiblePackageUserKey = packageUserKey;
             mLauncher.getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_APP_EXPANDED);
@@ -446,16 +352,6 @@
         updateVisibleEntries();
     }
 
-    private void cancelLoadingPreviews() {
-        mCachingPreviewLoader.clearAll();
-    }
-
-    /** Returns the position of the currently expanded header, or empty if it's not present. */
-    public OptionalInt getSelectedHeaderPosition() {
-        if (mWidgetsContentVisiblePackageUserKey == null) return OptionalInt.empty();
-        return getPositionForPackageUserKey(mWidgetsContentVisiblePackageUserKey);
-    }
-
     /**
      * Returns the position of {@code key} in {@link #mVisibleEntries}, or  empty if it's not
      * present.
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
index 00750bd..fadb637 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
@@ -23,6 +23,8 @@
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
 
+import java.util.List;
+
 /**
  * Binds data from {@link WidgetsListHeaderEntry} to UI elements in {@link WidgetsListHeaderHolder}.
  */
@@ -50,7 +52,7 @@
 
     @Override
     public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data,
-            @ListPosition int position) {
+            @ListPosition int position, List<Object> payloads) {
         WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
         widgetsListHeader.applyFromItemInfoWithIcon(data);
         widgetsListHeader.setExpanded(data.isWidgetListShown());
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
index 1e2a3bf..bff43c1 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
@@ -24,6 +24,8 @@
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
 import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
 
+import java.util.List;
+
 /**
  * Binds data from {@link WidgetsListHeaderEntry} to UI elements in {@link WidgetsListHeaderHolder}.
  */
@@ -51,7 +53,7 @@
 
     @Override
     public void bindViewHolder(WidgetsListSearchHeaderHolder viewHolder,
-            WidgetsListSearchHeaderEntry data, @ListPosition int position) {
+            WidgetsListSearchHeaderEntry data, @ListPosition int position, List<Object> payloads) {
         WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
         widgetsListHeader.applyFromItemInfoWithIcon(data);
         widgetsListHeader.setExpanded(data.isWidgetListShown());
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 804b0ae..feeb0fe 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -20,7 +20,7 @@
 
 import android.graphics.Bitmap;
 import android.util.Log;
-import android.util.Size;
+import android.util.Pair;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -33,7 +33,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.recyclerview.ViewHolderBinder;
-import com.android.launcher3.widget.CachingWidgetPreviewLoader;
 import com.android.launcher3.widget.WidgetCell;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.util.WidgetsTableUtils;
@@ -53,31 +52,18 @@
     private final OnClickListener mIconClickListener;
     private final OnLongClickListener mIconLongClickListener;
     private final WidgetsListDrawableFactory mListDrawableFactory;
-    private final CachingWidgetPreviewLoader mWidgetPreviewLoader;
-    private boolean mApplyBitmapDeferred = false;
 
     public WidgetsListTableViewHolderBinder(
             LayoutInflater layoutInflater,
             OnClickListener iconClickListener,
             OnLongClickListener iconLongClickListener,
-            CachingWidgetPreviewLoader widgetPreviewLoader,
             WidgetsListDrawableFactory listDrawableFactory) {
         mLayoutInflater = layoutInflater;
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
-        mWidgetPreviewLoader = widgetPreviewLoader;
         mListDrawableFactory = listDrawableFactory;
     }
 
-    /**
-     * Defers applying bitmap on all the {@link WidgetCell} at
-     * {@link #bindViewHolder(WidgetsRowViewHolder, WidgetsListContentEntry, int)} if
-     * {@code applyBitmapDeferred} is {@code true}.
-     */
-    public void setApplyBitmapDeferred(boolean applyBitmapDeferred) {
-        mApplyBitmapDeferred = applyBitmapDeferred;
-    }
-
     @Override
     public WidgetsRowViewHolder newViewHolder(ViewGroup parent) {
         if (DEBUG) {
@@ -87,25 +73,30 @@
         WidgetsRowViewHolder viewHolder =
                 new WidgetsRowViewHolder(mLayoutInflater.inflate(
                         R.layout.widgets_table_container, parent, false));
-        viewHolder.mTableContainer.setBackgroundDrawable(
+        viewHolder.tableContainer.setBackgroundDrawable(
                 mListDrawableFactory.createContentBackgroundDrawable());
         return viewHolder;
     }
 
     @Override
     public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry,
-            @ListPosition int position) {
-        WidgetsListTableView table = holder.mTableContainer;
+            @ListPosition int position, List<Object> payloads) {
+        for (Object payload : payloads) {
+            Pair<WidgetItem, Bitmap> pair = (Pair) payload;
+            holder.previewCache.put(pair.first, pair.second);
+        }
+
+        WidgetsListTableView table = holder.tableContainer;
         if (DEBUG) {
             Log.d(TAG, String.format("onBindViewHolder [widget#=%d, table.getChildCount=%d]",
                     entry.mWidgets.size(), table.getChildCount()));
         }
         table.setListDrawableState(((position & POSITION_LAST) != 0) ? LAST : MIDDLE);
-
         List<ArrayList<WidgetItem>> widgetItemsTable =
                 WidgetsTableUtils.groupWidgetItemsIntoTable(
                         entry.mWidgets, entry.getMaxSpanSizeInCells());
         recycleTableBeforeBinding(table, widgetItemsTable);
+
         // Bind the widget items.
         for (int i = 0; i < widgetItemsTable.size(); i++) {
             List<WidgetItem> widgetItemsPerRow = widgetItemsTable.get(i);
@@ -115,16 +106,14 @@
                 WidgetCell widget = (WidgetCell) row.getChildAt(j);
                 widget.clear();
                 WidgetItem widgetItem = widgetItemsPerRow.get(j);
-                Size previewSize = widget.setPreviewSize(widgetItem);
-                widget.applyFromCellItem(widgetItem, mWidgetPreviewLoader);
-                widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
-                Bitmap preview = mWidgetPreviewLoader.getPreview(widgetItem, previewSize);
-                if (preview == null) {
-                    widget.ensurePreview();
-                } else {
-                    widget.applyPreview(preview);
-                }
                 widget.setVisibility(View.VISIBLE);
+
+                // When preview loads, notify adapter to rebind the item and possibly animate
+                widget.applyFromCellItem(widgetItem, 1f,
+                        bitmap -> holder.getBindingAdapter().notifyItemChanged(
+                                holder.getBindingAdapterPosition(),
+                                Pair.create(widgetItem, bitmap)),
+                        holder.previewCache.get(widgetItem));
             }
         }
     }
@@ -165,6 +154,7 @@
                     View preview = widget.findViewById(R.id.widget_preview_container);
                     preview.setOnClickListener(mIconClickListener);
                     preview.setOnLongClickListener(mIconLongClickListener);
+                    widget.setAnimatePreview(false);
                     tableRow.addView(widget);
                 }
             }
@@ -173,9 +163,10 @@
 
     @Override
     public void unbindViewHolder(WidgetsRowViewHolder holder) {
-        int numOfRows = holder.mTableContainer.getChildCount();
+        int numOfRows = holder.tableContainer.getChildCount();
+        holder.previewCache.clear();
         for (int i = 0; i < numOfRows; i++) {
-            TableRow tableRow = (TableRow) holder.mTableContainer.getChildAt(i);
+            TableRow tableRow = (TableRow) holder.tableContainer.getChildAt(i);
             int numOfCols = tableRow.getChildCount();
             for (int j = 0; j < numOfCols; j++) {
                 WidgetCell widget = (WidgetCell) tableRow.getChildAt(j);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 60dfebe..c986007 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -32,7 +32,6 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.widget.WidgetCell;
@@ -109,10 +108,7 @@
 
             for (WidgetItem widgetItem : widgetItems) {
                 WidgetCell widgetCell = addItemCell(tableRow);
-                widgetCell.setPreviewSize(widgetItem, data.mPreviewScale);
-                widgetCell.applyFromCellItem(widgetItem,
-                        LauncherAppState.getInstance(getContext()).getWidgetCache());
-                widgetCell.ensurePreview();
+                widgetCell.applyFromCellItem(widgetItem, data.mPreviewScale);
             }
             addView(tableRow);
         }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
index 618e2cb..fe2d84b 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
@@ -15,20 +15,26 @@
  */
 package com.android.launcher3.widget.picker;
 
+import android.graphics.Bitmap;
 import android.view.View;
 
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
 import com.android.launcher3.R;
+import com.android.launcher3.model.WidgetItem;
+
+import java.util.HashMap;
+import java.util.Map;
 
 /** A {@link ViewHolder} for showing widgets of an app in the full widget picker. */
 public final class WidgetsRowViewHolder extends ViewHolder {
 
-    public final WidgetsListTableView mTableContainer;
+    public final WidgetsListTableView tableContainer;
+    public final Map<WidgetItem, Bitmap> previewCache = new HashMap<>();
 
     public WidgetsRowViewHolder(View v) {
         super(v);
 
-        mTableContainer = v.findViewById(R.id.widgets_table);
+        tableContainer = v.findViewById(R.id.widgets_table);
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
index f33c2fa..1aa5753 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.recyclerview.ViewHolderBinder;
 import com.android.launcher3.widget.model.WidgetListSpaceEntry;
 
+import java.util.List;
 import java.util.function.IntSupplier;
 
 /**
@@ -47,7 +48,8 @@
     }
 
     @Override
-    public void bindViewHolder(ViewHolder holder, WidgetListSpaceEntry data, int position) {
+    public void bindViewHolder(ViewHolder holder, WidgetListSpaceEntry data,
+            @ListPosition int position, List<Object> payloads) {
         ((EmptySpaceView) holder.itemView).setFixedHeight(mEmptySpaceHeightProvider.getAsInt());
     }
 
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 631067b..12e9e1e 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -150,7 +150,6 @@
             }
         }
 
-        app.getWidgetCache().removeObsoletePreviews(widgetsAndShortcuts, packageUser);
         return updatedItems;
     }