Loading core/java/android/app/Notification.java +11 −75 Original line number Diff line number Diff line Loading @@ -17,10 +17,10 @@ package android.app; import static android.annotation.Dimension.DP; import static android.app.Flags.FLAG_HIDE_STATUS_BAR_NOTIFICATION; import static android.app.Flags.FLAG_NM_SUMMARIZATION; import static android.app.Flags.FLAG_NM_SUMMARIZATION_ALL; import static android.app.Flags.FLAG_NOTIFICATION_IS_ANIMATED_ACTION_API; import static android.app.Flags.FLAG_HIDE_STATUS_BAR_NOTIFICATION; import static android.app.Flags.apiMetricStyle; import static android.app.Flags.notificationsRedesignTemplates; import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION; Loading @@ -35,7 +35,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.util.Preconditions.checkArgument; import static java.time.temporal.ChronoUnit.SECONDS; import static java.util.Objects.requireNonNull; import android.annotation.ColorInt; Loading Loading @@ -82,9 +81,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.icu.number.NumberFormatter; import android.icu.number.Precision; import android.icu.text.MeasureFormat; import android.icu.util.Measure; import android.icu.util.MeasureUnit; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.PlayerBase; Loading Loading @@ -11853,8 +11849,7 @@ public class Notification implements Parcelable contentView.setTextViewText(metricView.unitId(), valueString.subtext()); } if (metricValue instanceof Metric.TimeDifference timeDifference && timeDifference.getPausedDuration() == null) { if (metricValue instanceof Metric.TimeDifference timeDifference) { contentView.setViewVisibility(metricView.textValueId(), View.GONE); contentView.setViewVisibility(metricView.chronometerId(), View.VISIBLE); contentView.setChronometerCountDown( Loading @@ -11868,9 +11863,13 @@ public class Notification implements Parcelable contentView.setChronometer(metricView.chronometerId(), timeDifference.getZeroElapsedRealtime(), /* format= */ null, /* started= */ true); } else if (timeDifference.getPausedDuration() != null) { contentView.setChronometerPaused(metricView.chronometerId(), timeDifference.getPausedDuration()); } else { throw new IllegalStateException( "No zeroTime for running TimeDifference in " + metric); "No zeroTime or pausedDuration for running TimeDifference in " + metric); } // TODO(b/434910979): implement format support for Chronometer. } else { Loading Loading @@ -12057,6 +12056,8 @@ public class Notification implements Parcelable public ValueString(String text) { this(text, null); } private static final ValueString EMPTY = new ValueString("", null); } /** Loading Loading @@ -12357,73 +12358,8 @@ public class Notification implements Parcelable @NonNull @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public ValueString toValueString(Context context) { Duration duration = getCurrentDuration(); duration = duration.truncatedTo(SECONDS); // ms are ignored and we don't want -0:00 Duration absDuration = duration.abs(); Measure hours = new Measure(absDuration.toHours(), MeasureUnit.HOUR); Measure minutes = new Measure(absDuration.toMinutesPart(), MeasureUnit.MINUTE); Measure seconds = new Measure(absDuration.toSecondsPart(), MeasureUnit.SECOND); String absText = formatAbsoluteDuration(mFormat, hours, minutes, seconds); String text = duration.isNegative() ? context.getString(R.string.negative_duration, absText) : absText; return new ValueString(text, null); } private Duration getCurrentDuration() { if (mPausedDuration != null) { return mPausedDuration; } else if (mZeroTime != null) { // If the timer/stopwatch is running we likely want a Chronometer view, so this // path is mostly for debugging/completeness. Instant now = getSystemClock().instant(); if (isStopwatch()) { return Duration.between(mZeroTime, now); } else { return Duration.between(now, mZeroTime); } } else if (mZeroElapsedRealtime != null) { // If the timer/stopwatch is running we likely want a Chronometer view, so this // path is mostly for debugging/completeness. long elapsedRealtimeNow = getElapsedRealtimeClock().getAsLong(); if (isStopwatch()) { return Duration.ofMillis(elapsedRealtimeNow - mZeroElapsedRealtime); } else { return Duration.ofMillis(mZeroElapsedRealtime - elapsedRealtimeNow); } } else { throw new IllegalStateException( "None of mPausedDuration, mZeroTime, mZeroElapsedRealtime set!"); } } private static String formatAbsoluteDuration(@Format int format, Measure hours, Measure minutes, Measure seconds) { if (format == FORMAT_ADAPTIVE) { MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.NARROW); ArrayList<Measure> partsList = new ArrayList<>(); if (hours.getNumber().intValue() != 0) { partsList.add(hours); } if (minutes.getNumber().intValue() != 0) { partsList.add(minutes); } if (seconds.getNumber().intValue() != 0 || partsList.isEmpty()) { partsList.add(seconds); } return formatter.formatMeasures(partsList.toArray(new Measure[0])); } else { // FORMAT_AUTOMATIC / FORMAT_CHRONOMETER MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.NUMERIC); return hours.getNumber().intValue() != 0 ? formatter.formatMeasures(hours, minutes, seconds) : formatter.formatMeasures(minutes, seconds); } // Not used; Chronometer view will take charge of formatting. return ValueString.EMPTY; } } Loading core/java/android/widget/Chronometer.java +17 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.widget.RemoteViews.RemoteView; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import java.time.Duration; import java.time.Instant; import java.time.InstantSource; import java.util.ArrayList; Loading Loading @@ -224,6 +225,22 @@ public class Chronometer extends TextView { + (instant.toEpochMilli() - mSystemClock.millis()); } /** * Pauses the Chronometer (if it was running) and displays the specified {@link Duration} * (which can be negative). To do this, {@link #getBase()} will be modified according to the * current value of {@link #isCountDown()}. * * @hide */ @android.view.RemotableViewMethod public void setPausedDuration(@NonNull Duration duration) { stop(); long elapsedRealtime = mElapsedRealtimeClock.getAsLong(); mBase = elapsedRealtime + (isCountDown() ? 1 : -1) * duration.toMillis(); mBaseInstant = null; updateText(elapsedRealtime); } /** * Return the base time as set through {@link #setBase}. */ Loading core/java/android/widget/RemoteViews.java +81 −0 Original line number Diff line number Diff line Loading @@ -147,6 +147,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.time.Duration; import java.time.Instant; import java.util.ArrayDeque; import java.util.ArrayList; Loading Loading @@ -2172,6 +2173,8 @@ public class RemoteViews implements Parcelable, Filter { return BlendMode.class; case BaseReflectionAction.INSTANT: return Instant.class; case BaseReflectionAction.DURATION: return Duration.class; default: return null; } Loading Loading @@ -2737,6 +2740,7 @@ public class RemoteViews implements Parcelable, Filter { static final int ICON = 16; static final int BLEND_MODE = 17; static final int INSTANT = 18; static final int DURATION = 19; @UnsupportedAppUsage String mMethodName; Loading Loading @@ -2971,6 +2975,13 @@ public class RemoteViews implements Parcelable, Filter { mValue = null; } break; case DURATION: if (in.readInt() == 1) { mValue = Duration.ofSeconds(in.readLong(), in.readInt()); } else { mValue = null; } break; default: break; } Loading Loading @@ -3033,6 +3044,15 @@ public class RemoteViews implements Parcelable, Filter { out.writeInt(0); } break; case DURATION: if (mValue != null) { out.writeInt(1); out.writeLong(((Duration) this.mValue).getSeconds()); out.writeInt(((Duration) this.mValue).getNano()); } else { out.writeInt(0); } break; default: break; } Loading Loading @@ -3131,6 +3151,11 @@ public class RemoteViews implements Parcelable, Filter { case INSTANT: writeInstantToProto(out, (Instant) this.mValue, RemoteViewsProto.ReflectionAction.INSTANT_VALUE); break; case DURATION: writeDurationToProto(out, (Duration) this.mValue, RemoteViewsProto.ReflectionAction.DURATION_VALUE); break; case BUNDLE: case INTENT: default: Loading Loading @@ -3229,6 +3254,12 @@ public class RemoteViews implements Parcelable, Filter { values.put(RemoteViewsProto.ReflectionAction.INSTANT_VALUE, createInstantFromProto(in, RemoteViewsProto.ReflectionAction.INSTANT_VALUE)); break; case (int) RemoteViewsProto.ReflectionAction.DURATION_VALUE: values.put(RemoteViewsProto.ReflectionAction.DURATION_VALUE, createDurationFromProto(in, RemoteViewsProto.ReflectionAction.DURATION_VALUE)); break; default: Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + ProtoUtils.currentFieldToString(in)); Loading Loading @@ -3309,6 +3340,11 @@ public class RemoteViews implements Parcelable, Filter { case INSTANT: value = (Instant) values.get( RemoteViewsProto.ReflectionAction.INSTANT_VALUE); break; case DURATION: value = (Duration) values.get( RemoteViewsProto.ReflectionAction.DURATION_VALUE); break; case BUNDLE: case INTENT: default: Loading Loading @@ -6954,6 +6990,22 @@ public class RemoteViews implements Parcelable, Filter { setBoolean(viewId, "setStarted", started); } /** * Equivalent to calling {@link Chronometer#setPausedDuration(Duration)} (which will set the * chronometer to paused and the base so that the displayed time is {@code pausedDuration}). * * <p>{@link #setChronometerCountDown(int, boolean)} should be called <em>before</em> this * method, so that the base time can be computed correctly. * * @param viewId The id of the {@link Chronometer} to change * @param pausedDuration the time that the {@link Chronometer} should display * * @hide */ public void setChronometerPaused(@IdRes int viewId, Duration pausedDuration) { setDuration(viewId, "setPausedDuration", pausedDuration); } /** * Equivalent to calling {@link Chronometer#setCountDown(boolean) Chronometer.setCountDown} on * the chronometer with the given viewId. Loading Loading @@ -7957,6 +8009,19 @@ public class RemoteViews implements Parcelable, Filter { addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INSTANT, value)); } /** * Call a method taking one {@link Duration} on a view in the layout for this RemoteViews. * * @param viewId The id of the view on which to call the method. * @param methodName The name of the method to call. * @param value The value to pass to the method. * * @hide */ public void setDuration(@IdRes int viewId, String methodName, Duration value) { addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.DURATION, value)); } /** * Call a method taking one Bundle on a view in the layout for this RemoteViews. * Loading Loading @@ -10757,4 +10822,20 @@ public class RemoteViews implements Parcelable, Filter { in.end(token); return instant; } private static void writeDurationToProto(ProtoOutputStream out, Duration duration, long fieldId) { long token = out.start(fieldId); RemoteViewsSerializers.writeDurationToProto(out, duration); out.end(token); } private static Duration createDurationFromProto(ProtoInputStream in, long fieldId) throws Exception { long token = in.start(fieldId); Duration duration = RemoteViewsSerializers.createDurationFromProto(in); in.end(token); return duration; } } core/java/android/widget/RemoteViewsSerializers.java +30 −0 Original line number Diff line number Diff line Loading @@ -77,6 +77,7 @@ import androidx.annotation.NonNull; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.List; Loading Loading @@ -260,6 +261,35 @@ public class RemoteViewsSerializers { return Instant.ofEpochSecond(seconds, nanos); } /** Write {@link Duration} to proto. */ public static void writeDurationToProto(@NonNull ProtoOutputStream out, @NonNull Duration duration) { out.write(RemoteViewsProto.Duration.SECONDS, duration.getSeconds()); out.write(RemoteViewsProto.Duration.NANOS, duration.getNano()); } /** Create {@link Duration} from proto. */ public static Duration createDurationFromProto(@NonNull ProtoInputStream in) throws IOException { long seconds = 0; int nanos = 0; while (in.nextField() != NO_MORE_FIELDS) { switch (in.getFieldNumber()) { case (int) RemoteViewsProto.Duration.SECONDS: seconds = in.readLong(RemoteViewsProto.Duration.SECONDS); break; case (int) RemoteViewsProto.Duration.NANOS: nanos = in.readInt(RemoteViewsProto.Duration.NANOS); break; default: Log.w(TAG, "Unhandled field while reading Duration proto!\n" + ProtoUtils.currentFieldToString(in)); } } return Duration.ofSeconds(seconds, nanos); } public static void writeCharSequenceToProto(@NonNull ProtoOutputStream out, @NonNull CharSequence cs) { out.write(RemoteViewsProto.CharSequence.TEXT, cs.toString()); Loading core/proto/android/widget/remoteviews.proto +17 −0 Original line number Diff line number Diff line Loading @@ -108,6 +108,22 @@ message RemoteViewsProto { optional int32 nanos = 2; } /** A java.util.time.Duration. */ message Duration { // Signed seconds of the span of time. Must be from -315,576,000,000 // to +315,576,000,000 inclusive. Note: these bounds are computed from: // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years optional int64 seconds = 1; // Signed fractions of a second at nanosecond resolution of the span // of time. Durations less than one second are represented with a 0 // `seconds` field and a positive or negative `nanos` field. For durations // of one second or more, a non-zero value for the `nanos` field must be // of the same sign as the `seconds` field. Must be from -999,999,999 // to +999,999,999 inclusive. optional int32 nanos = 2; } /** * Represents a CharSequence with Spans. */ Loading Loading @@ -401,6 +417,7 @@ message RemoteViewsProto { Icon icon_value = 17; int32 blend_mode_value = 18; Instant instant_value = 19; Duration duration_value = 20; // Intent and Bundle values are excluded. } } Loading Loading
core/java/android/app/Notification.java +11 −75 Original line number Diff line number Diff line Loading @@ -17,10 +17,10 @@ package android.app; import static android.annotation.Dimension.DP; import static android.app.Flags.FLAG_HIDE_STATUS_BAR_NOTIFICATION; import static android.app.Flags.FLAG_NM_SUMMARIZATION; import static android.app.Flags.FLAG_NM_SUMMARIZATION_ALL; import static android.app.Flags.FLAG_NOTIFICATION_IS_ANIMATED_ACTION_API; import static android.app.Flags.FLAG_HIDE_STATUS_BAR_NOTIFICATION; import static android.app.Flags.apiMetricStyle; import static android.app.Flags.notificationsRedesignTemplates; import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION; Loading @@ -35,7 +35,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.util.Preconditions.checkArgument; import static java.time.temporal.ChronoUnit.SECONDS; import static java.util.Objects.requireNonNull; import android.annotation.ColorInt; Loading Loading @@ -82,9 +81,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.icu.number.NumberFormatter; import android.icu.number.Precision; import android.icu.text.MeasureFormat; import android.icu.util.Measure; import android.icu.util.MeasureUnit; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.PlayerBase; Loading Loading @@ -11853,8 +11849,7 @@ public class Notification implements Parcelable contentView.setTextViewText(metricView.unitId(), valueString.subtext()); } if (metricValue instanceof Metric.TimeDifference timeDifference && timeDifference.getPausedDuration() == null) { if (metricValue instanceof Metric.TimeDifference timeDifference) { contentView.setViewVisibility(metricView.textValueId(), View.GONE); contentView.setViewVisibility(metricView.chronometerId(), View.VISIBLE); contentView.setChronometerCountDown( Loading @@ -11868,9 +11863,13 @@ public class Notification implements Parcelable contentView.setChronometer(metricView.chronometerId(), timeDifference.getZeroElapsedRealtime(), /* format= */ null, /* started= */ true); } else if (timeDifference.getPausedDuration() != null) { contentView.setChronometerPaused(metricView.chronometerId(), timeDifference.getPausedDuration()); } else { throw new IllegalStateException( "No zeroTime for running TimeDifference in " + metric); "No zeroTime or pausedDuration for running TimeDifference in " + metric); } // TODO(b/434910979): implement format support for Chronometer. } else { Loading Loading @@ -12057,6 +12056,8 @@ public class Notification implements Parcelable public ValueString(String text) { this(text, null); } private static final ValueString EMPTY = new ValueString("", null); } /** Loading Loading @@ -12357,73 +12358,8 @@ public class Notification implements Parcelable @NonNull @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public ValueString toValueString(Context context) { Duration duration = getCurrentDuration(); duration = duration.truncatedTo(SECONDS); // ms are ignored and we don't want -0:00 Duration absDuration = duration.abs(); Measure hours = new Measure(absDuration.toHours(), MeasureUnit.HOUR); Measure minutes = new Measure(absDuration.toMinutesPart(), MeasureUnit.MINUTE); Measure seconds = new Measure(absDuration.toSecondsPart(), MeasureUnit.SECOND); String absText = formatAbsoluteDuration(mFormat, hours, minutes, seconds); String text = duration.isNegative() ? context.getString(R.string.negative_duration, absText) : absText; return new ValueString(text, null); } private Duration getCurrentDuration() { if (mPausedDuration != null) { return mPausedDuration; } else if (mZeroTime != null) { // If the timer/stopwatch is running we likely want a Chronometer view, so this // path is mostly for debugging/completeness. Instant now = getSystemClock().instant(); if (isStopwatch()) { return Duration.between(mZeroTime, now); } else { return Duration.between(now, mZeroTime); } } else if (mZeroElapsedRealtime != null) { // If the timer/stopwatch is running we likely want a Chronometer view, so this // path is mostly for debugging/completeness. long elapsedRealtimeNow = getElapsedRealtimeClock().getAsLong(); if (isStopwatch()) { return Duration.ofMillis(elapsedRealtimeNow - mZeroElapsedRealtime); } else { return Duration.ofMillis(mZeroElapsedRealtime - elapsedRealtimeNow); } } else { throw new IllegalStateException( "None of mPausedDuration, mZeroTime, mZeroElapsedRealtime set!"); } } private static String formatAbsoluteDuration(@Format int format, Measure hours, Measure minutes, Measure seconds) { if (format == FORMAT_ADAPTIVE) { MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.NARROW); ArrayList<Measure> partsList = new ArrayList<>(); if (hours.getNumber().intValue() != 0) { partsList.add(hours); } if (minutes.getNumber().intValue() != 0) { partsList.add(minutes); } if (seconds.getNumber().intValue() != 0 || partsList.isEmpty()) { partsList.add(seconds); } return formatter.formatMeasures(partsList.toArray(new Measure[0])); } else { // FORMAT_AUTOMATIC / FORMAT_CHRONOMETER MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.NUMERIC); return hours.getNumber().intValue() != 0 ? formatter.formatMeasures(hours, minutes, seconds) : formatter.formatMeasures(minutes, seconds); } // Not used; Chronometer view will take charge of formatting. return ValueString.EMPTY; } } Loading
core/java/android/widget/Chronometer.java +17 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.widget.RemoteViews.RemoteView; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import java.time.Duration; import java.time.Instant; import java.time.InstantSource; import java.util.ArrayList; Loading Loading @@ -224,6 +225,22 @@ public class Chronometer extends TextView { + (instant.toEpochMilli() - mSystemClock.millis()); } /** * Pauses the Chronometer (if it was running) and displays the specified {@link Duration} * (which can be negative). To do this, {@link #getBase()} will be modified according to the * current value of {@link #isCountDown()}. * * @hide */ @android.view.RemotableViewMethod public void setPausedDuration(@NonNull Duration duration) { stop(); long elapsedRealtime = mElapsedRealtimeClock.getAsLong(); mBase = elapsedRealtime + (isCountDown() ? 1 : -1) * duration.toMillis(); mBaseInstant = null; updateText(elapsedRealtime); } /** * Return the base time as set through {@link #setBase}. */ Loading
core/java/android/widget/RemoteViews.java +81 −0 Original line number Diff line number Diff line Loading @@ -147,6 +147,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.time.Duration; import java.time.Instant; import java.util.ArrayDeque; import java.util.ArrayList; Loading Loading @@ -2172,6 +2173,8 @@ public class RemoteViews implements Parcelable, Filter { return BlendMode.class; case BaseReflectionAction.INSTANT: return Instant.class; case BaseReflectionAction.DURATION: return Duration.class; default: return null; } Loading Loading @@ -2737,6 +2740,7 @@ public class RemoteViews implements Parcelable, Filter { static final int ICON = 16; static final int BLEND_MODE = 17; static final int INSTANT = 18; static final int DURATION = 19; @UnsupportedAppUsage String mMethodName; Loading Loading @@ -2971,6 +2975,13 @@ public class RemoteViews implements Parcelable, Filter { mValue = null; } break; case DURATION: if (in.readInt() == 1) { mValue = Duration.ofSeconds(in.readLong(), in.readInt()); } else { mValue = null; } break; default: break; } Loading Loading @@ -3033,6 +3044,15 @@ public class RemoteViews implements Parcelable, Filter { out.writeInt(0); } break; case DURATION: if (mValue != null) { out.writeInt(1); out.writeLong(((Duration) this.mValue).getSeconds()); out.writeInt(((Duration) this.mValue).getNano()); } else { out.writeInt(0); } break; default: break; } Loading Loading @@ -3131,6 +3151,11 @@ public class RemoteViews implements Parcelable, Filter { case INSTANT: writeInstantToProto(out, (Instant) this.mValue, RemoteViewsProto.ReflectionAction.INSTANT_VALUE); break; case DURATION: writeDurationToProto(out, (Duration) this.mValue, RemoteViewsProto.ReflectionAction.DURATION_VALUE); break; case BUNDLE: case INTENT: default: Loading Loading @@ -3229,6 +3254,12 @@ public class RemoteViews implements Parcelable, Filter { values.put(RemoteViewsProto.ReflectionAction.INSTANT_VALUE, createInstantFromProto(in, RemoteViewsProto.ReflectionAction.INSTANT_VALUE)); break; case (int) RemoteViewsProto.ReflectionAction.DURATION_VALUE: values.put(RemoteViewsProto.ReflectionAction.DURATION_VALUE, createDurationFromProto(in, RemoteViewsProto.ReflectionAction.DURATION_VALUE)); break; default: Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + ProtoUtils.currentFieldToString(in)); Loading Loading @@ -3309,6 +3340,11 @@ public class RemoteViews implements Parcelable, Filter { case INSTANT: value = (Instant) values.get( RemoteViewsProto.ReflectionAction.INSTANT_VALUE); break; case DURATION: value = (Duration) values.get( RemoteViewsProto.ReflectionAction.DURATION_VALUE); break; case BUNDLE: case INTENT: default: Loading Loading @@ -6954,6 +6990,22 @@ public class RemoteViews implements Parcelable, Filter { setBoolean(viewId, "setStarted", started); } /** * Equivalent to calling {@link Chronometer#setPausedDuration(Duration)} (which will set the * chronometer to paused and the base so that the displayed time is {@code pausedDuration}). * * <p>{@link #setChronometerCountDown(int, boolean)} should be called <em>before</em> this * method, so that the base time can be computed correctly. * * @param viewId The id of the {@link Chronometer} to change * @param pausedDuration the time that the {@link Chronometer} should display * * @hide */ public void setChronometerPaused(@IdRes int viewId, Duration pausedDuration) { setDuration(viewId, "setPausedDuration", pausedDuration); } /** * Equivalent to calling {@link Chronometer#setCountDown(boolean) Chronometer.setCountDown} on * the chronometer with the given viewId. Loading Loading @@ -7957,6 +8009,19 @@ public class RemoteViews implements Parcelable, Filter { addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INSTANT, value)); } /** * Call a method taking one {@link Duration} on a view in the layout for this RemoteViews. * * @param viewId The id of the view on which to call the method. * @param methodName The name of the method to call. * @param value The value to pass to the method. * * @hide */ public void setDuration(@IdRes int viewId, String methodName, Duration value) { addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.DURATION, value)); } /** * Call a method taking one Bundle on a view in the layout for this RemoteViews. * Loading Loading @@ -10757,4 +10822,20 @@ public class RemoteViews implements Parcelable, Filter { in.end(token); return instant; } private static void writeDurationToProto(ProtoOutputStream out, Duration duration, long fieldId) { long token = out.start(fieldId); RemoteViewsSerializers.writeDurationToProto(out, duration); out.end(token); } private static Duration createDurationFromProto(ProtoInputStream in, long fieldId) throws Exception { long token = in.start(fieldId); Duration duration = RemoteViewsSerializers.createDurationFromProto(in); in.end(token); return duration; } }
core/java/android/widget/RemoteViewsSerializers.java +30 −0 Original line number Diff line number Diff line Loading @@ -77,6 +77,7 @@ import androidx.annotation.NonNull; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.List; Loading Loading @@ -260,6 +261,35 @@ public class RemoteViewsSerializers { return Instant.ofEpochSecond(seconds, nanos); } /** Write {@link Duration} to proto. */ public static void writeDurationToProto(@NonNull ProtoOutputStream out, @NonNull Duration duration) { out.write(RemoteViewsProto.Duration.SECONDS, duration.getSeconds()); out.write(RemoteViewsProto.Duration.NANOS, duration.getNano()); } /** Create {@link Duration} from proto. */ public static Duration createDurationFromProto(@NonNull ProtoInputStream in) throws IOException { long seconds = 0; int nanos = 0; while (in.nextField() != NO_MORE_FIELDS) { switch (in.getFieldNumber()) { case (int) RemoteViewsProto.Duration.SECONDS: seconds = in.readLong(RemoteViewsProto.Duration.SECONDS); break; case (int) RemoteViewsProto.Duration.NANOS: nanos = in.readInt(RemoteViewsProto.Duration.NANOS); break; default: Log.w(TAG, "Unhandled field while reading Duration proto!\n" + ProtoUtils.currentFieldToString(in)); } } return Duration.ofSeconds(seconds, nanos); } public static void writeCharSequenceToProto(@NonNull ProtoOutputStream out, @NonNull CharSequence cs) { out.write(RemoteViewsProto.CharSequence.TEXT, cs.toString()); Loading
core/proto/android/widget/remoteviews.proto +17 −0 Original line number Diff line number Diff line Loading @@ -108,6 +108,22 @@ message RemoteViewsProto { optional int32 nanos = 2; } /** A java.util.time.Duration. */ message Duration { // Signed seconds of the span of time. Must be from -315,576,000,000 // to +315,576,000,000 inclusive. Note: these bounds are computed from: // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years optional int64 seconds = 1; // Signed fractions of a second at nanosecond resolution of the span // of time. Durations less than one second are represented with a 0 // `seconds` field and a positive or negative `nanos` field. For durations // of one second or more, a non-zero value for the `nanos` field must be // of the same sign as the `seconds` field. Must be from -999,999,999 // to +999,999,999 inclusive. optional int32 nanos = 2; } /** * Represents a CharSequence with Spans. */ Loading Loading @@ -401,6 +417,7 @@ message RemoteViewsProto { Icon icon_value = 17; int32 blend_mode_value = 18; Instant instant_value = 19; Duration duration_value = 20; // Intent and Bundle values are excluded. } } Loading