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

Commit 53357ba9 authored by Xiaowen Lei's avatar Xiaowen Lei Committed by Android (Google) Code Review
Browse files

Merge "Update drawable per UX spec without dashed segments." into main

parents ca291369 939a555e
Loading
Loading
Loading
Loading
+15 −19
Original line number Diff line number Diff line
@@ -59,6 +59,8 @@ import java.util.TreeSet;
public final class NotificationProgressBar extends ProgressBar {
    private static final String TAG = "NotificationProgressBar";

    private NotificationProgressDrawable mNotificationProgressDrawable;

    private NotificationProgressModel mProgressModel;

    @Nullable
@@ -94,6 +96,12 @@ public final class NotificationProgressBar extends ProgressBar {
                defStyleAttr,
                defStyleRes);

        try {
            mNotificationProgressDrawable = getNotificationProgressDrawable();
        } catch (IllegalStateException ex) {
            Log.e(TAG, "Can't get NotificationProgressDrawable", ex);
        }

        // Supports setting the tracker in xml, but ProgressStyle notifications set/override it
        // via {@code setProgressTrackerIcon}.
        final Drawable tracker = a.getDrawable(R.styleable.NotificationProgressBar_tracker);
@@ -131,11 +139,8 @@ public final class NotificationProgressBar extends ProgressBar {
                    progressMax,
                    mProgressModel.isStyledByProgress());

            try {
                final NotificationProgressDrawable drawable = getNotificationProgressDrawable();
                drawable.setParts(mProgressDrawableParts);
            } catch (IllegalStateException ex) {
                Log.e(TAG, "Can't set parts because can't get NotificationProgressDrawable", ex);
            if (mNotificationProgressDrawable != null) {
                mNotificationProgressDrawable.setParts(mProgressDrawableParts);
            }

            setMax(progressMax);
@@ -195,10 +200,6 @@ public final class NotificationProgressBar extends ProgressBar {
    }

    private void setTracker(@Nullable Drawable tracker) {
        if (isIndeterminate() && tracker != null) {
            return;
        }

        final boolean needUpdate = mTracker != null && tracker != mTracker;
        if (needUpdate) {
            mTracker.setCallback(null);
@@ -222,6 +223,9 @@ public final class NotificationProgressBar extends ProgressBar {
        }

        mTracker = tracker;
        if (mNotificationProgressDrawable != null) {
            mNotificationProgressDrawable.setHasTrackerIcon(mTracker != null);
        }

        configureTrackerBounds();

@@ -274,16 +278,6 @@ public final class NotificationProgressBar extends ProgressBar {
        mTrackerDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
    }

    @Override
    @RemotableViewMethod
    public synchronized void setIndeterminate(boolean indeterminate) {
        super.setIndeterminate(indeterminate);

        if (isIndeterminate()) {
            setTracker(null);
        }
    }

    @Override
    protected boolean verifyDrawable(@NonNull Drawable who) {
        return who == mTracker || super.verifyDrawable(who);
@@ -421,6 +415,8 @@ public final class NotificationProgressBar extends ProgressBar {
    @Override
    protected synchronized void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (isIndeterminate()) return;
        drawTracker(canvas);
    }

+102 −157
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -62,21 +61,15 @@ public final class NotificationProgressDrawable extends Drawable {
    private boolean mMutated;

    private final ArrayList<Part> mParts = new ArrayList<>();
    private boolean mHasTrackerIcon;

    private final RectF mSegRectF = new RectF();
    private final Rect mPointRect = new Rect();
    private final RectF mPointRectF = new RectF();

    private final Paint mStrokePaint = new Paint();
    private final Paint mDashedStrokePaint = new Paint();
    private final Paint mFillPaint = new Paint();

    {
        mStrokePaint.setStyle(Paint.Style.STROKE);
        mStrokePaint.setStrokeCap(Paint.Cap.ROUND);

        mDashedStrokePaint.setStyle(Paint.Style.STROKE);

        mFillPaint.setStyle(Paint.Style.FILL);
    }

@@ -87,49 +80,15 @@ public final class NotificationProgressDrawable extends Drawable {
    }

    /**
     * <p>Set the stroke width and default color for the drawable.</p>
     * <p>Note: changing this property will affect all instances of a drawable loaded from a
     * resource. It is recommended to invoke
     * {@link #mutate()} before changing this property.</p>
     *
     * @param width The width in pixels of the stroke
     * @param color The color of the stroke
     * @see #mutate()
     * @see #setStroke(int, int, float, float)
     */
    public void setStroke(int width, @ColorInt int color) {
        setStroke(width, color, 0, 0);
    }

    /**
     * <p>Set the stroke width and default color for the drawable. This method can also be used
     * to dash the stroke for the dashed segments.</p>
     * <p>Set the segment default color for the drawable.</p>
     * <p>Note: changing this property will affect all instances of a drawable loaded from a
     * resource. It is recommended to invoke {@link #mutate()} before changing this property.</p>
     *
     * @param width     The width in pixels of the stroke
     * @param color The color of the stroke
     * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes
     * @param dashGap   The gap in pixels between dashes
     * @see #mutate()
     * @see #setStroke(int, int)
     */
    public void setStroke(int width, @ColorInt int color, float dashWidth, float dashGap) {
        mState.setStroke(width, color, dashWidth, dashGap);
        setStrokeInternal(width, dashWidth, dashGap);
    }

    /**
     * <p>Set the stroke default color for the drawable.</p>
     * <p>Note: changing this property will affect all instances of a drawable loaded from a
     * resource. It is recommended to invoke {@link #mutate()} before changing this property.</p>
     *
     * @param color The color of the stroke
     * @see #mutate()
     * @see #setStroke(int, int, float, float)
     */
    public void setStrokeDefaultColor(@ColorInt int color) {
        mState.setStrokeColor(color);
    public void setSegmentDefaultColor(@ColorInt int color) {
        mState.setSegmentColor(color);
    }

    /**
@@ -144,25 +103,14 @@ public final class NotificationProgressDrawable extends Drawable {
        mState.setPointRectColor(color);
    }

    private void setStrokeInternal(int width, float dashWidth, float dashGap) {
        mStrokePaint.setStrokeWidth(width);

        mDashedStrokePaint.setStrokeWidth(width);
        DashPathEffect e = null;
        if (dashWidth > 0) {
            e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0);
        }
        mDashedStrokePaint.setPathEffect(e);

        invalidateSelf();
    }

    /**
     * Set the segments and points that constitute the drawable.
     */
    public void setParts(List<Part> parts) {
        mParts.clear();
        mParts.addAll(parts);

        invalidateSelf();
    }

    /**
@@ -172,6 +120,16 @@ public final class NotificationProgressDrawable extends Drawable {
        setParts(Arrays.asList(parts));
    }

    /**
     * Set whether a tracker is drawn on top of this NotificationProgressDrawable.
     */
    public void setHasTrackerIcon(boolean hasTrackerIcon) {
        if (mHasTrackerIcon != hasTrackerIcon) {
            mHasTrackerIcon = hasTrackerIcon;
            invalidateSelf();
        }
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        final float pointRadius =
@@ -181,6 +139,7 @@ public final class NotificationProgressDrawable extends Drawable {
        float x = (float) getBounds().left;
        final float centerY = (float) getBounds().centerY();
        final float totalWidth = (float) getBounds().width();
        float segPointGap = mState.mSegPointGap;

        final int numParts = mParts.size();
        for (int iPart = 0; iPart < numParts; iPart++) {
@@ -188,15 +147,19 @@ public final class NotificationProgressDrawable extends Drawable {
            final Part prevPart = iPart == 0 ? null : mParts.get(iPart - 1);
            final Part nextPart = iPart + 1 == numParts ? null : mParts.get(iPart + 1);
            if (part instanceof Segment segment) {
                // Update the segment-point gap to 2X upon seeing the first faded segment.
                // (Assuming that all segments before are solid, and all segments after are faded.)
                if (segment.mFaded) {
                    segPointGap = mState.mSegPointGap * 2;
                }
                final float segWidth = segment.mFraction * totalWidth;
                // Advance the start position to account for a point immediately prior.
                final float startOffset = getSegStartOffset(prevPart, pointRadius,
                        mState.mSegPointGap, x);
                final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap, x);
                final float start = x + startOffset;
                // Retract the end position to account for the padding and a point immediately
                // after.
                final float endOffset = getSegEndOffset(nextPart, pointRadius, mState.mSegPointGap,
                        mState.mSegSegGap, x + segWidth, totalWidth);
                final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
                        mState.mSegSegGap, x + segWidth, totalWidth, mHasTrackerIcon);
                final float end = x + segWidth - endOffset;

                // Advance the current position to account for the segment's fraction of the total
@@ -206,35 +169,15 @@ public final class NotificationProgressDrawable extends Drawable {
                // No space left to draw the segment
                if (start > end) continue;

                if (segment.mDashed) {
                    // No caps when the segment is dashed.

                    mDashedStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
                            : mState.mFadedStrokeColor);
                    canvas.drawLine(start, centerY, end, centerY, mDashedStrokePaint);
                } else if (end - start < mState.mStrokeWidth) {
                    // Not enough segment length to draw the caps

                    final float rad = (end - start) / 2F;
                    final float capWidth = mStrokePaint.getStrokeWidth() / 2F;
                final float radiusY = segment.mFaded ? mState.mFadedSegmentHeight / 2F
                        : mState.mSegmentHeight / 2F;
                final float cornerRadius = mState.mSegmentCornerRadius;

                mFillPaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
                            : mState.mStrokeColor);

                    mSegRectF.set(start, centerY - capWidth, end, centerY + capWidth);
                    canvas.drawRoundRect(mSegRectF, rad, rad, mFillPaint);
                } else {
                    // Leave space for the rounded line cap which extends beyond start/end.
                    final float capWidth = mStrokePaint.getStrokeWidth() / 2F;

                    // Transparent is not allowed (and also is the default in the data), so use that
                    // as a sentinel to be replaced by default
                    mStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
                            : mState.mStrokeColor);
                        : (segment.mFaded ? mState.mFadedSegmentColor : mState.mSegmentColor));

                    canvas.drawLine(start + capWidth, centerY, end - capWidth, centerY,
                            mStrokePaint);
                }
                mSegRectF.set(start, centerY - radiusY, end, centerY + radiusY);
                canvas.drawRoundRect(mSegRectF, cornerRadius, cornerRadius, mFillPaint);
            } else if (part instanceof Point point) {
                final float pointWidth = 2 * pointRadius;
                float start = x - pointRadius;
@@ -275,10 +218,17 @@ public final class NotificationProgressDrawable extends Drawable {
        return pointOffset + pointRadius + segPointGap;
    }

    private static float getSegEndOffset(Part nextPart, float pointRadius, float segPointGap,
            float segSegGap, float endX, float totalWidth) {
    private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
            float segPointGap,
            float segSegGap, float endX, float totalWidth, boolean hasTrackerIcon) {
        if (nextPart == null) return 0F;
        if (!(nextPart instanceof Point)) return segSegGap;
        if (nextPart instanceof Segment nextSeg) {
            if (!seg.mFaded && nextSeg.mFaded) {
                // @see Segment#mFaded
                return hasTrackerIcon ? 0F : segSegGap * 4F;
            }
            return segSegGap;
        }

        final float pointWidth = 2 * pointRadius;
        final float pointOffset = (endX + pointRadius > totalWidth && totalWidth > pointWidth)
@@ -439,21 +389,17 @@ public final class NotificationProgressDrawable extends Drawable {
        // Extract the theme attributes, if any.
        state.mThemeAttrsSegments = a.extractThemeAttrs();

        final int width = a.getDimensionPixelSize(
                R.styleable.NotificationProgressDrawableSegments_width, state.mStrokeWidth);
        final float dashWidth = a.getDimension(
                R.styleable.NotificationProgressDrawableSegments_dashWidth, state.mStrokeDashWidth);

        state.mSegmentHeight = a.getDimension(
                R.styleable.NotificationProgressDrawableSegments_height, state.mSegmentHeight);
        state.mFadedSegmentHeight = a.getDimension(
                R.styleable.NotificationProgressDrawableSegments_fadedHeight,
                state.mFadedSegmentHeight);
        state.mSegmentCornerRadius = a.getDimension(
                R.styleable.NotificationProgressDrawableSegments_cornerRadius,
                state.mSegmentCornerRadius);
        final int color = a.getColor(R.styleable.NotificationProgressDrawableSegments_color,
                state.mStrokeColor);

        if (dashWidth != 0.0f) {
            final float dashGap = a.getDimension(
                    R.styleable.NotificationProgressDrawableSegments_dashGap, state.mStrokeDashGap);
            setStroke(width, color, dashWidth, dashGap);
        } else {
            setStroke(width, color);
        }
                state.mSegmentColor);
        setSegmentDefaultColor(color);
    }

    private void updatePointsFromTypedArray(TypedArray a) {
@@ -532,11 +478,24 @@ public final class NotificationProgressDrawable extends Drawable {
    /**
     * A segment is a part of the progress bar with non-zero length. For example, it can
     * represent a portion in a navigation journey with certain traffic condition.
     *
     */
    public static final class Segment implements Part {
        private final float mFraction;
        @ColorInt private final int mColor;
        private final boolean mDashed;
        /** Whether the segment is faded or not.
         * <p>
         *     <pre>
         *     When mFaded is set to true, a combination of the following is done to the segment:
         *       1. The drawing color is mColor with opacity updated to 15%.
         *       2. The segment-point gap is 2X the segment-point gap for non-faded segments.
         *       3. The gap between faded and non-faded segments is:
         *          4X the segment-segment gap, when there is no tracker icon
         *          0, when there is tracker icon
         *     </pre>
         * </p>
         */
        private final boolean mFaded;

        public Segment(float fraction) {
            this(fraction, Color.TRANSPARENT);
@@ -546,10 +505,10 @@ public final class NotificationProgressDrawable extends Drawable {
            this(fraction, color, false);
        }

        public Segment(float fraction, @ColorInt int color, boolean dashed) {
        public Segment(float fraction, @ColorInt int color, boolean faded) {
            mFraction = fraction;
            mColor = color;
            mDashed = dashed;
            mFaded = faded;
        }

        public float getFraction() {
@@ -560,14 +519,14 @@ public final class NotificationProgressDrawable extends Drawable {
            return this.mColor;
        }

        public boolean getDashed() {
            return this.mDashed;
        public boolean getFaded() {
            return this.mFaded;
        }

        @Override
        public String toString() {
            return "Segment(fraction=" + this.mFraction + ", color=" + this.mColor + ", dashed="
                    + this.mDashed + ')';
            return "Segment(fraction=" + this.mFraction + ", color=" + this.mColor + ", faded="
                    + this.mFaded + ')';
        }

        // Needed for unit tests
@@ -580,12 +539,12 @@ public final class NotificationProgressDrawable extends Drawable {
            Segment that = (Segment) other;
            if (Float.compare(this.mFraction, that.mFraction) != 0) return false;
            if (this.mColor != that.mColor) return false;
            return this.mDashed == that.mDashed;
            return this.mFaded == that.mFaded;
        }

        @Override
        public int hashCode() {
            return Objects.hash(mFraction, mColor, mDashed);
            return Objects.hash(mFraction, mColor, mFaded);
        }
    }

@@ -675,11 +634,11 @@ public final class NotificationProgressDrawable extends Drawable {
        int mChangingConfigurations;
        float mSegSegGap = 0.0f;
        float mSegPointGap = 0.0f;
        int mStrokeWidth = 0;
        int mStrokeColor;
        int mFadedStrokeColor;
        float mStrokeDashWidth = 0.0f;
        float mStrokeDashGap = 0.0f;
        float mSegmentHeight;
        float mFadedSegmentHeight;
        float mSegmentCornerRadius;
        int mSegmentColor;
        int mFadedSegmentColor;
        float mPointRadius;
        float mPointRectInset;
        float mPointRectCornerRadius;
@@ -699,11 +658,11 @@ public final class NotificationProgressDrawable extends Drawable {
            mChangingConfigurations = orig.mChangingConfigurations;
            mSegSegGap = orig.mSegSegGap;
            mSegPointGap = orig.mSegPointGap;
            mStrokeWidth = orig.mStrokeWidth;
            mStrokeColor = orig.mStrokeColor;
            mFadedStrokeColor = orig.mFadedStrokeColor;
            mStrokeDashWidth = orig.mStrokeDashWidth;
            mStrokeDashGap = orig.mStrokeDashGap;
            mSegmentHeight = orig.mSegmentHeight;
            mFadedSegmentHeight = orig.mFadedSegmentHeight;
            mSegmentCornerRadius = orig.mSegmentCornerRadius;
            mSegmentColor = orig.mSegmentColor;
            mFadedSegmentColor = orig.mFadedSegmentColor;
            mPointRadius = orig.mPointRadius;
            mPointRectInset = orig.mPointRectInset;
            mPointRectCornerRadius = orig.mPointRectCornerRadius;
@@ -721,17 +680,17 @@ public final class NotificationProgressDrawable extends Drawable {
        }

        private void applyDensityScaling(int sourceDensity, int targetDensity) {
            if (mStrokeWidth > 0) {
                mStrokeWidth = scaleFromDensity(
                        mStrokeWidth, sourceDensity, targetDensity, true);
            if (mSegmentHeight > 0) {
                mSegmentHeight = scaleFromDensity(
                        mSegmentHeight, sourceDensity, targetDensity);
            }
            if (mStrokeDashWidth > 0) {
                mStrokeDashWidth = scaleFromDensity(
                        mStrokeDashWidth, sourceDensity, targetDensity);
            if (mFadedSegmentHeight > 0) {
                mFadedSegmentHeight = scaleFromDensity(
                        mFadedSegmentHeight, sourceDensity, targetDensity);
            }
            if (mStrokeDashGap > 0) {
                mStrokeDashGap = scaleFromDensity(
                        mStrokeDashGap, sourceDensity, targetDensity);
            if (mSegmentCornerRadius > 0) {
                mSegmentCornerRadius = scaleFromDensity(
                        mSegmentCornerRadius, sourceDensity, targetDensity);
            }
            if (mPointRadius > 0) {
                mPointRadius = scaleFromDensity(
@@ -788,17 +747,9 @@ public final class NotificationProgressDrawable extends Drawable {
            }
        }

        public void setStroke(int width, int color, float dashWidth, float dashGap) {
            mStrokeWidth = width;
            mStrokeDashWidth = dashWidth;
            mStrokeDashGap = dashGap;

            setStrokeColor(color);
        }

        public void setStrokeColor(int color) {
            mStrokeColor = color;
            mFadedStrokeColor = getFadedColor(color);
        public void setSegmentColor(int color) {
            mSegmentColor = color;
            mFadedSegmentColor = getFadedColor(color);
        }

        public void setPointRectColor(int color) {
@@ -808,11 +759,14 @@ public final class NotificationProgressDrawable extends Drawable {
    }

    /**
     * Get a color with an opacity that's 50% of the input color.
     * Get a color with an opacity that's 25% of the input color.
     */
    @ColorInt
    static int getFadedColor(@ColorInt int color) {
        return Color.argb(Color.alpha(color) / 2, Color.red(color), Color.green(color),
        return Color.argb(
                (int) (Color.alpha(color) * 0.25f + 0.5f),
                Color.red(color),
                Color.green(color),
                Color.blue(color));
    }

@@ -836,15 +790,6 @@ public final class NotificationProgressDrawable extends Drawable {
    }

    private void updateLocalState() {
        final State state = mState;

        mStrokePaint.setStrokeWidth(state.mStrokeWidth);
        mDashedStrokePaint.setStrokeWidth(state.mStrokeWidth);

        if (state.mStrokeDashWidth != 0.0f) {
            final DashPathEffect e = new DashPathEffect(
                    new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0);
            mDashedStrokePaint.setPathEffect(e);
        }
        // NO-OP
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -24,9 +24,9 @@
            android:segPointGap="@dimen/notification_progress_segPoint_gap">
            <segments
                android:color="?attr/colorProgressBackgroundNormal"
                android:dashGap="@dimen/notification_progress_segments_dash_gap"
                android:dashWidth="@dimen/notification_progress_segments_dash_width"
                android:width="@dimen/notification_progress_segments_height" />
                android:height="@dimen/notification_progress_segments_height"
                android:fadedHeight="@dimen/notification_progress_segments_faded_height"
                android:cornerRadius="@dimen/notification_progress_segments_corner_radius"/>
            <points
                android:color="?attr/colorProgressBackgroundNormal"
                android:radius="@dimen/notification_progress_points_radius"
+8 −8
Original line number Diff line number Diff line
@@ -7748,14 +7748,14 @@
    <!-- Used to config the segments of a NotificationProgressDrawable. -->
    <!-- @hide internal use only -->
    <declare-styleable name="NotificationProgressDrawableSegments">
        <!-- Width of the stroke. -->
        <attr name="width" />
        <!-- Default color of the stroke. -->
        <!-- Height of the solid segments -->
        <attr name="height" />
        <!-- Height of the faded segments -->
        <attr name="fadedHeight" format="dimension"/>
        <!-- Corner radius of the segment rect. -->
        <attr name="cornerRadius" format="dimension" />
        <!-- Default color of the segment. -->
        <attr name="color" />
        <!-- Length of a dash in the stroke for the dashed segments. -->
        <attr name="dashWidth" />
        <!-- Gap between dashes in the stroke for the dashed segments. -->
        <attr name="dashGap" />
    </declare-styleable>
    <!-- Used to config the points of a NotificationProgressDrawable. -->
@@ -7766,7 +7766,7 @@
        <!-- Inset of the point icon or rect. -->
        <attr name="inset" />
        <!-- Corner radius of the point rect. -->
        <attr name="cornerRadius" format="dimension" />
        <attr name="cornerRadius"/>
        <!-- Default color of the point rect. -->
        <attr name="color" />
    </declare-styleable>
+5 −5
Original line number Diff line number Diff line
@@ -838,13 +838,13 @@
    <!-- The gap between segments in the notification progress bar -->
    <dimen name="notification_progress_segSeg_gap">2dp</dimen>
    <!-- The gap between a segment and a point in the notification progress bar -->
    <dimen name="notification_progress_segPoint_gap">8dp</dimen>
    <!-- The dash gap of the notification progress bar segments -->
    <dimen name="notification_progress_segments_dash_gap">8dp</dimen>
    <!-- The dash width of the notification progress bar segments -->
    <dimen name="notification_progress_segments_dash_width">3dp</dimen>
    <dimen name="notification_progress_segPoint_gap">4dp</dimen>
    <!-- The height of the notification progress bar segments -->
    <dimen name="notification_progress_segments_height">6dp</dimen>
    <!-- The height of the notification progress bar faded segments -->
    <dimen name="notification_progress_segments_faded_height">2dp</dimen>
    <!-- The corner radius of the notification progress bar segments -->
    <dimen name="notification_progress_segments_corner_radius">16dp</dimen>
    <!-- The radius of the notification progress bar points -->
    <dimen name="notification_progress_points_radius">6dp</dimen>
    <!-- The corner radius of the notification progress bar points drawn as rects -->
Loading