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();