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

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

Merge "Implement fallbacks for when the drawable doesn't have enough width." into main

parents 514ace3c 5bb5a9cc
Loading
Loading
Loading
Loading
+25 −2
Original line number Diff line number Diff line
@@ -11854,7 +11854,7 @@ public class Notification implements Parcelable
                    // If segment limit is exceeded. All segments will be replaced
                    // with a single segment
                    boolean allSameColor = true;
                    int firstSegmentColor = segments.get(0).getColor();
                    int firstSegmentColor = segments.getFirst().getColor();
                    for (int i = 1; i < segments.size(); i++) {
                        if (segments.get(i).getColor() != firstSegmentColor) {
@@ -11887,8 +11887,31 @@ public class Notification implements Parcelable
                    }
                }
                // If the segments and points can't all fit inside the progress drawable, the
                // view will replace all segments with a single segment.
                final int segmentsFallbackColor;
                if (segments.size() <= 1) {
                    segmentsFallbackColor = NotificationProgressModel.INVALID_COLOR;
                } else {
                    boolean allSameColor = true;
                    int firstSegmentColor = segments.getFirst().getColor();
                    for (int i = 1; i < segments.size(); i++) {
                        if (segments.get(i).getColor() != firstSegmentColor) {
                            allSameColor = false;
                            break;
                        }
                    }
                    // If the segments are of the same color, the view can just use that color.
                    // In that case there is no need to send the fallback color.
                    segmentsFallbackColor = allSameColor ? NotificationProgressModel.INVALID_COLOR
                            : sanitizeProgressColor(Notification.COLOR_DEFAULT, backgroundColor,
                                    defaultProgressColor);
                }
                model = new NotificationProgressModel(segments, points,
                        Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress);
                        Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress,
                        segmentsFallbackColor);
            }
            return model;
        }
+146 −34
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import com.android.internal.widget.NotificationProgressDrawable.DrawablePoint;
import com.android.internal.widget.NotificationProgressDrawable.DrawableSegment;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -161,7 +162,7 @@ public final class NotificationProgressBar extends ProgressBar implements
            final int progress = mProgressModel.getProgress();
            final int progressMax = mProgressModel.getProgressMax();

            mParts = processAndConvertToViewParts(mProgressModel.getSegments(),
            mParts = processModelAndConvertToViewParts(mProgressModel.getSegments(),
                    mProgressModel.getPoints(),
                    progress,
                    progressMax);
@@ -439,23 +440,107 @@ public final class NotificationProgressBar extends ProgressBar implements
            return;
        }

        mProgressDrawableParts = processAndConvertToDrawableParts(
        final float segSegGap = mNotificationProgressDrawable.getSegSegGap();
        final float segPointGap = mNotificationProgressDrawable.getSegPointGap();
        final float pointRadius = mNotificationProgressDrawable.getPointRadius();
        mProgressDrawableParts = processPartsAndConvertToDrawableParts(
                mParts,
                width,
                mNotificationProgressDrawable.getSegSegGap(),
                mNotificationProgressDrawable.getSegPointGap(),
                mNotificationProgressDrawable.getPointRadius(),
                segSegGap,
                segPointGap,
                pointRadius,
                mHasTrackerIcon
        );
        Pair<List<DrawablePart>, Float> p = maybeStretchAndRescaleSegments(

        final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth();
        final float progressFraction = getProgressFraction();
        final boolean isStyledByProgress = mProgressModel.isStyledByProgress();
        final float progressGap =
                mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap();
        Pair<List<DrawablePart>, Float> p = null;
        try {
            p = maybeStretchAndRescaleSegments(
                    mParts,
                    mProgressDrawableParts,
                mNotificationProgressDrawable.getSegmentMinWidth(),
                mNotificationProgressDrawable.getPointRadius(),
                getProgressFraction(),
                    segmentMinWidth,
                    pointRadius,
                    progressFraction,
                    width,
                mProgressModel.isStyledByProgress(),
                mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap());
                    isStyledByProgress,
                    progressGap
            );
        } catch (NotEnoughWidthToFitAllPartsException ex) {
            Log.w(TAG, "Failed to stretch and rescale segments", ex);
        }

        List<ProgressStyle.Segment> fallbackSegments = null;
        if (p == null && mProgressModel.getSegments().size() > 1) {
            Log.w(TAG, "Falling back to single segment");
            try {
                fallbackSegments = List.of(new ProgressStyle.Segment(getMax()).setColor(
                        mProgressModel.getSegmentsFallbackColor()
                                == NotificationProgressModel.INVALID_COLOR
                                ? mProgressModel.getSegments().getFirst().getColor()
                                : mProgressModel.getSegmentsFallbackColor()));
                p = processModelAndConvertToFinalDrawableParts(
                        fallbackSegments,
                        mProgressModel.getPoints(),
                        mProgressModel.getProgress(),
                        getMax(),
                        width,
                        segSegGap,
                        segPointGap,
                        pointRadius,
                        mHasTrackerIcon,
                        segmentMinWidth,
                        isStyledByProgress
                );
            } catch (NotEnoughWidthToFitAllPartsException ex) {
                Log.w(TAG, "Failed to stretch and rescale segments with single segment fallback",
                        ex);
            }
        }

        if (p == null && !mProgressModel.getPoints().isEmpty()) {
            Log.w(TAG, "Falling back to single segment and no points");
            if (fallbackSegments == null) {
                fallbackSegments = List.of(new ProgressStyle.Segment(getMax()).setColor(
                        mProgressModel.getSegmentsFallbackColor()
                                == NotificationProgressModel.INVALID_COLOR
                                ? mProgressModel.getSegments().getFirst().getColor()
                                : mProgressModel.getSegmentsFallbackColor()));
            }
            try {
                p = processModelAndConvertToFinalDrawableParts(
                        fallbackSegments,
                        Collections.emptyList(),
                        mProgressModel.getProgress(),
                        getMax(),
                        width,
                        segSegGap,
                        segPointGap,
                        pointRadius,
                        mHasTrackerIcon,
                        segmentMinWidth,
                        isStyledByProgress
                );
            } catch (NotEnoughWidthToFitAllPartsException ex) {
                Log.w(TAG,
                        "Failed to stretch and rescale segments with single segments and no points",
                        ex);
            }
        }

        if (p == null) {
            Log.w(TAG, "Falling back to no stretching and rescaling");
            p = maybeSplitDrawableSegmentsByProgress(
                    mParts,
                    mProgressDrawableParts,
                    progressFraction,
                    width,
                    isStyledByProgress,
                    progressGap);
        }

        if (DEBUG) {
            Log.d(TAG, "Updating NotificationProgressDrawable parts");
@@ -502,7 +587,11 @@ public final class NotificationProgressBar extends ProgressBar implements
        int min = getMin();
        int max = getMax();
        int range = max - min;
        return range > 0 ? (getProgress() - min) / (float) range : 0;
        return getProgressFraction(range, (getProgress() - min));
    }

    private static float getProgressFraction(int progressMax, int progress) {
        return progressMax > 0 ? progress / (float) progressMax : 0;
    }

    /**
@@ -636,7 +725,7 @@ public final class NotificationProgressBar extends ProgressBar implements
     * Processes the ProgressStyle data and convert to a list of {@code Part}.
     */
    @VisibleForTesting
    public static List<Part> processAndConvertToViewParts(
    public static List<Part> processModelAndConvertToViewParts(
            List<ProgressStyle.Segment> segments,
            List<ProgressStyle.Point> points,
            int progress,
@@ -796,7 +885,7 @@ public final class NotificationProgressBar extends ProgressBar implements
     * Processes the list of {@code Part} and convert to a list of {@code DrawablePart}.
     */
    @VisibleForTesting
    public static List<DrawablePart> processAndConvertToDrawableParts(
    public static List<DrawablePart> processPartsAndConvertToDrawableParts(
            List<Part> parts,
            float totalWidth,
            float segSegGap,
@@ -823,7 +912,7 @@ public final class NotificationProgressBar extends ProgressBar implements
                // Retract the end position to account for the padding and a point immediately
                // after.
                final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
                        segSegGap, iPart == nParts - 2, totalWidth, hasTrackerIcon);
                        segSegGap, iPart == nParts - 2, hasTrackerIcon);
                final float end = x + segWidth - endOffset;

                drawableParts.add(new DrawableSegment(start, end, segment.mColor, segment.mFaded));
@@ -864,7 +953,7 @@ public final class NotificationProgressBar extends ProgressBar implements
    }

    private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
            float segPointGap, float segSegGap, boolean isSecondToLastPart, float totalWidth,
            float segPointGap, float segSegGap, boolean isSecondToLastPart,
            boolean hasTrackerIcon) {
        if (nextPart == null) return 0F;
        if (nextPart instanceof Segment nextSeg) {
@@ -894,7 +983,7 @@ public final class NotificationProgressBar extends ProgressBar implements
            float totalWidth,
            boolean isStyledByProgress,
            float progressGap
    ) {
    ) throws NotEnoughWidthToFitAllPartsException {
        final List<DrawableSegment> drawableSegments = drawableParts
                .stream()
                .filter(DrawableSegment.class::isInstance)
@@ -920,16 +1009,8 @@ public final class NotificationProgressBar extends ProgressBar implements
        }

        if (totalExcessWidth < 0) {
            // TODO: b/372908709 - throw an error so that the caller can catch and go to fallback
            //  option. (instead of return.)
            Log.w(TAG, "Not enough width to satisfy the minimum width for segments.");
            return maybeSplitDrawableSegmentsByProgress(
                    parts,
                    drawableParts,
                    progressFraction,
                    totalWidth,
                    isStyledByProgress,
                    progressGap);
            throw new NotEnoughWidthToFitAllPartsException(
                    "Not enough width to satisfy the minimum width for segments.");
        }

        final int nParts = drawableParts.size();
@@ -1003,8 +1084,7 @@ public final class NotificationProgressBar extends ProgressBar implements
        final int nParts = parts.size();
        for (int iPart = 0; iPart < nParts; iPart++) {
            final Part part = parts.get(iPart);
            if (!(part instanceof Segment)) continue;
            final Segment segment = (Segment) part;
            if (!(part instanceof Segment segment)) continue;
            if (startFraction == progressFraction) {
                iPartFirstSegmentToStyle = iPart;
                rescaledProgressX = segment.mStart;
@@ -1065,12 +1145,38 @@ public final class NotificationProgressBar extends ProgressBar implements
        return new Pair<>(splitDrawableParts, rescaledProgressX);
    }

    /**
     * Processes the ProgressStyle data and convert to a pair of:
     * - list of processed {@code DrawablePart}.
     * - location of progress on the stretched and rescaled progress bar.
     */
    @VisibleForTesting
    public static Pair<List<DrawablePart>, Float> processModelAndConvertToFinalDrawableParts(
            List<ProgressStyle.Segment> segments,
            List<ProgressStyle.Point> points,
            int progress,
            int progressMax,
            float totalWidth,
            float segSegGap,
            float segPointGap,
            float pointRadius,
            boolean hasTrackerIcon,
            float segmentMinWidth,
            boolean isStyledByProgress
    ) throws NotEnoughWidthToFitAllPartsException {
        List<Part> parts = processModelAndConvertToViewParts(segments, points, progress,
                progressMax);
        List<DrawablePart> drawableParts = processPartsAndConvertToDrawableParts(parts, totalWidth,
                segSegGap, segPointGap, pointRadius, hasTrackerIcon);
        return maybeStretchAndRescaleSegments(parts, drawableParts, segmentMinWidth, pointRadius,
                getProgressFraction(progressMax, progress), totalWidth, isStyledByProgress,
                hasTrackerIcon ? 0F : segSegGap);
    }

    /**
     * A part of the progress bar, which is either a {@link Segment} with non-zero length, or a
     * {@link Point} with zero length.
     */
    // TODO: b/372908709 - maybe this should be made private? Only test the final
    //  NotificationDrawable.Parts.
    public interface Part {
    }

@@ -1176,4 +1282,10 @@ public final class NotificationProgressBar extends ProgressBar implements
            return Objects.hash(mColor);
        }
    }

    public static class NotEnoughWidthToFitAllPartsException extends Exception {
        public NotEnoughWidthToFitAllPartsException(String message) {
            super(message);
        }
    }
}
+30 −10
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.internal.widget;


import android.annotation.ColorInt;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -45,16 +44,20 @@ import java.util.Objects;
 */
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
public final class NotificationProgressModel {
    private static final int INVALID_INDETERMINATE_COLOR = Color.TRANSPARENT;
    public static final int INVALID_COLOR = Color.TRANSPARENT;
    private static final String KEY_SEGMENTS = "segments";
    private static final String KEY_POINTS = "points";
    private static final String KEY_PROGRESS = "progress";
    private static final String KEY_IS_STYLED_BY_PROGRESS = "isStyledByProgress";
    private static final String KEY_SEGMENTS_FALLBACK_COLOR = "segmentsFallColor";
    private static final String KEY_INDETERMINATE_COLOR = "indeterminateColor";
    private final List<Segment> mSegments;
    private final List<Point> mPoints;
    private final int mProgress;
    private final boolean mIsStyledByProgress;
    @ColorInt
    private final int mSegmentsFallbackColor;

    @ColorInt
    private final int mIndeterminateColor;

@@ -62,7 +65,8 @@ public final class NotificationProgressModel {
            @NonNull List<Segment> segments,
            @NonNull List<Point> points,
            int progress,
            boolean isStyledByProgress
            boolean isStyledByProgress,
            @ColorInt int segmentsFallbackColor
    ) {
        Preconditions.checkArgument(progress >= 0);
        Preconditions.checkArgument(!segments.isEmpty());
@@ -70,17 +74,19 @@ public final class NotificationProgressModel {
        mPoints = points;
        mProgress = progress;
        mIsStyledByProgress = isStyledByProgress;
        mIndeterminateColor = INVALID_INDETERMINATE_COLOR;
        mSegmentsFallbackColor = segmentsFallbackColor;
        mIndeterminateColor = INVALID_COLOR;
    }

    public NotificationProgressModel(
            @ColorInt int indeterminateColor
    ) {
        Preconditions.checkArgument(indeterminateColor != INVALID_INDETERMINATE_COLOR);
        Preconditions.checkArgument(indeterminateColor != INVALID_COLOR);
        mSegments = Collections.emptyList();
        mPoints = Collections.emptyList();
        mProgress = 0;
        mIsStyledByProgress = false;
        mSegmentsFallbackColor = INVALID_COLOR;
        mIndeterminateColor = indeterminateColor;
    }

@@ -104,13 +110,18 @@ public final class NotificationProgressModel {
        return mIsStyledByProgress;
    }

    @ColorInt
    public int getSegmentsFallbackColor() {
        return mSegmentsFallbackColor;
    }

    @ColorInt
    public int getIndeterminateColor() {
        return mIndeterminateColor;
    }

    public boolean isIndeterminate() {
        return mIndeterminateColor != INVALID_INDETERMINATE_COLOR;
        return mIndeterminateColor != INVALID_COLOR;
    }

    /**
@@ -119,7 +130,7 @@ public final class NotificationProgressModel {
    @NonNull
    public Bundle toBundle() {
        final Bundle bundle = new Bundle();
        if (mIndeterminateColor != INVALID_INDETERMINATE_COLOR) {
        if (mIndeterminateColor != INVALID_COLOR) {
            bundle.putInt(KEY_INDETERMINATE_COLOR, mIndeterminateColor);
        } else {
            bundle.putParcelableList(KEY_SEGMENTS,
@@ -128,6 +139,9 @@ public final class NotificationProgressModel {
                    Notification.ProgressStyle.getProgressPointsAsBundleList(mPoints));
            bundle.putInt(KEY_PROGRESS, mProgress);
            bundle.putBoolean(KEY_IS_STYLED_BY_PROGRESS, mIsStyledByProgress);
            if (mSegmentsFallbackColor != INVALID_COLOR) {
                bundle.putInt(KEY_SEGMENTS_FALLBACK_COLOR, mSegmentsFallbackColor);
            }
        }
        return bundle;
    }
@@ -138,8 +152,8 @@ public final class NotificationProgressModel {
    @NonNull
    public static NotificationProgressModel fromBundle(@NonNull Bundle bundle) {
        final int indeterminateColor = bundle.getInt(KEY_INDETERMINATE_COLOR,
                INVALID_INDETERMINATE_COLOR);
        if (indeterminateColor != INVALID_INDETERMINATE_COLOR) {
                INVALID_COLOR);
        if (indeterminateColor != INVALID_COLOR) {
            return new NotificationProgressModel(indeterminateColor);
        } else {
            final List<Segment> segments =
@@ -150,7 +164,10 @@ public final class NotificationProgressModel {
                            bundle.getParcelableArrayList(KEY_POINTS, Bundle.class));
            final int progress = bundle.getInt(KEY_PROGRESS);
            final boolean isStyledByProgress = bundle.getBoolean(KEY_IS_STYLED_BY_PROGRESS);
            return new NotificationProgressModel(segments, points, progress, isStyledByProgress);
            final int segmentsFallbackColor = bundle.getInt(KEY_SEGMENTS_FALLBACK_COLOR,
                    INVALID_COLOR);
            return new NotificationProgressModel(segments, points, progress, isStyledByProgress,
                    segmentsFallbackColor);
        }
    }

@@ -161,6 +178,7 @@ public final class NotificationProgressModel {
                + ", mPoints=" + mPoints
                + ", mProgress=" + mProgress
                + ", mIsStyledByProgress=" + mIsStyledByProgress
                + ", mSegmentsFallbackColor=" + mSegmentsFallbackColor
                + ", mIndeterminateColor=" + mIndeterminateColor + "}";
    }

@@ -171,6 +189,7 @@ public final class NotificationProgressModel {
        final NotificationProgressModel that = (NotificationProgressModel) o;
        return mProgress == that.mProgress
                && mIsStyledByProgress == that.mIsStyledByProgress
                && mSegmentsFallbackColor == that.mSegmentsFallbackColor
                && mIndeterminateColor == that.mIndeterminateColor
                && Objects.equals(mSegments, that.mSegments)
                && Objects.equals(mPoints, that.mPoints);
@@ -182,6 +201,7 @@ public final class NotificationProgressModel {
                mPoints,
                mProgress,
                mIsStyledByProgress,
                mSegmentsFallbackColor,
                mIndeterminateColor);
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -7585,7 +7585,7 @@
    <!-- Used to config the segments of a NotificationProgressDrawable. -->
    <!-- @hide internal use only -->
    <declare-styleable name="NotificationProgressDrawableSegments">
        <!-- TODO: b/372908709 - maybe move this to NotificationProgressBar, because that's the only
        <!-- TODO: b/390196782 - maybe move this to NotificationProgressBar, because that's the only
         place this is used actually. Same for NotificationProgressDrawable.segSegGap/segPointGap
         above. -->
        <!-- Minimum required drawing width. The drawing width refers to the width after
+312 −68

File changed.

Preview size limit exceeded, changes collapsed.

Loading