diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index db5d7d45c7..b8fdfe7876 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -1315,7 +1315,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, applyFromApplicationInfo((AppInfo) info); } else if (info instanceof WorkspaceItemInfo) { applyFromWorkspaceItem((WorkspaceItemInfo) info); - mActivity.invalidateParent(info); } else if (info != null) { applyFromItemInfoWithIcon(info); } @@ -1329,12 +1328,11 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, * Verifies that the current icon is high-res otherwise posts a request to load the icon. */ public void verifyHighRes() { - if (mIconLoadRequest != null) { - mIconLoadRequest.cancel(); - mIconLoadRequest = null; - } if (getTag() instanceof ItemInfoWithIcon info && !mHighResUpdateInProgress && info.getMatchingLookupFlag().useLowRes()) { + if (mIconLoadRequest != null) { + mIconLoadRequest.cancel(); + } mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache() .updateIconInBackground(BubbleTextView.this, info); } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index b38efc26b4..b9e4710856 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -72,7 +72,6 @@ import static com.android.launcher3.LauncherState.SPRING_LOADED; import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE; import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE; -import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer; import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE; import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.SHOW; import static com.android.launcher3.logging.StatsLogManager.EventEnum; @@ -206,7 +205,6 @@ import com.android.launcher3.model.ItemInstallQueue; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.model.StringCache; import com.android.launcher3.model.data.AppInfo; -import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.CollectionInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; @@ -831,26 +829,6 @@ public class Launcher extends StatefulActivity return true; } - @Override - public void invalidateParent(ItemInfo info) { - if (info.container >= 0) { - View collectionIcon = getWorkspace().getHomescreenIconByItemId(info.container); - if (collectionIcon instanceof FolderIcon folderIcon - && collectionIcon.getTag() instanceof FolderInfo) { - if (createFolderGridOrganizer(getDeviceProfile()) - .setFolderInfo((FolderInfo) folderIcon.getTag()) - .isItemInPreview(info.rank)) { - folderIcon.invalidate(); - } - } else if (collectionIcon instanceof AppPairIcon appPairIcon - && collectionIcon.getTag() instanceof AppPairInfo appPairInfo) { - if (appPairInfo.getContents().contains(info)) { - appPairIcon.getIconDrawableArea().redraw(); - } - } - } - } - /** * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have * a configuration step, this allows the proper animations to run after other transitions. diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index 8d751e652c..22f1164667 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -24,7 +24,6 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Path; -import android.graphics.drawable.Drawable; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; @@ -541,19 +540,10 @@ public class FolderPagedView extends PagedView implements Cli ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets(); for (int i = parent.getChildCount() - 1; i >= 0; i--) { View iconView = parent.getChildAt(i); - Drawable d = null; if (iconView instanceof BubbleTextView btv) { btv.verifyHighRes(); - d = btv.getIcon(); } else if (iconView instanceof AppPairIcon api) { api.verifyHighRes(); - d = api.getIconDrawableArea().getDrawable(); - } - - // Set the callback back to the actual icon, in case - // it was captured by the FolderIcon - if (d != null) { - d.setCallback(iconView); } } } diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java index 2276ac7d85..5ee6a252bd 100644 --- a/src/com/android/launcher3/folder/PreviewItemManager.java +++ b/src/com/android/launcher3/folder/PreviewItemManager.java @@ -17,6 +17,7 @@ package com.android.launcher3.folder; import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER; +import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; @@ -43,12 +44,14 @@ import androidx.annotation.VisibleForTesting; import com.android.launcher3.BubbleTextView; import com.android.launcher3.Flags; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; import com.android.launcher3.apppairs.AppPairIcon; import com.android.launcher3.apppairs.AppPairIconDrawingParams; import com.android.launcher3.apppairs.AppPairIconGraphic; import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ActivityContext; @@ -460,6 +463,18 @@ public class PreviewItemManager { // Set the callback to FolderIcon as it is responsible to drawing the icon. The // callback will be released when the folder is opened. p.drawable.setCallback(mIcon); + + // Verify high res + if (item instanceof ItemInfoWithIcon info + && info.getMatchingLookupFlag().isVisuallyLessThan(DESKTOP_ICON_FLAG)) { + LauncherAppState.getInstance(mContext).getIconCache().updateIconInBackground( + newInfo -> { + if (p.item == newInfo) { + setDrawable(p, newInfo); + mIcon.invalidate(); + } + }, info); + } } /** diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java index b8481c5fb8..b164b7ffad 100644 --- a/src/com/android/launcher3/views/ActivityContext.java +++ b/src/com/android/launcher3/views/ActivityContext.java @@ -105,13 +105,6 @@ public interface ActivityContext { return null; } - /** - * For items with tree hierarchy, notifies the activity to invalidate the parent when a root - * is invalidated - * @param info info associated with a root node. - */ - default void invalidateParent(ItemInfo info) { } - default AccessibilityDelegate getAccessibilityDelegate() { return null; } diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt index 7f481b775b..548cf5bf54 100644 --- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt @@ -17,21 +17,22 @@ package com.android.launcher3.folder import android.R -import android.content.Context import android.graphics.Bitmap import android.os.Process -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherPrefs.Companion.THEMED_ICONS import com.android.launcher3.LauncherPrefs.Companion.get import com.android.launcher3.graphics.PreloadIconDrawable +import com.android.launcher3.icons.BitmapInfo import com.android.launcher3.icons.FastBitmapDrawable +import com.android.launcher3.icons.IconCache +import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver +import com.android.launcher3.icons.PlaceHolderIconDrawable import com.android.launcher3.icons.UserBadgeDrawable import com.android.launcher3.icons.mono.MonoThemedBitmap import com.android.launcher3.model.data.FolderInfo -import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE import com.android.launcher3.model.data.WorkspaceItemInfo @@ -40,6 +41,7 @@ import com.android.launcher3.util.Executors import com.android.launcher3.util.FlagOp import com.android.launcher3.util.LauncherLayoutBuilder import com.android.launcher3.util.LauncherModelHelper +import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext import com.android.launcher3.util.TestUtil import com.android.launcher3.util.UserIconInfo import com.google.common.truth.Truth.assertThat @@ -47,6 +49,13 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever /** Tests for [PreviewItemManager] */ @SmallTest @@ -54,22 +63,27 @@ import org.junit.runner.RunWith class PreviewItemManagerTest { private lateinit var previewItemManager: PreviewItemManager - private lateinit var context: Context - private lateinit var folderItems: ArrayList + private lateinit var context: SandboxModelContext + private lateinit var folderItems: ArrayList private lateinit var modelHelper: LauncherModelHelper private lateinit var folderIcon: FolderIcon + private lateinit var iconCache: IconCache private var defaultThemedIcons = false @Before fun setup() { - getInstrumentation().runOnMainSync { - folderIcon = - FolderIcon(ActivityContextWrapper(ApplicationProvider.getApplicationContext())) - } - context = getInstrumentation().targetContext - previewItemManager = PreviewItemManager(folderIcon) modelHelper = LauncherModelHelper() + context = modelHelper.sandboxContext + folderIcon = FolderIcon(ActivityContextWrapper(context)) + + val app = spy(LauncherAppState.getInstance(context)) + iconCache = spy(app.iconCache) + doReturn(iconCache).whenever(app).iconCache + context.putObject(LauncherAppState.INSTANCE, app) + doReturn(null).whenever(iconCache).updateIconInBackground(any(), any()) + + previewItemManager = PreviewItemManager(folderIcon) modelHelper .setupDefaultLayoutProvider( LauncherLayoutBuilder() @@ -82,33 +96,35 @@ class PreviewItemManagerTest { .build() ) .loadModelSync() - folderItems = modelHelper.bgDataModel.collections.valueAt(0).getContents() + + // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps + folderItems = modelHelper.bgDataModel.collections.valueAt(0).getAppContents() folderIcon.mInfo = modelHelper.bgDataModel.collections.valueAt(0) as FolderInfo folderIcon.mInfo.getContents().addAll(folderItems) - // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps - val folderApps = modelHelper.bgDataModel.collections.valueAt(0).getAppContents() // Set first icon to be themed. - folderApps[0].bitmap.themedBitmap = + folderItems[0].bitmap.themedBitmap = MonoThemedBitmap( - folderApps[0].bitmap.icon, + folderItems[0].bitmap.icon, Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), ) // Set second icon to be non-themed. - folderApps[1].bitmap.themedBitmap = null + folderItems[1].bitmap.themedBitmap = null // Set third icon to be themed with badge. - folderApps[2].bitmap.themedBitmap = + folderItems[2].bitmap.themedBitmap = MonoThemedBitmap( - folderApps[2].bitmap.icon, + folderItems[2].bitmap.icon, Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), ) - folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) + folderItems[2].bitmap = + folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) // Set fourth icon to be non-themed with badge. - folderApps[3].bitmap = folderApps[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) - folderApps[3].bitmap.themedBitmap = null + folderItems[3].bitmap = + folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) + folderItems[3].bitmap.themedBitmap = null defaultThemedIcons = get(context).get(THEMED_ICONS) } @@ -232,6 +248,31 @@ class PreviewItemManagerTest { assertThat(drawingParams.drawable).isInstanceOf(PreloadIconDrawable::class.java) } + @Test + fun `Preview item loads and apply high res icon`() { + val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) + val originalBitmap = folderItems[3].bitmap + folderItems[3].bitmap = BitmapInfo.LOW_RES_INFO + + previewItemManager.setDrawable(drawingParams, folderItems[3]) + assertThat(drawingParams.drawable).isInstanceOf(PlaceHolderIconDrawable::class.java) + + val callbackCaptor = argumentCaptor() + verify(iconCache).updateIconInBackground(callbackCaptor.capture(), eq(folderItems[3])) + + // Restore high-res icon + folderItems[3].bitmap = originalBitmap + + // Calling with a different item info will ignore the update + callbackCaptor.firstValue.reapplyItemInfo(folderItems[2]) + assertThat(drawingParams.drawable).isInstanceOf(PlaceHolderIconDrawable::class.java) + + // Calling with correct value will update the drawable to high-res + callbackCaptor.firstValue.reapplyItemInfo(folderItems[3]) + assertThat(drawingParams.drawable).isNotInstanceOf(PlaceHolderIconDrawable::class.java) + assertThat(drawingParams.drawable).isInstanceOf(FastBitmapDrawable::class.java) + } + private fun profileFlagOp(type: Int) = UserIconInfo(Process.myUserHandle(), type).applyBitmapInfoFlags(FlagOp.NO_OP) }