From d462190ef26551df8a6f65227f0db39035cd4813 Mon Sep 17 00:00:00 2001 From: Andrew Cole Date: Fri, 16 Feb 2024 14:34:45 -0800 Subject: [PATCH] Folder Previews Show Grey Pending Icon When downloading an app previously the pending icon would show the full color icon when it was in a folder preview. To fix, we consolidated the logic for pending icons in PreloadIconDrawable and moved the logic of the pending state to the ItemInfoWithIcon model to represent the state of a pending application inside the model instead of in views. Bug: b/324629535 Test: ItemInfoWithIconTest.kt Flag: None Change-Id: I4ffab44ddead046adcc84911039c87ea7c304e63 --- src/com/android/launcher3/BubbleTextView.java | 57 ++++++++-------- .../launcher3/folder/PreviewItemManager.java | 18 +++-- .../graphics/PreloadIconDrawable.java | 2 + .../model/data/ItemInfoWithIcon.java | 22 ++++-- .../model/data/ItemInfoWithIconTest.kt | 68 +++++++++++++++++++ 5 files changed, 125 insertions(+), 42 deletions(-) create mode 100644 tests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 2f0c0968e9..ed16752b48 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -419,7 +419,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } /** - * Only if actual text can be displayed in two line, the {@code true} value will be effective. + * Only if actual text can be displayed in two line, the {@code true} value will be effective. */ protected boolean shouldUseTwoLine() { return (FeatureFlags.enableTwolineAllapps() && isCurrentLanguageEnglish()) @@ -575,12 +575,12 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, /** * Find the appropriate text spacing to display the provided text - * @param paint the paint used by the text view - * @param text the text to display - * @param allowedWidthPx available space to render the text - * @param minSpacingEm minimum spacing allowed between characters - * @return the final textSpacing value * + * @param paint the paint used by the text view + * @param text the text to display + * @param allowedWidthPx available space to render the text + * @param minSpacingEm minimum spacing allowed between characters + * @return the final textSpacing value * @see #setLetterSpacing(float) */ private float findBestSpacingValue(TextPaint paint, String text, float allowedWidthPx, @@ -808,13 +808,13 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, * iterating through the list of break points and determining if the strings between the break * points can fit within the line it is in. We will show the modified string if there is enough * horizontal and vertical space, otherwise this method will just return the original string. - * Example assuming each character takes up one spot: - * title = "Battery Stats", breakpoint = [6], stringPtr = 0, limitedWidth = 7 - * We get the current word -> from sublist(0, breakpoint[i]+1) so sublist (0,7) -> Battery, - * now stringPtr = 7 then from sublist(7) the current string is " Stats" and the runningWidth - * at this point exceeds limitedWidth and so we put " Stats" onto the next line (after checking - * if the first char is a SPACE, we trim to append "Stats". So resulting string would be - * "Battery\nStats" + * Example assuming each character takes up one spot: + * title = "Battery Stats", breakpoint = [6], stringPtr = 0, limitedWidth = 7 + * We get the current word -> from sublist(0, breakpoint[i]+1) so sublist (0,7) -> Battery, + * now stringPtr = 7 then from sublist(7) the current string is " Stats" and the runningWidth + * at this point exceeds limitedWidth and so we put " Stats" onto the next line (after checking + * if the first char is a SPACE, we trim to append "Stats". So resulting string would be + * "Battery\nStats" */ public static CharSequence modifyTitleToSupportMultiLine(int limitedWidth, int limitedHeight, CharSequence title, TextPaint paint, IntArray breakPoints, float spacingMultiplier, @@ -828,25 +828,25 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, StringBuilder newString = new StringBuilder(); paint.setLetterSpacing(MIN_LETTER_SPACING); int stringPtr = 0; - for (int i = 0; i < breakPoints.size()+1; i++) { + for (int i = 0; i < breakPoints.size() + 1; i++) { if (i < breakPoints.size()) { - currentWord = title.subSequence(stringPtr, breakPoints.get(i)+1); + currentWord = title.subSequence(stringPtr, breakPoints.get(i) + 1); } else { // last word from recent breakpoint until the end of the string currentWord = title.subSequence(stringPtr, title.length()); } - currentWordWidth = paint.measureText(currentWord,0, currentWord.length()); + currentWordWidth = paint.measureText(currentWord, 0, currentWord.length()); runningWidth += currentWordWidth; if (runningWidth <= limitedWidth) { newString.append(currentWord); } else { - if (i != 0) { + if (i != 0) { // If putting word onto a new line, make sure there is no space or new line // character in the beginning of the current word and just put in the rest of // the characters. CharSequence lastCharacters = title.subSequence(stringPtr, title.length()); int beginningLetterType = - Character.getType(Character.codePointAt(lastCharacters,0)); + Character.getType(Character.codePointAt(lastCharacters, 0)); if (beginningLetterType == Character.SPACE_SEPARATOR || beginningLetterType == Character.LINE_SEPARATOR) { lastCharacters = lastCharacters.length() > 1 @@ -867,7 +867,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, // no need to look forward into the string if we've already finished processing break; } - stringPtr = breakPoints.get(i)+1; + stringPtr = breakPoints.get(i) + 1; } return newString.toString(); } @@ -957,12 +957,12 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, return preloadDrawable; } - private boolean isIconDisabled(ItemInfoWithIcon info) { - if (info.isArchived()) { - return info.getProgressLevel() == 0 - && (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0; - } - return info.getProgressLevel() == 0; + /** + * Returns true to grey the icon if the icon is either suspended or if the icon is pending + * download + */ + public boolean isIconDisabled(ItemInfoWithIcon info) { + return info.isDisabled() || info.isPendingDownload(); } public void applyDotState(ItemInfo itemInfo, boolean animate) { @@ -1009,12 +1009,12 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, if ((info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0) { setContentDescription(getContext() .getString( - R.string.app_installing_title, info.title, percentageString)); + R.string.app_installing_title, info.title, percentageString)); } else if ((info.runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0) { setContentDescription(getContext() .getString( - R.string.app_downloading_title, info.title, percentageString)); + R.string.app_downloading_title, info.title, percentageString)); } } } @@ -1181,7 +1181,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, public SafeCloseable prepareDrawDragView() { resetIconScale(); setForceHideDot(true); - return () -> { }; + return () -> { + }; } private void resetIconScale() { diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java index 871643e816..9001a0c271 100644 --- a/src/com/android/launcher3/folder/PreviewItemManager.java +++ b/src/com/android/launcher3/folder/PreviewItemManager.java @@ -119,7 +119,7 @@ public class PreviewItemManager { final Runnable onCompleteRunnable) { return reverse ? new FolderPreviewItemAnim(this, mFirstPageParams.get(0), 0, 2, -1, -1, - FINAL_ITEM_ANIMATION_DURATION, onCompleteRunnable) + FINAL_ITEM_ANIMATION_DURATION, onCompleteRunnable) : new FolderPreviewItemAnim(this, mFirstPageParams.get(0), -1, -1, 0, 2, INITIAL_ITEM_ANIMATION_DURATION, onCompleteRunnable); } @@ -219,9 +219,9 @@ public class PreviewItemManager { /** * Draws each preview item. * - * @param offset The offset needed to draw the preview items. + * @param offset The offset needed to draw the preview items. * @param shouldClipPath Iff true, clip path using {@param clipPath}. - * @param clipPath The clip path of the folder icon. + * @param clipPath The clip path of the folder icon. */ private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params, PointF offset, boolean shouldClipPath, Path clipPath) { @@ -362,13 +362,13 @@ public class PreviewItemManager { /** * Handles the case where items in the preview are either: - * - Moving into the preview - * - Moving into a new position - * - Moving out of the preview + * - Moving into the preview + * - Moving into a new position + * - Moving out of the preview * * @param oldItems The list of items in the old preview. * @param newItems The list of items in the new preview. - * @param dropped The item that was dropped onto the FolderIcon. + * @param dropped The item that was dropped onto the FolderIcon. */ public void onDrop(List oldItems, List newItems, WorkspaceItemInfo dropped) { @@ -433,9 +433,8 @@ public class PreviewItemManager { @VisibleForTesting public void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) { if (item.hasPromiseIconUi() || (item.runtimeStatusFlags - & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) { + & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) { PreloadIconDrawable drawable = newPendingIcon(mContext, item); - drawable.setLevel(item.getProgressLevel()); p.drawable = drawable; } else { p.drawable = item.newIcon(mContext, @@ -443,7 +442,6 @@ public class PreviewItemManager { } p.drawable.setBounds(0, 0, mIconSize, mIconSize); p.item = item; - // 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); diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java index 3e77c78f98..9fffcc1bd2 100644 --- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java +++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java @@ -173,6 +173,8 @@ public class PreloadIconDrawable extends FastBitmapDrawable { mIconScaleMultiplier.updateValue(info.getProgressLevel() == 0 ? 0 : 1); setLevel(info.getProgressLevel()); + // Set a disabled icon color if the app is suspended or if the app is pending download + setIsDisabled(info.isDisabled() || info.isPendingDownload()); } @Override diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java index e46c502a51..352c3633ac 100644 --- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java @@ -139,7 +139,8 @@ public abstract class ItemInfoWithIcon extends ItemInfo { */ private int mProgressLevel = 100; - protected ItemInfoWithIcon() { } + protected ItemInfoWithIcon() { + } protected ItemInfoWithIcon(ItemInfoWithIcon info) { super(info); @@ -155,7 +156,20 @@ public abstract class ItemInfoWithIcon extends ItemInfo { } /** - * Returns true if the app corresponding to the item is archived. */ + * @return {@code true} if the app is pending download (0 progress) or if the app is archived + * and its install session is active + */ + public boolean isPendingDownload() { + if (isArchived()) { + return this.getProgressLevel() == 0 + && (this.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0; + } + return getProgressLevel() == 0; + } + + /** + * Returns true if the app corresponding to the item is archived. + */ public boolean isArchived() { if (!Utilities.enableSupportForArchiving()) { return false; @@ -179,7 +193,7 @@ public abstract class ItemInfoWithIcon extends ItemInfo { public boolean isAppStartable() { return ((runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) == 0) && (((runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0) - || mProgressLevel == 100 || isArchived()); + || mProgressLevel == 100 || isArchived()); } /** @@ -242,7 +256,7 @@ public abstract class ItemInfoWithIcon extends ItemInfo { return targetPackage != null ? ApiWrapper.getAppMarketActivityIntent( - context, targetPackage, Process.myUserHandle()) + context, targetPackage, Process.myUserHandle()) : null; } diff --git a/tests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt b/tests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt new file mode 100644 index 0000000000..cfbde98eee --- /dev/null +++ b/tests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 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.model.data + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.pm.PackageInstallInfo +import com.android.launcher3.util.LauncherModelHelper +import com.google.common.truth.Truth +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ItemInfoWithIconTest { + + private var context = LauncherModelHelper.SandboxModelContext() + private lateinit var itemInfoWithIcon: ItemInfoWithIcon + + @Before + fun setup() { + itemInfoWithIcon = + object : ItemInfoWithIcon() { + override fun clone(): ItemInfoWithIcon? { + return null + } + } + } + + @After + fun tearDown() { + context.destroy() + } + + @Test + fun itemInfoWithIconDefaultParamsTest() { + Truth.assertThat(itemInfoWithIcon.isDisabled).isFalse() + Truth.assertThat(itemInfoWithIcon.isPendingDownload).isFalse() + Truth.assertThat(itemInfoWithIcon.isArchived).isFalse() + } + + @Test + fun isDisabledOrPendingTest() { + itemInfoWithIcon.setProgressLevel(0, PackageInstallInfo.STATUS_INSTALLING) + Truth.assertThat(itemInfoWithIcon.isDisabled).isFalse() + Truth.assertThat(itemInfoWithIcon.isPendingDownload).isTrue() + + itemInfoWithIcon.setProgressLevel(1, PackageInstallInfo.STATUS_INSTALLING) + Truth.assertThat(itemInfoWithIcon.isDisabled).isFalse() + Truth.assertThat(itemInfoWithIcon.isPendingDownload).isFalse() + } +}