summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt41
-rw-r--r--core/java/android/app/Notification.java820
-rw-r--r--core/res/res/values/dimens.xml6
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java303
5 files changed, 1166 insertions, 7 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 8eb881139b34..59da04ce55a2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6854,6 +6854,47 @@ package android.app {
method public android.app.Notification.MessagingStyle.Message setData(String, android.net.Uri);
}
+ @FlaggedApi("android.app.api_rich_ongoing") public static class Notification.ProgressStyle extends android.app.Notification.Style {
+ ctor public Notification.ProgressStyle();
+ method @NonNull public android.app.Notification.ProgressStyle addProgressSegment(@NonNull android.app.Notification.ProgressStyle.Segment);
+ method @NonNull public android.app.Notification.ProgressStyle addProgressStep(@NonNull android.app.Notification.ProgressStyle.Step);
+ method public int getProgress();
+ method @Nullable public android.graphics.drawable.Icon getProgressEndIcon();
+ method public int getProgressMax();
+ method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Segment> getProgressSegments();
+ method @Nullable public android.graphics.drawable.Icon getProgressStartIcon();
+ method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Step> getProgressSteps();
+ method @Nullable public android.graphics.drawable.Icon getProgressTrackerIcon();
+ method public boolean isProgressIndeterminate();
+ method public boolean isStyledByProgress();
+ method @NonNull public android.app.Notification.ProgressStyle setProgress(int);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressEndIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressIndeterminate(boolean);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressSegments(@NonNull java.util.List<android.app.Notification.ProgressStyle.Segment>);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressStartIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressSteps(@NonNull java.util.List<android.app.Notification.ProgressStyle.Step>);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressTrackerIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.app.Notification.ProgressStyle setStyledByProgress(boolean);
+ }
+
+ public static final class Notification.ProgressStyle.Segment {
+ ctor public Notification.ProgressStyle.Segment(int);
+ method @ColorInt public int getColor();
+ method public int getLength();
+ method public int getStableId();
+ method @NonNull public android.app.Notification.ProgressStyle.Segment setColor(@ColorInt int);
+ method @NonNull public android.app.Notification.ProgressStyle.Segment setStableId(int);
+ }
+
+ public static final class Notification.ProgressStyle.Step {
+ ctor public Notification.ProgressStyle.Step(int);
+ method @ColorInt public int getColor();
+ method public int getPosition();
+ method public int getStableId();
+ method @NonNull public android.app.Notification.ProgressStyle.Step setColor(@ColorInt int);
+ method @NonNull public android.app.Notification.ProgressStyle.Step setStableId(int);
+ }
+
public abstract static class Notification.Style {
ctor @Deprecated public Notification.Style();
method public android.app.Notification build();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 392a1f113c23..a39f216d033e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -121,7 +121,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -783,10 +782,32 @@ public class Notification implements Parcelable
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
public static final int FLAG_PROMOTED_ONGOING = 0x00040000;
- private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
- BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
- DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
- MessagingStyle.class, CallStyle.class);
+ private static final Set<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Set.of(
+ BigTextStyle.class,
+ BigPictureStyle.class,
+ InboxStyle.class,
+ MediaStyle.class,
+ DecoratedCustomViewStyle.class,
+ DecoratedMediaCustomViewStyle.class,
+ MessagingStyle.class,
+ CallStyle.class
+ );
+
+ private static boolean isPlatformStyle(Style style) {
+ if (style == null) {
+ return false;
+ }
+
+ if (PLATFORM_STYLE_CLASSES.contains(style.getClass())) {
+ return true;
+ }
+
+ if (Flags.apiRichOngoing()) {
+ return style.getClass() == ProgressStyle.class;
+ }
+
+ return false;
+ }
/** @hide */
@IntDef(flag = true, prefix = {"FLAG_"}, value = {
@@ -1620,6 +1641,66 @@ public class Notification implements Parcelable
public static final String EXTRA_COLORIZED = "android.colorized";
/**
+ * {@link #extras} key: an arraylist of {@link android.app.Notification.ProgressStyle.Segment}
+ * bundles provided by a
+ * {@link android.app.Notification.ProgressStyle} notification as supplied to
+ * {@link ProgressStyle#setProgressSegments}
+ * or {@link ProgressStyle#addProgressSegment(ProgressStyle.Segment)}.
+ * This extra is a parcelable array list of bundles.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final String EXTRA_PROGRESS_SEGMENTS = "android.progressSegments";
+
+ /**
+ * {@link #extras} key: an arraylist of {@link android.app.Notification.ProgressStyle.Step}
+ * bundles provided by a
+ * {@link android.app.Notification.ProgressStyle} notification as supplied to
+ * {@link ProgressStyle#setProgressSteps}
+ * or {@link ProgressStyle#addProgressStep(ProgressStyle.Step)}.
+ * This extra is a parcelable array list of bundles.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final String EXTRA_PROGRESS_STEPS = "android.progressSteps";
+
+ /**
+ * {@link #extras} key: whether the progress bar should be styled by its progress as
+ * supplied to {@link ProgressStyle#setStyledByProgress}.
+ * This extra is a boolean.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final String EXTRA_STYLED_BY_PROGRESS = "android.styledByProgress";
+
+ /**
+ * {@link #extras} key: this is an {@link Icon} of an image to be
+ * shown as progress bar progress tracker icon in {@link ProgressStyle}, supplied to
+ *{@link ProgressStyle#setProgressTrackerIcon(Icon)}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final String EXTRA_PROGRESS_TRACKER_ICON = "android.progressTrackerIcon";
+
+ /**
+ * {@link #extras} key: this is an {@link Icon} of an image to be
+ * shown at the beginning of the progress bar in {@link ProgressStyle}, supplied to
+ *{@link ProgressStyle#setProgressStartIcon(Icon)}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final String EXTRA_PROGRESS_START_ICON = "android.progressStartIcon";
+
+ /**
+ * {@link #extras} key: this is an {@link Icon} of an image to be
+ * shown at the end of the progress bar in {@link ProgressStyle}, supplied to
+ *{@link ProgressStyle#setProgressEndIcon(Icon)}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final String EXTRA_PROGRESS_END_ICON = "android.progressEndIcon";
+
+ /**
* @hide
*/
public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
@@ -3072,6 +3153,9 @@ public class Notification implements Parcelable
if (Flags.apiRichOngoing()) {
visitIconUri(visitor, extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class));
+ visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class));
+ visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class));
+ visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class));
}
if (mBubbleMetadata != null) {
@@ -6630,7 +6714,7 @@ public class Notification implements Parcelable
// Custom views which come from a platform style class are safe, and thus do not need to
// be wrapped. Any subclass of those styles has the opportunity to make arbitrary
// changes to the RemoteViews, and thus can't be trusted as a fully vetted view.
- if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) {
+ if (fromStyle && isPlatformStyle(mStyle)) {
return false;
}
return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S;
@@ -7971,6 +8055,12 @@ public class Notification implements Parcelable
return innerClass;
}
}
+
+ if (Flags.apiRichOngoing()) {
+ if (templateClass.equals(ProgressStyle.class.getName())) {
+ return ProgressStyle.class;
+ }
+ }
return null;
}
@@ -11220,6 +11310,724 @@ public class Notification implements Parcelable
}
}
+
+ /**
+ * A Notification Style used to to define a notification whose expanded state includes
+ * a highly customizable progress bar with segments, steps, a custom tracker icon,
+ * and custom icons at the start and end of the progress bar.
+ *
+ * This style is suggested for use cases where the app is showing a tracker to the
+ * user of a thing they are interested in: the location of a car on its way
+ * to pick them up, food being delivered, or their own progress in a navigation
+ * journey.
+ *
+ * To use this style with your Notification, feed it to
+ * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
+ * <pre class="prettyprint">
+ * new Notification.Builder(context)
+ * .setSmallIcon(R.drawable.ic_notification)
+ * .setColor(Color.GREEN)
+ * .setColorized(true)
+ * .setContentTitle("Arrive 10:08 AM").
+ * .setContentText("Dominique Ansel Bakery Soho")
+ * .addAction(new Notification.Action("Exit navigation",...))
+ * .setStyle(new Notification.ProgressStyle()
+ * .setStyledByProgress(false)
+ * .setProgress(456)
+ * .setProgressTrackerIcon(Icon.createWithResource(R.drawable.ic_driving_tracker))
+ * .addProgressSegment(new Segment(41).setColor(Color.BLACK))
+ * .addProgressSegment(new Segment(552).setColor(Color.YELLOW))
+ * .addProgressSegment(new Segment(253).setColor(Color.YELLOW))
+ * .addProgressSegment(new Segment(94).setColor(Color.BLUE))
+ * .addProgressStep(new Step(60).setColor(Color.RED))
+ * .addProgressStep(new Step(560).setColor(Color.YELLOW))
+ * )
+ * </pre>
+ *
+ *
+ *
+ * NOTE: The progress bar layout will be mirrored for RTL layout.
+ * NOTE: The extras set by {@link Notification.Builder#setProgress} will be overridden by
+ * the values set on this style object when the notification is built.
+ *
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static class ProgressStyle extends Notification.Style {
+ private static final String KEY_ELEMENT_STABLE_ID = "stableId";
+ private static final String KEY_ELEMENT_COLOR = "colorInt";
+ private static final String KEY_SEGMENT_LENGTH = "length";
+ private static final String KEY_STEP_POSITION = "position";
+
+ private static final int MAX_PROGRESS_SEGMENT_LIMIT = 15;
+ private static final int MAX_PROGRESS_STEP_LIMIT = 5;
+ private static final int DEFAULT_PROGRESS_MAX = 100;
+
+ private List<Segment> mProgressSegments = new ArrayList<>();
+ private List<Step> mProgressSteps = new ArrayList<>();
+
+ private int mProgress = 0;
+
+ private boolean mIndeterminate;
+
+ private boolean mIsStyledByProgress = true;
+
+ @Nullable
+ private Icon mTrackerIcon;
+ @Nullable
+ private Icon mStartIcon;
+ @Nullable
+ private Icon mEndIcon;
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Style other) {
+ if (other == null || getClass() != other.getClass()) {
+ return true;
+ }
+
+ final ProgressStyle progressStyle = (ProgressStyle) other;
+
+ /**
+ * @see #setProgressIndeterminate
+ */
+ if (!Objects.equals(mIndeterminate, progressStyle.mIndeterminate)) {
+ return true;
+ }
+ boolean nonIndeterminateCheckResult = false;
+ if (!mIndeterminate) {
+ nonIndeterminateCheckResult = !Objects.equals(mProgress, progressStyle.mProgress)
+ || !Objects.equals(mIsStyledByProgress, progressStyle.mIsStyledByProgress)
+ || !Objects.equals(mProgressSegments, progressStyle.mProgressSegments)
+ || !Objects.equals(mProgressSteps, progressStyle.mProgressSteps)
+ || !Objects.equals(mTrackerIcon, progressStyle.mTrackerIcon);
+ }
+
+ return !Objects.equals(mStartIcon, progressStyle.mStartIcon)
+ || !Objects.equals(mEndIcon, progressStyle.mEndIcon)
+ || nonIndeterminateCheckResult;
+ }
+
+ /**
+ * Gets the segments that define the background layer of the progress bar.
+ *
+ * If no segments are provided, the progress bar will be rendered with a single segment
+ * with length 100 and default color.
+ *
+ * @see #setProgressSegments
+ * @see #addProgressSegment
+ * @see Segment
+ */
+ public @NonNull List<Segment> getProgressSegments() {
+ return mProgressSegments;
+ }
+
+ /**
+ * Sets or replaces the segments of the progress bar.
+ *
+ * Segments allow for creating progress bars with multiple colors or sections
+ * to represent different stages or categories of progress.
+ * For example, Traffic conditions along a navigation journey.
+ * @see Segment
+ */
+ public @NonNull ProgressStyle setProgressSegments(@NonNull List<Segment> progressSegments) {
+ mProgressSegments = new ArrayList<>(progressSegments.size());
+ return this;
+ }
+
+ /**
+ * Appends a segment to the end of the progress bar.
+ *
+ * Segments allow for creating progress bars with multiple colors or sections
+ * to represent different stages or categories of progress.
+ * For example, Traffic conditions along a navigation journey.
+ * @see Segment
+ */
+ public @NonNull ProgressStyle addProgressSegment(@NonNull Segment segment) {
+ if (mProgressSegments == null) {
+ mProgressSegments = new ArrayList<>();
+ }
+ mProgressSegments.add(segment);
+
+ return this;
+ }
+
+ /**
+ * Gets the steps that are displayed on the progress bar.
+ *.
+ * @see #setProgressSteps
+ * @see #addProgressStep
+ * @see Step
+ */
+ public @NonNull List<Step> getProgressSteps() {
+ return mProgressSteps;
+ }
+
+ /**
+ * Replaces all the progress steps.
+ *
+ * Steps are designated points within a progressbar to visualize
+ * distinct stages or milestones.
+ * For example, you might use steps to mark stops in a multi-stop
+ * navigation journey, where each step represents a destination.
+ * @see Step
+ */
+ public @NonNull ProgressStyle setProgressSteps(@NonNull List<Step> steps) {
+ mProgressSteps = new ArrayList<>(steps);
+ return this;
+ }
+
+ /**
+ * Adds another step.
+ *
+ * Steps are designated points within a progressbar to visualize
+ * distinct stages or milestones.
+ * For example, you might use steps to mark stops in a multi-stop
+ * navigation journey, where each step represents a destination.
+ *
+ * Steps can be added in any order, as their
+ * position within the progress bar is determined by their individual
+ * {@link Step#getPosition()}.
+ * @see Step
+ */
+ public @NonNull ProgressStyle addProgressStep(@NonNull Step step) {
+ if (mProgressSteps == null) {
+ mProgressSteps = new ArrayList<>();
+ }
+ mProgressSteps.add(step);
+
+ return this;
+ }
+
+ /**
+ * Gets the progress value of the progress bar.
+ * @see #setProgress
+ */
+ public int getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Specifies the progress (in the same units as {@link Segment#getLength()})
+ * of the tracker along the length of the bar.
+ *
+ * The max progress value is the sum of all Segment lengths.
+ * The default value is 0.
+ */
+ public @NonNull ProgressStyle setProgress(int progress) {
+ mProgress = progress;
+ return this;
+ }
+
+ /**
+ * Gets the sum of the lengths of all Segments in the style, which
+ * defines the maximum progress. Defaults to 100 when segments are omitted.
+ */
+ public int getProgressMax() {
+ final List<Segment> progressSegment = mProgressSegments;
+ if (progressSegment == null || progressSegment.isEmpty()) {
+ return DEFAULT_PROGRESS_MAX;
+ } else {
+ int progressMax = 0;
+ int validSegmentCount = 0;
+ for (int i = 0; i < progressSegment.size()
+ && validSegmentCount < MAX_PROGRESS_SEGMENT_LIMIT; i++) {
+ int segmentLength = progressSegment.get(i).getLength();
+ if (segmentLength > 0) {
+ try {
+ progressMax = Math.addExact(progressMax, segmentLength);
+ validSegmentCount++;
+ } catch (ArithmeticException e) {
+ Log.e(TAG,
+ "Notification.ProgressStyle segment total overflowed.", e);
+ return DEFAULT_PROGRESS_MAX;
+ }
+ }
+ }
+
+ if (validSegmentCount == 0) {
+ return DEFAULT_PROGRESS_MAX;
+ }
+
+ return progressMax;
+ }
+
+ }
+
+ /**
+ * Get indeterminate value of the progress bar.
+ * @see #setProgressIndeterminate
+ */
+ public boolean isProgressIndeterminate() {
+ return mIndeterminate;
+ }
+
+ /**
+ * Used to indicate an initialization state without a known progress amount.
+ * When specified, the following fields are ignored:
+ * @see #setProgress
+ * @see #setProgressSegments
+ * @see #setProgressSteps
+ * @see #setProgressTrackerIcon
+ * @see #setStyledByProgress
+ *
+ * If the app provides exactly one Segment, that segment's color will be
+ * used to style the indeterminate bar.
+ */
+ public @NonNull ProgressStyle setProgressIndeterminate(boolean indeterminate) {
+ mIndeterminate = indeterminate;
+ return this;
+ }
+
+ /**
+ * Gets whether the progress bar's style is based on its progress.
+ * @see #setStyledByProgress
+ */
+ public boolean isStyledByProgress() {
+ return mIsStyledByProgress;
+ }
+
+ /**
+ * Indicates whether the segments and steps will be styled differently
+ * based on whether they are behind or ahead of the current progress.
+ * When true, segments appearing ahead of the current progress will be given a
+ * slightly different appearance to indicate that it is part of the progress bar
+ * that is not "filled".
+ * When false, all segments will be given the filled appearance, and it will be
+ * the app's responsibility to use #setProgressTrackerIcon or segment colors
+ * to make the current progress clear to the user.
+ * the default value is true.
+ */
+ public @NonNull ProgressStyle setStyledByProgress(boolean enabled) {
+ mIsStyledByProgress = enabled;
+ return this;
+ }
+
+
+ /**
+ * Gets the progress tracker icon for the progress bar.
+ * @see #setProgressTrackerIcon
+ */
+ public @Nullable Icon getProgressTrackerIcon() {
+ return mTrackerIcon;
+ }
+
+ /**
+ * An optional icon that can appear as an overlay on the bar at the point of
+ * current progress.
+ * Aspect ratio may be anywhere from 2:1 to 1:2; content outside that
+ * aspect ratio range will be cropped.
+ * This icon will be mirrored in RTL.
+ */
+ public @NonNull ProgressStyle setProgressTrackerIcon(@Nullable Icon trackerIcon) {
+ mTrackerIcon = trackerIcon;
+ return this;
+ }
+
+ /**
+ * Gets the progress bar start icon.
+ * @see #setProgressStartIcon
+ */
+ public @Nullable Icon getProgressStartIcon() {
+ return mStartIcon;
+ }
+
+ /**
+ * An optional square icon that appears at the start of the progress bar.
+ * This icon will be cropped to its central square.
+ * This icon will NOT be mirrored in RTL layouts.
+ */
+ public @NonNull ProgressStyle setProgressStartIcon(@Nullable Icon startIcon) {
+ mStartIcon = startIcon;
+ return this;
+ }
+
+ /**
+ * Gets the progress bar end icon.
+ * @see #setProgressEndIcon(Icon)
+ */
+ public @Nullable Icon getProgressEndIcon() {
+ return mEndIcon;
+ }
+
+ /**
+ * An optional square icon that appears at the end of the progress bar.
+ * This icon will be cropped to its central square.
+ * This icon will NOT be mirrored in RTL layouts.
+ */
+ public @NonNull ProgressStyle setProgressEndIcon(@Nullable Icon endIcon) {
+ mEndIcon = endIcon;
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void purgeResources() {
+ super.purgeResources();
+ if (mTrackerIcon != null) {
+ mTrackerIcon.convertToAshmem();
+ }
+ if (mStartIcon != null) {
+ mStartIcon.convertToAshmem();
+ }
+ if (mEndIcon != null) {
+ mEndIcon.convertToAshmem();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void reduceImageSizes(Context context) {
+ super.reduceImageSizes(context);
+
+ final Resources resources = context.getResources();
+
+ int progressIconSize =
+ resources.getDimensionPixelSize(R.dimen.notification_progress_icon_size);
+ if (mStartIcon != null) {
+ mStartIcon.scaleDownIfNecessary(progressIconSize, progressIconSize);
+ }
+ if (mEndIcon != null) {
+ mEndIcon.scaleDownIfNecessary(progressIconSize, progressIconSize);
+ }
+ if (mTrackerIcon != null) {
+ int progressTrackerWidth = resources.getDimensionPixelSize(
+ R.dimen.notification_progress_tracker_width);
+ int progressTrackerHeight = resources.getDimensionPixelSize(
+ R.dimen.notification_progress_tracker_height);
+ mTrackerIcon.scaleDownIfNecessary(progressTrackerWidth, progressTrackerHeight);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void addExtras(Bundle extras) {
+ super.addExtras(extras);
+ extras.putParcelableArrayList(EXTRA_PROGRESS_SEGMENTS,
+ getProgressSegmentsAsBundleList(mProgressSegments));
+ extras.putParcelableArrayList(EXTRA_PROGRESS_STEPS,
+ getProgressStepsAsBundleList(mProgressSteps));
+
+ extras.putInt(EXTRA_PROGRESS, mProgress);
+ extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mIndeterminate);
+ extras.putInt(EXTRA_PROGRESS_MAX, getProgressMax());
+ extras.putBoolean(EXTRA_STYLED_BY_PROGRESS, mIsStyledByProgress);
+
+ if (mTrackerIcon != null) {
+ extras.putParcelable(EXTRA_PROGRESS_TRACKER_ICON, mTrackerIcon);
+ } else {
+ extras.remove(EXTRA_PROGRESS_TRACKER_ICON);
+ }
+
+ if (mStartIcon != null) {
+ extras.putParcelable(EXTRA_PROGRESS_START_ICON, mStartIcon);
+ } else {
+ extras.remove(EXTRA_PROGRESS_START_ICON);
+ }
+
+ if (mEndIcon != null) {
+ extras.putParcelable(EXTRA_PROGRESS_END_ICON, mEndIcon);
+ } else {
+ extras.remove(EXTRA_PROGRESS_END_ICON);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ protected void restoreFromExtras(Bundle extras) {
+ super.restoreFromExtras(extras);
+ mProgressSegments = getProgressSegmentsFromBundleList(
+ extras.getParcelableArrayList(EXTRA_PROGRESS_SEGMENTS, Bundle.class));
+ mProgress = extras.getInt(EXTRA_PROGRESS, 0);
+ mIndeterminate = extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE, false);
+ mIsStyledByProgress = extras.getBoolean(EXTRA_STYLED_BY_PROGRESS, true);
+ mTrackerIcon = extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class);
+ mStartIcon = extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class);
+ mEndIcon = extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class);
+ mProgressSteps = getProgressStepsFromBundleList(
+ extras.getParcelableArrayList(EXTRA_PROGRESS_STEPS, Bundle.class));
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean displayCustomViewInline() {
+ // This is a lie; True is returned for progress notifications to make sure
+ // that the custom view is not used instead of the template, but it will not
+ // actually be included.
+ return true;
+ }
+
+ private static @NonNull ArrayList<Bundle> getProgressSegmentsAsBundleList(
+ @Nullable List<Segment> progressSegments) {
+ final ArrayList<Bundle> segments = new ArrayList<>();
+ if (progressSegments != null && !progressSegments.isEmpty()) {
+ for (int i = 0; i < progressSegments.size(); i++) {
+ final Segment segment = progressSegments.get(i);
+ if (segment.getLength() <= 0) {
+ continue;
+ }
+
+ final Bundle bundle = new Bundle();
+ bundle.putInt(KEY_SEGMENT_LENGTH, segment.getLength());
+ bundle.putInt(KEY_ELEMENT_STABLE_ID, segment.getStableId());
+ bundle.putInt(KEY_ELEMENT_COLOR, segment.getColor());
+
+ segments.add(bundle);
+ }
+ }
+
+ return segments;
+ }
+
+ private static @NonNull List<Segment> getProgressSegmentsFromBundleList(
+ @Nullable List<Bundle> segmentBundleList) {
+ final ArrayList<Segment> segments = new ArrayList<>();
+ if (segmentBundleList != null && !segmentBundleList.isEmpty()) {
+ for (int i = 0; i < segmentBundleList.size(); i++) {
+ final Bundle segmentBundle = segmentBundleList.get(i);
+ final int length = segmentBundle.getInt(KEY_SEGMENT_LENGTH);
+ if (length <= 0) {
+ continue;
+ }
+
+ final int stableId = segmentBundle.getInt(KEY_ELEMENT_STABLE_ID);
+ final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR,
+ Notification.COLOR_DEFAULT);
+ final Segment segment = new Segment(length)
+ .setStableId(stableId).setColor(color);
+
+ segments.add(segment);
+ }
+ }
+
+ return segments;
+ }
+
+ private static @NonNull ArrayList<Bundle> getProgressStepsAsBundleList(
+ @Nullable List<Step> progressSteps) {
+ final ArrayList<Bundle> steps = new ArrayList<>();
+ if (progressSteps != null && !progressSteps.isEmpty()) {
+ for (int i = 0; i < progressSteps.size(); i++) {
+ final Step step = progressSteps.get(i);
+ if (step.getPosition() < 0) {
+ continue;
+ }
+
+ final Bundle bundle = new Bundle();
+ bundle.putInt(KEY_STEP_POSITION, step.getPosition());
+ bundle.putInt(KEY_ELEMENT_STABLE_ID, step.getStableId());
+ bundle.putInt(KEY_ELEMENT_COLOR, step.getColor());
+
+ steps.add(bundle);
+ }
+ }
+
+ return steps;
+ }
+
+ private static @NonNull List<Step> getProgressStepsFromBundleList(
+ @Nullable List<Bundle> stepBundleList) {
+ final ArrayList<Step> steps = new ArrayList<>();
+
+ if (stepBundleList != null && !stepBundleList.isEmpty()) {
+ for (int i = 0; i < stepBundleList.size(); i++) {
+ final Bundle segmentBundle = stepBundleList.get(i);
+ final int position = segmentBundle.getInt(KEY_STEP_POSITION);
+ if (position < 0) {
+ continue;
+ }
+ final int stableId = segmentBundle.getInt(KEY_ELEMENT_STABLE_ID);
+ final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR,
+ Notification.COLOR_DEFAULT);
+ final Step step = new Step(position).setStableId(stableId).setColor(color);
+ steps.add(step);
+ }
+ }
+
+ return steps;
+ }
+
+ /**
+ * A segment of the progress bar, which defines its length and color.
+ * Segments allow for creating progress bars with multiple colors or sections
+ * to represent different stages or categories of progress.
+ * For example, Traffic conditions along a navigation journey.
+ */
+ public static final class Segment {
+ private int mLength;
+ private int mStableId = 0;
+ @ColorInt
+ private int mColor = Notification.COLOR_DEFAULT;
+
+ /**
+ * Create a segment with a non-zero length.
+ * @param length
+ * See {@link #getLength}
+ */
+ public Segment(int length) {
+ mLength = length;
+ }
+
+ /**
+ * The length of this Segment within the progress bar.
+ * This value has no units, it is just relative to the length of other segments,
+ * and the value provided to {@link ProgressStyle#setProgress}.
+ */
+ public int getLength() {
+ return mLength;
+ }
+
+ /**
+ * Gets the stable id of this Segment.
+ *
+ * @see #setStableId
+ */
+ public int getStableId() {
+ return mStableId;
+ }
+
+ /**
+ * Optional ID used to uniquely identify the element across updates.
+ */
+ public @NonNull Segment setStableId(int stableId) {
+ mStableId = stableId;
+ return this;
+ }
+
+ /**
+ * Returns the color of this Segment.
+ *
+ * @see #setColor
+ */
+ @ColorInt
+ public int getColor() {
+ return mColor;
+ }
+
+ /**
+ * Optional color of this Segment
+ */
+ public @NonNull Segment setColor(@ColorInt int color) {
+ mColor = color;
+ return this;
+ }
+
+ /**
+ * Needed for {@link Notification.Style#areNotificationsVisiblyDifferent}
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Segment segment = (Segment) o;
+ return mLength == segment.mLength && mStableId == segment.mStableId
+ && mColor == segment.mColor;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mLength, mStableId, mColor);
+ }
+ }
+
+ /**
+ * A step within the progress bar, defining its position and color.
+ * Steps are designated points within a progressbar to visualize
+ * distinct stages or milestones.
+ * For example, you might use steps to mark stops in a multi-stop
+ * navigation journey, where each step represents a destination.
+ */
+ public static final class Step {
+
+ private int mPosition;
+ private int mStableId;
+ @ColorInt
+ private int mColor = Notification.COLOR_DEFAULT;
+
+ /**
+ * Create a step element.
+ * The position of this step on the progress bar
+ * relative to {@link ProgressStyle#getProgressMax}
+ * @param position
+ * See {@link #getPosition}
+ */
+ public Step(int position) {
+ mPosition = position;
+ }
+
+ /**
+ * Gets the position of this Step.
+ * The position of this step on the progress bar
+ * relative to {@link ProgressStyle#getProgressMax}.
+ */
+ public int getPosition() {
+ return mPosition;
+ }
+
+
+ /**
+ * Optional ID used to uniqurely identify the element across updates.
+ */
+ public int getStableId() {
+ return mStableId;
+ }
+
+ /**
+ * Optional ID used to uniqurely identify the element across updates.
+ */
+ public @NonNull Step setStableId(int stableId) {
+ mStableId = stableId;
+ return this;
+ }
+
+ /**
+ * Returns the color of this Segment.
+ *
+ * @see #setColor
+ */
+ @ColorInt
+ public int getColor() {
+ return mColor;
+ }
+
+ /**
+ * Optional color of this Segment
+ */
+ public @NonNull Step setColor(@ColorInt int color) {
+ mColor = color;
+ return this;
+ }
+
+ /**
+ * Needed for {@link Notification.Style#areNotificationsVisiblyDifferent}
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Step step = (Step) o;
+ return mPosition == step.mPosition && mStableId == step.mStableId
+ && mColor == step.mColor;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPosition, mStableId, mColor);
+ }
+ }
+ }
+
/**
* Notification style for custom views that are decorated by the system
*
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index f397ef2b151c..6683dc044c9a 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -808,6 +808,12 @@
This is bigger than displayed because listeners can use it for other displays
e.g. wearables. -->
<dimen name="notification_person_icon_max_size">144dp</dimen>
+ <!-- The size of the progress bar icon -->
+ <dimen name="notification_progress_icon_size">20dp</dimen>
+ <!-- The size of the progress tracker width -->
+ <dimen name="notification_progress_tracker_width">40dp</dimen>
+ <!-- The size of the progress tracker height -->
+ <dimen name="notification_progress_tracker_height">20dp</dimen>
<!-- The maximum size of the small notification icon on low memory devices. -->
<dimen name="notification_small_icon_size_low_ram">@dimen/notification_small_icon_size</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 76a8e9254e2f..5f40a6c7eba4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3855,6 +3855,9 @@
<java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
<java-symbol type="dimen" name="notification_person_icon_max_size" />
+ <java-symbol type="dimen" name="notification_progress_icon_size" />
+ <java-symbol type="dimen" name="notification_progress_tracker_width" />
+ <java-symbol type="dimen" name="notification_progress_tracker_height" />
<java-symbol type="dimen" name="notification_small_icon_size_low_ram"/>
<java-symbol type="dimen" name="notification_big_picture_max_height_low_ram"/>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 0f73df92ca93..edcea241e620 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -97,7 +97,6 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.TextAppearanceSpan;
import android.util.Pair;
-import android.util.Slog;
import android.widget.RemoteViews;
import androidx.test.InstrumentationRegistry;
@@ -118,6 +117,7 @@ import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
+import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
@@ -2114,6 +2114,300 @@ public class NotificationTest {
assertThat(n.getWhen()).isEqualTo(9);
}
+ @Test
+ public void getNotificationStyleClass_forPlatformClassName_returnsPlatformClass() {
+ final List<Class<? extends Notification.Style>> platformStyleClasses = List.of(
+ Notification.BigTextStyle.class, Notification.BigPictureStyle.class,
+ Notification.MessagingStyle.class, Notification.CallStyle.class,
+ Notification.InboxStyle.class, Notification.MediaStyle.class,
+ Notification.DecoratedCustomViewStyle.class,
+ Notification.DecoratedMediaCustomViewStyle.class
+ );
+
+ for (Class<? extends Notification.Style> platformStyleClass : platformStyleClasses) {
+ assertThat(Notification.getNotificationStyleClass(platformStyleClass.getName()))
+ .isEqualTo(platformStyleClass);
+ }
+ }
+
+ @Test
+ public void getNotificationStyleClass_forNotPlatformClassName_returnsNull() {
+ assertThat(Notification.getNotificationStyleClass(NotAPlatformStyle.class.getName()))
+ .isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_richOngoingEnabled_platformClass() {
+ assertThat(
+ Notification.getNotificationStyleClass(Notification.ProgressStyle.class.getName()))
+ .isEqualTo(Notification.ProgressStyle.class);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_richOngoingDisabled_notPlatformClass() {
+ assertThat(
+ Notification.getNotificationStyleClass(Notification.ProgressStyle.class.getName()))
+ .isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onSegmentChange_visiblyDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .addProgressSegment(new Notification.ProgressStyle.Segment(100))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(50)
+ .setColor(Color.RED)));
+
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .addProgressSegment(new Notification.ProgressStyle.Segment(100))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(50)
+ .setColor(Color.BLUE)));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void indeterminateProgressStyle_onSegmentChange_visiblyNotDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
+ .addProgressSegment(new Notification.ProgressStyle.Segment(100))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(50)
+ .setColor(Color.RED)));
+
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
+ .addProgressSegment(new Notification.ProgressStyle.Segment(100))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(50)
+ .setColor(Color.BLUE)));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onStartIconChange_visiblyDifferent() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
+
+ final Icon icon2 = Icon.createWithBitmap(
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressStartIcon(icon1));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressStartIcon(icon2));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onEndIconChange_visiblyDifferent() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
+
+ final Icon icon2 = Icon.createWithBitmap(
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressEndIcon(icon1));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressEndIcon(icon2));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onProgressChange_visiblyDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgress(20));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgress(21));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void indeterminateProgressStyle_onProgressChange_visiblyNotDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .setProgressIndeterminate(true).setProgress(20));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .setProgressIndeterminate(true).setProgress(21));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onIsStyledByProgressChange_visiblyDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setStyledByProgress(true));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setStyledByProgress(false));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void indeterminateProgressStyle_onIsStyledByProgressChange_visiblyNotDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .setProgressIndeterminate(true).setStyledByProgress(true));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .setProgressIndeterminate(true).setStyledByProgress(false));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onProgressStepChange_visiblyDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .addProgressStep(new Notification.ProgressStyle.Step(10)));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .addProgressStep(new Notification.ProgressStyle.Step(12)));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void indeterminateProgressStyle_onProgressStepChange_visiblyNotDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
+ .addProgressStep(new Notification.ProgressStyle.Step(10)));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
+ .addProgressStep(new Notification.ProgressStyle.Step(12)));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onTrackerIconChange_visiblyDifferent() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
+
+ final Icon icon2 = Icon.createWithBitmap(
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressTrackerIcon(icon1));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressTrackerIcon(icon2));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void indeterminateProgressStyle_onTrackerIconChange_visiblyNotDifferent() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
+
+ final Icon icon2 = Icon.createWithBitmap(
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
+ .setProgressTrackerIcon(icon1));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .setProgressIndeterminate(true).setProgressTrackerIcon(icon2));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onIndeterminateChange_visiblyDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(false));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_getProgressMax_default100() {
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ assertThat(progressStyle.getProgressMax()).isEqualTo(100);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_getProgressMax_nooSegments_returnsDefault() {
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ progressStyle.setProgressSegments(Collections.emptyList());
+ assertThat(progressStyle.getProgressMax()).isEqualTo(100);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_getProgressMax_returnsSumOfSegmentLength() {
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ progressStyle
+ .addProgressSegment(new Notification.ProgressStyle.Segment(10))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(20));
+
+ assertThat(progressStyle.getProgressMax()).isEqualTo(30);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_getProgressMax_onSegmentOverflow_returnsDefault() {
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ progressStyle
+ .addProgressSegment(new Notification.ProgressStyle.Segment(Integer.MAX_VALUE))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(10));
+
+ assertThat(progressStyle.getProgressMax()).isEqualTo(100);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_indeterminate_defaultValueFalse() {
+ final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
+
+ assertThat(progressStyle1.isProgressIndeterminate()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_styledByProgress_defaultValueTrue() {
+ final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
+
+ assertThat(progressStyle1.isStyledByProgress()).isTrue();
+ }
private void assertValid(Notification.Colors c) {
// Assert that all colors are populated
assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
@@ -2214,4 +2508,11 @@ public class NotificationTest {
new Intent(action).setPackage(mContext.getPackageName()),
PendingIntent.FLAG_MUTABLE);
}
+
+ private static class NotAPlatformStyle extends Notification.Style {
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Notification.Style other) {
+ return false;
+ }
+ }
}