Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c03bb43f authored by Federico Baron's avatar Federico Baron
Browse files

Improve restore icon treatment

This is the first of a few CL's that change the way pending/downloading/installing apps look to follow the new mocks. In this CL we:

-Fix a regression where the track path doesn't show up when downloading an app
-Change the sizes of icons to reflect the new design mock
-Add skeleton motion animation

In upcoming CL's we will add visual polish and make sure it matches the motion spec exactly

Bug: 254858049
Test: Download an app and check pending, downloading, and installing visuals
Change-Id: Ief1038ea52d77874b4031db76d51a2787615a14d
parent dad173f9
Loading
Loading
Loading
Loading
+128 −20
Original line number Diff line number Diff line
@@ -17,31 +17,41 @@

package com.android.launcher3.graphics;

import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V2;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.Property;

import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.window.RefreshRateTracker;

import java.util.WeakHashMap;
import java.util.function.Function;

/**
 * Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon.
 */
public class PreloadIconDrawable extends FastBitmapDrawable {
public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable {

    private static final Property<PreloadIconDrawable, Float> INTERNAL_STATE =
            new Property<PreloadIconDrawable, Float>(Float.TYPE, "internalStateProgress") {
@@ -67,12 +77,20 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
    // Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE
    private static final float COMPLETE_ANIM_FRACTION = 0.3f;

    private static final float SMALL_SCALE = 0.7f;
    private static final float SMALL_SCALE = ENABLE_DOWNLOAD_APP_UX_V2.get() ? 0.85f : 0.7f;
    private static final float PROGRESS_STROKE_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 long WAVE_MOTION_DELAY_FACTOR_MILLIS = 100;
    private static final WeakHashMap<Integer, PorterDuffColorFilter> COLOR_FILTER_MAP =
            new WeakHashMap<>();
    public static final Function<Integer, PorterDuffColorFilter> FILTER_FACTORY =
            currArgb -> new PorterDuffColorFilter(currArgb, PorterDuff.Mode.SRC_ATOP);

    private final Matrix mTmpMatrix = new Matrix();
    private final PathMeasure mPathMeasure = new PathMeasure();

@@ -96,6 +114,9 @@ public class PreloadIconDrawable extends FastBitmapDrawable {

    private boolean mRanFinishAnimation;

    private int mOverlayAlpha = 127;
    private int mRefreshRateMillis;

    // 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;
@@ -109,14 +130,16 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
                info,
                IconPalette.getPreloadProgressColor(context, info.bitmap.color),
                getPreloadColors(context),
                Utilities.isDarkTheme(context));
                Utilities.isDarkTheme(context),
                getRefreshRateMillis(context));
    }

    public PreloadIconDrawable(
            ItemInfoWithIcon info,
            int indicatorColor,
            int[] preloadColors,
            boolean isDarkMode) {
            boolean isDarkMode,
            int refreshRateMillis) {
        super(info.bitmap);
        mItem = info;
        mShapePath = GraphicsUtils.getShapePath(DEFAULT_PATH_SIZE);
@@ -130,6 +153,7 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
        mSystemAccentColor = preloadColors[PRELOAD_ACCENT_COLOR_INDEX];
        mSystemBackgroundColor = preloadColors[PRELOAD_BACKGROUND_COLOR_INDEX];
        mIsDarkMode = isDarkMode;
        mRefreshRateMillis = refreshRateMillis;

        setLevel(info.getProgressLevel());
        setIsStartable(info.isAppStartable());
@@ -178,12 +202,18 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
        canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY());
        super.drawInternal(canvas, bounds);
        canvas.restoreToCount(saveCount);

        if (ENABLE_DOWNLOAD_APP_UX_V2.get() && mInternalStateProgress == 0) {
            reschedule();
        }
    }

    @Override
    protected void updateFilter() {
        if (!ENABLE_DOWNLOAD_APP_UX_V2.get()) {
            setAlpha(mIsDisabled ? DISABLED_ICON_ALPHA : MAX_PAINT_ALPHA);
        }
    }

    /**
     * Updates the install progress based on the level
@@ -237,7 +267,7 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
            mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress);
            mCurrentAnim.setDuration(
                    (long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
            mCurrentAnim.setInterpolator(Interpolators.LINEAR);
            mCurrentAnim.setInterpolator(LINEAR);
            if (isFinish) {
                mCurrentAnim.addListener(new AnimatorListenerAdapter() {
                    @Override
@@ -253,13 +283,13 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
    /**
     * Sets the internal progress and updates the UI accordingly
     *   for progress <= 0:
     *     - icon in the small scale and disabled state
     *     - progress track is visible
     *     - icon with pending motion
     *     - progress track is not visible
     *     - progress bar is not visible
     *   for 0 < progress < 1
     *     - icon in the small scale and disabled state
     *   for progress < 1
     *     - icon without pending motion
     *     - progress track is visible
     *     - progress bar is visible with dominant color. Progress bar is drawn as a fraction of
     *     - 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)
@@ -273,16 +303,18 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
    private void setInternalProgress(float progress) {
        mInternalStateProgress = progress;
        if (progress <= 0) {
            mIconScale = SMALL_SCALE;
            mIconScale = ENABLE_DOWNLOAD_APP_UX_V2.get() ? 1 : SMALL_SCALE;
            mScaledTrackPath.reset();
            mTrackAlpha = MAX_PAINT_ALPHA;
        }

        if (progress < 1 && progress > 0) {
        } else if (progress < 1) {
            mPathMeasure.getSegment(0, progress * mTrackLength, mScaledProgressPath, true);
            if (ENABLE_DOWNLOAD_APP_UX_V2.get()) {
                mPaint.setColorFilter(null);
                mPathMeasure.getSegment(0, mTrackLength, mScaledTrackPath, true);
            }
            mIconScale = SMALL_SCALE;
            mTrackAlpha = MAX_PAINT_ALPHA;
        } else if (progress >= 1) {
        } else {
            setIsDisabled(mItem.isDisabled());
            mScaledTrackPath.set(mScaledProgressPath);
            float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION;
@@ -310,6 +342,10 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
        return preloadColors;
    }

    private static int getRefreshRateMillis(Context context) {
        return RefreshRateTracker.getSingleFrameMs(context);
    }

    /**
     * Returns a FastBitmapDrawable with the icon.
     */
@@ -325,7 +361,75 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
                mItem,
                mIndicatorColor,
                new int[] {mSystemAccentColor, mSystemBackgroundColor},
                mIsDarkMode);
                mIsDarkMode,
                mRefreshRateMillis);
    }

    @Override
    public void run() {
        if (!ENABLE_DOWNLOAD_APP_UX_V2.get() || mInternalStateProgress > 0) {
            return;
        }
        if (applyPendingIconOverlay()) {
            invalidateSelf();
        } else {
            reschedule();
        }
    }

    @Override
    public boolean setVisible(boolean visible, boolean restart) {
        boolean result = super.setVisible(visible, restart);
        if (visible) {
            reschedule();
        } else {
            unscheduleSelf(this);
        }
        return result;
    }

    private void reschedule() {
        unscheduleSelf(this);

        if (!isVisible()) {
            return;
        }

        final long upTime = SystemClock.uptimeMillis();
        scheduleSelf(this, 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.
     */
    private boolean applyPendingIconOverlay() {
        long waveMotionDelay = (mItem.cellX * WAVE_MOTION_DELAY_FACTOR_MILLIS)
                + (mItem.cellY * WAVE_MOTION_DELAY_FACTOR_MILLIS);
        long time = SystemClock.uptimeMillis();
        int newAlpha = (int) Utilities.mapBoundToRange(
                (float) (time + waveMotionDelay) % ALPHA_DURATION_MILLIS,
                0,
                ALPHA_DURATION_MILLIS,
                0,
                MAX_PAINT_ALPHA,
                LINEAR);
        if (newAlpha > OVERLAY_ALPHA_RANGE) {
            newAlpha = (int) (OVERLAY_ALPHA_RANGE - (newAlpha % OVERLAY_ALPHA_RANGE));
        }

        boolean invalidate = false;
        if (mOverlayAlpha != newAlpha) {
            mOverlayAlpha = newAlpha;
            int overlayColor = mIsDarkMode ? 0 : 255;
            int currArgb = Color.argb(mOverlayAlpha, overlayColor, overlayColor, overlayColor);
            mPaint.setColorFilter(COLOR_FILTER_MAP.computeIfAbsent(
                    currArgb,
                    FILTER_FACTORY));
            invalidate = true;
        }
        return invalidate;
    }

    protected static class PreloadIconConstantState extends FastBitmapConstantState {
@@ -335,6 +439,7 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
        protected final int[] mPreloadColors;
        protected final boolean mIsDarkMode;
        protected final int mLevel;
        protected final int mRefreshRateMillis;

        public PreloadIconConstantState(
                Bitmap bitmap,
@@ -342,13 +447,15 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
                ItemInfoWithIcon info,
                int indicatorColor,
                int[] preloadColors,
                boolean isDarkMode) {
                boolean isDarkMode,
                int refreshRateMillis) {
            super(bitmap, iconColor);
            mInfo = info;
            mIndicatorColor = indicatorColor;
            mPreloadColors = preloadColors;
            mIsDarkMode = isDarkMode;
            mLevel = info.getProgressLevel();
            mRefreshRateMillis = refreshRateMillis;
        }

        @Override
@@ -357,7 +464,8 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
                    mInfo,
                    mIndicatorColor,
                    mPreloadColors,
                    mIsDarkMode);
                    mIsDarkMode,
                    mRefreshRateMillis);
        }
    }
}