Revert^2 "Unifying various model update callbacks into one"
72f9943f642dd9f57ff9d25deb12dc84cf3e7bc9
Change-Id: I38901714947a2b7926723ea25df4a2b8216303e4
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index f905c5f..6815f97 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -25,7 +25,6 @@
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -39,9 +38,9 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Predicate;
/**
@@ -114,15 +113,9 @@
return modified;
}
-
@Override
- public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
- updateWorkspaceItems(updated, mContext);
- }
-
- @Override
- public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
- updateRestoreItems(updates, mContext);
+ public void bindItemsUpdated(Set<ItemInfo> updates) {
+ updateContainerItems(updates, mContext);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index a85e5e0..9b7bb1c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -214,7 +214,7 @@
boolean animate = shouldAnimateIconChange(info);
Drawable oldIcon = getIcon();
int oldPlateColor = mPlateColor.currentColor;
- applyFromWorkspaceItem(info, null);
+ applyFromWorkspaceItem(info);
setContentDescription(
mIsPinned ? info.contentDescription :
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 315096c..d3684b2 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -29,6 +29,7 @@
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -63,6 +64,7 @@
import android.widget.TextView;
import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
@@ -366,11 +368,6 @@
mDotScaleAnim.start();
}
- @UiThread
- public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
- applyFromWorkspaceItem(info, null);
- }
-
@Override
public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
if (delegate instanceof BaseAccessibilityDelegate) {
@@ -384,10 +381,10 @@
}
@UiThread
- public void applyFromWorkspaceItem(WorkspaceItemInfo info, PreloadIconDrawable icon) {
+ public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
applyIconAndLabel(info);
setItemInfo(info);
- applyLoadingState(icon);
+
applyDotState(info, false /* animate */);
setDownloadStateContentDescription(info, info.getProgressLevel());
}
@@ -395,17 +392,11 @@
@UiThread
public void applyFromApplicationInfo(AppInfo info) {
applyIconAndLabel(info);
-
- // We don't need to check the info since it's not a WorkspaceItemInfo
setItemInfo(info);
-
// Verify high res immediately
verifyHighRes();
- if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
- applyProgressLevel();
- }
applyDotState(info, false /* animate */);
setDownloadStateContentDescription(info, info.getProgressLevel());
}
@@ -449,6 +440,50 @@
@VisibleForTesting
@UiThread
public void applyIconAndLabel(ItemInfoWithIcon info) {
+ FastBitmapDrawable oldIcon = mIcon;
+ if (!canReuseIcon(info)) {
+ setNonPendingIcon(info);
+ }
+ applyLabel(info);
+ maybeApplyProgressLevel(info, oldIcon);
+ }
+
+ /**
+ * Check if we can reuse icon so that any animation is preserved
+ */
+ private boolean canReuseIcon(ItemInfoWithIcon info) {
+ return mIcon instanceof PreloadIconDrawable p
+ && p.hasNotCompleted() && p.isSameInfo(info.bitmap);
+ }
+
+ /**
+ * Apply progress level to the icon if necessary
+ */
+ private void maybeApplyProgressLevel(ItemInfoWithIcon info, FastBitmapDrawable oldIcon) {
+ if (!shouldApplyProgressLevel(info, oldIcon)) {
+ return;
+ }
+ PreloadIconDrawable pendingIcon = applyProgressLevel(info);
+ boolean isNoLongerPending = info instanceof WorkspaceItemInfo wii
+ ? !wii.hasPromiseIconUi() : !info.isArchived();
+ if (isNoLongerPending && info.getProgressLevel() == 100 && pendingIcon != null) {
+ pendingIcon.maybePerformFinishedAnimation(
+ (oldIcon instanceof PreloadIconDrawable p) ? p : pendingIcon,
+ () -> setNonPendingIcon(
+ (getTag() instanceof ItemInfoWithIcon iiwi) ? iiwi : info));
+ }
+ }
+
+ /**
+ * Check if progress level should be applied to the icon
+ */
+ private boolean shouldApplyProgressLevel(ItemInfoWithIcon info, FastBitmapDrawable oldIcon) {
+ return (info.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0
+ || (info instanceof WorkspaceItemInfo wii && wii.hasPromiseIconUi())
+ || (oldIcon instanceof PreloadIconDrawable p && p.hasNotCompleted());
+ }
+
+ private void setNonPendingIcon(ItemInfoWithIcon info) {
ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext());
int flags = (shouldUseTheme()
&& themeManager.isMonoThemeEnabled()) ? FLAG_THEMED : 0;
@@ -463,7 +498,6 @@
mDotParams.appColor = iconDrawable.getIconColor();
mDotParams.dotColor = Themes.getAttrColor(getContext(), R.attr.notificationDotColor);
setIcon(iconDrawable);
- applyLabel(info);
}
protected boolean shouldUseTheme() {
@@ -1070,38 +1104,10 @@
mLongPressHelper.cancelLongPress();
}
- /**
- * Applies the loading progress value to the progress bar.
- *
- * If this app is installing, the progress bar will be updated with the installation progress.
- * If this app is installed and downloading incrementally, the progress bar will be updated
- * with the total download progress.
- */
- public void applyLoadingState(PreloadIconDrawable icon) {
- if (getTag() instanceof ItemInfoWithIcon) {
- WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
- if ((info.runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0
- || info.hasPromiseIconUi()
- || (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0
- || (icon != null)) {
- updateProgressBarUi(info.getProgressLevel() == 100 ? icon : null);
- }
- }
- }
-
- private void updateProgressBarUi(PreloadIconDrawable oldIcon) {
- FastBitmapDrawable originalIcon = mIcon;
- PreloadIconDrawable preloadDrawable = applyProgressLevel();
- if (preloadDrawable != null && oldIcon != null) {
- preloadDrawable.maybePerformFinishedAnimation(oldIcon, () -> setIcon(originalIcon));
- }
- }
-
/** Applies the given progress level to the this icon's progress bar. */
@Nullable
- public PreloadIconDrawable applyProgressLevel() {
- if (!(getTag() instanceof ItemInfoWithIcon info)
- || ((ItemInfoWithIcon) getTag()).isInactiveArchive()) {
+ private PreloadIconDrawable applyProgressLevel(ItemInfoWithIcon info) {
+ if (info.isInactiveArchive()) {
return null;
}
@@ -1115,23 +1121,16 @@
setContentDescription(getContext()
.getString(R.string.app_waiting_download_title, info.title));
}
- if (mIcon != null) {
- PreloadIconDrawable preloadIconDrawable;
- if (mIcon instanceof PreloadIconDrawable) {
- preloadIconDrawable = (PreloadIconDrawable) mIcon;
- preloadIconDrawable.setLevel(progressLevel);
- preloadIconDrawable.setIsDisabled(isIconDisabled(info));
- } else {
- preloadIconDrawable = makePreloadIcon();
- setIcon(preloadIconDrawable);
- if (info.isArchived() && Flags.useNewIconForArchivedApps()) {
- // reapply text without cloud icon as soon as unarchiving is triggered
- applyLabel(info);
- }
- }
- return preloadIconDrawable;
+ PreloadIconDrawable pid;
+ if (mIcon instanceof PreloadIconDrawable p) {
+ pid = p;
+ pid.setLevel(progressLevel);
+ pid.setIsDisabled(isIconDisabled(info));
+ } else {
+ pid = makePreloadIcon(info);
+ setIcon(pid);
}
- return null;
+ return pid;
}
/**
@@ -1140,11 +1139,11 @@
*/
@Nullable
public PreloadIconDrawable makePreloadIcon() {
- if (!(getTag() instanceof ItemInfoWithIcon)) {
- return null;
- }
+ return getTag() instanceof ItemInfoWithIcon info ? makePreloadIcon(info) : null;
+ }
- ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
+ @NonNull
+ private PreloadIconDrawable makePreloadIcon(ItemInfoWithIcon info) {
int progressLevel = info.getProgressLevel();
final PreloadIconDrawable preloadDrawable = newPendingIcon(getContext(), info);
@@ -1163,7 +1162,7 @@
public void applyDotState(ItemInfo itemInfo, boolean animate) {
- if (mIcon instanceof FastBitmapDrawable) {
+ if (mIcon != null) {
boolean wasDotted = mDotInfo != null;
mDotInfo = mActivity.getDotInfoForItem(itemInfo);
boolean isDotted = mDotInfo != null;
@@ -1212,7 +1211,7 @@
setContentDescription(getContext().getString(
R.string.app_archived_title, info.title));
}
- } else if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
+ } else if ((info.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
!= 0) {
String percentageString = NumberFormat.getPercentInstance()
.format(progressLevel * 0.01);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7df4014..647d2ad 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -277,11 +277,11 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -2598,25 +2598,12 @@
mModelCallbacks.bindIncrementalDownloadProgressUpdated(app);
}
- @Override
- public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) {
- mModelCallbacks.bindWidgetsRestored(widgets);
- }
-
/**
* See {@code LauncherBindingDelegate}
*/
@Override
- public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
- mModelCallbacks.bindWorkspaceItemsChanged(updated);
- }
-
- /**
- * See {@code LauncherBindingDelegate}
- */
- @Override
- public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
- mModelCallbacks.bindRestoreItemsChange(updates);
+ public void bindItemsUpdated(Set<ItemInfo> updates) {
+ mModelCallbacks.bindItemsUpdated(updates);
}
/**
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 5d32525..5338fb4 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -17,8 +17,6 @@
import com.android.launcher3.model.StringCache
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.LauncherAppWidgetInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.popup.PopupContainerWithArrow
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.IntArray as LIntArray
@@ -215,29 +213,13 @@
launcher.appsView.appsStore.updateProgressBar(app)
}
- override fun bindWidgetsRestored(widgets: ArrayList<LauncherAppWidgetInfo?>?) {
- launcher.workspace.widgetsRestored(widgets)
- }
-
- /**
- * Some shortcuts were updated in the background. Implementation of the method from
- * LauncherModel.Callbacks.
- *
- * @param updated list of shortcuts which have changed.
- */
- override fun bindWorkspaceItemsChanged(updated: List<WorkspaceItemInfo?>) {
- if (updated.isNotEmpty()) {
- launcher.workspace.updateWorkspaceItems(updated, launcher)
- PopupContainerWithArrow.dismissInvalidPopup(launcher)
- }
- }
-
/**
* Update the state of a package, typically related to install state. Implementation of the
* method from LauncherModel.Callbacks.
*/
- override fun bindRestoreItemsChange(updates: HashSet<ItemInfo?>?) {
- launcher.workspace.updateRestoreItems(updates, launcher)
+ override fun bindItemsUpdated(updates: Set<ItemInfo>) {
+ launcher.workspace.updateContainerItems(updates, launcher)
+ PopupContainerWithArrow.dismissInvalidPopup(launcher)
}
/**
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 86c49d0..7d82179 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -53,8 +53,6 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
@@ -125,13 +123,9 @@
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.WallpaperOffsetInterpolator;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.launcher3.widget.LauncherWidgetHolder;
-import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
import com.android.launcher3.widget.NavigableAppWidgetHostView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
-import com.android.launcher3.widget.WidgetManagerHelper;
import com.android.launcher3.widget.util.WidgetSizes;
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks;
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayTouchProxy;
@@ -664,9 +658,6 @@
bindAndInitFirstWorkspaceScreen();
}
- // Remove any deferred refresh callbacks
- mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
-
// Re-enable the layout transitions
enableLayoutTransitions();
}
@@ -3465,43 +3456,6 @@
removeItemsByMatcher(matcher);
}
- public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
- if (!changedInfo.isEmpty()) {
- DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
- mLauncher.getAppWidgetHolder());
-
- LauncherAppWidgetInfo item = changedInfo.get(0);
- final AppWidgetProviderInfo widgetInfo;
- WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext());
- if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
- widgetInfo = widgetHelper.findProvider(item.providerName, item.user);
- } else {
- widgetInfo = widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId,
- item.getTargetComponent());
- }
-
- if (widgetInfo != null) {
- // Re-inflate the widgets which have changed status
- widgetRefresh.run();
- } else {
- // widgetRefresh will automatically run when the packages are updated.
- // For now just update the progress bars
- mapOverItems(new ItemOperator() {
- @Override
- public boolean evaluate(ItemInfo info, View view) {
- if (view instanceof PendingAppWidgetHostView
- && changedInfo.contains(info)) {
- ((LauncherAppWidgetInfo) info).installProgress = 100;
- ((PendingAppWidgetHostView) view).applyState();
- }
- // process all the shortcuts
- return false;
- }
- });
- }
- }
- }
-
public boolean isOverlayShown() {
return mOverlayShown;
}
@@ -3608,62 +3562,6 @@
return mLauncher.getCellPosMapper();
}
- /**
- * Used as a workaround to ensure that the AppWidgetService receives the
- * PACKAGE_ADDED broadcast before updating widgets.
- */
- private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener {
- private final ArrayList<LauncherAppWidgetInfo> mInfos;
- private final LauncherWidgetHolder mWidgetHolder;
- private final Handler mHandler;
-
- private boolean mRefreshPending;
-
- DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
- LauncherWidgetHolder holder) {
- mInfos = infos;
- mWidgetHolder = holder;
- mHandler = mLauncher.mHandler;
- mRefreshPending = true;
-
- mWidgetHolder.addProviderChangeListener(this);
- // Force refresh after 10 seconds, if we don't get the provider changed event.
- // This could happen when the provider is no longer available in the app.
- Message msg = Message.obtain(mHandler, this);
- msg.obj = DeferredWidgetRefresh.class;
- mHandler.sendMessageDelayed(msg, 10000);
- }
-
- @Override
- public void run() {
- mWidgetHolder.removeProviderChangeListener(this);
- mHandler.removeCallbacks(this);
-
- if (!mRefreshPending) {
- return;
- }
-
- mRefreshPending = false;
-
- ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size());
- mapOverItems((info, view) -> {
- if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
- views.add((PendingAppWidgetHostView) view);
- }
- // process all children
- return false;
- });
- for (PendingAppWidgetHostView view : views) {
- view.reInflate();
- }
- }
-
- @Override
- public void notifyWidgetProvidersChanged() {
- run();
- }
- }
-
private class StateTransitionListener extends AnimatorListenerAdapter
implements AnimatorUpdateListener {
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 9afe06c..d5a4022 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
-import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
import android.content.Context;
import android.os.UserHandle;
@@ -229,11 +228,7 @@
public void updateProgressBar(AppInfo app) {
updateAllIcons((child) -> {
if (child.getTag() == app) {
- if ((app.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) == 0) {
- child.applyFromApplicationInfo(app);
- } else {
- child.applyProgressLevel();
- }
+ child.applyFromApplicationInfo(app);
}
});
}
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 3464e9b..f189182 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -24,7 +24,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
@@ -33,12 +32,14 @@
import android.graphics.Rect;
import android.util.Property;
+import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.Themes;
@@ -63,8 +64,6 @@
private static final int DEFAULT_PATH_SIZE = 100;
private static final int MAX_PAINT_ALPHA = 255;
- private static final int TRACK_ALPHA = (int) (0.27f * MAX_PAINT_ALPHA);
- private static final int DISABLED_ICON_ALPHA = (int) (0.6f * MAX_PAINT_ALPHA);
private static final long DURATION_SCALE = 500;
private static final long SCALE_AND_ALPHA_ANIM_DURATION = 500;
@@ -284,20 +283,25 @@
(long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
mCurrentAnim.setInterpolator(LINEAR);
if (isFinish) {
- if (onFinishCallback != null) {
- mCurrentAnim.addListener(AnimatorListeners.forEndCallback(onFinishCallback));
- }
mCurrentAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mRanFinishAnimation = true;
}
});
+ if (onFinishCallback != null) {
+ mCurrentAnim.addListener(AnimatorListeners.forEndCallback(onFinishCallback));
+ }
}
mCurrentAnim.start();
}
}
+ @VisibleForTesting
+ public ObjectAnimator getActiveAnimation() {
+ return mCurrentAnim;
+ }
+
/**
* Sets the internal progress and updates the UI accordingly
* for progress <= 0:
@@ -358,8 +362,7 @@
@Override
public FastBitmapConstantState newConstantState() {
return new PreloadIconConstantState(
- mBitmap,
- mIconColor,
+ mBitmapInfo,
mItem,
mIndicatorColor,
new int[] {mSystemAccentColor, mSystemBackgroundColor},
@@ -377,14 +380,13 @@
private final Path mShapePath;
public PreloadIconConstantState(
- Bitmap bitmap,
- int iconColor,
+ BitmapInfo bitmapInfo,
ItemInfoWithIcon info,
int indicatorColor,
int[] preloadColors,
boolean isDarkMode,
Path shapePath) {
- super(bitmap, iconColor);
+ super(bitmapInfo);
mInfo = info;
mIndicatorColor = indicatorColor;
mPreloadColors = preloadColors;
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index a04cbfb..ddc775d 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -49,7 +49,6 @@
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -70,7 +69,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -419,9 +417,9 @@
* Binds updated incremental download progress
*/
default void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
- default void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
- default void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
- default void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
+
+ /** Called when a runtime property of the ItemInfo is updated due to some system event */
+ default void bindItemsUpdated(Set<ItemInfo> updates) { }
default void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) { }
/**
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index b544b91..48934e2 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+
import android.content.ComponentName;
import android.os.UserHandle;
@@ -23,6 +26,8 @@
import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import java.util.ArrayList;
@@ -55,7 +60,7 @@
public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
@NonNull AllAppsList apps) {
IconCache iconCache = taskController.getApp().getIconCache();
- ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
+ ArrayList<ItemInfo> updatedItems = new ArrayList<>();
synchronized (dataModel) {
dataModel.forAllWorkspaceItemInfos(mUser, si -> {
@@ -64,12 +69,25 @@
&& isValidShortcut(si) && cn != null
&& mPackages.contains(cn.getPackageName())) {
iconCache.getTitleAndIcon(si, si.getMatchingLookupFlag());
- updatedShortcuts.add(si);
+ updatedItems.add(si);
}
});
+
+ dataModel.itemsIdMap.stream()
+ .filter(WIDGET_FILTER)
+ .filter(item -> mUser.equals(item.user))
+ .map(item -> (LauncherAppWidgetInfo) item)
+ .filter(widget -> mPackages.contains(widget.providerName.getPackageName())
+ && widget.pendingItemInfo != null)
+ .forEach(widget -> {
+ iconCache.getTitleAndIconForApp(
+ widget.pendingItemInfo, DEFAULT_LOOKUP_FLAG);
+ updatedItems.add(widget);
+ });
+
apps.updateIconsAndLabels(mPackages, mUser);
}
- taskController.bindUpdatedWorkspaceItems(updatedShortcuts);
+ taskController.bindUpdatedWorkspaceItems(updatedItems);
taskController.bindApplicationsIfNeeded();
}
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
index fc53343..40ea17d 100644
--- a/src/com/android/launcher3/model/ModelTaskController.kt
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -22,7 +22,6 @@
import com.android.launcher3.celllayout.CellPosMapper
import com.android.launcher3.model.BgDataModel.FixedContainerItems
import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.PackageUserKey
import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder
import java.util.Objects
@@ -51,18 +50,17 @@
*/
fun getModelWriter() = model.getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null)
- fun bindUpdatedWorkspaceItems(allUpdates: List<WorkspaceItemInfo>) {
+ fun bindUpdatedWorkspaceItems(allUpdates: Collection<ItemInfo>) {
// Bind workspace items
- val workspaceUpdates =
- allUpdates.stream().filter { info -> info.id != ItemInfo.NO_ID }.toList()
+ val workspaceUpdates = allUpdates.filter { it.id != ItemInfo.NO_ID }.toSet()
if (workspaceUpdates.isNotEmpty()) {
- scheduleCallbackTask { it.bindWorkspaceItemsChanged(workspaceUpdates) }
+ scheduleCallbackTask { it.bindItemsUpdated(workspaceUpdates) }
}
// Bind extra items if any
allUpdates
.stream()
- .mapToInt { info: WorkspaceItemInfo -> info.container }
+ .mapToInt { it.container }
.distinct()
.mapToObj { dataModel.extraItems.get(it) }
.filter { Objects.nonNull(it) }
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 4103937..a216042 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -99,8 +99,7 @@
});
if (!updates.isEmpty()) {
- taskController.scheduleCallbackTask(
- callbacks -> callbacks.bindRestoreItemsChange(updates));
+ taskController.bindUpdatedWorkspaceItems(updates);
}
}
}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 1153f48..6bef292 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -214,8 +214,7 @@
// Update shortcut infos
if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
- final ArrayList<WorkspaceItemInfo> updatedWorkspaceItems = new ArrayList<>();
- final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
+ final ArrayList<ItemInfo> updatedWorkspaceItems = new ArrayList<>();
// For system apps, package manager send OP_UPDATE when an app is enabled.
final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE;
@@ -364,8 +363,8 @@
// if the widget has a config activity. In case there is no config
// activity, it will be marked as 'restored' during bind.
widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-
- widgets.add(widgetInfo);
+ widgetInfo.installProgress = 100;
+ updatedWorkspaceItems.add(widgetInfo);
taskController.getModelWriter().updateItemInDatabase(widgetInfo);
});
}
@@ -377,10 +376,6 @@
"removing shortcuts with invalid target components."
+ " ids=" + removedShortcuts);
}
-
- if (!widgets.isEmpty()) {
- taskController.scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets));
- }
}
final HashSet<String> removedPackages = new HashSet<>();
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 78709b8..381d17a 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -228,10 +228,9 @@
private static void onClickPendingAppItem(View v, Launcher launcher, String packageName,
boolean downloadStarted) {
ItemInfo item = (ItemInfo) v.getTag();
- CompletableFuture<SessionInfo> siFuture;
- siFuture = CompletableFuture.supplyAsync(() ->
- InstallSessionHelper.INSTANCE.get(launcher)
- .getActiveSessionInfo(item.user, packageName),
+ CompletableFuture<SessionInfo> siFuture = CompletableFuture.supplyAsync(() ->
+ InstallSessionHelper.INSTANCE.get(launcher)
+ .getActiveSessionInfo(item.user, packageName),
UI_HELPER_EXECUTOR);
Consumer<SessionInfo> marketLaunchAction = sessionInfo -> {
if (sessionInfo != null) {
@@ -245,8 +244,8 @@
}
}
// Fallback to using custom market intent.
- Intent intent = ApiWrapper.INSTANCE.get(launcher).getAppMarketActivityIntent(
- packageName, Process.myUserHandle());
+ Intent intent = ApiWrapper.INSTANCE.get(launcher).getMarketSearchIntent(
+ packageName, item.user);
launcher.startActivitySafely(v, intent, item);
};
@@ -358,9 +357,7 @@
// Check for abandoned promise
if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()
&& (!Flags.enableSupportForArchiving() || !shortcut.isArchived())) {
- String packageName = shortcut.getIntent().getComponent() != null
- ? shortcut.getIntent().getComponent().getPackageName()
- : shortcut.getIntent().getPackage();
+ String packageName = shortcut.getTargetPackage();
if (!TextUtils.isEmpty(packageName)) {
onClickPendingAppItem(
v,
diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
index 467a7ec..d24d084 100644
--- a/src/com/android/launcher3/util/ApiWrapper.java
+++ b/src/com/android/launcher3/util/ApiWrapper.java
@@ -28,6 +28,7 @@
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
@@ -120,6 +121,21 @@
* Activity).
*/
public Intent getAppMarketActivityIntent(String packageName, UserHandle user) {
+ return createMarketIntent(packageName);
+ }
+
+ /**
+ * Returns an intent which can be used to start a search for a package on app market
+ */
+ public Intent getMarketSearchIntent(String packageName, UserHandle user) {
+ // If we are search for the current user, just launch the market directly as the
+ // system won't have the installer details either
+ return (Process.myUserHandle().equals(user))
+ ? createMarketIntent(packageName)
+ : getAppMarketActivityIntent(packageName, user);
+ }
+
+ private static Intent createMarketIntent(String packageName) {
return new Intent(Intent.ACTION_VIEW)
.setData(new Uri.Builder()
.scheme("market")
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
index 02779ce..20e3eaf 100644
--- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
@@ -15,24 +15,20 @@
*/
package com.android.launcher3.util;
-import android.graphics.drawable.Drawable;
import android.view.View;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.PendingAppWidgetHostView;
-import java.util.HashSet;
-import java.util.List;
+import java.util.Set;
/**
* Interface representing a container which can bind Launcher items with some utility methods
@@ -41,27 +37,22 @@
/**
* Called to update workspace items as a result of
- * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindWorkspaceItemsChanged(List)}
+ * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindItemsUpdated(Set)}
*/
- default void updateWorkspaceItems(List<WorkspaceItemInfo> shortcuts, ActivityContext context) {
- final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
+ default void updateContainerItems(Set<ItemInfo> updates, ActivityContext context) {
ItemOperator op = (info, v) -> {
- if (v instanceof BubbleTextView && updates.contains(info)) {
- WorkspaceItemInfo si = (WorkspaceItemInfo) info;
- BubbleTextView shortcut = (BubbleTextView) v;
- Drawable oldIcon = shortcut.getIcon();
- boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
- && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
- shortcut.applyFromWorkspaceItem(
- si,
- si.isPromise() != oldPromiseState
- && oldIcon instanceof PreloadIconDrawable
- ? (PreloadIconDrawable) oldIcon
- : null);
- } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
- ((FolderIcon) v).updatePreviewItems(updates::contains);
+ if (v instanceof BubbleTextView shortcut
+ && info instanceof WorkspaceItemInfo wii
+ && updates.contains(info)) {
+ shortcut.applyFromWorkspaceItem(wii);
+ } else if (info instanceof FolderInfo && v instanceof FolderIcon folderIcon) {
+ folderIcon.updatePreviewItems(updates::contains);
} else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
+ } else if (v instanceof PendingAppWidgetHostView pendingView
+ && updates.contains(info)) {
+ pendingView.applyState();
+ pendingView.postProviderAvailabilityCheck();
}
// Iterate all items
@@ -76,35 +67,6 @@
}
/**
- * Called to update restored items as a result of
- * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindRestoreItemsChange(HashSet)}}
- */
- default void updateRestoreItems(final HashSet<ItemInfo> updates, ActivityContext context) {
- ItemOperator op = (info, v) -> {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
- && updates.contains(info)) {
- ((BubbleTextView) v).applyLoadingState(null);
- } else if (v instanceof PendingAppWidgetHostView
- && info instanceof LauncherAppWidgetInfo
- && updates.contains(info)) {
- ((PendingAppWidgetHostView) v).applyState();
- } else if (v instanceof FolderIcon && info instanceof FolderInfo) {
- ((FolderIcon) v).updatePreviewItems(updates::contains);
- } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
- appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
- }
- // process all the shortcuts
- return false;
- };
-
- mapOverItems(op);
- Folder folder = Folder.getOpen(context);
- if (folder != null) {
- folder.iterateOverItems(op);
- }
- }
-
- /**
* Map the operator over the shortcuts and widgets.
*
* @param op the operator to map over the shortcuts
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 9c9b80d..cd8e457 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -21,7 +21,7 @@
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
-import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.appwidget.AppWidgetProviderInfo;
@@ -37,6 +37,9 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
@@ -60,8 +63,10 @@
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
import java.util.List;
@@ -81,6 +86,8 @@
private final Matrix mMatrix = new Matrix();
private final RectF mPreviewBitmapRect = new RectF();
private final RectF mCanvasRect = new RectF();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final RunnableList mOnDetachCleanup = new RunnableList();
private final LauncherWidgetHolder mWidgetHolder;
private final LauncherAppWidgetProviderInfo mAppwidget;
@@ -90,7 +97,6 @@
private final CharSequence mLabel;
private OnClickListener mClickListener;
- private SafeCloseable mOnDetachCleanup;
private int mDragFlags;
@@ -210,16 +216,15 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ mOnDetachCleanup.executeAllAndClear();
if ((mAppwidget != null)
&& !mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)
&& mInfo.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED) {
// If the widget is not completely restored, but has a valid ID, then listen of
// updates from provider app for potential restore complete.
- if (mOnDetachCleanup != null) {
- mOnDetachCleanup.close();
- }
- mOnDetachCleanup = mWidgetHolder.addOnUpdateListener(
+ SafeCloseable updateCleanup = mWidgetHolder.addOnUpdateListener(
mInfo.appWidgetId, mAppwidget, this::checkIfRestored);
+ mOnDetachCleanup.add(updateCleanup::close);
checkIfRestored();
}
}
@@ -227,10 +232,7 @@
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (mOnDetachCleanup != null) {
- mOnDetachCleanup.close();
- mOnDetachCleanup = null;
- }
+ mOnDetachCleanup.executeAllAndClear();
}
/**
@@ -295,43 +297,30 @@
mCenterDrawable.setCallback(null);
mCenterDrawable = null;
}
- mDragFlags = 0;
- if (info.bitmap.icon != null) {
- mDragFlags = FLAG_DRAW_ICON;
+ mDragFlags = FLAG_DRAW_ICON;
- Drawable widgetCategoryIcon = getWidgetCategoryIcon();
- // The view displays three modes,
- // 1) App icon in the center
- // 2) Preload icon in the center
- // 3) App icon in the center with a setup icon on the top left corner.
- if (mDisabledForSafeMode) {
- if (widgetCategoryIcon == null) {
- FastBitmapDrawable disabledIcon = info.newIcon(getContext());
- disabledIcon.setIsDisabled(true);
- mCenterDrawable = disabledIcon;
- } else {
- widgetCategoryIcon.setColorFilter(getDisabledColorFilter());
- mCenterDrawable = widgetCategoryIcon;
- }
- mSettingIconDrawable = null;
- } else if (isReadyForClickSetup()) {
- mCenterDrawable = widgetCategoryIcon == null
- ? info.newIcon(getContext())
- : widgetCategoryIcon;
- mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
- updateSettingColor(info.bitmap.color);
+ // The view displays three modes,
+ // 1) App icon in the center
+ // 2) Preload icon in the center
+ // 3) App icon in the center with a setup icon on the top left corner.
+ if (mDisabledForSafeMode) {
+ FastBitmapDrawable disabledIcon = info.newIcon(getContext());
+ disabledIcon.setIsDisabled(true);
+ mCenterDrawable = disabledIcon;
+ mSettingIconDrawable = null;
+ } else if (isReadyForClickSetup()) {
+ mCenterDrawable = info.newIcon(getContext());
+ mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
+ updateSettingColor(info.bitmap.color);
- mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL;
- } else {
- mCenterDrawable = widgetCategoryIcon == null
- ? newPendingIcon(getContext(), info)
- : widgetCategoryIcon;
- mSettingIconDrawable = null;
- applyState();
- }
- mCenterDrawable.setCallback(this);
- mDrawableSizeChanged = true;
+ mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL;
+ } else {
+ mCenterDrawable = newPendingIcon(getContext(), info);
+ mSettingIconDrawable = null;
+ applyState();
}
+ mCenterDrawable.setCallback(this);
+ mDrawableSizeChanged = true;
invalidate();
}
@@ -350,6 +339,11 @@
}
public void applyState() {
+ if (mCenterDrawable instanceof FastBitmapDrawable fb
+ && mInfo.pendingItemInfo != null
+ && !fb.isSameInfo(mInfo.pendingItemInfo.bitmap)) {
+ reapplyItemInfo(mInfo.pendingItemInfo);
+ }
if (mCenterDrawable != null) {
mCenterDrawable.setLevel(Math.max(mInfo.installProgress, 0));
}
@@ -486,16 +480,72 @@
}
/**
- * Returns the widget category icon for {@link #mInfo}.
- *
- * <p>If {@link #mInfo}'s category is {@code PackageItemInfo#NO_CATEGORY} or unknown, returns
- * {@code null}.
+ * Creates a runnable runnable which tries to refresh the widget if it is restored
*/
- @Nullable
- private Drawable getWidgetCategoryIcon() {
- if (mInfo.pendingItemInfo.widgetCategory == WidgetSections.NO_CATEGORY) {
- return null;
+ public void postProviderAvailabilityCheck() {
+ if (!mInfo.hasRestoreFlag(FLAG_PROVIDER_NOT_READY) && getAppWidgetInfo() == null) {
+ // If the info state suggests that the provider is ready, but there is no
+ // provider info attached on this pending view, recreate when the provider is available
+ DeferredWidgetRefresh restoreRunnable = new DeferredWidgetRefresh();
+ mOnDetachCleanup.add(restoreRunnable::cleanup);
+ mHandler.post(restoreRunnable::notifyWidgetProvidersChanged);
}
- return mInfo.pendingItemInfo.newIcon(getContext());
+ }
+
+ /**
+ * Used as a workaround to ensure that the AppWidgetService receives the
+ * PACKAGE_ADDED broadcast before updating widgets.
+ *
+ * This class will periodically check for the availability of the WidgetProvider as a result
+ * of providerChanged callback from the host. When the provider is available or a timeout of
+ * 10-sec is reached, it reinflates the pending-widget which in-turn goes through the process
+ * of re-evaluating the pending state of the widget,
+ */
+ private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener {
+ private boolean mRefreshPending = true;
+
+ DeferredWidgetRefresh() {
+ mWidgetHolder.addProviderChangeListener(this);
+ // Force refresh after 10 seconds, if we don't get the provider changed event.
+ // This could happen when the provider is no longer available in the app.
+ Message msg = Message.obtain(getHandler(), this);
+ msg.obj = DeferredWidgetRefresh.class;
+ mHandler.sendMessageDelayed(msg, 10000);
+ }
+
+ /**
+ * Reinflate the widget if it is still attached.
+ */
+ @Override
+ public void run() {
+ cleanup();
+ if (mRefreshPending) {
+ reInflate();
+ mRefreshPending = false;
+ }
+ }
+
+ @Override
+ public void notifyWidgetProvidersChanged() {
+ final AppWidgetProviderInfo widgetInfo;
+ WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext());
+ if (mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
+ widgetInfo = widgetHelper.findProvider(mInfo.providerName, mInfo.user);
+ } else {
+ widgetInfo = widgetHelper.getLauncherAppWidgetInfo(mInfo.appWidgetId,
+ mInfo.getTargetComponent());
+ }
+ if (widgetInfo != null) {
+ run();
+ }
+ }
+
+ /**
+ * Removes any scheduled callbacks and change listeners, no-op if nothing is scheduled
+ */
+ public void cleanup() {
+ mWidgetHolder.removeProviderChangeListener(this);
+ mHandler.removeCallbacks(this);
+ }
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index f51871b..5c326f9 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -28,6 +28,7 @@
import static com.android.launcher3.Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS;
import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.google.common.truth.Truth.assertThat;
@@ -39,6 +40,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
import android.graphics.Typeface;
import android.os.Build;
import android.os.UserHandle;
@@ -57,13 +60,17 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.search.StringMatcherUtility;
import com.android.launcher3.util.ActivityContextWrapper;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext;
+import com.android.launcher3.util.TestUtil;
import com.android.launcher3.views.BaseDragLayer;
import org.junit.After;
@@ -485,4 +492,38 @@
assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(true);
}
+
+ @Test
+ public void applyingPendingIcon_preserves_last_icon() throws Exception {
+ mItemInfoWithIcon.bitmap =
+ BitmapInfo.fromBitmap(Bitmap.createBitmap(100, 100, Config.ARGB_8888));
+ mItemInfoWithIcon.setProgressLevel(30, PackageInstallInfo.STATUS_INSTALLING);
+
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR,
+ () -> mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon));
+ assertThat(mBubbleTextView.getIcon()).isInstanceOf(PreloadIconDrawable.class);
+ assertThat(mBubbleTextView.getIcon().getLevel()).isEqualTo(30);
+ PreloadIconDrawable oldIcon = (PreloadIconDrawable) mBubbleTextView.getIcon();
+
+ // Same icon is used when progress changes
+ mItemInfoWithIcon.setProgressLevel(50, PackageInstallInfo.STATUS_INSTALLING);
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR,
+ () -> mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon));
+ assertThat(mBubbleTextView.getIcon()).isSameInstanceAs(oldIcon);
+ assertThat(mBubbleTextView.getIcon().getLevel()).isEqualTo(50);
+
+ // Icon is replaced with a non pending icon when download finishes
+ mItemInfoWithIcon.setProgressLevel(100, PackageInstallInfo.STATUS_INSTALLED);
+
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () -> {
+ mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon);
+ assertThat(mBubbleTextView.getIcon()).isSameInstanceAs(oldIcon);
+ assertThat(oldIcon.getActiveAnimation()).isNotNull();
+ oldIcon.getActiveAnimation().end();
+ });
+
+ // Assert that the icon is replaced with a non-pending icon
+ assertThat(mBubbleTextView.getIcon()).isNotInstanceOf(PreloadIconDrawable.class);
+ }
+
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt
new file mode 100644
index 0000000..93be5f5
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2025 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.util
+
+import android.content.ComponentName
+import android.content.pm.LauncherApps
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.ARGB_8888
+import android.os.Process.myUserHandle
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.graphics.PreloadIconDrawable
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.FastBitmapDrawable
+import com.android.launcher3.icons.PlaceHolderIconDrawable
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.AppInfo.makeLaunchIntent
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.PackageInstallInfo
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3
+import com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LauncherBindableItemsContainerTest {
+
+ private val icon1 by lazy { getLAI(TEST_ACTIVITY) }
+ private val icon2 by lazy { getLAI(TEST_ACTIVITY2) }
+ private val icon3 by lazy { getLAI(TEST_ACTIVITY3) }
+
+ private val container = TestContainer()
+
+ @Test
+ fun `icon bitmap is updated`() {
+ container.addIcon(icon1)
+ container.addIcon(icon2)
+ container.addIcon(icon3)
+
+ assertThat(container.getAppIcon(icon1).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon2).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon3).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+
+ icon2.bitmap = BitmapInfo.fromBitmap(Bitmap.createBitmap(200, 200, ARGB_8888))
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {
+ container.updateContainerItems(setOf(icon2), container)
+ }
+
+ assertThat(container.getAppIcon(icon1).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon3).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon2).icon)
+ .isNotInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon2).icon).isInstanceOf(FastBitmapDrawable::class.java)
+ }
+
+ @Test
+ fun `icon download progress updated`() {
+ container.addIcon(icon1)
+ container.addIcon(icon2)
+ assertThat(container.getAppIcon(icon1).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon2).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+
+ icon1.status = WorkspaceItemInfo.FLAG_RESTORED_ICON
+ icon1.bitmap = BitmapInfo.fromBitmap(Bitmap.createBitmap(200, 200, ARGB_8888))
+ icon1.setProgressLevel(30, PackageInstallInfo.STATUS_INSTALLING)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {
+ container.updateContainerItems(setOf(icon1), container)
+ }
+
+ assertThat(container.getAppIcon(icon2).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon1).icon).isInstanceOf(PreloadIconDrawable::class.java)
+ val oldIcon = container.getAppIcon(icon1).icon as PreloadIconDrawable
+ assertThat(oldIcon.level).isEqualTo(30)
+ }
+
+ private fun getLAI(className: String): WorkspaceItemInfo =
+ AppInfo(
+ context,
+ context
+ .getSystemService(LauncherApps::class.java)!!
+ .resolveActivity(
+ makeLaunchIntent(ComponentName(TEST_PACKAGE, className)),
+ myUserHandle(),
+ )!!,
+ myUserHandle(),
+ )
+ .makeWorkspaceItem(context)
+
+ class TestContainer : ActivityContextWrapper(context), LauncherBindableItemsContainer {
+
+ val items = mutableMapOf<ItemInfo, View>()
+
+ override fun mapOverItems(op: ItemOperator) {
+ items.forEach { (item, view) -> if (op.evaluate(item, view)) return@forEach }
+ }
+
+ fun addIcon(info: WorkspaceItemInfo) {
+ val btv = BubbleTextView(this)
+ btv.applyFromWorkspaceItem(info)
+ items[info] = btv
+ }
+
+ fun getAppIcon(info: WorkspaceItemInfo) = items[info] as BubbleTextView
+ }
+}