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