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

Commit a5beb4fa authored by Matías Hernández's avatar Matías Hernández Committed by Android (Google) Code Review
Browse files

Merge "Flesh out Notification.MetricStyle" into main

parents 2bbd131e d209de9c
Loading
Loading
Loading
Loading
+117 −0
Original line number Diff line number Diff line
@@ -6985,8 +6985,125 @@ package android.app {
    method public android.app.Notification.MessagingStyle.Message setData(String, android.net.Uri);
  }
  @FlaggedApi("android.app.api_metric_style") public static class Notification.Metric {
    ctor public Notification.Metric(@NonNull android.app.Notification.Metric.MetricValue, @NonNull String, int);
    method @NonNull public String getLabel();
    method public int getMeaning();
    method @NonNull public android.app.Notification.Metric.MetricValue getValue();
    field public static final int MEANING_CELESTIAL = 393216; // 0x60000
    field public static final int MEANING_CELESTIAL_MOON_PHASE = 393217; // 0x60001
    field public static final int MEANING_CELESTIAL_SUNRISE = 393219; // 0x60003
    field public static final int MEANING_CELESTIAL_SUNSET = 393220; // 0x60004
    field public static final int MEANING_CELESTIAL_TIDE = 393218; // 0x60002
    field public static final int MEANING_CHRONOMETER = 131072; // 0x20000
    field public static final int MEANING_CHRONOMETER_ELAPSED_DURATION = 131076; // 0x20004
    field public static final int MEANING_CHRONOMETER_EVENT_COUNTDOWN = 131074; // 0x20002
    field public static final int MEANING_CHRONOMETER_STOPWATCH = 131075; // 0x20003
    field public static final int MEANING_CHRONOMETER_TIMER = 131073; // 0x20001
    field public static final int MEANING_EVENT = 196608; // 0x30000
    field public static final int MEANING_EVENT_DATE = 196610; // 0x30002
    field public static final int MEANING_EVENT_DATE_COUNTDOWN = 196609; // 0x30001
    field public static final int MEANING_EVENT_TIME = 196611; // 0x30003
    field public static final int MEANING_HEALTH = 262144; // 0x40000
    field public static final int MEANING_HEALTH_ACTIVE_TIME = 262153; // 0x40009
    field public static final int MEANING_HEALTH_BLOOD_OXYGEN_SATURATION = 262159; // 0x4000f
    field public static final int MEANING_HEALTH_BLOOD_PRESSURE = 262148; // 0x40004
    field public static final int MEANING_HEALTH_BREATHING_RATE = 262161; // 0x40011
    field public static final int MEANING_HEALTH_CALORIES = 262151; // 0x40007
    field public static final int MEANING_HEALTH_HEART_RATE_CURRENT = 262145; // 0x40001
    field public static final int MEANING_HEALTH_HEART_RATE_RESTING = 262146; // 0x40002
    field public static final int MEANING_HEALTH_HEART_RATE_VARIABILITY = 262147; // 0x40003
    field public static final int MEANING_HEALTH_READINESS = 262152; // 0x40008
    field public static final int MEANING_HEALTH_SKIN_TEMPERATURE = 262160; // 0x40010
    field public static final int MEANING_HEALTH_SLEEP_DURATION = 262158; // 0x4000e
    field public static final int MEANING_HEALTH_SLEEP_SCORE = 262156; // 0x4000c
    field public static final int MEANING_HEALTH_SLEEP_STAGE = 262157; // 0x4000d
    field public static final int MEANING_HEALTH_STEPS = 262149; // 0x40005
    field public static final int MEANING_HEALTH_STEP_GOAL = 262150; // 0x40006
    field public static final int MEANING_HEALTH_WATER_CONSUMPTION = 262154; // 0x4000a
    field public static final int MEANING_HEALTH_WATER_GOAL = 262155; // 0x4000b
    field public static final int MEANING_MOVEMENT = 65536; // 0x10000
    field public static final int MEANING_MOVEMENT_DISTANCE_REMAINING = 65538; // 0x10002
    field public static final int MEANING_MOVEMENT_DISTANCE_TRAVELED = 65537; // 0x10001
    field public static final int MEANING_MOVEMENT_SPEED = 65539; // 0x10003
    field public static final int MEANING_TRAVEL = 458752; // 0x70000
    field public static final int MEANING_TRAVEL_BOARDING_LOCATION = 458755; // 0x70003
    field public static final int MEANING_TRAVEL_PORT = 458753; // 0x70001
    field public static final int MEANING_TRAVEL_TERMINAL = 458754; // 0x70002
    field public static final int MEANING_UNKNOWN = 0; // 0x0
    field public static final int MEANING_WEATHER = 327680; // 0x50000
    field public static final int MEANING_WEATHER_AIR_QUALITY_INDEX = 327683; // 0x50003
    field public static final int MEANING_WEATHER_ATMOSPHERIC_PRESSURE = 327689; // 0x50009
    field public static final int MEANING_WEATHER_FORECAST = 327681; // 0x50001
    field public static final int MEANING_WEATHER_POLLUTANT = 327684; // 0x50004
    field public static final int MEANING_WEATHER_PRECIPITATION = 327688; // 0x50008
    field public static final int MEANING_WEATHER_RELATIVE_HUMIDITY = 327687; // 0x50007
    field public static final int MEANING_WEATHER_TEMPERATURE_INDOOR = 327686; // 0x50006
    field public static final int MEANING_WEATHER_TEMPERATURE_OUTDOOR = 327685; // 0x50005
    field public static final int MEANING_WEATHER_UV_INDEX = 327682; // 0x50002
  }
  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);
    ctor public Notification.Metric.FixedFloat(float, @Nullable String, @IntRange(from=0x0, to=0x6) int, @IntRange(from=0x0, to=0x6) int);
    method @IntRange(from=0, to=6) public int getMaxFractionDigits();
    method @IntRange(from=0, to=6) public int getMinFractionDigits();
    method @Nullable public String getUnit();
    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);
    method @Nullable public String getUnit();
    method public int getValue();
  }
  public static final class Notification.Metric.FixedString extends android.app.Notification.Metric.MetricValue {
    ctor public Notification.Metric.FixedString(@NonNull String);
    method @NonNull public String getValue();
  }
  public abstract static class Notification.Metric.MetricValue {
  }
  public static final class Notification.Metric.TimeDifference extends android.app.Notification.Metric.MetricValue {
    method @NonNull public static android.app.Notification.Metric.TimeDifference forPausedStopwatch(@NonNull java.time.Duration, int);
    method @NonNull public static android.app.Notification.Metric.TimeDifference forPausedTimer(@NonNull java.time.Duration, int);
    method @NonNull public static android.app.Notification.Metric.TimeDifference forStopwatch(@NonNull java.time.Instant, int);
    method @NonNull public static android.app.Notification.Metric.TimeDifference forTimer(@NonNull java.time.Instant, int);
    method public int getFormat();
    method @Nullable public java.time.Duration getPausedDuration();
    method @Nullable public java.time.Instant getZeroTime();
    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
  }
  @FlaggedApi("android.app.api_metric_style") public static class Notification.MetricStyle extends android.app.Notification.Style {
    ctor public Notification.MetricStyle();
    method @NonNull public android.app.Notification.MetricStyle addMetric(@NonNull android.app.Notification.Metric);
    method @NonNull public java.util.List<android.app.Notification.Metric> getMetrics();
    method @NonNull public android.app.Notification.MetricStyle setMetrics(@NonNull java.util.List<android.app.Notification.Metric>);
  }
  @FlaggedApi("android.app.api_rich_ongoing") public static class Notification.ProgressStyle extends android.app.Notification.Style {
+5 −0
Original line number Diff line number Diff line
@@ -369,6 +369,11 @@ package android.app {
    field public static final int FLAG_USER_INITIATED_JOB = 32768; // 0x8000
  }

  public static final class Notification.Metric.FixedFloat extends android.app.Notification.Metric.MetricValue {
    field public static final int DEFAULT_MAX_FRACTION_DIGITS = 3; // 0x3
    field public static final int DEFAULT_MIN_FRACTION_DIGITS = 0; // 0x0
  }

  public final class NotificationChannel implements android.os.Parcelable {
    method @NonNull public android.app.NotificationChannel copy();
    method public boolean isImportanceLockedByCriticalDeviceFunction();
+1202 −20

File changed.

Preview size limit exceeded, changes collapsed.

+210 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

package android.app;

import static android.app.Notification.EXTRA_METRICS;
import static android.app.Notification.Metric.MEANING_CELESTIAL;
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_TIME;
import static android.app.Notification.Metric.MEANING_HEALTH;
import static android.app.Notification.Metric.MEANING_HEALTH_ACTIVE_TIME;
import static android.app.Notification.Metric.MEANING_HEALTH_CALORIES;
import static android.app.Notification.Metric.MEANING_HEALTH_READINESS;
import static android.app.Notification.Metric.MEANING_TRAVEL;
import static android.app.Notification.Metric.MEANING_TRAVEL_TERMINAL;
import static android.app.Notification.Metric.MEANING_UNKNOWN;
import static android.app.Notification.Metric.MEANING_WEATHER_TEMPERATURE_OUTDOOR;

import static com.google.common.truth.Truth.assertThat;

import android.app.Notification.Metric;
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.TimeDifference;
import android.app.Notification.MetricStyle;
import android.os.Bundle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;

import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

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

@RunWith(AndroidJUnit4.class)
@Presubmit
@EnableFlags(Flags.FLAG_API_METRIC_STYLE)
public class NotificationMetricStyleTest {

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    @Test
    public void addExtras_writesExtras() {
        MetricStyle style = new MetricStyle()
                .addMetric(new Metric(new FixedInt(4, "birds"), "4", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(5, "rings"), "5", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(6, "geese"), "6", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(7, "swans"), "7", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(8, "maids"), "8", MEANING_UNKNOWN));

        Bundle bundle = new Bundle();
        style.addExtras(bundle);

        ArrayList<Bundle> storedBundles = bundle.getParcelableArrayList(EXTRA_METRICS,
                Bundle.class);

        assertThat(storedBundles).isNotNull();
        assertThat(storedBundles).hasSize(5);
    }

    @Test
    public void restoreFromExtras_restoresWrittenMetrics() {
        Bundle bundle = new Bundle();
        MetricStyle original = new MetricStyle()
                .addMetric(new Metric(
                        TimeDifference.forTimer(Instant.ofEpochMilli(1),
                                TimeDifference.FORMAT_ADAPTIVE),
                        "Time:", MEANING_CHRONOMETER_TIMER))
                .addMetric(new Metric(
                        TimeDifference.forPausedStopwatch(Duration.ofHours(4),
                                TimeDifference.FORMAT_CHRONOMETER_MINUTES),
                        "Stopwatch:", MEANING_CHRONOMETER_STOPWATCH))
                .addMetric(new Metric(
                        new FixedInstant(Instant.ofEpochMilli(55),
                                FixedInstant.FORMAT_SHORT_DATE,
                                TimeZone.getTimeZone("America/Montevideo")),
                        "Event time:", MEANING_EVENT_TIME))
                .addMetric(new Metric(
                        new FixedInt(12, "drummers"), "Label", MEANING_UNKNOWN))
                .addMetric(new Metric(
                        new FixedInt(42), "Answer", MEANING_CELESTIAL))
                .addMetric(new Metric(
                        new FixedFloat(0.75f), "Readiness", MEANING_HEALTH_READINESS))
                .addMetric(new Metric(
                        new FixedFloat(273f, "°K"),
                        "Temp", MEANING_WEATHER_TEMPERATURE_OUTDOOR))
                .addMetric(new Metric(
                        new FixedFloat(12.345f, null, 0, 3),
                        "Active time", MEANING_HEALTH_ACTIVE_TIME))
                .addMetric(new Metric(
                        new FixedString("This is the last"), "Last", MEANING_UNKNOWN));

        original.addExtras(bundle);
        MetricStyle recovered = new MetricStyle();
        recovered.restoreFromExtras(bundle);

        assertThat(recovered).isEqualTo(original);
    }

    @Test
    public void areNotificationsVisiblyDifferent_sameMetrics_false() {
        MetricStyle style1 = new MetricStyle()
                .addMetric(new Metric(new FixedInt(1), "Cal", MEANING_HEALTH_CALORIES))
                .addMetric(new Metric(new FixedInt(2), "Cal", MEANING_HEALTH_CALORIES));

        MetricStyle style2 = new MetricStyle()
                .addMetric(new Metric(new FixedInt(1), "Cal", MEANING_HEALTH_CALORIES))
                .addMetric(new Metric(new FixedInt(2), "Cal", MEANING_HEALTH_CALORIES));

        assertThat(style1.areNotificationsVisiblyDifferent(style2)).isFalse();
        assertThat(style2.areNotificationsVisiblyDifferent(style1)).isFalse();
    }

    @Test
    public void areNotificationsVisiblyDifferent_differentMetrics_true() {
        MetricStyle style1 = new MetricStyle()
                .addMetric(new Metric(new FixedInt(1, "thingies"), "a", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(2, "widgets"), "b", MEANING_UNKNOWN));

        MetricStyle style2 = new MetricStyle()
                .addMetric(new Metric(new FixedInt(1, "gizmos"), "c", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(2, "doodads"), "d", MEANING_UNKNOWN));

        assertThat(style1.areNotificationsVisiblyDifferent(style2)).isTrue();
        assertThat(style2.areNotificationsVisiblyDifferent(style1)).isTrue();
    }

    @Test
    public void areNotificationsVisiblyDifferent_differentMetricCounts_true() {
        MetricStyle style1 = new MetricStyle()
                .addMetric(new Metric(new FixedInt(1, "gizmos"), "a", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(2, "doodads"), "b", MEANING_UNKNOWN));

        MetricStyle style2 = new MetricStyle()
                .addMetric(new Metric(new FixedInt(1, "gizmos"), "a", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(2, "doodads"), "b", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(3, "whatsits"), "c", MEANING_UNKNOWN));

        assertThat(style1.areNotificationsVisiblyDifferent(style2)).isTrue();
        assertThat(style2.areNotificationsVisiblyDifferent(style1)).isTrue();
    }

    @Test
    public void areNotificationsVisiblyDifferent_firstThreeEqual_false() {
        MetricStyle style1 = new MetricStyle()
                .addMetric(new Metric(new FixedInt(1), "a", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(2), "b", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(3), "c", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedString("Ignored thing"), "d", MEANING_UNKNOWN));

        MetricStyle style2 = new MetricStyle()
                .addMetric(new Metric(new FixedInt(1), "a", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(2), "b", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedInt(3), "c", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedString("Also ignored"), "d", MEANING_UNKNOWN))
                .addMetric(new Metric(new FixedString("And this too"), "e", MEANING_UNKNOWN));

        assertThat(style1.areNotificationsVisiblyDifferent(style2)).isFalse();
        assertThat(style2.areNotificationsVisiblyDifferent(style1)).isFalse();
    }

    @Test
    public void getMeaningCategory_concreteMeaning_returnsCategory() {
        assertThat(Metric.getMeaningCategory(MEANING_CHRONOMETER_TIMER)).isEqualTo(
                MEANING_CHRONOMETER);
        assertThat(Metric.getMeaningCategory(MEANING_CELESTIAL_TIDE)).isEqualTo(MEANING_CELESTIAL);
        assertThat(Metric.getMeaningCategory(MEANING_HEALTH_ACTIVE_TIME)).isEqualTo(MEANING_HEALTH);
        assertThat(Metric.getMeaningCategory(MEANING_TRAVEL_TERMINAL)).isEqualTo(MEANING_TRAVEL);
    }

    @Test
    public void getMeaningCategory_categoryMeaning_returnsCategory() {
        assertThat(Metric.getMeaningCategory(MEANING_UNKNOWN)).isEqualTo(MEANING_UNKNOWN);
        assertThat(Metric.getMeaningCategory(MEANING_CHRONOMETER)).isEqualTo(MEANING_CHRONOMETER);
        assertThat(Metric.getMeaningCategory(MEANING_CELESTIAL)).isEqualTo(MEANING_CELESTIAL);
        assertThat(Metric.getMeaningCategory(MEANING_HEALTH)).isEqualTo(MEANING_HEALTH);
        assertThat(Metric.getMeaningCategory(MEANING_TRAVEL)).isEqualTo(MEANING_TRAVEL);
    }

    @Test
    public void getMeaningCategory_invalidCategory_returnsUnknown() {
        assertThat(Metric.getMeaningCategory(0xaaaa0001)).isEqualTo(MEANING_UNKNOWN);
    }
}
+48 −11

File changed.

Preview size limit exceeded, changes collapsed.