Prepare for rate limiting TimeZoneProviderService

Prepare for rate limiting TimeZoneProviderService.  The first step is to
add duplicate event detection. Later, this will be extended to add a
time component.

i.e. the android.service.timezone.TimeZoneProviderService
will pass on suggestions that are semantically different from a previous
event, but will not if they are semantically the same.

In a later commit it will be made to pass on changes that are the same but
newer than some threshold.

Most of the changes in this commit are to track a change to the binder
API and the move of TimeZoneProviderEvent so that it can be used in code
outside of the system server.

Also:
+ TimeZoneProviderService is now stateful, so add dump() implementation.
Because dump() is called on the main thread, add locks needed for safe
threading (previously, all logic took place on a known handler thread).

Test: atest hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
Test: atest core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
Test: treehugger
Bug: 197624972
Bug: 200710190
Change-Id: Ia3246e969fda61836c8e1eee3007a8177d119994
This commit is contained in:
Neil Fuller
2021-10-11 12:07:20 +01:00
parent 251fa87f30
commit a395ffdd2c
18 changed files with 330 additions and 58 deletions

View File

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

View File

@@ -0,0 +1,22 @@
/*
* 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;

View File

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

View File

@@ -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,12 +201,19 @@ public abstract class TimeZoneProviderService extends Service {
Objects.requireNonNull(suggestion);
mHandler.post(() -> {
ITimeZoneProviderManager manager = mManager;
if (manager != null) {
try {
manager.onTimeZoneProviderSuggestion(suggestion);
} catch (RemoteException | RuntimeException e) {
Log.w(TAG, e);
synchronized (mLock) {
ITimeZoneProviderManager manager = mManager;
if (manager != null) {
try {
TimeZoneProviderEvent thisEvent =
TimeZoneProviderEvent.createSuggestionEvent(suggestion);
if (!thisEvent.isEquivalentTo(mLastEventSent)) {
manager.onTimeZoneProviderEvent(thisEvent);
mLastEventSent = thisEvent;
}
} catch (RemoteException | RuntimeException e) {
Log.w(TAG, e);
}
}
}
});
@@ -200,12 +226,19 @@ public abstract class TimeZoneProviderService extends Service {
*/
public final void reportUncertain() {
mHandler.post(() -> {
ITimeZoneProviderManager manager = mManager;
if (manager != null) {
try {
manager.onTimeZoneProviderUncertain();
} catch (RemoteException | RuntimeException e) {
Log.w(TAG, e);
synchronized (mLock) {
ITimeZoneProviderManager manager = mManager;
if (manager != null) {
try {
TimeZoneProviderEvent thisEvent =
TimeZoneProviderEvent.createUncertainEvent();
if (!thisEvent.isEquivalentTo(mLastEventSent)) {
manager.onTimeZoneProviderEvent(thisEvent);
mLastEventSent = thisEvent;
}
} catch (RemoteException | RuntimeException e) {
Log.w(TAG, e);
}
}
}
});
@@ -219,12 +252,20 @@ public abstract class TimeZoneProviderService extends Service {
Objects.requireNonNull(cause);
mHandler.post(() -> {
ITimeZoneProviderManager manager = mManager;
if (manager != null) {
try {
manager.onTimeZoneProviderPermanentFailure(cause.getMessage());
} catch (RemoteException | RuntimeException e) {
Log.w(TAG, e);
synchronized (mLock) {
ITimeZoneProviderManager manager = mManager;
if (manager != null) {
try {
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);
}
}
}
});
@@ -232,8 +273,11 @@ public abstract class TimeZoneProviderService extends Service {
private void onStartUpdatesInternal(@NonNull ITimeZoneProviderManager manager,
@DurationMillisLong long initializationTimeoutMillis) {
mManager = manager;
onStartUpdates(initializationTimeoutMillis);
synchronized (mLock) {
mManager = manager;
mLastEventSent = null;
onStartUpdates(initializationTimeoutMillis);
}
}
/**
@@ -265,8 +309,10 @@ public abstract class TimeZoneProviderService extends Service {
public abstract void onStartUpdates(@DurationMillisLong long initializationTimeoutMillis);
private void onStopUpdatesInternal() {
onStopUpdates();
mManager = null;
synchronized (mLock) {
onStopUpdates();
mManager = null;
}
}
/**
@@ -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,

View File

@@ -0,0 +1,149 @@
/*
* 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));
}
}

View File

@@ -26,6 +26,7 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.service.timezone.TimeZoneProviderEvent;
import android.util.IndentingPrintWriter;
import java.time.Duration;

View File

@@ -16,6 +16,10 @@
package com.android.server.timezonedetector.location;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
@@ -25,14 +29,12 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
import android.annotation.DurationMillisLong;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.service.timezone.TimeZoneProviderEvent;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;

View File

@@ -28,6 +28,7 @@ import android.os.Binder;
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderService;
import android.util.IndentingPrintWriter;
import android.util.Log;

View File

@@ -16,6 +16,10 @@
package com.android.server.timezonedetector.location;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
@@ -24,9 +28,6 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
@@ -34,6 +35,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.SystemClock;
import android.service.timezone.TimeZoneProviderEvent;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

View File

@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Handler;
import android.service.timezone.TimeZoneProviderEvent;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;

View File

@@ -19,6 +19,7 @@ package com.android.server.timezonedetector.location;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.service.timezone.TimeZoneProviderEvent;
import android.util.IndentingPrintWriter;
/**

View File

@@ -26,7 +26,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.service.timezone.ITimeZoneProvider;
import android.service.timezone.ITimeZoneProviderManager;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.service.timezone.TimeZoneProviderEvent;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
@@ -177,25 +177,7 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy im
// executed on binder thread
@Override
public void onTimeZoneProviderSuggestion(TimeZoneProviderSuggestion suggestion) {
onTimeZoneProviderEvent(TimeZoneProviderEvent.createSuggestionEvent(suggestion));
}
// executed on binder thread
@Override
public void onTimeZoneProviderUncertain() {
onTimeZoneProviderEvent(TimeZoneProviderEvent.createUncertainEvent());
}
// executed on binder thread
@Override
public void onTimeZoneProviderPermanentFailure(String failureReason) {
onTimeZoneProviderEvent(
TimeZoneProviderEvent.createPermanentFailureEvent(failureReason));
}
private void onTimeZoneProviderEvent(TimeZoneProviderEvent event) {
public void onTimeZoneProviderEvent(TimeZoneProviderEvent event) {
synchronized (mSharedLock) {
if (mManagerProxy != this) {
// Ignore incoming calls if this instance is no longer the current

View File

@@ -17,6 +17,7 @@
package com.android.server.timezonedetector.location;
import android.annotation.NonNull;
import android.service.timezone.TimeZoneProviderEvent;
/**
* Used by {@link LocationTimeZoneProvider} to ensure that all time zone IDs are understood by the

View File

@@ -19,6 +19,7 @@ package com.android.server.timezonedetector.location;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog;
import android.annotation.NonNull;
import android.service.timezone.TimeZoneProviderEvent;
import com.android.i18n.timezone.ZoneInfoDb;

View File

@@ -36,6 +36,7 @@ import static java.util.Arrays.asList;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.platform.test.annotations.Presubmit;
import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;

View File

@@ -16,6 +16,8 @@
package com.android.server.timezonedetector.location;
import android.service.timezone.TimeZoneProviderEvent;
/**
* Fake implementation of {@link TimeZoneProviderEventPreProcessor} which assumes that all events
* are valid or always uncertain if {@link #enterUncertainMode()} was called.

View File

@@ -32,6 +32,7 @@ import static java.util.Arrays.asList;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.platform.test.annotations.Presubmit;
import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;

View File

@@ -19,6 +19,7 @@ package com.android.server.timezonedetector.location;
import static com.google.common.truth.Truth.assertWithMessage;
import android.platform.test.annotations.Presubmit;
import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderSuggestion;
import org.junit.Test;