diff options
| author | 2024-12-02 19:56:23 +0000 | |
|---|---|---|
| committer | 2024-12-02 19:56:23 +0000 | |
| commit | cefc86d2efb831308e55bfb552c30e517335f894 (patch) | |
| tree | ccaf662a737185e576fde62dea5f0dde0701fca0 | |
| parent | ac37f0f68a22b192e8d478e808cb0dba58cb65fd (diff) | |
| parent | 66e289404e87ee2edd4092c7444dadcf4d530c12 (diff) | |
Merge "DateTimeView: Add additional display configuration options." into main
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() |