Loading core/java/android/service/timezone/ITimeZoneProvider.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.service.timezone.ITimeZoneProviderManager; * @hide */ oneway interface ITimeZoneProvider { void startUpdates(in ITimeZoneProviderManager manager, in long initializationTimeoutMillis); void startUpdates(in ITimeZoneProviderManager manager, in long initializationTimeoutMillis, in long eventFilteringAgeThresholdMillis); void stopUpdates(); } core/java/android/service/timezone/TimeZoneProviderEvent.java +30 −12 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.service.timezone; import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -26,6 +27,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.time.Duration; import java.util.Objects; /** Loading Loading @@ -59,11 +61,11 @@ public final class TimeZoneProviderEvent implements Parcelable { */ public static final @EventType int EVENT_TYPE_UNCERTAIN = 3; private static final TimeZoneProviderEvent UNCERTAIN_EVENT = new TimeZoneProviderEvent(EVENT_TYPE_UNCERTAIN, null, null); private final @EventType int mType; @ElapsedRealtimeLong private final long mCreationElapsedMillis; @Nullable private final TimeZoneProviderSuggestion mSuggestion; Loading @@ -71,28 +73,34 @@ public final class TimeZoneProviderEvent implements Parcelable { private final String mFailureCause; private TimeZoneProviderEvent(@EventType int type, @ElapsedRealtimeLong long creationElapsedMillis, @Nullable TimeZoneProviderSuggestion suggestion, @Nullable String failureCause) { mType = type; mCreationElapsedMillis = creationElapsedMillis; mSuggestion = suggestion; mFailureCause = failureCause; } /** Returns a event of type {@link #EVENT_TYPE_SUGGESTION}. */ public static TimeZoneProviderEvent createSuggestionEvent( @ElapsedRealtimeLong long creationElapsedMillis, @NonNull TimeZoneProviderSuggestion suggestion) { return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION, return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION, creationElapsedMillis, Objects.requireNonNull(suggestion), null); } /** Returns a event of type {@link #EVENT_TYPE_UNCERTAIN}. */ public static TimeZoneProviderEvent createUncertainEvent() { return UNCERTAIN_EVENT; public static TimeZoneProviderEvent createUncertainEvent( @ElapsedRealtimeLong long creationElapsedMillis) { return new TimeZoneProviderEvent(EVENT_TYPE_UNCERTAIN, creationElapsedMillis, null, null); } /** Returns a event of type {@link #EVENT_TYPE_PERMANENT_FAILURE}. */ public static TimeZoneProviderEvent createPermanentFailureEvent(@NonNull String cause) { return new TimeZoneProviderEvent(EVENT_TYPE_PERMANENT_FAILURE, null, public static TimeZoneProviderEvent createPermanentFailureEvent( @ElapsedRealtimeLong long creationElapsedMillis, @NonNull String cause) { return new TimeZoneProviderEvent(EVENT_TYPE_PERMANENT_FAILURE, creationElapsedMillis, null, Objects.requireNonNull(cause)); } Loading @@ -103,6 +111,12 @@ public final class TimeZoneProviderEvent implements Parcelable { return mType; } /** Returns the time according to the elapsed realtime clock when the event was created. */ @ElapsedRealtimeLong public long getCreationElapsedMillis() { return mCreationElapsedMillis; } /** * Returns the suggestion. Populated when {@link #getType()} is {@link #EVENT_TYPE_SUGGESTION}. */ Loading @@ -125,10 +139,12 @@ public final class TimeZoneProviderEvent implements Parcelable { @Override public TimeZoneProviderEvent createFromParcel(Parcel in) { int type = in.readInt(); long creationElapsedMillis = in.readLong(); TimeZoneProviderSuggestion suggestion = in.readParcelable(getClass().getClassLoader()); String failureCause = in.readString8(); return new TimeZoneProviderEvent(type, suggestion, failureCause); return new TimeZoneProviderEvent( type, creationElapsedMillis, suggestion, failureCause); } @Override Loading @@ -145,6 +161,7 @@ public final class TimeZoneProviderEvent implements Parcelable { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { parcel.writeInt(mType); parcel.writeLong(mCreationElapsedMillis); parcel.writeParcelable(mSuggestion, 0); parcel.writeString8(mFailureCause); } Loading @@ -153,6 +170,7 @@ public final class TimeZoneProviderEvent implements Parcelable { public String toString() { return "TimeZoneProviderEvent{" + "mType=" + mType + ", mCreationElapsedMillis=" + Duration.ofMillis(mCreationElapsedMillis).toString() + ", mSuggestion=" + mSuggestion + ", mFailureCause=" + mFailureCause + '}'; Loading @@ -173,8 +191,7 @@ public final class TimeZoneProviderEvent implements Parcelable { return false; } if (mType == EVENT_TYPE_SUGGESTION) { // Only check the time zone IDs. The times will be different, but we don't mind. return mSuggestion.getTimeZoneIds().equals(other.getSuggestion().getTimeZoneIds()); return mSuggestion.isEquivalentTo(other.getSuggestion()); } return true; } Loading @@ -189,12 +206,13 @@ public final class TimeZoneProviderEvent implements Parcelable { } TimeZoneProviderEvent that = (TimeZoneProviderEvent) o; return mType == that.mType && mCreationElapsedMillis == that.mCreationElapsedMillis && Objects.equals(mSuggestion, that.mSuggestion) && Objects.equals(mFailureCause, that.mFailureCause); } @Override public int hashCode() { return Objects.hash(mType, mSuggestion, mFailureCause); return Objects.hash(mType, mCreationElapsedMillis, mSuggestion, mFailureCause); } } core/java/android/service/timezone/TimeZoneProviderService.java +42 −9 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import com.android.internal.annotations.GuardedBy; Loading Loading @@ -178,6 +179,10 @@ public abstract class TimeZoneProviderService extends Service { @Nullable private ITimeZoneProviderManager mManager; /** Set by {@link #mHandler} thread. */ @GuardedBy("mLock") private long mEventFilteringAgeThresholdMillis; /** * The type of the last suggestion sent to the system server. Used to de-dupe suggestions client * side and avoid calling into the system server unnecessarily. {@code null} means no previous Loading Loading @@ -206,8 +211,9 @@ public abstract class TimeZoneProviderService extends Service { if (manager != null) { try { TimeZoneProviderEvent thisEvent = TimeZoneProviderEvent.createSuggestionEvent(suggestion); if (!thisEvent.isEquivalentTo(mLastEventSent)) { TimeZoneProviderEvent.createSuggestionEvent( SystemClock.elapsedRealtime(), suggestion); if (shouldSendEvent(thisEvent)) { manager.onTimeZoneProviderEvent(thisEvent); mLastEventSent = thisEvent; } Loading @@ -231,8 +237,9 @@ public abstract class TimeZoneProviderService extends Service { if (manager != null) { try { TimeZoneProviderEvent thisEvent = TimeZoneProviderEvent.createUncertainEvent(); if (!thisEvent.isEquivalentTo(mLastEventSent)) { TimeZoneProviderEvent.createUncertainEvent( SystemClock.elapsedRealtime()); if (shouldSendEvent(thisEvent)) { manager.onTimeZoneProviderEvent(thisEvent); mLastEventSent = thisEvent; } Loading @@ -258,8 +265,9 @@ public abstract class TimeZoneProviderService extends Service { try { String causeString = cause.getMessage(); TimeZoneProviderEvent thisEvent = TimeZoneProviderEvent.createPermanentFailureEvent(causeString); if (!thisEvent.isEquivalentTo(mLastEventSent)) { TimeZoneProviderEvent.createPermanentFailureEvent( SystemClock.elapsedRealtime(), causeString); if (shouldSendEvent(thisEvent)) { manager.onTimeZoneProviderEvent(thisEvent); mLastEventSent = thisEvent; } Loading @@ -271,10 +279,33 @@ public abstract class TimeZoneProviderService extends Service { }); } @GuardedBy("mLock") private boolean shouldSendEvent(TimeZoneProviderEvent newEvent) { // Always send an event if it indicates a state or suggestion change. if (!newEvent.isEquivalentTo(mLastEventSent)) { return true; } // Guard against implementations that generate a lot of uninteresting events in a short // space of time and would cause the time_zone_detector to evaluate time zone suggestions // too frequently. // // If the new event and last event sent are equivalent, the client will still send an update // if their creation times are sufficiently different. This enables the time_zone_detector // to better understand how recently the location time zone provider was certain / // uncertain, which can be useful when working out ordering of events, e.g. to work out // whether a suggestion was generated before or after a device left airplane mode. long timeSinceLastEventMillis = newEvent.getCreationElapsedMillis() - mLastEventSent.getCreationElapsedMillis(); return timeSinceLastEventMillis > mEventFilteringAgeThresholdMillis; } private void onStartUpdatesInternal(@NonNull ITimeZoneProviderManager manager, @DurationMillisLong long initializationTimeoutMillis) { @DurationMillisLong long initializationTimeoutMillis, @DurationMillisLong long eventFilteringAgeThresholdMillis) { synchronized (mLock) { mManager = manager; mEventFilteringAgeThresholdMillis = eventFilteringAgeThresholdMillis; mLastEventSent = null; onStartUpdates(initializationTimeoutMillis); } Loading Loading @@ -332,9 +363,11 @@ public abstract class TimeZoneProviderService extends Service { private class TimeZoneProviderServiceWrapper extends ITimeZoneProvider.Stub { public void startUpdates(@NonNull ITimeZoneProviderManager manager, @DurationMillisLong long initializationTimeoutMillis) { @DurationMillisLong long initializationTimeoutMillis, @DurationMillisLong long eventFilteringAgeThresholdMillis) { Objects.requireNonNull(manager); mHandler.post(() -> onStartUpdatesInternal(manager, initializationTimeoutMillis)); mHandler.post(() -> onStartUpdatesInternal( manager, initializationTimeoutMillis, eventFilteringAgeThresholdMillis)); } public void stopUpdates() { Loading core/java/android/service/timezone/TimeZoneProviderSuggestion.java +19 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.service.timezone; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; Loading Loading @@ -121,6 +122,24 @@ public final class TimeZoneProviderSuggestion implements Parcelable { parcel.writeLong(mElapsedRealtimeMillis); } /** * Similar to {@link #equals} except this methods checks for equivalence, not equality. * i.e. two suggestions are equivalent if they suggest the same time zones. * * @hide */ @SuppressWarnings("ReferenceEquality") public boolean isEquivalentTo(@Nullable TimeZoneProviderSuggestion other) { if (this == other) { return true; } if (other == null) { return false; } // Only check the time zone IDs. The times can be different, but we don't mind. return mTimeZoneIds.equals(other.mTimeZoneIds); } @Override public boolean equals(Object o) { if (this == o) { Loading core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java +30 −12 Original line number Diff line number Diff line Loading @@ -33,7 +33,8 @@ public class TimeZoneProviderEventTest { @Test public void isEquivalentToAndEquals() { TimeZoneProviderEvent fail1v1 = TimeZoneProviderEvent.createPermanentFailureEvent("one"); TimeZoneProviderEvent fail1v1 = TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "one"); assertEquals(fail1v1, fail1v1); assertIsEquivalentTo(fail1v1, fail1v1); assertNotEquals(fail1v1, null); Loading @@ -41,58 +42,74 @@ public class TimeZoneProviderEventTest { { TimeZoneProviderEvent fail1v2 = TimeZoneProviderEvent.createPermanentFailureEvent("one"); TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "one"); assertEquals(fail1v1, fail1v2); assertIsEquivalentTo(fail1v1, fail1v2); TimeZoneProviderEvent fail2 = TimeZoneProviderEvent.createPermanentFailureEvent("two"); TimeZoneProviderEvent fail2 = TimeZoneProviderEvent.createPermanentFailureEvent(2222L, "two"); assertNotEquals(fail1v1, fail2); assertIsEquivalentTo(fail1v1, fail2); } TimeZoneProviderEvent uncertain1v1 = TimeZoneProviderEvent.createUncertainEvent(); TimeZoneProviderEvent uncertain1v1 = TimeZoneProviderEvent.createUncertainEvent(1111L); assertEquals(uncertain1v1, uncertain1v1); assertIsEquivalentTo(uncertain1v1, uncertain1v1); assertNotEquals(uncertain1v1, null); assertNotEquivalentTo(uncertain1v1, null); { TimeZoneProviderEvent uncertain1v2 = TimeZoneProviderEvent.createUncertainEvent(); TimeZoneProviderEvent uncertain1v2 = TimeZoneProviderEvent.createUncertainEvent(1111L); assertEquals(uncertain1v1, uncertain1v2); assertIsEquivalentTo(uncertain1v1, uncertain1v2); TimeZoneProviderEvent uncertain2 = TimeZoneProviderEvent.createUncertainEvent(2222L); assertNotEquals(uncertain1v1, uncertain2); assertIsEquivalentTo(uncertain1v1, uncertain2); } TimeZoneProviderSuggestion suggestion1 = new TimeZoneProviderSuggestion.Builder() .setElapsedRealtimeMillis(1111L) .setTimeZoneIds(Collections.singletonList("Europe/London")) .build(); TimeZoneProviderEvent certain1v1 = TimeZoneProviderEvent.createSuggestionEvent(suggestion1); TimeZoneProviderEvent certain1v1 = TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion1); assertEquals(certain1v1, certain1v1); assertIsEquivalentTo(certain1v1, certain1v1); assertNotEquals(certain1v1, null); assertNotEquivalentTo(certain1v1, null); { // Same suggestion, same time. TimeZoneProviderEvent certain1v2 = TimeZoneProviderEvent.createSuggestionEvent(suggestion1); TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion1); assertEquals(certain1v1, certain1v2); assertIsEquivalentTo(certain1v1, certain1v2); // Same suggestion, different time. TimeZoneProviderEvent certain1v3 = TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion1); assertNotEquals(certain1v1, certain1v3); assertIsEquivalentTo(certain1v1, certain1v3); // suggestion1 is equivalent to suggestion2, but not equal TimeZoneProviderSuggestion suggestion2 = new TimeZoneProviderSuggestion.Builder() .setElapsedRealtimeMillis(2222L) .setTimeZoneIds(Collections.singletonList("Europe/London")) .build(); assertNotEquals(suggestion1, suggestion2); TimeZoneProviderSuggestionTest.assertIsEquivalentTo(suggestion1, suggestion2); TimeZoneProviderEvent certain2 = TimeZoneProviderEvent.createSuggestionEvent(suggestion2); TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion2); assertNotEquals(certain1v1, certain2); assertIsEquivalentTo(certain1v1, certain2); // suggestion3 is not equivalent to suggestion1 TimeZoneProviderSuggestion suggestion3 = new TimeZoneProviderSuggestion.Builder() .setTimeZoneIds(Collections.singletonList("Europe/Paris")) .build(); TimeZoneProviderEvent certain3 = TimeZoneProviderEvent.createSuggestionEvent(suggestion3); TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion3); assertNotEquals(certain1v1, certain3); assertNotEquivalentTo(certain1v1, certain3); } Loading @@ -107,13 +124,13 @@ public class TimeZoneProviderEventTest { @Test public void testParcelable_failureEvent() { TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent("failure reason"); TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "failure reason"); assertRoundTripParcelable(event); } @Test public void testParcelable_uncertain() { TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(); TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(1111L); assertRoundTripParcelable(event); } Loading @@ -122,7 +139,8 @@ public class TimeZoneProviderEventTest { TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder() .setTimeZoneIds(Arrays.asList("Europe/London", "Europe/Paris")) .build(); TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion); TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion); assertRoundTripParcelable(event); } Loading Loading
core/java/android/service/timezone/ITimeZoneProvider.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.service.timezone.ITimeZoneProviderManager; * @hide */ oneway interface ITimeZoneProvider { void startUpdates(in ITimeZoneProviderManager manager, in long initializationTimeoutMillis); void startUpdates(in ITimeZoneProviderManager manager, in long initializationTimeoutMillis, in long eventFilteringAgeThresholdMillis); void stopUpdates(); }
core/java/android/service/timezone/TimeZoneProviderEvent.java +30 −12 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.service.timezone; import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -26,6 +27,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.time.Duration; import java.util.Objects; /** Loading Loading @@ -59,11 +61,11 @@ public final class TimeZoneProviderEvent implements Parcelable { */ public static final @EventType int EVENT_TYPE_UNCERTAIN = 3; private static final TimeZoneProviderEvent UNCERTAIN_EVENT = new TimeZoneProviderEvent(EVENT_TYPE_UNCERTAIN, null, null); private final @EventType int mType; @ElapsedRealtimeLong private final long mCreationElapsedMillis; @Nullable private final TimeZoneProviderSuggestion mSuggestion; Loading @@ -71,28 +73,34 @@ public final class TimeZoneProviderEvent implements Parcelable { private final String mFailureCause; private TimeZoneProviderEvent(@EventType int type, @ElapsedRealtimeLong long creationElapsedMillis, @Nullable TimeZoneProviderSuggestion suggestion, @Nullable String failureCause) { mType = type; mCreationElapsedMillis = creationElapsedMillis; mSuggestion = suggestion; mFailureCause = failureCause; } /** Returns a event of type {@link #EVENT_TYPE_SUGGESTION}. */ public static TimeZoneProviderEvent createSuggestionEvent( @ElapsedRealtimeLong long creationElapsedMillis, @NonNull TimeZoneProviderSuggestion suggestion) { return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION, return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION, creationElapsedMillis, Objects.requireNonNull(suggestion), null); } /** Returns a event of type {@link #EVENT_TYPE_UNCERTAIN}. */ public static TimeZoneProviderEvent createUncertainEvent() { return UNCERTAIN_EVENT; public static TimeZoneProviderEvent createUncertainEvent( @ElapsedRealtimeLong long creationElapsedMillis) { return new TimeZoneProviderEvent(EVENT_TYPE_UNCERTAIN, creationElapsedMillis, null, null); } /** Returns a event of type {@link #EVENT_TYPE_PERMANENT_FAILURE}. */ public static TimeZoneProviderEvent createPermanentFailureEvent(@NonNull String cause) { return new TimeZoneProviderEvent(EVENT_TYPE_PERMANENT_FAILURE, null, public static TimeZoneProviderEvent createPermanentFailureEvent( @ElapsedRealtimeLong long creationElapsedMillis, @NonNull String cause) { return new TimeZoneProviderEvent(EVENT_TYPE_PERMANENT_FAILURE, creationElapsedMillis, null, Objects.requireNonNull(cause)); } Loading @@ -103,6 +111,12 @@ public final class TimeZoneProviderEvent implements Parcelable { return mType; } /** Returns the time according to the elapsed realtime clock when the event was created. */ @ElapsedRealtimeLong public long getCreationElapsedMillis() { return mCreationElapsedMillis; } /** * Returns the suggestion. Populated when {@link #getType()} is {@link #EVENT_TYPE_SUGGESTION}. */ Loading @@ -125,10 +139,12 @@ public final class TimeZoneProviderEvent implements Parcelable { @Override public TimeZoneProviderEvent createFromParcel(Parcel in) { int type = in.readInt(); long creationElapsedMillis = in.readLong(); TimeZoneProviderSuggestion suggestion = in.readParcelable(getClass().getClassLoader()); String failureCause = in.readString8(); return new TimeZoneProviderEvent(type, suggestion, failureCause); return new TimeZoneProviderEvent( type, creationElapsedMillis, suggestion, failureCause); } @Override Loading @@ -145,6 +161,7 @@ public final class TimeZoneProviderEvent implements Parcelable { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { parcel.writeInt(mType); parcel.writeLong(mCreationElapsedMillis); parcel.writeParcelable(mSuggestion, 0); parcel.writeString8(mFailureCause); } Loading @@ -153,6 +170,7 @@ public final class TimeZoneProviderEvent implements Parcelable { public String toString() { return "TimeZoneProviderEvent{" + "mType=" + mType + ", mCreationElapsedMillis=" + Duration.ofMillis(mCreationElapsedMillis).toString() + ", mSuggestion=" + mSuggestion + ", mFailureCause=" + mFailureCause + '}'; Loading @@ -173,8 +191,7 @@ public final class TimeZoneProviderEvent implements Parcelable { return false; } if (mType == EVENT_TYPE_SUGGESTION) { // Only check the time zone IDs. The times will be different, but we don't mind. return mSuggestion.getTimeZoneIds().equals(other.getSuggestion().getTimeZoneIds()); return mSuggestion.isEquivalentTo(other.getSuggestion()); } return true; } Loading @@ -189,12 +206,13 @@ public final class TimeZoneProviderEvent implements Parcelable { } TimeZoneProviderEvent that = (TimeZoneProviderEvent) o; return mType == that.mType && mCreationElapsedMillis == that.mCreationElapsedMillis && Objects.equals(mSuggestion, that.mSuggestion) && Objects.equals(mFailureCause, that.mFailureCause); } @Override public int hashCode() { return Objects.hash(mType, mSuggestion, mFailureCause); return Objects.hash(mType, mCreationElapsedMillis, mSuggestion, mFailureCause); } }
core/java/android/service/timezone/TimeZoneProviderService.java +42 −9 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import com.android.internal.annotations.GuardedBy; Loading Loading @@ -178,6 +179,10 @@ public abstract class TimeZoneProviderService extends Service { @Nullable private ITimeZoneProviderManager mManager; /** Set by {@link #mHandler} thread. */ @GuardedBy("mLock") private long mEventFilteringAgeThresholdMillis; /** * The type of the last suggestion sent to the system server. Used to de-dupe suggestions client * side and avoid calling into the system server unnecessarily. {@code null} means no previous Loading Loading @@ -206,8 +211,9 @@ public abstract class TimeZoneProviderService extends Service { if (manager != null) { try { TimeZoneProviderEvent thisEvent = TimeZoneProviderEvent.createSuggestionEvent(suggestion); if (!thisEvent.isEquivalentTo(mLastEventSent)) { TimeZoneProviderEvent.createSuggestionEvent( SystemClock.elapsedRealtime(), suggestion); if (shouldSendEvent(thisEvent)) { manager.onTimeZoneProviderEvent(thisEvent); mLastEventSent = thisEvent; } Loading @@ -231,8 +237,9 @@ public abstract class TimeZoneProviderService extends Service { if (manager != null) { try { TimeZoneProviderEvent thisEvent = TimeZoneProviderEvent.createUncertainEvent(); if (!thisEvent.isEquivalentTo(mLastEventSent)) { TimeZoneProviderEvent.createUncertainEvent( SystemClock.elapsedRealtime()); if (shouldSendEvent(thisEvent)) { manager.onTimeZoneProviderEvent(thisEvent); mLastEventSent = thisEvent; } Loading @@ -258,8 +265,9 @@ public abstract class TimeZoneProviderService extends Service { try { String causeString = cause.getMessage(); TimeZoneProviderEvent thisEvent = TimeZoneProviderEvent.createPermanentFailureEvent(causeString); if (!thisEvent.isEquivalentTo(mLastEventSent)) { TimeZoneProviderEvent.createPermanentFailureEvent( SystemClock.elapsedRealtime(), causeString); if (shouldSendEvent(thisEvent)) { manager.onTimeZoneProviderEvent(thisEvent); mLastEventSent = thisEvent; } Loading @@ -271,10 +279,33 @@ public abstract class TimeZoneProviderService extends Service { }); } @GuardedBy("mLock") private boolean shouldSendEvent(TimeZoneProviderEvent newEvent) { // Always send an event if it indicates a state or suggestion change. if (!newEvent.isEquivalentTo(mLastEventSent)) { return true; } // Guard against implementations that generate a lot of uninteresting events in a short // space of time and would cause the time_zone_detector to evaluate time zone suggestions // too frequently. // // If the new event and last event sent are equivalent, the client will still send an update // if their creation times are sufficiently different. This enables the time_zone_detector // to better understand how recently the location time zone provider was certain / // uncertain, which can be useful when working out ordering of events, e.g. to work out // whether a suggestion was generated before or after a device left airplane mode. long timeSinceLastEventMillis = newEvent.getCreationElapsedMillis() - mLastEventSent.getCreationElapsedMillis(); return timeSinceLastEventMillis > mEventFilteringAgeThresholdMillis; } private void onStartUpdatesInternal(@NonNull ITimeZoneProviderManager manager, @DurationMillisLong long initializationTimeoutMillis) { @DurationMillisLong long initializationTimeoutMillis, @DurationMillisLong long eventFilteringAgeThresholdMillis) { synchronized (mLock) { mManager = manager; mEventFilteringAgeThresholdMillis = eventFilteringAgeThresholdMillis; mLastEventSent = null; onStartUpdates(initializationTimeoutMillis); } Loading Loading @@ -332,9 +363,11 @@ public abstract class TimeZoneProviderService extends Service { private class TimeZoneProviderServiceWrapper extends ITimeZoneProvider.Stub { public void startUpdates(@NonNull ITimeZoneProviderManager manager, @DurationMillisLong long initializationTimeoutMillis) { @DurationMillisLong long initializationTimeoutMillis, @DurationMillisLong long eventFilteringAgeThresholdMillis) { Objects.requireNonNull(manager); mHandler.post(() -> onStartUpdatesInternal(manager, initializationTimeoutMillis)); mHandler.post(() -> onStartUpdatesInternal( manager, initializationTimeoutMillis, eventFilteringAgeThresholdMillis)); } public void stopUpdates() { Loading
core/java/android/service/timezone/TimeZoneProviderSuggestion.java +19 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.service.timezone; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; Loading Loading @@ -121,6 +122,24 @@ public final class TimeZoneProviderSuggestion implements Parcelable { parcel.writeLong(mElapsedRealtimeMillis); } /** * Similar to {@link #equals} except this methods checks for equivalence, not equality. * i.e. two suggestions are equivalent if they suggest the same time zones. * * @hide */ @SuppressWarnings("ReferenceEquality") public boolean isEquivalentTo(@Nullable TimeZoneProviderSuggestion other) { if (this == other) { return true; } if (other == null) { return false; } // Only check the time zone IDs. The times can be different, but we don't mind. return mTimeZoneIds.equals(other.mTimeZoneIds); } @Override public boolean equals(Object o) { if (this == o) { Loading
core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java +30 −12 Original line number Diff line number Diff line Loading @@ -33,7 +33,8 @@ public class TimeZoneProviderEventTest { @Test public void isEquivalentToAndEquals() { TimeZoneProviderEvent fail1v1 = TimeZoneProviderEvent.createPermanentFailureEvent("one"); TimeZoneProviderEvent fail1v1 = TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "one"); assertEquals(fail1v1, fail1v1); assertIsEquivalentTo(fail1v1, fail1v1); assertNotEquals(fail1v1, null); Loading @@ -41,58 +42,74 @@ public class TimeZoneProviderEventTest { { TimeZoneProviderEvent fail1v2 = TimeZoneProviderEvent.createPermanentFailureEvent("one"); TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "one"); assertEquals(fail1v1, fail1v2); assertIsEquivalentTo(fail1v1, fail1v2); TimeZoneProviderEvent fail2 = TimeZoneProviderEvent.createPermanentFailureEvent("two"); TimeZoneProviderEvent fail2 = TimeZoneProviderEvent.createPermanentFailureEvent(2222L, "two"); assertNotEquals(fail1v1, fail2); assertIsEquivalentTo(fail1v1, fail2); } TimeZoneProviderEvent uncertain1v1 = TimeZoneProviderEvent.createUncertainEvent(); TimeZoneProviderEvent uncertain1v1 = TimeZoneProviderEvent.createUncertainEvent(1111L); assertEquals(uncertain1v1, uncertain1v1); assertIsEquivalentTo(uncertain1v1, uncertain1v1); assertNotEquals(uncertain1v1, null); assertNotEquivalentTo(uncertain1v1, null); { TimeZoneProviderEvent uncertain1v2 = TimeZoneProviderEvent.createUncertainEvent(); TimeZoneProviderEvent uncertain1v2 = TimeZoneProviderEvent.createUncertainEvent(1111L); assertEquals(uncertain1v1, uncertain1v2); assertIsEquivalentTo(uncertain1v1, uncertain1v2); TimeZoneProviderEvent uncertain2 = TimeZoneProviderEvent.createUncertainEvent(2222L); assertNotEquals(uncertain1v1, uncertain2); assertIsEquivalentTo(uncertain1v1, uncertain2); } TimeZoneProviderSuggestion suggestion1 = new TimeZoneProviderSuggestion.Builder() .setElapsedRealtimeMillis(1111L) .setTimeZoneIds(Collections.singletonList("Europe/London")) .build(); TimeZoneProviderEvent certain1v1 = TimeZoneProviderEvent.createSuggestionEvent(suggestion1); TimeZoneProviderEvent certain1v1 = TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion1); assertEquals(certain1v1, certain1v1); assertIsEquivalentTo(certain1v1, certain1v1); assertNotEquals(certain1v1, null); assertNotEquivalentTo(certain1v1, null); { // Same suggestion, same time. TimeZoneProviderEvent certain1v2 = TimeZoneProviderEvent.createSuggestionEvent(suggestion1); TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion1); assertEquals(certain1v1, certain1v2); assertIsEquivalentTo(certain1v1, certain1v2); // Same suggestion, different time. TimeZoneProviderEvent certain1v3 = TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion1); assertNotEquals(certain1v1, certain1v3); assertIsEquivalentTo(certain1v1, certain1v3); // suggestion1 is equivalent to suggestion2, but not equal TimeZoneProviderSuggestion suggestion2 = new TimeZoneProviderSuggestion.Builder() .setElapsedRealtimeMillis(2222L) .setTimeZoneIds(Collections.singletonList("Europe/London")) .build(); assertNotEquals(suggestion1, suggestion2); TimeZoneProviderSuggestionTest.assertIsEquivalentTo(suggestion1, suggestion2); TimeZoneProviderEvent certain2 = TimeZoneProviderEvent.createSuggestionEvent(suggestion2); TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion2); assertNotEquals(certain1v1, certain2); assertIsEquivalentTo(certain1v1, certain2); // suggestion3 is not equivalent to suggestion1 TimeZoneProviderSuggestion suggestion3 = new TimeZoneProviderSuggestion.Builder() .setTimeZoneIds(Collections.singletonList("Europe/Paris")) .build(); TimeZoneProviderEvent certain3 = TimeZoneProviderEvent.createSuggestionEvent(suggestion3); TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion3); assertNotEquals(certain1v1, certain3); assertNotEquivalentTo(certain1v1, certain3); } Loading @@ -107,13 +124,13 @@ public class TimeZoneProviderEventTest { @Test public void testParcelable_failureEvent() { TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent("failure reason"); TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "failure reason"); assertRoundTripParcelable(event); } @Test public void testParcelable_uncertain() { TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(); TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(1111L); assertRoundTripParcelable(event); } Loading @@ -122,7 +139,8 @@ public class TimeZoneProviderEventTest { TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder() .setTimeZoneIds(Arrays.asList("Europe/London", "Europe/Paris")) .build(); TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion); TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion); assertRoundTripParcelable(event); } Loading