diff options
author | Neil Fuller <nfuller@google.com> | 2020-01-07 02:14:01 -0800 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2020-01-07 02:14:01 -0800 |
commit | f11a45b756704281486bb92e69909e954576663d (patch) | |
tree | 6cc7cbe49a1361a5cf817f6ec71ce5609ea1f7e5 | |
parent | 75b03696158a07acdc3ad35fdfd4cf818b16ddc3 (diff) | |
parent | 48247d9223fd8c3a1276983df22dccd978176e96 (diff) | |
download | base-f11a45b756704281486bb92e69909e954576663d.tar.gz |
Merge "Change NetworkTimeUpdateServiceImpl behavior"
am: 48247d9223
Change-Id: I299a69b5a3abf5da66237f79aa7ca56b6268452a
14 files changed, 733 insertions, 199 deletions
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl index 9877fc741b7b..de8f4700de2d 100644 --- a/core/java/android/app/timedetector/ITimeDetectorService.aidl +++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl @@ -17,6 +17,7 @@ package android.app.timedetector; import android.app.timedetector.ManualTimeSuggestion; +import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; /** @@ -35,4 +36,5 @@ import android.app.timedetector.PhoneTimeSuggestion; interface ITimeDetectorService { void suggestPhoneTime(in PhoneTimeSuggestion timeSuggestion); void suggestManualTime(in ManualTimeSuggestion timeSuggestion); + void suggestNetworkTime(in NetworkTimeSuggestion timeSuggestion); } diff --git a/core/java/android/app/timedetector/NetworkTimeSuggestion.aidl b/core/java/android/app/timedetector/NetworkTimeSuggestion.aidl new file mode 100644 index 000000000000..731c907f6837 --- /dev/null +++ b/core/java/android/app/timedetector/NetworkTimeSuggestion.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 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.app.timedetector; + +parcelable NetworkTimeSuggestion; diff --git a/core/java/android/app/timedetector/NetworkTimeSuggestion.java b/core/java/android/app/timedetector/NetworkTimeSuggestion.java new file mode 100644 index 000000000000..4c55ba12d881 --- /dev/null +++ b/core/java/android/app/timedetector/NetworkTimeSuggestion.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2019 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.app.timedetector; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.TimestampedValue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * A time signal from a network time source like NTP. The value consists of the number of + * milliseconds elapsed since 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime + * clock when that number was established. The elapsed realtime clock is considered accurate but + * volatile, so time signals must not be persisted across device resets. + * + * @hide + */ +public final class NetworkTimeSuggestion implements Parcelable { + + public static final @NonNull Creator<NetworkTimeSuggestion> CREATOR = + new Creator<NetworkTimeSuggestion>() { + public NetworkTimeSuggestion createFromParcel(Parcel in) { + return NetworkTimeSuggestion.createFromParcel(in); + } + + public NetworkTimeSuggestion[] newArray(int size) { + return new NetworkTimeSuggestion[size]; + } + }; + + @NonNull + private final TimestampedValue<Long> mUtcTime; + @Nullable + private ArrayList<String> mDebugInfo; + + public NetworkTimeSuggestion(@NonNull TimestampedValue<Long> utcTime) { + mUtcTime = Objects.requireNonNull(utcTime); + Objects.requireNonNull(utcTime.getValue()); + } + + private static NetworkTimeSuggestion createFromParcel(Parcel in) { + TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */); + NetworkTimeSuggestion suggestion = new NetworkTimeSuggestion(utcTime); + @SuppressWarnings("unchecked") + ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */); + suggestion.mDebugInfo = debugInfo; + return suggestion; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mUtcTime, 0); + dest.writeList(mDebugInfo); + } + + @NonNull + public TimestampedValue<Long> getUtcTime() { + return mUtcTime; + } + + @NonNull + public List<String> getDebugInfo() { + return mDebugInfo == null + ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo); + } + + /** + * Associates information with the instance that can be useful for debugging / logging. The + * information is present in {@link #toString()} but is not considered for + * {@link #equals(Object)} and {@link #hashCode()}. + */ + public void addDebugInfo(String... debugInfos) { + if (mDebugInfo == null) { + mDebugInfo = new ArrayList<>(); + } + mDebugInfo.addAll(Arrays.asList(debugInfos)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NetworkTimeSuggestion that = (NetworkTimeSuggestion) o; + return Objects.equals(mUtcTime, that.mUtcTime); + } + + @Override + public int hashCode() { + return Objects.hash(mUtcTime); + } + + @Override + public String toString() { + return "NetworkTimeSuggestion{" + + "mUtcTime=" + mUtcTime + + ", mDebugInfo=" + mDebugInfo + + '}'; + } +} diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index 611b66bc0265..af9ece00f491 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -85,4 +85,19 @@ public class TimeDetector { manualTimeSuggestion.addDebugInfo(why); return manualTimeSuggestion; } + + /** + * Suggests the time according to a network time source like NTP. + */ + @RequiresPermission(android.Manifest.permission.SET_TIME) + public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) { + if (DEBUG) { + Log.d(TAG, "suggestNetworkTime called: " + timeSuggestion); + } + try { + mITimeDetectorService.suggestNetworkTime(timeSuggestion); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java index da566c934ef7..524078678460 100644 --- a/core/java/android/util/NtpTrustedTime.java +++ b/core/java/android/util/NtpTrustedTime.java @@ -175,4 +175,22 @@ public class NtpTrustedTime implements TrustedTime { public long getCachedNtpTimeReference() { return mCachedNtpElapsedRealtime; } + + /** + * Returns the combination of {@link #getCachedNtpTime()} and {@link + * #getCachedNtpTimeReference()} as a {@link TimestampedValue}. This method is useful when + * passing the time to another component that will adjust for elapsed time. + * + * @throws IllegalStateException if there is no cached value + */ + @UnsupportedAppUsage + public TimestampedValue<Long> getCachedNtpTimeSignal() { + if (!mHasCache) { + throw new IllegalStateException("Missing authoritative time source"); + } + if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit"); + + return new TimestampedValue<>(mCachedNtpElapsedRealtime, mCachedNtpTime); + } + } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index a404e2ed6f52..04d1eef9cba1 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2266,9 +2266,6 @@ <!-- Number of times to try again with the shorter interval, before backing off until the normal polling interval. A value < 0 indicates infinite. --> <integer name="config_ntpRetry">3</integer> - <!-- If the time difference is greater than this threshold in milliseconds, - then update the time. --> - <integer name="config_ntpThreshold">5000</integer> <!-- Timeout to wait for NTP server response in milliseconds. --> <integer name="config_ntpTimeout">5000</integer> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2507787e2847..383fcd4753f0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -489,7 +489,6 @@ <java-symbol type="integer" name="config_ntpPollingInterval" /> <java-symbol type="integer" name="config_ntpPollingIntervalShorter" /> <java-symbol type="integer" name="config_ntpRetry" /> - <java-symbol type="integer" name="config_ntpThreshold" /> <java-symbol type="integer" name="config_ntpTimeout" /> <java-symbol type="integer" name="config_shortPressOnPowerBehavior" /> <java-symbol type="integer" name="config_toastDefaultGravity" /> diff --git a/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java new file mode 100644 index 000000000000..9b3d0c9eaff6 --- /dev/null +++ b/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2019 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.app.timedetector; + +import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; +import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.util.TimestampedValue; + +import org.junit.Test; + +public class NetworkTimeSuggestionTest { + + private static final TimestampedValue<Long> ARBITRARY_TIME = + new TimestampedValue<>(1111L, 2222L); + + @Test + public void testEquals() { + NetworkTimeSuggestion one = new NetworkTimeSuggestion(ARBITRARY_TIME); + assertEquals(one, one); + + NetworkTimeSuggestion two = new NetworkTimeSuggestion(ARBITRARY_TIME); + assertEquals(one, two); + assertEquals(two, one); + + TimestampedValue<Long> differentTime = new TimestampedValue<>( + ARBITRARY_TIME.getReferenceTimeMillis() + 1, + ARBITRARY_TIME.getValue()); + NetworkTimeSuggestion three = new NetworkTimeSuggestion(differentTime); + assertNotEquals(one, three); + assertNotEquals(three, one); + + // DebugInfo must not be considered in equals(). + one.addDebugInfo("Debug info 1"); + two.addDebugInfo("Debug info 2"); + assertEquals(one, two); + } + + @Test + public void testParcelable() { + NetworkTimeSuggestion suggestion = new NetworkTimeSuggestion(ARBITRARY_TIME); + assertRoundTripParcelable(suggestion); + + // DebugInfo should also be stored (but is not checked by equals() + suggestion.addDebugInfo("This is debug info"); + NetworkTimeSuggestion rtSuggestion = roundTripParcelable(suggestion); + assertEquals(suggestion.getDebugInfo(), rtSuggestion.getDebugInfo()); + } +} diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java index 39be311e902d..cfe56052118f 100644 --- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java +++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java @@ -18,6 +18,8 @@ package com.android.server; import android.app.AlarmManager; import android.app.PendingIntent; +import android.app.timedetector.NetworkTimeSuggestion; +import android.app.timedetector.TimeDetector; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -35,10 +37,10 @@ import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; import android.provider.Settings; -import android.telephony.TelephonyManager; import android.util.Log; import android.util.NtpTrustedTime; import android.util.TimeUtils; +import android.util.TimestampedValue; import com.android.internal.util.DumpUtils; @@ -46,21 +48,19 @@ import java.io.FileDescriptor; import java.io.PrintWriter; /** - * Monitors the network time and updates the system time if it is out of sync - * and there hasn't been any NITZ update from the carrier recently. - * If looking up the network time fails for some reason, it tries a few times with a short - * interval and then resets to checking on longer intervals. - * <p> - * If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't - * available. - * </p> + * Monitors the network time. If looking up the network time fails for some reason, it tries a few + * times with a short interval and then resets to checking on longer intervals. + * + * <p>When available, the time is always suggested to the {@link + * com.android.server.timedetector.TimeDetectorService} where it may be used to set the device + * system clock, depending on user settings and what other signals are available. */ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeUpdateService { private static final String TAG = "NetworkTimeUpdateService"; private static final boolean DBG = false; - private static final int EVENT_AUTO_TIME_CHANGED = 1; + private static final int EVENT_AUTO_TIME_ENABLED = 1; private static final int EVENT_POLL_NETWORK_TIME = 2; private static final int EVENT_NETWORK_CHANGED = 3; @@ -69,20 +69,19 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU private static final int POLL_REQUEST = 0; - private static final long NOT_SET = -1; - private long mNitzTimeSetTime = NOT_SET; private Network mDefaultNetwork = null; private final Context mContext; private final NtpTrustedTime mTime; private final AlarmManager mAlarmManager; + private final TimeDetector mTimeDetector; private final ConnectivityManager mCM; private final PendingIntent mPendingPollIntent; private final PowerManager.WakeLock mWakeLock; // NTP lookup is done on this thread and handler private Handler mHandler; - private SettingsObserver mSettingsObserver; + private AutoTimeSettingObserver mAutoTimeSettingObserver; private NetworkTimeUpdateCallback mNetworkTimeUpdateCallback; // Normal polling frequency @@ -91,8 +90,6 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU private final long mPollingIntervalShorterMs; // Number of times to try again private final int mTryAgainTimesMax; - // If the time difference is greater than this threshold, then update the time. - private final int mTimeErrorThresholdMs; // Keeps track of how many quick attempts were made to fetch NTP time. // During bootup, the network may not have been up yet, or it's taking time for the // connection to happen. @@ -102,6 +99,7 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU mContext = context; mTime = NtpTrustedTime.getInstance(context); mAlarmManager = mContext.getSystemService(AlarmManager.class); + mTimeDetector = mContext.getSystemService(TimeDetector.class); mCM = mContext.getSystemService(ConnectivityManager.class); Intent pollIntent = new Intent(ACTION_POLL, null); @@ -113,8 +111,6 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU com.android.internal.R.integer.config_ntpPollingIntervalShorter); mTryAgainTimesMax = mContext.getResources().getInteger( com.android.internal.R.integer.config_ntpRetry); - mTimeErrorThresholdMs = mContext.getResources().getInteger( - com.android.internal.R.integer.config_ntpThreshold); mWakeLock = context.getSystemService(PowerManager.class).newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, TAG); @@ -122,7 +118,6 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU @Override public void systemRunning() { - registerForTelephonyIntents(); registerForAlarms(); HandlerThread thread = new HandlerThread(TAG); @@ -131,14 +126,9 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback(); mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler); - mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED); - mSettingsObserver.observe(mContext); - } - - private void registerForTelephonyIntents() { - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(TelephonyManager.ACTION_NETWORK_SET_TIME); - mContext.registerReceiver(mNitzReceiver, intentFilter); + mAutoTimeSettingObserver = new AutoTimeSettingObserver(mContext, mHandler, + EVENT_AUTO_TIME_ENABLED); + mAutoTimeSettingObserver.observe(); } private void registerForAlarms() { @@ -152,8 +142,7 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU } private void onPollNetworkTime(int event) { - // If Automatic time is not set, don't bother. Similarly, if we don't - // have any default network, don't bother. + // If we don't have any default network, don't bother. if (mDefaultNetwork == null) return; mWakeLock.acquire(); try { @@ -173,10 +162,12 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU if (mTime.getCacheAge() < mPollingIntervalMs) { // Obtained fresh fix; schedule next normal update resetAlarm(mPollingIntervalMs); - if (isAutomaticTimeRequested()) { - updateSystemClock(event); - } + // Suggest the time to the time detector. It may choose use it to set the system clock. + TimestampedValue<Long> timeSignal = mTime.getCachedNtpTimeSignal(); + NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal); + timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateServiceImpl. event=" + event); + mTimeDetector.suggestNetworkTime(timeSuggestion); } else { // No fresh fix; schedule retry mTryAgainCounter++; @@ -190,36 +181,6 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU } } - private long getNitzAge() { - if (mNitzTimeSetTime == NOT_SET) { - return Long.MAX_VALUE; - } else { - return SystemClock.elapsedRealtime() - mNitzTimeSetTime; - } - } - - /** - * Consider updating system clock based on current NTP fix, if requested by - * user, significant enough delta, and we don't have a recent NITZ. - */ - private void updateSystemClock(int event) { - final boolean forceUpdate = (event == EVENT_AUTO_TIME_CHANGED); - if (!forceUpdate) { - if (getNitzAge() < mPollingIntervalMs) { - if (DBG) Log.d(TAG, "Ignoring NTP update due to recent NITZ"); - return; - } - - final long skew = Math.abs(mTime.currentTimeMillis() - System.currentTimeMillis()); - if (skew < mTimeErrorThresholdMs) { - if (DBG) Log.d(TAG, "Ignoring NTP update due to low skew"); - return; - } - } - - SystemClock.setCurrentTimeMillis(mTime.currentTimeMillis()); - } - /** * Cancel old alarm and starts a new one for the specified interval. * @@ -232,27 +193,6 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent); } - /** - * Checks if the user prefers to automatically set the time. - */ - private boolean isAutomaticTimeRequested() { - return Settings.Global.getInt( - mContext.getContentResolver(), Settings.Global.AUTO_TIME, 0) != 0; - } - - /** Receiver for Nitz time events */ - private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (DBG) Log.d(TAG, "Received " + action); - if (TelephonyManager.ACTION_NETWORK_SET_TIME.equals(action)) { - mNitzTimeSetTime = SystemClock.elapsedRealtime(); - } - } - }; - /** Handler to do the network accesses on */ private class MyHandler extends Handler { @@ -263,7 +203,7 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU @Override public void handleMessage(Message msg) { switch (msg.what) { - case EVENT_AUTO_TIME_CHANGED: + case EVENT_AUTO_TIME_ENABLED: case EVENT_POLL_NETWORK_TIME: case EVENT_NETWORK_CHANGED: onPollNetworkTime(msg.what); @@ -287,27 +227,42 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU } } - /** Observer to watch for changes to the AUTO_TIME setting */ - private static class SettingsObserver extends ContentObserver { + /** + * Observer to watch for changes to the AUTO_TIME setting. It only triggers when the setting + * is enabled. + */ + private static class AutoTimeSettingObserver extends ContentObserver { - private int mMsg; - private Handler mHandler; + private final Context mContext; + private final int mMsg; + private final Handler mHandler; - SettingsObserver(Handler handler, int msg) { + AutoTimeSettingObserver(Context context, Handler handler, int msg) { super(handler); + mContext = context; mHandler = handler; mMsg = msg; } - void observe(Context context) { - ContentResolver resolver = context.getContentResolver(); + void observe() { + ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME), false, this); } @Override public void onChange(boolean selfChange) { - mHandler.obtainMessage(mMsg).sendToTarget(); + if (isAutomaticTimeEnabled()) { + mHandler.obtainMessage(mMsg).sendToTarget(); + } + } + + /** + * Checks if the user prefers to automatically set the time. + */ + private boolean isAutomaticTimeEnabled() { + ContentResolver resolver = mContext.getContentResolver(); + return Settings.Global.getInt(resolver, Settings.Global.AUTO_TIME, 0) != 0; } } @@ -319,8 +274,6 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU pw.print("\nPollingIntervalShorterMs: "); TimeUtils.formatDuration(mPollingIntervalShorterMs, pw); pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax); - pw.print("TimeErrorThresholdMs: "); - TimeUtils.formatDuration(mTimeErrorThresholdMs, pw); pw.println("\nTryAgainCounter: " + mTryAgainCounter); pw.println("NTP cache age: " + mTime.getCacheAge()); pw.println("NTP cache certainty: " + mTime.getCacheCertainty()); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 668630e06cf0..b7d63609cff9 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.timedetector.ITimeDetectorService; import android.app.timedetector.ManualTimeSuggestion; +import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; import android.content.ContentResolver; import android.content.Context; @@ -105,6 +106,14 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { mHandler.post(() -> mTimeDetectorStrategy.suggestManualTime(timeSignal)); } + @Override + public void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSignal) { + enforceSuggestNetworkTimePermission(); + Objects.requireNonNull(timeSignal); + + mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(timeSignal)); + } + @VisibleForTesting public void handleAutoTimeDetectionToggle() { mHandler.post(mTimeDetectorStrategy::handleAutoTimeDetectionChanged); @@ -129,4 +138,10 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE, "suggest manual time and time zone"); } + + private void enforceSuggestNetworkTimePermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.SET_TIME, + "set time"); + } } diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index 0a6c2e776072..b4f4eca5f188 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -19,6 +19,7 @@ package com.android.server.timedetector; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.timedetector.ManualTimeSuggestion; +import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; import android.content.Intent; import android.util.TimestampedValue; @@ -86,6 +87,9 @@ public interface TimeDetectorStrategy { /** Process the suggested manually entered time. */ void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion); + /** Process the suggested time from network sources. */ + void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSuggestion); + /** Handle the auto-time setting being toggled on or off. */ void handleAutoTimeDetectionChanged(); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index e30ac8aa0b0c..02656eaf5c6c 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AlarmManager; import android.app.timedetector.ManualTimeSuggestion; +import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; import android.content.Intent; import android.telephony.TelephonyManager; @@ -32,6 +33,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.timezonedetector.ArrayMapWithHistory; +import com.android.server.timezonedetector.ReferenceWithHistory; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -56,11 +58,11 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { /** Each bucket is this size. All buckets are equally sized. */ @VisibleForTesting static final int PHONE_BUCKET_SIZE_MILLIS = 60 * 60 * 1000; - /** Phone suggestions older than this value are considered too old. */ + /** Phone and network suggestions older than this value are considered too old to be used. */ @VisibleForTesting - static final long PHONE_MAX_AGE_MILLIS = PHONE_BUCKET_COUNT * PHONE_BUCKET_SIZE_MILLIS; + static final long MAX_UTC_TIME_AGE_MILLIS = PHONE_BUCKET_COUNT * PHONE_BUCKET_SIZE_MILLIS; - @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL }) + @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL, ORIGIN_NETWORK }) @Retention(RetentionPolicy.SOURCE) public @interface Origin {} @@ -72,6 +74,10 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { @Origin private static final int ORIGIN_MANUAL = 2; + /** Used when a time value originated from a network signal. */ + @Origin + private static final int ORIGIN_NETWORK = 3; + /** * CLOCK_PARANOIA: The maximum difference allowed between the expected system clock time and the * actual system clock time before a warning is logged. Used to help identify situations where @@ -101,9 +107,13 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { * will have a small number of telephony devices and phoneIds are assumed to be stable. */ @GuardedBy("this") - private ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionByPhoneId = + private final ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionByPhoneId = new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); + @GuardedBy("this") + private final ReferenceWithHistory<NetworkTimeSuggestion> mLastNetworkSuggestion = + new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); + @Override public void initialize(@NonNull Callback callback) { mCallback = callback; @@ -122,6 +132,19 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } @Override + public synchronized void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSuggestion) { + if (!validateSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) { + return; + } + mLastNetworkSuggestion.set(timeSuggestion); + + // Now perform auto time detection. The new suggestion may be used to modify the system + // clock. + String reason = "New network time suggested. timeSuggestion=" + timeSuggestion; + doAutoTimeDetection(reason); + } + + @Override public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { // Empty time suggestion means that telephony network connectivity has been lost. // The passage of time is relentless, and we don't expect our users to use a time machine, @@ -184,6 +207,11 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { mSuggestionByPhoneId.dump(ipw); ipw.decreaseIndent(); // level 2 + ipw.println("Network suggestion history:"); + ipw.increaseIndent(); // level 2 + mLastNetworkSuggestion.dump(ipw); + ipw.decreaseIndent(); // level 2 + ipw.decreaseIndent(); // level 1 ipw.flush(); } @@ -253,23 +281,34 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { return; } + // Android devices currently prioritize any telephony over network signals. There are + // carrier compliance tests that would need to be changed before we could ignore NITZ or + // prefer NTP generally. This check is cheap on devices without phone hardware. PhoneTimeSuggestion bestPhoneSuggestion = findBestPhoneSuggestion(); + if (bestPhoneSuggestion != null) { + final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime(); + String cause = "Found good phone suggestion." + + ", bestPhoneSuggestion=" + bestPhoneSuggestion + + ", detectionReason=" + detectionReason; + setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause); + return; + } - // Work out what to do with the best suggestion. - if (bestPhoneSuggestion == null) { - // There is no good phone suggestion. - if (DBG) { - Slog.d(LOG_TAG, "Could not determine time: No best phone suggestion." - + " detectionReason=" + detectionReason); - } + // There is no good phone suggestion, try network. + NetworkTimeSuggestion networkSuggestion = findLatestValidNetworkSuggestion(); + if (networkSuggestion != null) { + final TimestampedValue<Long> newUtcTime = networkSuggestion.getUtcTime(); + String cause = "Found good network suggestion." + + ", networkSuggestion=" + networkSuggestion + + ", detectionReason=" + detectionReason; + setSystemClockIfRequired(ORIGIN_NETWORK, newUtcTime, cause); return; } - final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime(); - String cause = "Found good suggestion." - + ", bestPhoneSuggestion=" + bestPhoneSuggestion - + ", detectionReason=" + detectionReason; - setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause); + if (DBG) { + Slog.d(LOG_TAG, "Could not determine time: No best phone or network suggestion." + + " detectionReason=" + detectionReason); + } } @GuardedBy("this") @@ -348,37 +387,50 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { private static int scorePhoneSuggestion( long elapsedRealtimeMillis, @NonNull PhoneTimeSuggestion timeSuggestion) { - // The score is based on the age since receipt. Suggestions are bucketed so two - // suggestions in the same bucket from different phoneIds are scored the same. + + // Validate first. TimestampedValue<Long> utcTime = timeSuggestion.getUtcTime(); - long referenceTimeMillis = utcTime.getReferenceTimeMillis(); - if (referenceTimeMillis > elapsedRealtimeMillis) { - // Future times are ignored. They imply the reference time was wrong, or the elapsed - // realtime clock has gone backwards, neither of which are supportable situations. - Slog.w(LOG_TAG, "Existing suggestion found to be in the future. " + if (!validateSuggestionUtcTime(elapsedRealtimeMillis, utcTime)) { + Slog.w(LOG_TAG, "Existing suggestion found to be invalid " + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + ", timeSuggestion=" + timeSuggestion); return PHONE_INVALID_SCORE; } - long ageMillis = elapsedRealtimeMillis - referenceTimeMillis; + // The score is based on the age since receipt. Suggestions are bucketed so two + // suggestions in the same bucket from different phoneIds are scored the same. + long ageMillis = elapsedRealtimeMillis - utcTime.getReferenceTimeMillis(); - // Any suggestion > MAX_AGE_MILLIS is treated as too old. Although time is relentless and - // predictable, the accuracy of the reference time clock may be poor over long periods which - // would lead to errors creeping in. Also, in edge cases where a bad suggestion has been - // made and never replaced, it could also mean that the time detection code remains - // opinionated using a bad invalid suggestion. This caps that edge case at MAX_AGE_MILLIS. - if (ageMillis > PHONE_MAX_AGE_MILLIS) { + // Turn the age into a discrete value: 0 <= bucketIndex < PHONE_BUCKET_COUNT. + int bucketIndex = (int) (ageMillis / PHONE_BUCKET_SIZE_MILLIS); + if (bucketIndex >= PHONE_BUCKET_COUNT) { return PHONE_INVALID_SCORE; } - // Turn the age into a discrete value: 0 <= bucketIndex < MAX_AGE_HOURS. - int bucketIndex = (int) (ageMillis / PHONE_BUCKET_SIZE_MILLIS); - // We want the lowest bucket index to have the highest score. 0 > score >= BUCKET_COUNT. return PHONE_BUCKET_COUNT - bucketIndex; } + /** Returns the latest, valid, network suggestion. Returns {@code null} if there isn't one. */ + @GuardedBy("this") + @Nullable + private NetworkTimeSuggestion findLatestValidNetworkSuggestion() { + NetworkTimeSuggestion networkSuggestion = mLastNetworkSuggestion.get(); + if (networkSuggestion == null) { + // No network suggestions received. This is normal if there's no connectivity. + return null; + } + + TimestampedValue<Long> utcTime = networkSuggestion.getUtcTime(); + long elapsedRealTimeMillis = mCallback.elapsedRealtimeMillis(); + if (!validateSuggestionUtcTime(elapsedRealTimeMillis, utcTime)) { + // The latest suggestion is not valid, usually due to its age. + return null; + } + + return networkSuggestion; + } + @GuardedBy("this") private void setSystemClockIfRequired( @Origin int origin, @NonNull TimestampedValue<Long> time, @NonNull String cause) { @@ -415,7 +467,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } private static boolean isOriginAutomatic(@Origin int origin) { - return origin == ORIGIN_PHONE; + return origin != ORIGIN_MANUAL; } @GuardedBy("this") @@ -507,6 +559,16 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } /** + * Returns the latest valid network suggestion. Not intended for general use: it is used during + * tests to check strategy behavior. + */ + @VisibleForTesting + @Nullable + public NetworkTimeSuggestion findLatestValidNetworkSuggestionForTests() { + return findLatestValidNetworkSuggestion(); + } + + /** * A method used to inspect state during tests. Not intended for general use. */ @VisibleForTesting @@ -514,4 +576,32 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) { return mSuggestionByPhoneId.get(phoneId); } + + /** + * A method used to inspect state during tests. Not intended for general use. + */ + @VisibleForTesting + @Nullable + public NetworkTimeSuggestion getLatestNetworkSuggestion() { + return mLastNetworkSuggestion.get(); + } + + private static boolean validateSuggestionUtcTime( + long elapsedRealtimeMillis, TimestampedValue<Long> utcTime) { + long referenceTimeMillis = utcTime.getReferenceTimeMillis(); + if (referenceTimeMillis > elapsedRealtimeMillis) { + // Future reference times are ignored. They imply the reference time was wrong, or the + // elapsed realtime clock used to derive it has gone backwards, neither of which are + // supportable situations. + return false; + } + + // Any suggestion > MAX_AGE_MILLIS is treated as too old. Although time is relentless and + // predictable, the accuracy of the reference time clock may be poor over long periods which + // would lead to errors creeping in. Also, in edge cases where a bad suggestion has been + // made and never replaced, it could also mean that the time detection code remains + // opinionated using a bad invalid suggestion. This caps that edge case at MAX_AGE_MILLIS. + long ageMillis = elapsedRealtimeMillis - referenceTimeMillis; + return ageMillis <= MAX_UTC_TIME_AGE_MILLIS; + } } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index 3456cc361eb4..71b568cc06c5 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.timedetector.ManualTimeSuggestion; +import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; import android.content.Context; import android.content.pm.PackageManager; @@ -143,6 +144,36 @@ public class TimeDetectorServiceTest { mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion); } + @Test(expected = SecurityException.class) + public void testSuggestNetworkTime_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any()); + NetworkTimeSuggestion NetworkTimeSuggestion = createNetworkTimeSuggestion(); + + try { + mTimeDetectorService.suggestNetworkTime(NetworkTimeSuggestion); + fail(); + } finally { + verify(mMockContext).enforceCallingOrSelfPermission( + eq(android.Manifest.permission.SET_TIME), anyString()); + } + } + + @Test + public void testSuggestNetworkTime() throws Exception { + doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any()); + + NetworkTimeSuggestion NetworkTimeSuggestion = createNetworkTimeSuggestion(); + mTimeDetectorService.suggestNetworkTime(NetworkTimeSuggestion); + mTestHandler.assertTotalMessagesEnqueued(1); + + verify(mMockContext).enforceCallingOrSelfPermission( + eq(android.Manifest.permission.SET_TIME), anyString()); + + mTestHandler.waitForEmptyQueue(); + mStubbedTimeDetectorStrategy.verifySuggestNetworkTimeCalled(NetworkTimeSuggestion); + } + @Test public void testDump() { when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)) @@ -180,11 +211,17 @@ public class TimeDetectorServiceTest { return new ManualTimeSuggestion(timeValue); } + private static NetworkTimeSuggestion createNetworkTimeSuggestion() { + TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L); + return new NetworkTimeSuggestion(timeValue); + } + private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy { // Call tracking. private PhoneTimeSuggestion mLastPhoneSuggestion; private ManualTimeSuggestion mLastManualSuggestion; + private NetworkTimeSuggestion mLastNetworkSuggestion; private boolean mLastAutoTimeDetectionToggleCalled; private boolean mDumpCalled; @@ -205,6 +242,12 @@ public class TimeDetectorServiceTest { } @Override + public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) { + resetCallTracking(); + mLastNetworkSuggestion = timeSuggestion; + } + + @Override public void handleAutoTimeDetectionChanged() { resetCallTracking(); mLastAutoTimeDetectionToggleCalled = true; @@ -219,6 +262,7 @@ public class TimeDetectorServiceTest { void resetCallTracking() { mLastPhoneSuggestion = null; mLastManualSuggestion = null; + mLastNetworkSuggestion = null; mLastAutoTimeDetectionToggleCalled = false; mDumpCalled = false; } @@ -231,6 +275,10 @@ public class TimeDetectorServiceTest { assertEquals(expectedSuggestion, mLastManualSuggestion); } + public void verifySuggestNetworkTimeCalled(NetworkTimeSuggestion expectedSuggestion) { + assertEquals(expectedSuggestion, mLastNetworkSuggestion); + } + void verifyHandleAutoTimeDetectionToggleCalled() { assertTrue(mLastAutoTimeDetectionToggleCalled); } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index 1aa3d8fe654b..ca6fd08092df 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.timedetector.ManualTimeSuggestion; +import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; import android.content.Intent; import android.icu.util.Calendar; @@ -45,14 +46,16 @@ public class TimeDetectorStrategyImplTest { private static final TimestampedValue<Long> ARBITRARY_CLOCK_INITIALIZATION_INFO = new TimestampedValue<>( 123456789L /* realtimeClockMillis */, - createUtcTime(1977, 1, 1, 12, 0, 0)); + createUtcTime(2008, 5, 23, 12, 0, 0)); + /** + * An arbitrary time, very different from the {@link #ARBITRARY_CLOCK_INITIALIZATION_INFO} + * time. Can be used as the basis for time suggestions. + */ private static final long ARBITRARY_TEST_TIME_MILLIS = createUtcTime(2018, 1, 1, 12, 0, 0); private static final int ARBITRARY_PHONE_ID = 123456; - private static final long ONE_DAY_MILLIS = Duration.ofDays(1).toMillis(); - private Script mScript; @Before @@ -67,15 +70,16 @@ public class TimeDetectorStrategyImplTest { int phoneId = ARBITRARY_PHONE_ID; long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; + PhoneTimeSuggestion timeSuggestion = mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); - int clockIncrement = 1000; - long expectedSystemClockMillis = testTimeMillis + clockIncrement; + mScript.simulateTimePassing() + .simulatePhoneTimeSuggestion(timeSuggestion); - mScript.simulateTimePassing(clockIncrement) - .simulatePhoneTimeSuggestion(timeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, true /* expectNetworkBroadcast */) + long expectedSystemClockMillis = + mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime()); + mScript.verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis, true /* expectNetworkBroadcast */) .assertLatestPhoneSuggestion(phoneId, timeSuggestion); } @@ -94,26 +98,24 @@ public class TimeDetectorStrategyImplTest { @Test public void testSuggestPhoneTime_systemClockThreshold() { - int systemClockUpdateThresholdMillis = 1000; + final int systemClockUpdateThresholdMillis = 1000; + final int clockIncrementMillis = 100; mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeThresholds(systemClockUpdateThresholdMillis) .pokeAutoTimeDetectionEnabled(true); - final int clockIncrement = 100; int phoneId = ARBITRARY_PHONE_ID; // Send the first time signal. It should be used. { - long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; PhoneTimeSuggestion timeSuggestion1 = - mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); - TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); + mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS); // Increment the the device clocks to simulate the passage of time. - mScript.simulateTimePassing(clockIncrement); + mScript.simulateTimePassing(clockIncrementMillis); long expectedSystemClockMillis1 = - TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis()); + mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime()); mScript.simulatePhoneTimeSuggestion(timeSuggestion1) .verifySystemClockWasSetAndResetCallTracking( @@ -127,7 +129,7 @@ public class TimeDetectorStrategyImplTest { int underThresholdMillis = systemClockUpdateThresholdMillis - 1; PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion( phoneId, mScript.peekSystemClockMillis() + underThresholdMillis); - mScript.simulateTimePassing(clockIncrement) + mScript.simulateTimePassing(clockIncrementMillis) .simulatePhoneTimeSuggestion(timeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestPhoneSuggestion(phoneId, timeSuggestion2); @@ -138,11 +140,10 @@ public class TimeDetectorStrategyImplTest { PhoneTimeSuggestion timeSuggestion3 = mScript.generatePhoneTimeSuggestion( phoneId, mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis); - mScript.simulateTimePassing(clockIncrement); + mScript.simulateTimePassing(clockIncrementMillis); long expectedSystemClockMillis3 = - TimeDetectorStrategy.getTimeAt(timeSuggestion3.getUtcTime(), - mScript.peekElapsedRealtimeMillis()); + mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime()); mScript.simulatePhoneTimeSuggestion(timeSuggestion3) .verifySystemClockWasSetAndResetCallTracking( @@ -162,17 +163,16 @@ public class TimeDetectorStrategyImplTest { int phone1Id = ARBITRARY_PHONE_ID; int phone2Id = ARBITRARY_PHONE_ID + 1; long phone1TimeMillis = ARBITRARY_TEST_TIME_MILLIS; - long phone2TimeMillis = phone1TimeMillis + 60000; - - final int clockIncrement = 999; + long phone2TimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(1).toMillis(); // Make a suggestion with phone2Id. { PhoneTimeSuggestion phone2TimeSuggestion = mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); - mScript.simulateTimePassing(clockIncrement); + mScript.simulateTimePassing(); - long expectedSystemClockMillis = phone2TimeMillis + clockIncrement; + long expectedSystemClockMillis = + mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) .verifySystemClockWasSetAndResetCallTracking( @@ -181,15 +181,16 @@ public class TimeDetectorStrategyImplTest { .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); } - mScript.simulateTimePassing(clockIncrement); + mScript.simulateTimePassing(); // Now make a different suggestion with phone1Id. { PhoneTimeSuggestion phone1TimeSuggestion = mScript.generatePhoneTimeSuggestion(phone1Id, phone1TimeMillis); - mScript.simulateTimePassing(clockIncrement); + mScript.simulateTimePassing(); - long expectedSystemClockMillis = phone1TimeMillis + clockIncrement; + long expectedSystemClockMillis = + mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion) .verifySystemClockWasSetAndResetCallTracking( @@ -198,14 +199,14 @@ public class TimeDetectorStrategyImplTest { } - mScript.simulateTimePassing(clockIncrement); + mScript.simulateTimePassing(); // Make another suggestion with phone2Id. It should be stored but not used because the // phone1Id suggestion will still "win". { PhoneTimeSuggestion phone2TimeSuggestion = mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); - mScript.simulateTimePassing(clockIncrement); + mScript.simulateTimePassing(); mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() @@ -220,9 +221,10 @@ public class TimeDetectorStrategyImplTest { { PhoneTimeSuggestion phone2TimeSuggestion = mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); - mScript.simulateTimePassing(clockIncrement); + mScript.simulateTimePassing(); - long expectedSystemClockMillis = phone2TimeMillis + clockIncrement; + long expectedSystemClockMillis = + mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) .verifySystemClockWasSetAndResetCallTracking( @@ -239,7 +241,7 @@ public class TimeDetectorStrategyImplTest { int phoneId = ARBITRARY_PHONE_ID; PhoneTimeSuggestion timeSuggestion = mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS); - mScript.simulateTimePassing(1000) + mScript.simulateTimePassing() .simulatePhoneTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() .assertLatestPhoneSuggestion(phoneId, timeSuggestion); @@ -260,9 +262,8 @@ public class TimeDetectorStrategyImplTest { TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); // Initialize the strategy / device with a time set from a phone suggestion. - mScript.simulateTimePassing(100); - long expectedSystemClockMillis1 = - TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis()); + mScript.simulateTimePassing(); + long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1); mScript.simulatePhoneTimeSuggestion(timeSuggestion1) .verifySystemClockWasSetAndResetCallTracking( expectedSystemClockMillis1, true /* expectNetworkBroadcast */) @@ -299,8 +300,7 @@ public class TimeDetectorStrategyImplTest { long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100; TimestampedValue<Long> utcTime4 = new TimestampedValue<>( validReferenceTimeMillis, validUtcTimeMillis); - long expectedSystemClockMillis4 = - TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis()); + long expectedSystemClockMillis4 = mScript.calculateTimeInMillisForNow(utcTime4); PhoneTimeSuggestion timeSuggestion4 = createPhoneTimeSuggestion(phoneId, utcTime4); mScript.simulatePhoneTimeSuggestion(timeSuggestion4) @@ -335,8 +335,7 @@ public class TimeDetectorStrategyImplTest { // Simulate more time passing. mScript.simulateTimePassing(clockIncrementMillis); - long expectedSystemClockMillis1 = - TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis()); + long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1); // Turn on auto time detection. mScript.simulateAutoTimeDetectionToggle() @@ -357,8 +356,8 @@ public class TimeDetectorStrategyImplTest { // Simulate more time passing. mScript.simulateTimePassing(clockIncrementMillis); - long expectedSystemClockMillis2 = TimeDetectorStrategy.getTimeAt( - timeSuggestion2.getUtcTime(), mScript.peekElapsedRealtimeMillis()); + long expectedSystemClockMillis2 = + mScript.calculateTimeInMillisForNow(timeSuggestion2.getUtcTime()); // The new time, though valid, should not be set in the system clock because auto time is // disabled. @@ -382,19 +381,21 @@ public class TimeDetectorStrategyImplTest { long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; PhoneTimeSuggestion phoneSuggestion = mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); - int clockIncrementMillis = 1000; - mScript.simulateTimePassing(clockIncrementMillis) - .simulatePhoneTimeSuggestion(phoneSuggestion) + mScript.simulateTimePassing(); + + long expectedSystemClockMillis = + mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime()); + mScript.simulatePhoneTimeSuggestion(phoneSuggestion) .verifySystemClockWasSetAndResetCallTracking( - testTimeMillis + clockIncrementMillis, true /* expectedNetworkBroadcast */) + expectedSystemClockMillis, true /* expectedNetworkBroadcast */) .assertLatestPhoneSuggestion(phoneId, phoneSuggestion); // Look inside and check what the strategy considers the current best phone suggestion. assertEquals(phoneSuggestion, mScript.peekBestPhoneSuggestion()); // Simulate time passing, long enough that phoneSuggestion is now too old. - mScript.simulateTimePassing(TimeDetectorStrategyImpl.PHONE_MAX_AGE_MILLIS); + mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS); // Look inside and check what the strategy considers the current best phone suggestion. It // should still be the, it's just no longer used. @@ -407,13 +408,14 @@ public class TimeDetectorStrategyImplTest { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(false); - long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; - ManualTimeSuggestion timeSuggestion = mScript.generateManualTimeSuggestion(testTimeMillis); - final int clockIncrement = 1000; - long expectedSystemClockMillis = testTimeMillis + clockIncrement; + ManualTimeSuggestion timeSuggestion = + mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS); - mScript.simulateTimePassing(clockIncrement) - .simulateManualTimeSuggestion(timeSuggestion) + mScript.simulateTimePassing(); + + long expectedSystemClockMillis = + mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime()); + mScript.simulateManualTimeSuggestion(timeSuggestion) .verifySystemClockWasSetAndResetCallTracking( expectedSystemClockMillis, false /* expectNetworkBroadcast */); } @@ -430,21 +432,19 @@ public class TimeDetectorStrategyImplTest { long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; PhoneTimeSuggestion phoneTimeSuggestion = mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); - long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue(); - final int clockIncrement = 1000; // Simulate the passage of time. - mScript.simulateTimePassing(clockIncrement); - expectedAutoClockMillis += clockIncrement; + mScript.simulateTimePassing(); + long expectedAutoClockMillis = + mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking( expectedAutoClockMillis, true /* expectNetworkBroadcast */) .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); // Simulate the passage of time. - mScript.simulateTimePassing(clockIncrement); - expectedAutoClockMillis += clockIncrement; + mScript.simulateTimePassing(); // Switch to manual. mScript.simulateAutoTimeDetectionToggle() @@ -452,26 +452,29 @@ public class TimeDetectorStrategyImplTest { .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); // Simulate the passage of time. - mScript.simulateTimePassing(clockIncrement); - expectedAutoClockMillis += clockIncrement; + mScript.simulateTimePassing(); // Simulate a manual suggestion 1 day different from the auto suggestion. - long manualTimeMillis = testTimeMillis + ONE_DAY_MILLIS; - long expectedManualClockMillis = manualTimeMillis; + long manualTimeMillis = testTimeMillis + Duration.ofDays(1).toMillis(); ManualTimeSuggestion manualTimeSuggestion = mScript.generateManualTimeSuggestion(manualTimeMillis); + mScript.simulateTimePassing(); + + long expectedManualClockMillis = + mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime()); mScript.simulateManualTimeSuggestion(manualTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking( expectedManualClockMillis, false /* expectNetworkBroadcast */) .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); // Simulate the passage of time. - mScript.simulateTimePassing(clockIncrement); - expectedAutoClockMillis += clockIncrement; + mScript.simulateTimePassing(); // Switch back to auto. mScript.simulateAutoTimeDetectionToggle(); + expectedAutoClockMillis = + mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()); mScript.verifySystemClockWasSetAndResetCallTracking( expectedAutoClockMillis, true /* expectNetworkBroadcast */) .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); @@ -492,13 +495,143 @@ public class TimeDetectorStrategyImplTest { ManualTimeSuggestion timeSuggestion = mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS); - final int clockIncrement = 1000; - mScript.simulateTimePassing(clockIncrement) + mScript.simulateTimePassing() .simulateManualTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking(); } + @Test + public void testSuggestNetworkTime_autoTimeEnabled() { + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeAutoTimeDetectionEnabled(true); + + NetworkTimeSuggestion timeSuggestion = + mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS); + + mScript.simulateTimePassing(); + + long expectedSystemClockMillis = + mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime()); + mScript.simulateNetworkTimeSuggestion(timeSuggestion) + .verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis, false /* expectNetworkBroadcast */); + } + + @Test + public void testSuggestNetworkTime_autoTimeDisabled() { + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeAutoTimeDetectionEnabled(false); + + NetworkTimeSuggestion timeSuggestion = + mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS); + + mScript.simulateTimePassing() + .simulateNetworkTimeSuggestion(timeSuggestion) + .verifySystemClockWasNotSetAndResetCallTracking(); + } + + @Test + public void testSuggestNetworkTime_phoneSuggestionsBeatNetworkSuggestions() { + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeAutoTimeDetectionEnabled(true); + + // Three obviously different times that could not be mistaken for each other. + long networkTimeMillis1 = ARBITRARY_TEST_TIME_MILLIS; + long networkTimeMillis2 = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(30).toMillis(); + long phoneTimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(60).toMillis(); + // A small increment used to simulate the passage of time, but not enough to interfere with + // macro-level time changes associated with suggestion age. + final long smallTimeIncrementMillis = 101; + + // A network suggestion is made. It should be used because there is no phone suggestion. + NetworkTimeSuggestion networkTimeSuggestion1 = + mScript.generateNetworkTimeSuggestion(networkTimeMillis1); + mScript.simulateTimePassing(smallTimeIncrementMillis) + .simulateNetworkTimeSuggestion(networkTimeSuggestion1) + .verifySystemClockWasSetAndResetCallTracking( + mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()), + false /* expectNetworkBroadcast */); + + // Check internal state. + mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, null) + .assertLatestNetworkSuggestion(networkTimeSuggestion1); + assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion()); + assertNull(mScript.peekBestPhoneSuggestion()); + + // Simulate a little time passing. + mScript.simulateTimePassing(smallTimeIncrementMillis) + .verifySystemClockWasNotSetAndResetCallTracking(); + + // Now a phone suggestion is made. Phone suggestions are prioritized over network + // suggestions so it should "win". + PhoneTimeSuggestion phoneTimeSuggestion = + mScript.generatePhoneTimeSuggestion(ARBITRARY_PHONE_ID, phoneTimeMillis); + mScript.simulateTimePassing(smallTimeIncrementMillis) + .simulatePhoneTimeSuggestion(phoneTimeSuggestion) + .verifySystemClockWasSetAndResetCallTracking( + mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()), + true /* expectNetworkBroadcast */); + + // Check internal state. + mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) + .assertLatestNetworkSuggestion(networkTimeSuggestion1); + assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion()); + assertEquals(phoneTimeSuggestion, mScript.peekBestPhoneSuggestion()); + + // Simulate some significant time passing: half the time allowed before a time signal + // becomes "too old to use". + mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS / 2) + .verifySystemClockWasNotSetAndResetCallTracking(); + + // Now another network suggestion is made. Phone suggestions are prioritized over network + // suggestions so the latest phone suggestion should still "win". + NetworkTimeSuggestion networkTimeSuggestion2 = + mScript.generateNetworkTimeSuggestion(networkTimeMillis2); + mScript.simulateTimePassing(smallTimeIncrementMillis) + .simulateNetworkTimeSuggestion(networkTimeSuggestion2) + .verifySystemClockWasNotSetAndResetCallTracking(); + + // Check internal state. + mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) + .assertLatestNetworkSuggestion(networkTimeSuggestion2); + assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion()); + assertEquals(phoneTimeSuggestion, mScript.peekBestPhoneSuggestion()); + + // Simulate some significant time passing: half the time allowed before a time signal + // becomes "too old to use". This should mean that phoneTimeSuggestion is now too old to be + // used but networkTimeSuggestion2 is not. + mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS / 2); + + // NOTE: The TimeDetectorStrategyImpl doesn't set an alarm for the point when the last + // suggestion it used becomes too old: it requires a new suggestion or an auto-time toggle + // to re-run the detection logic. This may change in future but until then we rely on a + // steady stream of suggestions to re-evaluate. + mScript.verifySystemClockWasNotSetAndResetCallTracking(); + + // Check internal state. + mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) + .assertLatestNetworkSuggestion(networkTimeSuggestion2); + assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion()); + assertNull(mScript.peekBestPhoneSuggestion()); + + // Toggle auto-time off and on to force the detection logic to run. + mScript.simulateAutoTimeDetectionToggle() + .simulateTimePassing(smallTimeIncrementMillis) + .simulateAutoTimeDetectionToggle(); + + // Verify the latest network time now wins. + mScript.verifySystemClockWasSetAndResetCallTracking( + mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()), + false /* expectNetworkTimeBroadcast */); + + // Check internal state. + mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) + .assertLatestNetworkSuggestion(networkTimeSuggestion2); + assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion()); + assertNull(mScript.peekBestPhoneSuggestion()); + } + /** * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving * like the real thing should, it also asserts preconditions. @@ -674,6 +807,11 @@ public class TimeDetectorStrategyImplTest { return this; } + Script simulateNetworkTimeSuggestion(NetworkTimeSuggestion timeSuggestion) { + mTimeDetectorStrategy.suggestNetworkTime(timeSuggestion); + return this; + } + Script simulateAutoTimeDetectionToggle() { mFakeCallback.simulateAutoTimeZoneDetectionToggle(); mTimeDetectorStrategy.handleAutoTimeDetectionChanged(); @@ -685,6 +823,13 @@ public class TimeDetectorStrategyImplTest { return this; } + /** + * Simulates time passing by an arbitrary (but relatively small) amount. + */ + Script simulateTimePassing() { + return simulateTimePassing(999); + } + Script verifySystemClockWasNotSetAndResetCallTracking() { mFakeCallback.verifySystemClockNotSet(); mFakeCallback.verifyIntentWasNotBroadcast(); @@ -711,14 +856,30 @@ public class TimeDetectorStrategyImplTest { } /** + * White box test info: Asserts the latest network suggestion is as expected. + */ + Script assertLatestNetworkSuggestion(NetworkTimeSuggestion expected) { + assertEquals(expected, mTimeDetectorStrategy.getLatestNetworkSuggestion()); + return this; + } + + /** * White box test info: Returns the phone suggestion that would be used, if any, given the - * current elapsed real time clock. + * current elapsed real time clock and regardless of origin prioritization. */ PhoneTimeSuggestion peekBestPhoneSuggestion() { return mTimeDetectorStrategy.findBestPhoneSuggestionForTests(); } /** + * White box test info: Returns the network suggestion that would be used, if any, given the + * current elapsed real time clock and regardless of origin prioritization. + */ + NetworkTimeSuggestion peekLatestValidNetworkSuggestion() { + return mTimeDetectorStrategy.findLatestValidNetworkSuggestionForTests(); + } + + /** * Generates a ManualTimeSuggestion using the current elapsed realtime clock for the * reference time. */ @@ -739,6 +900,24 @@ public class TimeDetectorStrategyImplTest { } return createPhoneTimeSuggestion(phoneId, time); } + + /** + * Generates a NetworkTimeSuggestion using the current elapsed realtime clock for the + * reference time. + */ + NetworkTimeSuggestion generateNetworkTimeSuggestion(long timeMillis) { + TimestampedValue<Long> utcTime = + new TimestampedValue<>(mFakeCallback.peekElapsedRealtimeMillis(), timeMillis); + return new NetworkTimeSuggestion(utcTime); + } + + /** + * Calculates what the supplied time would be when adjusted for the movement of the fake + * elapsed realtime clock. + */ + long calculateTimeInMillisForNow(TimestampedValue<Long> utcTime) { + return TimeDetectorStrategy.getTimeAt(utcTime, peekElapsedRealtimeMillis()); + } } private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId, |