Loading core/java/com/android/internal/widget/NotificationProgressBar.java +277 −12 Original line number Diff line number Diff line Loading @@ -21,6 +21,9 @@ import android.annotation.Nullable; import android.app.Notification.ProgressStyle; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.graphics.drawable.LayerDrawable; Loading Loading @@ -52,7 +55,7 @@ import java.util.TreeSet; * represent Notification ProgressStyle progress, such as for ridesharing and navigation. */ @RemoteViews.RemoteView public class NotificationProgressBar extends ProgressBar { public final class NotificationProgressBar extends ProgressBar { private static final String TAG = "NotificationProgressBar"; private NotificationProgressModel mProgressModel; Loading @@ -61,7 +64,7 @@ public class NotificationProgressBar extends ProgressBar { private List<Part> mProgressDrawableParts = null; @Nullable private Drawable mProgressTrackerDrawable = null; private Drawable mTracker = null; public NotificationProgressBar(Context context) { this(context, null); Loading @@ -78,28 +81,44 @@ public class NotificationProgressBar extends ProgressBar { public NotificationProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.NotificationProgressBar, defStyleAttr, defStyleRes); saveAttributeDataForStyleable(context, R.styleable.NotificationProgressBar, attrs, a, defStyleAttr, defStyleRes); // Supports setting the tracker in xml, but ProgressStyle notifications set/override it // via {@code setProgressTrackerIcon}. final Drawable tracker = a.getDrawable(R.styleable.NotificationProgressBar_tracker); setTracker(tracker); } /** * Setter for the notification progress model. * * @see NotificationProgressModel#fromBundle * @see #setProgressModelAsync */ @RemotableViewMethod(asyncImpl = "setProgressModelAsync") @RemotableViewMethod public void setProgressModel(@Nullable Bundle bundle) { Preconditions.checkArgument(bundle != null, "Bundle shouldn't be null"); mProgressModel = NotificationProgressModel.fromBundle(bundle); final boolean isIndeterminate = mProgressModel.isIndeterminate(); setIndeterminate(isIndeterminate); if (mProgressModel.isIndeterminate()) { if (isIndeterminate) { final int indeterminateColor = mProgressModel.getIndeterminateColor(); setIndeterminateTintList(ColorStateList.valueOf(indeterminateColor)); } else { final int progress = mProgressModel.getProgress(); final int progressMax = mProgressModel.getProgressMax(); mProgressDrawableParts = processAndConvertToDrawableParts(mProgressModel.getSegments(), mProgressModel.getPoints(), mProgressModel.getProgress(), mProgressModel.isStyledByProgress()); progress, progressMax, mProgressModel.isStyledByProgress()); try { final NotificationProgressDrawable drawable = getNotificationProgressDrawable(); Loading @@ -107,6 +126,9 @@ public class NotificationProgressBar extends ProgressBar { } catch (IllegalStateException ex) { Log.e(TAG, "Can't set parts because can't get NotificationProgressDrawable", ex); } setMax(progressMax); setProgress(progress); } } Loading Loading @@ -137,6 +159,13 @@ public class NotificationProgressBar extends ProgressBar { */ @RemotableViewMethod(asyncImpl = "setProgressTrackerIconAsync") public void setProgressTrackerIcon(@Nullable Icon icon) { final Drawable progressTrackerDrawable; if (icon != null) { progressTrackerDrawable = icon.loadDrawable(getContext()); } else { progressTrackerDrawable = null; } setTracker(progressTrackerDrawable); } /** Loading @@ -150,12 +179,245 @@ public class NotificationProgressBar extends ProgressBar { progressTrackerDrawable = null; } return () -> { setProgressTrackerDrawable(progressTrackerDrawable); setTracker(progressTrackerDrawable); }; } private void setProgressTrackerDrawable(@Nullable Drawable drawable) { mProgressTrackerDrawable = drawable; private void setTracker(@Nullable Drawable tracker) { if (isIndeterminate() && tracker != null) { return; } final boolean needUpdate = mTracker != null && tracker != mTracker; if (needUpdate) { mTracker.setCallback(null); } if (tracker != null) { tracker.setCallback(this); if (canResolveLayoutDirection()) { tracker.setLayoutDirection(getLayoutDirection()); } // If we're updating get the new states if (needUpdate && (tracker.getIntrinsicWidth() != mTracker.getIntrinsicWidth() || tracker.getIntrinsicHeight() != mTracker.getIntrinsicHeight())) { requestLayout(); } } mTracker = tracker; invalidate(); if (needUpdate) { updateTrackerAndBarPos(getWidth(), getHeight()); if (tracker != null && tracker.isStateful()) { // Note that if the states are different this won't work. // For now, let's consider that an app bug. tracker.setState(getDrawableState()); } } } @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); } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (mTracker != null) { mTracker.jumpToCurrentState(); } } @Override protected void drawableStateChanged() { super.drawableStateChanged(); final Drawable tracker = mTracker; if (tracker != null && tracker.isStateful() && tracker.setState(getDrawableState())) { invalidateDrawable(tracker); } } @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (mTracker != null) { mTracker.setHotspot(x, y); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); updateTrackerAndBarPos(w, h); } private void updateTrackerAndBarPos(int w, int h) { final int paddedHeight = h - mPaddingTop - mPaddingBottom; final Drawable bar = getCurrentDrawable(); final Drawable tracker = mTracker; // The max height does not incorporate padding, whereas the height // parameter does. final int barHeight = Math.min(getMaxHeight(), paddedHeight); final int trackerHeight = tracker == null ? 0 : tracker.getIntrinsicHeight(); // Apply offset to whichever item is taller. final int barOffsetY; final int trackerOffsetY; if (trackerHeight > barHeight) { final int offsetHeight = (paddedHeight - trackerHeight) / 2; barOffsetY = offsetHeight + (trackerHeight - barHeight) / 2; trackerOffsetY = offsetHeight; } else { final int offsetHeight = (paddedHeight - barHeight) / 2; barOffsetY = offsetHeight; trackerOffsetY = offsetHeight + (barHeight - trackerHeight) / 2; } if (bar != null) { final int barWidth = w - mPaddingRight - mPaddingLeft; bar.setBounds(0, barOffsetY, barWidth, barOffsetY + barHeight); } if (tracker != null) { setTrackerPos(w, tracker, getScale(), trackerOffsetY); } } private float getScale() { int min = getMin(); int max = getMax(); int range = max - min; return range > 0 ? (getProgress() - min) / (float) range : 0; } /** * Updates the tracker drawable bounds. * * @param w Width of the view, including padding * @param tracker Drawable used for the tracker * @param scale 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) { int available = w - mPaddingLeft - mPaddingRight; final int trackerWidth = tracker.getIntrinsicWidth(); final int trackerHeight = tracker.getIntrinsicHeight(); available -= trackerWidth; final int trackerPos = (int) (scale * available + 0.5f); final int top, bottom; if (offsetY == Integer.MIN_VALUE) { final Rect oldBounds = tracker.getBounds(); top = oldBounds.top; bottom = oldBounds.bottom; } else { top = offsetY; bottom = offsetY + trackerHeight; } final int left = (isLayoutRtl() && getMirrorForRtl()) ? available - trackerPos : trackerPos; final int right = left + trackerWidth; final Drawable background = getBackground(); if (background != null) { final int bkgOffsetX = mPaddingLeft; final int bkgOffsetY = mPaddingTop; background.setHotspotBounds(left + bkgOffsetX, top + bkgOffsetY, right + bkgOffsetX, bottom + bkgOffsetY); } // Canvas will be translated, so 0,0 is where we start drawing tracker.setBounds(left, top, right, bottom); } @Override public void onResolveDrawables(int layoutDirection) { super.onResolveDrawables(layoutDirection); if (mTracker != null) { mTracker.setLayoutDirection(layoutDirection); } } @Override protected synchronized void onDraw(Canvas canvas) { super.onDraw(canvas); drawTracker(canvas); } /** * Draw the tracker. */ private void drawTracker(Canvas canvas) { if (mTracker != null) { final int saveCount = canvas.save(); // Translate the padding. For the x, we need to allow the tracker to // draw in its extra space canvas.translate(mPaddingLeft, mPaddingTop); mTracker.draw(canvas); canvas.restoreToCount(saveCount); } } @Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Drawable d = getCurrentDrawable(); int trackerHeight = mTracker == null ? 0 : mTracker.getIntrinsicHeight(); int dw = 0; int dh = 0; if (d != null) { dw = Math.max(getMinWidth(), Math.min(getMaxWidth(), d.getIntrinsicWidth())); dh = Math.max(getMinHeight(), Math.min(getMaxHeight(), d.getIntrinsicHeight())); dh = Math.max(trackerHeight, dh); } dw += mPaddingLeft + mPaddingRight; dh += mPaddingTop + mPaddingBottom; setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0), resolveSizeAndState(dh, heightMeasureSpec, 0)); } @Override public CharSequence getAccessibilityClassName() { return NotificationProgressBar.class.getName(); } @Override public void onRtlPropertiesChanged(int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); final Drawable tracker = mTracker; if (tracker != null) { setTrackerPos(getWidth(), tracker, getScale(), 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, // so just invalidate this whole view. invalidate(); } } /** Loading @@ -167,12 +429,18 @@ public class NotificationProgressBar extends ProgressBar { List<ProgressStyle.Segment> segments, List<ProgressStyle.Point> points, int progress, int progressMax, boolean isStyledByProgress ) { if (segments.isEmpty()) { throw new IllegalArgumentException("List of segments shouldn't be empty"); } final int totalLength = segments.stream().mapToInt(ProgressStyle.Segment::getLength).sum(); if (progressMax != totalLength) { throw new IllegalArgumentException("Invalid progressMax : " + progressMax); } for (ProgressStyle.Segment segment : segments) { final int length = segment.getLength(); if (length <= 0) { Loading @@ -180,8 +448,6 @@ public class NotificationProgressBar extends ProgressBar { } } final int progressMax = segments.stream().mapToInt(ProgressStyle.Segment::getLength).sum(); if (progress < 0 || progress > progressMax) { throw new IllegalArgumentException("Invalid progress : " + progress); } Loading @@ -208,7 +474,6 @@ public class NotificationProgressBar extends ProgressBar { 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( Loading core/java/com/android/internal/widget/NotificationProgressModel.java +4 −0 Original line number Diff line number Diff line Loading @@ -96,6 +96,10 @@ public final class NotificationProgressModel { return mProgress; } public int getProgressMax() { return mSegments.stream().mapToInt(Notification.ProgressStyle.Segment::getLength).sum(); } public boolean isStyledByProgress() { return mIsStyledByProgress; } Loading core/res/res/values/attrs.xml +6 −0 Original line number Diff line number Diff line Loading @@ -5731,6 +5731,12 @@ </attr> </declare-styleable> <!-- @hide internal use only --> <declare-styleable name="NotificationProgressBar"> <!-- Draws the tracker on a NotificationProgressBar. --> <attr name="tracker" format="reference" /> </declare-styleable> <declare-styleable name="StackView"> <!-- Color of the res-out outline. --> <attr name="resOutColor" format="color" /> Loading core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java +47 −7 Original line number Diff line number Diff line Loading @@ -41,9 +41,26 @@ public class NotificationProgressBarTest { List<ProgressStyle.Segment> segments = new ArrayList<>(); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } @Test(expected = IllegalArgumentException.class) public void processAndConvertToDrawableParts_segmentsLengthNotMatchingProgressMax() { List<ProgressStyle.Segment> segments = new ArrayList<>(); segments.add(new ProgressStyle.Segment(50)); segments.add(new ProgressStyle.Segment(100)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -51,11 +68,14 @@ public class NotificationProgressBarTest { public void processAndConvertToDrawableParts_segmentLengthIsNegative() { List<ProgressStyle.Segment> segments = new ArrayList<>(); segments.add(new ProgressStyle.Segment(-50)); segments.add(new ProgressStyle.Segment(150)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -63,11 +83,14 @@ public class NotificationProgressBarTest { public void processAndConvertToDrawableParts_segmentLengthIsZero() { List<ProgressStyle.Segment> segments = new ArrayList<>(); segments.add(new ProgressStyle.Segment(0)); segments.add(new ProgressStyle.Segment(100)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -77,9 +100,11 @@ public class NotificationProgressBarTest { segments.add(new ProgressStyle.Segment(100)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = -50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -89,10 +114,11 @@ public class NotificationProgressBarTest { segments.add(new ProgressStyle.Segment(100).setColor(Color.RED)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 0; int progressMax = 100; boolean isStyledByProgress = true; List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, isStyledByProgress); segments, points, progress, progressMax, isStyledByProgress); int fadedRed = 0x7FFF0000; List<Part> expected = new ArrayList<>(List.of(new Segment(1f, fadedRed, true))); Loading @@ -106,10 +132,11 @@ public class NotificationProgressBarTest { segments.add(new ProgressStyle.Segment(100).setColor(Color.RED)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 100; int progressMax = 100; boolean isStyledByProgress = true; List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, isStyledByProgress); segments, points, progress, progressMax, isStyledByProgress); List<Part> expected = new ArrayList<>(List.of(new Segment(1f, Color.RED))); Loading @@ -122,10 +149,11 @@ public class NotificationProgressBarTest { segments.add(new ProgressStyle.Segment(100)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 150; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, isStyledByProgress); progressMax, isStyledByProgress); } @Test(expected = IllegalArgumentException.class) Loading @@ -135,9 +163,11 @@ public class NotificationProgressBarTest { List<ProgressStyle.Point> points = new ArrayList<>(); points.add(new ProgressStyle.Point(-50).setColor(Color.RED)); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -148,9 +178,11 @@ public class NotificationProgressBarTest { List<ProgressStyle.Point> points = new ArrayList<>(); points.add(new ProgressStyle.Point(0).setColor(Color.RED)); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -161,9 +193,11 @@ public class NotificationProgressBarTest { List<ProgressStyle.Point> points = new ArrayList<>(); points.add(new ProgressStyle.Point(100).setColor(Color.RED)); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -174,9 +208,11 @@ public class NotificationProgressBarTest { List<ProgressStyle.Point> points = new ArrayList<>(); points.add(new ProgressStyle.Point(150).setColor(Color.RED)); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -187,10 +223,11 @@ public class NotificationProgressBarTest { segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 60; int progressMax = 100; boolean isStyledByProgress = true; List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, isStyledByProgress); segments, points, progress, progressMax, isStyledByProgress); // Colors with 50% opacity int fadedGreen = 0x7F00FF00; Loading @@ -213,6 +250,7 @@ public class NotificationProgressBarTest { points.add(new ProgressStyle.Point(60).setColor(Color.BLUE)); points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW)); int progress = 60; int progressMax = 100; boolean isStyledByProgress = true; // Colors with 50% opacity Loading @@ -231,7 +269,7 @@ public class NotificationProgressBarTest { new Segment(0.25f, fadedBlue, true))); List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, isStyledByProgress); segments, points, progress, progressMax, isStyledByProgress); assertThat(parts).isEqualTo(expected); } Loading @@ -247,10 +285,11 @@ public class NotificationProgressBarTest { points.add(new ProgressStyle.Point(60).setColor(Color.BLUE)); points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW)); int progress = 60; int progressMax = 100; boolean isStyledByProgress = true; List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, isStyledByProgress); segments, points, progress, progressMax, isStyledByProgress); // Colors with 50% opacity int fadedGreen = 0x7F00FF00; Loading Loading @@ -281,10 +320,11 @@ public class NotificationProgressBarTest { points.add(new ProgressStyle.Point(25).setColor(Color.BLUE)); points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW)); int progress = 60; int progressMax = 100; boolean isStyledByProgress = false; List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, isStyledByProgress); segments, points, progress, progressMax, isStyledByProgress); List<Part> expected = new ArrayList<>(List.of( new Segment(0.15f, Color.RED), Loading Loading
core/java/com/android/internal/widget/NotificationProgressBar.java +277 −12 Original line number Diff line number Diff line Loading @@ -21,6 +21,9 @@ import android.annotation.Nullable; import android.app.Notification.ProgressStyle; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.graphics.drawable.LayerDrawable; Loading Loading @@ -52,7 +55,7 @@ import java.util.TreeSet; * represent Notification ProgressStyle progress, such as for ridesharing and navigation. */ @RemoteViews.RemoteView public class NotificationProgressBar extends ProgressBar { public final class NotificationProgressBar extends ProgressBar { private static final String TAG = "NotificationProgressBar"; private NotificationProgressModel mProgressModel; Loading @@ -61,7 +64,7 @@ public class NotificationProgressBar extends ProgressBar { private List<Part> mProgressDrawableParts = null; @Nullable private Drawable mProgressTrackerDrawable = null; private Drawable mTracker = null; public NotificationProgressBar(Context context) { this(context, null); Loading @@ -78,28 +81,44 @@ public class NotificationProgressBar extends ProgressBar { public NotificationProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.NotificationProgressBar, defStyleAttr, defStyleRes); saveAttributeDataForStyleable(context, R.styleable.NotificationProgressBar, attrs, a, defStyleAttr, defStyleRes); // Supports setting the tracker in xml, but ProgressStyle notifications set/override it // via {@code setProgressTrackerIcon}. final Drawable tracker = a.getDrawable(R.styleable.NotificationProgressBar_tracker); setTracker(tracker); } /** * Setter for the notification progress model. * * @see NotificationProgressModel#fromBundle * @see #setProgressModelAsync */ @RemotableViewMethod(asyncImpl = "setProgressModelAsync") @RemotableViewMethod public void setProgressModel(@Nullable Bundle bundle) { Preconditions.checkArgument(bundle != null, "Bundle shouldn't be null"); mProgressModel = NotificationProgressModel.fromBundle(bundle); final boolean isIndeterminate = mProgressModel.isIndeterminate(); setIndeterminate(isIndeterminate); if (mProgressModel.isIndeterminate()) { if (isIndeterminate) { final int indeterminateColor = mProgressModel.getIndeterminateColor(); setIndeterminateTintList(ColorStateList.valueOf(indeterminateColor)); } else { final int progress = mProgressModel.getProgress(); final int progressMax = mProgressModel.getProgressMax(); mProgressDrawableParts = processAndConvertToDrawableParts(mProgressModel.getSegments(), mProgressModel.getPoints(), mProgressModel.getProgress(), mProgressModel.isStyledByProgress()); progress, progressMax, mProgressModel.isStyledByProgress()); try { final NotificationProgressDrawable drawable = getNotificationProgressDrawable(); Loading @@ -107,6 +126,9 @@ public class NotificationProgressBar extends ProgressBar { } catch (IllegalStateException ex) { Log.e(TAG, "Can't set parts because can't get NotificationProgressDrawable", ex); } setMax(progressMax); setProgress(progress); } } Loading Loading @@ -137,6 +159,13 @@ public class NotificationProgressBar extends ProgressBar { */ @RemotableViewMethod(asyncImpl = "setProgressTrackerIconAsync") public void setProgressTrackerIcon(@Nullable Icon icon) { final Drawable progressTrackerDrawable; if (icon != null) { progressTrackerDrawable = icon.loadDrawable(getContext()); } else { progressTrackerDrawable = null; } setTracker(progressTrackerDrawable); } /** Loading @@ -150,12 +179,245 @@ public class NotificationProgressBar extends ProgressBar { progressTrackerDrawable = null; } return () -> { setProgressTrackerDrawable(progressTrackerDrawable); setTracker(progressTrackerDrawable); }; } private void setProgressTrackerDrawable(@Nullable Drawable drawable) { mProgressTrackerDrawable = drawable; private void setTracker(@Nullable Drawable tracker) { if (isIndeterminate() && tracker != null) { return; } final boolean needUpdate = mTracker != null && tracker != mTracker; if (needUpdate) { mTracker.setCallback(null); } if (tracker != null) { tracker.setCallback(this); if (canResolveLayoutDirection()) { tracker.setLayoutDirection(getLayoutDirection()); } // If we're updating get the new states if (needUpdate && (tracker.getIntrinsicWidth() != mTracker.getIntrinsicWidth() || tracker.getIntrinsicHeight() != mTracker.getIntrinsicHeight())) { requestLayout(); } } mTracker = tracker; invalidate(); if (needUpdate) { updateTrackerAndBarPos(getWidth(), getHeight()); if (tracker != null && tracker.isStateful()) { // Note that if the states are different this won't work. // For now, let's consider that an app bug. tracker.setState(getDrawableState()); } } } @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); } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (mTracker != null) { mTracker.jumpToCurrentState(); } } @Override protected void drawableStateChanged() { super.drawableStateChanged(); final Drawable tracker = mTracker; if (tracker != null && tracker.isStateful() && tracker.setState(getDrawableState())) { invalidateDrawable(tracker); } } @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (mTracker != null) { mTracker.setHotspot(x, y); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); updateTrackerAndBarPos(w, h); } private void updateTrackerAndBarPos(int w, int h) { final int paddedHeight = h - mPaddingTop - mPaddingBottom; final Drawable bar = getCurrentDrawable(); final Drawable tracker = mTracker; // The max height does not incorporate padding, whereas the height // parameter does. final int barHeight = Math.min(getMaxHeight(), paddedHeight); final int trackerHeight = tracker == null ? 0 : tracker.getIntrinsicHeight(); // Apply offset to whichever item is taller. final int barOffsetY; final int trackerOffsetY; if (trackerHeight > barHeight) { final int offsetHeight = (paddedHeight - trackerHeight) / 2; barOffsetY = offsetHeight + (trackerHeight - barHeight) / 2; trackerOffsetY = offsetHeight; } else { final int offsetHeight = (paddedHeight - barHeight) / 2; barOffsetY = offsetHeight; trackerOffsetY = offsetHeight + (barHeight - trackerHeight) / 2; } if (bar != null) { final int barWidth = w - mPaddingRight - mPaddingLeft; bar.setBounds(0, barOffsetY, barWidth, barOffsetY + barHeight); } if (tracker != null) { setTrackerPos(w, tracker, getScale(), trackerOffsetY); } } private float getScale() { int min = getMin(); int max = getMax(); int range = max - min; return range > 0 ? (getProgress() - min) / (float) range : 0; } /** * Updates the tracker drawable bounds. * * @param w Width of the view, including padding * @param tracker Drawable used for the tracker * @param scale 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) { int available = w - mPaddingLeft - mPaddingRight; final int trackerWidth = tracker.getIntrinsicWidth(); final int trackerHeight = tracker.getIntrinsicHeight(); available -= trackerWidth; final int trackerPos = (int) (scale * available + 0.5f); final int top, bottom; if (offsetY == Integer.MIN_VALUE) { final Rect oldBounds = tracker.getBounds(); top = oldBounds.top; bottom = oldBounds.bottom; } else { top = offsetY; bottom = offsetY + trackerHeight; } final int left = (isLayoutRtl() && getMirrorForRtl()) ? available - trackerPos : trackerPos; final int right = left + trackerWidth; final Drawable background = getBackground(); if (background != null) { final int bkgOffsetX = mPaddingLeft; final int bkgOffsetY = mPaddingTop; background.setHotspotBounds(left + bkgOffsetX, top + bkgOffsetY, right + bkgOffsetX, bottom + bkgOffsetY); } // Canvas will be translated, so 0,0 is where we start drawing tracker.setBounds(left, top, right, bottom); } @Override public void onResolveDrawables(int layoutDirection) { super.onResolveDrawables(layoutDirection); if (mTracker != null) { mTracker.setLayoutDirection(layoutDirection); } } @Override protected synchronized void onDraw(Canvas canvas) { super.onDraw(canvas); drawTracker(canvas); } /** * Draw the tracker. */ private void drawTracker(Canvas canvas) { if (mTracker != null) { final int saveCount = canvas.save(); // Translate the padding. For the x, we need to allow the tracker to // draw in its extra space canvas.translate(mPaddingLeft, mPaddingTop); mTracker.draw(canvas); canvas.restoreToCount(saveCount); } } @Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Drawable d = getCurrentDrawable(); int trackerHeight = mTracker == null ? 0 : mTracker.getIntrinsicHeight(); int dw = 0; int dh = 0; if (d != null) { dw = Math.max(getMinWidth(), Math.min(getMaxWidth(), d.getIntrinsicWidth())); dh = Math.max(getMinHeight(), Math.min(getMaxHeight(), d.getIntrinsicHeight())); dh = Math.max(trackerHeight, dh); } dw += mPaddingLeft + mPaddingRight; dh += mPaddingTop + mPaddingBottom; setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0), resolveSizeAndState(dh, heightMeasureSpec, 0)); } @Override public CharSequence getAccessibilityClassName() { return NotificationProgressBar.class.getName(); } @Override public void onRtlPropertiesChanged(int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); final Drawable tracker = mTracker; if (tracker != null) { setTrackerPos(getWidth(), tracker, getScale(), 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, // so just invalidate this whole view. invalidate(); } } /** Loading @@ -167,12 +429,18 @@ public class NotificationProgressBar extends ProgressBar { List<ProgressStyle.Segment> segments, List<ProgressStyle.Point> points, int progress, int progressMax, boolean isStyledByProgress ) { if (segments.isEmpty()) { throw new IllegalArgumentException("List of segments shouldn't be empty"); } final int totalLength = segments.stream().mapToInt(ProgressStyle.Segment::getLength).sum(); if (progressMax != totalLength) { throw new IllegalArgumentException("Invalid progressMax : " + progressMax); } for (ProgressStyle.Segment segment : segments) { final int length = segment.getLength(); if (length <= 0) { Loading @@ -180,8 +448,6 @@ public class NotificationProgressBar extends ProgressBar { } } final int progressMax = segments.stream().mapToInt(ProgressStyle.Segment::getLength).sum(); if (progress < 0 || progress > progressMax) { throw new IllegalArgumentException("Invalid progress : " + progress); } Loading @@ -208,7 +474,6 @@ public class NotificationProgressBar extends ProgressBar { 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( Loading
core/java/com/android/internal/widget/NotificationProgressModel.java +4 −0 Original line number Diff line number Diff line Loading @@ -96,6 +96,10 @@ public final class NotificationProgressModel { return mProgress; } public int getProgressMax() { return mSegments.stream().mapToInt(Notification.ProgressStyle.Segment::getLength).sum(); } public boolean isStyledByProgress() { return mIsStyledByProgress; } Loading
core/res/res/values/attrs.xml +6 −0 Original line number Diff line number Diff line Loading @@ -5731,6 +5731,12 @@ </attr> </declare-styleable> <!-- @hide internal use only --> <declare-styleable name="NotificationProgressBar"> <!-- Draws the tracker on a NotificationProgressBar. --> <attr name="tracker" format="reference" /> </declare-styleable> <declare-styleable name="StackView"> <!-- Color of the res-out outline. --> <attr name="resOutColor" format="color" /> Loading
core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java +47 −7 Original line number Diff line number Diff line Loading @@ -41,9 +41,26 @@ public class NotificationProgressBarTest { List<ProgressStyle.Segment> segments = new ArrayList<>(); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } @Test(expected = IllegalArgumentException.class) public void processAndConvertToDrawableParts_segmentsLengthNotMatchingProgressMax() { List<ProgressStyle.Segment> segments = new ArrayList<>(); segments.add(new ProgressStyle.Segment(50)); segments.add(new ProgressStyle.Segment(100)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -51,11 +68,14 @@ public class NotificationProgressBarTest { public void processAndConvertToDrawableParts_segmentLengthIsNegative() { List<ProgressStyle.Segment> segments = new ArrayList<>(); segments.add(new ProgressStyle.Segment(-50)); segments.add(new ProgressStyle.Segment(150)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -63,11 +83,14 @@ public class NotificationProgressBarTest { public void processAndConvertToDrawableParts_segmentLengthIsZero() { List<ProgressStyle.Segment> segments = new ArrayList<>(); segments.add(new ProgressStyle.Segment(0)); segments.add(new ProgressStyle.Segment(100)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -77,9 +100,11 @@ public class NotificationProgressBarTest { segments.add(new ProgressStyle.Segment(100)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = -50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -89,10 +114,11 @@ public class NotificationProgressBarTest { segments.add(new ProgressStyle.Segment(100).setColor(Color.RED)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 0; int progressMax = 100; boolean isStyledByProgress = true; List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, isStyledByProgress); segments, points, progress, progressMax, isStyledByProgress); int fadedRed = 0x7FFF0000; List<Part> expected = new ArrayList<>(List.of(new Segment(1f, fadedRed, true))); Loading @@ -106,10 +132,11 @@ public class NotificationProgressBarTest { segments.add(new ProgressStyle.Segment(100).setColor(Color.RED)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 100; int progressMax = 100; boolean isStyledByProgress = true; List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, isStyledByProgress); segments, points, progress, progressMax, isStyledByProgress); List<Part> expected = new ArrayList<>(List.of(new Segment(1f, Color.RED))); Loading @@ -122,10 +149,11 @@ public class NotificationProgressBarTest { segments.add(new ProgressStyle.Segment(100)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 150; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, isStyledByProgress); progressMax, isStyledByProgress); } @Test(expected = IllegalArgumentException.class) Loading @@ -135,9 +163,11 @@ public class NotificationProgressBarTest { List<ProgressStyle.Point> points = new ArrayList<>(); points.add(new ProgressStyle.Point(-50).setColor(Color.RED)); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -148,9 +178,11 @@ public class NotificationProgressBarTest { List<ProgressStyle.Point> points = new ArrayList<>(); points.add(new ProgressStyle.Point(0).setColor(Color.RED)); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -161,9 +193,11 @@ public class NotificationProgressBarTest { List<ProgressStyle.Point> points = new ArrayList<>(); points.add(new ProgressStyle.Point(100).setColor(Color.RED)); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -174,9 +208,11 @@ public class NotificationProgressBarTest { List<ProgressStyle.Point> points = new ArrayList<>(); points.add(new ProgressStyle.Point(150).setColor(Color.RED)); int progress = 50; int progressMax = 100; boolean isStyledByProgress = true; NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress, progressMax, isStyledByProgress); } Loading @@ -187,10 +223,11 @@ public class NotificationProgressBarTest { segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN)); List<ProgressStyle.Point> points = new ArrayList<>(); int progress = 60; int progressMax = 100; boolean isStyledByProgress = true; List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, isStyledByProgress); segments, points, progress, progressMax, isStyledByProgress); // Colors with 50% opacity int fadedGreen = 0x7F00FF00; Loading @@ -213,6 +250,7 @@ public class NotificationProgressBarTest { points.add(new ProgressStyle.Point(60).setColor(Color.BLUE)); points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW)); int progress = 60; int progressMax = 100; boolean isStyledByProgress = true; // Colors with 50% opacity Loading @@ -231,7 +269,7 @@ public class NotificationProgressBarTest { new Segment(0.25f, fadedBlue, true))); List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, isStyledByProgress); segments, points, progress, progressMax, isStyledByProgress); assertThat(parts).isEqualTo(expected); } Loading @@ -247,10 +285,11 @@ public class NotificationProgressBarTest { points.add(new ProgressStyle.Point(60).setColor(Color.BLUE)); points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW)); int progress = 60; int progressMax = 100; boolean isStyledByProgress = true; List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, isStyledByProgress); segments, points, progress, progressMax, isStyledByProgress); // Colors with 50% opacity int fadedGreen = 0x7F00FF00; Loading Loading @@ -281,10 +320,11 @@ public class NotificationProgressBarTest { points.add(new ProgressStyle.Point(25).setColor(Color.BLUE)); points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW)); int progress = 60; int progressMax = 100; boolean isStyledByProgress = false; List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, isStyledByProgress); segments, points, progress, progressMax, isStyledByProgress); List<Part> expected = new ArrayList<>(List.of( new Segment(0.15f, Color.RED), Loading