From 4eb502ae10b94c8d6d63b1bfd51632f680b0fe89 Mon Sep 17 00:00:00 2001 From: Federico Baron Date: Wed, 14 Dec 2022 00:04:53 -0800 Subject: [PATCH] Add finish icon scale animation for downloading apps We add an animation when we finish downloading an app where the scale of the icon increases to from scale 0.867 to scale 1 Bug: 254858049 Test: Download an app and visualize finish download animation Change-Id: I67912f86b0e35091a5fe1b39c3463a1ff203bd3b --- src/com/android/launcher3/BubbleTextView.java | 33 +-- .../graphics/PreloadIconDrawable.java | 218 ++++++++---------- .../util/LauncherBindableItemsContainer.java | 9 +- 3 files changed, 121 insertions(+), 139 deletions(-) diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 9f54f09bb6..edbce1068e 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -16,11 +16,14 @@ package com.android.launcher3; +import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V2; import static com.android.launcher3.config.FeatureFlags.ENABLE_ICON_LABEL_AUTO_SCALING; import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; import static com.android.launcher3.icons.BitmapInfo.FLAG_NO_BADGE; import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED; 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 android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -291,7 +294,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @UiThread public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) { - applyFromWorkspaceItem(info, false); + applyFromWorkspaceItem(info, null); } /** @@ -320,10 +323,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } @UiThread - public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean promiseStateChanged) { + public void applyFromWorkspaceItem(WorkspaceItemInfo info, PreloadIconDrawable icon) { applyIconAndLabel(info); setItemInfo(info); - applyLoadingState(promiseStateChanged); + applyLoadingState(icon); applyDotState(info, false /* animate */); setDownloadStateContentDescription(info, info.getProgressLevel()); } @@ -710,23 +713,23 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, * If this app is installed and downloading incrementally, the progress bar will be updated * with the total download progress. */ - public void applyLoadingState(boolean promiseStateChanged) { + public void applyLoadingState(PreloadIconDrawable icon) { if (getTag() instanceof ItemInfoWithIcon) { WorkspaceItemInfo info = (WorkspaceItemInfo) getTag(); - if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) - != 0) { - updateProgressBarUi(info.getProgressLevel() == 100); - } else if (info.hasPromiseIconUi() || (info.runtimeStatusFlags - & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) { - updateProgressBarUi(promiseStateChanged); + if ((info.runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0 + || info.hasPromiseIconUi() + || (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0 + || (ENABLE_DOWNLOAD_APP_UX_V2.get() && icon != null)) { + updateProgressBarUi(icon); } } } - private void updateProgressBarUi(boolean maybePerformFinishedAnimation) { + private void updateProgressBarUi(PreloadIconDrawable oldIcon) { + FastBitmapDrawable originalIcon = mIcon; PreloadIconDrawable preloadDrawable = applyProgressLevel(); - if (preloadDrawable != null && maybePerformFinishedAnimation) { - preloadDrawable.maybePerformFinishedAnimation(); + if (preloadDrawable != null && oldIcon != null) { + preloadDrawable.maybePerformFinishedAnimation(oldIcon, () -> setIcon(originalIcon)); } } @@ -824,12 +827,12 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, != 0) { String percentageString = NumberFormat.getPercentInstance() .format(progressLevel * 0.01); - if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) { + if ((info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0) { setContentDescription(getContext() .getString( R.string.app_installing_title, info.title, percentageString)); } else if ((info.runtimeStatusFlags - & ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0) { + & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0) { setContentDescription(getContext() .getString( R.string.app_downloading_title, info.title, percentageString)); diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java index de47cb5057..9001a5270e 100644 --- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java +++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java @@ -28,6 +28,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; @@ -41,6 +42,7 @@ import android.util.Property; 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.FastBitmapDrawable; import com.android.launcher3.icons.GraphicsUtils; import com.android.launcher3.model.data.ItemInfoWithIcon; @@ -53,7 +55,7 @@ import java.util.function.Function; /** * Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon. */ -public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable { +public class PreloadIconDrawable extends FastBitmapDrawable { private static final Property INTERNAL_STATE = new Property(Float.TYPE, "internalStateProgress") { @@ -78,16 +80,19 @@ public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable // The smaller the number, the faster the animation would be. // Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE - private static final float COMPLETE_ANIM_FRACTION = 0.3f; + private static final float COMPLETE_ANIM_FRACTION = 1f; private static final float SMALL_SCALE = ENABLE_DOWNLOAD_APP_UX_V2.get() ? 0.867f : 0.7f; - private static final float PROGRESS_STROKE_SCALE = 0.075f; + private static final float PROGRESS_STROKE_SCALE = ENABLE_DOWNLOAD_APP_UX_V2.get() + ? 0.0655f + : 0.075f; + private static final float PROGRESS_BOUNDS_SCALE = 0.075f; private static final int PRELOAD_ACCENT_COLOR_INDEX = 0; private static final int PRELOAD_BACKGROUND_COLOR_INDEX = 1; private static final int ALPHA_DURATION_MILLIS = 3000; - private static final float OVERLAY_ALPHA_RANGE = 127.5f; + private static final int OVERLAY_ALPHA_RANGE = 127; private static final long WAVE_MOTION_DELAY_FACTOR_MILLIS = 100; private static final WeakHashMap COLOR_FILTER_MAP = new WeakHashMap<>(); @@ -111,19 +116,17 @@ public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable private final int mSystemBackgroundColor; private final boolean mIsDarkMode; - private int mTrackAlpha; private float mTrackLength; private boolean mRanFinishAnimation; - private final int mRefreshRateMillis; - private final AnimatedFloat mIconScale = new AnimatedFloat(this::invalidateSelf); - private final AnimatedFloat mOverlayAlpha = new AnimatedFloat(this::updateOverlayAlpha); - private boolean mShouldAnimateScaleAndAlpha; // Progress of the internal state. [0, 1] indicates the fraction of completed progress, // [1, (1 + COMPLETE_ANIM_FRACTION)] indicates the progress of zoom animation. private float mInternalStateProgress; + // This multiplier is used to animate scale when going from 0 to non-zero and expanding + private final Runnable mInvalidateRunnable = this::invalidateSelf; + private final AnimatedFloat mIconScaleMultiplier = new AnimatedFloat(mInvalidateRunnable); private ObjectAnimator mCurrentAnim; @@ -160,10 +163,7 @@ public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable mRefreshRateMillis = refreshRateMillis; // If it's a pending app we will animate scale and alpha when it's no longer pending. - if (ENABLE_DOWNLOAD_APP_UX_V2.get() && info.getProgressLevel() == 0) { - mShouldAnimateScaleAndAlpha = true; - mOverlayAlpha.updateValue(127); - } + mIconScaleMultiplier.updateValue(info.getProgressLevel() == 0 ? 0 : 1); setLevel(info.getProgressLevel()); setIsStartable(info.isAppStartable()); @@ -173,14 +173,17 @@ public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); - float progressWidth = PROGRESS_STROKE_SCALE * bounds.width(); + + float progressWidth = bounds.width() * (ENABLE_DOWNLOAD_APP_UX_V2.get() + ? PROGRESS_BOUNDS_SCALE + : PROGRESS_STROKE_SCALE); mTmpMatrix.setScale( (bounds.width() - 2 * progressWidth) / DEFAULT_PATH_SIZE, (bounds.height() - 2 * progressWidth) / DEFAULT_PATH_SIZE); mTmpMatrix.postTranslate(bounds.left + progressWidth, bounds.top + progressWidth); mShapePath.transform(mTmpMatrix, mScaledTrackPath); - mProgressPaint.setStrokeWidth(progressWidth); + mProgressPaint.setStrokeWidth(PROGRESS_STROKE_SCALE * bounds.width()); mPathMeasure.setPath(mScaledTrackPath, true); mTrackLength = mPathMeasure.getLength(); @@ -195,26 +198,35 @@ public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable return; } - // Draw background. - mProgressPaint.setStyle(Paint.Style.FILL_AND_STROKE); - mProgressPaint.setColor(mSystemBackgroundColor); - canvas.drawPath(mScaledTrackPath, mProgressPaint); + if (!ENABLE_DOWNLOAD_APP_UX_V2.get() && mInternalStateProgress > 0) { + // Draw background. + mProgressPaint.setStyle(Paint.Style.FILL_AND_STROKE); + mProgressPaint.setColor(mSystemBackgroundColor); + canvas.drawPath(mScaledTrackPath, mProgressPaint); + } - // Draw track and progress. - mProgressPaint.setStyle(Paint.Style.STROKE); - mProgressPaint.setColor(mIsStartable ? mIndicatorColor : mSystemAccentColor); - mProgressPaint.setAlpha(TRACK_ALPHA); - canvas.drawPath(mScaledTrackPath, mProgressPaint); - mProgressPaint.setAlpha(mTrackAlpha); - canvas.drawPath(mScaledProgressPath, mProgressPaint); + if (!ENABLE_DOWNLOAD_APP_UX_V2.get() || mInternalStateProgress > 0) { + // Draw track and progress. + mProgressPaint.setStyle(Paint.Style.STROKE); + mProgressPaint.setColor(mSystemAccentColor); + mProgressPaint.setAlpha(TRACK_ALPHA); + canvas.drawPath(mScaledTrackPath, mProgressPaint); + mProgressPaint.setAlpha(MAX_PAINT_ALPHA); + canvas.drawPath(mScaledProgressPath, mProgressPaint); + } int saveCount = canvas.save(); - canvas.scale( - mIconScale.value, mIconScale.value, bounds.exactCenterX(), bounds.exactCenterY()); + float scale = ENABLE_DOWNLOAD_APP_UX_V2.get() + ? 1 - mIconScaleMultiplier.value * (1 - SMALL_SCALE) + : SMALL_SCALE; + canvas.scale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()); + + ColorFilter filter = getOverlayFilter(); + mPaint.setColorFilter(filter); super.drawInternal(canvas, bounds); canvas.restoreToCount(saveCount); - if (ENABLE_DOWNLOAD_APP_UX_V2.get() && mInternalStateProgress == 0) { + if (ENABLE_DOWNLOAD_APP_UX_V2.get() && filter != null) { reschedule(); } } @@ -232,7 +244,7 @@ public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable @Override protected boolean onLevelChange(int level) { // Run the animation if we have already been bound. - updateInternalState(level * 0.01f, getBounds().width() > 0, false); + updateInternalState(level * 0.01f, false, null); return true; } @@ -240,12 +252,18 @@ public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable * Runs the finish animation if it is has not been run after last call to * {@link #onLevelChange} */ - public void maybePerformFinishedAnimation() { + public void maybePerformFinishedAnimation( + PreloadIconDrawable oldIcon, Runnable onFinishCallback) { + + if (oldIcon.mInternalStateProgress >= 1) { + mInternalStateProgress = oldIcon.mInternalStateProgress; + } + // If the drawable was recently initialized, skip the progress animation. if (mInternalStateProgress == 0) { mInternalStateProgress = 1; } - updateInternalState(1 + COMPLETE_ANIM_FRACTION, true, true); + updateInternalState(1 + COMPLETE_ANIM_FRACTION, true, onFinishCallback); } public boolean hasNotCompleted() { @@ -260,26 +278,29 @@ public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable } } - private void updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish) { + private void updateInternalState( + float finalProgress, boolean isFinish, Runnable onFinishCallback) { if (mCurrentAnim != null) { mCurrentAnim.cancel(); mCurrentAnim = null; } - if (Float.compare(finalProgress, mInternalStateProgress) == 0) { - return; - } - if (finalProgress < mInternalStateProgress) { - shouldAnimate = false; - } - if (!shouldAnimate || mRanFinishAnimation) { + boolean animateProgress = + finalProgress >= mInternalStateProgress && getBounds().width() > 0; + if (!animateProgress || mRanFinishAnimation) { setInternalProgress(finalProgress); + if (isFinish && onFinishCallback != null) { + onFinishCallback.run(); + } } else { mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress); mCurrentAnim.setDuration( (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) { @@ -297,62 +318,38 @@ public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable * - icon with pending motion * - progress track is not visible * - progress bar is not visible - * for progress < 1 + * for progress < 1: * - icon without pending motion * - progress track is visible * - progress bar is visible. Progress bar is drawn as a fraction of * {@link #mScaledTrackPath}. * @see PathMeasure#getSegment(float, float, Path, boolean) - * for 1 <= progress < (1 + COMPLETE_ANIM_FRACTION) - * - we calculate fraction of progress in the above range - * - progress track is drawn with alpha based on fraction - * - progress bar is drawn at 100% with alpha based on fraction - * - icon is scaled up based on fraction and is drawn in enabled state - * for progress >= (1 + COMPLETE_ANIM_FRACTION) - * - only icon is drawn in normal state + * for progress > 1: + * - scale the icon back to full size */ private void setInternalProgress(float progress) { // Animate scale and alpha from pending to downloading state. - if (ENABLE_DOWNLOAD_APP_UX_V2.get() - && mShouldAnimateScaleAndAlpha && mInternalStateProgress == 0 && progress > 0) { - Animator iconScaleAnimator = mIconScale.animateToValue(SMALL_SCALE); + if (ENABLE_DOWNLOAD_APP_UX_V2.get() && progress > 0 && mInternalStateProgress == 0) { + // Progress is changing for the first time, animate the icon scale + Animator iconScaleAnimator = mIconScaleMultiplier.animateToValue(1); iconScaleAnimator.setDuration(SCALE_AND_ALPHA_ANIM_DURATION); iconScaleAnimator.setInterpolator(EMPHASIZED); iconScaleAnimator.start(); - - Animator overlayAlphaAnimator = mOverlayAlpha.animateToValue(0); - overlayAlphaAnimator.setDuration(SCALE_AND_ALPHA_ANIM_DURATION); - overlayAlphaAnimator.setInterpolator(EMPHASIZED); - overlayAlphaAnimator.start(); } mInternalStateProgress = progress; if (progress <= 0) { - mIconScale.updateValue(ENABLE_DOWNLOAD_APP_UX_V2.get() ? 1 : SMALL_SCALE); - mScaledTrackPath.reset(); - mTrackAlpha = MAX_PAINT_ALPHA; - } else if (progress < 1) { - mPathMeasure.getSegment(0, progress * mTrackLength, mScaledProgressPath, true); - if (ENABLE_DOWNLOAD_APP_UX_V2.get()) { - mPathMeasure.getSegment(0, mTrackLength, mScaledTrackPath, true); + if (!ENABLE_DOWNLOAD_APP_UX_V2.get()) { + mScaledTrackPath.reset(); } - - if (!ENABLE_DOWNLOAD_APP_UX_V2.get() || !mShouldAnimateScaleAndAlpha) { - mIconScale.updateValue(SMALL_SCALE); - } - mTrackAlpha = MAX_PAINT_ALPHA; + mIconScaleMultiplier.updateValue(0); } else { - setIsDisabled(mItem.isDisabled()); - mScaledTrackPath.set(mScaledProgressPath); - float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION; - - if (fraction >= 1) { - // Animation has completed - mIconScale.updateValue(1); - mTrackAlpha = 0; - } else { - mTrackAlpha = Math.round((1 - fraction) * MAX_PAINT_ALPHA); - mIconScale.updateValue(SMALL_SCALE + (1 - SMALL_SCALE) * fraction); + mPathMeasure.getSegment( + 0, Math.min(progress, 1) * mTrackLength, mScaledProgressPath, true); + if (progress > 1 && ENABLE_DOWNLOAD_APP_UX_V2.get()) { + // map the scale back to original value + mIconScaleMultiplier.updateValue(Utilities.mapBoundToRange( + progress - 1, 0, COMPLETE_ANIM_FRACTION, 1, 0, EMPHASIZED)); } } invalidateSelf(); @@ -392,72 +389,49 @@ public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable mRefreshRateMillis); } - @Override - public void run() { - if (!ENABLE_DOWNLOAD_APP_UX_V2.get() || mInternalStateProgress > 0) { - return; - } - if (!applyPendingIconOverlay()) { - reschedule(); - } - } - @Override public boolean setVisible(boolean visible, boolean restart) { - boolean result = super.setVisible(visible, restart); - if (visible) { - reschedule(); - } else { - unscheduleSelf(this); + if (!visible) { + unscheduleSelf(mInvalidateRunnable); } - return result; + return super.setVisible(visible, restart); } private void reschedule() { - unscheduleSelf(this); - + unscheduleSelf(mInvalidateRunnable); if (!isVisible()) { return; } - final long upTime = SystemClock.uptimeMillis(); - scheduleSelf(this, upTime - ((upTime % mRefreshRateMillis)) + mRefreshRateMillis); + scheduleSelf(mInvalidateRunnable, + upTime - ((upTime % mRefreshRateMillis)) + mRefreshRateMillis); } - /** - * Apply an overlay on the pending icon with cascading motion based on its position. - * Returns {@code true} if the icon alpha is updated, so that we re-draw. + * Returns a color filter to be used as an overlay on the pending icon with cascading motion + * based on its position. */ - private boolean applyPendingIconOverlay() { + private ColorFilter getOverlayFilter() { + if (!ENABLE_DOWNLOAD_APP_UX_V2.get() || mInternalStateProgress > 0) { + // If the download has started, we do no need to animate + return null; + } long waveMotionDelay = (mItem.cellX * WAVE_MOTION_DELAY_FACTOR_MILLIS) + (mItem.cellY * WAVE_MOTION_DELAY_FACTOR_MILLIS); long time = SystemClock.uptimeMillis(); - float newAlpha = Utilities.mapBoundToRange( - (float) (time + waveMotionDelay) % ALPHA_DURATION_MILLIS, + int alpha = (int) Utilities.mapBoundToRange( + (int) ((time + waveMotionDelay) % ALPHA_DURATION_MILLIS), 0, ALPHA_DURATION_MILLIS, 0, - MAX_PAINT_ALPHA, + OVERLAY_ALPHA_RANGE * 2, LINEAR); - if (newAlpha > OVERLAY_ALPHA_RANGE) { - newAlpha = (OVERLAY_ALPHA_RANGE - (newAlpha % OVERLAY_ALPHA_RANGE)); + if (alpha > OVERLAY_ALPHA_RANGE) { + alpha = (OVERLAY_ALPHA_RANGE - (alpha % OVERLAY_ALPHA_RANGE)); } - - boolean invalidate = false; - if ((int) mOverlayAlpha.value != newAlpha) { - mOverlayAlpha.updateValue(newAlpha); - invalidate = true; - } - return invalidate; - } - - private void updateOverlayAlpha() { int overlayColor = mIsDarkMode ? 0 : 255; - int currArgb = - Color.argb((int) mOverlayAlpha.value, overlayColor, overlayColor, overlayColor); - mPaint.setColorFilter(COLOR_FILTER_MAP.computeIfAbsent(currArgb, FILTER_FACTORY)); - invalidateSelf(); + int currArgb = Color.argb(alpha, overlayColor, overlayColor, overlayColor); + return COLOR_FILTER_MAP.computeIfAbsent(currArgb, FILTER_FACTORY); } protected static class PreloadIconConstantState extends FastBitmapConstantState { diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java index a4cb30a374..f73940bfee 100644 --- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java +++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java @@ -50,7 +50,12 @@ public interface LauncherBindableItemsContainer { Drawable oldIcon = shortcut.getIcon(); boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable) && ((PreloadIconDrawable) oldIcon).hasNotCompleted(); - shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState); + 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); } @@ -74,7 +79,7 @@ public interface LauncherBindableItemsContainer { ItemOperator op = (info, v) -> { if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView && updates.contains(info)) { - ((BubbleTextView) v).applyLoadingState(false /* promiseStateChanged */); + ((BubbleTextView) v).applyLoadingState(null); } else if (v instanceof PendingAppWidgetHostView && info instanceof LauncherAppWidgetInfo && updates.contains(info)) {