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

Commit 135703fe authored by Neil Fuller's avatar Neil Fuller
Browse files

Adding selective filtering to LTZP events

This commit introduces a time threshold for passing on "equivalent"
events for location time zone providers. When TimeZoneProviderEvent
instances are created, they are assigned a time from the elapsed
realtime clock, which will enable comparison with other types of events
generated by other flows. Events that have a sufficiently different time
will be passed to the system server. The time will be in the order of a
few minutes, the intention being to filter out high frequency events
that would cause the system server to do work unnecessarily, while
keeping a coarse idea of how recently the LTZP reported the state.

This commit adds the time to the TimeZoneProviderEvent and plumbs
through the configuration for the filter threshold from the system
server to the client code. The threshold can be set with server flags,
which are mostly intended to support the test logic.

Changes made at the same time:
1) Fix an issue in TimeZoneProviderRequest.equals() with an incorrect
reference equality check.
2) Rename some long-winded constant names - LOCATION_TIME_ZONE_PROVIDER_
-> LTZP_
3) Change some server flag strings so that "ltzp_" is spelt correctly
(which I do not anticipate using, but does mean that S and >S devices
respond to different flag strings).

Bug: 197624972
Bug: 200710190
Test: atest core/tests/coretests/src/android/service/timezone/
Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/location/
Test: See associated cts/ change
Change-Id: Id682d420be21b9215b92f54bad599517c322fb6a
parent a395ffdd
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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();
}
+30 −12
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.service.timezone;

import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -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;

/**
@@ -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;

@@ -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));
    }

@@ -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}.
     */
@@ -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
@@ -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);
    }
@@ -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
                + '}';
@@ -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;
    }
@@ -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);
    }
}
+42 −9
Original line number Diff line number Diff line
@@ -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;
@@ -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
@@ -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;
                        }
@@ -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;
                        }
@@ -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;
                        }
@@ -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);
        }
@@ -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() {
+19 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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) {
+30 −12
Original line number Diff line number Diff line
@@ -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);
@@ -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);
        }
@@ -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);
    }

@@ -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