summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Caitlin Shkuratov <caitlinshk@google.com> 2024-12-02 19:56:23 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-12-02 19:56:23 +0000
commitcefc86d2efb831308e55bfb552c30e517335f894 (patch)
treeccaf662a737185e576fde62dea5f0dde0701fca0
parentac37f0f68a22b192e8d478e808cb0dba58cb65fd (diff)
parent66e289404e87ee2edd4092c7444dadcf4d530c12 (diff)
Merge "DateTimeView: Add additional display configuration options." into main
-rw-r--r--core/java/android/view/flags/view_flags.aconfig10
-rw-r--r--core/java/android/widget/DateTimeView.java227
-rw-r--r--core/res/res/values/attrs.xml26
-rw-r--r--core/res/res/values/strings.xml80
-rw-r--r--core/res/res/values/symbols.xml17
-rw-r--r--core/tests/coretests/src/android/widget/DateTimeViewTest.java135
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt10
12 files changed, 496 insertions, 41 deletions
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 641b01054acb..f6fdec94c332 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -142,4 +142,12 @@ flag {
description: "Recover from buffer stuffing when SurfaceFlinger misses a frame"
bug: "294922229"
is_fixed_read_only: true
-} \ No newline at end of file
+}
+
+flag {
+ name: "date_time_view_relative_time_display_configs"
+ namespace: "systemui"
+ description: "Enables DateTimeView to use additional display configurations for relative time"
+ bug: "364653005"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
index 41ff69d6fb5f..143b4b770984 100644
--- a/core/java/android/widget/DateTimeView.java
+++ b/core/java/android/widget/DateTimeView.java
@@ -21,6 +21,7 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+import android.annotation.IntDef;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
@@ -41,6 +42,8 @@ import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.text.DateFormat;
import java.time.Instant;
import java.time.LocalDate;
@@ -70,6 +73,23 @@ public class DateTimeView extends TextView {
private static final int SHOW_TIME = 0;
private static final int SHOW_MONTH_DAY_YEAR = 1;
+ /** @hide */
+ @IntDef(value = {UNIT_DISPLAY_LENGTH_SHORTEST, UNIT_DISPLAY_LENGTH_MEDIUM})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UnitDisplayLength {}
+ public static final int UNIT_DISPLAY_LENGTH_SHORTEST = 0;
+ public static final int UNIT_DISPLAY_LENGTH_MEDIUM = 1;
+
+ /** @hide */
+ @IntDef(flag = true, value = {DISAMBIGUATION_TEXT_PAST, DISAMBIGUATION_TEXT_FUTURE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DisambiguationTextMask {}
+ public static final int DISAMBIGUATION_TEXT_PAST = 0x01;
+ public static final int DISAMBIGUATION_TEXT_FUTURE = 0x02;
+
+ private final boolean mCanUseRelativeTimeDisplayConfigs =
+ android.view.flags.Flags.dateTimeViewRelativeTimeDisplayConfigs();
+
private long mTimeMillis;
// The LocalDateTime equivalent of mTimeMillis but truncated to minute, i.e. no seconds / nanos.
private LocalDateTime mLocalTime;
@@ -81,6 +101,8 @@ public class DateTimeView extends TextView {
private static final ThreadLocal<ReceiverInfo> sReceiverInfo = new ThreadLocal<ReceiverInfo>();
private String mNowText;
private boolean mShowRelativeTime;
+ private int mRelativeTimeDisambiguationTextMask;
+ private int mRelativeTimeUnitDisplayLength = UNIT_DISPLAY_LENGTH_SHORTEST;
public DateTimeView(Context context) {
this(context, null);
@@ -89,20 +111,23 @@ public class DateTimeView extends TextView {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public DateTimeView(Context context, AttributeSet attrs) {
super(context, attrs);
- final TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.DateTimeView, 0,
- 0);
-
- final int N = a.getIndexCount();
- for (int i = 0; i < N; i++) {
- int attr = a.getIndex(i);
- switch (attr) {
- case R.styleable.DateTimeView_showRelative:
- boolean relative = a.getBoolean(i, false);
- setShowRelativeTime(relative);
- break;
- }
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.DateTimeView, 0, 0);
+
+ setShowRelativeTime(a.getBoolean(R.styleable.DateTimeView_showRelative, false));
+ if (mCanUseRelativeTimeDisplayConfigs) {
+ setRelativeTimeDisambiguationTextMask(
+ a.getInt(
+ R.styleable.DateTimeView_relativeTimeDisambiguationText,
+ // The original implementation showed disambiguation text for future
+ // times only, so continue with that default.
+ DISAMBIGUATION_TEXT_FUTURE));
+ setRelativeTimeUnitDisplayLength(
+ a.getInt(
+ R.styleable.DateTimeView_relativeTimeUnitDisplayLength,
+ UNIT_DISPLAY_LENGTH_SHORTEST));
}
+
a.recycle();
}
@@ -150,6 +175,29 @@ public class DateTimeView extends TextView {
update();
}
+ /** See {@link R.styleable.DateTimeView_relativeTimeDisambiguationText}. */
+ @android.view.RemotableViewMethod
+ public void setRelativeTimeDisambiguationTextMask(
+ @DisambiguationTextMask int disambiguationTextMask) {
+ if (!mCanUseRelativeTimeDisplayConfigs) {
+ return;
+ }
+ mRelativeTimeDisambiguationTextMask = disambiguationTextMask;
+ updateNowText();
+ update();
+ }
+
+ /** See {@link R.styleable.DateTimeView_relativeTimeUnitDisplayLength}. */
+ @android.view.RemotableViewMethod
+ public void setRelativeTimeUnitDisplayLength(@UnitDisplayLength int unitDisplayLength) {
+ if (!mCanUseRelativeTimeDisplayConfigs) {
+ return;
+ }
+ mRelativeTimeUnitDisplayLength = unitDisplayLength;
+ updateNowText();
+ update();
+ }
+
/**
* Returns whether this view shows relative time
*
@@ -264,17 +312,11 @@ public class DateTimeView extends TextView {
return;
} else if (duration < HOUR_IN_MILLIS) {
count = (int)(duration / MINUTE_IN_MILLIS);
- result = getContext().getResources().getString(past
- ? com.android.internal.R.string.duration_minutes_shortest
- : com.android.internal.R.string.duration_minutes_shortest_future,
- count);
+ result = getContext().getResources().getString(getMinutesStringId(past), count);
millisIncrease = MINUTE_IN_MILLIS;
} else if (duration < DAY_IN_MILLIS) {
count = (int)(duration / HOUR_IN_MILLIS);
- result = getContext().getResources().getString(past
- ? com.android.internal.R.string.duration_hours_shortest
- : com.android.internal.R.string.duration_hours_shortest_future,
- count);
+ result = getContext().getResources().getString(getHoursStringId(past), count);
millisIncrease = HOUR_IN_MILLIS;
} else if (duration < YEAR_IN_MILLIS) {
// In weird cases it can become 0 because of daylight savings
@@ -283,10 +325,7 @@ public class DateTimeView extends TextView {
LocalDateTime localNow = toLocalDateTime(now, zoneId);
count = Math.max(Math.abs(dayDistance(localDateTime, localNow)), 1);
- result = getContext().getResources().getString(past
- ? com.android.internal.R.string.duration_days_shortest
- : com.android.internal.R.string.duration_days_shortest_future,
- count);
+ result = getContext().getResources().getString(getDaysStringId(past), count);
if (past || count != 1) {
mUpdateTimeMillis = computeNextMidnight(localNow, zoneId);
millisIncrease = -1;
@@ -296,10 +335,7 @@ public class DateTimeView extends TextView {
} else {
count = (int)(duration / YEAR_IN_MILLIS);
- result = getContext().getResources().getString(past
- ? com.android.internal.R.string.duration_years_shortest
- : com.android.internal.R.string.duration_years_shortest_future,
- count);
+ result = getContext().getResources().getString(getYearsStringId(past), count);
millisIncrease = YEAR_IN_MILLIS;
}
if (millisIncrease != -1) {
@@ -312,6 +348,139 @@ public class DateTimeView extends TextView {
maybeSetText(result);
}
+ private int getMinutesStringId(boolean past) {
+ if (!mCanUseRelativeTimeDisplayConfigs) {
+ return past
+ ? com.android.internal.R.string.duration_minutes_shortest
+ : com.android.internal.R.string.duration_minutes_shortest_future;
+ }
+
+ if (mRelativeTimeUnitDisplayLength == UNIT_DISPLAY_LENGTH_SHORTEST) {
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1m ago"
+ return com.android.internal.R.string.duration_minutes_shortest_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1m"
+ return com.android.internal.R.string.duration_minutes_shortest_future;
+ } else {
+ // "1m"
+ return com.android.internal.R.string.duration_minutes_shortest;
+ }
+ } else { // UNIT_DISPLAY_LENGTH_MEDIUM
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1min ago"
+ return com.android.internal.R.string.duration_minutes_medium_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1min"
+ return com.android.internal.R.string.duration_minutes_medium_future;
+ } else {
+ // "1min"
+ return com.android.internal.R.string.duration_minutes_medium;
+ }
+ }
+ }
+
+ private int getHoursStringId(boolean past) {
+ if (!mCanUseRelativeTimeDisplayConfigs) {
+ return past
+ ? com.android.internal.R.string.duration_hours_shortest
+ : com.android.internal.R.string.duration_hours_shortest_future;
+ }
+ if (mRelativeTimeUnitDisplayLength == UNIT_DISPLAY_LENGTH_SHORTEST) {
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1h ago"
+ return com.android.internal.R.string.duration_hours_shortest_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1h"
+ return com.android.internal.R.string.duration_hours_shortest_future;
+ } else {
+ // "1h"
+ return com.android.internal.R.string.duration_hours_shortest;
+ }
+ } else { // UNIT_DISPLAY_LENGTH_MEDIUM
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1hr ago"
+ return com.android.internal.R.string.duration_hours_medium_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1hr"
+ return com.android.internal.R.string.duration_hours_medium_future;
+ } else {
+ // "1hr"
+ return com.android.internal.R.string.duration_hours_medium;
+ }
+ }
+ }
+
+ private int getDaysStringId(boolean past) {
+ if (!mCanUseRelativeTimeDisplayConfigs) {
+ return past
+ ? com.android.internal.R.string.duration_days_shortest
+ : com.android.internal.R.string.duration_days_shortest_future;
+ }
+ if (mRelativeTimeUnitDisplayLength == UNIT_DISPLAY_LENGTH_SHORTEST) {
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1d ago"
+ return com.android.internal.R.string.duration_days_shortest_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1d"
+ return com.android.internal.R.string.duration_days_shortest_future;
+ } else {
+ // "1d"
+ return com.android.internal.R.string.duration_days_shortest;
+ }
+ } else { // UNIT_DISPLAY_LENGTH_MEDIUM
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1d ago"
+ return com.android.internal.R.string.duration_days_medium_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1d"
+ return com.android.internal.R.string.duration_days_medium_future;
+ } else {
+ // "1d"
+ return com.android.internal.R.string.duration_days_medium;
+ }
+ }
+ }
+
+ private int getYearsStringId(boolean past) {
+ if (!mCanUseRelativeTimeDisplayConfigs) {
+ return past
+ ? com.android.internal.R.string.duration_years_shortest
+ : com.android.internal.R.string.duration_years_shortest_future;
+ }
+ if (mRelativeTimeUnitDisplayLength == UNIT_DISPLAY_LENGTH_SHORTEST) {
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1y ago"
+ return com.android.internal.R.string.duration_years_shortest_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1y"
+ return com.android.internal.R.string.duration_years_shortest_future;
+ } else {
+ // "1y"
+ return com.android.internal.R.string.duration_years_shortest;
+ }
+ } else { // UNIT_DISPLAY_LENGTH_MEDIUM
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1y ago"
+ return com.android.internal.R.string.duration_years_medium_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1y"
+ return com.android.internal.R.string.duration_years_medium_future;
+ } else {
+ // "1y"
+ return com.android.internal.R.string.duration_years_medium;
+ }
+ }
+ }
+
/**
* Sets text only if the text has actually changed. This prevents needles relayouts of this
* view when set to wrap_content.
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8c46ccc84f39..238aca556003 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10570,6 +10570,32 @@
<declare-styleable name="DateTimeView">
<attr name="showRelative" format="boolean" />
+ <!-- For relative times, controls what kinds of times get disambiguation text.
+
+ The default value is "future".
+
+ Does nothing if showRelative=false.
+ -->
+ <attr name="relativeTimeDisambiguationText">
+ <!-- Times in the past will have extra clarifying text indicating that the time is in
+ the past. For example, 1 minute ago is represented as "1m ago". If this flag is not
+ set, times in the past are represented as just "1m". -->
+ <flag name="past" value="0x01" />
+ <!-- Times in the future will have extra clarifying text indicating that the time is in
+ the future. For example, 1 minute in the future is represented as "in 1m". If this
+ flag is not set, times in the future are represented as just "1m". -->
+ <flag name="future" value="0x02" />
+ </attr>
+ <!-- For relative times, sets the length of the time unit displayed (minutes, hours, etc.).
+
+ Does nothing if showRelative=false.
+ -->
+ <attr name="relativeTimeUnitDisplayLength">
+ <!-- The time unit will be shown as a short as possible (1 character if possible). -->
+ <enum name="shortest" value="0" />
+ <!-- The time unit will be shortened to a medium length (2-3 characters in general). -->
+ <enum name="medium" value="1" />
+ </attr>
</declare-styleable>
<declare-styleable name="ResolverDrawerLayout_LayoutParams">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d498b9191559..cfc3ddca27eb 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3135,6 +3135,86 @@
in <xliff:g id="count">%d</xliff:g>y
</string>
+ <!-- Phrase describing a time duration using minutes that is as short as possible, preferrably one character. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
+ <string name="duration_minutes_shortest_past">
+ <xliff:g id="count">%d</xliff:g>m ago
+ </string>
+
+ <!-- Phrase describing a time duration using hours that is as short as possible, preferrably one character. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
+ <string name="duration_hours_shortest_past">
+ <xliff:g id="count">%d</xliff:g>h ago
+ </string>
+
+ <!-- Phrase describing a time duration using days that is as short as possible, preferrably one character. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
+ <string name="duration_days_shortest_past">
+ <xliff:g example="1" id="count">%d</xliff:g>d ago
+ </string>
+
+ <!-- Phrase describing a time duration using years that is as short as possible, preferrably one character. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
+ <string name="duration_years_shortest_past">
+ <xliff:g id="count">%d</xliff:g>y ago
+ </string>
+
+ <!-- Phrase describing a time duration using minutes that is a medium length, preferrably two or three characters. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=8] -->
+ <string name="duration_minutes_medium">
+ <xliff:g id="count">%d</xliff:g>min
+ </string>
+
+ <!-- Phrase describing a time duration using hours that is a medium length, preferrably two or three characters. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=8] -->
+ <string name="duration_hours_medium">
+ <xliff:g id="count">%d</xliff:g>hr
+ </string>
+
+ <!-- Phrase describing a time duration using days that is a medium length, preferrably two or three characters. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=8] -->
+ <string name="duration_days_medium">
+ <xliff:g id="count">%d</xliff:g>d
+ </string>
+
+ <!-- Phrase describing a time duration using years that is a medium length, preferrably two or three characters. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=8] -->
+ <string name="duration_years_medium">
+ <xliff:g id="count">%d</xliff:g>yr
+ </string>
+
+ <!-- Phrase describing a time duration using minutes that is a medium length, preferrably two or three characters. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_minutes_medium_future">
+ in <xliff:g id="count">%d</xliff:g>min
+ </string>
+
+ <!-- Phrase describing a time duration using hours that is a medium length, preferrably two or three characters. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_hours_medium_future">
+ in <xliff:g id="count">%d</xliff:g>hr
+ </string>
+
+ <!-- Phrase describing a time duration using days that is a medium length, preferrably two or three characters. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_days_medium_future">
+ in <xliff:g example="1" id="count">%d</xliff:g>d
+ </string>
+
+ <!-- Phrase describing a time duration using years that is a medium length, preferrably two or three characters. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_years_medium_future">
+ in <xliff:g id="count">%d</xliff:g>yr
+ </string>
+
+ <!-- Phrase describing a time duration using minutes that is a medium length, preferrably two or three characters. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_minutes_medium_past">
+ <xliff:g id="count">%d</xliff:g>min ago
+ </string>
+
+ <!-- Phrase describing a time duration using hours that is a medium length, preferrably two or three characters. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_hours_medium_past">
+ <xliff:g id="count">%d</xliff:g>hr ago
+ </string>
+
+ <!-- Phrase describing a time duration using days that is a medium length, preferrably two or three characters. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_days_medium_past">
+ <xliff:g example="1" id="count">%d</xliff:g>d ago
+ </string>
+
+ <!-- Phrase describing a time duration using years that is a medium length, preferrably two or three characters. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_years_medium_past">
+ <xliff:g id="count">%d</xliff:g>yr ago
+ </string>
+
<!-- Phrase describing a relative time using minutes in the past that is not shown on the screen but used for accessibility. [CHAR LIMIT=NONE] -->
<string name="duration_minutes_relative">{count, plural,
=1 {# minute ago}
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9a51b724a09c..ff8f571664fe 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3367,6 +3367,23 @@
<java-symbol type="string" name="duration_hours_shortest_future" />
<java-symbol type="string" name="duration_days_shortest_future" />
<java-symbol type="string" name="duration_years_shortest_future" />
+ <java-symbol type="string" name="duration_minutes_shortest_past" />
+ <java-symbol type="string" name="duration_hours_shortest_past" />
+ <java-symbol type="string" name="duration_days_shortest_past" />
+ <java-symbol type="string" name="duration_years_shortest_past" />
+
+ <java-symbol type="string" name="duration_minutes_medium" />
+ <java-symbol type="string" name="duration_hours_medium" />
+ <java-symbol type="string" name="duration_days_medium" />
+ <java-symbol type="string" name="duration_years_medium" />
+ <java-symbol type="string" name="duration_minutes_medium_future" />
+ <java-symbol type="string" name="duration_hours_medium_future" />
+ <java-symbol type="string" name="duration_days_medium_future" />
+ <java-symbol type="string" name="duration_years_medium_future" />
+ <java-symbol type="string" name="duration_minutes_medium_past" />
+ <java-symbol type="string" name="duration_hours_medium_past" />
+ <java-symbol type="string" name="duration_days_medium_past" />
+ <java-symbol type="string" name="duration_years_medium_past" />
<java-symbol type="string" name="duration_minutes_relative" />
<java-symbol type="string" name="duration_hours_relative" />
diff --git a/core/tests/coretests/src/android/widget/DateTimeViewTest.java b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
index a8fd913d857f..be65277a020e 100644
--- a/core/tests/coretests/src/android/widget/DateTimeViewTest.java
+++ b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
@@ -69,6 +69,141 @@ public class DateTimeViewTest {
Assert.assertFalse(dateTimeView.wasLayoutRequested());
}
+ @UiThreadTest
+ @Test
+ public void disambiguationTextMask_none_noPastOrFutureDisambiguationText() {
+ final TestDateTimeView dateTimeView = new TestDateTimeView();
+ dateTimeView.setShowRelativeTime(true);
+ dateTimeView.setRelativeTimeDisambiguationTextMask(0);
+
+ // Minutes
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofMinutes(8).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofMinutes(8).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("ago"));
+
+ // Hours
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofHours(4).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofHours(4).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("ago"));
+
+ // Days
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(14).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(14).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("ago"));
+
+ // Years
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(400).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(400).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("ago"));
+ }
+
+ @UiThreadTest
+ @Test
+ public void disambiguationTextMask_bothPastAndFuture_usesPastAndFutureDisambiguationText() {
+ final TestDateTimeView dateTimeView = new TestDateTimeView();
+ dateTimeView.setShowRelativeTime(true);
+ dateTimeView.setRelativeTimeDisambiguationTextMask(
+ DateTimeView.DISAMBIGUATION_TEXT_PAST | DateTimeView.DISAMBIGUATION_TEXT_FUTURE);
+
+ // Minutes
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofMinutes(8).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofMinutes(8).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("ago"));
+
+ // Hours
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofHours(4).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofHours(4).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("ago"));
+
+ // Days
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(14).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(14).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("ago"));
+
+ // Years
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(400).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(400).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("ago"));
+ }
+
+ @UiThreadTest
+ @Test
+ public void unitDisplayLength_shortest_noMediumText() {
+ final TestDateTimeView dateTimeView = new TestDateTimeView();
+ dateTimeView.setShowRelativeTime(true);
+ dateTimeView.setRelativeTimeUnitDisplayLength(DateTimeView.UNIT_DISPLAY_LENGTH_SHORTEST);
+
+ // Minutes
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofMinutes(8).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("min"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofMinutes(8).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("min"));
+
+ // Hours
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofHours(4).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("hr"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofHours(4).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("hr"));
+
+ // Days excluded because the string is the same for both shortest length and medium length
+
+ // Years
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(400).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("yr"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(400).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("yr"));
+ }
+
+ @UiThreadTest
+ @Test
+ public void unitDisplayLength_medium_usesMediumText() {
+ final TestDateTimeView dateTimeView = new TestDateTimeView();
+ dateTimeView.setShowRelativeTime(true);
+ dateTimeView.setRelativeTimeUnitDisplayLength(DateTimeView.UNIT_DISPLAY_LENGTH_MEDIUM);
+
+ // Minutes
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofMinutes(8).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("min"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofMinutes(8).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("min"));
+
+ // Hours
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofHours(4).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("hr"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofHours(4).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("hr"));
+
+ // Days excluded because the string is the same for both shortest length and medium length
+
+ // Years
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(400).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("yr"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(400).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("yr"));
+ }
+
private static class TestDateTimeView extends DateTimeView {
private boolean mRequestedLayout = false;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
index 7fed47a4653e..e96dd16e9023 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -42,7 +42,8 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
fun notificationChip_startsWithStartingModel() =
kosmos.runTest {
val icon = mock<StatusBarIconView>()
- val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = icon)
+ val startingNotif =
+ activeNotificationModel(key = "notif1", statusBarChipIcon = icon, whenTime = 5432)
val underTest = factory.create(startingNotif)
@@ -50,6 +51,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
assertThat(latest!!.key).isEqualTo("notif1")
assertThat(latest!!.statusBarChipIconView).isEqualTo(icon)
+ assertThat(latest!!.whenTime).isEqualTo(5432)
}
@Test
@@ -65,11 +67,16 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
val newIconView = mock<StatusBarIconView>()
underTest.setNotification(
- activeNotificationModel(key = "notif1", statusBarChipIcon = newIconView)
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = newIconView,
+ whenTime = 6543,
+ )
)
assertThat(latest!!.key).isEqualTo("notif1")
assertThat(latest!!.statusBarChipIconView).isEqualTo(newIconView)
+ assertThat(latest!!.whenTime).isEqualTo(6543)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 16376c5b3850..a09cb69dc9d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -101,7 +101,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
assertThat(latest).hasSize(1)
val chip = latest!![0]
- assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
+ assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
assertThat(chip.icon).isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(icon))
}
@@ -171,7 +171,8 @@ class NotifChipsViewModelTest : SysuiTestCase() {
companion object {
fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) {
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
+ assertThat(latest)
+ .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
.isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon))
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
index 087b51032fcf..c57c807360b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
@@ -105,7 +105,7 @@ constructor(
}
return null
}
- return NotificationChipModel(key, statusBarChipIconView)
+ return NotificationChipModel(key, statusBarChipIconView, whenTime)
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
index 5698ee6d1917..bc4241d9bca5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -19,4 +19,8 @@ package com.android.systemui.statusbar.chips.notification.domain.model
import com.android.systemui.statusbar.StatusBarIconView
/** Modeling all the data needed to render a status bar notification chip. */
-data class NotificationChipModel(val key: String, val statusBarChipIconView: StatusBarIconView)
+data class NotificationChipModel(
+ val key: String,
+ val statusBarChipIconView: StatusBarIconView,
+ val whenTime: Long,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 9eff627c8714..b2f7e2fe7660 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -63,9 +63,13 @@ constructor(
)
}
}
- return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+ return OngoingActivityChipModel.Shown.ShortTimeDelta(
+ icon,
+ colors,
+ time = this.whenTime,
+ onClickListener,
+ )
// TODO(b/364653005): Use Notification.showWhen to determine if we should show the time.
- // TODO(b/364653005): If Notification.whenTime is in the past, show "ago" in the text.
// TODO(b/364653005): If Notification.shortCriticalText is set, use that instead of `when`.
// TODO(b/364653005): If the app that posted the notification is in the foreground, don't
// show that app's chip.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index f4462a434477..730784a46001 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -192,10 +192,14 @@ object OngoingActivityChipBinder {
}
is OngoingActivityChipModel.Shown.ShortTimeDelta -> {
chipShortTimeDeltaView.setTime(chipModel.time)
- // TODO(b/364653005): DateTimeView's relative time doesn't quite match the format we
- // want in the status bar chips.
- chipShortTimeDeltaView.isShowRelativeTime = true
chipShortTimeDeltaView.visibility = View.VISIBLE
+ chipShortTimeDeltaView.isShowRelativeTime = true
+ chipShortTimeDeltaView.setRelativeTimeDisambiguationTextMask(
+ DateTimeView.DISAMBIGUATION_TEXT_PAST
+ )
+ chipShortTimeDeltaView.setRelativeTimeUnitDisplayLength(
+ DateTimeView.UNIT_DISPLAY_LENGTH_MEDIUM
+ )
chipTextView.visibility = View.GONE
chipTimeView.hide()