diff options
7 files changed, 581 insertions, 97 deletions
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java index d8001d82b3e9..f0b54937546b 100644 --- a/core/java/com/android/internal/widget/NotificationProgressBar.java +++ b/core/java/com/android/internal/widget/NotificationProgressBar.java @@ -32,6 +32,7 @@ import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; +import android.util.Pair; import android.view.RemotableViewMethod; import android.widget.ProgressBar; import android.widget.RemoteViews; @@ -67,6 +68,7 @@ public final class NotificationProgressBar extends ProgressBar implements @Nullable private List<Part> mParts = null; + // List of drawable parts before segment splitting by process. @Nullable private List<NotificationProgressDrawable.Part> mProgressDrawableParts = null; @@ -81,7 +83,13 @@ public final class NotificationProgressBar extends ProgressBar implements private final Matrix mMatrix = new Matrix(); private Matrix mTrackerDrawMatrix = null; - private float mScale = 0; + private float mProgressFraction = 0; + /** + * The location of progress on the stretched and rescaled progress bar, in fraction. Used for + * calculating the tracker position. If stretching and rescaling is not needed, == + * mProgressFraction. + */ + private float mAdjustedProgressFraction = 0; /** Indicates whether mTrackerPos needs to be recalculated before the tracker is drawn. */ private boolean mTrackerPosIsDirty = false; @@ -143,6 +151,10 @@ public final class NotificationProgressBar extends ProgressBar implements final int indeterminateColor = mProgressModel.getIndeterminateColor(); setIndeterminateTintList(ColorStateList.valueOf(indeterminateColor)); } else { + // TODO: b/372908709 - maybe don't rerun the entire calculation every time the + // progress model is updated? For example, if the segments and parts aren't changed, + // there is no need to call `processAndConvertToViewParts` again. + final int progress = mProgressModel.getProgress(); final int progressMax = mProgressModel.getProgressMax(); @@ -342,10 +354,10 @@ public final class NotificationProgressBar extends ProgressBar implements } private void onMaybeVisualProgressChanged() { - float scale = getScale(); - if (mScale == scale) return; + float progressFraction = getProgressFraction(); + if (mProgressFraction == progressFraction) return; - mScale = scale; + mProgressFraction = progressFraction; mTrackerPosIsDirty = true; invalidate(); } @@ -427,12 +439,21 @@ public final class NotificationProgressBar extends ProgressBar implements mNotificationProgressDrawable.getSegSegGap(), mNotificationProgressDrawable.getSegPointGap(), mNotificationProgressDrawable.getPointRadius(), - mHasTrackerIcon, - getScale() * width, - mProgressModel.isStyledByProgress()); + mHasTrackerIcon + ); + Pair<List<NotificationProgressDrawable.Part>, Float> p = maybeStretchAndRescaleSegments( + mParts, + mProgressDrawableParts, + mNotificationProgressDrawable.getSegmentMinWidth(), + mNotificationProgressDrawable.getPointRadius(), + getProgressFraction(), + width, + mProgressModel.isStyledByProgress(), + mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap()); Log.d(TAG, "Updating NotificationProgressDrawable parts"); - mNotificationProgressDrawable.setParts(mProgressDrawableParts); + mNotificationProgressDrawable.setParts(p.first); + mAdjustedProgressFraction = p.second / width; } private void updateTrackerAndBarPos(int w, int h) { @@ -465,11 +486,11 @@ public final class NotificationProgressBar extends ProgressBar implements } if (tracker != null) { - setTrackerPos(w, tracker, mScale, trackerOffsetY); + setTrackerPos(w, tracker, mAdjustedProgressFraction, trackerOffsetY); } } - private float getScale() { + private float getProgressFraction() { int min = getMin(); int max = getMax(); int range = max - min; @@ -481,17 +502,17 @@ public final class NotificationProgressBar extends ProgressBar implements * * @param w Width of the view, including padding * @param tracker Drawable used for the tracker - * @param scale Current progress between 0 and 1 + * @param progressFraction Current progress between 0 and 1 * @param offsetY Vertical offset for centering. If set to * {@link Integer#MIN_VALUE}, the current offset will be used. */ - private void setTrackerPos(int w, Drawable tracker, float scale, int offsetY) { + private void setTrackerPos(int w, Drawable tracker, float progressFraction, int offsetY) { int available = w - mPaddingLeft - mPaddingRight; final int trackerWidth = tracker.getIntrinsicWidth(); final int trackerHeight = tracker.getIntrinsicHeight(); available -= ((mTrackerHeight <= 0) ? trackerWidth : mTrackerWidth); - final int trackerPos = (int) (scale * available + 0.5f); + final int trackerPos = (int) (progressFraction * available + 0.5f); final int top, bottom; if (offsetY == Integer.MIN_VALUE) { @@ -545,7 +566,7 @@ public final class NotificationProgressBar extends ProgressBar implements if (mTracker == null) return; if (mTrackerPosIsDirty) { - setTrackerPos(getWidth(), mTracker, mScale, Integer.MIN_VALUE); + setTrackerPos(getWidth(), mTracker, mAdjustedProgressFraction, Integer.MIN_VALUE); } final int saveCount = canvas.save(); @@ -594,7 +615,7 @@ public final class NotificationProgressBar extends ProgressBar implements final Drawable tracker = mTracker; if (tracker != null) { - setTrackerPos(getWidth(), tracker, mScale, Integer.MIN_VALUE); + setTrackerPos(getWidth(), tracker, mAdjustedProgressFraction, Integer.MIN_VALUE); // Since we draw translated, the drawable's bounds that it signals // for invalidation won't be the actual bounds we want invalidated, @@ -776,9 +797,7 @@ public final class NotificationProgressBar extends ProgressBar implements float segSegGap, float segPointGap, float pointRadius, - boolean hasTrackerIcon, - float progressX, - boolean isStyledByProgress + boolean hasTrackerIcon ) { List<NotificationProgressDrawable.Part> drawableParts = new ArrayList<>(); @@ -826,12 +845,7 @@ public final class NotificationProgressBar extends ProgressBar implements } } - return maybeSplitDrawableSegmentsByProgress( - isStyledByProgress, - parts, - drawableParts, - progressX, - hasTrackerIcon ? 0F : segSegGap); + return drawableParts; } private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap, @@ -859,33 +873,148 @@ public final class NotificationProgressBar extends ProgressBar implements return segPointGap + pointRadius + pointOffset; } - // If isStyledByProgress is true, also split the segment with the progress value in its range. - private static List<NotificationProgressDrawable.Part> maybeSplitDrawableSegmentsByProgress( + /** + * Processes the list of {@code NotificationProgressBar.Part} data and convert to a pair of: + * - list of {@code NotificationProgressDrawable.Part}. + * - location of progress on the stretched and rescaled progress bar. + */ + @VisibleForTesting + public static Pair<List<NotificationProgressDrawable.Part>, Float> + maybeStretchAndRescaleSegments( + List<Part> parts, + List<NotificationProgressDrawable.Part> drawableParts, + float segmentMinWidth, + float pointRadius, + float progressFraction, + float totalWidth, boolean isStyledByProgress, + float progressGap + ) { + final List<NotificationProgressDrawable.Segment> drawableSegments = drawableParts + .stream() + .filter(NotificationProgressDrawable.Segment.class::isInstance) + .map(NotificationProgressDrawable.Segment.class::cast) + .toList(); + float totalExcessWidth = 0; + float totalPositiveExcessWidth = 0; + for (NotificationProgressDrawable.Segment drawableSegment : drawableSegments) { + final float excessWidth = drawableSegment.getWidth() - segmentMinWidth; + totalExcessWidth += excessWidth; + if (excessWidth > 0) totalPositiveExcessWidth += excessWidth; + } + + // All drawable segments are above minimum width. No need to stretch and rescale. + if (totalExcessWidth == totalPositiveExcessWidth) { + return maybeSplitDrawableSegmentsByProgress( + parts, + drawableParts, + progressFraction, + totalWidth, + isStyledByProgress, + progressGap); + } + + 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); + } + + final int nParts = drawableParts.size(); + float startOffset = 0; + for (int iPart = 0; iPart < nParts; iPart++) { + final NotificationProgressDrawable.Part drawablePart = drawableParts.get(iPart); + if (drawablePart instanceof NotificationProgressDrawable.Segment drawableSegment) { + final float origDrawableSegmentWidth = drawableSegment.getWidth(); + + float drawableSegmentWidth = segmentMinWidth; + // Allocate the totalExcessWidth to the segments above minimum, proportionally to + // their initial excessWidth. + if (origDrawableSegmentWidth > segmentMinWidth) { + drawableSegmentWidth += + totalExcessWidth * (origDrawableSegmentWidth - segmentMinWidth) + / totalPositiveExcessWidth; + } + + final float widthDiff = drawableSegmentWidth - drawableSegment.getWidth(); + + // Adjust drawable segments to new widths + drawableSegment.setStart(drawableSegment.getStart() + startOffset); + drawableSegment.setEnd( + drawableSegment.getStart() + origDrawableSegmentWidth + widthDiff); + + // Also adjust view segments to new width. (For view segments, only start is + // needed?) + // Check that segments and drawableSegments are of the same size? + final Segment segment = (Segment) parts.get(iPart); + final float origSegmentWidth = segment.getWidth(); + segment.mStart = segment.mStart + startOffset; + segment.mEnd = segment.mStart + origSegmentWidth + widthDiff; + + // Increase startOffset for the subsequent segments. + startOffset += widthDiff; + } else if (drawablePart instanceof NotificationProgressDrawable.Point drawablePoint) { + drawablePoint.setStart(drawablePoint.getStart() + startOffset); + drawablePoint.setEnd(drawablePoint.getStart() + 2 * pointRadius); + } + } + + return maybeSplitDrawableSegmentsByProgress( + parts, + drawableParts, + progressFraction, + totalWidth, + isStyledByProgress, + progressGap); + } + + // Find the location of progress on the stretched and rescaled progress bar. + // If isStyledByProgress is true, also split the segment with the progress value in its range. + private static Pair<List<NotificationProgressDrawable.Part>, Float> + maybeSplitDrawableSegmentsByProgress( // Needed to get the original segment start and end positions in pixels. List<Part> parts, List<NotificationProgressDrawable.Part> drawableParts, - float progressX, + float progressFraction, + float totalWidth, + boolean isStyledByProgress, float progressGap ) { - if (!isStyledByProgress) return drawableParts; + if (progressFraction == 1) return new Pair<>(drawableParts, totalWidth); int iPartFirstSegmentToStyle = -1; int iPartSegmentToSplit = -1; + float rescaledProgressX = 0; + float startFraction = 0; 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 (segment.mStart == progressX) { + if (startFraction == progressFraction) { iPartFirstSegmentToStyle = iPart; + rescaledProgressX = segment.mStart; break; - } else if (segment.mStart < progressX && progressX < segment.mEnd) { + } else if (startFraction < progressFraction + && progressFraction < startFraction + segment.mFraction) { iPartSegmentToSplit = iPart; + rescaledProgressX = + segment.mStart + (progressFraction - startFraction) / segment.mFraction + * segment.getWidth(); break; } + startFraction += segment.mFraction; } + if (!isStyledByProgress) return new Pair<>(drawableParts, rescaledProgressX); + List<NotificationProgressDrawable.Part> splitDrawableParts = new ArrayList<>(); boolean styleRemainingParts = false; for (int iPart = 0; iPart < nParts; iPart++) { @@ -899,22 +1028,23 @@ public final class NotificationProgressBar extends ProgressBar implements if (iPart == iPartFirstSegmentToStyle) styleRemainingParts = true; if (drawablePart instanceof NotificationProgressDrawable.Segment drawableSegment) { if (iPart == iPartSegmentToSplit) { - if (progressX <= drawableSegment.getStart()) { + if (rescaledProgressX <= drawableSegment.getStart()) { styleRemainingParts = true; final int color = maybeGetFadedColor(drawableSegment.getColor(), true); splitDrawableParts.add( new NotificationProgressDrawable.Segment(drawableSegment.getStart(), drawableSegment.getEnd(), color, true)); - } else if (drawableSegment.getStart() < progressX - && progressX < drawableSegment.getEnd()) { + } else if (drawableSegment.getStart() < rescaledProgressX + && rescaledProgressX < drawableSegment.getEnd()) { splitDrawableParts.add( new NotificationProgressDrawable.Segment(drawableSegment.getStart(), - progressX - progressGap, + rescaledProgressX - progressGap, drawableSegment.getColor())); final int color = maybeGetFadedColor(drawableSegment.getColor(), true); - splitDrawableParts.add(new NotificationProgressDrawable.Segment(progressX, - drawableSegment.getEnd(), color, true)); + splitDrawableParts.add( + new NotificationProgressDrawable.Segment(rescaledProgressX, + drawableSegment.getEnd(), color, true)); styleRemainingParts = true; } else { splitDrawableParts.add( @@ -934,7 +1064,7 @@ public final class NotificationProgressBar extends ProgressBar implements } } - return splitDrawableParts; + return new Pair<>(splitDrawableParts, rescaledProgressX); } /** @@ -984,6 +1114,11 @@ public final class NotificationProgressBar extends ProgressBar implements mFaded = faded; } + /** Returns the calculated drawing width of the part */ + public float getWidth() { + return mEnd - mStart; + } + @Override public String toString() { return "Segment(fraction=" + this.mFraction + ", color=" + this.mColor + ", faded=" diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java index 3327b942c8e3..ef0a5d5cdec2 100644 --- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java +++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java @@ -95,6 +95,13 @@ public final class NotificationProgressDrawable extends Drawable { } /** + * Returns the gap between a segment and a point. + */ + public float getSegmentMinWidth() { + return mState.mSegmentMinWidth; + } + + /** * Returns the radius for the points. */ public float getPointRadius() { @@ -322,6 +329,8 @@ public final class NotificationProgressDrawable extends Drawable { // Extract the theme attributes, if any. state.mThemeAttrsSegments = a.extractThemeAttrs(); + state.mSegmentMinWidth = a.getDimension( + R.styleable.NotificationProgressDrawableSegments_minWidth, state.mSegmentMinWidth); state.mSegmentHeight = a.getDimension( R.styleable.NotificationProgressDrawableSegments_height, state.mSegmentHeight); state.mFadedSegmentHeight = a.getDimension( @@ -408,10 +417,12 @@ public final class NotificationProgressDrawable extends Drawable { * varying drawing width, or a {@link Point} with zero length and fixed size for drawing. */ public abstract static class Part { - /** Start position for drawing (in pixels). */ - protected final float mStart; - /** End position for drawing (in pixels). */ - protected final float mEnd; + // TODO: b/372908709 - maybe rename start/end to left/right, to be consistent with the + // bounds rect. + /** Start position for drawing (in pixels) */ + protected float mStart; + /** End position for drawing (in pixels) */ + protected float mEnd; /** Drawing color. */ @ColorInt protected final int mColor; @@ -425,10 +436,23 @@ public final class NotificationProgressDrawable extends Drawable { return this.mStart; } + public void setStart(float start) { + mStart = start; + } + public float getEnd() { return this.mEnd; } + public void setEnd(float end) { + mEnd = end; + } + + /** Returns the calculated drawing width of the part */ + public float getWidth() { + return mEnd - mStart; + } + public int getColor() { return this.mColor; } @@ -537,6 +561,7 @@ public final class NotificationProgressDrawable extends Drawable { int mChangingConfigurations; float mSegSegGap = 0.0f; float mSegPointGap = 0.0f; + float mSegmentMinWidth = 0.0f; float mSegmentHeight; float mFadedSegmentHeight; float mSegmentCornerRadius; @@ -558,6 +583,7 @@ public final class NotificationProgressDrawable extends Drawable { mChangingConfigurations = orig.mChangingConfigurations; mSegSegGap = orig.mSegSegGap; mSegPointGap = orig.mSegPointGap; + mSegmentMinWidth = orig.mSegmentMinWidth; mSegmentHeight = orig.mSegmentHeight; mFadedSegmentHeight = orig.mFadedSegmentHeight; mSegmentCornerRadius = orig.mSegmentCornerRadius; @@ -576,6 +602,18 @@ public final class NotificationProgressDrawable extends Drawable { } private void applyDensityScaling(int sourceDensity, int targetDensity) { + if (mSegSegGap > 0) { + mSegSegGap = scaleFromDensity( + mSegSegGap, sourceDensity, targetDensity); + } + if (mSegPointGap > 0) { + mSegPointGap = scaleFromDensity( + mSegPointGap, sourceDensity, targetDensity); + } + if (mSegmentMinWidth > 0) { + mSegmentMinWidth = scaleFromDensity( + mSegmentMinWidth, sourceDensity, targetDensity); + } if (mSegmentHeight > 0) { mSegmentHeight = scaleFromDensity( mSegmentHeight, sourceDensity, targetDensity); diff --git a/core/res/res/drawable/notification_progress.xml b/core/res/res/drawable/notification_progress.xml index 5d272fb00e34..ff5450ee106f 100644 --- a/core/res/res/drawable/notification_progress.xml +++ b/core/res/res/drawable/notification_progress.xml @@ -24,6 +24,7 @@ android:segPointGap="@dimen/notification_progress_segPoint_gap"> <segments android:color="?attr/colorProgressBackgroundNormal" + android:minWidth="@dimen/notification_progress_segments_min_width" android:height="@dimen/notification_progress_segments_height" android:fadedHeight="@dimen/notification_progress_segments_faded_height" android:cornerRadius="@dimen/notification_progress_segments_corner_radius"/> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 02207f8c7ea4..8372aecf0d27 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -7584,6 +7584,13 @@ <!-- 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 + place this is used actually. Same for NotificationProgressDrawable.segSegGap/segPointGap + above. --> + <!-- Minimum required drawing width. The drawing width refers to the width after + the original segments have been adjusted for the neighboring Points and gaps. This is + enforced by stretching the segments that are too short. --> + <attr name="minWidth" format="dimension" /> <!-- Height of the solid segments. --> <attr name="height" /> <!-- Height of the faded segments. --> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 2adb79118ed9..593618b086c5 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -888,6 +888,8 @@ <dimen name="notification_progress_segSeg_gap">4dp</dimen> <!-- The gap between a segment and a point in the notification progress bar --> <dimen name="notification_progress_segPoint_gap">4dp</dimen> + <!-- The minimum required drawing width of the notification progress bar segments --> + <dimen name="notification_progress_segments_min_width">16dp</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 --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a18f923d625b..3e8e7f6fbc6e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3939,6 +3939,7 @@ <java-symbol type="dimen" name="notification_progress_tracker_height" /> <java-symbol type="dimen" name="notification_progress_segSeg_gap" /> <java-symbol type="dimen" name="notification_progress_segPoint_gap" /> + <java-symbol type="dimen" name="notification_progress_segments_min_width" /> <java-symbol type="dimen" name="notification_progress_segments_height" /> <java-symbol type="dimen" name="notification_progress_segments_faded_height" /> <java-symbol type="dimen" name="notification_progress_segments_corner_radius" /> diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java index 6a74b4451308..f105ec305eab 100644 --- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.app.Notification.ProgressStyle; import android.graphics.Color; +import android.util.Pair; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -109,9 +110,6 @@ public class NotificationProgressBarTest { List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points, progress, progressMax); - // Colors with 40% opacity - int fadedRed = 0x66FF0000; - List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED))); assertThat(parts).isEqualTo(expectedParts); @@ -121,17 +119,32 @@ public class NotificationProgressBarTest { float segPointGap = 4; float pointRadius = 6; boolean hasTrackerIcon = true; - float progressX = 0; - boolean isStyledByProgress = true; + List<NotificationProgressDrawable.Part> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth, - segSegGap, segPointGap, pointRadius, hasTrackerIcon, progressX, - isStyledByProgress); + segSegGap, segPointGap, pointRadius, hasTrackerIcon + ); List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>( - List.of(new NotificationProgressDrawable.Segment(0, 300, fadedRed, true))); + List.of(new NotificationProgressDrawable.Segment(0, 300, Color.RED))); assertThat(drawableParts).isEqualTo(expectedDrawableParts); + + float segmentMinWidth = 16; + boolean isStyledByProgress = true; + + Pair<List<NotificationProgressDrawable.Part>, Float> p = + NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts, + segmentMinWidth, pointRadius, (float) progress / progressMax, 300, + isStyledByProgress, hasTrackerIcon ? 0 : segSegGap); + + // Colors with 40% opacity + int fadedRed = 0x66FF0000; + expectedDrawableParts = new ArrayList<>( + List.of(new NotificationProgressDrawable.Segment(0, 300, fadedRed, true))); + + assertThat(p.second).isEqualTo(0); + assertThat(p.first).isEqualTo(expectedDrawableParts); } @Test @@ -154,17 +167,27 @@ public class NotificationProgressBarTest { float segPointGap = 4; float pointRadius = 6; boolean hasTrackerIcon = true; - float progressX = 300; - boolean isStyledByProgress = true; + List<NotificationProgressDrawable.Part> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth, - segSegGap, segPointGap, pointRadius, hasTrackerIcon, progressX, - isStyledByProgress); + segSegGap, segPointGap, pointRadius, hasTrackerIcon + ); List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>( List.of(new NotificationProgressDrawable.Segment(0, 300, Color.RED))); assertThat(drawableParts).isEqualTo(expectedDrawableParts); + + float segmentMinWidth = 16; + boolean isStyledByProgress = true; + + Pair<List<NotificationProgressDrawable.Part>, Float> p = + NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts, + segmentMinWidth, pointRadius, (float) progress / progressMax, 300, + isStyledByProgress, hasTrackerIcon ? 0 : segSegGap); + + assertThat(p.second).isEqualTo(300); + assertThat(p.first).isEqualTo(expectedDrawableParts); } @Test(expected = IllegalArgumentException.class) @@ -217,9 +240,6 @@ public class NotificationProgressBarTest { List<Part> parts = NotificationProgressBar.processAndConvertToViewParts( segments, points, progress, progressMax); - // Colors with 40% opacity - int fadedGreen = 0x6600FF00; - List<Part> expectedParts = new ArrayList<>(List.of( new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN))); @@ -231,19 +251,34 @@ public class NotificationProgressBarTest { float segPointGap = 4; float pointRadius = 6; boolean hasTrackerIcon = true; - float progressX = 180; - boolean isStyledByProgress = true; + List<NotificationProgressDrawable.Part> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth, - segSegGap, segPointGap, pointRadius, hasTrackerIcon, progressX, - isStyledByProgress); + segSegGap, segPointGap, pointRadius, hasTrackerIcon + ); List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>( List.of(new NotificationProgressDrawable.Segment(0, 146, Color.RED), + new NotificationProgressDrawable.Segment(150, 300, Color.GREEN))); + + assertThat(drawableParts).isEqualTo(expectedDrawableParts); + + float segmentMinWidth = 16; + boolean isStyledByProgress = true; + Pair<List<NotificationProgressDrawable.Part>, Float> p = + NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts, + segmentMinWidth, pointRadius, (float) progress / progressMax, 300, + isStyledByProgress, hasTrackerIcon ? 0 : segSegGap); + + // Colors with 40% opacity + int fadedGreen = 0x6600FF00; + expectedDrawableParts = new ArrayList<>( + List.of(new NotificationProgressDrawable.Segment(0, 146, Color.RED), new NotificationProgressDrawable.Segment(150, 180, Color.GREEN), new NotificationProgressDrawable.Segment(180, 300, fadedGreen, true))); - assertThat(drawableParts).isEqualTo(expectedDrawableParts); + assertThat(p.second).isEqualTo(180); + assertThat(p.first).isEqualTo(expectedDrawableParts); } @Test @@ -257,9 +292,6 @@ public class NotificationProgressBarTest { List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points, progress, progressMax); - // Colors with 40% opacity - int fadedGreen = 0x6600FF00; - List<Part> expectedParts = new ArrayList<>(List.of( new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN))); @@ -271,19 +303,34 @@ public class NotificationProgressBarTest { float segPointGap = 4; float pointRadius = 6; boolean hasTrackerIcon = false; - float progressX = 180; - boolean isStyledByProgress = true; + List<NotificationProgressDrawable.Part> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth, - segSegGap, segPointGap, pointRadius, hasTrackerIcon, progressX, - isStyledByProgress); + segSegGap, segPointGap, pointRadius, hasTrackerIcon + ); List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>( List.of(new NotificationProgressDrawable.Segment(0, 146, Color.RED), + new NotificationProgressDrawable.Segment(150, 300, Color.GREEN))); + + assertThat(drawableParts).isEqualTo(expectedDrawableParts); + + float segmentMinWidth = 16; + boolean isStyledByProgress = true; + Pair<List<NotificationProgressDrawable.Part>, Float> p = + NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts, + segmentMinWidth, pointRadius, (float) progress / progressMax, 300, + isStyledByProgress, hasTrackerIcon ? 0 : segSegGap); + + // Colors with 40% opacity + int fadedGreen = 0x6600FF00; + expectedDrawableParts = new ArrayList<>( + List.of(new NotificationProgressDrawable.Segment(0, 146, Color.RED), new NotificationProgressDrawable.Segment(150, 176, Color.GREEN), new NotificationProgressDrawable.Segment(180, 300, fadedGreen, true))); - assertThat(drawableParts).isEqualTo(expectedDrawableParts); + assertThat(p.second).isEqualTo(180); + assertThat(p.first).isEqualTo(expectedDrawableParts); } @Test @@ -298,9 +345,8 @@ public class NotificationProgressBarTest { int progress = 60; int progressMax = 100; - // Colors with 40% opacity - int fadedBlue = 0x660000FF; - int fadedYellow = 0x66FFFF00; + List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points, + progress, progressMax); List<Part> expectedParts = new ArrayList<>(List.of( new Segment(0.15f, Color.BLUE), @@ -313,9 +359,6 @@ public class NotificationProgressBarTest { new Point(Color.YELLOW), new Segment(0.25f, Color.BLUE))); - List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points, - progress, progressMax); - assertThat(parts).isEqualTo(expectedParts); float drawableWidth = 300; @@ -323,12 +366,11 @@ public class NotificationProgressBarTest { float segPointGap = 4; float pointRadius = 6; boolean hasTrackerIcon = true; - float progressX = 180; - boolean isStyledByProgress = true; + List<NotificationProgressDrawable.Part> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth, - segSegGap, segPointGap, pointRadius, hasTrackerIcon, progressX, - isStyledByProgress); + segSegGap, segPointGap, pointRadius, hasTrackerIcon + ); List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>( List.of(new NotificationProgressDrawable.Segment(0, 35, Color.BLUE), @@ -337,11 +379,38 @@ public class NotificationProgressBarTest { new NotificationProgressDrawable.Point(69, 81, Color.BLUE), new NotificationProgressDrawable.Segment(85, 170, Color.BLUE), new NotificationProgressDrawable.Point(174, 186, Color.BLUE), - new NotificationProgressDrawable.Segment(190, 215, fadedBlue, true), - new NotificationProgressDrawable.Point(219, 231, fadedYellow), - new NotificationProgressDrawable.Segment(235, 300, fadedBlue, true))); + new NotificationProgressDrawable.Segment(190, 215, Color.BLUE), + new NotificationProgressDrawable.Point(219, 231, Color.YELLOW), + new NotificationProgressDrawable.Segment(235, 300, Color.BLUE))); assertThat(drawableParts).isEqualTo(expectedDrawableParts); + + float segmentMinWidth = 16; + boolean isStyledByProgress = true; + + Pair<List<NotificationProgressDrawable.Part>, Float> p = + NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts, + segmentMinWidth, pointRadius, (float) progress / progressMax, 300, + isStyledByProgress, hasTrackerIcon ? 0 : segSegGap); + + // Colors with 40% opacity + int fadedBlue = 0x660000FF; + int fadedYellow = 0x66FFFF00; + expectedDrawableParts = new ArrayList<>( + List.of(new NotificationProgressDrawable.Segment(0, 34.219177F, Color.BLUE), + new NotificationProgressDrawable.Point(38.219177F, 50.219177F, Color.RED), + new NotificationProgressDrawable.Segment(54.219177F, 70.21918F, Color.BLUE), + new NotificationProgressDrawable.Point(74.21918F, 86.21918F, Color.BLUE), + new NotificationProgressDrawable.Segment(90.21918F, 172.38356F, Color.BLUE), + new NotificationProgressDrawable.Point(176.38356F, 188.38356F, Color.BLUE), + new NotificationProgressDrawable.Segment(192.38356F, 217.0137F, fadedBlue, + true), + new NotificationProgressDrawable.Point(221.0137F, 233.0137F, fadedYellow), + new NotificationProgressDrawable.Segment(237.0137F, 300F, fadedBlue, + true))); + + assertThat(p.second).isEqualTo(182.38356F); + assertThat(p.first).isEqualTo(expectedDrawableParts); } @Test @@ -360,10 +429,6 @@ public class NotificationProgressBarTest { List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points, progress, progressMax); - // Colors with 40% opacity - int fadedGreen = 0x6600FF00; - int fadedYellow = 0x66FFFF00; - List<Part> expectedParts = new ArrayList<>(List.of( new Segment(0.15f, Color.RED), new Point(Color.RED), @@ -383,12 +448,10 @@ public class NotificationProgressBarTest { float segPointGap = 4; float pointRadius = 6; boolean hasTrackerIcon = true; - float progressX = 180; - boolean isStyledByProgress = true; List<NotificationProgressDrawable.Part> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth, - segSegGap, segPointGap, pointRadius, hasTrackerIcon, progressX, - isStyledByProgress); + segSegGap, segPointGap, pointRadius, hasTrackerIcon + ); List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>( List.of(new NotificationProgressDrawable.Segment(0, 35, Color.RED), @@ -398,11 +461,41 @@ public class NotificationProgressBarTest { new NotificationProgressDrawable.Segment(85, 146, Color.RED), new NotificationProgressDrawable.Segment(150, 170, Color.GREEN), new NotificationProgressDrawable.Point(174, 186, Color.BLUE), - new NotificationProgressDrawable.Segment(190, 215, fadedGreen, true), - new NotificationProgressDrawable.Point(219, 231, fadedYellow), - new NotificationProgressDrawable.Segment(235, 300, fadedGreen, true))); + new NotificationProgressDrawable.Segment(190, 215, Color.GREEN), + new NotificationProgressDrawable.Point(219, 231, Color.YELLOW), + new NotificationProgressDrawable.Segment(235, 300, Color.GREEN))); assertThat(drawableParts).isEqualTo(expectedDrawableParts); + + float segmentMinWidth = 16; + boolean isStyledByProgress = true; + + Pair<List<NotificationProgressDrawable.Part>, Float> p = + NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts, + segmentMinWidth, pointRadius, (float) progress / progressMax, 300, + isStyledByProgress, hasTrackerIcon ? 0 : segSegGap); + + // Colors with 40% opacity + int fadedGreen = 0x6600FF00; + int fadedYellow = 0x66FFFF00; + expectedDrawableParts = new ArrayList<>( + List.of(new NotificationProgressDrawable.Segment(0, 34.095238F, Color.RED), + new NotificationProgressDrawable.Point(38.095238F, 50.095238F, Color.RED), + new NotificationProgressDrawable.Segment(54.095238F, 70.09524F, Color.RED), + new NotificationProgressDrawable.Point(74.09524F, 86.09524F, Color.BLUE), + new NotificationProgressDrawable.Segment(90.09524F, 148.9524F, Color.RED), + new NotificationProgressDrawable.Segment(152.95238F, 172.7619F, + Color.GREEN), + new NotificationProgressDrawable.Point(176.7619F, 188.7619F, Color.BLUE), + new NotificationProgressDrawable.Segment(192.7619F, 217.33333F, + fadedGreen, true), + new NotificationProgressDrawable.Point(221.33333F, 233.33333F, + fadedYellow), + new NotificationProgressDrawable.Segment(237.33333F, 299.99997F, + fadedGreen, true))); + + assertThat(p.second).isEqualTo(182.7619F); + assertThat(p.first).isEqualTo(expectedDrawableParts); } @Test @@ -437,12 +530,11 @@ public class NotificationProgressBarTest { float segPointGap = 4; float pointRadius = 6; boolean hasTrackerIcon = true; - float progressX = 180; - boolean isStyledByProgress = false; + List<NotificationProgressDrawable.Part> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth, - segSegGap, segPointGap, pointRadius, hasTrackerIcon, progressX, - isStyledByProgress); + segSegGap, segPointGap, pointRadius, hasTrackerIcon + ); List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>( List.of(new NotificationProgressDrawable.Segment(0, 35, Color.RED), @@ -455,5 +547,213 @@ public class NotificationProgressBarTest { new NotificationProgressDrawable.Segment(235, 300, Color.GREEN))); assertThat(drawableParts).isEqualTo(expectedDrawableParts); + + float segmentMinWidth = 16; + boolean isStyledByProgress = false; + + Pair<List<NotificationProgressDrawable.Part>, Float> p = + NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts, + segmentMinWidth, pointRadius, (float) progress / progressMax, 300, + isStyledByProgress, hasTrackerIcon ? 0 : segSegGap); + + expectedDrawableParts = new ArrayList<>( + List.of(new NotificationProgressDrawable.Segment(0, 34.296295F, Color.RED), + new NotificationProgressDrawable.Point(38.296295F, 50.296295F, Color.RED), + new NotificationProgressDrawable.Segment(54.296295F, 70.296295F, Color.RED), + new NotificationProgressDrawable.Point(74.296295F, 86.296295F, Color.BLUE), + new NotificationProgressDrawable.Segment(90.296295F, 149.62962F, Color.RED), + new NotificationProgressDrawable.Segment(153.62962F, 216.8148F, + Color.GREEN), + new NotificationProgressDrawable.Point(220.81482F, 232.81482F, + Color.YELLOW), + new NotificationProgressDrawable.Segment(236.81482F, 300, Color.GREEN))); + + assertThat(p.second).isEqualTo(182.9037F); + assertThat(p.first).isEqualTo(expectedDrawableParts); + } + + // The only difference from the `zeroWidthDrawableSegment` test below is the longer + // segmentMinWidth (= 16dp). + @Test + public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment() { + List<ProgressStyle.Segment> segments = new ArrayList<>(); + segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE)); + segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE)); + segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE)); + segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE)); + List<ProgressStyle.Point> points = new ArrayList<>(); + points.add(new ProgressStyle.Point(0).setColor(Color.BLUE)); + int progress = 1000; + int progressMax = 1000; + + List<Part> parts = NotificationProgressBar.processAndConvertToViewParts( + segments, points, progress, progressMax); + + List<Part> expectedParts = new ArrayList<>(List.of( + new Point(Color.BLUE), + new Segment(0.1f, Color.BLUE), + new Segment(0.2f, Color.BLUE), + new Segment(0.3f, Color.BLUE), + new Segment(0.4f, Color.BLUE))); + + assertThat(parts).isEqualTo(expectedParts); + + float drawableWidth = 200; + float segSegGap = 4; + float segPointGap = 4; + float pointRadius = 6; + boolean hasTrackerIcon = true; + List<NotificationProgressDrawable.Part> drawableParts = + NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth, + segSegGap, segPointGap, pointRadius, hasTrackerIcon + ); + + List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>( + List.of(new NotificationProgressDrawable.Point(0, 12, Color.BLUE), + new NotificationProgressDrawable.Segment(16, 16, Color.BLUE), + new NotificationProgressDrawable.Segment(20, 56, Color.BLUE), + new NotificationProgressDrawable.Segment(60, 116, Color.BLUE), + new NotificationProgressDrawable.Segment(120, 200, Color.BLUE))); + + assertThat(drawableParts).isEqualTo(expectedDrawableParts); + + float segmentMinWidth = 16; + boolean isStyledByProgress = true; + + Pair<List<NotificationProgressDrawable.Part>, Float> p = + NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts, + segmentMinWidth, pointRadius, (float) progress / progressMax, 200, + isStyledByProgress, hasTrackerIcon ? 0 : segSegGap); + + expectedDrawableParts = new ArrayList<>( + List.of(new NotificationProgressDrawable.Point(0, 12, Color.BLUE), + new NotificationProgressDrawable.Segment(16, 32, Color.BLUE), + new NotificationProgressDrawable.Segment(36, 69.41936F, Color.BLUE), + new NotificationProgressDrawable.Segment(73.41936F, 124.25807F, Color.BLUE), + new NotificationProgressDrawable.Segment(128.25807F, 200, Color.BLUE))); + + assertThat(p.second).isEqualTo(200); + assertThat(p.first).isEqualTo(expectedDrawableParts); + } + + // The only difference from the `negativeWidthDrawableSegment` test above is the shorter + // segmentMinWidth (= 10dp). + @Test + public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment() { + List<ProgressStyle.Segment> segments = new ArrayList<>(); + segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE)); + segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE)); + segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE)); + segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE)); + List<ProgressStyle.Point> points = new ArrayList<>(); + points.add(new ProgressStyle.Point(0).setColor(Color.BLUE)); + int progress = 1000; + int progressMax = 1000; + + List<Part> parts = NotificationProgressBar.processAndConvertToViewParts( + segments, points, progress, progressMax); + + List<Part> expectedParts = new ArrayList<>(List.of( + new Point(Color.BLUE), + new Segment(0.1f, Color.BLUE), + new Segment(0.2f, Color.BLUE), + new Segment(0.3f, Color.BLUE), + new Segment(0.4f, Color.BLUE))); + + assertThat(parts).isEqualTo(expectedParts); + + float drawableWidth = 200; + float segSegGap = 4; + float segPointGap = 4; + float pointRadius = 6; + boolean hasTrackerIcon = true; + List<NotificationProgressDrawable.Part> drawableParts = + NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth, + segSegGap, segPointGap, pointRadius, hasTrackerIcon + ); + + List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>( + List.of(new NotificationProgressDrawable.Point(0, 12, Color.BLUE), + new NotificationProgressDrawable.Segment(16, 16, Color.BLUE), + new NotificationProgressDrawable.Segment(20, 56, Color.BLUE), + new NotificationProgressDrawable.Segment(60, 116, Color.BLUE), + new NotificationProgressDrawable.Segment(120, 200, Color.BLUE))); + + assertThat(drawableParts).isEqualTo(expectedDrawableParts); + + float segmentMinWidth = 10; + boolean isStyledByProgress = true; + + Pair<List<NotificationProgressDrawable.Part>, Float> p = + NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts, + segmentMinWidth, pointRadius, (float) progress / progressMax, 200, + isStyledByProgress, hasTrackerIcon ? 0 : segSegGap); + + expectedDrawableParts = new ArrayList<>( + List.of(new NotificationProgressDrawable.Point(0, 12, Color.BLUE), + new NotificationProgressDrawable.Segment(16, 26, Color.BLUE), + new NotificationProgressDrawable.Segment(30, 64.169014F, Color.BLUE), + new NotificationProgressDrawable.Segment(68.169014F, 120.92958F, + Color.BLUE), + new NotificationProgressDrawable.Segment(124.92958F, 200, Color.BLUE))); + + assertThat(p.second).isEqualTo(200); + assertThat(p.first).isEqualTo(expectedDrawableParts); + } + + @Test + public void maybeStretchAndRescaleSegments_noStretchingNecessary() { + List<ProgressStyle.Segment> segments = new ArrayList<>(); + segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE)); + segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE)); + segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE)); + segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE)); + List<ProgressStyle.Point> points = new ArrayList<>(); + points.add(new ProgressStyle.Point(0).setColor(Color.BLUE)); + int progress = 1000; + int progressMax = 1000; + + List<Part> parts = NotificationProgressBar.processAndConvertToViewParts( + segments, points, progress, progressMax); + + List<Part> expectedParts = new ArrayList<>(List.of( + new Point(Color.BLUE), + new Segment(0.2f, Color.BLUE), + new Segment(0.1f, Color.BLUE), + new Segment(0.3f, Color.BLUE), + new Segment(0.4f, Color.BLUE))); + + assertThat(parts).isEqualTo(expectedParts); + + float drawableWidth = 200; + float segSegGap = 4; + float segPointGap = 4; + float pointRadius = 6; + boolean hasTrackerIcon = true; + + List<NotificationProgressDrawable.Part> drawableParts = + NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth, + segSegGap, segPointGap, pointRadius, hasTrackerIcon + ); + + List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>( + List.of(new NotificationProgressDrawable.Point(0, 12, Color.BLUE), + new NotificationProgressDrawable.Segment(16, 36, Color.BLUE), + new NotificationProgressDrawable.Segment(40, 56, Color.BLUE), + new NotificationProgressDrawable.Segment(60, 116, Color.BLUE), + new NotificationProgressDrawable.Segment(120, 200, Color.BLUE))); + + assertThat(drawableParts).isEqualTo(expectedDrawableParts); + + float segmentMinWidth = 10; + boolean isStyledByProgress = true; + + Pair<List<NotificationProgressDrawable.Part>, Float> p = + NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts, + segmentMinWidth, pointRadius, (float) progress / progressMax, 200, + isStyledByProgress, hasTrackerIcon ? 0 : segSegGap); + + assertThat(p.second).isEqualTo(200); + assertThat(p.first).isEqualTo(expectedDrawableParts); } } |