diff options
7 files changed, 314 insertions, 216 deletions
diff --git a/services/core/java/com/android/server/timezonedetector/Environment.java b/services/core/java/com/android/server/timezonedetector/Environment.java new file mode 100644 index 000000000000..795fb02373ff --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/Environment.java @@ -0,0 +1,80 @@ +/* + * Copyright 2025 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 com.android.server.timezonedetector; + +import android.annotation.CurrentTimeMillisLong; +import android.annotation.ElapsedRealtimeLong; +import android.annotation.NonNull; + +import com.android.server.SystemTimeZone; + +import java.io.PrintWriter; + +/** + * Used by the time zone detector code to interact with device state besides that available from + * {@link ServiceConfigAccessor}. It can be faked for testing. + */ +public interface Environment { + + /** + * Returns the device's currently configured time zone. May return an empty string. + */ + @NonNull + String getDeviceTimeZone(); + + /** + * Returns the confidence of the device's current time zone. + */ + @SystemTimeZone.TimeZoneConfidence + int getDeviceTimeZoneConfidence(); + + /** + * Sets the device's time zone, associated confidence, and records a debug log entry. + */ + void setDeviceTimeZoneAndConfidence( + @NonNull String zoneId, @SystemTimeZone.TimeZoneConfidence int confidence, + @NonNull String logInfo); + + /** + * Returns the time according to the elapsed realtime clock, the same as {@link + * android.os.SystemClock#elapsedRealtime()}. + */ + @ElapsedRealtimeLong + long elapsedRealtimeMillis(); + + /** + * Returns the current time in milliseconds, the same as + * {@link java.lang.System#currentTimeMillis()}. + */ + @CurrentTimeMillisLong + long currentTimeMillis(); + + /** + * Adds a standalone entry to the time zone debug log. + */ + void addDebugLogEntry(@NonNull String logMsg); + + /** + * Dumps the time zone debug log to the supplied {@link PrintWriter}. + */ + void dumpDebugLog(PrintWriter printWriter); + + /** + * Requests that the supplied runnable be invoked asynchronously. + */ + void runAsync(@NonNull Runnable runnable); +} diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java index 449b41a09c51..8491b4818c2e 100644 --- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java @@ -16,6 +16,7 @@ package com.android.server.timezonedetector; +import android.annotation.CurrentTimeMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.os.Handler; @@ -31,9 +32,9 @@ import java.io.PrintWriter; import java.util.Objects; /** - * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}. + * The real implementation of {@link Environment}. */ -final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment { +final class EnvironmentImpl implements Environment { private static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; @@ -69,6 +70,11 @@ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment } @Override + public @CurrentTimeMillisLong long currentTimeMillis() { + return System.currentTimeMillis(); + } + + @Override public void addDebugLogEntry(@NonNull String logMsg) { SystemTimeZone.addDebugLogEntry(logMsg); } diff --git a/services/core/java/com/android/server/timezonedetector/NotifyingTimeZoneChangeListener.java b/services/core/java/com/android/server/timezonedetector/NotifyingTimeZoneChangeListener.java index 2e73829ca143..cf85a9a6a706 100644 --- a/services/core/java/com/android/server/timezonedetector/NotifyingTimeZoneChangeListener.java +++ b/services/core/java/com/android/server/timezonedetector/NotifyingTimeZoneChangeListener.java @@ -29,6 +29,7 @@ import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.ORIGI import android.annotation.DurationMillisLong; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; @@ -44,7 +45,6 @@ import android.icu.text.DateFormat; import android.icu.text.SimpleDateFormat; import android.icu.util.TimeZone; import android.os.Handler; -import android.os.SystemClock; import android.os.UserHandle; import android.util.IndentingPrintWriter; import android.util.Log; @@ -153,6 +153,9 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { } }; + @NonNull + private final Environment mEnvironment; + private final Object mConfigurationLock = new Object(); @GuardedBy("mConfigurationLock") private ConfigurationInternal mConfigurationInternal; @@ -170,12 +173,14 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { /** Create and initialise a new {@code TimeZoneChangeTrackerImpl} */ @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public static NotifyingTimeZoneChangeListener create(Handler handler, Context context, - ServiceConfigAccessor serviceConfigAccessor) { + ServiceConfigAccessor serviceConfigAccessor, + @NonNull Environment environment) { NotifyingTimeZoneChangeListener changeTracker = new NotifyingTimeZoneChangeListener(handler, context, serviceConfigAccessor, - context.getSystemService(NotificationManager.class)); + context.getSystemService(NotificationManager.class), + environment); // Pretend there was an update to initialize configuration. changeTracker.handleConfigurationUpdate(); @@ -184,9 +189,9 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { } @VisibleForTesting - NotifyingTimeZoneChangeListener( - Handler handler, Context context, ServiceConfigAccessor serviceConfigAccessor, - NotificationManager notificationManager) { + NotifyingTimeZoneChangeListener(Handler handler, Context context, + ServiceConfigAccessor serviceConfigAccessor, NotificationManager notificationManager, + @NonNull Environment environment) { mHandler = Objects.requireNonNull(handler); mContext = Objects.requireNonNull(context); mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); @@ -194,6 +199,7 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { this::handleConfigurationUpdate); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mNotificationManager = notificationManager; + mEnvironment = Objects.requireNonNull(environment); } @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") @@ -420,7 +426,7 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener { if (!changeEvent.getOldZoneId().equals(lastChangeEvent.getNewZoneId())) { int changeEventId = mNextChangeEventId.getAndIncrement(); TimeZoneChangeEvent syntheticChangeEvent = new TimeZoneChangeEvent( - SystemClock.elapsedRealtime(), System.currentTimeMillis(), + mEnvironment.elapsedRealtimeMillis(), mEnvironment.currentTimeMillis(), ORIGIN_UNKNOWN, UserHandle.USER_NULL, lastChangeEvent.getNewZoneId(), changeEvent.getOldZoneId(), 0, "Synthetic"); TimeZoneChangeRecord syntheticTrackedChangeEvent = diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index b2b06b0af5fa..042d81ab6885 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -25,7 +25,6 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_S import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW; -import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -54,7 +53,6 @@ import com.android.server.SystemTimeZone.TimeZoneConfidence; import com.android.server.flags.Flags; import com.android.server.timezonedetector.ConfigurationInternal.DetectionMode; -import java.io.PrintWriter; import java.time.Duration; import java.util.ArrayList; import java.util.List; @@ -67,55 +65,6 @@ import java.util.Objects; */ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy { - /** - * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device state besides that - * available from {@link #mServiceConfigAccessor}. It can be faked for testing. - */ - @VisibleForTesting - public interface Environment { - - /** - * Returns the device's currently configured time zone. May return an empty string. - */ - @NonNull - String getDeviceTimeZone(); - - /** - * Returns the confidence of the device's current time zone. - */ - @TimeZoneConfidence - int getDeviceTimeZoneConfidence(); - - /** - * Sets the device's time zone, associated confidence, and records a debug log entry. - */ - void setDeviceTimeZoneAndConfidence( - @NonNull String zoneId, @TimeZoneConfidence int confidence, - @NonNull String logInfo); - - /** - * Returns the time according to the elapsed realtime clock, the same as {@link - * android.os.SystemClock#elapsedRealtime()}. - */ - @ElapsedRealtimeLong - long elapsedRealtimeMillis(); - - /** - * Adds a standalone entry to the time zone debug log. - */ - void addDebugLogEntry(@NonNull String logMsg); - - /** - * Dumps the time zone debug log to the supplied {@link PrintWriter}. - */ - void dumpDebugLog(PrintWriter printWriter); - - /** - * Requests that the supplied runnable be invoked asynchronously. - */ - void runAsync(@NonNull Runnable runnable); - } - private static final String LOG_TAG = TimeZoneDetectorService.TAG; private static final boolean DBG = TimeZoneDetectorService.DBG; @@ -263,10 +212,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat public static TimeZoneDetectorStrategyImpl create( @NonNull Context context, @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) { - Environment environment = new EnvironmentImpl(handler); TimeZoneChangeListener changeEventTracker = - NotifyingTimeZoneChangeListener.create(handler, context, serviceConfigAccessor); + NotifyingTimeZoneChangeListener.create(handler, context, serviceConfigAccessor, + environment); return new TimeZoneDetectorStrategyImpl( serviceConfigAccessor, environment, changeEventTracker); } diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/FakeEnvironment.java b/services/tests/timetests/src/com/android/server/timezonedetector/FakeEnvironment.java new file mode 100644 index 000000000000..5d181b81eb4c --- /dev/null +++ b/services/tests/timetests/src/com/android/server/timezonedetector/FakeEnvironment.java @@ -0,0 +1,141 @@ +/* + * Copyright 2025 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 com.android.server.timezonedetector; + +import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW; + +import android.annotation.CurrentTimeMillisLong; +import android.annotation.ElapsedRealtimeLong; + +import com.android.server.SystemTimeZone; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * A partially implemented, fake implementation of Environment for tests. + */ +public class FakeEnvironment implements Environment { + + private final TestState<String> mTimeZoneId = new TestState<>(); + private final TestState<Integer> mTimeZoneConfidence = new TestState<>(); + private final List<Runnable> mAsyncRunnables = new ArrayList<>(); + private @ElapsedRealtimeLong long mElapsedRealtimeMillis; + private @CurrentTimeMillisLong long mInitializationTimeMillis; + + FakeEnvironment() { + // Ensure the fake environment starts with the defaults a fresh device would. + initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW); + } + + void initializeClock(@CurrentTimeMillisLong long currentTimeMillis, + @ElapsedRealtimeLong long elapsedRealtimeMillis) { + mInitializationTimeMillis = currentTimeMillis - elapsedRealtimeMillis; + mElapsedRealtimeMillis = elapsedRealtimeMillis; + } + + void initializeTimeZoneSetting(String zoneId, + @SystemTimeZone.TimeZoneConfidence int timeZoneConfidence) { + mTimeZoneId.init(zoneId); + mTimeZoneConfidence.init(timeZoneConfidence); + } + + void incrementClock() { + mElapsedRealtimeMillis++; + } + + @Override + public String getDeviceTimeZone() { + return mTimeZoneId.getLatest(); + } + + @Override + public int getDeviceTimeZoneConfidence() { + return mTimeZoneConfidence.getLatest(); + } + + @Override + public void setDeviceTimeZoneAndConfidence( + String zoneId, @SystemTimeZone.TimeZoneConfidence int confidence, String logInfo) { + mTimeZoneId.set(zoneId); + mTimeZoneConfidence.set(confidence); + } + + void assertTimeZoneNotChanged() { + mTimeZoneId.assertHasNotBeenSet(); + mTimeZoneConfidence.assertHasNotBeenSet(); + } + + void assertTimeZoneChangedTo(String timeZoneId, + @SystemTimeZone.TimeZoneConfidence int confidence) { + mTimeZoneId.assertHasBeenSet(); + mTimeZoneId.assertChangeCount(1); + mTimeZoneId.assertLatestEquals(timeZoneId); + + mTimeZoneConfidence.assertHasBeenSet(); + mTimeZoneConfidence.assertChangeCount(1); + mTimeZoneConfidence.assertLatestEquals(confidence); + } + + void commitAllChanges() { + mTimeZoneId.commitLatest(); + mTimeZoneConfidence.commitLatest(); + } + + @Override + @ElapsedRealtimeLong + public long elapsedRealtimeMillis() { + return mElapsedRealtimeMillis; + } + + @Override + @CurrentTimeMillisLong + public long currentTimeMillis() { + return mInitializationTimeMillis + mElapsedRealtimeMillis; + } + + @Override + public void addDebugLogEntry(String logMsg) { + // No-op for tests + } + + @Override + public void dumpDebugLog(PrintWriter printWriter) { + // No-op for tests + } + + /** + * Adds the supplied runnable to a list but does not run them. To run all the runnables that + * have been supplied, call {@code #runAsyncRunnables}. + */ + @Override + public void runAsync(Runnable runnable) { + mAsyncRunnables.add(runnable); + } + + /** + * Requests that the runnable that have been supplied to {@code #runAsync} are invoked + * asynchronously and cleared. + */ + public void runAsyncRunnables() { + for (Runnable runnable : mAsyncRunnables) { + runnable.run(); + } + mAsyncRunnables.clear(); + } +} diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/NotifyingTimeZoneChangeListenerTest.java b/services/tests/timetests/src/com/android/server/timezonedetector/NotifyingTimeZoneChangeListenerTest.java index 23c13bd04368..45cc891bff55 100644 --- a/services/tests/timetests/src/com/android/server/timezonedetector/NotifyingTimeZoneChangeListenerTest.java +++ b/services/tests/timetests/src/com/android/server/timezonedetector/NotifyingTimeZoneChangeListenerTest.java @@ -85,9 +85,6 @@ public class NotifyingTimeZoneChangeListenerTest { return List.of(ORIGIN_LOCATION, ORIGIN_TELEPHONY); } - private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234; - /** A time zone used for initialization that does not occur elsewhere in tests. */ - private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC"; private static final String INTERACT_ACROSS_USERS_FULL_PERMISSION = "android.permission.INTERACT_ACROSS_USERS_FULL"; @@ -99,6 +96,7 @@ public class NotifyingTimeZoneChangeListenerTest { private HandlerThread mHandlerThread; private TestHandler mHandler; private FakeServiceConfigAccessor mServiceConfigAccessor; + private FakeEnvironment mFakeEnvironment; private int mUid; private NotifyingTimeZoneChangeListener mTimeZoneChangeTracker; @@ -106,6 +104,9 @@ public class NotifyingTimeZoneChangeListenerTest { @Before public void setUp() { mUid = Process.myUid(); + mFakeEnvironment = new FakeEnvironment(); + mFakeEnvironment.initializeClock(1735689600L, 1234L); + // Create a thread + handler for processing the work that the service posts. mHandlerThread = new HandlerThread("TimeZoneDetectorInternalTest"); mHandlerThread.start(); @@ -138,7 +139,7 @@ public class NotifyingTimeZoneChangeListenerTest { mNotificationManager = new FakeNotificationManager(mContext, InstantSource.system()); mTimeZoneChangeTracker = new NotifyingTimeZoneChangeListener(mHandler, mContext, - mServiceConfigAccessor, mNotificationManager); + mServiceConfigAccessor, mNotificationManager, mFakeEnvironment); } @After @@ -151,7 +152,7 @@ public class NotifyingTimeZoneChangeListenerTest { public void process_autoDetectionOff_noManualTracking_shouldTrackWithoutNotifying() { enableTimeZoneNotifications(); - TimeZoneChangeRecord expectedChangeEvent = new TimeZoneChangeRecord( + TimeZoneChangeRecord expectedTimeZoneChangeRecord = new TimeZoneChangeRecord( /* id= */ 1, new TimeZoneChangeEvent( /* elapsedRealtimeMillis= */ 0, @@ -162,13 +163,14 @@ public class NotifyingTimeZoneChangeListenerTest { /* newZoneId= */ "Europe/London", /* newConfidence= */ 1, /* cause= */ "NO_REASON")); - expectedChangeEvent.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE); + expectedTimeZoneChangeRecord.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE); assertNull(mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); - mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent()); + mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); - assertEquals(expectedChangeEvent, mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); + assertEquals(expectedTimeZoneChangeRecord, + mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); assertEquals(0, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(0); } @@ -177,7 +179,7 @@ public class NotifyingTimeZoneChangeListenerTest { public void process_autoDetectionOff_shouldTrackWithoutNotifying() { enableNotificationsWithManualChangeTracking(); - TimeZoneChangeRecord expectedChangeEvent = new TimeZoneChangeRecord( + TimeZoneChangeRecord expectedTimeZoneChangeRecord = new TimeZoneChangeRecord( /* id= */ 1, new TimeZoneChangeEvent( /* elapsedRealtimeMillis= */ 0, @@ -188,13 +190,14 @@ public class NotifyingTimeZoneChangeListenerTest { /* newZoneId= */ "Europe/London", /* newConfidence= */ 1, /* cause= */ "NO_REASON")); - expectedChangeEvent.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE); + expectedTimeZoneChangeRecord.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE); assertNull(mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); - mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent()); + mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); - assertEquals(expectedChangeEvent, mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); + assertEquals(expectedTimeZoneChangeRecord, + mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); assertEquals(0, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(1); } @@ -214,7 +217,7 @@ public class NotifyingTimeZoneChangeListenerTest { enableNotificationsWithManualChangeTracking(); - TimeZoneChangeRecord expectedChangeEvent = new TimeZoneChangeRecord( + TimeZoneChangeRecord expectedTimeZoneChangeRecord = new TimeZoneChangeRecord( /* id= */ 1, new TimeZoneChangeEvent( /* elapsedRealtimeMillis= */ 0, @@ -225,19 +228,20 @@ public class NotifyingTimeZoneChangeListenerTest { /* newZoneId= */ "Europe/London", /* newConfidence= */ 1, /* cause= */ "NO_REASON")); - expectedChangeEvent.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN); + expectedTimeZoneChangeRecord.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN); assertNull(mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); // lastTrackedChangeEvent == null - mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent()); - TimeZoneChangeRecord trackedEvent1 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); + mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); + TimeZoneChangeRecord timeZoneChangeRecord1 = + mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); - assertEquals(expectedChangeEvent, trackedEvent1); + assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord1); assertEquals(1, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(1); - expectedChangeEvent = new TimeZoneChangeRecord( + expectedTimeZoneChangeRecord = new TimeZoneChangeRecord( /* id= */ 2, new TimeZoneChangeEvent( /* elapsedRealtimeMillis= */ 1000L, @@ -248,14 +252,15 @@ public class NotifyingTimeZoneChangeListenerTest { /* newZoneId= */ "Europe/Paris", /* newConfidence= */ 1, /* cause= */ "NO_REASON")); - expectedChangeEvent.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN); + expectedTimeZoneChangeRecord.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN); // lastTrackedChangeEvent != null - mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent()); - TimeZoneChangeRecord trackedEvent2 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); + mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); + TimeZoneChangeRecord timeZoneChangeRecord2 = + mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); - assertEquals(STATUS_SUPERSEDED, trackedEvent1.getStatus()); - assertEquals(expectedChangeEvent, trackedEvent2); + assertEquals(STATUS_SUPERSEDED, timeZoneChangeRecord1.getStatus()); + assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord2); assertEquals(2, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(2); @@ -263,7 +268,7 @@ public class NotifyingTimeZoneChangeListenerTest { // Test manual change within revert threshold { - expectedChangeEvent = new TimeZoneChangeRecord( + expectedTimeZoneChangeRecord = new TimeZoneChangeRecord( /* id= */ 3, new TimeZoneChangeEvent( /* elapsedRealtimeMillis= */ 999L + AUTO_REVERT_THRESHOLD, @@ -275,16 +280,16 @@ public class NotifyingTimeZoneChangeListenerTest { /* newZoneId= */ "Europe/London", /* newConfidence= */ 1, /* cause= */ "NO_REASON")); - expectedChangeEvent.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE); + expectedTimeZoneChangeRecord.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE); - mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent()); - TimeZoneChangeRecord trackedEvent3 = + mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); + TimeZoneChangeRecord timeZoneChangeRecord3 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); // The user manually changed the time zone within a short period of receiving the // notification, indicating that they rejected the automatic change of time zone - assertEquals(STATUS_REJECTED, trackedEvent2.getStatus()); - assertEquals(expectedChangeEvent, trackedEvent3); + assertEquals(STATUS_REJECTED, timeZoneChangeRecord2.getStatus()); + assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord3); assertEquals(2, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(3); } @@ -293,12 +298,12 @@ public class NotifyingTimeZoneChangeListenerTest { { // [START] Reset previous event enableNotificationsWithManualChangeTracking(); - mTimeZoneChangeTracker.process(trackedEvent2.getEvent()); - trackedEvent2 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); + mTimeZoneChangeTracker.process(timeZoneChangeRecord2.getEvent()); + timeZoneChangeRecord2 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); disableTimeZoneAutoDetection(); // [END] Reset previous event - expectedChangeEvent = new TimeZoneChangeRecord( + expectedTimeZoneChangeRecord = new TimeZoneChangeRecord( /* id= */ 5, new TimeZoneChangeEvent( /* elapsedRealtimeMillis= */ 1001L + AUTO_REVERT_THRESHOLD, @@ -310,15 +315,15 @@ public class NotifyingTimeZoneChangeListenerTest { /* newZoneId= */ "Europe/London", /* newConfidence= */ 1, /* cause= */ "NO_REASON")); - expectedChangeEvent.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE); + expectedTimeZoneChangeRecord.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE); - mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent()); - TimeZoneChangeRecord trackedEvent3 = + mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); + TimeZoneChangeRecord timeZoneChangeRecord3 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); // The user manually changed the time zone outside of the period we consider as a revert - assertEquals(STATUS_SUPERSEDED, trackedEvent2.getStatus()); - assertEquals(expectedChangeEvent, trackedEvent3); + assertEquals(STATUS_SUPERSEDED, timeZoneChangeRecord2.getStatus()); + assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord3); assertEquals(3, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(5); } @@ -339,7 +344,7 @@ public class NotifyingTimeZoneChangeListenerTest { enableNotificationsWithManualChangeTracking(); - TimeZoneChangeRecord expectedChangeEvent = new TimeZoneChangeRecord( + TimeZoneChangeRecord expectedTimeZoneChangeRecord = new TimeZoneChangeRecord( /* id= */ 1, new TimeZoneChangeEvent( /* elapsedRealtimeMillis= */ 0, @@ -350,19 +355,20 @@ public class NotifyingTimeZoneChangeListenerTest { /* newZoneId= */ "Europe/London", /* newConfidence= */ 1, /* cause= */ "NO_REASON")); - expectedChangeEvent.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN); + expectedTimeZoneChangeRecord.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN); assertNull(mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); // lastTrackedChangeEvent == null - mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent()); - TimeZoneChangeRecord trackedEvent1 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); + mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); + TimeZoneChangeRecord timeZoneChangeRecord1 = + mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); - assertEquals(expectedChangeEvent, trackedEvent1); + assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord1); assertEquals(1, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(1); - expectedChangeEvent = new TimeZoneChangeRecord( + expectedTimeZoneChangeRecord = new TimeZoneChangeRecord( /* id= */ 3, new TimeZoneChangeEvent( /* elapsedRealtimeMillis= */ 1000L, @@ -373,14 +379,15 @@ public class NotifyingTimeZoneChangeListenerTest { /* newZoneId= */ "Europe/Paris", /* newConfidence= */ 1, /* cause= */ "NO_REASON")); - expectedChangeEvent.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN); + expectedTimeZoneChangeRecord.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN); // lastTrackedChangeEvent != null - mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent()); - TimeZoneChangeRecord trackedEvent2 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); + mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); + TimeZoneChangeRecord timeZoneChangeRecord2 = + mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); - assertEquals(STATUS_SUPERSEDED, trackedEvent1.getStatus()); - assertEquals(expectedChangeEvent, trackedEvent2); + assertEquals(STATUS_SUPERSEDED, timeZoneChangeRecord1.getStatus()); + assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord2); assertEquals(2, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(2); } @@ -400,7 +407,7 @@ public class NotifyingTimeZoneChangeListenerTest { enableNotificationsWithManualChangeTracking(); - TimeZoneChangeRecord expectedChangeEvent = new TimeZoneChangeRecord( + TimeZoneChangeRecord expectedTimeZoneChangeRecord = new TimeZoneChangeRecord( /* id= */ 1, new TimeZoneChangeEvent( /* elapsedRealtimeMillis= */ 0, @@ -411,15 +418,16 @@ public class NotifyingTimeZoneChangeListenerTest { /* newZoneId= */ "Europe/Rome", /* newConfidence= */ 1, /* cause= */ "NO_REASON")); - expectedChangeEvent.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN); + expectedTimeZoneChangeRecord.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN); assertNull(mTimeZoneChangeTracker.getLastTimeZoneChangeRecord()); // lastTrackedChangeEvent == null - mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent()); - TimeZoneChangeRecord trackedEvent1 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); + mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent()); + TimeZoneChangeRecord timeZoneChangeRecord1 = + mTimeZoneChangeTracker.getLastTimeZoneChangeRecord(); - assertEquals(expectedChangeEvent, trackedEvent1); + assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord1); // No notification sent for the same UTC offset assertEquals(0, mNotificationManager.getNotifications().size()); mHandler.assertTotalMessagesEnqueued(1); diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index 9a01fa2eb966..44232e7ddfa2 100644 --- a/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java +++ b/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -63,7 +63,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.time.LocationTimeZoneAlgorithmStatus; @@ -94,7 +93,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -114,6 +112,7 @@ public class TimeZoneDetectorStrategyImplTest { @Rule public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule(); + private static final long ARBITRARY_CURRENT_TIME_MILLIS = 1735689600; // 2025-01-01 00:00:00 private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234; /** A time zone used for initialization that does not occur elsewhere in tests. */ private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC"; @@ -1220,7 +1219,7 @@ public class TimeZoneDetectorStrategyImplTest { .build(); Script script = new Script() - .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeClock(ARBITRARY_CURRENT_TIME_MILLIS, ARBITRARY_ELAPSED_REALTIME_MILLIS) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(config) .resetConfigurationTracking(); @@ -1420,7 +1419,7 @@ public class TimeZoneDetectorStrategyImplTest { .build(); Script script = new Script() - .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeClock(ARBITRARY_CURRENT_TIME_MILLIS, ARBITRARY_ELAPSED_REALTIME_MILLIS) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(config) .resetConfigurationTracking(); @@ -1591,7 +1590,7 @@ public class TimeZoneDetectorStrategyImplTest { .build(); Script script = new Script() - .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeClock(ARBITRARY_CURRENT_TIME_MILLIS, ARBITRARY_ELAPSED_REALTIME_MILLIS) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(config) .resetConfigurationTracking(); @@ -1666,7 +1665,7 @@ public class TimeZoneDetectorStrategyImplTest { @Test public void testGetTimeZoneState() { Script script = new Script() - .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeClock(ARBITRARY_CURRENT_TIME_MILLIS, ARBITRARY_ELAPSED_REALTIME_MILLIS) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED) .resetConfigurationTracking(); @@ -1688,7 +1687,7 @@ public class TimeZoneDetectorStrategyImplTest { @Test public void testSetTimeZoneState() { Script script = new Script() - .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeClock(ARBITRARY_CURRENT_TIME_MILLIS, ARBITRARY_ELAPSED_REALTIME_MILLIS) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED) .resetConfigurationTracking(); @@ -1715,7 +1714,7 @@ public class TimeZoneDetectorStrategyImplTest { private void testConfirmTimeZone(ConfigurationInternal config) { String timeZoneId = "Europe/London"; Script script = new Script() - .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeClock(ARBITRARY_CURRENT_TIME_MILLIS, ARBITRARY_ELAPSED_REALTIME_MILLIS) .initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_LOW) .simulateConfigurationInternalChange(config) .resetConfigurationTracking(); @@ -1902,97 +1901,6 @@ public class TimeZoneDetectorStrategyImplTest { mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds)); } - static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment { - - private final TestState<String> mTimeZoneId = new TestState<>(); - private final TestState<Integer> mTimeZoneConfidence = new TestState<>(); - private final List<Runnable> mAsyncRunnables = new ArrayList<>(); - private @ElapsedRealtimeLong long mElapsedRealtimeMillis; - - FakeEnvironment() { - // Ensure the fake environment starts with the defaults a fresh device would. - initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW); - } - - void initializeClock(@ElapsedRealtimeLong long elapsedRealtimeMillis) { - mElapsedRealtimeMillis = elapsedRealtimeMillis; - } - - void initializeTimeZoneSetting(String zoneId, @TimeZoneConfidence int timeZoneConfidence) { - mTimeZoneId.init(zoneId); - mTimeZoneConfidence.init(timeZoneConfidence); - } - - void incrementClock() { - mElapsedRealtimeMillis++; - } - - @Override - public String getDeviceTimeZone() { - return mTimeZoneId.getLatest(); - } - - @Override - public int getDeviceTimeZoneConfidence() { - return mTimeZoneConfidence.getLatest(); - } - - @Override - public void setDeviceTimeZoneAndConfidence( - String zoneId, @TimeZoneConfidence int confidence, String logInfo) { - mTimeZoneId.set(zoneId); - mTimeZoneConfidence.set(confidence); - } - - void assertTimeZoneNotChanged() { - mTimeZoneId.assertHasNotBeenSet(); - mTimeZoneConfidence.assertHasNotBeenSet(); - } - - void assertTimeZoneChangedTo(String timeZoneId, @TimeZoneConfidence int confidence) { - mTimeZoneId.assertHasBeenSet(); - mTimeZoneId.assertChangeCount(1); - mTimeZoneId.assertLatestEquals(timeZoneId); - - mTimeZoneConfidence.assertHasBeenSet(); - mTimeZoneConfidence.assertChangeCount(1); - mTimeZoneConfidence.assertLatestEquals(confidence); - } - - void commitAllChanges() { - mTimeZoneId.commitLatest(); - mTimeZoneConfidence.commitLatest(); - } - - @Override - @ElapsedRealtimeLong - public long elapsedRealtimeMillis() { - return mElapsedRealtimeMillis; - } - - @Override - public void addDebugLogEntry(String logMsg) { - // No-op for tests - } - - @Override - public void dumpDebugLog(PrintWriter printWriter) { - // No-op for tests - } - - @Override - public void runAsync(Runnable runnable) { - mAsyncRunnables.add(runnable); - } - - public void runAsyncRunnables() { - for (Runnable runnable : mAsyncRunnables) { - runnable.run(); - } - mAsyncRunnables.clear(); - } - } - private void assertStateChangeNotificationsSent( TestStateChangeListener stateChangeListener, int expectedCount) { // The fake environment needs to be told to run posted work. @@ -2013,8 +1921,8 @@ public class TimeZoneDetectorStrategyImplTest { return this; } - Script initializeClock(long elapsedRealtimeMillis) { - mFakeEnvironment.initializeClock(elapsedRealtimeMillis); + Script initializeClock(long currentTimeMillis, long elapsedRealtimeMillis) { + mFakeEnvironment.initializeClock(currentTimeMillis, elapsedRealtimeMillis); return this; } |