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

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

Merge "Prepare for rate limiting TimeZoneProviderService"

parents 1b438caa a395ffdd
Loading
Loading
Loading
Loading
+2 −4
Original line number Diff line number Diff line
@@ -16,13 +16,11 @@

package android.service.timezone;

import android.service.timezone.TimeZoneProviderSuggestion;
import android.service.timezone.TimeZoneProviderEvent;

/**
 * @hide
 */
oneway interface ITimeZoneProviderManager {
    void onTimeZoneProviderSuggestion(in TimeZoneProviderSuggestion timeZoneProviderSuggestion);
    void onTimeZoneProviderUncertain();
    void onTimeZoneProviderPermanentFailure(in String failureReason);
    void onTimeZoneProviderEvent(in TimeZoneProviderEvent timeZoneProviderEvent);
}
+22 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.service.timezone;

/**
 * @hide
 */
parcelable TimeZoneProviderEvent;
+57 −5
Original line number Diff line number Diff line
@@ -14,13 +14,13 @@
 * limitations under the License.
 */

package com.android.server.timezonedetector.location;
package android.service.timezone;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.service.timezone.TimeZoneProviderService;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.os.Parcel;
import android.os.Parcelable;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -29,9 +29,11 @@ import java.lang.annotation.Target;
import java.util.Objects;

/**
 * An event from a {@link TimeZoneProviderService}.
 * Encapsulates a reported event from a {@link TimeZoneProviderService}.
 *
 * @hide
 */
final class TimeZoneProviderEvent {
public final class TimeZoneProviderEvent implements Parcelable {

    @IntDef(prefix = "EVENT_TYPE_",
            value = { EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUGGESTION, EVENT_TYPE_UNCERTAIN })
@@ -118,6 +120,35 @@ final class TimeZoneProviderEvent {
        return mFailureCause;
    }

    public static final @NonNull Creator<TimeZoneProviderEvent> CREATOR =
            new Creator<TimeZoneProviderEvent>() {
                @Override
                public TimeZoneProviderEvent createFromParcel(Parcel in) {
                    int type = in.readInt();
                    TimeZoneProviderSuggestion suggestion =
                            in.readParcelable(getClass().getClassLoader());
                    String failureCause = in.readString8();
                    return new TimeZoneProviderEvent(type, suggestion, failureCause);
                }

                @Override
                public TimeZoneProviderEvent[] newArray(int size) {
                    return new TimeZoneProviderEvent[size];
                }
            };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel parcel, int flags) {
        parcel.writeInt(mType);
        parcel.writeParcelable(mSuggestion, 0);
        parcel.writeString8(mFailureCause);
    }

    @Override
    public String toString() {
        return "TimeZoneProviderEvent{"
@@ -127,6 +158,27 @@ final class TimeZoneProviderEvent {
                + '}';
    }

    /**
     * Similar to {@link #equals} except this methods checks for equivalence, not equality.
     * i.e. two {@link #EVENT_TYPE_UNCERTAIN} and {@link #EVENT_TYPE_PERMANENT_FAILURE} events are
     * always equivalent, two {@link #EVENT_TYPE_SUGGESTION} events are equivalent if they suggest
     * the same time zones.
     */
    @SuppressWarnings("ReferenceEquality")
    public boolean isEquivalentTo(@Nullable TimeZoneProviderEvent other) {
        if (this == other) {
            return true;
        }
        if (other == null || mType != other.mType) {
            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 true;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
+77 −23
Original line number Diff line number Diff line
@@ -28,8 +28,11 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;

/**
@@ -122,7 +125,9 @@ import java.util.Objects;
 * #onDestroy()} can occur on a different thread from those made to {@link
 * TimeZoneProviderService}-defined service methods, so implementations must be defensive and not
 * assume an ordering between them, e.g. a call to {@link #onStopUpdates()} can occur after {@link
 * #onDestroy()} and should be handled safely.
 * #onDestroy()} and should be handled safely. {@link #mLock} is used to ensure that synchronous
 * calls like {@link #dump(FileDescriptor, PrintWriter, String[])} are safe with respect to
 * asynchronous behavior.
 *
 * @hide
 */
@@ -162,12 +167,26 @@ public abstract class TimeZoneProviderService extends Service {

    private final TimeZoneProviderServiceWrapper mWrapper = new TimeZoneProviderServiceWrapper();

    /** The object used for operations that occur between the main / handler thread. */
    private final Object mLock = new Object();

    /** The handler used for most operations. */
    private final Handler mHandler = BackgroundThread.getHandler();

    /** Set by {@link #mHandler} thread. */
    @GuardedBy("mLock")
    @Nullable
    private ITimeZoneProviderManager mManager;

    /**
     * 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
     * event has been sent this cycle; this field is cleared when the service is started.
     */
    @GuardedBy("mLock")
    @Nullable
    private TimeZoneProviderEvent mLastEventSent;

    @Override
    @NonNull
    public final IBinder onBind(@NonNull Intent intent) {
@@ -182,14 +201,21 @@ public abstract class TimeZoneProviderService extends Service {
        Objects.requireNonNull(suggestion);

        mHandler.post(() -> {
            synchronized (mLock) {
                ITimeZoneProviderManager manager = mManager;
                if (manager != null) {
                    try {
                    manager.onTimeZoneProviderSuggestion(suggestion);
                        TimeZoneProviderEvent thisEvent =
                                TimeZoneProviderEvent.createSuggestionEvent(suggestion);
                        if (!thisEvent.isEquivalentTo(mLastEventSent)) {
                            manager.onTimeZoneProviderEvent(thisEvent);
                            mLastEventSent = thisEvent;
                        }
                    } catch (RemoteException | RuntimeException e) {
                        Log.w(TAG, e);
                    }
                }
            }
        });
    }

@@ -200,14 +226,21 @@ public abstract class TimeZoneProviderService extends Service {
     */
    public final void reportUncertain() {
        mHandler.post(() -> {
            synchronized (mLock) {
                ITimeZoneProviderManager manager = mManager;
                if (manager != null) {
                    try {
                    manager.onTimeZoneProviderUncertain();
                        TimeZoneProviderEvent thisEvent =
                                TimeZoneProviderEvent.createUncertainEvent();
                        if (!thisEvent.isEquivalentTo(mLastEventSent)) {
                            manager.onTimeZoneProviderEvent(thisEvent);
                            mLastEventSent = thisEvent;
                        }
                    } catch (RemoteException | RuntimeException e) {
                        Log.w(TAG, e);
                    }
                }
            }
        });
    }

@@ -219,22 +252,33 @@ public abstract class TimeZoneProviderService extends Service {
        Objects.requireNonNull(cause);

        mHandler.post(() -> {
            synchronized (mLock) {
                ITimeZoneProviderManager manager = mManager;
                if (manager != null) {
                    try {
                    manager.onTimeZoneProviderPermanentFailure(cause.getMessage());
                        String causeString = cause.getMessage();
                        TimeZoneProviderEvent thisEvent =
                                TimeZoneProviderEvent.createPermanentFailureEvent(causeString);
                        if (!thisEvent.isEquivalentTo(mLastEventSent)) {
                            manager.onTimeZoneProviderEvent(thisEvent);
                            mLastEventSent = thisEvent;
                        }
                    } catch (RemoteException | RuntimeException e) {
                        Log.w(TAG, e);
                    }
                }
            }
        });
    }

    private void onStartUpdatesInternal(@NonNull ITimeZoneProviderManager manager,
            @DurationMillisLong long initializationTimeoutMillis) {
        synchronized (mLock) {
            mManager = manager;
            mLastEventSent = null;
            onStartUpdates(initializationTimeoutMillis);
        }
    }

    /**
     * Informs the provider that it should start detecting and reporting the detected time zone
@@ -265,9 +309,11 @@ public abstract class TimeZoneProviderService extends Service {
    public abstract void onStartUpdates(@DurationMillisLong long initializationTimeoutMillis);

    private void onStopUpdatesInternal() {
        synchronized (mLock) {
            onStopUpdates();
            mManager = null;
        }
    }

    /**
     * Stops the provider sending further updates. This will be called after {@link
@@ -275,6 +321,14 @@ public abstract class TimeZoneProviderService extends Service {
     */
    public abstract void onStopUpdates();

    /** @hide */
    @Override
    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
        synchronized (mLock) {
            writer.append("mLastEventSent=" + mLastEventSent);
        }
    }

    private class TimeZoneProviderServiceWrapper extends ITimeZoneProvider.Stub {

        public void startUpdates(@NonNull ITimeZoneProviderManager manager,
+149 −0
Original line number Diff line number Diff line
/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.service.timezone;

import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;

public class TimeZoneProviderEventTest {

    @Test
    public void isEquivalentToAndEquals() {
        TimeZoneProviderEvent fail1v1 = TimeZoneProviderEvent.createPermanentFailureEvent("one");
        assertEquals(fail1v1, fail1v1);
        assertIsEquivalentTo(fail1v1, fail1v1);
        assertNotEquals(fail1v1, null);
        assertNotEquivalentTo(fail1v1, null);

        {
            TimeZoneProviderEvent fail1v2 =
                    TimeZoneProviderEvent.createPermanentFailureEvent("one");
            assertEquals(fail1v1, fail1v2);
            assertIsEquivalentTo(fail1v1, fail1v2);

            TimeZoneProviderEvent fail2 = TimeZoneProviderEvent.createPermanentFailureEvent("two");
            assertNotEquals(fail1v1, fail2);
            assertIsEquivalentTo(fail1v1, fail2);
        }

        TimeZoneProviderEvent uncertain1v1 = TimeZoneProviderEvent.createUncertainEvent();
        assertEquals(uncertain1v1, uncertain1v1);
        assertIsEquivalentTo(uncertain1v1, uncertain1v1);
        assertNotEquals(uncertain1v1, null);
        assertNotEquivalentTo(uncertain1v1, null);

        {
            TimeZoneProviderEvent uncertain1v2 = TimeZoneProviderEvent.createUncertainEvent();
            assertEquals(uncertain1v1, uncertain1v2);
            assertIsEquivalentTo(uncertain1v1, uncertain1v2);
        }

        TimeZoneProviderSuggestion suggestion1 = new TimeZoneProviderSuggestion.Builder()
                .setElapsedRealtimeMillis(1111L)
                .setTimeZoneIds(Collections.singletonList("Europe/London"))
                .build();
        TimeZoneProviderEvent certain1v1 = TimeZoneProviderEvent.createSuggestionEvent(suggestion1);
        assertEquals(certain1v1, certain1v1);
        assertIsEquivalentTo(certain1v1, certain1v1);
        assertNotEquals(certain1v1, null);
        assertNotEquivalentTo(certain1v1, null);

        {
            TimeZoneProviderEvent certain1v2 =
                    TimeZoneProviderEvent.createSuggestionEvent(suggestion1);
            assertEquals(certain1v1, certain1v2);
            assertIsEquivalentTo(certain1v1, certain1v2);

            TimeZoneProviderSuggestion suggestion2 = new TimeZoneProviderSuggestion.Builder()
                    .setElapsedRealtimeMillis(2222L)
                    .setTimeZoneIds(Collections.singletonList("Europe/London"))
                    .build();
            assertNotEquals(suggestion1, suggestion2);
            TimeZoneProviderEvent certain2 =
                    TimeZoneProviderEvent.createSuggestionEvent(suggestion2);
            assertNotEquals(certain1v1, certain2);
            assertIsEquivalentTo(certain1v1, certain2);

            TimeZoneProviderSuggestion suggestion3 = new TimeZoneProviderSuggestion.Builder()
                    .setTimeZoneIds(Collections.singletonList("Europe/Paris"))
                    .build();
            TimeZoneProviderEvent certain3 =
                    TimeZoneProviderEvent.createSuggestionEvent(suggestion3);
            assertNotEquals(certain1v1, certain3);
            assertNotEquivalentTo(certain1v1, certain3);
        }

        assertNotEquals(fail1v1, uncertain1v1);
        assertNotEquivalentTo(fail1v1, uncertain1v1);

        assertNotEquals(fail1v1, certain1v1);
        assertNotEquivalentTo(fail1v1, certain1v1);
    }

    @Test
    public void testParcelable_failureEvent() {
        TimeZoneProviderEvent event =
                TimeZoneProviderEvent.createPermanentFailureEvent("failure reason");
        assertRoundTripParcelable(event);
    }

    @Test
    public void testParcelable_uncertain() {
        TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent();
        assertRoundTripParcelable(event);
    }

    @Test
    public void testParcelable_suggestion() {
        TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
                .setTimeZoneIds(Arrays.asList("Europe/London", "Europe/Paris"))
                .build();
        TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion);
        assertRoundTripParcelable(event);
    }

    private static void assertNotEquivalentTo(
            TimeZoneProviderEvent one, TimeZoneProviderEvent two) {
        if (one == null && two == null) {
            fail("null arguments");
        }
        if (one != null) {
            assertFalse("one=" + one + ", two=" + two, one.isEquivalentTo(two));
        }
        if (two != null) {
            assertFalse("one=" + one + ", two=" + two, two.isEquivalentTo(one));
        }
    }

    private static void assertIsEquivalentTo(TimeZoneProviderEvent one, TimeZoneProviderEvent two) {
        if (one == null || two == null) {
            fail("null arguments");
        }
        assertTrue("one=" + one + ", two=" + two, one.isEquivalentTo(two));
        assertTrue("one=" + one + ", two=" + two, two.isEquivalentTo(one));
    }
}
Loading