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

Commit 87b26046 authored by Neil Fuller's avatar Neil Fuller Committed by Android (Google) Code Review
Browse files

Merge "Adding selective filtering to LTZP events"

parents e958da73 135703fe
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