Merge "Remove DEBUG protection around keyguard display logging" into main
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 8cd7843..f0b5493 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -23,6 +23,7 @@
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -31,6 +32,7 @@
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;
@@ -40,14 +42,12 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-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.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -56,18 +56,25 @@
* represent Notification ProgressStyle progress, such as for ridesharing and navigation.
*/
@RemoteViews.RemoteView
-public final class NotificationProgressBar extends ProgressBar {
+public final class NotificationProgressBar extends ProgressBar implements
+ NotificationProgressDrawable.BoundsChangeListener {
private static final String TAG = "NotificationProgressBar";
private NotificationProgressDrawable mNotificationProgressDrawable;
+ private final Rect mProgressDrawableBounds = new Rect();
private NotificationProgressModel mProgressModel;
@Nullable
- private List<Part> mProgressDrawableParts = null;
+ private List<Part> mParts = null;
+
+ // List of drawable parts before segment splitting by process.
+ @Nullable
+ private List<NotificationProgressDrawable.Part> mProgressDrawableParts = null;
@Nullable
private Drawable mTracker = null;
+ private boolean mHasTrackerIcon = false;
/** @see R.styleable#NotificationProgressBar_trackerHeight */
private final int mTrackerHeight;
@@ -76,7 +83,13 @@
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;
@@ -104,12 +117,13 @@
try {
mNotificationProgressDrawable = getNotificationProgressDrawable();
+ mNotificationProgressDrawable.setBoundsChangeListener(this);
} catch (IllegalStateException ex) {
Log.e(TAG, "Can't get NotificationProgressDrawable", ex);
}
// Supports setting the tracker in xml, but ProgressStyle notifications set/override it
- // via {@code setProgressTrackerIcon}.
+ // via {@code #setProgressTrackerIcon}.
final Drawable tracker = a.getDrawable(R.styleable.NotificationProgressBar_tracker);
setTracker(tracker);
@@ -137,20 +151,25 @@
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();
- mProgressDrawableParts = processAndConvertToDrawableParts(mProgressModel.getSegments(),
+
+ mParts = processAndConvertToViewParts(mProgressModel.getSegments(),
mProgressModel.getPoints(),
progress,
- progressMax,
- mProgressModel.isStyledByProgress());
-
- if (mNotificationProgressDrawable != null) {
- mNotificationProgressDrawable.setParts(mProgressDrawableParts);
- }
+ progressMax);
setMax(progressMax);
setProgress(progress);
+
+ if (mNotificationProgressDrawable != null
+ && mNotificationProgressDrawable.getBounds().width() != 0) {
+ updateDrawableParts();
+ }
}
}
@@ -200,9 +219,7 @@
} else {
progressTrackerDrawable = null;
}
- return () -> {
- setTracker(progressTrackerDrawable);
- };
+ return () -> setTracker(progressTrackerDrawable);
}
private void setTracker(@Nullable Drawable tracker) {
@@ -226,8 +243,14 @@
final boolean trackerSizeChanged = trackerSizeChanged(tracker, mTracker);
mTracker = tracker;
- if (mNotificationProgressDrawable != null) {
- mNotificationProgressDrawable.setHasTrackerIcon(mTracker != null);
+ final boolean hasTrackerIcon = (mTracker != null);
+ if (mHasTrackerIcon != hasTrackerIcon) {
+ mHasTrackerIcon = hasTrackerIcon;
+ if (mNotificationProgressDrawable != null
+ && mNotificationProgressDrawable.getBounds().width() != 0
+ && mProgressModel.isStyledByProgress()) {
+ updateDrawableParts();
+ }
}
configureTrackerBounds();
@@ -293,6 +316,8 @@
mTrackerDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
}
+ // This updates the visual position of the progress indicator, i.e., the tracker. It doesn't
+ // update the NotificationProgressDrawable, which is updated by {@code #setProgressModel}.
@Override
public synchronized void setProgress(int progress) {
super.setProgress(progress);
@@ -300,6 +325,8 @@
onMaybeVisualProgressChanged();
}
+ // This updates the visual position of the progress indicator, i.e., the tracker. It doesn't
+ // update the NotificationProgressDrawable, which is updated by {@code #setProgressModel}.
@Override
public void setProgress(int progress, boolean animate) {
// Animation isn't supported by NotificationProgressBar.
@@ -308,6 +335,8 @@
onMaybeVisualProgressChanged();
}
+ // This updates the visual position of the progress indicator, i.e., the tracker. It doesn't
+ // update the NotificationProgressDrawable, which is updated by {@code #setProgressModel}.
@Override
public synchronized void setMin(int min) {
super.setMin(min);
@@ -315,6 +344,8 @@
onMaybeVisualProgressChanged();
}
+ // This updates the visual position of the progress indicator, i.e., the tracker. It doesn't
+ // update the NotificationProgressDrawable, which is updated by {@code #setProgressModel}.
@Override
public synchronized void setMax(int max) {
super.setMax(max);
@@ -323,10 +354,10 @@
}
private void onMaybeVisualProgressChanged() {
- float scale = getScale();
- if (mScale == scale) return;
+ float progressFraction = getProgressFraction();
+ if (mProgressFraction == progressFraction) return;
- mScale = scale;
+ mProgressFraction = progressFraction;
mTrackerPosIsDirty = true;
invalidate();
}
@@ -372,6 +403,59 @@
updateTrackerAndBarPos(w, h);
}
+ @Override
+ public void onDrawableBoundsChanged() {
+ final Rect progressDrawableBounds = mNotificationProgressDrawable.getBounds();
+
+ if (mProgressDrawableBounds.equals(progressDrawableBounds)) return;
+
+ if (mProgressDrawableBounds.width() != progressDrawableBounds.width()) {
+ updateDrawableParts();
+ }
+
+ mProgressDrawableBounds.set(progressDrawableBounds);
+ }
+
+ private void updateDrawableParts() {
+ Log.d(TAG, "updateDrawableParts() called. mNotificationProgressDrawable = "
+ + mNotificationProgressDrawable + ", mParts = " + mParts);
+
+ if (mNotificationProgressDrawable == null) return;
+ if (mParts == null) return;
+
+ final float width = mNotificationProgressDrawable.getBounds().width();
+ if (width == 0) {
+ if (mProgressDrawableParts != null) {
+ Log.d(TAG, "Clearing mProgressDrawableParts");
+ mProgressDrawableParts.clear();
+ mNotificationProgressDrawable.setParts(mProgressDrawableParts);
+ }
+ return;
+ }
+
+ mProgressDrawableParts = processAndConvertToDrawableParts(
+ mParts,
+ width,
+ mNotificationProgressDrawable.getSegSegGap(),
+ mNotificationProgressDrawable.getSegPointGap(),
+ mNotificationProgressDrawable.getPointRadius(),
+ 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(p.first);
+ mAdjustedProgressFraction = p.second / width;
+ }
+
private void updateTrackerAndBarPos(int w, int h) {
final int paddedHeight = h - mPaddingTop - mPaddingBottom;
final Drawable bar = getCurrentDrawable();
@@ -402,11 +486,11 @@
}
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;
@@ -418,17 +502,17 @@
*
* @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) {
@@ -482,7 +566,7 @@
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();
@@ -531,7 +615,7 @@
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,
@@ -541,16 +625,14 @@
}
/**
- * Processes the ProgressStyle data and convert to list of {@code
- * NotificationProgressDrawable.Part}.
+ * Processes the ProgressStyle data and convert to a list of {@code Part}.
*/
@VisibleForTesting
- public static List<Part> processAndConvertToDrawableParts(
+ public static List<Part> processAndConvertToViewParts(
List<ProgressStyle.Segment> segments,
List<ProgressStyle.Point> points,
int progress,
- int progressMax,
- boolean isStyledByProgress
+ int progressMax
) {
if (segments.isEmpty()) {
throw new IllegalArgumentException("List of segments shouldn't be empty");
@@ -571,6 +653,7 @@
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) {
@@ -583,23 +666,21 @@
final Map<Integer, ProgressStyle.Point> positionToPointMap = generatePositionToPointMap(
points);
final SortedSet<Integer> sortedPos = generateSortedPositionSet(startToSegmentMap,
- positionToPointMap, progress, isStyledByProgress);
+ positionToPointMap);
final Map<Integer, ProgressStyle.Segment> startToSplitSegmentMap =
- splitSegmentsByPointsAndProgress(
- startToSegmentMap, sortedPos, progressMax);
+ splitSegmentsByPoints(startToSegmentMap, sortedPos, progressMax);
- return convertToDrawableParts(startToSplitSegmentMap, positionToPointMap, sortedPos,
- progress, progressMax,
- isStyledByProgress);
+ return convertToViewParts(startToSplitSegmentMap, positionToPointMap, sortedPos,
+ progressMax);
}
// 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(
+ private static Map<Integer, ProgressStyle.Segment> splitSegmentsByPoints(
Map<Integer, ProgressStyle.Segment> startToSegmentMap,
SortedSet<Integer> sortedPos,
- int progressMax) {
+ int progressMax
+ ) {
int prevSegStart = 0;
for (Integer pos : sortedPos) {
if (pos == 0 || pos == progressMax) continue;
@@ -624,32 +705,22 @@
return startToSegmentMap;
}
- private static List<Part> convertToDrawableParts(
+ private static List<Part> convertToViewParts(
Map<Integer, ProgressStyle.Segment> startToSegmentMap,
Map<Integer, ProgressStyle.Point> positionToPointMap,
SortedSet<Integer> sortedPos,
- int progress,
- int progressMax,
- boolean isStyledByProgress
+ int progressMax
) {
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;
+ parts.add(new Point(point.getColor()));
}
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));
+ (float) seg.getLength() / progressMax, seg.getColor()));
}
}
@@ -660,11 +731,24 @@
private static int maybeGetFadedColor(@ColorInt int color, boolean fade) {
if (!fade) return color;
- return NotificationProgressDrawable.getFadedColor(color);
+ return getFadedColor(color);
+ }
+
+ /**
+ * Get a color with an opacity that's 40% of the input color.
+ */
+ @ColorInt
+ static int getFadedColor(@ColorInt int color) {
+ return Color.argb(
+ (int) (Color.alpha(color) * 0.4f + 0.5f),
+ Color.red(color),
+ Color.green(color),
+ Color.blue(color));
}
private static Map<Integer, ProgressStyle.Segment> generateStartToSegmentMap(
- List<ProgressStyle.Segment> segments) {
+ List<ProgressStyle.Segment> segments
+ ) {
final Map<Integer, ProgressStyle.Segment> startToSegmentMap = new HashMap<>();
int currentStart = 0; // Initial start position is 0
@@ -681,7 +765,8 @@
}
private static Map<Integer, ProgressStyle.Point> generatePositionToPointMap(
- List<ProgressStyle.Point> points) {
+ List<ProgressStyle.Point> points
+ ) {
final Map<Integer, ProgressStyle.Point> positionToPointMap = new HashMap<>();
for (ProgressStyle.Point point : points) {
@@ -693,14 +778,404 @@
private static SortedSet<Integer> generateSortedPositionSet(
Map<Integer, ProgressStyle.Segment> startToSegmentMap,
- Map<Integer, ProgressStyle.Point> positionToPointMap, int progress,
- boolean isStyledByProgress) {
+ Map<Integer, ProgressStyle.Point> positionToPointMap
+ ) {
final SortedSet<Integer> sortedPos = new TreeSet<>(startToSegmentMap.keySet());
sortedPos.addAll(positionToPointMap.keySet());
- if (isStyledByProgress) {
- sortedPos.add(progress);
- }
return sortedPos;
}
+
+ /**
+ * Processes the list of {@code Part} and convert to a list of
+ * {@code NotificationProgressDrawable.Part}.
+ */
+ @VisibleForTesting
+ public static List<NotificationProgressDrawable.Part> processAndConvertToDrawableParts(
+ List<Part> parts,
+ float totalWidth,
+ float segSegGap,
+ float segPointGap,
+ float pointRadius,
+ boolean hasTrackerIcon
+ ) {
+ List<NotificationProgressDrawable.Part> drawableParts = new ArrayList<>();
+
+ // generally, we will start drawing at (x, y) and end at (x+w, y)
+ float x = (float) 0;
+
+ final int nParts = parts.size();
+ for (int iPart = 0; iPart < nParts; iPart++) {
+ final Part part = parts.get(iPart);
+ final Part prevPart = iPart == 0 ? null : parts.get(iPart - 1);
+ final Part nextPart = iPart + 1 == nParts ? null : parts.get(iPart + 1);
+ if (part instanceof Segment segment) {
+ final float segWidth = segment.mFraction * totalWidth;
+ // Advance the start position to account for a point immediately prior.
+ final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap, x);
+ final float start = x + startOffset;
+ // Retract the end position to account for the padding and a point immediately
+ // after.
+ final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
+ segSegGap, x + segWidth, totalWidth, hasTrackerIcon);
+ final float end = x + segWidth - endOffset;
+
+ drawableParts.add(
+ new NotificationProgressDrawable.Segment(start, end, segment.mColor,
+ segment.mFaded));
+
+ segment.mStart = x;
+ segment.mEnd = x + segWidth;
+
+ // Advance the current position to account for the segment's fraction of the total
+ // width (ignoring offset and padding)
+ x += segWidth;
+ } else if (part instanceof Point point) {
+ final float pointWidth = 2 * pointRadius;
+ float start = x - pointRadius;
+ if (start < 0) start = 0;
+ float end = start + pointWidth;
+ if (end > totalWidth) {
+ end = totalWidth;
+ if (totalWidth > pointWidth) start = totalWidth - pointWidth;
+ }
+
+ drawableParts.add(
+ new NotificationProgressDrawable.Point(start, end, point.mColor));
+ }
+ }
+
+ return drawableParts;
+ }
+
+ private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap,
+ float startX) {
+ if (!(prevPart instanceof Point)) return 0F;
+ final float pointOffset = (startX < pointRadius) ? (pointRadius - startX) : 0;
+ return pointOffset + pointRadius + segPointGap;
+ }
+
+ private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
+ float segPointGap,
+ float segSegGap, float endX, float totalWidth, boolean hasTrackerIcon) {
+ if (nextPart == null) return 0F;
+ if (nextPart instanceof Segment nextSeg) {
+ if (!seg.mFaded && nextSeg.mFaded) {
+ // @see Segment#mFaded
+ return hasTrackerIcon ? 0F : segSegGap;
+ }
+ return segSegGap;
+ }
+
+ final float pointWidth = 2 * pointRadius;
+ final float pointOffset = (endX + pointRadius > totalWidth && totalWidth > pointWidth)
+ ? (endX + pointRadius - totalWidth) : 0;
+ return segPointGap + pointRadius + pointOffset;
+ }
+
+ /**
+ * 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 progressFraction,
+ float totalWidth,
+ boolean isStyledByProgress,
+ float progressGap
+ ) {
+ 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 (startFraction == progressFraction) {
+ iPartFirstSegmentToStyle = iPart;
+ rescaledProgressX = segment.mStart;
+ break;
+ } 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++) {
+ final NotificationProgressDrawable.Part drawablePart = drawableParts.get(iPart);
+ if (drawablePart instanceof NotificationProgressDrawable.Point drawablePoint) {
+ final int color = maybeGetFadedColor(drawablePoint.getColor(), styleRemainingParts);
+ splitDrawableParts.add(
+ new NotificationProgressDrawable.Point(drawablePoint.getStart(),
+ drawablePoint.getEnd(), color));
+ }
+ if (iPart == iPartFirstSegmentToStyle) styleRemainingParts = true;
+ if (drawablePart instanceof NotificationProgressDrawable.Segment drawableSegment) {
+ if (iPart == iPartSegmentToSplit) {
+ 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() < rescaledProgressX
+ && rescaledProgressX < drawableSegment.getEnd()) {
+ splitDrawableParts.add(
+ new NotificationProgressDrawable.Segment(drawableSegment.getStart(),
+ rescaledProgressX - progressGap,
+ drawableSegment.getColor()));
+ final int color = maybeGetFadedColor(drawableSegment.getColor(), true);
+ splitDrawableParts.add(
+ new NotificationProgressDrawable.Segment(rescaledProgressX,
+ drawableSegment.getEnd(), color, true));
+ styleRemainingParts = true;
+ } else {
+ splitDrawableParts.add(
+ new NotificationProgressDrawable.Segment(drawableSegment.getStart(),
+ drawableSegment.getEnd(),
+ drawableSegment.getColor()));
+ styleRemainingParts = true;
+ }
+ } else {
+ final int color = maybeGetFadedColor(drawableSegment.getColor(),
+ styleRemainingParts);
+ splitDrawableParts.add(
+ new NotificationProgressDrawable.Segment(drawableSegment.getStart(),
+ drawableSegment.getEnd(),
+ color, styleRemainingParts));
+ }
+ }
+ }
+
+ return new Pair<>(splitDrawableParts, rescaledProgressX);
+ }
+
+ /**
+ * A part of the progress bar, which is either a {@link Segment} with non-zero length, or a
+ * {@link Point} with zero length.
+ */
+ // TODO: b/372908709 - maybe this should be made private? Only test the final
+ // NotificationDrawable.Parts.
+ // TODO: b/372908709 - rename to BarPart, BarSegment, BarPoint. This avoids naming ambiguity
+ // with the types in NotificationProgressDrawable.
+ public interface Part {
+ }
+
+ /**
+ * A segment is a part of the progress bar with non-zero length. For example, it can
+ * represent a portion in a navigation journey with certain traffic condition.
+ *
+ */
+ public static final class Segment implements Part {
+ private final float mFraction;
+ @ColorInt private final int mColor;
+ /** Whether the segment is faded or not.
+ * <p>
+ * <pre>
+ * When mFaded is set to true, a combination of the following is done to the segment:
+ * 1. The drawing color is mColor with opacity updated to 40%.
+ * 2. The gap between faded and non-faded segments is:
+ * - the segment-segment gap, when there is no tracker icon
+ * - 0, when there is tracker icon
+ * </pre>
+ * </p>
+ */
+ private final boolean mFaded;
+
+ /** Start position (in pixels) */
+ private float mStart;
+ /** End position (in pixels */
+ private float mEnd;
+
+ public Segment(float fraction, @ColorInt int color) {
+ this(fraction, color, false);
+ }
+
+ public Segment(float fraction, @ColorInt int color, boolean faded) {
+ mFraction = fraction;
+ mColor = color;
+ 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="
+ + this.mFaded + "), mStart = " + this.mStart + ", mEnd = " + this.mEnd;
+ }
+
+ // Needed for unit tests
+ @Override
+ public boolean equals(@androidx.annotation.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.mFaded == that.mFaded;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFraction, mColor, mFaded);
+ }
+ }
+
+ /**
+ * A point is a part of the progress bar with zero length. Points are designated points within a
+ * progress bar to visualize distinct stages or milestones. For example, a stop in a multi-stop
+ * ride-share journey.
+ */
+ public static final class Point implements Part {
+ @ColorInt private final int mColor;
+
+ public Point(@ColorInt int color) {
+ mColor = color;
+ }
+
+ @Override
+ public String toString() {
+ return "Point(color=" + this.mColor + ")";
+ }
+
+ // Needed for unit tests.
+ @Override
+ public boolean equals(@androidx.annotation.Nullable Object other) {
+ if (this == other) return true;
+
+ if (other == null || getClass() != other.getClass()) return false;
+
+ Point that = (Point) other;
+
+ return this.mColor == that.mColor;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mColor);
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
index 8629a1c..ef0a5d5 100644
--- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java
+++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
@@ -21,7 +21,6 @@
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
@@ -49,7 +48,8 @@
/**
* This is used by NotificationProgressBar for displaying a custom background. It composes of
- * segments, which have non-zero length, and points, which have zero length.
+ * segments, which have non-zero length varying drawing width, and points, which have zero length
+ * and fixed size for drawing.
*
* @see Segment
* @see Point
@@ -57,14 +57,15 @@
public final class NotificationProgressDrawable extends Drawable {
private static final String TAG = "NotifProgressDrawable";
+ @Nullable
+ private BoundsChangeListener mBoundsChangeListener = null;
+
private State mState;
private boolean mMutated;
private final ArrayList<Part> mParts = new ArrayList<>();
- private boolean mHasTrackerIcon;
private final RectF mSegRectF = new RectF();
- private final Rect mPointRect = new Rect();
private final RectF mPointRectF = new RectF();
private final Paint mFillPaint = new Paint();
@@ -80,27 +81,31 @@
}
/**
- * <p>Set the segment default color for the drawable.</p>
- * <p>Note: changing this property will affect all instances of a drawable loaded from a
- * resource. It is recommended to invoke {@link #mutate()} before changing this property.</p>
- *
- * @param color The color of the stroke
- * @see #mutate()
+ * Returns the gap between two segments.
*/
- public void setSegmentDefaultColor(@ColorInt int color) {
- mState.setSegmentColor(color);
+ public float getSegSegGap() {
+ return mState.mSegSegGap;
}
/**
- * <p>Set the point rect default color for the drawable.</p>
- * <p>Note: changing this property will affect all instances of a drawable loaded from a
- * resource. It is recommended to invoke {@link #mutate()} before changing this property.</p>
- *
- * @param color The color of the point rect
- * @see #mutate()
+ * Returns the gap between a segment and a point.
*/
- public void setPointRectDefaultColor(@ColorInt int color) {
- mState.setPointRectColor(color);
+ public float getSegPointGap() {
+ return mState.mSegPointGap;
+ }
+
+ /**
+ * Returns the gap between a segment and a point.
+ */
+ public float getSegmentMinWidth() {
+ return mState.mSegmentMinWidth;
+ }
+
+ /**
+ * Returns the radius for the points.
+ */
+ public float getPointRadius() {
+ return mState.mPointRadius;
}
/**
@@ -120,47 +125,18 @@
setParts(Arrays.asList(parts));
}
- /**
- * Set whether a tracker is drawn on top of this NotificationProgressDrawable.
- */
- public void setHasTrackerIcon(boolean hasTrackerIcon) {
- if (mHasTrackerIcon != hasTrackerIcon) {
- mHasTrackerIcon = hasTrackerIcon;
- invalidateSelf();
- }
- }
-
@Override
public void draw(@NonNull Canvas canvas) {
- final float pointRadius =
- mState.mPointRadius; // how big the point icon will be, halved
-
- // generally, we will start drawing at (x, y) and end at (x+w, y)
- float x = (float) getBounds().left;
+ final float pointRadius = mState.mPointRadius;
+ final float left = (float) getBounds().left;
final float centerY = (float) getBounds().centerY();
- final float totalWidth = (float) getBounds().width();
- float segPointGap = mState.mSegPointGap;
final int numParts = mParts.size();
for (int iPart = 0; iPart < numParts; iPart++) {
final Part part = mParts.get(iPart);
- final Part prevPart = iPart == 0 ? null : mParts.get(iPart - 1);
- final Part nextPart = iPart + 1 == numParts ? null : mParts.get(iPart + 1);
+ final float start = left + part.mStart;
+ final float end = left + part.mEnd;
if (part instanceof Segment segment) {
- final float segWidth = segment.mFraction * totalWidth;
- // Advance the start position to account for a point immediately prior.
- final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap, x);
- final float start = x + startOffset;
- // Retract the end position to account for the padding and a point immediately
- // after.
- final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
- mState.mSegSegGap, x + segWidth, totalWidth, mHasTrackerIcon);
- final float end = x + segWidth - endOffset;
-
- // Advance the current position to account for the segment's fraction of the total
- // width (ignoring offset and padding)
- x += segWidth;
-
// No space left to draw the segment
if (start > end) continue;
@@ -168,69 +144,25 @@
: mState.mSegmentHeight / 2F;
final float cornerRadius = mState.mSegmentCornerRadius;
- mFillPaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
- : (segment.mFaded ? mState.mFadedSegmentColor : mState.mSegmentColor));
+ mFillPaint.setColor(segment.mColor);
mSegRectF.set(start, centerY - radiusY, end, centerY + radiusY);
canvas.drawRoundRect(mSegRectF, cornerRadius, cornerRadius, mFillPaint);
} else if (part instanceof Point point) {
- final float pointWidth = 2 * pointRadius;
- float start = x - pointRadius;
- if (start < 0) start = 0;
- float end = start + pointWidth;
- if (end > totalWidth) {
- end = totalWidth;
- if (totalWidth > pointWidth) start = totalWidth - pointWidth;
- }
- mPointRect.set((int) start, (int) (centerY - pointRadius), (int) end,
- (int) (centerY + pointRadius));
+ // TODO: b/367804171 - actually use a vector asset for the default point
+ // rather than drawing it as a box?
+ mPointRectF.set(start, centerY - pointRadius, end, centerY + pointRadius);
+ final float inset = mState.mPointRectInset;
+ final float cornerRadius = mState.mPointRectCornerRadius;
+ mPointRectF.inset(inset, inset);
- if (point.mIcon != null) {
- point.mIcon.setBounds(mPointRect);
- point.mIcon.draw(canvas);
- } else {
- // TODO: b/367804171 - actually use a vector asset for the default point
- // rather than drawing it as a box?
- mPointRectF.set(start, centerY - pointRadius, end, centerY + pointRadius);
- final float inset = mState.mPointRectInset;
- final float cornerRadius = mState.mPointRectCornerRadius;
- mPointRectF.inset(inset, inset);
+ mFillPaint.setColor(point.mColor);
- mFillPaint.setColor(point.mColor != Color.TRANSPARENT ? point.mColor
- : (point.mFaded ? mState.mFadedPointRectColor
- : mState.mPointRectColor));
-
- canvas.drawRoundRect(mPointRectF, cornerRadius, cornerRadius, mFillPaint);
- }
+ canvas.drawRoundRect(mPointRectF, cornerRadius, cornerRadius, mFillPaint);
}
}
}
- private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap,
- float startX) {
- if (!(prevPart instanceof Point)) return 0F;
- final float pointOffset = (startX < pointRadius) ? (pointRadius - startX) : 0;
- return pointOffset + pointRadius + segPointGap;
- }
-
- private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
- float segPointGap,
- float segSegGap, float endX, float totalWidth, boolean hasTrackerIcon) {
- if (nextPart == null) return 0F;
- if (nextPart instanceof Segment nextSeg) {
- if (!seg.mFaded && nextSeg.mFaded) {
- // @see Segment#mFaded
- return hasTrackerIcon ? 0F : segSegGap;
- }
- return segSegGap;
- }
-
- final float pointWidth = 2 * pointRadius;
- final float pointOffset = (endX + pointRadius > totalWidth && totalWidth > pointWidth)
- ? (endX + pointRadius - totalWidth) : 0;
- return segPointGap + pointRadius + pointOffset;
- }
-
@Override
public @Config int getChangingConfigurations() {
return super.getChangingConfigurations() | mState.getChangingConfigurations();
@@ -260,6 +192,19 @@
return PixelFormat.UNKNOWN;
}
+ public void setBoundsChangeListener(BoundsChangeListener listener) {
+ mBoundsChangeListener = listener;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+
+ if (mBoundsChangeListener != null) {
+ mBoundsChangeListener.onDrawableBoundsChanged();
+ }
+ }
+
@Override
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Resources.Theme theme)
@@ -384,6 +329,8 @@
// 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(
@@ -392,9 +339,6 @@
state.mSegmentCornerRadius = a.getDimension(
R.styleable.NotificationProgressDrawableSegments_cornerRadius,
state.mSegmentCornerRadius);
- final int color = a.getColor(R.styleable.NotificationProgressDrawableSegments_color,
- state.mSegmentColor);
- setSegmentDefaultColor(color);
}
private void updatePointsFromTypedArray(TypedArray a) {
@@ -413,9 +357,6 @@
state.mPointRectCornerRadius = a.getDimension(
R.styleable.NotificationProgressDrawablePoints_cornerRadius,
state.mPointRectCornerRadius);
- final int color = a.getColor(R.styleable.NotificationProgressDrawablePoints_color,
- state.mPointRectColor);
- setPointRectDefaultColor(color);
}
static int resolveDensity(@Nullable Resources r, int parentDensity) {
@@ -464,65 +405,58 @@
}
/**
- * A part of the progress bar, which is either a S{@link Segment} with non-zero length, or a
- * {@link Point} with zero length.
+ * Listener to receive updates about drawable bounds changing
*/
- public interface Part {
+ public interface BoundsChangeListener {
+ /** Called when bounds have changed */
+ void onDrawableBoundsChanged();
}
/**
- * A segment is a part of the progress bar with non-zero length. For example, it can
- * represent a portion in a navigation journey with certain traffic condition.
- *
+ * A part of the progress bar, which is either a {@link Segment} with non-zero length and
+ * varying drawing width, or a {@link Point} with zero length and fixed size for drawing.
*/
- public static final class Segment implements Part {
- private final float mFraction;
- @ColorInt private final int mColor;
- /** Whether the segment is faded or not.
- * <p>
- * <pre>
- * When mFaded is set to true, a combination of the following is done to the segment:
- * 1. The drawing color is mColor with opacity updated to 40%.
- * 2. The gap between faded and non-faded segments is:
- * - the segment-segment gap, when there is no tracker icon
- * - 0, when there is tracker icon
- * </pre>
- * </p>
- */
- private final boolean mFaded;
+ public abstract static class Part {
+ // 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;
- public Segment(float fraction) {
- this(fraction, Color.TRANSPARENT);
- }
-
- public Segment(float fraction, @ColorInt int color) {
- this(fraction, color, false);
- }
-
- public Segment(float fraction, @ColorInt int color, boolean faded) {
- mFraction = fraction;
+ protected Part(float start, float end, @ColorInt int color) {
+ mStart = start;
+ mEnd = end;
mColor = color;
- mFaded = faded;
}
- public float getFraction() {
- return this.mFraction;
+ public float getStart() {
+ 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;
}
- public boolean getFaded() {
- return this.mFaded;
- }
-
- @Override
- public String toString() {
- return "Segment(fraction=" + this.mFraction + ", color=" + this.mColor + ", faded="
- + this.mFaded + ')';
- }
-
// Needed for unit tests
@Override
public boolean equals(@Nullable Object other) {
@@ -530,80 +464,79 @@
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.mFaded == that.mFaded;
+ Part that = (Part) other;
+ if (Float.compare(this.mStart, that.mStart) != 0) return false;
+ if (Float.compare(this.mEnd, that.mEnd) != 0) return false;
+ return this.mColor == that.mColor;
}
@Override
public int hashCode() {
- return Objects.hash(mFraction, mColor, mFaded);
+ return Objects.hash(mStart, mEnd, mColor);
}
}
/**
- * A point is a part of the progress bar with zero length. Points are designated points within a
- * progressbar to visualize distinct stages or milestones. For example, a stop in a multi-stop
- * ride-share journey.
+ * A segment is a part of the progress bar with non-zero length. For example, it can
+ * represent a portion in a navigation journey with certain traffic condition.
+ * <p>
+ * The start and end positions for drawing a segment are assumed to have been adjusted for
+ * the Points and gaps neighboring the segment.
+ * </p>
*/
- public static final class Point implements Part {
- @Nullable
- private final Drawable mIcon;
- @ColorInt private final int mColor;
+ public static final class Segment extends Part {
+ /**
+ * Whether the segment is faded or not.
+ * <p>
+ * Faded segments and non-faded segments are drawn with different heights.
+ * </p>
+ */
private final boolean mFaded;
- public Point(@Nullable Drawable icon) {
- this(icon, Color.TRANSPARENT, false);
+ public Segment(float start, float end, int color) {
+ this(start, end, color, 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;
+ public Segment(float start, float end, int color, boolean faded) {
+ super(start, end, color);
mFaded = faded;
}
- @Nullable
- public Drawable getIcon() {
- 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 + ", faded=" + this.mFaded
- + ")";
+ return "Segment(start=" + this.mStart + ", end=" + this.mEnd + ", color=" + this.mColor
+ + ", faded=" + this.mFaded + ')';
}
// Needed for unit tests.
@Override
public boolean equals(@Nullable Object other) {
- if (this == other) return true;
+ if (!super.equals(other)) return false;
- 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;
+ Segment that = (Segment) other;
return this.mFaded == that.mFaded;
}
@Override
public int hashCode() {
- return Objects.hash(mIcon, mColor, mFaded);
+ return Objects.hash(super.hashCode(), mFaded);
+ }
+ }
+
+ /**
+ * A point is a part of the progress bar with zero length. Points are designated points within a
+ * progress bar to visualize distinct stages or milestones. For example, a stop in a multi-stop
+ * ride-share journey.
+ */
+ public static final class Point extends Part {
+ public Point(float start, float end, int color) {
+ super(start, end, color);
+ }
+
+ @Override
+ public String toString() {
+ return "Point(start=" + this.mStart + ", end=" + this.mEnd + ", color=" + this.mColor
+ + ")";
}
}
@@ -628,16 +561,14 @@
int mChangingConfigurations;
float mSegSegGap = 0.0f;
float mSegPointGap = 0.0f;
+ float mSegmentMinWidth = 0.0f;
float mSegmentHeight;
float mFadedSegmentHeight;
float mSegmentCornerRadius;
- int mSegmentColor;
- int mFadedSegmentColor;
+ // how big the point icon will be, halved
float mPointRadius;
float mPointRectInset;
float mPointRectCornerRadius;
- int mPointRectColor;
- int mFadedPointRectColor;
int[] mThemeAttrs;
int[] mThemeAttrsSegments;
@@ -652,16 +583,13 @@
mChangingConfigurations = orig.mChangingConfigurations;
mSegSegGap = orig.mSegSegGap;
mSegPointGap = orig.mSegPointGap;
+ mSegmentMinWidth = orig.mSegmentMinWidth;
mSegmentHeight = orig.mSegmentHeight;
mFadedSegmentHeight = orig.mFadedSegmentHeight;
mSegmentCornerRadius = orig.mSegmentCornerRadius;
- mSegmentColor = orig.mSegmentColor;
- mFadedSegmentColor = orig.mFadedSegmentColor;
mPointRadius = orig.mPointRadius;
mPointRectInset = orig.mPointRectInset;
mPointRectCornerRadius = orig.mPointRectCornerRadius;
- mPointRectColor = orig.mPointRectColor;
- mFadedPointRectColor = orig.mFadedPointRectColor;
mThemeAttrs = orig.mThemeAttrs;
mThemeAttrsSegments = orig.mThemeAttrsSegments;
@@ -674,6 +602,18 @@
}
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);
@@ -740,28 +680,6 @@
applyDensityScaling(sourceDensity, targetDensity);
}
}
-
- public void setSegmentColor(int color) {
- mSegmentColor = color;
- mFadedSegmentColor = getFadedColor(color);
- }
-
- public void setPointRectColor(int color) {
- mPointRectColor = color;
- mFadedPointRectColor = getFadedColor(color);
- }
- }
-
- /**
- * Get a color with an opacity that's 25% of the input color.
- */
- @ColorInt
- static int getFadedColor(@ColorInt int color) {
- return Color.argb(
- (int) (Color.alpha(color) * 0.4f + 0.5f),
- Color.red(color),
- Color.green(color),
- Color.blue(color));
}
@Override
diff --git a/core/res/res/drawable/notification_progress.xml b/core/res/res/drawable/notification_progress.xml
index 5d272fb..ff5450e 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 728c856..8372aec 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7572,25 +7572,31 @@
<!-- NotificationProgressDrawable class -->
<!-- ================================== -->
- <!-- Drawable used to render a segmented bar, with segments and points. -->
+ <!-- Drawable used to render a notification progress bar, with segments and points. -->
<!-- @hide internal use only -->
<declare-styleable name="NotificationProgressDrawable">
- <!-- Default color for the parts. -->
+ <!-- The gap between two segments. -->
<attr name="segSegGap" format="dimension" />
+ <!-- The gap between a segment and a point. -->
<attr name="segPointGap" format="dimension" />
</declare-styleable>
<!-- Used to config the segments of a NotificationProgressDrawable. -->
<!-- @hide internal use only -->
<declare-styleable name="NotificationProgressDrawableSegments">
- <!-- Height of the solid segments -->
+ <!-- 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 -->
- <attr name="fadedHeight" format="dimension"/>
+ <!-- Height of the faded segments. -->
+ <attr name="fadedHeight" format="dimension" />
<!-- Corner radius of the segment rect. -->
<attr name="cornerRadius" format="dimension" />
- <!-- Default color of the segment. -->
- <attr name="color" />
</declare-styleable>
<!-- Used to config the points of a NotificationProgressDrawable. -->
@@ -7602,8 +7608,6 @@
<attr name="inset" />
<!-- Corner radius of the point rect. -->
<attr name="cornerRadius"/>
- <!-- Default color of the point rect. -->
- <attr name="color" />
</declare-styleable>
<!-- ========================== -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 4f7351c..d6b8704 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -899,6 +899,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 f89ca44..6c014e9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3950,6 +3950,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/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index ca6ad6f..7be6950 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -2504,6 +2504,21 @@
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_setProgressSegments() {
+ final List<Notification.ProgressStyle.Segment> segments = List.of(
+ new Notification.ProgressStyle.Segment(100).setColor(Color.WHITE),
+ new Notification.ProgressStyle.Segment(50).setColor(Color.RED),
+ new Notification.ProgressStyle.Segment(50).setColor(Color.BLUE)
+ );
+
+ final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
+ progressStyle1.setProgressSegments(segments);
+
+ assertThat(progressStyle1.getProgressSegments()).isEqualTo(segments);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void progressStyle_addProgressPoint_dropsNegativePoints() {
// GIVEN
final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
@@ -2532,6 +2547,21 @@
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_setProgressPoints() {
+ final List<Notification.ProgressStyle.Point> points = List.of(
+ new Notification.ProgressStyle.Point(0).setColor(Color.WHITE),
+ new Notification.ProgressStyle.Point(50).setColor(Color.RED),
+ new Notification.ProgressStyle.Point(100).setColor(Color.BLUE)
+ );
+
+ final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
+ progressStyle1.setProgressPoints(points);
+
+ assertThat(progressStyle1.getProgressPoints()).isEqualTo(points);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void progressStyle_createProgressModel_ignoresPointsExceedingMax() {
// GIVEN
final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
@@ -2673,11 +2703,58 @@
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_setProgressIndeterminate() {
+ final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
+ progressStyle1.setProgressIndeterminate(true);
+ assertThat(progressStyle1.isProgressIndeterminate()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void progressStyle_styledByProgress_defaultValueTrue() {
final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
assertThat(progressStyle1.isStyledByProgress()).isTrue();
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_setStyledByProgress() {
+ final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
+ progressStyle1.setStyledByProgress(false);
+ assertThat(progressStyle1.isStyledByProgress()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_point() {
+ final int id = 1;
+ final int position = 10;
+ final int color = Color.RED;
+
+ final Notification.ProgressStyle.Point point =
+ new Notification.ProgressStyle.Point(position).setId(id).setColor(color);
+
+ assertEquals(id, point.getId());
+ assertEquals(position, point.getPosition());
+ assertEquals(color, point.getColor());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_segment() {
+ final int id = 1;
+ final int length = 100;
+ final int color = Color.RED;
+
+ final Notification.ProgressStyle.Segment segment =
+ new Notification.ProgressStyle.Segment(length).setId(id).setColor(color);
+
+ assertEquals(id, segment.getId());
+ assertEquals(length, segment.getLength());
+ assertEquals(color, segment.getColor());
+ }
+
private void assertValid(Notification.Colors c) {
// Assert that all colors are populated
assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
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 d26bb35..f105ec3 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -20,12 +20,13 @@
import android.app.Notification.ProgressStyle;
import android.graphics.Color;
+import android.util.Pair;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.internal.widget.NotificationProgressDrawable.Part;
-import com.android.internal.widget.NotificationProgressDrawable.Point;
-import com.android.internal.widget.NotificationProgressDrawable.Segment;
+import com.android.internal.widget.NotificationProgressBar.Part;
+import com.android.internal.widget.NotificationProgressBar.Point;
+import com.android.internal.widget.NotificationProgressBar.Segment;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,183 +38,303 @@
public class NotificationProgressBarTest {
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_segmentsIsEmpty() {
+ public void processAndConvertToParts_segmentsIsEmpty() {
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);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_segmentsLengthNotMatchingProgressMax() {
+ public void processAndConvertToParts_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);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_segmentLengthIsNegative() {
+ public void processAndConvertToParts_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);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_segmentLengthIsZero() {
+ public void processAndConvertToParts_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);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_progressIsNegative() {
+ public void processAndConvertToParts_progressIsNegative() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
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);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test
- public void processAndConvertToDrawableParts_progressIsZero() {
+ public void processAndConvertToParts_progressIsZero() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 0;
int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ 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.Segment(0, 300, Color.RED)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
boolean isStyledByProgress = true;
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
+ 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)));
- List<Part> expected = new ArrayList<>(List.of(new Segment(1f, fadedRed, true)));
-
- assertThat(parts).isEqualTo(expected);
+ assertThat(p.second).isEqualTo(0);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@Test
- public void processAndConvertToDrawableParts_progressAtMax() {
+ public void processAndConvertToParts_progressAtMax() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 100;
int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ 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.Segment(0, 300, Color.RED)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
boolean isStyledByProgress = true;
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
+ Pair<List<NotificationProgressDrawable.Part>, Float> p =
+ NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts,
+ segmentMinWidth, pointRadius, (float) progress / progressMax, 300,
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
- List<Part> expected = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
-
- assertThat(parts).isEqualTo(expected);
+ assertThat(p.second).isEqualTo(300);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_progressAboveMax() {
+ public void processAndConvertToParts_progressAboveMax() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
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,
- progressMax, isStyledByProgress);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_pointPositionIsNegative() {
+ public void processAndConvertToParts_pointPositionIsNegative() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100));
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);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_pointPositionAboveMax() {
+ public void processAndConvertToParts_pointPositionAboveMax() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100));
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);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test
- public void processAndConvertToDrawableParts_multipleSegmentsWithoutPoints() {
+ public void processAndConvertToParts_multipleSegmentsWithoutPoints() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
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, progressMax, isStyledByProgress);
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(
+ segments, points, progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(List.of(
+ new Segment(0.50f, Color.RED),
+ new Segment(0.50f, Color.GREEN)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ 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.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)));
- List<Part> expected = new ArrayList<>(List.of(
- new Segment(0.50f, Color.RED),
- new Segment(0.10f, Color.GREEN),
- new Segment(0.40f, fadedGreen, true)));
-
- assertThat(parts).isEqualTo(expected);
+ assertThat(p.second).isEqualTo(180);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@Test
- public void processAndConvertToDrawableParts_singleSegmentWithPoints() {
+ public void processAndConvertToParts_multipleSegmentsWithoutPoints_noTracker() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
+ segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 60;
+ int progressMax = 100;
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(List.of(
+ new Segment(0.50f, Color.RED),
+ new Segment(0.50f, Color.GREEN)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = false;
+
+ List<NotificationProgressDrawable.Part> drawableParts =
+ NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth,
+ 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(p.second).isEqualTo(180);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
+
+ @Test
+ public void processAndConvertToParts_singleSegmentWithPoints() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
List<ProgressStyle.Point> points = new ArrayList<>();
@@ -223,31 +344,77 @@
points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
int progress = 60;
int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(List.of(
+ new Segment(0.15f, Color.BLUE),
+ new Point(Color.RED),
+ new Segment(0.10f, Color.BLUE),
+ new Point(Color.BLUE),
+ new Segment(0.35f, Color.BLUE),
+ new Point(Color.BLUE),
+ new Segment(0.15f, Color.BLUE),
+ new Point(Color.YELLOW),
+ new Segment(0.25f, Color.BLUE)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ 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.Segment(0, 35, Color.BLUE),
+ new NotificationProgressDrawable.Point(39, 51, Color.RED),
+ new NotificationProgressDrawable.Segment(55, 65, Color.BLUE),
+ 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, 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)));
- List<Part> expected = new ArrayList<>(List.of(
- new Segment(0.15f, Color.BLUE),
- new Point(null, Color.RED),
- new Segment(0.10f, Color.BLUE),
- new Point(null, Color.BLUE),
- new Segment(0.35f, Color.BLUE),
- new Point(null, Color.BLUE),
- new Segment(0.15f, fadedBlue, true),
- new Point(null, fadedYellow, true),
- new Segment(0.25f, fadedBlue, true)));
-
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
-
- assertThat(parts).isEqualTo(expected);
+ assertThat(p.second).isEqualTo(182.38356F);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@Test
- public void processAndConvertToDrawableParts_multipleSegmentsWithPoints() {
+ public void processAndConvertToParts_multipleSegmentsWithPoints() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -258,32 +425,81 @@
points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
int progress = 60;
int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(List.of(
+ new Segment(0.15f, Color.RED),
+ new Point(Color.RED),
+ new Segment(0.10f, Color.RED),
+ new Point(Color.BLUE),
+ new Segment(0.25f, Color.RED),
+ new Segment(0.10f, Color.GREEN),
+ new Point(Color.BLUE),
+ new Segment(0.15f, Color.GREEN),
+ new Point(Color.YELLOW),
+ new Segment(0.25f, Color.GREEN)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ 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.Segment(0, 35, Color.RED),
+ new NotificationProgressDrawable.Point(39, 51, Color.RED),
+ new NotificationProgressDrawable.Segment(55, 65, Color.RED),
+ new NotificationProgressDrawable.Point(69, 81, Color.BLUE),
+ 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, 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;
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
+ 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)));
- List<Part> expected = new ArrayList<>(List.of(
- new Segment(0.15f, Color.RED),
- new Point(null, Color.RED),
- new Segment(0.10f, Color.RED),
- new Point(null, Color.BLUE),
- new Segment(0.25f, Color.RED),
- new Segment(0.10f, Color.GREEN),
- new Point(null, Color.BLUE),
- new Segment(0.15f, fadedGreen, true),
- new Point(null, fadedYellow, true),
- new Segment(0.25f, fadedGreen, true)));
-
- assertThat(parts).isEqualTo(expected);
+ assertThat(p.second).isEqualTo(182.7619F);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@Test
- public void processAndConvertToDrawableParts_multipleSegmentsWithPoints_notStyledByProgress() {
+ public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -293,21 +509,251 @@
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, progressMax, isStyledByProgress);
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
- List<Part> expected = new ArrayList<>(List.of(
+ List<Part> expectedParts = new ArrayList<>(List.of(
new Segment(0.15f, Color.RED),
- new Point(null, Color.RED),
+ new Point(Color.RED),
new Segment(0.10f, Color.RED),
- new Point(null, Color.BLUE),
+ new Point(Color.BLUE),
new Segment(0.25f, Color.RED),
new Segment(0.25f, Color.GREEN),
- new Point(null, Color.YELLOW),
+ new Point(Color.YELLOW),
new Segment(0.25f, Color.GREEN)));
- assertThat(parts).isEqualTo(expected);
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ 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.Segment(0, 35, Color.RED),
+ new NotificationProgressDrawable.Point(39, 51, Color.RED),
+ new NotificationProgressDrawable.Segment(55, 65, Color.RED),
+ new NotificationProgressDrawable.Point(69, 81, Color.BLUE),
+ new NotificationProgressDrawable.Segment(85, 146, Color.RED),
+ new NotificationProgressDrawable.Segment(150, 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 = 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);
}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 755f472..2fed138 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -233,6 +233,16 @@
}
/**
+ * Returns whether the multiple desktops feature is enabled for this device (both backend and
+ * frontend implementations).
+ */
+ public static boolean enableMultipleDesktops(@NonNull Context context) {
+ return Flags.enableMultipleDesktopsBackend()
+ && Flags.enableMultipleDesktopsFrontend()
+ && canEnterDesktopMode(context);
+ }
+
+ /**
* @return {@code true} if this device is requesting to show the app handle despite non
* necessarily enabling desktop mode
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
new file mode 100644
index 0000000..5018fdb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
@@ -0,0 +1,288 @@
+/*
+ * 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.wm.shell.animation;
+
+import static com.android.wm.shell.transition.DefaultSurfaceAnimator.setupValueAnimator;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
+
+import java.util.function.Consumer;
+
+/**
+ * Animation implementation for size-changing window container animations. Ported from
+ * {@link com.android.server.wm.WindowChangeAnimationSpec}.
+ * <p>
+ * This animation behaves slightly differently depending on whether the window is growing
+ * or shrinking:
+ * <ul>
+ * <li>If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old)
+ * snapshot.
+ * <li>If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker
+ * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into
+ * place.
+ * </ul>
+ */
+public class SizeChangeAnimation {
+ private final Rect mTmpRect = new Rect();
+ final Transformation mTmpTransform = new Transformation();
+ final Matrix mTmpMatrix = new Matrix();
+ final float[] mTmpFloats = new float[9];
+ final float[] mTmpVecs = new float[4];
+
+ private final Animation mAnimation;
+ private final Animation mSnapshotAnim;
+
+ private final ValueAnimator mAnimator = ValueAnimator.ofFloat(0f, 1f);
+
+ /**
+ * The maximum of stretching applied to any surface during interpolation (since the animation
+ * is a combination of stretching/cropping/fading).
+ */
+ private static final float SCALE_FACTOR = 0.7f;
+
+ /**
+ * Since this animation is made of several sub-animations, we want to pre-arrange the
+ * sub-animations on a "virtual timeline" and then drive the overall progress in lock-step.
+ *
+ * To do this, we have a single value-animator which animates progress from 0-1 with an
+ * arbitrary duration and interpolator. Then we convert the progress to a frame in our virtual
+ * timeline to get the interpolated transforms.
+ *
+ * The APIs for arranging the sub-animations use integral frame numbers, so we need to pick
+ * an integral "duration" for our virtual timeline. That's what this constant specifies. It
+ * is effectively an animation "resolution" since it divides-up the 0-1 interpolation-space.
+ */
+ private static final int ANIMATION_RESOLUTION = 1000;
+
+ public SizeChangeAnimation(Rect startBounds, Rect endBounds) {
+ mAnimation = buildContainerAnimation(startBounds, endBounds);
+ mSnapshotAnim = buildSnapshotAnimation(startBounds, endBounds);
+ }
+
+ /**
+ * Initialize a size-change animation for a container leash.
+ */
+ public void initialize(SurfaceControl leash, SurfaceControl snapshot,
+ SurfaceControl.Transaction startT) {
+ startT.reparent(snapshot, leash);
+ startT.setPosition(snapshot, 0, 0);
+ startT.show(snapshot);
+ startT.show(leash);
+ apply(startT, leash, snapshot, 0.f);
+ }
+
+ /**
+ * Initialize a size-change animation for a view containing the leash surface(s).
+ *
+ * Note that this **will** apply {@param startToApply}!
+ */
+ public void initialize(View view, SurfaceControl leash, SurfaceControl snapshot,
+ SurfaceControl.Transaction startToApply) {
+ startToApply.reparent(snapshot, leash);
+ startToApply.setPosition(snapshot, 0, 0);
+ startToApply.show(snapshot);
+ startToApply.show(leash);
+ apply(view, startToApply, leash, snapshot, 0.f);
+ }
+
+ private ValueAnimator buildAnimatorInner(ValueAnimator.AnimatorUpdateListener updater,
+ SurfaceControl leash, SurfaceControl snapshot, Consumer<Animator> onFinish,
+ SurfaceControl.Transaction transaction, @Nullable View view) {
+ return setupValueAnimator(mAnimator, updater, (anim) -> {
+ transaction.reparent(snapshot, null);
+ if (view != null) {
+ view.setClipBounds(null);
+ view.setAnimationMatrix(null);
+ transaction.setCrop(leash, null);
+ }
+ transaction.apply();
+ transaction.close();
+ onFinish.accept(anim);
+ });
+ }
+
+ /**
+ * Build an animator which works on a pair of surface controls (where the snapshot is assumed
+ * to be a child of the main leash).
+ *
+ * @param onFinish Called when animation finishes. This is called on the anim thread!
+ */
+ public ValueAnimator buildAnimator(SurfaceControl leash, SurfaceControl snapshot,
+ Consumer<Animator> onFinish) {
+ final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ Choreographer choreographer = Choreographer.getInstance();
+ return buildAnimatorInner(animator -> {
+ // The finish callback in buildSurfaceAnimation will ensure that the animation ends
+ // with fraction 1.
+ final float progress = Math.clamp(animator.getAnimatedFraction(), 0.f, 1.f);
+ apply(transaction, leash, snapshot, progress);
+ transaction.setFrameTimelineVsync(choreographer.getVsyncId());
+ transaction.apply();
+ }, leash, snapshot, onFinish, transaction, null /* view */);
+ }
+
+ /**
+ * Build an animator which works on a view that contains a pair of surface controls (where
+ * the snapshot is assumed to be a child of the main leash).
+ *
+ * @param onFinish Called when animation finishes. This is called on the anim thread!
+ */
+ public ValueAnimator buildViewAnimator(View view, SurfaceControl leash,
+ SurfaceControl snapshot, Consumer<Animator> onFinish) {
+ final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ return buildAnimatorInner(animator -> {
+ // The finish callback in buildSurfaceAnimation will ensure that the animation ends
+ // with fraction 1.
+ final float progress = Math.clamp(animator.getAnimatedFraction(), 0.f, 1.f);
+ apply(view, transaction, leash, snapshot, progress);
+ }, leash, snapshot, onFinish, transaction, view);
+ }
+
+ /** Animation for the whole container (snapshot is inside this container). */
+ private static AnimationSet buildContainerAnimation(Rect startBounds, Rect endBounds) {
+ final long duration = ANIMATION_RESOLUTION;
+ boolean growing = endBounds.width() - startBounds.width()
+ + endBounds.height() - startBounds.height() >= 0;
+ long scalePeriod = (long) (duration * SCALE_FACTOR);
+ float startScaleX = SCALE_FACTOR * ((float) startBounds.width()) / endBounds.width()
+ + (1.f - SCALE_FACTOR);
+ float startScaleY = SCALE_FACTOR * ((float) startBounds.height()) / endBounds.height()
+ + (1.f - SCALE_FACTOR);
+ final AnimationSet animSet = new AnimationSet(true);
+
+ final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1);
+ scaleAnim.setDuration(scalePeriod);
+ if (!growing) {
+ scaleAnim.setStartOffset(duration - scalePeriod);
+ }
+ animSet.addAnimation(scaleAnim);
+ final Animation translateAnim = new TranslateAnimation(startBounds.left,
+ endBounds.left, startBounds.top, endBounds.top);
+ translateAnim.setDuration(duration);
+ animSet.addAnimation(translateAnim);
+ Rect startClip = new Rect(startBounds);
+ Rect endClip = new Rect(endBounds);
+ startClip.offsetTo(0, 0);
+ endClip.offsetTo(0, 0);
+ final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
+ clipAnim.setDuration(duration);
+ animSet.addAnimation(clipAnim);
+ animSet.initialize(startBounds.width(), startBounds.height(),
+ endBounds.width(), endBounds.height());
+ return animSet;
+ }
+
+ /** The snapshot surface is assumed to be a child of the container surface. */
+ private static AnimationSet buildSnapshotAnimation(Rect startBounds, Rect endBounds) {
+ final long duration = ANIMATION_RESOLUTION;
+ boolean growing = endBounds.width() - startBounds.width()
+ + endBounds.height() - startBounds.height() >= 0;
+ long scalePeriod = (long) (duration * SCALE_FACTOR);
+ float endScaleX = 1.f / (SCALE_FACTOR * ((float) startBounds.width()) / endBounds.width()
+ + (1.f - SCALE_FACTOR));
+ float endScaleY = 1.f / (SCALE_FACTOR * ((float) startBounds.height()) / endBounds.height()
+ + (1.f - SCALE_FACTOR));
+
+ AnimationSet snapAnimSet = new AnimationSet(true);
+ // Animation for the "old-state" snapshot that is atop the task.
+ final Animation snapAlphaAnim = new AlphaAnimation(1.f, 0.f);
+ snapAlphaAnim.setDuration(scalePeriod);
+ if (!growing) {
+ snapAlphaAnim.setStartOffset(duration - scalePeriod);
+ }
+ snapAnimSet.addAnimation(snapAlphaAnim);
+ final Animation snapScaleAnim =
+ new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY);
+ snapScaleAnim.setDuration(duration);
+ snapAnimSet.addAnimation(snapScaleAnim);
+ snapAnimSet.initialize(startBounds.width(), startBounds.height(),
+ endBounds.width(), endBounds.height());
+ return snapAnimSet;
+ }
+
+ private void calcCurrentClipBounds(Rect outClip, Transformation fromTransform) {
+ // The following applies an inverse scale to the clip-rect so that it crops "after" the
+ // scale instead of before.
+ mTmpVecs[1] = mTmpVecs[2] = 0;
+ mTmpVecs[0] = mTmpVecs[3] = 1;
+ fromTransform.getMatrix().mapVectors(mTmpVecs);
+
+ mTmpVecs[0] = 1.f / mTmpVecs[0];
+ mTmpVecs[3] = 1.f / mTmpVecs[3];
+ final Rect clipRect = fromTransform.getClipRect();
+ outClip.left = (int) (clipRect.left * mTmpVecs[0] + 0.5f);
+ outClip.right = (int) (clipRect.right * mTmpVecs[0] + 0.5f);
+ outClip.top = (int) (clipRect.top * mTmpVecs[3] + 0.5f);
+ outClip.bottom = (int) (clipRect.bottom * mTmpVecs[3] + 0.5f);
+ }
+
+ private void apply(SurfaceControl.Transaction t, SurfaceControl leash, SurfaceControl snapshot,
+ float progress) {
+ long currentPlayTime = (long) (((float) ANIMATION_RESOLUTION) * progress);
+ // update thumbnail surface
+ mSnapshotAnim.getTransformation(currentPlayTime, mTmpTransform);
+ t.setMatrix(snapshot, mTmpTransform.getMatrix(), mTmpFloats);
+ t.setAlpha(snapshot, mTmpTransform.getAlpha());
+
+ // update container surface
+ mAnimation.getTransformation(currentPlayTime, mTmpTransform);
+ final Matrix matrix = mTmpTransform.getMatrix();
+ t.setMatrix(leash, matrix, mTmpFloats);
+
+ calcCurrentClipBounds(mTmpRect, mTmpTransform);
+ t.setCrop(leash, mTmpRect);
+ }
+
+ private void apply(View view, SurfaceControl.Transaction tmpT, SurfaceControl leash,
+ SurfaceControl snapshot, float progress) {
+ long currentPlayTime = (long) (((float) ANIMATION_RESOLUTION) * progress);
+ // update thumbnail surface
+ mSnapshotAnim.getTransformation(currentPlayTime, mTmpTransform);
+ tmpT.setMatrix(snapshot, mTmpTransform.getMatrix(), mTmpFloats);
+ tmpT.setAlpha(snapshot, mTmpTransform.getAlpha());
+
+ // update container surface
+ mAnimation.getTransformation(currentPlayTime, mTmpTransform);
+ final Matrix matrix = mTmpTransform.getMatrix();
+ mTmpMatrix.set(matrix);
+ // animationMatrix is applied after getTranslation, so "move" the translate to the end.
+ mTmpMatrix.preTranslate(-view.getTranslationX(), -view.getTranslationY());
+ mTmpMatrix.postTranslate(view.getTranslationX(), view.getTranslationY());
+ view.setAnimationMatrix(mTmpMatrix);
+
+ calcCurrentClipBounds(mTmpRect, mTmpTransform);
+ tmpT.setCrop(leash, mTmpRect);
+ view.setClipBounds(mTmpRect);
+
+ // this takes stuff out of mTmpT so mTmpT can be re-used immediately
+ view.getViewRootImpl().applyTransactionOnDraw(tmpT);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
index d8884f6..f5aaaad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
@@ -33,6 +33,7 @@
import com.android.wm.shell.shared.TransactionPool;
import java.util.ArrayList;
+import java.util.function.Consumer;
public class DefaultSurfaceAnimator {
@@ -58,42 +59,12 @@
// Animation length is already expected to be scaled.
va.overrideDurationScale(1.0f);
va.setDuration(anim.computeDurationHint());
- va.addUpdateListener(updateListener);
- va.addListener(new AnimatorListenerAdapter() {
- // It is possible for the end/cancel to be called more than once, which may cause
- // issues if the animating surface has already been released. Track the finished
- // state here to skip duplicate callbacks. See b/252872225.
- private boolean mFinished;
-
- @Override
- public void onAnimationEnd(Animator animation) {
- onFinish();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- onFinish();
- }
-
- private void onFinish() {
- if (mFinished) return;
- mFinished = true;
- // Apply transformation of end state in case the animation is canceled.
- if (va.getAnimatedFraction() < 1f) {
- va.setCurrentFraction(1f);
- }
-
- pool.release(transaction);
- mainExecutor.execute(() -> {
- animations.remove(va);
- finishCallback.run();
- });
- // The update listener can continue to be called after the animation has ended if
- // end() is called manually again before the finisher removes the animation.
- // Remove it manually here to prevent animating a released surface.
- // See b/252872225.
- va.removeUpdateListener(updateListener);
- }
+ setupValueAnimator(va, updateListener, (vanim) -> {
+ pool.release(transaction);
+ mainExecutor.execute(() -> {
+ animations.remove(vanim);
+ finishCallback.run();
+ });
});
animations.add(va);
}
@@ -188,4 +159,50 @@
}
}
}
+
+ /**
+ * Setup some callback logic on a value-animator. This helper ensures that a value animator
+ * finishes at its final fraction (1f) and that relevant callbacks are only called once.
+ */
+ public static ValueAnimator setupValueAnimator(ValueAnimator animator,
+ ValueAnimator.AnimatorUpdateListener updateListener,
+ Consumer<ValueAnimator> afterFinish) {
+ animator.addUpdateListener(updateListener);
+ animator.addListener(new AnimatorListenerAdapter() {
+ // It is possible for the end/cancel to be called more than once, which may cause
+ // issues if the animating surface has already been released. Track the finished
+ // state here to skip duplicate callbacks. See b/252872225.
+ private boolean mFinished;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onFinish();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ onFinish();
+ }
+
+ private void onFinish() {
+ if (mFinished) return;
+ mFinished = true;
+ // Apply transformation of end state in case the animation is canceled.
+ if (animator.getAnimatedFraction() < 1f) {
+ animator.setCurrentFraction(1f);
+ }
+ afterFinish.accept(animator);
+ // The update listener can continue to be called after the animation has ended if
+ // end() is called manually again before the finisher removes the animation.
+ // Remove it manually here to prevent animating a released surface.
+ // See b/252872225.
+ animator.removeUpdateListener(updateListener);
+ }
+ });
+ return animator;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 1689bb5..36c3e97 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -55,6 +55,7 @@
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static com.android.internal.policy.TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CHANGE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
@@ -69,6 +70,7 @@
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -104,6 +106,7 @@
import com.android.internal.protolog.ProtoLog;
import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.animation.SizeChangeAnimation;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -422,6 +425,14 @@
ROTATION_ANIMATION_ROTATE, 0 /* flags */, animations, onAnimFinish);
continue;
}
+
+ if (Flags.portWindowSizeAnimation() && isTask
+ && TransitionInfo.isIndependent(change, info)
+ && change.getSnapshot() != null) {
+ startBoundsChangeAnimation(startTransaction, animations, change, onAnimFinish,
+ mMainExecutor);
+ continue;
+ }
}
// Hide the invisible surface directly without animating it if there is a display
@@ -734,6 +745,21 @@
}
}
+ private void startBoundsChangeAnimation(@NonNull SurfaceControl.Transaction startT,
+ @NonNull ArrayList<Animator> animations, @NonNull TransitionInfo.Change change,
+ @NonNull Runnable finishCb, @NonNull ShellExecutor mainExecutor) {
+ final SizeChangeAnimation sca =
+ new SizeChangeAnimation(change.getStartAbsBounds(), change.getEndAbsBounds());
+ sca.initialize(change.getLeash(), change.getSnapshot(), startT);
+ final ValueAnimator va = sca.buildAnimator(change.getLeash(), change.getSnapshot(),
+ (animator) -> mainExecutor.execute(() -> {
+ animations.remove(animator);
+ finishCb.run();
+ }));
+ va.setDuration(DEFAULT_APP_TRANSITION_DURATION);
+ animations.add(va);
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
index 068074a..8e52a00 100644
--- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
+++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
@@ -38,6 +38,7 @@
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.os.Bundle;
+import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -301,8 +302,13 @@
.setWorkSource(mRequest.getWorkSource())
.setHiddenFromAppOps(true)
.build();
- mLocationManager.requestLocationUpdates(mProvider, request,
- mContext.getMainExecutor(), this);
+
+ try {
+ mLocationManager.requestLocationUpdates(
+ mProvider, request, mContext.getMainExecutor(), this);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to request location updates");
+ }
}
}
}
@@ -311,7 +317,11 @@
synchronized (mLock) {
int requestCode = mNextFlushCode++;
mPendingFlushes.put(requestCode, callback);
- mLocationManager.requestFlush(mProvider, this, requestCode);
+ try {
+ mLocationManager.requestFlush(mProvider, this, requestCode);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to request flush");
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index a9db0b7..faf736a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -31,7 +31,7 @@
class FakeHomeStatusBarViewModel(
override val operatorNameViewModel: StatusBarOperatorNameViewModel
) : HomeStatusBarViewModel {
- private val areNotificationLightsOut = MutableStateFlow(false)
+ override val areNotificationsLightsOut = MutableStateFlow(false)
override val isTransitioningFromLockscreenToOccluded = MutableStateFlow(false)
@@ -77,14 +77,12 @@
override val iconBlockList: MutableStateFlow<List<String>> = MutableStateFlow(listOf())
- override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut
-
val darkRegions = mutableListOf<Rect>()
var darkIconTint = Color.BLACK
var lightIconTint = Color.WHITE
- override fun areaTint(displayId: Int): Flow<StatusBarTintColor> =
+ override val areaTint: Flow<StatusBarTintColor> =
MutableStateFlow(
StatusBarTintColor { viewBounds ->
if (DarkIconDispatcher.isInAreas(darkRegions, viewBounds)) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index e91875c..a70b777 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -22,6 +22,7 @@
import android.app.StatusBarManager.DISABLE_NONE
import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
+import android.content.testableContext
import android.graphics.Rect
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -59,7 +60,6 @@
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip
import com.android.systemui.statusbar.data.model.StatusBarMode
-import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
@@ -363,7 +363,7 @@
activeNotificationListRepository.activeNotifications.value =
activeNotificationsStore(testNotifications)
- val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+ val actual by collectLastValue(underTest.areNotificationsLightsOut)
assertThat(actual).isTrue()
}
@@ -377,7 +377,7 @@
activeNotificationListRepository.activeNotifications.value =
activeNotificationsStore(emptyList())
- val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+ val actual by collectLastValue(underTest.areNotificationsLightsOut)
assertThat(actual).isFalse()
}
@@ -391,7 +391,7 @@
activeNotificationListRepository.activeNotifications.value =
activeNotificationsStore(emptyList())
- val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+ val actual by collectLastValue(underTest.areNotificationsLightsOut)
assertThat(actual).isFalse()
}
@@ -405,7 +405,7 @@
activeNotificationListRepository.activeNotifications.value =
activeNotificationsStore(testNotifications)
- val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+ val actual by collectLastValue(underTest.areNotificationsLightsOut)
assertThat(actual).isFalse()
}
@@ -415,7 +415,7 @@
fun areNotificationsLightsOut_requiresFlagEnabled() =
kosmos.runTest {
assertLogsWtf {
- val flow = underTest.areNotificationsLightsOut(DISPLAY_ID)
+ val flow = underTest.areNotificationsLightsOut
assertThat(flow).isEqualTo(emptyFlow<Boolean>())
}
}
@@ -1005,11 +1005,11 @@
@Test
fun areaTint_viewIsInDarkBounds_getsDarkTint() =
kosmos.runTest {
- val displayId = 321
+ val displayId = testableContext.displayId
fakeDarkIconRepository.darkState(displayId).value =
SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC)
- val areaTint by collectLastValue(underTest.areaTint(displayId))
+ val areaTint by collectLastValue(underTest.areaTint)
val tint = areaTint?.tint(Rect(1, 1, 3, 3))
@@ -1019,11 +1019,11 @@
@Test
fun areaTint_viewIsNotInDarkBounds_getsDefaultTint() =
kosmos.runTest {
- val displayId = 321
+ val displayId = testableContext.displayId
fakeDarkIconRepository.darkState(displayId).value =
SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC)
- val areaTint by collectLastValue(underTest.areaTint(displayId))
+ val areaTint by collectLastValue(underTest.areaTint)
val tint = areaTint?.tint(Rect(6, 6, 7, 7))
@@ -1033,11 +1033,11 @@
@Test
fun areaTint_viewIsInDarkBounds_darkBoundsChange_viewUpdates() =
kosmos.runTest {
- val displayId = 321
+ val displayId = testableContext.displayId
fakeDarkIconRepository.darkState(displayId).value =
SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC)
- val areaTint by collectLastValue(underTest.areaTint(displayId))
+ val areaTint by collectLastValue(underTest.areaTint)
var tint = areaTint?.tint(Rect(1, 1, 3, 3))
diff --git a/packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml b/packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml
new file mode 100644
index 0000000..f8c0fa0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2025 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.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="scaleX"
+ android:startOffset="1000"
+ android:valueFrom="0.45561"
+ android:valueTo="0.69699"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="scaleY"
+ android:startOffset="1000"
+ android:valueFrom="0.6288400000000001"
+ android:valueTo="0.81618"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="417"
+ android:propertyName="scaleX"
+ android:startOffset="1083"
+ android:valueFrom="0.69699"
+ android:valueTo="1.05905"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="417"
+ android:propertyName="scaleY"
+ android:startOffset="1083"
+ android:valueFrom="0.81618"
+ android:valueTo="1.0972"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="500"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="90"
+ android:valueTo="135"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="500"
+ android:propertyName="rotation"
+ android:startOffset="500"
+ android:valueFrom="135"
+ android:valueTo="180"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="scaleX"
+ android:startOffset="1000"
+ android:valueFrom="0.0434"
+ android:valueTo="0.05063"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="scaleY"
+ android:startOffset="1000"
+ android:valueFrom="0.0434"
+ android:valueTo="0.042350000000000006"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="417"
+ android:propertyName="scaleX"
+ android:startOffset="1083"
+ android:valueFrom="0.05063"
+ android:valueTo="0.06146"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="417"
+ android:propertyName="scaleY"
+ android:startOffset="1083"
+ android:valueFrom="0.042350000000000006"
+ android:valueTo="0.040780000000000004"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="1017"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="88dp"
+ android:height="56dp"
+ android:viewportHeight="56"
+ android:viewportWidth="88">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:pivotX="0.493"
+ android:pivotY="0.124"
+ android:scaleX="1.05905"
+ android:scaleY="1.0972"
+ android:translateX="43.528999999999996"
+ android:translateY="27.898">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#3d90ff"
+ android:fillType="nonZero"
+ android:pathData=" M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:rotation="0"
+ android:scaleX="0.06146"
+ android:scaleY="0.040780000000000004"
+ android:translateX="44"
+ android:translateY="28">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#3d90ff"
+ android:fillType="nonZero"
+ android:pathData=" M-0.65 -437.37 C-0.65,-437.37 8.33,-437.66 8.33,-437.66 C8.33,-437.66 17.31,-437.95 17.31,-437.95 C17.31,-437.95 26.25,-438.78 26.25,-438.78 C26.25,-438.78 35.16,-439.95 35.16,-439.95 C35.16,-439.95 44.07,-441.11 44.07,-441.11 C44.07,-441.11 52.85,-443 52.85,-443 C52.85,-443 61.6,-445.03 61.6,-445.03 C61.6,-445.03 70.35,-447.09 70.35,-447.09 C70.35,-447.09 78.91,-449.83 78.91,-449.83 C78.91,-449.83 87.43,-452.67 87.43,-452.67 C87.43,-452.67 95.79,-455.97 95.79,-455.97 C95.79,-455.97 104.11,-459.35 104.11,-459.35 C104.11,-459.35 112.36,-462.93 112.36,-462.93 C112.36,-462.93 120.6,-466.51 120.6,-466.51 C120.6,-466.51 128.84,-470.09 128.84,-470.09 C128.84,-470.09 137.09,-473.67 137.09,-473.67 C137.09,-473.67 145.49,-476.84 145.49,-476.84 C145.49,-476.84 153.9,-480.01 153.9,-480.01 C153.9,-480.01 162.31,-483.18 162.31,-483.18 C162.31,-483.18 170.98,-485.54 170.98,-485.54 C170.98,-485.54 179.66,-487.85 179.66,-487.85 C179.66,-487.85 188.35,-490.15 188.35,-490.15 C188.35,-490.15 197.22,-491.58 197.22,-491.58 C197.22,-491.58 206.09,-493.01 206.09,-493.01 C206.09,-493.01 214.98,-494.28 214.98,-494.28 C214.98,-494.28 223.95,-494.81 223.95,-494.81 C223.95,-494.81 232.93,-495.33 232.93,-495.33 C232.93,-495.33 241.9,-495.5 241.9,-495.5 C241.9,-495.5 250.88,-495.13 250.88,-495.13 C250.88,-495.13 259.86,-494.75 259.86,-494.75 C259.86,-494.75 268.78,-493.78 268.78,-493.78 C268.78,-493.78 277.68,-492.52 277.68,-492.52 C277.68,-492.52 286.57,-491.26 286.57,-491.26 C286.57,-491.26 295.31,-489.16 295.31,-489.16 C295.31,-489.16 304.04,-487.04 304.04,-487.04 C304.04,-487.04 312.7,-484.65 312.7,-484.65 C312.7,-484.65 321.19,-481.72 321.19,-481.72 C321.19,-481.72 329.68,-478.78 329.68,-478.78 C329.68,-478.78 337.96,-475.31 337.96,-475.31 C337.96,-475.31 346.14,-471.59 346.14,-471.59 C346.14,-471.59 354.3,-467.82 354.3,-467.82 C354.3,-467.82 362.11,-463.38 362.11,-463.38 C362.11,-463.38 369.92,-458.93 369.92,-458.93 C369.92,-458.93 377.53,-454.17 377.53,-454.17 C377.53,-454.17 384.91,-449.04 384.91,-449.04 C384.91,-449.04 392.29,-443.91 392.29,-443.91 C392.29,-443.91 399.26,-438.24 399.26,-438.24 C399.26,-438.24 406.15,-432.48 406.15,-432.48 C406.15,-432.48 412.92,-426.57 412.92,-426.57 C412.92,-426.57 419.27,-420.22 419.27,-420.22 C419.27,-420.22 425.62,-413.87 425.62,-413.87 C425.62,-413.87 431.61,-407.18 431.61,-407.18 C431.61,-407.18 437.38,-400.29 437.38,-400.29 C437.38,-400.29 443.14,-393.39 443.14,-393.39 C443.14,-393.39 448.27,-386.01 448.27,-386.01 C448.27,-386.01 453.4,-378.64 453.4,-378.64 C453.4,-378.64 458.26,-371.09 458.26,-371.09 C458.26,-371.09 462.71,-363.28 462.71,-363.28 C462.71,-363.28 467.16,-355.47 467.16,-355.47 C467.16,-355.47 471.03,-347.37 471.03,-347.37 C471.03,-347.37 474.75,-339.19 474.75,-339.19 C474.75,-339.19 478.34,-330.95 478.34,-330.95 C478.34,-330.95 481.28,-322.46 481.28,-322.46 C481.28,-322.46 484.21,-313.97 484.21,-313.97 C484.21,-313.97 486.72,-305.35 486.72,-305.35 C486.72,-305.35 488.84,-296.62 488.84,-296.62 C488.84,-296.62 490.96,-287.88 490.96,-287.88 C490.96,-287.88 492.33,-279.01 492.33,-279.01 C492.33,-279.01 493.59,-270.11 493.59,-270.11 C493.59,-270.11 494.69,-261.2 494.69,-261.2 C494.69,-261.2 495.07,-252.22 495.07,-252.22 C495.07,-252.22 495.44,-243.24 495.44,-243.24 C495.44,-243.24 495.41,-234.27 495.41,-234.27 C495.41,-234.27 494.88,-225.29 494.88,-225.29 C494.88,-225.29 494.35,-216.32 494.35,-216.32 C494.35,-216.32 493.22,-207.42 493.22,-207.42 C493.22,-207.42 491.79,-198.55 491.79,-198.55 C491.79,-198.55 490.36,-189.68 490.36,-189.68 C490.36,-189.68 488.19,-180.96 488.19,-180.96 C488.19,-180.96 485.88,-172.28 485.88,-172.28 C485.88,-172.28 483.56,-163.6 483.56,-163.6 C483.56,-163.6 480.48,-155.16 480.48,-155.16 C480.48,-155.16 477.31,-146.75 477.31,-146.75 C477.31,-146.75 474.14,-138.34 474.14,-138.34 C474.14,-138.34 470.62,-130.07 470.62,-130.07 C470.62,-130.07 467.04,-121.83 467.04,-121.83 C467.04,-121.83 463.46,-113.59 463.46,-113.59 C463.46,-113.59 459.88,-105.35 459.88,-105.35 C459.88,-105.35 456.54,-97.01 456.54,-97.01 C456.54,-97.01 453.37,-88.6 453.37,-88.6 C453.37,-88.6 450.21,-80.19 450.21,-80.19 C450.21,-80.19 447.68,-71.57 447.68,-71.57 C447.68,-71.57 445.36,-62.89 445.36,-62.89 C445.36,-62.89 443.04,-54.21 443.04,-54.21 C443.04,-54.21 441.54,-45.35 441.54,-45.35 C441.54,-45.35 440.09,-36.48 440.09,-36.48 C440.09,-36.48 438.78,-27.6 438.78,-27.6 C438.78,-27.6 438.19,-18.63 438.19,-18.63 C438.19,-18.63 437.61,-9.66 437.61,-9.66 C437.61,-9.66 437.36,-0.69 437.36,-0.69 C437.36,-0.69 437.65,8.29 437.65,8.29 C437.65,8.29 437.95,17.27 437.95,17.27 C437.95,17.27 438.77,26.21 438.77,26.21 C438.77,26.21 439.94,35.12 439.94,35.12 C439.94,35.12 441.11,44.03 441.11,44.03 C441.11,44.03 442.99,52.81 442.99,52.81 C442.99,52.81 445.02,61.57 445.02,61.57 C445.02,61.57 447.07,70.31 447.07,70.31 C447.07,70.31 449.82,78.87 449.82,78.87 C449.82,78.87 452.65,87.4 452.65,87.4 C452.65,87.4 455.96,95.75 455.96,95.75 C455.96,95.75 459.33,104.08 459.33,104.08 C459.33,104.08 462.91,112.32 462.91,112.32 C462.91,112.32 466.49,120.57 466.49,120.57 C466.49,120.57 470.07,128.81 470.07,128.81 C470.07,128.81 473.65,137.05 473.65,137.05 C473.65,137.05 476.82,145.46 476.82,145.46 C476.82,145.46 479.99,153.87 479.99,153.87 C479.99,153.87 483.17,162.28 483.17,162.28 C483.17,162.28 485.52,170.94 485.52,170.94 C485.52,170.94 487.84,179.63 487.84,179.63 C487.84,179.63 490.14,188.31 490.14,188.31 C490.14,188.31 491.57,197.18 491.57,197.18 C491.57,197.18 493,206.06 493,206.06 C493,206.06 494.27,214.95 494.27,214.95 C494.27,214.95 494.8,223.92 494.8,223.92 C494.8,223.92 495.33,232.89 495.33,232.89 C495.33,232.89 495.5,241.86 495.5,241.86 C495.5,241.86 495.12,250.84 495.12,250.84 C495.12,250.84 494.75,259.82 494.75,259.82 C494.75,259.82 493.78,268.74 493.78,268.74 C493.78,268.74 492.52,277.64 492.52,277.64 C492.52,277.64 491.27,286.54 491.27,286.54 C491.27,286.54 489.16,295.27 489.16,295.27 C489.16,295.27 487.05,304.01 487.05,304.01 C487.05,304.01 484.66,312.66 484.66,312.66 C484.66,312.66 481.73,321.16 481.73,321.16 C481.73,321.16 478.79,329.65 478.79,329.65 C478.79,329.65 475.32,337.93 475.32,337.93 C475.32,337.93 471.6,346.11 471.6,346.11 C471.6,346.11 467.84,354.27 467.84,354.27 C467.84,354.27 463.39,362.08 463.39,362.08 C463.39,362.08 458.94,369.89 458.94,369.89 C458.94,369.89 454.19,377.5 454.19,377.5 C454.19,377.5 449.06,384.88 449.06,384.88 C449.06,384.88 443.93,392.26 443.93,392.26 C443.93,392.26 438.26,399.23 438.26,399.23 C438.26,399.23 432.5,406.12 432.5,406.12 C432.5,406.12 426.6,412.89 426.6,412.89 C426.6,412.89 420.24,419.24 420.24,419.24 C420.24,419.24 413.89,425.6 413.89,425.6 C413.89,425.6 407.2,431.59 407.2,431.59 C407.2,431.59 400.31,437.36 400.31,437.36 C400.31,437.36 393.42,443.12 393.42,443.12 C393.42,443.12 386.04,448.25 386.04,448.25 C386.04,448.25 378.66,453.38 378.66,453.38 C378.66,453.38 371.11,458.24 371.11,458.24 C371.11,458.24 363.31,462.69 363.31,462.69 C363.31,462.69 355.5,467.14 355.5,467.14 C355.5,467.14 347.4,471.02 347.4,471.02 C347.4,471.02 339.22,474.73 339.22,474.73 C339.22,474.73 330.99,478.33 330.99,478.33 C330.99,478.33 322.49,481.27 322.49,481.27 C322.49,481.27 314,484.2 314,484.2 C314,484.2 305.38,486.71 305.38,486.71 C305.38,486.71 296.65,488.83 296.65,488.83 C296.65,488.83 287.91,490.95 287.91,490.95 C287.91,490.95 279.04,492.33 279.04,492.33 C279.04,492.33 270.14,493.59 270.14,493.59 C270.14,493.59 261.23,494.69 261.23,494.69 C261.23,494.69 252.25,495.07 252.25,495.07 C252.25,495.07 243.28,495.44 243.28,495.44 C243.28,495.44 234.3,495.41 234.3,495.41 C234.3,495.41 225.33,494.88 225.33,494.88 C225.33,494.88 216.36,494.35 216.36,494.35 C216.36,494.35 207.45,493.23 207.45,493.23 C207.45,493.23 198.58,491.8 198.58,491.8 C198.58,491.8 189.71,490.37 189.71,490.37 C189.71,490.37 180.99,488.21 180.99,488.21 C180.99,488.21 172.31,485.89 172.31,485.89 C172.31,485.89 163.63,483.57 163.63,483.57 C163.63,483.57 155.19,480.5 155.19,480.5 C155.19,480.5 146.78,477.32 146.78,477.32 C146.78,477.32 138.37,474.15 138.37,474.15 C138.37,474.15 130.11,470.63 130.11,470.63 C130.11,470.63 121.86,467.06 121.86,467.06 C121.86,467.06 113.62,463.48 113.62,463.48 C113.62,463.48 105.38,459.9 105.38,459.9 C105.38,459.9 97.04,456.56 97.04,456.56 C97.04,456.56 88.63,453.39 88.63,453.39 C88.63,453.39 80.22,450.22 80.22,450.22 C80.22,450.22 71.6,447.7 71.6,447.7 C71.6,447.7 62.92,445.37 62.92,445.37 C62.92,445.37 54.24,443.05 54.24,443.05 C54.24,443.05 45.38,441.55 45.38,441.55 C45.38,441.55 36.52,440.1 36.52,440.1 C36.52,440.1 27.63,438.78 27.63,438.78 C27.63,438.78 18.66,438.2 18.66,438.2 C18.66,438.2 9.7,437.61 9.7,437.61 C9.7,437.61 0.72,437.36 0.72,437.36 C0.72,437.36 -8.26,437.65 -8.26,437.65 C-8.26,437.65 -17.24,437.95 -17.24,437.95 C-17.24,437.95 -26.18,438.77 -26.18,438.77 C-26.18,438.77 -35.09,439.94 -35.09,439.94 C-35.09,439.94 -44,441.1 -44,441.1 C-44,441.1 -52.78,442.98 -52.78,442.98 C-52.78,442.98 -61.53,445.02 -61.53,445.02 C-61.53,445.02 -70.28,447.07 -70.28,447.07 C-70.28,447.07 -78.84,449.81 -78.84,449.81 C-78.84,449.81 -87.37,452.64 -87.37,452.64 C-87.37,452.64 -95.72,455.95 -95.72,455.95 C-95.72,455.95 -104.05,459.32 -104.05,459.32 C-104.05,459.32 -112.29,462.9 -112.29,462.9 C-112.29,462.9 -120.53,466.48 -120.53,466.48 C-120.53,466.48 -128.78,470.06 -128.78,470.06 C-128.78,470.06 -137.02,473.63 -137.02,473.63 C-137.02,473.63 -145.43,476.81 -145.43,476.81 C-145.43,476.81 -153.84,479.98 -153.84,479.98 C-153.84,479.98 -162.24,483.15 -162.24,483.15 C-162.24,483.15 -170.91,485.52 -170.91,485.52 C-170.91,485.52 -179.59,487.83 -179.59,487.83 C-179.59,487.83 -188.28,490.13 -188.28,490.13 C-188.28,490.13 -197.15,491.56 -197.15,491.56 C-197.15,491.56 -206.02,492.99 -206.02,492.99 C-206.02,492.99 -214.91,494.27 -214.91,494.27 C-214.91,494.27 -223.88,494.8 -223.88,494.8 C-223.88,494.8 -232.85,495.33 -232.85,495.33 C-232.85,495.33 -241.83,495.5 -241.83,495.5 C-241.83,495.5 -250.81,495.13 -250.81,495.13 C-250.81,495.13 -259.79,494.75 -259.79,494.75 C-259.79,494.75 -268.71,493.79 -268.71,493.79 C-268.71,493.79 -277.61,492.53 -277.61,492.53 C-277.61,492.53 -286.51,491.27 -286.51,491.27 C-286.51,491.27 -295.24,489.17 -295.24,489.17 C-295.24,489.17 -303.98,487.06 -303.98,487.06 C-303.98,487.06 -312.63,484.67 -312.63,484.67 C-312.63,484.67 -321.12,481.74 -321.12,481.74 C-321.12,481.74 -329.62,478.8 -329.62,478.8 C-329.62,478.8 -337.9,475.33 -337.9,475.33 C-337.9,475.33 -346.08,471.62 -346.08,471.62 C-346.08,471.62 -354.24,467.85 -354.24,467.85 C-354.24,467.85 -362.05,463.41 -362.05,463.41 C-362.05,463.41 -369.86,458.96 -369.86,458.96 C-369.86,458.96 -377.47,454.21 -377.47,454.21 C-377.47,454.21 -384.85,449.08 -384.85,449.08 C-384.85,449.08 -392.23,443.95 -392.23,443.95 C-392.23,443.95 -399.2,438.29 -399.2,438.29 C-399.2,438.29 -406.09,432.52 -406.09,432.52 C-406.09,432.52 -412.86,426.62 -412.86,426.62 C-412.86,426.62 -419.22,420.27 -419.22,420.27 C-419.22,420.27 -425.57,413.91 -425.57,413.91 C-425.57,413.91 -431.57,407.23 -431.57,407.23 C-431.57,407.23 -437.33,400.34 -437.33,400.34 C-437.33,400.34 -443.1,393.44 -443.1,393.44 C-443.1,393.44 -448.23,386.07 -448.23,386.07 C-448.23,386.07 -453.36,378.69 -453.36,378.69 C-453.36,378.69 -458.23,371.15 -458.23,371.15 C-458.23,371.15 -462.67,363.33 -462.67,363.33 C-462.67,363.33 -467.12,355.53 -467.12,355.53 C-467.12,355.53 -471,347.43 -471,347.43 C-471,347.43 -474.72,339.25 -474.72,339.25 C-474.72,339.25 -478.32,331.02 -478.32,331.02 C-478.32,331.02 -481.25,322.52 -481.25,322.52 C-481.25,322.52 -484.19,314.03 -484.19,314.03 C-484.19,314.03 -486.71,305.42 -486.71,305.42 C-486.71,305.42 -488.82,296.68 -488.82,296.68 C-488.82,296.68 -490.94,287.95 -490.94,287.95 C-490.94,287.95 -492.32,279.07 -492.32,279.07 C-492.32,279.07 -493.58,270.18 -493.58,270.18 C-493.58,270.18 -494.69,261.27 -494.69,261.27 C-494.69,261.27 -495.07,252.29 -495.07,252.29 C-495.07,252.29 -495.44,243.31 -495.44,243.31 C-495.44,243.31 -495.42,234.33 -495.42,234.33 C-495.42,234.33 -494.89,225.36 -494.89,225.36 C-494.89,225.36 -494.36,216.39 -494.36,216.39 C-494.36,216.39 -493.23,207.49 -493.23,207.49 C-493.23,207.49 -491.8,198.61 -491.8,198.61 C-491.8,198.61 -490.37,189.74 -490.37,189.74 C-490.37,189.74 -488.22,181.02 -488.22,181.02 C-488.22,181.02 -485.9,172.34 -485.9,172.34 C-485.9,172.34 -483.58,163.66 -483.58,163.66 C-483.58,163.66 -480.51,155.22 -480.51,155.22 C-480.51,155.22 -477.34,146.81 -477.34,146.81 C-477.34,146.81 -474.17,138.41 -474.17,138.41 C-474.17,138.41 -470.65,130.14 -470.65,130.14 C-470.65,130.14 -467.07,121.9 -467.07,121.9 C-467.07,121.9 -463.49,113.65 -463.49,113.65 C-463.49,113.65 -459.91,105.41 -459.91,105.41 C-459.91,105.41 -456.57,97.07 -456.57,97.07 C-456.57,97.07 -453.4,88.66 -453.4,88.66 C-453.4,88.66 -450.23,80.25 -450.23,80.25 C-450.23,80.25 -447.7,71.64 -447.7,71.64 C-447.7,71.64 -445.38,62.96 -445.38,62.96 C-445.38,62.96 -443.06,54.28 -443.06,54.28 C-443.06,54.28 -441.56,45.42 -441.56,45.42 C-441.56,45.42 -440.1,36.55 -440.1,36.55 C-440.1,36.55 -438.78,27.67 -438.78,27.67 C-438.78,27.67 -438.2,18.7 -438.2,18.7 C-438.2,18.7 -437.62,9.73 -437.62,9.73 C-437.62,9.73 -437.36,0.76 -437.36,0.76 C-437.36,0.76 -437.66,-8.22 -437.66,-8.22 C-437.66,-8.22 -437.95,-17.2 -437.95,-17.2 C-437.95,-17.2 -438.77,-26.14 -438.77,-26.14 C-438.77,-26.14 -439.93,-35.05 -439.93,-35.05 C-439.93,-35.05 -441.1,-43.96 -441.1,-43.96 C-441.1,-43.96 -442.98,-52.75 -442.98,-52.75 C-442.98,-52.75 -445.01,-61.5 -445.01,-61.5 C-445.01,-61.5 -447.06,-70.25 -447.06,-70.25 C-447.06,-70.25 -449.8,-78.81 -449.8,-78.81 C-449.8,-78.81 -452.63,-87.33 -452.63,-87.33 C-452.63,-87.33 -455.94,-95.69 -455.94,-95.69 C-455.94,-95.69 -459.31,-104.02 -459.31,-104.02 C-459.31,-104.02 -462.89,-112.26 -462.89,-112.26 C-462.89,-112.26 -466.47,-120.5 -466.47,-120.5 C-466.47,-120.5 -470.05,-128.74 -470.05,-128.74 C-470.05,-128.74 -473.68,-137.12 -473.68,-137.12 C-473.68,-137.12 -476.85,-145.53 -476.85,-145.53 C-476.85,-145.53 -480.03,-153.94 -480.03,-153.94 C-480.03,-153.94 -483.2,-162.34 -483.2,-162.34 C-483.2,-162.34 -485.55,-171.02 -485.55,-171.02 C-485.55,-171.02 -487.86,-179.7 -487.86,-179.7 C-487.86,-179.7 -490.15,-188.39 -490.15,-188.39 C-490.15,-188.39 -491.58,-197.26 -491.58,-197.26 C-491.58,-197.26 -493.01,-206.13 -493.01,-206.13 C-493.01,-206.13 -494.28,-215.02 -494.28,-215.02 C-494.28,-215.02 -494.81,-223.99 -494.81,-223.99 C-494.81,-223.99 -495.33,-232.96 -495.33,-232.96 C-495.33,-232.96 -495.5,-241.94 -495.5,-241.94 C-495.5,-241.94 -495.12,-250.92 -495.12,-250.92 C-495.12,-250.92 -494.75,-259.9 -494.75,-259.9 C-494.75,-259.9 -493.78,-268.82 -493.78,-268.82 C-493.78,-268.82 -492.52,-277.72 -492.52,-277.72 C-492.52,-277.72 -491.26,-286.61 -491.26,-286.61 C-491.26,-286.61 -489.15,-295.35 -489.15,-295.35 C-489.15,-295.35 -487.03,-304.08 -487.03,-304.08 C-487.03,-304.08 -484.64,-312.73 -484.64,-312.73 C-484.64,-312.73 -481.7,-321.23 -481.7,-321.23 C-481.7,-321.23 -478.77,-329.72 -478.77,-329.72 C-478.77,-329.72 -475.29,-338 -475.29,-338 C-475.29,-338 -471.57,-346.18 -471.57,-346.18 C-471.57,-346.18 -467.8,-354.33 -467.8,-354.33 C-467.8,-354.33 -463.36,-362.14 -463.36,-362.14 C-463.36,-362.14 -458.91,-369.95 -458.91,-369.95 C-458.91,-369.95 -454.15,-377.56 -454.15,-377.56 C-454.15,-377.56 -449.02,-384.94 -449.02,-384.94 C-449.02,-384.94 -443.88,-392.32 -443.88,-392.32 C-443.88,-392.32 -438.22,-399.28 -438.22,-399.28 C-438.22,-399.28 -432.45,-406.18 -432.45,-406.18 C-432.45,-406.18 -426.55,-412.94 -426.55,-412.94 C-426.55,-412.94 -420.19,-419.3 -420.19,-419.3 C-420.19,-419.3 -413.84,-425.65 -413.84,-425.65 C-413.84,-425.65 -407.15,-431.64 -407.15,-431.64 C-407.15,-431.64 -400.26,-437.41 -400.26,-437.41 C-400.26,-437.41 -393.36,-443.16 -393.36,-443.16 C-393.36,-443.16 -385.98,-448.29 -385.98,-448.29 C-385.98,-448.29 -378.6,-453.43 -378.6,-453.43 C-378.6,-453.43 -371.05,-458.28 -371.05,-458.28 C-371.05,-458.28 -363.24,-462.73 -363.24,-462.73 C-363.24,-462.73 -355.43,-467.18 -355.43,-467.18 C-355.43,-467.18 -347.33,-471.05 -347.33,-471.05 C-347.33,-471.05 -339.15,-474.76 -339.15,-474.76 C-339.15,-474.76 -330.92,-478.35 -330.92,-478.35 C-330.92,-478.35 -322.42,-481.29 -322.42,-481.29 C-322.42,-481.29 -313.93,-484.23 -313.93,-484.23 C-313.93,-484.23 -305.31,-486.73 -305.31,-486.73 C-305.31,-486.73 -296.58,-488.85 -296.58,-488.85 C-296.58,-488.85 -287.85,-490.97 -287.85,-490.97 C-287.85,-490.97 -278.97,-492.34 -278.97,-492.34 C-278.97,-492.34 -270.07,-493.6 -270.07,-493.6 C-270.07,-493.6 -261.16,-494.7 -261.16,-494.7 C-261.16,-494.7 -252.18,-495.07 -252.18,-495.07 C-252.18,-495.07 -243.2,-495.44 -243.2,-495.44 C-243.2,-495.44 -234.23,-495.41 -234.23,-495.41 C-234.23,-495.41 -225.26,-494.88 -225.26,-494.88 C-225.26,-494.88 -216.29,-494.35 -216.29,-494.35 C-216.29,-494.35 -207.38,-493.22 -207.38,-493.22 C-207.38,-493.22 -198.51,-491.79 -198.51,-491.79 C-198.51,-491.79 -189.64,-490.36 -189.64,-490.36 C-189.64,-490.36 -180.92,-488.19 -180.92,-488.19 C-180.92,-488.19 -172.24,-485.87 -172.24,-485.87 C-172.24,-485.87 -163.56,-483.56 -163.56,-483.56 C-163.56,-483.56 -155.12,-480.47 -155.12,-480.47 C-155.12,-480.47 -146.72,-477.3 -146.72,-477.3 C-146.72,-477.3 -138.31,-474.13 -138.31,-474.13 C-138.31,-474.13 -130.04,-470.61 -130.04,-470.61 C-130.04,-470.61 -121.8,-467.03 -121.8,-467.03 C-121.8,-467.03 -113.55,-463.45 -113.55,-463.45 C-113.55,-463.45 -105.31,-459.87 -105.31,-459.87 C-105.31,-459.87 -96.97,-456.53 -96.97,-456.53 C-96.97,-456.53 -88.56,-453.37 -88.56,-453.37 C-88.56,-453.37 -80.15,-450.2 -80.15,-450.2 C-80.15,-450.2 -71.53,-447.68 -71.53,-447.68 C-71.53,-447.68 -62.85,-445.36 -62.85,-445.36 C-62.85,-445.36 -54.17,-443.04 -54.17,-443.04 C-54.17,-443.04 -45.31,-441.54 -45.31,-441.54 C-45.31,-441.54 -36.44,-440.09 -36.44,-440.09 C-36.44,-440.09 -27.56,-438.78 -27.56,-438.78 C-27.56,-438.78 -18.59,-438.19 -18.59,-438.19 C-18.59,-438.19 -9.62,-437.61 -9.62,-437.61 C-9.62,-437.61 -0.65,-437.37 -0.65,-437.37c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_pause_button.xml b/packages/SystemUI/res/drawable/ic_media_pause_button.xml
new file mode 100644
index 0000000..6ae89f9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_pause_button.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2025 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.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-5.06 -18 C-5.06,-18 -5.06,-1.24 -5.06,-1.24 C-5.06,-1.24 -5.06,17.7 -5.06,17.7 C-5.06,19.36 -6.41,20.7 -8.06,20.7 C-8.06,20.7 -16,20.7 -16,20.7 C-17.66,20.7 -19,19.36 -19,17.7 C-19,17.7 -19,-18 -19,-18 C-19,-19.66 -17.66,-21 -16,-21 C-16,-21 -8.06,-21 -8.06,-21 C-6.41,-21 -5.06,-19.66 -5.06,-18c "
+ android:valueTo="M-4.69 -16.69 C-4.69,-16.69 20.25,-1.25 20.31,0.25 C20.38,1.75 -4.88,16.89 -4.88,16.89 C-6.94,18.25 -8.56,19.4 -9.75,19.58 C-10.94,19.75 -12.19,18.94 -12.12,16.14 C-12.09,14.76 -12.12,15.92 -12.12,14.26 C-12.12,14.26 -11.94,-16.44 -11.94,-16.44 C-11.94,-18.09 -12.09,-19.69 -10.44,-19.69 C-10.44,-19.69 -9.5,-19.56 -9.5,-19.56 C-8.62,-19.12 -6.19,-17.44 -4.69,-16.69c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.449,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-5.06 -18 C-5.06,-18 -5.06,-0.75 -5.06,-0.75 C-5.06,-0.75 -5.06,17.7 -5.06,17.7 C-5.06,19.36 -6.41,20.7 -8.06,20.7 C-8.06,20.7 -16,20.7 -16,20.7 C-17.66,20.7 -19,19.36 -19,17.7 C-19,17.7 -19,-18 -19,-18 C-19,-19.66 -17.66,-21 -16,-21 C-16,-21 -8.06,-21 -8.06,-21 C-6.41,-21 -5.06,-19.66 -5.06,-18c "
+ android:valueTo="M-4.69 -16.69 C-4.69,-16.69 20.25,-1.25 20.31,0.25 C20.38,1.75 -4.88,16.89 -4.88,16.89 C-6.94,18.25 -8.56,19.4 -9.75,19.58 C-10.94,19.75 -12.19,18.94 -12.12,16.14 C-12.09,14.76 -12.12,15.92 -12.12,14.26 C-12.12,14.26 -11.94,-16.44 -11.94,-16.44 C-11.94,-18.09 -12.09,-19.69 -10.44,-19.69 C-10.44,-19.69 -9.5,-19.56 -9.5,-19.56 C-8.62,-19.12 -6.19,-17.44 -4.69,-16.69c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.449,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="56"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="15.485"
+ android:valueTo="12.321"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="278"
+ android:propertyName="translateX"
+ android:startOffset="56"
+ android:valueFrom="12.321"
+ android:valueTo="7.576"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="517"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:pivotX="-12.031"
+ android:scaleX="0.33299999999999996"
+ android:scaleY="0.33299999999999996"
+ android:translateX="19.524"
+ android:translateY="12.084">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-5.06 -18 C-5.06,-18 -5.06,-1.24 -5.06,-1.24 C-5.06,-1.24 -5.06,17.7 -5.06,17.7 C-5.06,19.36 -6.41,20.7 -8.06,20.7 C-8.06,20.7 -16,20.7 -16,20.7 C-17.66,20.7 -19,19.36 -19,17.7 C-19,17.7 -19,-18 -19,-18 C-19,-19.66 -17.66,-21 -16,-21 C-16,-21 -8.06,-21 -8.06,-21 C-6.41,-21 -5.06,-19.66 -5.06,-18c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G_T_1"
+ android:scaleX="0.33299999999999996"
+ android:scaleY="0.33299999999999996"
+ android:translateX="15.485"
+ android:translateY="12.084">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12.031">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-5.06 -18 C-5.06,-18 -5.06,-0.75 -5.06,-0.75 C-5.06,-0.75 -5.06,17.7 -5.06,17.7 C-5.06,19.36 -6.41,20.7 -8.06,20.7 C-8.06,20.7 -16,20.7 -16,20.7 C-17.66,20.7 -19,19.36 -19,17.7 C-19,17.7 -19,-18 -19,-18 C-19,-19.66 -17.66,-21 -16,-21 C-16,-21 -8.06,-21 -8.06,-21 C-6.41,-21 -5.06,-19.66 -5.06,-18c " />
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_pause_button_container.xml b/packages/SystemUI/res/drawable/ic_media_pause_button_container.xml
new file mode 100644
index 0000000..571f69d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_pause_button_container.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2025 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.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="88dp"
+ android:height="56dp"
+ android:viewportHeight="56"
+ android:viewportWidth="88">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:pivotX="0.493"
+ android:pivotY="0.124"
+ android:scaleX="1.05905"
+ android:scaleY="1.0972"
+ android:translateX="43.528999999999996"
+ android:translateY="27.898">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#3d90ff"
+ android:fillType="nonZero"
+ android:pathData=" M34.49 -5.75 C34.49,-5.75 34.49,6 34.49,6 C34.49,14.84 27.32,22 18.49,22 C18.49,22 -17.5,22 -17.5,22 C-26.34,22 -33.5,14.84 -33.5,6 C-33.5,6 -33.5,-5.75 -33.5,-5.75 C-33.5,-14.59 -26.34,-21.75 -17.5,-21.75 C-17.5,-21.75 18.49,-21.75 18.49,-21.75 C27.32,-21.75 34.49,-14.59 34.49,-5.75c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="133"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M34.49 -5.75 C34.49,-5.75 34.49,6 34.49,6 C34.49,14.84 27.32,22 18.49,22 C18.49,22 -17.5,22 -17.5,22 C-26.34,22 -33.5,14.84 -33.5,6 C-33.5,6 -33.5,-5.75 -33.5,-5.75 C-33.5,-14.59 -26.34,-21.75 -17.5,-21.75 C-17.5,-21.75 18.49,-21.75 18.49,-21.75 C27.32,-21.75 34.49,-14.59 34.49,-5.75c "
+ android:valueTo="M34.49 -5.75 C34.49,-5.75 34.49,6 34.49,6 C34.49,14.84 27.32,22 18.49,22 C18.49,22 -17.5,22 -17.5,22 C-26.34,22 -33.5,14.84 -33.5,6 C-33.5,6 -33.5,-5.75 -33.5,-5.75 C-33.5,-14.59 -26.34,-21.75 -17.5,-21.75 C-17.5,-21.75 18.49,-21.75 18.49,-21.75 C27.32,-21.75 34.49,-14.59 34.49,-5.75c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.473,0 0.065,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="367"
+ android:propertyName="pathData"
+ android:startOffset="133"
+ android:valueFrom="M34.49 -5.75 C34.49,-5.75 34.49,6 34.49,6 C34.49,14.84 27.32,22 18.49,22 C18.49,22 -17.5,22 -17.5,22 C-26.34,22 -33.5,14.84 -33.5,6 C-33.5,6 -33.5,-5.75 -33.5,-5.75 C-33.5,-14.59 -26.34,-21.75 -17.5,-21.75 C-17.5,-21.75 18.49,-21.75 18.49,-21.75 C27.32,-21.75 34.49,-14.59 34.49,-5.75c "
+ android:valueTo="M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.473,0 0.065,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="scaleX"
+ android:startOffset="0"
+ android:valueFrom="1.05905"
+ android:valueTo="1.17758"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.226,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="scaleY"
+ android:startOffset="0"
+ android:valueFrom="1.0972"
+ android:valueTo="1.22"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.226,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="scaleX"
+ android:startOffset="167"
+ android:valueFrom="1.17758"
+ android:valueTo="1.05905"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.213,0 0.248,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="scaleY"
+ android:startOffset="167"
+ android:valueFrom="1.22"
+ android:valueTo="1.0972"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.213,0 0.248,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="517"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_play_button.xml b/packages/SystemUI/res/drawable/ic_media_play_button.xml
new file mode 100644
index 0000000..f646902
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_play_button.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2025 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.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-4.69 -16.69 C-4.69,-16.69 20.25,-1.25 20.31,0.25 C20.38,1.75 -4.88,16.89 -4.88,16.89 C-6.94,18.25 -8.56,19.4 -9.75,19.58 C-10.94,19.75 -12.19,18.94 -12.12,16.14 C-12.09,14.76 -12.12,15.92 -12.12,14.26 C-12.12,14.26 -11.94,-16.44 -11.94,-16.44 C-11.94,-18.09 -12.09,-19.69 -10.44,-19.69 C-10.44,-19.69 -9.5,-19.56 -9.5,-19.56 C-8.62,-19.12 -6.19,-17.44 -4.69,-16.69c "
+ android:valueTo="M-5.06 -18 C-5.06,-18 -5.06,-1.24 -5.06,-1.24 C-5.06,-1.24 -5.06,17.7 -5.06,17.7 C-5.06,19.36 -6.41,20.7 -8.06,20.7 C-8.06,20.7 -16,20.7 -16,20.7 C-17.66,20.7 -19,19.36 -19,17.7 C-19,17.7 -19,-18 -19,-18 C-19,-19.66 -17.66,-21 -16,-21 C-16,-21 -8.06,-21 -8.06,-21 C-6.41,-21 -5.06,-19.66 -5.06,-18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.433,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-4.69 -16.69 C-4.69,-16.69 20.25,-1.25 20.31,0.25 C20.38,1.75 -4.88,16.89 -4.88,16.89 C-6.94,18.25 -8.56,19.4 -9.75,19.58 C-10.94,19.75 -12.19,18.94 -12.12,16.14 C-12.09,14.76 -12.12,15.92 -12.12,14.26 C-12.12,14.26 -11.94,-16.44 -11.94,-16.44 C-11.94,-18.09 -12.09,-19.69 -10.44,-19.69 C-10.44,-19.69 -9.5,-19.56 -9.5,-19.56 C-8.62,-19.12 -6.19,-17.44 -4.69,-16.69c "
+ android:valueTo="M-5.06 -18 C-5.06,-18 -5.06,-0.75 -5.06,-0.75 C-5.06,-0.75 -5.06,17.7 -5.06,17.7 C-5.06,19.36 -6.41,20.7 -8.06,20.7 C-8.06,20.7 -16,20.7 -16,20.7 C-17.66,20.7 -19,19.36 -19,17.7 C-19,17.7 -19,-18 -19,-18 C-19,-19.66 -17.66,-21 -16,-21 C-16,-21 -8.06,-21 -8.06,-21 C-6.41,-21 -5.06,-19.66 -5.06,-18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.433,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="7.576"
+ android:valueTo="15.485"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.583,0 0.089,0.874 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="517"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:pivotX="-12.031"
+ android:scaleX="0.33299999999999996"
+ android:scaleY="0.33299999999999996"
+ android:translateX="19.524"
+ android:translateY="12.084">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-4.69 -16.69 C-4.69,-16.69 20.25,-1.25 20.31,0.25 C20.38,1.75 -4.88,16.89 -4.88,16.89 C-6.94,18.25 -8.56,19.4 -9.75,19.58 C-10.94,19.75 -12.19,18.94 -12.12,16.14 C-12.09,14.76 -12.12,15.92 -12.12,14.26 C-12.12,14.26 -11.94,-16.44 -11.94,-16.44 C-11.94,-18.09 -12.09,-19.69 -10.44,-19.69 C-10.44,-19.69 -9.5,-19.56 -9.5,-19.56 C-8.62,-19.12 -6.19,-17.44 -4.69,-16.69c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G_T_1"
+ android:scaleX="0.33299999999999996"
+ android:scaleY="0.33299999999999996"
+ android:translateX="7.576"
+ android:translateY="12.084">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12.031">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-4.69 -16.69 C-4.69,-16.69 20.25,-1.25 20.31,0.25 C20.38,1.75 -4.88,16.89 -4.88,16.89 C-6.94,18.25 -8.56,19.4 -9.75,19.58 C-10.94,19.75 -12.19,18.94 -12.12,16.14 C-12.09,14.76 -12.12,15.92 -12.12,14.26 C-12.12,14.26 -11.94,-16.44 -11.94,-16.44 C-11.94,-18.09 -12.09,-19.69 -10.44,-19.69 C-10.44,-19.69 -9.5,-19.56 -9.5,-19.56 C-8.62,-19.12 -6.19,-17.44 -4.69,-16.69c " />
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_play_button_container.xml b/packages/SystemUI/res/drawable/ic_media_play_button_container.xml
new file mode 100644
index 0000000..aa4e09fa
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_play_button_container.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2025 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.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:height="56dp"
+ android:width="88dp"
+ android:viewportHeight="56"
+ android:viewportWidth="88">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="43.528999999999996"
+ android:translateY="27.898"
+ android:pivotX="0.493"
+ android:pivotY="0.124"
+ android:scaleX="1.05905"
+ android:scaleY="1.0972">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#3d90ff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="167"
+ android:startOffset="0"
+ android:valueFrom="M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c "
+ android:valueTo="M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.493,0 0,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="167"
+ android:valueFrom="M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c "
+ android:valueTo="M34.49 -5.75 C34.49,-5.75 34.49,6 34.49,6 C34.49,14.84 27.32,22 18.49,22 C18.49,22 -17.5,22 -17.5,22 C-26.34,22 -33.5,14.84 -33.5,6 C-33.5,6 -33.5,-5.75 -33.5,-5.75 C-33.5,-14.59 -26.34,-21.75 -17.5,-21.75 C-17.5,-21.75 18.49,-21.75 18.49,-21.75 C27.32,-21.75 34.49,-14.59 34.49,-5.75c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.493,0 0,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:duration="167"
+ android:startOffset="0"
+ android:valueFrom="1.05905"
+ android:valueTo="1.17758"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.226,0 0.667,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="167"
+ android:startOffset="0"
+ android:valueFrom="1.0972"
+ android:valueTo="1.22"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.226,0 0.667,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:duration="333"
+ android:startOffset="167"
+ android:valueFrom="1.17758"
+ android:valueTo="1.05905"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.213,0 0.248,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="333"
+ android:startOffset="167"
+ android:valueFrom="1.22"
+ android:valueTo="1.0972"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.213,0 0.248,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="translateX"
+ android:duration="517"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_ringer_button.xml b/packages/SystemUI/res/layout/volume_ringer_button.xml
index e65d0b9..6748cfa 100644
--- a/packages/SystemUI/res/layout/volume_ringer_button.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_button.xml
@@ -20,10 +20,9 @@
<ImageButton
android:id="@+id/volume_drawer_button"
- android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
- android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
- android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
android:contentDescription="@string/volume_ringer_mode"
android:gravity="center"
android:tint="@androidprv:color/materialColorOnSurface"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 1f78892..2ffa3d1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1278,6 +1278,7 @@
<dimen name="qs_center_guideline_padding">10dp</dimen>
<dimen name="qs_media_action_spacing">4dp</dimen>
<dimen name="qs_media_action_margin">12dp</dimen>
+ <dimen name="qs_media_action_play_pause_width">72dp</dimen>
<dimen name="qs_seamless_height">24dp</dimen>
<dimen name="qs_seamless_icon_size">12dp</dimen>
<dimen name="qs_media_disabled_seekbar_height">1dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
index 0954482..a6b9442 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
@@ -31,6 +31,7 @@
import androidx.media3.session.MediaController as Media3Controller
import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionToken
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -128,7 +129,11 @@
drawable,
null, // no action to perform when clicked
context.getString(R.string.controls_media_button_connecting),
- context.getDrawable(R.drawable.ic_media_connecting_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_connecting_status_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_connecting_container)
+ },
// Specify a rebind id to prevent the spinner from restarting on later binds.
com.android.internal.R.drawable.progress_small_material,
)
@@ -230,17 +235,33 @@
Player.COMMAND_PLAY_PAUSE -> {
if (!controller.isPlaying) {
MediaAction(
- context.getDrawable(R.drawable.ic_media_play),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_play_button)
+ } else {
+ context.getDrawable(R.drawable.ic_media_play)
+ },
{ executeAction(packageName, token, Player.COMMAND_PLAY_PAUSE) },
context.getString(R.string.controls_media_button_play),
- context.getDrawable(R.drawable.ic_media_play_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_play_button_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_play_container)
+ },
)
} else {
MediaAction(
- context.getDrawable(R.drawable.ic_media_pause),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_pause_button)
+ } else {
+ context.getDrawable(R.drawable.ic_media_pause)
+ },
{ executeAction(packageName, token, Player.COMMAND_PLAY_PAUSE) },
context.getString(R.string.controls_media_button_pause),
- context.getDrawable(R.drawable.ic_media_pause_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_pause_button_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_pause_container)
+ },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
index 4f97913..9bf556c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -29,6 +29,7 @@
import android.service.notification.StatusBarNotification
import android.util.Log
import androidx.media.utils.MediaConstants
+import com.android.systemui.Flags
import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_COMPACT_ACTIONS
import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_NOTIFICATION_ACTIONS
import com.android.systemui.media.controls.shared.MediaControlDrawables
@@ -69,7 +70,11 @@
drawable,
null, // no action to perform when clicked
context.getString(R.string.controls_media_button_connecting),
- context.getDrawable(R.drawable.ic_media_connecting_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_connecting_status_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_connecting_container)
+ },
// Specify a rebind id to prevent the spinner from restarting on later binds.
com.android.internal.R.drawable.progress_small_material,
)
@@ -157,18 +162,34 @@
return when (action) {
PlaybackState.ACTION_PLAY -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_play),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_play_button)
+ } else {
+ context.getDrawable(R.drawable.ic_media_play)
+ },
{ controller.transportControls.play() },
context.getString(R.string.controls_media_button_play),
- context.getDrawable(R.drawable.ic_media_play_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_play_button_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_play_container)
+ },
)
}
PlaybackState.ACTION_PAUSE -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_pause),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_pause_button)
+ } else {
+ context.getDrawable(R.drawable.ic_media_pause)
+ },
{ controller.transportControls.pause() },
context.getString(R.string.controls_media_button_pause),
- context.getDrawable(R.drawable.ic_media_pause_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_pause_button_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_pause_container)
+ },
)
}
PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 3928a71..a2ddc20 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -1016,9 +1016,24 @@
expandedLayout.load(context, R.xml.media_recommendations_expanded)
}
}
+ readjustPlayPauseWidth()
refreshState()
}
+ private fun readjustPlayPauseWidth() {
+ // TODO: move to xml file when flag is removed.
+ if (Flags.mediaControlsUiUpdate()) {
+ collapsedLayout.constrainWidth(
+ R.id.actionPlayPause,
+ context.resources.getDimensionPixelSize(R.dimen.qs_media_action_play_pause_width),
+ )
+ expandedLayout.constrainWidth(
+ R.id.actionPlayPause,
+ context.resources.getDimensionPixelSize(R.dimen.qs_media_action_play_pause_width),
+ )
+ }
+ }
+
/** Get a view state based on the width and height set by the scene */
private fun obtainSceneContainerViewState(state: MediaHostState?): TransitionViewState? {
logger.logMediaSize("scene container", widthInSceneContainerPx, heightInSceneContainerPx)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
index 10e67a4..640d364 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
@@ -25,7 +25,7 @@
import android.app.NotificationManager.IMPORTANCE_UNSPECIFIED
import android.content.Context
import android.graphics.drawable.Drawable
-import android.text.TextUtils
+import android.text.TextUtils.isEmpty
import android.transition.AutoTransition
import android.transition.Transition
import android.transition.TransitionManager
@@ -37,13 +37,10 @@
import android.widget.Switch
import android.widget.TextView
import com.android.settingslib.Utils
-
import com.android.systemui.res.R
import com.android.systemui.util.Assert
-/**
- * Half-shelf for notification channel controls
- */
+/** Half-shelf for notification channel controls */
class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
lateinit var controller: ChannelEditorDialogController
var appIcon: Drawable? = null
@@ -84,23 +81,21 @@
val transition = AutoTransition()
transition.duration = 200
- transition.addListener(object : Transition.TransitionListener {
- override fun onTransitionEnd(p0: Transition?) {
- notifySubtreeAccessibilityStateChangedIfNeeded()
- }
+ transition.addListener(
+ object : Transition.TransitionListener {
+ override fun onTransitionEnd(p0: Transition?) {
+ notifySubtreeAccessibilityStateChangedIfNeeded()
+ }
- override fun onTransitionResume(p0: Transition?) {
- }
+ override fun onTransitionResume(p0: Transition?) {}
- override fun onTransitionPause(p0: Transition?) {
- }
+ override fun onTransitionPause(p0: Transition?) {}
- override fun onTransitionCancel(p0: Transition?) {
- }
+ override fun onTransitionCancel(p0: Transition?) {}
- override fun onTransitionStart(p0: Transition?) {
+ override fun onTransitionStart(p0: Transition?) {}
}
- })
+ )
TransitionManager.beginDelayedTransition(this, transition)
// Remove any rows
@@ -130,8 +125,9 @@
private fun updateAppControlRow(enabled: Boolean) {
appControlRow.iconView.setImageDrawable(appIcon)
- appControlRow.channelName.text = context.resources
- .getString(R.string.notification_channel_dialog_title, appName)
+ val title = context.resources.getString(R.string.notification_channel_dialog_title, appName)
+ appControlRow.channelName.text = title
+ appControlRow.switch.contentDescription = title
appControlRow.switch.isChecked = enabled
appControlRow.switch.setOnCheckedChangeListener { _, b ->
controller.proposeSetAppNotificationsEnabled(b)
@@ -164,8 +160,8 @@
var gentle = false
init {
- highlightColor = Utils.getColorAttrDefaultColor(
- context, android.R.attr.colorControlHighlight)
+ highlightColor =
+ Utils.getColorAttrDefaultColor(context, android.R.attr.colorControlHighlight)
}
var channel: NotificationChannel? = null
@@ -182,17 +178,16 @@
switch = requireViewById(R.id.toggle)
switch.setOnCheckedChangeListener { _, b ->
channel?.let {
- controller.proposeEditForChannel(it,
- if (b) it.originalImportance.coerceAtLeast(IMPORTANCE_LOW)
- else IMPORTANCE_NONE)
+ controller.proposeEditForChannel(
+ it,
+ if (b) it.originalImportance.coerceAtLeast(IMPORTANCE_LOW) else IMPORTANCE_NONE,
+ )
}
}
setOnClickListener { switch.toggle() }
}
- /**
- * Play an animation that highlights this row
- */
+ /** Play an animation that highlights this row */
fun playHighlight() {
// Use 0 for the start value because our background is given to us by our parent
val fadeInLoop = ValueAnimator.ofObject(ArgbEvaluator(), 0, highlightColor)
@@ -211,17 +206,21 @@
channelName.text = nc.name ?: ""
- nc.group?.let { groupId ->
- channelDescription.text = controller.groupNameForId(groupId)
- }
+ nc.group?.let { groupId -> channelDescription.text = controller.groupNameForId(groupId) }
- if (nc.group == null || TextUtils.isEmpty(channelDescription.text)) {
+ if (nc.group == null || isEmpty(channelDescription.text)) {
channelDescription.visibility = View.GONE
} else {
channelDescription.visibility = View.VISIBLE
}
switch.isChecked = nc.importance != IMPORTANCE_NONE
+ switch.contentDescription =
+ if (isEmpty(channelDescription.text)) {
+ channelName.text
+ } else {
+ "${channelName.text} ${channelDescription.text}"
+ }
}
private fun updateImportance() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index c31e34c5..e622d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -81,6 +81,7 @@
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel;
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
@@ -142,6 +143,8 @@
private StatusBarVisibilityModel mLastModifiedVisibility =
StatusBarVisibilityModel.createDefaultModel();
private DarkIconManager mDarkIconManager;
+ private HomeStatusBarViewModel mHomeStatusBarViewModel;
+
private final HomeStatusBarComponent.Factory mHomeStatusBarComponentFactory;
private final CommandQueue mCommandQueue;
private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
@@ -151,8 +154,8 @@
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final StatusBarIconController mStatusBarIconController;
private final CarrierConfigTracker mCarrierConfigTracker;
- private final HomeStatusBarViewModel mHomeStatusBarViewModel;
private final HomeStatusBarViewBinder mHomeStatusBarViewBinder;
+ private final HomeStatusBarViewModelFactory mHomeStatusBarViewModelFactory;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final DarkIconManager.Factory mDarkIconManagerFactory;
private final SecureSettings mSecureSettings;
@@ -256,7 +259,7 @@
ShadeExpansionStateManager shadeExpansionStateManager,
StatusBarIconController statusBarIconController,
DarkIconManager.Factory darkIconManagerFactory,
- HomeStatusBarViewModel homeStatusBarViewModel,
+ HomeStatusBarViewModelFactory homeStatusBarViewModelFactory,
HomeStatusBarViewBinder homeStatusBarViewBinder,
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
KeyguardStateController keyguardStateController,
@@ -281,7 +284,7 @@
mAnimationScheduler = animationScheduler;
mShadeExpansionStateManager = shadeExpansionStateManager;
mStatusBarIconController = statusBarIconController;
- mHomeStatusBarViewModel = homeStatusBarViewModel;
+ mHomeStatusBarViewModelFactory = homeStatusBarViewModelFactory;
mHomeStatusBarViewBinder = homeStatusBarViewBinder;
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
mDarkIconManagerFactory = darkIconManagerFactory;
@@ -410,6 +413,7 @@
mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
+ mHomeStatusBarViewModel = mHomeStatusBarViewModelFactory.create(displayId);
mHomeStatusBarViewBinder.bind(
view.getContext().getDisplayId(),
mStatusBar,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 96666d8..c71162a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -56,8 +56,8 @@
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinderImpl
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModelImpl
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModelImpl.HomeStatusBarViewModelFactoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
@@ -148,7 +148,9 @@
abstract fun bindCarrierConfigStartable(impl: CarrierConfigCoreStartable): CoreStartable
@Binds
- abstract fun homeStatusBarViewModel(impl: HomeStatusBarViewModelImpl): HomeStatusBarViewModel
+ abstract fun homeStatusBarViewModelFactory(
+ impl: HomeStatusBarViewModelFactoryImpl
+ ): HomeStatusBarViewModelFactory
@Binds
abstract fun homeStatusBarViewBinder(impl: HomeStatusBarViewBinderImpl): HomeStatusBarViewBinder
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 0dd7c84..2541d84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -107,10 +107,9 @@
}
if (NotificationsLiveDataStoreRefactor.isEnabled) {
- val displayId = view.display.displayId
val lightsOutView: View = view.requireViewById(R.id.notification_lights_out)
launch {
- viewModel.areNotificationsLightsOut(displayId).collect { show ->
+ viewModel.areNotificationsLightsOut.collect { show ->
animateLightsOutView(lightsOutView, show)
}
}
@@ -218,7 +217,7 @@
StatusBarOperatorNameViewBinder.bind(
operatorNameView,
viewModel.operatorNameViewModel,
- viewModel::areaTint,
+ viewModel.areaTint,
)
launch {
viewModel.shouldShowOperatorNameView.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarOperatorNameViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarOperatorNameViewBinder.kt
index b7744d3..5dd76f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarOperatorNameViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarOperatorNameViewBinder.kt
@@ -32,19 +32,16 @@
fun bind(
operatorFrameView: View,
viewModel: StatusBarOperatorNameViewModel,
- areaTint: (Int) -> Flow<StatusBarTintColor>,
+ areaTint: Flow<StatusBarTintColor>,
) {
operatorFrameView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- val displayId = operatorFrameView.display.displayId
-
val operatorNameText =
operatorFrameView.requireViewById<TextView>(R.id.operator_name)
launch { viewModel.operatorName.collect { operatorNameText.text = it } }
launch {
- val tint = areaTint(displayId)
- tint.collect { statusBarTintColors ->
+ areaTint.collect { statusBarTintColors ->
operatorNameText.setTextColor(
statusBarTintColors.tint(operatorNameText.viewBoundsOnScreen())
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index f286a1a..b78e010 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -53,13 +53,14 @@
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory
import javax.inject.Inject
/** Factory to simplify the dependency management for [StatusBarRoot] */
class StatusBarRootFactory
@Inject
constructor(
- private val homeStatusBarViewModel: HomeStatusBarViewModel,
+ private val homeStatusBarViewModelFactory: HomeStatusBarViewModelFactory,
private val homeStatusBarViewBinder: HomeStatusBarViewBinder,
private val notificationIconsBinder: NotificationIconContainerStatusBarViewBinder,
private val darkIconManagerFactory: DarkIconManager.Factory,
@@ -70,13 +71,14 @@
) {
fun create(root: ViewGroup, andThen: (ViewGroup) -> Unit): ComposeView {
val composeView = ComposeView(root.context)
+ val displayId = root.context.displayId
val darkIconDispatcher =
darkIconDispatcherStore.forDisplay(root.context.displayId) ?: return composeView
composeView.apply {
setContent {
StatusBarRoot(
parent = root,
- statusBarViewModel = homeStatusBarViewModel,
+ statusBarViewModel = homeStatusBarViewModelFactory.create(displayId),
statusBarViewBinder = homeStatusBarViewBinder,
notificationIconsBinder = notificationIconsBinder,
darkIconManagerFactory = darkIconManagerFactory,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index c9cc173..3f701fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -19,7 +19,6 @@
import android.annotation.ColorInt
import android.graphics.Rect
import android.view.View
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -53,7 +52,9 @@
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarIconBlockListInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarInteractor
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -118,6 +119,7 @@
val shouldShowOperatorNameView: Flow<Boolean>
val isClockVisible: Flow<VisibilityModel>
val isNotificationIconContainerVisible: Flow<VisibilityModel>
+
/**
* Pair of (system info visibility, event animation state). The animation state can be used to
* respond to the system event chip animations. In all cases, system info visibility correctly
@@ -137,13 +139,13 @@
* whether there are notifications when the device is in
* [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE].
*/
- fun areNotificationsLightsOut(displayId: Int): Flow<Boolean>
+ val areNotificationsLightsOut: Flow<Boolean>
/**
- * Given a displayId, returns a flow of [StatusBarTintColor], a functional interface that will
- * allow a view to calculate its correct tint depending on location
+ * A flow of [StatusBarTintColor], a functional interface that will allow a view to calculate
+ * its correct tint depending on location
*/
- fun areaTint(displayId: Int): Flow<StatusBarTintColor>
+ val areaTint: Flow<StatusBarTintColor>
/** Models the current visibility for a specific child view of status bar. */
data class VisibilityModel(
@@ -157,17 +159,22 @@
val baseVisibility: VisibilityModel,
val animationState: SystemEventAnimationState,
)
+
+ /** Interface for the assisted factory, to allow for providing a fake in tests */
+ interface HomeStatusBarViewModelFactory {
+ fun create(displayId: Int): HomeStatusBarViewModel
+ }
}
-@SysUISingleton
class HomeStatusBarViewModelImpl
-@Inject
+@AssistedInject
constructor(
+ @Assisted thisDisplayId: Int,
homeStatusBarInteractor: HomeStatusBarInteractor,
homeStatusBarIconBlockListInteractor: HomeStatusBarIconBlockListInteractor,
- private val lightsOutInteractor: LightsOutInteractor,
- private val notificationsInteractor: ActiveNotificationsInteractor,
- private val darkIconInteractor: DarkIconInteractor,
+ lightsOutInteractor: LightsOutInteractor,
+ notificationsInteractor: ActiveNotificationsInteractor,
+ darkIconInteractor: DarkIconInteractor,
headsUpNotificationInteractor: HeadsUpNotificationInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
keyguardInteractor: KeyguardInteractor,
@@ -211,22 +218,22 @@
}
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false)
- override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> =
+ override val areNotificationsLightsOut: Flow<Boolean> =
if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
emptyFlow()
} else {
combine(
notificationsInteractor.areAnyNotificationsPresent,
- lightsOutInteractor.isLowProfile(displayId) ?: flowOf(false),
+ lightsOutInteractor.isLowProfile(thisDisplayId) ?: flowOf(false),
) { hasNotifications, isLowProfile ->
hasNotifications && isLowProfile
}
.distinctUntilChanged()
}
- override fun areaTint(displayId: Int): Flow<StatusBarTintColor> =
+ override val areaTint: Flow<StatusBarTintColor> =
darkIconInteractor
- .darkState(displayId)
+ .darkState(thisDisplayId)
.map { (areas: Collection<Rect>, tint: Int) ->
StatusBarTintColor { viewBounds: Rect ->
if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
@@ -364,6 +371,13 @@
// Similar to the above, but uses INVISIBLE in place of GONE
@View.Visibility
private fun Boolean.toVisibleOrInvisible(): Int = if (this) View.VISIBLE else View.INVISIBLE
+
+ /** Inject this to create the display-dependent view model */
+ @AssistedFactory
+ interface HomeStatusBarViewModelFactoryImpl :
+ HomeStatusBarViewModel.HomeStatusBarViewModelFactory {
+ override fun create(displayId: Int): HomeStatusBarViewModelImpl
+ }
}
/** Lookup the color for a given view in the status bar */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 3a99328..30ab416 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -42,6 +42,7 @@
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -77,6 +78,8 @@
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeHomeStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeHomeStatusBarViewModel;
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel;
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.StatusBarOperatorNameViewModel;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -1268,6 +1271,15 @@
mock(StatusBarOperatorNameViewModel.class));
mCollapsedStatusBarViewBinder = new FakeHomeStatusBarViewBinder();
+ HomeStatusBarViewModelFactory homeStatusBarViewModelFactory =
+ new HomeStatusBarViewModelFactory() {
+ @NonNull
+ @Override
+ public HomeStatusBarViewModel create(int displayId) {
+ return mCollapsedStatusBarViewModel;
+ }
+ };
+
return new CollapsedStatusBarFragment(
mStatusBarFragmentComponentFactory,
mOngoingCallController,
@@ -1275,7 +1287,7 @@
mShadeExpansionStateManager,
mStatusBarIconController,
mIconManagerFactory,
- mCollapsedStatusBarViewModel,
+ homeStatusBarViewModelFactory,
mCollapsedStatusBarViewBinder,
mStatusBarHideIconsForBouncerManager,
mKeyguardStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index b38a723..5db0d5a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
+import android.content.testableContext
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
@@ -36,6 +37,7 @@
var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
Kosmos.Fixture {
HomeStatusBarViewModelImpl(
+ testableContext.displayId,
homeStatusBarInteractor,
homeStatusBarIconBlockListInteractor,
lightsOutInteractor,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6cd1f72..8e037c3 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -542,7 +542,8 @@
MagnificationController magnificationController,
@Nullable AccessibilityInputFilter inputFilter,
ProxyManager proxyManager,
- PermissionEnforcer permissionEnforcer) {
+ PermissionEnforcer permissionEnforcer,
+ HearingDevicePhoneCallNotificationController hearingDeviceNotificationController) {
super(permissionEnforcer);
mContext = context;
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -571,8 +572,7 @@
mVisibleBgUserIds = null;
mInputManager = context.getSystemService(InputManager.class);
if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
- mHearingDeviceNotificationController = new HearingDevicePhoneCallNotificationController(
- context);
+ mHearingDeviceNotificationController = hearingDeviceNotificationController;
} else {
mHearingDeviceNotificationController = null;
}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 86fc732..d440d3a 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -21,6 +21,7 @@
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.hardware.tv.mediaquality.IMediaQuality;
import android.media.quality.AmbientBacklightSettings;
import android.media.quality.IAmbientBacklightCallback;
import android.media.quality.IMediaQualityManager;
@@ -35,9 +36,11 @@
import android.media.quality.SoundProfileHandle;
import android.os.Binder;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
@@ -45,6 +48,7 @@
import android.util.SparseArray;
import com.android.server.SystemService;
+import com.android.server.utils.Slogf;
import org.json.JSONException;
import org.json.JSONObject;
@@ -74,6 +78,7 @@
private final BiMap<Long, String> mSoundProfileTempIdMap;
private final PackageManager mPackageManager;
private final SparseArray<UserState> mUserStates = new SparseArray<>();
+ private IMediaQuality mMediaQuality;
public MediaQualityService(Context context) {
super(context);
@@ -88,6 +93,12 @@
@Override
public void onStart() {
+ IBinder binder = ServiceManager.getService(IMediaQuality.DESCRIPTOR + "/default");
+ if (binder != null) {
+ Slogf.d(TAG, "binder is not null");
+ mMediaQuality = IMediaQuality.Stub.asInterface(binder);
+ }
+
publishBinderService(Context.MEDIA_QUALITY_SERVICE, new BinderService());
}
@@ -809,10 +820,29 @@
if (!hasGlobalPictureQualityServicePermission()) {
//TODO: error handling
}
+
+ try {
+ if (mMediaQuality != null) {
+ mMediaQuality.setAutoPqEnabled(enabled);
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set auto picture quality", e);
+ }
}
@Override
public boolean isAutoPictureQualityEnabled(UserHandle user) {
+ try {
+ if (mMediaQuality != null) {
+ return mMediaQuality.getAutoPqEnabled();
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get auto picture quality", e);
+ }
return false;
}
@@ -821,10 +851,29 @@
if (!hasGlobalPictureQualityServicePermission()) {
//TODO: error handling
}
+
+ try {
+ if (mMediaQuality != null) {
+ mMediaQuality.setAutoSrEnabled(enabled);
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set auto super resolution", e);
+ }
}
@Override
public boolean isSuperResolutionEnabled(UserHandle user) {
+ try {
+ if (mMediaQuality != null) {
+ return mMediaQuality.getAutoSrEnabled();
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get auto super resolution", e);
+ }
return false;
}
@@ -833,10 +882,29 @@
if (!hasGlobalSoundQualityServicePermission()) {
//TODO: error handling
}
+
+ try {
+ if (mMediaQuality != null) {
+ mMediaQuality.setAutoAqEnabled(enabled);
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set auto audio quality", e);
+ }
}
@Override
public boolean isAutoSoundQualityEnabled(UserHandle user) {
+ try {
+ if (mMediaQuality != null) {
+ return mMediaQuality.getAutoAqEnabled();
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get auto audio quality", e);
+ }
return false;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e69a741..b577710 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8909,11 +8909,15 @@
if (parent) {
Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity().getUserId()));
+ isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId()));
+ // If a DPC is querying on the parent instance, make sure it's only querying the parent
+ // user of itself. Querying any other user is not allowed.
+ Preconditions.checkArgument(caller.getUserId() == userHandle);
}
+ int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
Boolean disallowed = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.SCREEN_CAPTURE_DISABLED,
- userHandle);
+ affectedUserId);
return disallowed != null && disallowed;
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index fa78dfc..dafe482 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -220,6 +220,9 @@
@Mock private ProxyManager mProxyManager;
@Mock private StatusBarManagerInternal mStatusBarManagerInternal;
@Mock private DevicePolicyManager mDevicePolicyManager;
+ @Mock
+ private HearingDevicePhoneCallNotificationController
+ mMockHearingDevicePhoneCallNotificationController;
@Spy private IUserInitializationCompleteCallback mUserInitializationCompleteCallback;
@Captor private ArgumentCaptor<Intent> mIntentArgumentCaptor;
private IAccessibilityManager mA11yManagerServiceOnDevice;
@@ -289,7 +292,8 @@
mMockMagnificationController,
mInputFilter,
mProxyManager,
- mFakePermissionEnforcer);
+ mFakePermissionEnforcer,
+ mMockHearingDevicePhoneCallNotificationController);
mA11yms.switchUser(mTestableContext.getUserId());
mTestableLooper.processAllMessages();