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

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

Merge "Implement NotificationProgressBar.processAndConvertToDrawableParts." into main

parents 038c956a 07256015
Loading
Loading
Loading
Loading
+222 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.internal.widget;

import android.app.Notification.ProgressStyle;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ProgressBar;
import android.widget.RemoteViews;

import androidx.annotation.ColorInt;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.NotificationProgressDrawable.Part;
import com.android.internal.widget.NotificationProgressDrawable.Point;
import com.android.internal.widget.NotificationProgressDrawable.Segment;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * NotificationProgressBar extends the capabilities of ProgressBar by adding functionalities to
 * represent Notification ProgressStyle progress, such as for ridesharing and navigation.
 */
@RemoteViews.RemoteView
public class NotificationProgressBar extends ProgressBar {
    public NotificationProgressBar(Context context) {
        this(context, null);
    }

    public NotificationProgressBar(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.progressBarStyle);
    }

    public NotificationProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public NotificationProgressBar(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    /**
     * Processes the ProgressStyle data and convert to list of {@code
     * NotificationProgressDrawable.Part}.
     */
    @VisibleForTesting
    public static List<Part> processAndConvertToDrawableParts(
            List<ProgressStyle.Segment> segments,
            List<ProgressStyle.Point> points,
            int progress,
            boolean isStyledByProgress
    ) {
        if (segments.isEmpty()) {
            throw new IllegalArgumentException("List of segments shouldn't be empty");
        }

        for (ProgressStyle.Segment segment : segments) {
            final int length = segment.getLength();
            if (length <= 0) {
                throw new IllegalArgumentException("Invalid segment length : " + length);
            }
        }

        final int progressMax = segments.stream().mapToInt(ProgressStyle.Segment::getLength).sum();

        if (progress < 0 || progress > progressMax) {
            throw new IllegalArgumentException("Invalid progress : " + progress);
        }
        for (ProgressStyle.Point point : points) {
            final int pos = point.getPosition();
            if (pos <= 0 || pos >= progressMax) {
                throw new IllegalArgumentException("Invalid Point position : " + pos);
            }
        }

        final Map<Integer, ProgressStyle.Segment> startToSegmentMap = generateStartToSegmentMap(
                segments);
        final Map<Integer, ProgressStyle.Point> positionToPointMap = generatePositionToPointMap(
                points);
        final SortedSet<Integer> sortedPos = generateSortedPositionSet(startToSegmentMap,
                positionToPointMap, progress, isStyledByProgress);

        final Map<Integer, ProgressStyle.Segment> startToSplitSegmentMap =
                splitSegmentsByPointsAndProgress(
                        startToSegmentMap, sortedPos, progressMax);

        return convertToDrawableParts(startToSplitSegmentMap, positionToPointMap, sortedPos,
                progress, progressMax,
                isStyledByProgress);
    }


    // Any segment with a point on it gets split by the point.
    // If isStyledByProgress is true, also split the segment with the progress value in its range.
    private static Map<Integer, ProgressStyle.Segment> splitSegmentsByPointsAndProgress(
            Map<Integer, ProgressStyle.Segment> startToSegmentMap,
            SortedSet<Integer> sortedPos,
            int progressMax) {
        int prevSegStart = 0;
        for (Integer pos : sortedPos) {
            if (pos == 0 || pos == progressMax) continue;
            if (startToSegmentMap.containsKey(pos)) {
                prevSegStart = pos;
                continue;
            }

            final ProgressStyle.Segment prevSeg = startToSegmentMap.get(prevSegStart);
            final ProgressStyle.Segment leftSeg = new ProgressStyle.Segment(
                    pos - prevSegStart).setColor(
                    prevSeg.getColor());
            final ProgressStyle.Segment rightSeg = new ProgressStyle.Segment(
                    prevSegStart + prevSeg.getLength() - pos).setColor(prevSeg.getColor());

            startToSegmentMap.put(prevSegStart, leftSeg);
            startToSegmentMap.put(pos, rightSeg);

            prevSegStart = pos;
        }

        return startToSegmentMap;
    }

    private static List<Part> convertToDrawableParts(
            Map<Integer, ProgressStyle.Segment> startToSegmentMap,
            Map<Integer, ProgressStyle.Point> positionToPointMap,
            SortedSet<Integer> sortedPos,
            int progress,
            int progressMax,
            boolean isStyledByProgress
    ) {
        List<Part> parts = new ArrayList<>();
        boolean styleRemainingParts = false;
        for (Integer pos : sortedPos) {
            if (positionToPointMap.containsKey(pos)) {
                final ProgressStyle.Point point = positionToPointMap.get(pos);
                final int color = maybeGetFadedColor(point.getColor(), styleRemainingParts);
                parts.add(new Point(null, color, styleRemainingParts));
            }
            // We want the Point at the current progress to be filled (not faded), but a Segment
            // starting at this progress to be faded.
            if (isStyledByProgress && !styleRemainingParts && pos == progress) {
                styleRemainingParts = true;
            }
            if (startToSegmentMap.containsKey(pos)) {
                final ProgressStyle.Segment seg = startToSegmentMap.get(pos);
                final int color = maybeGetFadedColor(seg.getColor(), styleRemainingParts);
                parts.add(new Segment(
                        (float) seg.getLength() / progressMax, color, styleRemainingParts));
            }
        }

        return parts;
    }

    @ColorInt
    private static int maybeGetFadedColor(@ColorInt int color, boolean fade) {
        if (!fade) return color;

        return NotificationProgressDrawable.getFadedColor(color);
    }

    private static Map<Integer, ProgressStyle.Segment> generateStartToSegmentMap(
            List<ProgressStyle.Segment> segments) {
        final Map<Integer, ProgressStyle.Segment> startToSegmentMap = new HashMap<>();

        int currentStart = 0;  // Initial start position is 0

        for (ProgressStyle.Segment segment : segments) {
            // Use the current start position as the key, and the segment as the value
            startToSegmentMap.put(currentStart, segment);

            // Update the start position for the next segment
            currentStart += segment.getLength();
        }

        return startToSegmentMap;
    }

    private static Map<Integer, ProgressStyle.Point> generatePositionToPointMap(
            List<ProgressStyle.Point> points) {
        final Map<Integer, ProgressStyle.Point> positionToPointMap = new HashMap<>();

        for (ProgressStyle.Point point : points) {
            positionToPointMap.put(point.getPosition(), point);
        }

        return positionToPointMap;
    }

    private static SortedSet<Integer> generateSortedPositionSet(
            Map<Integer, ProgressStyle.Segment> startToSegmentMap,
            Map<Integer, ProgressStyle.Point> positionToPointMap, int progress,
            boolean isStyledByProgress) {
        final SortedSet<Integer> sortedPos = new TreeSet<>(startToSegmentMap.keySet());
        sortedPos.addAll(positionToPointMap.keySet());
        if (isStyledByProgress) {
            sortedPos.add(progress);
        }

        return sortedPos;
    }
}
+88 −9
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;

/**
 * This is used by NotificationProgressBar for displaying a custom background. It composes of
@@ -126,7 +127,7 @@ public final class NotificationProgressDrawable extends Drawable {
     * @see #setStroke(int, int, float, float)
     */
    public void setStrokeDefaultColor(@ColorInt int color) {
        mState.mStrokeColor = color;
        mState.setStrokeColor(color);
    }

    /**
@@ -138,7 +139,7 @@ public final class NotificationProgressDrawable extends Drawable {
     * @see #mutate()
     */
    public void setPointRectDefaultColor(@ColorInt int color) {
        mState.mPointRectColor = color;
        mState.setPointRectColor(color);
    }

    private void setStrokeInternal(int width, float dashWidth, float dashGap) {
@@ -194,7 +195,7 @@ public final class NotificationProgressDrawable extends Drawable {
                mStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
                        : mState.mStrokeColor);
                mDashedStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
                        : mState.mStrokeColor);
                        : mState.mFadedStrokeColor);

                // Leave space for the rounded line cap which extends beyond start/end.
                final float capWidth = mStrokePaint.getStrokeWidth() / 2F;
@@ -220,7 +221,8 @@ public final class NotificationProgressDrawable extends Drawable {
                    mPointRectF.inset(inset, inset);

                    mFillPaint.setColor(point.mColor != Color.TRANSPARENT ? point.mColor
                            : mState.mPointRectColor);
                            : (point.mFaded ? mState.mFadedPointRectColor
                                    : mState.mPointRectColor));

                    canvas.drawRoundRect(mPointRectF, cornerRadius, cornerRadius, mFillPaint);
                }
@@ -424,8 +426,9 @@ public final class NotificationProgressDrawable extends Drawable {
        state.mPointRectCornerRadius = a.getDimension(
                R.styleable.NotificationProgressDrawablePoints_cornerRadius,
                state.mPointRectCornerRadius);
        state.mPointRectColor = a.getColor(R.styleable.NotificationProgressDrawablePoints_color,
        final int color = a.getColor(R.styleable.NotificationProgressDrawablePoints_color,
                state.mPointRectColor);
        setPointRectDefaultColor(color);
    }

    static int resolveDensity(@Nullable Resources r, int parentDensity) {
@@ -478,7 +481,6 @@ public final class NotificationProgressDrawable extends Drawable {
     * {@link Point} with zero length.
     */
    public interface Part {

    }

    /**
@@ -521,6 +523,24 @@ public final class NotificationProgressDrawable extends Drawable {
            return "Segment(fraction=" + this.mFraction + ", color=" + this.mColor + ", dashed="
                    + this.mDashed + ')';
        }

        // Needed for unit tests
        @Override
        public boolean equals(@Nullable Object other) {
            if (this == other) return true;

            if (other == null || getClass() != other.getClass()) return false;

            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;
        }

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

    /**
@@ -532,14 +552,21 @@ public final class NotificationProgressDrawable extends Drawable {
        @Nullable
        private final Drawable mIcon;
        @ColorInt private final int mColor;
        private final boolean mFaded;

        public Point(@Nullable Drawable icon) {
            this(icon, Color.TRANSPARENT);
            this(icon, Color.TRANSPARENT, false);
        }

        public Point(@Nullable Drawable icon, @ColorInt int color) {
            this(icon, color, false);

        }

        public Point(@Nullable Drawable icon, @ColorInt int color, boolean faded) {
            mIcon = icon;
            mColor = color;
            mFaded = faded;
        }

        @Nullable
@@ -547,9 +574,37 @@ public final class NotificationProgressDrawable extends Drawable {
            return this.mIcon;
        }

        public int getColor() {
            return this.mColor;
        }

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

        @Override
        public String toString() {
            return "Point(icon=" + this.mIcon + ", color=" + this.mColor + ')';
            return "Point(icon=" + this.mIcon + ", color=" + this.mColor + ", faded=" + this.mFaded
                    + ")";
        }

        // Needed for unit tests.
        @Override
        public boolean equals(@Nullable Object other) {
            if (this == other) return true;

            if (other == null || getClass() != other.getClass()) return false;

            Point that = (Point) other;

            if (!Objects.equals(this.mIcon, that.mIcon)) return false;
            if (this.mColor != that.mColor) return false;
            return this.mFaded == that.mFaded;
        }

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

@@ -576,12 +631,14 @@ public final class NotificationProgressDrawable extends Drawable {
        float mSegPointGap = 0.0f;
        int mStrokeWidth = 0;
        int mStrokeColor;
        int mFadedStrokeColor;
        float mStrokeDashWidth = 0.0f;
        float mStrokeDashGap = 0.0f;
        float mPointRadius;
        float mPointRectInset;
        float mPointRectCornerRadius;
        int mPointRectColor;
        int mFadedPointRectColor;

        int[] mThemeAttrs;
        int[] mThemeAttrsSegments;
@@ -595,6 +652,7 @@ public final class NotificationProgressDrawable extends Drawable {
        State(@NonNull State orig, @Nullable Resources res) {
            mChangingConfigurations = orig.mChangingConfigurations;
            mStrokeColor = orig.mStrokeColor;
            mFadedStrokeColor = orig.mFadedStrokeColor;
            mStrokeWidth = orig.mStrokeWidth;
            mStrokeDashWidth = orig.mStrokeDashWidth;
            mStrokeDashGap = orig.mStrokeDashGap;
@@ -602,6 +660,7 @@ public final class NotificationProgressDrawable extends Drawable {
            mPointRectInset = orig.mPointRectInset;
            mPointRectCornerRadius = orig.mPointRectCornerRadius;
            mPointRectColor = orig.mPointRectColor;
            mFadedPointRectColor = orig.mFadedPointRectColor;

            mThemeAttrs = orig.mThemeAttrs;
            mThemeAttrsSegments = orig.mThemeAttrsSegments;
@@ -683,10 +742,30 @@ public final class NotificationProgressDrawable extends Drawable {

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

            setStrokeColor(color);
        }

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

        public void setPointRectColor(int color) {
            mPointRectColor = color;
            mFadedPointRectColor = getFadedColor(color);
        }
    }

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

    @Override
+302 −0

File added.

Preview size limit exceeded, changes collapsed.