Merge "Change NetworkTimeUpdateServiceImpl behavior"

This commit is contained in:
Neil Fuller
2020-01-07 10:07:08 +00:00
committed by Gerrit Code Review
14 changed files with 734 additions and 200 deletions

View File

@@ -17,6 +17,7 @@
package android.app.timedetector; package android.app.timedetector;
import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion;
/** /**
@@ -35,4 +36,5 @@ import android.app.timedetector.PhoneTimeSuggestion;
interface ITimeDetectorService { interface ITimeDetectorService {
void suggestPhoneTime(in PhoneTimeSuggestion timeSuggestion); void suggestPhoneTime(in PhoneTimeSuggestion timeSuggestion);
void suggestManualTime(in ManualTimeSuggestion timeSuggestion); void suggestManualTime(in ManualTimeSuggestion timeSuggestion);
void suggestNetworkTime(in NetworkTimeSuggestion timeSuggestion);
} }

View File

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

View File

@@ -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
+ '}';
}
}

View File

@@ -85,4 +85,19 @@ public class TimeDetector {
manualTimeSuggestion.addDebugInfo(why); manualTimeSuggestion.addDebugInfo(why);
return manualTimeSuggestion; 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();
}
}
} }

View File

@@ -175,4 +175,22 @@ public class NtpTrustedTime implements TrustedTime {
public long getCachedNtpTimeReference() { public long getCachedNtpTimeReference() {
return mCachedNtpElapsedRealtime; 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);
}
} }

View File

@@ -2266,9 +2266,6 @@
<!-- Number of times to try again with the shorter interval, before backing <!-- Number of times to try again with the shorter interval, before backing
off until the normal polling interval. A value < 0 indicates infinite. --> off until the normal polling interval. A value < 0 indicates infinite. -->
<integer name="config_ntpRetry">3</integer> <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. --> <!-- Timeout to wait for NTP server response in milliseconds. -->
<integer name="config_ntpTimeout">5000</integer> <integer name="config_ntpTimeout">5000</integer>

View File

@@ -489,7 +489,6 @@
<java-symbol type="integer" name="config_ntpPollingInterval" /> <java-symbol type="integer" name="config_ntpPollingInterval" />
<java-symbol type="integer" name="config_ntpPollingIntervalShorter" /> <java-symbol type="integer" name="config_ntpPollingIntervalShorter" />
<java-symbol type="integer" name="config_ntpRetry" /> <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_ntpTimeout" />
<java-symbol type="integer" name="config_shortPressOnPowerBehavior" /> <java-symbol type="integer" name="config_shortPressOnPowerBehavior" />
<java-symbol type="integer" name="config_toastDefaultGravity" /> <java-symbol type="integer" name="config_toastDefaultGravity" />

View File

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

View File

@@ -18,6 +18,8 @@ package com.android.server;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TimeDetector;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
@@ -35,10 +37,10 @@ import android.os.Message;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.SystemClock; import android.os.SystemClock;
import android.provider.Settings; import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.Log; import android.util.Log;
import android.util.NtpTrustedTime; import android.util.NtpTrustedTime;
import android.util.TimeUtils; import android.util.TimeUtils;
import android.util.TimestampedValue;
import com.android.internal.util.DumpUtils; import com.android.internal.util.DumpUtils;
@@ -46,21 +48,19 @@ import java.io.FileDescriptor;
import java.io.PrintWriter; import java.io.PrintWriter;
/** /**
* Monitors the network time and updates the system time if it is out of sync * Monitors the network time. If looking up the network time fails for some reason, it tries a few
* and there hasn't been any NITZ update from the carrier recently. * times with a short interval and then resets to checking on longer intervals.
* 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
* <p> * com.android.server.timedetector.TimeDetectorService} where it may be used to set the device
* If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't * system clock, depending on user settings and what other signals are available.
* available.
* </p>
*/ */
public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeUpdateService { public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeUpdateService {
private static final String TAG = "NetworkTimeUpdateService"; private static final String TAG = "NetworkTimeUpdateService";
private static final boolean DBG = false; 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_POLL_NETWORK_TIME = 2;
private static final int EVENT_NETWORK_CHANGED = 3; 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 int POLL_REQUEST = 0;
private static final long NOT_SET = -1;
private long mNitzTimeSetTime = NOT_SET;
private Network mDefaultNetwork = null; private Network mDefaultNetwork = null;
private final Context mContext; private final Context mContext;
private final NtpTrustedTime mTime; private final NtpTrustedTime mTime;
private final AlarmManager mAlarmManager; private final AlarmManager mAlarmManager;
private final TimeDetector mTimeDetector;
private final ConnectivityManager mCM; private final ConnectivityManager mCM;
private final PendingIntent mPendingPollIntent; private final PendingIntent mPendingPollIntent;
private final PowerManager.WakeLock mWakeLock; private final PowerManager.WakeLock mWakeLock;
// NTP lookup is done on this thread and handler // NTP lookup is done on this thread and handler
private Handler mHandler; private Handler mHandler;
private SettingsObserver mSettingsObserver; private AutoTimeSettingObserver mAutoTimeSettingObserver;
private NetworkTimeUpdateCallback mNetworkTimeUpdateCallback; private NetworkTimeUpdateCallback mNetworkTimeUpdateCallback;
// Normal polling frequency // Normal polling frequency
@@ -91,8 +90,6 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU
private final long mPollingIntervalShorterMs; private final long mPollingIntervalShorterMs;
// Number of times to try again // Number of times to try again
private final int mTryAgainTimesMax; 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. // 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 // During bootup, the network may not have been up yet, or it's taking time for the
// connection to happen. // connection to happen.
@@ -102,6 +99,7 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU
mContext = context; mContext = context;
mTime = NtpTrustedTime.getInstance(context); mTime = NtpTrustedTime.getInstance(context);
mAlarmManager = mContext.getSystemService(AlarmManager.class); mAlarmManager = mContext.getSystemService(AlarmManager.class);
mTimeDetector = mContext.getSystemService(TimeDetector.class);
mCM = mContext.getSystemService(ConnectivityManager.class); mCM = mContext.getSystemService(ConnectivityManager.class);
Intent pollIntent = new Intent(ACTION_POLL, null); 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); com.android.internal.R.integer.config_ntpPollingIntervalShorter);
mTryAgainTimesMax = mContext.getResources().getInteger( mTryAgainTimesMax = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpRetry); com.android.internal.R.integer.config_ntpRetry);
mTimeErrorThresholdMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpThreshold);
mWakeLock = context.getSystemService(PowerManager.class).newWakeLock( mWakeLock = context.getSystemService(PowerManager.class).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, TAG); PowerManager.PARTIAL_WAKE_LOCK, TAG);
@@ -122,7 +118,6 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU
@Override @Override
public void systemRunning() { public void systemRunning() {
registerForTelephonyIntents();
registerForAlarms(); registerForAlarms();
HandlerThread thread = new HandlerThread(TAG); HandlerThread thread = new HandlerThread(TAG);
@@ -131,14 +126,9 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU
mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback(); mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback();
mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler); mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler);
mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED); mAutoTimeSettingObserver = new AutoTimeSettingObserver(mContext, mHandler,
mSettingsObserver.observe(mContext); EVENT_AUTO_TIME_ENABLED);
} mAutoTimeSettingObserver.observe();
private void registerForTelephonyIntents() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TelephonyManager.ACTION_NETWORK_SET_TIME);
mContext.registerReceiver(mNitzReceiver, intentFilter);
} }
private void registerForAlarms() { private void registerForAlarms() {
@@ -152,8 +142,7 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU
} }
private void onPollNetworkTime(int event) { private void onPollNetworkTime(int event) {
// If Automatic time is not set, don't bother. Similarly, if we don't // If we don't have any default network, don't bother.
// have any default network, don't bother.
if (mDefaultNetwork == null) return; if (mDefaultNetwork == null) return;
mWakeLock.acquire(); mWakeLock.acquire();
try { try {
@@ -173,10 +162,12 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU
if (mTime.getCacheAge() < mPollingIntervalMs) { if (mTime.getCacheAge() < mPollingIntervalMs) {
// Obtained fresh fix; schedule next normal update // Obtained fresh fix; schedule next normal update
resetAlarm(mPollingIntervalMs); 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 { } else {
// No fresh fix; schedule retry // No fresh fix; schedule retry
mTryAgainCounter++; 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. * 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); 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 */ /** Handler to do the network accesses on */
private class MyHandler extends Handler { private class MyHandler extends Handler {
@@ -263,7 +203,7 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
switch (msg.what) { switch (msg.what) {
case EVENT_AUTO_TIME_CHANGED: case EVENT_AUTO_TIME_ENABLED:
case EVENT_POLL_NETWORK_TIME: case EVENT_POLL_NETWORK_TIME:
case EVENT_NETWORK_CHANGED: case EVENT_NETWORK_CHANGED:
onPollNetworkTime(msg.what); 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 final Context mContext;
private Handler mHandler; private final int mMsg;
private final Handler mHandler;
SettingsObserver(Handler handler, int msg) { AutoTimeSettingObserver(Context context, Handler handler, int msg) {
super(handler); super(handler);
mContext = context;
mHandler = handler; mHandler = handler;
mMsg = msg; mMsg = msg;
} }
void observe(Context context) { void observe() {
ContentResolver resolver = context.getContentResolver(); ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME), resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME),
false, this); false, this);
} }
@Override @Override
public void onChange(boolean selfChange) { 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: "); pw.print("\nPollingIntervalShorterMs: ");
TimeUtils.formatDuration(mPollingIntervalShorterMs, pw); TimeUtils.formatDuration(mPollingIntervalShorterMs, pw);
pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax); pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax);
pw.print("TimeErrorThresholdMs: ");
TimeUtils.formatDuration(mTimeErrorThresholdMs, pw);
pw.println("\nTryAgainCounter: " + mTryAgainCounter); pw.println("\nTryAgainCounter: " + mTryAgainCounter);
pw.println("NTP cache age: " + mTime.getCacheAge()); pw.println("NTP cache age: " + mTime.getCacheAge());
pw.println("NTP cache certainty: " + mTime.getCacheCertainty()); pw.println("NTP cache certainty: " + mTime.getCacheCertainty());

View File

@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.timedetector.ITimeDetectorService; import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
@@ -105,6 +106,14 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub {
mHandler.post(() -> mTimeDetectorStrategy.suggestManualTime(timeSignal)); mHandler.post(() -> mTimeDetectorStrategy.suggestManualTime(timeSignal));
} }
@Override
public void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSignal) {
enforceSuggestNetworkTimePermission();
Objects.requireNonNull(timeSignal);
mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(timeSignal));
}
@VisibleForTesting @VisibleForTesting
public void handleAutoTimeDetectionToggle() { public void handleAutoTimeDetectionToggle() {
mHandler.post(mTimeDetectorStrategy::handleAutoTimeDetectionChanged); mHandler.post(mTimeDetectorStrategy::handleAutoTimeDetectionChanged);
@@ -129,4 +138,10 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub {
android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE, android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE,
"suggest manual time and time zone"); "suggest manual time and time zone");
} }
private void enforceSuggestNetworkTimePermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.SET_TIME,
"set time");
}
} }

View File

@@ -19,6 +19,7 @@ package com.android.server.timedetector;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion;
import android.content.Intent; import android.content.Intent;
import android.util.TimestampedValue; import android.util.TimestampedValue;
@@ -86,6 +87,9 @@ public interface TimeDetectorStrategy {
/** Process the suggested manually entered time. */ /** Process the suggested manually entered time. */
void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion); 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. */ /** Handle the auto-time setting being toggled on or off. */
void handleAutoTimeDetectionChanged(); void handleAutoTimeDetectionChanged();

View File

@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion;
import android.content.Intent; import android.content.Intent;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
@@ -32,6 +33,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.IndentingPrintWriter;
import com.android.server.timezonedetector.ArrayMapWithHistory; import com.android.server.timezonedetector.ArrayMapWithHistory;
import com.android.server.timezonedetector.ReferenceWithHistory;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.lang.annotation.Retention; 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. */ /** Each bucket is this size. All buckets are equally sized. */
@VisibleForTesting @VisibleForTesting
static final int PHONE_BUCKET_SIZE_MILLIS = 60 * 60 * 1000; 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 @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) @Retention(RetentionPolicy.SOURCE)
public @interface Origin {} public @interface Origin {}
@@ -72,6 +74,10 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
@Origin @Origin
private static final int ORIGIN_MANUAL = 2; 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 * 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 * 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. * will have a small number of telephony devices and phoneIds are assumed to be stable.
*/ */
@GuardedBy("this") @GuardedBy("this")
private ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionByPhoneId = private final ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionByPhoneId =
new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
@GuardedBy("this")
private final ReferenceWithHistory<NetworkTimeSuggestion> mLastNetworkSuggestion =
new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
@Override @Override
public void initialize(@NonNull Callback callback) { public void initialize(@NonNull Callback callback) {
mCallback = callback; mCallback = callback;
@@ -121,6 +131,19 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, cause); setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, cause);
} }
@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 @Override
public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
// Empty time suggestion means that telephony network connectivity has been lost. // Empty time suggestion means that telephony network connectivity has been lost.
@@ -184,6 +207,11 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
mSuggestionByPhoneId.dump(ipw); mSuggestionByPhoneId.dump(ipw);
ipw.decreaseIndent(); // level 2 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.decreaseIndent(); // level 1
ipw.flush(); ipw.flush();
} }
@@ -253,23 +281,34 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
return; 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(); PhoneTimeSuggestion bestPhoneSuggestion = findBestPhoneSuggestion();
if (bestPhoneSuggestion != null) {
// Work out what to do with the best suggestion. final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime();
if (bestPhoneSuggestion == null) { String cause = "Found good phone suggestion."
// There is no good phone suggestion. + ", bestPhoneSuggestion=" + bestPhoneSuggestion
if (DBG) { + ", detectionReason=" + detectionReason;
Slog.d(LOG_TAG, "Could not determine time: No best phone suggestion." setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause);
+ " detectionReason=" + detectionReason);
}
return; return;
} }
final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime(); // There is no good phone suggestion, try network.
String cause = "Found good suggestion." NetworkTimeSuggestion networkSuggestion = findLatestValidNetworkSuggestion();
+ ", bestPhoneSuggestion=" + bestPhoneSuggestion if (networkSuggestion != null) {
+ ", detectionReason=" + detectionReason; final TimestampedValue<Long> newUtcTime = networkSuggestion.getUtcTime();
setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause); String cause = "Found good network suggestion."
+ ", networkSuggestion=" + networkSuggestion
+ ", detectionReason=" + detectionReason;
setSystemClockIfRequired(ORIGIN_NETWORK, newUtcTime, cause);
return;
}
if (DBG) {
Slog.d(LOG_TAG, "Could not determine time: No best phone or network suggestion."
+ " detectionReason=" + detectionReason);
}
} }
@GuardedBy("this") @GuardedBy("this")
@@ -348,37 +387,50 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
private static int scorePhoneSuggestion( private static int scorePhoneSuggestion(
long elapsedRealtimeMillis, @NonNull PhoneTimeSuggestion timeSuggestion) { 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(); TimestampedValue<Long> utcTime = timeSuggestion.getUtcTime();
long referenceTimeMillis = utcTime.getReferenceTimeMillis(); if (!validateSuggestionUtcTime(elapsedRealtimeMillis, utcTime)) {
if (referenceTimeMillis > elapsedRealtimeMillis) { Slog.w(LOG_TAG, "Existing suggestion found to be invalid "
// 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. "
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ ", timeSuggestion=" + timeSuggestion); + ", timeSuggestion=" + timeSuggestion);
return PHONE_INVALID_SCORE; 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 // Turn the age into a discrete value: 0 <= bucketIndex < PHONE_BUCKET_COUNT.
// predictable, the accuracy of the reference time clock may be poor over long periods which int bucketIndex = (int) (ageMillis / PHONE_BUCKET_SIZE_MILLIS);
// would lead to errors creeping in. Also, in edge cases where a bad suggestion has been if (bucketIndex >= PHONE_BUCKET_COUNT) {
// 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) {
return PHONE_INVALID_SCORE; 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. // We want the lowest bucket index to have the highest score. 0 > score >= BUCKET_COUNT.
return PHONE_BUCKET_COUNT - bucketIndex; 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") @GuardedBy("this")
private void setSystemClockIfRequired( private void setSystemClockIfRequired(
@Origin int origin, @NonNull TimestampedValue<Long> time, @NonNull String cause) { @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) { private static boolean isOriginAutomatic(@Origin int origin) {
return origin == ORIGIN_PHONE; return origin != ORIGIN_MANUAL;
} }
@GuardedBy("this") @GuardedBy("this")
@@ -506,6 +558,16 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
return findBestPhoneSuggestion(); return findBestPhoneSuggestion();
} }
/**
* 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. * A method used to inspect state during tests. Not intended for general use.
*/ */
@@ -514,4 +576,32 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) { public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) {
return mSuggestionByPhoneId.get(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;
}
} }

View File

@@ -29,6 +29,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@@ -143,6 +144,36 @@ public class TimeDetectorServiceTest {
mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion); 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 @Test
public void testDump() { public void testDump() {
when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)) when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
@@ -180,11 +211,17 @@ public class TimeDetectorServiceTest {
return new ManualTimeSuggestion(timeValue); 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 { private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy {
// Call tracking. // Call tracking.
private PhoneTimeSuggestion mLastPhoneSuggestion; private PhoneTimeSuggestion mLastPhoneSuggestion;
private ManualTimeSuggestion mLastManualSuggestion; private ManualTimeSuggestion mLastManualSuggestion;
private NetworkTimeSuggestion mLastNetworkSuggestion;
private boolean mLastAutoTimeDetectionToggleCalled; private boolean mLastAutoTimeDetectionToggleCalled;
private boolean mDumpCalled; private boolean mDumpCalled;
@@ -204,6 +241,12 @@ public class TimeDetectorServiceTest {
mLastManualSuggestion = timeSuggestion; mLastManualSuggestion = timeSuggestion;
} }
@Override
public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) {
resetCallTracking();
mLastNetworkSuggestion = timeSuggestion;
}
@Override @Override
public void handleAutoTimeDetectionChanged() { public void handleAutoTimeDetectionChanged() {
resetCallTracking(); resetCallTracking();
@@ -219,6 +262,7 @@ public class TimeDetectorServiceTest {
void resetCallTracking() { void resetCallTracking() {
mLastPhoneSuggestion = null; mLastPhoneSuggestion = null;
mLastManualSuggestion = null; mLastManualSuggestion = null;
mLastNetworkSuggestion = null;
mLastAutoTimeDetectionToggleCalled = false; mLastAutoTimeDetectionToggleCalled = false;
mDumpCalled = false; mDumpCalled = false;
} }
@@ -231,6 +275,10 @@ public class TimeDetectorServiceTest {
assertEquals(expectedSuggestion, mLastManualSuggestion); assertEquals(expectedSuggestion, mLastManualSuggestion);
} }
public void verifySuggestNetworkTimeCalled(NetworkTimeSuggestion expectedSuggestion) {
assertEquals(expectedSuggestion, mLastNetworkSuggestion);
}
void verifyHandleAutoTimeDetectionToggleCalled() { void verifyHandleAutoTimeDetectionToggleCalled() {
assertTrue(mLastAutoTimeDetectionToggleCalled); assertTrue(mLastAutoTimeDetectionToggleCalled);
} }

View File

@@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion;
import android.content.Intent; import android.content.Intent;
import android.icu.util.Calendar; import android.icu.util.Calendar;
@@ -45,14 +46,16 @@ public class TimeDetectorStrategyImplTest {
private static final TimestampedValue<Long> ARBITRARY_CLOCK_INITIALIZATION_INFO = private static final TimestampedValue<Long> ARBITRARY_CLOCK_INITIALIZATION_INFO =
new TimestampedValue<>( new TimestampedValue<>(
123456789L /* realtimeClockMillis */, 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 long ARBITRARY_TEST_TIME_MILLIS = createUtcTime(2018, 1, 1, 12, 0, 0);
private static final int ARBITRARY_PHONE_ID = 123456; private static final int ARBITRARY_PHONE_ID = 123456;
private static final long ONE_DAY_MILLIS = Duration.ofDays(1).toMillis();
private Script mScript; private Script mScript;
@Before @Before
@@ -67,15 +70,16 @@ public class TimeDetectorStrategyImplTest {
int phoneId = ARBITRARY_PHONE_ID; int phoneId = ARBITRARY_PHONE_ID;
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
PhoneTimeSuggestion timeSuggestion = PhoneTimeSuggestion timeSuggestion =
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
int clockIncrement = 1000; mScript.simulateTimePassing()
long expectedSystemClockMillis = testTimeMillis + clockIncrement; .simulatePhoneTimeSuggestion(timeSuggestion);
mScript.simulateTimePassing(clockIncrement) long expectedSystemClockMillis =
.simulatePhoneTimeSuggestion(timeSuggestion) mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
.verifySystemClockWasSetAndResetCallTracking( mScript.verifySystemClockWasSetAndResetCallTracking(
expectedSystemClockMillis, true /* expectNetworkBroadcast */) expectedSystemClockMillis, true /* expectNetworkBroadcast */)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion); .assertLatestPhoneSuggestion(phoneId, timeSuggestion);
} }
@@ -94,26 +98,24 @@ public class TimeDetectorStrategyImplTest {
@Test @Test
public void testSuggestPhoneTime_systemClockThreshold() { public void testSuggestPhoneTime_systemClockThreshold() {
int systemClockUpdateThresholdMillis = 1000; final int systemClockUpdateThresholdMillis = 1000;
final int clockIncrementMillis = 100;
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeThresholds(systemClockUpdateThresholdMillis) .pokeThresholds(systemClockUpdateThresholdMillis)
.pokeAutoTimeDetectionEnabled(true); .pokeAutoTimeDetectionEnabled(true);
final int clockIncrement = 100;
int phoneId = ARBITRARY_PHONE_ID; int phoneId = ARBITRARY_PHONE_ID;
// Send the first time signal. It should be used. // Send the first time signal. It should be used.
{ {
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
PhoneTimeSuggestion timeSuggestion1 = PhoneTimeSuggestion timeSuggestion1 =
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS);
TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
// Increment the the device clocks to simulate the passage of time. // Increment the the device clocks to simulate the passage of time.
mScript.simulateTimePassing(clockIncrement); mScript.simulateTimePassing(clockIncrementMillis);
long expectedSystemClockMillis1 = long expectedSystemClockMillis1 =
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis()); mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime());
mScript.simulatePhoneTimeSuggestion(timeSuggestion1) mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
.verifySystemClockWasSetAndResetCallTracking( .verifySystemClockWasSetAndResetCallTracking(
@@ -127,7 +129,7 @@ public class TimeDetectorStrategyImplTest {
int underThresholdMillis = systemClockUpdateThresholdMillis - 1; int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion( PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion(
phoneId, mScript.peekSystemClockMillis() + underThresholdMillis); phoneId, mScript.peekSystemClockMillis() + underThresholdMillis);
mScript.simulateTimePassing(clockIncrement) mScript.simulateTimePassing(clockIncrementMillis)
.simulatePhoneTimeSuggestion(timeSuggestion2) .simulatePhoneTimeSuggestion(timeSuggestion2)
.verifySystemClockWasNotSetAndResetCallTracking() .verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestPhoneSuggestion(phoneId, timeSuggestion2); .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
@@ -138,11 +140,10 @@ public class TimeDetectorStrategyImplTest {
PhoneTimeSuggestion timeSuggestion3 = mScript.generatePhoneTimeSuggestion( PhoneTimeSuggestion timeSuggestion3 = mScript.generatePhoneTimeSuggestion(
phoneId, phoneId,
mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis); mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
mScript.simulateTimePassing(clockIncrement); mScript.simulateTimePassing(clockIncrementMillis);
long expectedSystemClockMillis3 = long expectedSystemClockMillis3 =
TimeDetectorStrategy.getTimeAt(timeSuggestion3.getUtcTime(), mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime());
mScript.peekElapsedRealtimeMillis());
mScript.simulatePhoneTimeSuggestion(timeSuggestion3) mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
.verifySystemClockWasSetAndResetCallTracking( .verifySystemClockWasSetAndResetCallTracking(
@@ -162,17 +163,16 @@ public class TimeDetectorStrategyImplTest {
int phone1Id = ARBITRARY_PHONE_ID; int phone1Id = ARBITRARY_PHONE_ID;
int phone2Id = ARBITRARY_PHONE_ID + 1; int phone2Id = ARBITRARY_PHONE_ID + 1;
long phone1TimeMillis = ARBITRARY_TEST_TIME_MILLIS; long phone1TimeMillis = ARBITRARY_TEST_TIME_MILLIS;
long phone2TimeMillis = phone1TimeMillis + 60000; long phone2TimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(1).toMillis();
final int clockIncrement = 999;
// Make a suggestion with phone2Id. // Make a suggestion with phone2Id.
{ {
PhoneTimeSuggestion phone2TimeSuggestion = PhoneTimeSuggestion phone2TimeSuggestion =
mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
mScript.simulateTimePassing(clockIncrement); mScript.simulateTimePassing();
long expectedSystemClockMillis = phone2TimeMillis + clockIncrement; long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking( .verifySystemClockWasSetAndResetCallTracking(
@@ -181,15 +181,16 @@ public class TimeDetectorStrategyImplTest {
.assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
} }
mScript.simulateTimePassing(clockIncrement); mScript.simulateTimePassing();
// Now make a different suggestion with phone1Id. // Now make a different suggestion with phone1Id.
{ {
PhoneTimeSuggestion phone1TimeSuggestion = PhoneTimeSuggestion phone1TimeSuggestion =
mScript.generatePhoneTimeSuggestion(phone1Id, phone1TimeMillis); mScript.generatePhoneTimeSuggestion(phone1Id, phone1TimeMillis);
mScript.simulateTimePassing(clockIncrement); mScript.simulateTimePassing();
long expectedSystemClockMillis = phone1TimeMillis + clockIncrement; long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion) mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking( .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 // Make another suggestion with phone2Id. It should be stored but not used because the
// phone1Id suggestion will still "win". // phone1Id suggestion will still "win".
{ {
PhoneTimeSuggestion phone2TimeSuggestion = PhoneTimeSuggestion phone2TimeSuggestion =
mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
mScript.simulateTimePassing(clockIncrement); mScript.simulateTimePassing();
mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking() .verifySystemClockWasNotSetAndResetCallTracking()
@@ -220,9 +221,10 @@ public class TimeDetectorStrategyImplTest {
{ {
PhoneTimeSuggestion phone2TimeSuggestion = PhoneTimeSuggestion phone2TimeSuggestion =
mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
mScript.simulateTimePassing(clockIncrement); mScript.simulateTimePassing();
long expectedSystemClockMillis = phone2TimeMillis + clockIncrement; long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking( .verifySystemClockWasSetAndResetCallTracking(
@@ -239,7 +241,7 @@ public class TimeDetectorStrategyImplTest {
int phoneId = ARBITRARY_PHONE_ID; int phoneId = ARBITRARY_PHONE_ID;
PhoneTimeSuggestion timeSuggestion = PhoneTimeSuggestion timeSuggestion =
mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS); mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS);
mScript.simulateTimePassing(1000) mScript.simulateTimePassing()
.simulatePhoneTimeSuggestion(timeSuggestion) .simulatePhoneTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking() .verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestPhoneSuggestion(phoneId, timeSuggestion); .assertLatestPhoneSuggestion(phoneId, timeSuggestion);
@@ -260,9 +262,8 @@ public class TimeDetectorStrategyImplTest {
TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
// Initialize the strategy / device with a time set from a phone suggestion. // Initialize the strategy / device with a time set from a phone suggestion.
mScript.simulateTimePassing(100); mScript.simulateTimePassing();
long expectedSystemClockMillis1 = long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1);
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
mScript.simulatePhoneTimeSuggestion(timeSuggestion1) mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
.verifySystemClockWasSetAndResetCallTracking( .verifySystemClockWasSetAndResetCallTracking(
expectedSystemClockMillis1, true /* expectNetworkBroadcast */) expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
@@ -299,8 +300,7 @@ public class TimeDetectorStrategyImplTest {
long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100; long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100;
TimestampedValue<Long> utcTime4 = new TimestampedValue<>( TimestampedValue<Long> utcTime4 = new TimestampedValue<>(
validReferenceTimeMillis, validUtcTimeMillis); validReferenceTimeMillis, validUtcTimeMillis);
long expectedSystemClockMillis4 = long expectedSystemClockMillis4 = mScript.calculateTimeInMillisForNow(utcTime4);
TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
PhoneTimeSuggestion timeSuggestion4 = PhoneTimeSuggestion timeSuggestion4 =
createPhoneTimeSuggestion(phoneId, utcTime4); createPhoneTimeSuggestion(phoneId, utcTime4);
mScript.simulatePhoneTimeSuggestion(timeSuggestion4) mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
@@ -335,8 +335,7 @@ public class TimeDetectorStrategyImplTest {
// Simulate more time passing. // Simulate more time passing.
mScript.simulateTimePassing(clockIncrementMillis); mScript.simulateTimePassing(clockIncrementMillis);
long expectedSystemClockMillis1 = long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1);
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
// Turn on auto time detection. // Turn on auto time detection.
mScript.simulateAutoTimeDetectionToggle() mScript.simulateAutoTimeDetectionToggle()
@@ -357,8 +356,8 @@ public class TimeDetectorStrategyImplTest {
// Simulate more time passing. // Simulate more time passing.
mScript.simulateTimePassing(clockIncrementMillis); mScript.simulateTimePassing(clockIncrementMillis);
long expectedSystemClockMillis2 = TimeDetectorStrategy.getTimeAt( long expectedSystemClockMillis2 =
timeSuggestion2.getUtcTime(), mScript.peekElapsedRealtimeMillis()); mScript.calculateTimeInMillisForNow(timeSuggestion2.getUtcTime());
// The new time, though valid, should not be set in the system clock because auto time is // The new time, though valid, should not be set in the system clock because auto time is
// disabled. // disabled.
@@ -382,19 +381,21 @@ public class TimeDetectorStrategyImplTest {
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
PhoneTimeSuggestion phoneSuggestion = PhoneTimeSuggestion phoneSuggestion =
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
int clockIncrementMillis = 1000;
mScript.simulateTimePassing(clockIncrementMillis) mScript.simulateTimePassing();
.simulatePhoneTimeSuggestion(phoneSuggestion)
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phoneSuggestion)
.verifySystemClockWasSetAndResetCallTracking( .verifySystemClockWasSetAndResetCallTracking(
testTimeMillis + clockIncrementMillis, true /* expectedNetworkBroadcast */) expectedSystemClockMillis, true /* expectedNetworkBroadcast */)
.assertLatestPhoneSuggestion(phoneId, phoneSuggestion); .assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
// Look inside and check what the strategy considers the current best phone suggestion. // Look inside and check what the strategy considers the current best phone suggestion.
assertEquals(phoneSuggestion, mScript.peekBestPhoneSuggestion()); assertEquals(phoneSuggestion, mScript.peekBestPhoneSuggestion());
// Simulate time passing, long enough that phoneSuggestion is now too old. // 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 // Look inside and check what the strategy considers the current best phone suggestion. It
// should still be the, it's just no longer used. // should still be the, it's just no longer used.
@@ -407,13 +408,14 @@ public class TimeDetectorStrategyImplTest {
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(false); .pokeAutoTimeDetectionEnabled(false);
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; ManualTimeSuggestion timeSuggestion =
ManualTimeSuggestion timeSuggestion = mScript.generateManualTimeSuggestion(testTimeMillis); mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
final int clockIncrement = 1000;
long expectedSystemClockMillis = testTimeMillis + clockIncrement;
mScript.simulateTimePassing(clockIncrement) mScript.simulateTimePassing();
.simulateManualTimeSuggestion(timeSuggestion)
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
mScript.simulateManualTimeSuggestion(timeSuggestion)
.verifySystemClockWasSetAndResetCallTracking( .verifySystemClockWasSetAndResetCallTracking(
expectedSystemClockMillis, false /* expectNetworkBroadcast */); expectedSystemClockMillis, false /* expectNetworkBroadcast */);
} }
@@ -430,21 +432,19 @@ public class TimeDetectorStrategyImplTest {
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
PhoneTimeSuggestion phoneTimeSuggestion = PhoneTimeSuggestion phoneTimeSuggestion =
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue();
final int clockIncrement = 1000;
// Simulate the passage of time. // Simulate the passage of time.
mScript.simulateTimePassing(clockIncrement); mScript.simulateTimePassing();
expectedAutoClockMillis += clockIncrement;
long expectedAutoClockMillis =
mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion) mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking( .verifySystemClockWasSetAndResetCallTracking(
expectedAutoClockMillis, true /* expectNetworkBroadcast */) expectedAutoClockMillis, true /* expectNetworkBroadcast */)
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Simulate the passage of time. // Simulate the passage of time.
mScript.simulateTimePassing(clockIncrement); mScript.simulateTimePassing();
expectedAutoClockMillis += clockIncrement;
// Switch to manual. // Switch to manual.
mScript.simulateAutoTimeDetectionToggle() mScript.simulateAutoTimeDetectionToggle()
@@ -452,26 +452,29 @@ public class TimeDetectorStrategyImplTest {
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Simulate the passage of time. // Simulate the passage of time.
mScript.simulateTimePassing(clockIncrement); mScript.simulateTimePassing();
expectedAutoClockMillis += clockIncrement;
// Simulate a manual suggestion 1 day different from the auto suggestion. // Simulate a manual suggestion 1 day different from the auto suggestion.
long manualTimeMillis = testTimeMillis + ONE_DAY_MILLIS; long manualTimeMillis = testTimeMillis + Duration.ofDays(1).toMillis();
long expectedManualClockMillis = manualTimeMillis;
ManualTimeSuggestion manualTimeSuggestion = ManualTimeSuggestion manualTimeSuggestion =
mScript.generateManualTimeSuggestion(manualTimeMillis); mScript.generateManualTimeSuggestion(manualTimeMillis);
mScript.simulateTimePassing();
long expectedManualClockMillis =
mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime());
mScript.simulateManualTimeSuggestion(manualTimeSuggestion) mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking( .verifySystemClockWasSetAndResetCallTracking(
expectedManualClockMillis, false /* expectNetworkBroadcast */) expectedManualClockMillis, false /* expectNetworkBroadcast */)
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Simulate the passage of time. // Simulate the passage of time.
mScript.simulateTimePassing(clockIncrement); mScript.simulateTimePassing();
expectedAutoClockMillis += clockIncrement;
// Switch back to auto. // Switch back to auto.
mScript.simulateAutoTimeDetectionToggle(); mScript.simulateAutoTimeDetectionToggle();
expectedAutoClockMillis =
mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
mScript.verifySystemClockWasSetAndResetCallTracking( mScript.verifySystemClockWasSetAndResetCallTracking(
expectedAutoClockMillis, true /* expectNetworkBroadcast */) expectedAutoClockMillis, true /* expectNetworkBroadcast */)
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
@@ -492,13 +495,143 @@ public class TimeDetectorStrategyImplTest {
ManualTimeSuggestion timeSuggestion = ManualTimeSuggestion timeSuggestion =
mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS); mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
final int clockIncrement = 1000;
mScript.simulateTimePassing(clockIncrement) mScript.simulateTimePassing()
.simulateManualTimeSuggestion(timeSuggestion) .simulateManualTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking(); .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 * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
* like the real thing should, it also asserts preconditions. * like the real thing should, it also asserts preconditions.
@@ -674,6 +807,11 @@ public class TimeDetectorStrategyImplTest {
return this; return this;
} }
Script simulateNetworkTimeSuggestion(NetworkTimeSuggestion timeSuggestion) {
mTimeDetectorStrategy.suggestNetworkTime(timeSuggestion);
return this;
}
Script simulateAutoTimeDetectionToggle() { Script simulateAutoTimeDetectionToggle() {
mFakeCallback.simulateAutoTimeZoneDetectionToggle(); mFakeCallback.simulateAutoTimeZoneDetectionToggle();
mTimeDetectorStrategy.handleAutoTimeDetectionChanged(); mTimeDetectorStrategy.handleAutoTimeDetectionChanged();
@@ -685,6 +823,13 @@ public class TimeDetectorStrategyImplTest {
return this; return this;
} }
/**
* Simulates time passing by an arbitrary (but relatively small) amount.
*/
Script simulateTimePassing() {
return simulateTimePassing(999);
}
Script verifySystemClockWasNotSetAndResetCallTracking() { Script verifySystemClockWasNotSetAndResetCallTracking() {
mFakeCallback.verifySystemClockNotSet(); mFakeCallback.verifySystemClockNotSet();
mFakeCallback.verifyIntentWasNotBroadcast(); mFakeCallback.verifyIntentWasNotBroadcast();
@@ -710,14 +855,30 @@ public class TimeDetectorStrategyImplTest {
return this; return this;
} }
/**
* 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 * 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() { PhoneTimeSuggestion peekBestPhoneSuggestion() {
return mTimeDetectorStrategy.findBestPhoneSuggestionForTests(); 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 * Generates a ManualTimeSuggestion using the current elapsed realtime clock for the
* reference time. * reference time.
@@ -739,6 +900,24 @@ public class TimeDetectorStrategyImplTest {
} }
return createPhoneTimeSuggestion(phoneId, time); 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, private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId,