From ed2a55f413aa542b9ee7a1c01aa95cc4be7b1ce8 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 11 Aug 2021 15:13:17 -0700 Subject: [PATCH] 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 --- .../CachingWidgetPreviewLoaderTest.java | 409 ---------- .../widget/picker/WidgetsListAdapterTest.java | 4 +- ...WidgetsListHeaderViewHolderBinderTest.java | 6 +- ...sListSearchHeaderViewHolderBinderTest.java | 6 +- .../WidgetsListTableViewHolderBinderTest.java | 10 +- .../android/launcher3/LauncherAppState.java | 8 - .../launcher3/dragndrop/AddItemActivity.java | 4 +- .../launcher3/model/PackageUpdatedTask.java | 2 - .../recyclerview/ViewHolderBinder.java | 3 +- .../widget/CachingWidgetPreviewLoader.java | 289 ------- .../widget/DatabaseWidgetPreviewLoader.java | 710 +++--------------- .../widget/PendingItemDragHelper.java | 7 +- .../android/launcher3/widget/WidgetCell.java | 144 ++-- .../launcher3/widget/WidgetPreviewLoader.java | 47 -- .../launcher3/widget/WidgetsBottomSheet.java | 7 +- .../widget/picker/WidgetsFullSheet.java | 4 +- .../widget/picker/WidgetsListAdapter.java | 134 +--- .../WidgetsListHeaderViewHolderBinder.java | 4 +- ...dgetsListSearchHeaderViewHolderBinder.java | 4 +- .../WidgetsListTableViewHolderBinder.java | 51 +- .../WidgetsRecommendationTableLayout.java | 6 +- .../widget/picker/WidgetsRowViewHolder.java | 10 +- .../picker/WidgetsSpaceViewHolderBinder.java | 4 +- .../android/launcher3/model/WidgetsModel.java | 1 - 24 files changed, 241 insertions(+), 1633 deletions(-) delete mode 100644 robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java delete mode 100644 src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java delete mode 100644 src/com/android/launcher3/widget/WidgetPreviewLoader.java 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 1090d1e69b..0000000000 --- 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 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 fb44ca10d5..12aac8b2c0 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.icons.IconCache; 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 @@ public final class WidgetsListAdapterTest { 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 @@ public final class WidgetsListAdapterTest { 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 b7d778855e..fa000c0131 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.doAnswer; 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 @@ public final class WidgetsListHeaderViewHolderBinderTest { 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 @@ public final class WidgetsListHeaderViewHolderBinderTest { 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 2b4cea063c..b18c8b7578 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.doAnswer; 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 @@ public final class WidgetsListSearchHeaderViewHolderBinderTest { 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 @@ public final class WidgetsListSearchHeaderViewHolderBinderTest { 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 9f66fb7bb2..cb38c6ffe7 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.ArgumentMatchers.any; 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.icons.IconCache; 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 @@ public final class WidgetsListTableViewHolderBinderTest { LayoutInflater.from(mTestActivity), mOnIconClickListener, mOnLongClickListener, - new CachingWidgetPreviewLoader(mWidgetPreviewLoader), new WidgetsListDrawableFactory(mTestActivity)); } @@ -128,13 +128,13 @@ public final class WidgetsListTableViewHolderBinderTest { 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 3d6be696bb..702b73afba 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -48,7 +48,6 @@ import com.android.launcher3.util.SafeCloseable; 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 @@ public class LauncherAppState { 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 @@ public class LauncherAppState { 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 @@ public class LauncherAppState { LauncherIcons.clearPool(); mIconCache.updateIconParams( mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize); - mWidgetCache.refresh(); mModel.forceReload(); } @@ -181,10 +177,6 @@ public class LauncherAppState { 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 92ed18af06..466b2689ae 100644 --- a/src/com/android/launcher3/dragndrop/AddItemActivity.java +++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java @@ -286,9 +286,7 @@ public class AddItemActivity extends BaseActivity @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 82b0f7c7d5..83fb3d1582 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -123,7 +123,6 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { 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 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { 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 6215827aa8..31436c4d6c 100644 --- a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java +++ b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java @@ -22,6 +22,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.List; /** * Creates and populates views with data @@ -46,7 +47,7 @@ public interface ViewHolderBinder { 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 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 afceadd9a3..0000000000 --- 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> 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 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 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 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 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 items) { - List previousCacheResults = new ArrayList<>(); - synchronized (mCache) { - for (WidgetItem item : items) { - Map 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 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 mCallbacks; - - Loading(@NonNull CancellationSignal cancellationSignal, - @NonNull Set callbacks) { - mCancellationSignal = cancellationSignal; - mCallbacks = callbacks; - } - - @NonNull - Loading withCallback(@NonNull WidgetPreviewLoadedCallback callback) { - if (mCallbacks.contains(callback)) return this; - Set 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 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 4ec7e60c86..95c3e1e1e8 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.PorterDuffXfermode; 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 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 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 @@ public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader { * * @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 list, - @Nullable PackageUserKey packageUser) { - Preconditions.assertWorkerThread(); - - LongSparseArray> validPackages = new LongSparseArray<>(); - - for (ComponentKey key : list) { - final long userId = mUserCache.getSerialNumberForUser(key.user); - HashSet packages = validPackages.get(userId); - if (packages == null) { - packages = new HashSet<>(); - validPackages.put(userId, packages); - } - packages.add(key.componentName.getPackageName()); - } - - LongSparseArray> 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 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 callback) { + Handler handler = Executors.UI_HELPER_EXECUTOR.getHandler(); + HandlerRunnable 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 */ - private Pair 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 @@ public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader { * 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 */ - public Pair 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 @@ public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader { 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); + } + + 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) { } - } catch (Resources.NotFoundException e) { - savePreviewImage = false; } - c.setBitmap(null); - } - return new Pair<>(preview, savePreviewImage); + }); } private RectF drawBoxWithShadow(Canvas c, int width, int height) { @@ -537,42 +246,29 @@ public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader { 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 @@ public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader { 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 - 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 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 991910dfdd..2347d28f21 100644 --- a/src/com/android/launcher3/widget/PendingItemDragHelper.java +++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java @@ -140,10 +140,9 @@ public class PendingItemDragHelper extends DragPreviewProvider { .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 bd444dbe47..423c66a63b 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -26,14 +26,12 @@ import static com.android.launcher3.Utilities.ATLEAST_S; 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.LinearLayout; 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.Launcher; 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 @@ import com.android.launcher3.widget.util.WidgetSizes; * 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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { 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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { super(context, attrs, defStyle); mActivity = BaseActivity.fromContext(context); + mWidgetPreviewLoader = new DatabaseWidgetPreviewLoader(mActivity); mLongPressHelper = new CheckLongPressHelper(this); mLongPressHelper.setLongPressTimeoutFactor(1); @@ -218,7 +219,36 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { 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 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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { } } - 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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { 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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { } else { mWidgetImageContainer.setAlpha(1f); } + if (mActiveRequest != null) { + mActiveRequest.cancel(); + mActiveRequest = null; + } } private void setContainerSize(int width, int height) { @@ -358,7 +370,13 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { 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 callback, + @Nullable Bitmap cachedPreview) { if (mAppWidgetHostViewPreview != null) { int containerWidth = (int) (mTargetPreviewWidth * mPreviewContainerScale); int containerHeight = (int) (mTargetPreviewHeight * mPreviewContainerScale); @@ -382,38 +400,18 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { 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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { 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 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { } 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 ff5c82f872..0000000000 --- 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 6beff3a30e..bb4638a417 100644 --- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java +++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java @@ -37,7 +37,6 @@ import android.widget.TableRow; 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 @@ public class WidgetsBottomSheet extends BaseWidgetSheet { 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 9dbfa87861..09f02997c2 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -687,7 +687,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet .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 @@ public class WidgetsFullSheet extends BaseWidgetSheet mWidgetsListAdapter = new WidgetsListAdapter( context, LayoutInflater.from(context), - apps.getWidgetCache(), apps.getIconCache(), this::getEmptySpaceHeight, /* iconClickListener= */ WidgetsFullSheet.this, @@ -784,7 +783,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet 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 1ad1f7a7ad..de0d8b821d 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.content.Context; 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.Adapter; 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 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC 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 mViewHolderBinders = new SparseArray<>(); - private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder; private final WidgetListBaseRowEntryComparator mRowComparator = new WidgetListBaseRowEntryComparator(); @@ -115,26 +103,21 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC @Nullable private Predicate 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 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC 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 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC 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 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC /** Updates the widget list based on {@code tempEntries}. */ public void setWidgets(List tempEntries) { - mCachingPreviewLoader.clearAll(); mAllEntries.clear(); mAllEntries.add(new WidgetListSpaceEntry()); tempEntries.stream().sorted(mRowComparator).forEach(mAllEntries::add); @@ -247,15 +204,10 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC public void setWidgetsOnSearch(List 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 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC } } - /** - * 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 class WidgetsListAdapter extends Adapter implements OnHeaderC 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 payloads) { ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos)); WidgetsListBaseEntry entry = mVisibleEntries.get(pos); @@ -376,7 +284,7 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC 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 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC // 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 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC 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 00750bd5e3..fadb637054 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.recyclerview.ViewHolderBinder; 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 @@ public final class WidgetsListHeaderViewHolderBinder implements @Override public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data, - @ListPosition int position) { + @ListPosition int position, List 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 1e2a3bfc4a..bff43c101b 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.util.PackageUserKey; 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 @@ public final class WidgetsListSearchHeaderViewHolderBinder implements @Override public void bindViewHolder(WidgetsListSearchHeaderHolder viewHolder, - WidgetsListSearchHeaderEntry data, @ListPosition int position) { + WidgetsListSearchHeaderEntry data, @ListPosition int position, List 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 804b0aef6a..feeb0fef6e 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java @@ -20,7 +20,7 @@ import static com.android.launcher3.widget.picker.WidgetsListDrawableState.MIDDL 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 android.widget.TableRow; 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 @@ public final class WidgetsListTableViewHolderBinder 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 @@ public final class WidgetsListTableViewHolderBinder 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 payloads) { + for (Object payload : payloads) { + Pair 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> widgetItemsTable = WidgetsTableUtils.groupWidgetItemsIntoTable( entry.mWidgets, entry.getMaxSpanSizeInCells()); recycleTableBeforeBinding(table, widgetItemsTable); + // Bind the widget items. for (int i = 0; i < widgetItemsTable.size(); i++) { List widgetItemsPerRow = widgetItemsTable.get(i); @@ -115,16 +106,14 @@ public final class WidgetsListTableViewHolderBinder 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 @@ public final class WidgetsListTableViewHolderBinder 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 @@ public final class WidgetsListTableViewHolderBinder @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 60dfebe505..c986007a3f 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java +++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java @@ -32,7 +32,6 @@ import androidx.annotation.Nullable; 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 @@ public final class WidgetsRecommendationTableLayout extends TableLayout { 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 618e2cbbd6..fe2d84bad4 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 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 f33c2fa08b..1aa5753791 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java +++ b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java @@ -27,6 +27,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; 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 @@ public class WidgetsSpaceViewHolderBinder } @Override - public void bindViewHolder(ViewHolder holder, WidgetListSpaceEntry data, int position) { + public void bindViewHolder(ViewHolder holder, WidgetListSpaceEntry data, + @ListPosition int position, List 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 631067b929..12e9e1e440 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 @@ public class WidgetsModel { } } - app.getWidgetCache().removeObsoletePreviews(widgetsAndShortcuts, packageUser); return updatedItems; }