Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 43844aaa authored by Matías Hernández's avatar Matías Hernández
Browse files

Replace FixedInstant by FixedDate + FixedTime

Using LocalDate and LocalTime removes the need for TimeZone shenanigans, and also for choosing whether to display date or time. The date+time combination is not a good fit for the limited space of metric anyway.

Also, remove FORMAT_CHRONOMETER_MINUTES and adjust naming of remaining options.

Bug: 415827681
Test: atest FrameworkCoreTests:NotificationMetricStyleTest
Flag: android.app.api_metric_style
Change-Id: I3b70ea6acd621ba9039b7cb10d26eea79d5cc771
parent d209de9c
Loading
Loading
Loading
Loading
+17 −18
Original line number Diff line number Diff line
@@ -7041,6 +7041,16 @@ package android.app {
    field public static final int MEANING_WEATHER_UV_INDEX = 327682; // 0x50002
  }
  public static final class Notification.Metric.FixedDate extends android.app.Notification.Metric.MetricValue {
    ctor public Notification.Metric.FixedDate(@NonNull java.time.LocalDate);
    ctor public Notification.Metric.FixedDate(@NonNull java.time.LocalDate, int);
    method public int getFormat();
    method @NonNull public java.time.LocalDate getValue();
    field public static final int FORMAT_AUTOMATIC = 0; // 0x0
    field public static final int FORMAT_LONG_DATE = 1; // 0x1
    field public static final int FORMAT_SHORT_DATE = 2; // 0x2
  }
  public static final class Notification.Metric.FixedFloat extends android.app.Notification.Metric.MetricValue {
    ctor public Notification.Metric.FixedFloat(float);
    ctor public Notification.Metric.FixedFloat(float, @Nullable String);
@@ -7051,21 +7061,6 @@ package android.app {
    method public float getValue();
  }
  public static final class Notification.Metric.FixedInstant extends android.app.Notification.Metric.MetricValue {
    ctor public Notification.Metric.FixedInstant(@NonNull java.time.Instant);
    ctor public Notification.Metric.FixedInstant(@NonNull java.time.Instant, int);
    ctor public Notification.Metric.FixedInstant(@NonNull java.time.Instant, int, @Nullable java.util.TimeZone);
    method public int getFormat();
    method @Nullable public java.util.TimeZone getTimeZone();
    method @NonNull public java.time.Instant getValue();
    field public static final int FORMAT_AUTOMATIC = 0; // 0x0
    field public static final int FORMAT_LONG_DATE = 1; // 0x1
    field public static final int FORMAT_LONG_DATE_TIME = 3; // 0x3
    field public static final int FORMAT_SHORT_DATE = 2; // 0x2
    field public static final int FORMAT_SHORT_DATE_TIME = 4; // 0x4
    field public static final int FORMAT_TIME = 5; // 0x5
  }
  public static final class Notification.Metric.FixedInt extends android.app.Notification.Metric.MetricValue {
    ctor public Notification.Metric.FixedInt(int);
    ctor public Notification.Metric.FixedInt(int, @Nullable String);
@@ -7078,6 +7073,11 @@ package android.app {
    method @NonNull public String getValue();
  }
  public static final class Notification.Metric.FixedTime extends android.app.Notification.Metric.MetricValue {
    ctor public Notification.Metric.FixedTime(@NonNull java.time.LocalTime);
    method @NonNull public java.time.LocalTime getValue();
  }
  public abstract static class Notification.Metric.MetricValue {
  }
@@ -7092,9 +7092,8 @@ package android.app {
    method public boolean isStopwatch();
    method public boolean isTimer();
    field public static final int FORMAT_ADAPTIVE = 1; // 0x1
    field public static final int FORMAT_CHRONOMETER_AUTOMATIC = 0; // 0x0
    field public static final int FORMAT_CHRONOMETER_MINUTES = 2; // 0x2
    field public static final int FORMAT_CHRONOMETER_SECONDS = 3; // 0x3
    field public static final int FORMAT_AUTOMATIC = 0; // 0x0
    field public static final int FORMAT_CHRONOMETER = 3; // 0x3
  }
  @FlaggedApi("android.app.api_metric_style") public static class Notification.MetricStyle extends android.app.Notification.Style {
+116 −105
Original line number Diff line number Diff line
@@ -131,12 +131,14 @@ import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
import java.util.function.Consumer;
/**
@@ -11711,8 +11713,9 @@ public class Notification implements Parcelable
        // Fixed-time-related meanings.
        /**
         * Point-in-time-related metric. Generally associated with {@link FixedInstant}
         * values. Use when none of the specific {@code MEANING_EVENT_} options are a good fit.
         * Point-in-time-related metric. Generally associated with {@link FixedDate} or
         * {@link FixedTime} values. Use when none of the specific {@code MEANING_EVENT_} options
         * are a good fit.
         */
        public static final int MEANING_EVENT = 3 << 16;
@@ -11916,7 +11919,7 @@ public class Notification implements Parcelable
        /**
         * Creates a Metric with the specified value, meaning, and label.
         *
         * @param value   one of the subclasses of {@link MetricValue}, such as {@link FixedInstant}
         * @param value   one of the subclasses of {@link MetricValue}, such as {@link FixedInt}
         * @param label   metric label -- should be 10 characters or fewer
         * @param meaning recommended so that Notification Listeners can judge the importance
         *                (and required freshness) of the metric
@@ -12036,10 +12039,11 @@ public class Notification implements Parcelable
            private static final String KEY_TYPE = "_type";
            private static final int TYPE_TIME_DIFFERENCE = 1;
            private static final int TYPE_FIXED_INSTANT = 2;
            private static final int TYPE_FIXED_INT = 3;
            private static final int TYPE_FIXED_FLOAT = 4;
            private static final int TYPE_FIXED_STRING = 5;
            private static final int TYPE_FIXED_DATE = 2;
            private static final int TYPE_FIXED_TIME = 3;
            private static final int TYPE_FIXED_INT = 4;
            private static final int TYPE_FIXED_FLOAT = 5;
            private static final int TYPE_FIXED_STRING = 6;
            // Restrict inheritance to inner classes of Notification.
            private MetricValue() { }
@@ -12049,7 +12053,8 @@ public class Notification implements Parcelable
                int type = bundle.getInt(KEY_TYPE);
                return switch (type) {
                    case TYPE_TIME_DIFFERENCE -> TimeDifference.fromBundle(bundle);
                    case TYPE_FIXED_INSTANT -> FixedInstant.fromBundle(bundle);
                    case TYPE_FIXED_DATE -> FixedDate.fromBundle(bundle);
                    case TYPE_FIXED_TIME -> FixedTime.fromBundle(bundle);
                    case TYPE_FIXED_INT -> FixedInt.fromBundle(bundle);
                    case TYPE_FIXED_FLOAT -> FixedFloat.fromBundle(bundle);
                    case TYPE_FIXED_STRING -> FixedString.fromBundle(bundle);
@@ -12062,8 +12067,10 @@ public class Notification implements Parcelable
                Bundle bundle = new Bundle();
                if (value instanceof TimeDifference) {
                    bundle.putInt(KEY_TYPE, TYPE_TIME_DIFFERENCE);
                } else if (value instanceof FixedInstant) {
                    bundle.putInt(KEY_TYPE, TYPE_FIXED_INSTANT);
                } else if (value instanceof FixedDate) {
                    bundle.putInt(KEY_TYPE, TYPE_FIXED_DATE);
                } else if (value instanceof FixedTime) {
                    bundle.putInt(KEY_TYPE, TYPE_FIXED_TIME);
                } else if (value instanceof FixedInt) {
                    bundle.putInt(KEY_TYPE, TYPE_FIXED_INT);
                } else if (value instanceof FixedFloat) {
@@ -12098,32 +12105,20 @@ public class Notification implements Parcelable
         */
        public static final class TimeDifference extends MetricValue {
            /**
             * Formatting option: chronometer-style (e.g. 1:05:00; 15:00; 1:00, 0:00), with
             * precision chosen by the system.
             */
            public static final int FORMAT_CHRONOMETER_AUTOMATIC = 0;
            /** Formatting option: automatically chosen by the system. */
            public static final int FORMAT_AUTOMATIC = 0;
            /** Formatting option: adaptive (e.g. 1h 5m; 15m; 1m; now). */
            public static final int FORMAT_ADAPTIVE = 1;
            /**
             * Formatting option: chronometer-style, showing minutes but not including seconds
             * (i.e. two hours = "2:00").
             */
            public static final int FORMAT_CHRONOMETER_MINUTES = 2;
            /**
             * Formatting option: chronometer-style, including seconds (i.e. two hours = "2:00").
             */
            public static final int FORMAT_CHRONOMETER_SECONDS = 3;
            /** Formatting option: chronometer-style, (e.g. two hours = "2:00:00"). */
            public static final int FORMAT_CHRONOMETER = 3;
            /** @hide */
            @IntDef(prefix = { "FORMAT_" }, value = {
                    FORMAT_CHRONOMETER_AUTOMATIC,
                    FORMAT_AUTOMATIC,
                    FORMAT_ADAPTIVE,
                    FORMAT_CHRONOMETER_MINUTES,
                    FORMAT_CHRONOMETER_SECONDS
                    FORMAT_CHRONOMETER
            })
            @Retention(RetentionPolicy.SOURCE)
            public @interface Format {}
@@ -12189,8 +12184,8 @@ public class Notification implements Parcelable
                        "Either zeroTime or pausedDuration must be present, and not both. "
                                + "Received %s,%s",
                        zeroTime, pausedDuration);
                checkArgument(format >= FORMAT_CHRONOMETER_AUTOMATIC
                        && format <= FORMAT_CHRONOMETER_SECONDS, "Invalid format: %s", format);
                checkArgument(format >= FORMAT_AUTOMATIC && format <= FORMAT_CHRONOMETER,
                        "Invalid format: %s", format);
                mZeroTime = zeroTime;
                mPausedDuration = pausedDuration;
                mCountDown = countDown;
@@ -12206,7 +12201,7 @@ public class Notification implements Parcelable
                if (zeroTime != null || pausedDuration != null) {
                    return new TimeDifference(zeroTime, pausedDuration,
                            bundle.getBoolean(KEY_COUNT_DOWN),
                            bundle.getInt(KEY_FORMAT, FORMAT_CHRONOMETER_AUTOMATIC));
                            bundle.getInt(KEY_FORMAT, FORMAT_AUTOMATIC));
                } else {
                    return null;
                }
@@ -12300,100 +12295,69 @@ public class Notification implements Parcelable
            }
        }
        /** A metric value for showing a clock time. */
        public static final class FixedInstant extends MetricValue {
        /** A metric value for showing a date. */
        public static final class FixedDate extends MetricValue {
            /**
             * Formatting option. The system will decide how to format the date and time, and
             * whether to omit any pieces, depending on available space, the relationship between
             * the {@link Instant} and the current date and time of day, etc.
             * Formatting option. The system will decide how to format the date, and whether to omit
             * any pieces, depending on available space, the relationship between the
             * {@link LocalDate} and the current date, etc.
             */
            public static final int FORMAT_AUTOMATIC = 0;
            /** Formatting option. Only the date will be shown, in long format. */
            public static final int FORMAT_LONG_DATE = 1;
            /** Formatting option. Only the date will be shown, in short format. */
            public static final int FORMAT_SHORT_DATE = 2;
            /**
             * Formatting option. Both the date (in long format) and the time of day will be
             * shown.
             * Formatting option. The date will be shown in a longer format, e.g. "Aug 13 2025"
             * (according to the device's locale).
             */
            public static final int FORMAT_LONG_DATE_TIME = 3;
            public static final int FORMAT_LONG_DATE = 1;
            /**
             * Formatting option. Both the date (in short format) and the time of day will be
             * shown.
             * Formatting option. The date will be shown in a shorter format, e.g. "13/8/25"
             * (according to the device's locale).
             */
            public static final int FORMAT_SHORT_DATE_TIME = 4;
            /** Formatting option. Only the time of day will be shown. */
            public static final int FORMAT_TIME = 5;
            public static final int FORMAT_SHORT_DATE = 2;
            /** @hide */
            @IntDef(prefix = { "FORMAT_" }, value = {
                    FORMAT_AUTOMATIC,
                    FORMAT_LONG_DATE,
                    FORMAT_SHORT_DATE,
                    FORMAT_LONG_DATE_TIME,
                    FORMAT_SHORT_DATE_TIME,
                    FORMAT_TIME
            })
            @Retention(RetentionPolicy.SOURCE)
            public @interface Format {}
            private static final String KEY_VALUE = "value";
            private static final String KEY_FORMAT = "format";
            private static final String KEY_TIMEZONE = "timezone";
            private final Instant mValue;
            private final LocalDate mValue;
            private final @Format int mFormat;
            private final TimeZone mTimeZone;
            /**
             * Creates a {@link FixedInstant} where the {@link Instant} will be displayed with
             * {@link #FORMAT_AUTOMATIC} and in the device's {@link TimeZone}.
             * Creates a {@link FixedDate} where the {@link LocalDate} will be displayed with
             * {@link #FORMAT_AUTOMATIC}.
             */
            public FixedInstant(@NonNull Instant value) {
            public FixedDate(@NonNull LocalDate value) {
                this(value, FORMAT_AUTOMATIC);
            }
            /**
             * Creates a {@link FixedInstant} where the {@link Instant} will be displayed in the
             * device's {@link TimeZone}.
             */
            public FixedInstant(@NonNull Instant value, @Format int format) {
                this(value, format, /* timeZone= */ null);
            }
            /**
             * Creates a {@link FixedInstant} where the {@link Instant} will be displayed in the
             * specified {@link TimeZone}.
             *
             * @param timeZone this should be used <em>only</em> for situations where the user
             *                 would understand that the explicit timezone differs from the
             *                 device's, e.g. the estimated arrival time of a plane in a different
             *                 timezone
             * Creates a {@link FixedDate} where the {@link LocalDate} will be displayed in the
             * specified formatting option.
             */
            public FixedInstant(@NonNull Instant value, @Format int format,
                    @Nullable TimeZone timeZone) {
            public FixedDate(@NonNull LocalDate value, @Format int format) {
                mValue = requireNonNull(value);
                checkArgument(format >= FORMAT_AUTOMATIC && format <= FORMAT_TIME,
                checkArgument(format >= FORMAT_AUTOMATIC && format <= FORMAT_SHORT_DATE,
                        "Invalid format: %s", format);
                mFormat = format;
                mTimeZone = timeZone;
            }
            @Nullable
            private static FixedInstant fromBundle(Bundle bundle) {
                Instant value = bundle.containsKey(KEY_VALUE)
                        ? Instant.ofEpochMilli(bundle.getLong(KEY_VALUE)) : null;
            private static FixedDate fromBundle(Bundle bundle) {
                LocalDate value = bundle.containsKey(KEY_VALUE)
                        ? LocalDate.ofEpochDay(bundle.getLong(KEY_VALUE)) : null;
                if (value != null) {
                    int format = bundle.getInt(KEY_FORMAT, FORMAT_AUTOMATIC);
                    TimeZone timeZone = bundle.containsKey(KEY_TIMEZONE)
                            ? TimeZone.getTimeZone(bundle.getString(KEY_TIMEZONE)) : null;
                    return new FixedInstant(value, format, timeZone);
                    return new FixedDate(value, format);
                } else {
                    return null;
                }
@@ -12402,25 +12366,21 @@ public class Notification implements Parcelable
            /** @hide */
            @Override
            protected void toBundle(Bundle bundle) {
                bundle.putLong(KEY_VALUE, mValue.toEpochMilli());
                bundle.putLong(KEY_VALUE, mValue.toEpochDay());
                bundle.putInt(KEY_FORMAT, mFormat);
                if (mTimeZone != null) {
                    bundle.putString(KEY_TIMEZONE, mTimeZone.getID());
                }
            }
            @Override
            public boolean equals(Object obj) {
                if (!(obj instanceof FixedInstant that)) return false;
                if (!(obj instanceof FixedDate that)) return false;
                if (this == that) return true;
                return Objects.equals(this.mValue, that.mValue)
                        && this.mFormat == that.mFormat
                        && Objects.equals(this.mTimeZone, that.mTimeZone);
                        && this.mFormat == that.mFormat;
            }
            @Override
            public int hashCode() {
                return Objects.hash(mValue, mFormat, mTimeZone);
                return Objects.hash(mValue, mFormat);
            }
            @Override
@@ -12428,30 +12388,81 @@ public class Notification implements Parcelable
                return getClass().getSimpleName() + "{"
                        + "mValue=" + mValue
                        + ", mFormat=" + mFormat
                        + ", mTimeZone=" + mTimeZone
                        + "}";
            }
            /** The {@link Instant} value. */
            public @NonNull Instant getValue() {
            /** The {@link LocalDate} value. */
            public @NonNull LocalDate getValue() {
                return mValue;
            }
            /** The formatting option for the {@link Instant} value. */
            /** The formatting option for the {@link LocalDate} value. */
            public @Format int getFormat() {
                return mFormat;
            }
        }
        /**
         * A metric value for showing a clock time.
         *
         * The time will be shown as-is, so should be in a user-understandable timezone (most
         * likely the device's own, unless it's clear from context that it would be different, such
         * as a flight's arrival time).
         */
        public static final class FixedTime extends MetricValue {
            private static final String KEY_VALUE = "value";
            private final LocalTime mValue;
            /**
             * (Optional) The time zone to use. Defaults to the device’s local timezone.
             * This may not be shown to the user.
             * Creates a {@link FixedTime} with the specified {@link LocalTime}.
             *
             * <p>This should be used <em>only</em> for situations where the user would understand
             * that the explicit timezone differs from the current one, e.g. estimated
             * arrival time of a plane in a different timezone.
             * <p>Maximum precision is seconds; milliseconds will be ignored.
             */
            @Nullable public TimeZone getTimeZone() {
                return mTimeZone;
            public FixedTime(@NonNull LocalTime value) {
                mValue = requireNonNull(value).truncatedTo(ChronoUnit.SECONDS);
            }
            @Nullable
            private static FixedTime fromBundle(Bundle bundle) {
                LocalTime value = bundle.containsKey(KEY_VALUE)
                        ? LocalTime.ofSecondOfDay(bundle.getLong(KEY_VALUE)) : null;
                if (value != null) {
                    return new FixedTime(value);
                } else {
                    return null;
                }
            }
            /** @hide */
            @Override
            protected void toBundle(Bundle bundle) {
                bundle.putLong(KEY_VALUE, mValue.toSecondOfDay());
            }
            @Override
            public boolean equals(Object obj) {
                if (!(obj instanceof FixedTime that)) return false;
                if (this == that) return true;
                return Objects.equals(this.mValue, that.mValue);
            }
            @Override
            public int hashCode() {
                return Objects.hash(mValue);
            }
            @Override
            public String toString() {
                return getClass().getSimpleName() + "{"
                        + "mValue=" + mValue
                        + "}";
            }
            /** The {@link LocalTime} value. */
            public @NonNull LocalTime getValue() {
                return mValue;
            }
        }
+10 −6
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.app.Notification.Metric.MEANING_CELESTIAL_TIDE;
import static android.app.Notification.Metric.MEANING_CHRONOMETER;
import static android.app.Notification.Metric.MEANING_CHRONOMETER_STOPWATCH;
import static android.app.Notification.Metric.MEANING_CHRONOMETER_TIMER;
import static android.app.Notification.Metric.MEANING_EVENT_DATE;
import static android.app.Notification.Metric.MEANING_EVENT_TIME;
import static android.app.Notification.Metric.MEANING_HEALTH;
import static android.app.Notification.Metric.MEANING_HEALTH_ACTIVE_TIME;
@@ -35,10 +36,11 @@ import static android.app.Notification.Metric.MEANING_WEATHER_TEMPERATURE_OUTDOO
import static com.google.common.truth.Truth.assertThat;

import android.app.Notification.Metric;
import android.app.Notification.Metric.FixedDate;
import android.app.Notification.Metric.FixedFloat;
import android.app.Notification.Metric.FixedInstant;
import android.app.Notification.Metric.FixedInt;
import android.app.Notification.Metric.FixedString;
import android.app.Notification.Metric.FixedTime;
import android.app.Notification.Metric.TimeDifference;
import android.app.Notification.MetricStyle;
import android.os.Bundle;
@@ -54,8 +56,9 @@ import org.junit.runner.RunWith;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.TimeZone;

@RunWith(AndroidJUnit4.class)
@Presubmit
@@ -94,12 +97,13 @@ public class NotificationMetricStyleTest {
                        "Time:", MEANING_CHRONOMETER_TIMER))
                .addMetric(new Metric(
                        TimeDifference.forPausedStopwatch(Duration.ofHours(4),
                                TimeDifference.FORMAT_CHRONOMETER_MINUTES),
                                TimeDifference.FORMAT_CHRONOMETER),
                        "Stopwatch:", MEANING_CHRONOMETER_STOPWATCH))
                .addMetric(new Metric(
                        new FixedInstant(Instant.ofEpochMilli(55),
                                FixedInstant.FORMAT_SHORT_DATE,
                                TimeZone.getTimeZone("America/Montevideo")),
                        new FixedDate(LocalDate.of(2025, 6, 2), FixedDate.FORMAT_SHORT_DATE),
                        "Event date:", MEANING_EVENT_DATE))
                .addMetric(new Metric(
                        new FixedTime(LocalTime.of(10, 30)),
                        "Event time:", MEANING_EVENT_TIME))
                .addMetric(new Metric(
                        new FixedInt(12, "drummers"), "Label", MEANING_UNKNOWN))
+4 −2
Original line number Diff line number Diff line
@@ -77,6 +77,8 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -84,7 +86,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -687,7 +688,8 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
                        .put(char.class, 'N')
                        .put(Instant.class, Instant.ofEpochMilli(1747306630000L))
                        .put(Duration.class, Duration.ofSeconds(10))
                        .put(TimeZone.class, TimeZone.getDefault())
                        .put(LocalDate.class, LocalDate.of(1789, 7, 14))
                        .put(LocalTime.class, LocalTime.of(17, 0))
                        .build();

        private final Context mContext;