Merge "Handle multiple phoneIds in time detection"
This commit is contained in:
@@ -56,6 +56,7 @@ public final class ManualTimeSuggestion implements Parcelable {
|
||||
|
||||
public ManualTimeSuggestion(@NonNull TimestampedValue<Long> utcTime) {
|
||||
mUtcTime = Objects.requireNonNull(utcTime);
|
||||
Objects.requireNonNull(utcTime.getValue());
|
||||
}
|
||||
|
||||
private static ManualTimeSuggestion createFromParcel(Parcel in) {
|
||||
|
||||
@@ -166,7 +166,12 @@ public final class PhoneTimeSuggestion implements Parcelable {
|
||||
}
|
||||
|
||||
/** Returns the builder for call chaining. */
|
||||
public Builder setUtcTime(TimestampedValue<Long> utcTime) {
|
||||
public Builder setUtcTime(@Nullable TimestampedValue<Long> utcTime) {
|
||||
if (utcTime != null) {
|
||||
// utcTime can be null, but the value it holds cannot.
|
||||
Objects.requireNonNull(utcTime.getValue());
|
||||
}
|
||||
|
||||
mUtcTime = utcTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -23,21 +23,26 @@ import android.app.AlarmManager;
|
||||
import android.app.timedetector.ManualTimeSuggestion;
|
||||
import android.app.timedetector.PhoneTimeSuggestion;
|
||||
import android.content.Intent;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.LocalLog;
|
||||
import android.util.Slog;
|
||||
import android.util.TimestampedValue;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.telephony.TelephonyIntents;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An implementation of TimeDetectorStrategy that passes only NITZ suggestions to
|
||||
* {@link AlarmManager}.
|
||||
* An implementation of TimeDetectorStrategy that passes phone and manual suggestions to
|
||||
* {@link AlarmManager}. When there are multiple phone sources, the one with the lowest ID is used
|
||||
* unless the data becomes too stale.
|
||||
*
|
||||
* <p>Most public methods are marked synchronized to ensure thread safety around internal state.
|
||||
*/
|
||||
@@ -46,6 +51,17 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
||||
private static final boolean DBG = false;
|
||||
private static final String LOG_TAG = "SimpleTimeDetectorStrategy";
|
||||
|
||||
/** A score value used to indicate "no score", either due to validation failure or age. */
|
||||
private static final int PHONE_INVALID_SCORE = -1;
|
||||
/** The number of buckets phone suggestions can be put in by age. */
|
||||
private static final int PHONE_BUCKET_COUNT = 24;
|
||||
/** 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. */
|
||||
@VisibleForTesting
|
||||
static final long PHONE_MAX_AGE_MILLIS = PHONE_BUCKET_COUNT * PHONE_BUCKET_SIZE_MILLIS;
|
||||
|
||||
@IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Origin {}
|
||||
@@ -61,10 +77,13 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
||||
/**
|
||||
* 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
|
||||
* there is something other than this class setting the system clock automatically.
|
||||
* there is something other than this class setting the system clock.
|
||||
*/
|
||||
private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000;
|
||||
|
||||
/** The number of previous phone suggestions to keep for each ID (for use during debugging). */
|
||||
private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30;
|
||||
|
||||
// A log for changes made to the system clock and why.
|
||||
@NonNull
|
||||
private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
|
||||
@@ -72,15 +91,22 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
||||
// @NonNull after initialize()
|
||||
private Callback mCallback;
|
||||
|
||||
// Last phone suggestion.
|
||||
@Nullable private PhoneTimeSuggestion mLastPhoneSuggestion;
|
||||
// Used to store the last time the system clock state was set automatically. It is used to
|
||||
// detect (and log) issues with the realtime clock or whether the clock is being set without
|
||||
// going through this strategy code.
|
||||
@GuardedBy("this")
|
||||
@Nullable
|
||||
private TimestampedValue<Long> mLastAutoSystemClockTimeSet;
|
||||
|
||||
// Information about the last time signal received: Used when toggling auto-time.
|
||||
@Nullable private TimestampedValue<Long> mLastAutoSystemClockTime;
|
||||
private boolean mLastAutoSystemClockTimeSendNetworkBroadcast;
|
||||
|
||||
// System clock state.
|
||||
@Nullable private TimestampedValue<Long> mLastAutoSystemClockTimeSet;
|
||||
/**
|
||||
* A mapping from phoneId to a linked list of time suggestions (the "first" being the latest).
|
||||
* We typically expect one or two entries in this Map: devices will have a small number
|
||||
* of telephony devices and phoneIds are assumed to be stable. The LinkedList associated with
|
||||
* the ID will not exceed {@link #KEEP_SUGGESTION_HISTORY_SIZE} in size.
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private ArrayMap<Integer, LinkedList<PhoneTimeSuggestion>> mSuggestionByPhoneId =
|
||||
new ArrayMap<>();
|
||||
|
||||
@Override
|
||||
public void initialize(@NonNull Callback callback) {
|
||||
@@ -88,66 +114,297 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
|
||||
// NITZ logic
|
||||
public synchronized void suggestManualTime(ManualTimeSuggestion suggestion) {
|
||||
final TimestampedValue<Long> newUtcTime = suggestion.getUtcTime();
|
||||
|
||||
// Empty suggestions are just ignored as we don't currently keep track of suggestion origin.
|
||||
// We can validate the suggestion against the reference time clock.
|
||||
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
|
||||
if (elapsedRealtimeMillis < newUtcTime.getReferenceTimeMillis()) {
|
||||
// elapsedRealtime clock went backwards?
|
||||
Slog.w(LOG_TAG, "New reference time is in the future? Ignoring."
|
||||
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
|
||||
+ ", timeSuggestion=" + suggestion);
|
||||
return;
|
||||
}
|
||||
|
||||
String cause = "Manual time suggestion received: suggestion=" + suggestion;
|
||||
setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, cause);
|
||||
}
|
||||
|
||||
@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,
|
||||
// so we can continue relying on previous suggestions when we lose connectivity. This is
|
||||
// unlike time zone, where a user may lose connectivity when boarding a flight and where we
|
||||
// do want to "forget" old signals. Suggestions that are too old are discarded later in the
|
||||
// detection algorithm.
|
||||
if (timeSuggestion.getUtcTime() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean timeSuggestionIsValid =
|
||||
validateNewPhoneSuggestion(timeSuggestion, mLastPhoneSuggestion);
|
||||
if (!timeSuggestionIsValid) {
|
||||
// Perform validation / input filtering and record the validated suggestion against the
|
||||
// phoneId.
|
||||
if (!validateAndStorePhoneSuggestion(timeSuggestion)) {
|
||||
return;
|
||||
}
|
||||
// Always store the last NITZ value received, regardless of whether we go on to use it to
|
||||
// update the system clock. This is so that we can validate future phone suggestions.
|
||||
mLastPhoneSuggestion = timeSuggestion;
|
||||
|
||||
// System clock update logic.
|
||||
final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
|
||||
setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, timeSuggestion);
|
||||
// Now perform auto time detection. The new suggestion may be used to modify the system
|
||||
// clock.
|
||||
String reason = "New phone time suggested. timeSuggestion=" + timeSuggestion;
|
||||
doAutoTimeDetection(reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void suggestManualTime(ManualTimeSuggestion timeSuggestion) {
|
||||
final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
|
||||
setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, timeSuggestion);
|
||||
public synchronized void handleAutoTimeDetectionChanged() {
|
||||
boolean enabled = mCallback.isAutoTimeDetectionEnabled();
|
||||
// When automatic time detection is enabled we update the system clock instantly if we can.
|
||||
// Conversely, when automatic time detection is disabled we leave the clock as it is.
|
||||
if (enabled) {
|
||||
String reason = "Auto time zone detection setting enabled.";
|
||||
doAutoTimeDetection(reason);
|
||||
} else {
|
||||
// CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
|
||||
// it should be in future.
|
||||
mLastAutoSystemClockTimeSet = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean validateNewPhoneSuggestion(@NonNull PhoneTimeSuggestion newSuggestion,
|
||||
@Nullable PhoneTimeSuggestion lastSuggestion) {
|
||||
@Override
|
||||
public synchronized void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
|
||||
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
|
||||
ipw.println("TimeDetectorStrategy:");
|
||||
ipw.increaseIndent(); // level 1
|
||||
|
||||
if (lastSuggestion != null) {
|
||||
long referenceTimeDifference = TimestampedValue.referenceTimeDifference(
|
||||
newSuggestion.getUtcTime(), lastSuggestion.getUtcTime());
|
||||
if (referenceTimeDifference < 0 || referenceTimeDifference > Integer.MAX_VALUE) {
|
||||
// Out of order or bogus.
|
||||
Slog.w(LOG_TAG, "Bad NITZ signal received."
|
||||
+ " referenceTimeDifference=" + referenceTimeDifference
|
||||
+ " lastSuggestion=" + lastSuggestion
|
||||
+ " newSuggestion=" + newSuggestion);
|
||||
ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet);
|
||||
|
||||
ipw.println("Time change log:");
|
||||
ipw.increaseIndent(); // level 2
|
||||
mTimeChangesLog.dump(ipw);
|
||||
ipw.decreaseIndent(); // level 2
|
||||
|
||||
ipw.println("Phone suggestion history:");
|
||||
ipw.increaseIndent(); // level 2
|
||||
for (Map.Entry<Integer, LinkedList<PhoneTimeSuggestion>> entry
|
||||
: mSuggestionByPhoneId.entrySet()) {
|
||||
ipw.println("Phone " + entry.getKey());
|
||||
|
||||
ipw.increaseIndent(); // level 3
|
||||
for (PhoneTimeSuggestion suggestion : entry.getValue()) {
|
||||
ipw.println(suggestion);
|
||||
}
|
||||
ipw.decreaseIndent(); // level 3
|
||||
}
|
||||
ipw.decreaseIndent(); // level 2
|
||||
|
||||
ipw.decreaseIndent(); // level 1
|
||||
ipw.flush();
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private boolean validateAndStorePhoneSuggestion(@NonNull PhoneTimeSuggestion timeSuggestion) {
|
||||
if (timeSuggestion.getUtcTime().getValue() == null) {
|
||||
Slog.w(LOG_TAG, "Suggestion utcTime contains null value"
|
||||
+ " timeSuggestion=" + timeSuggestion);
|
||||
return false;
|
||||
}
|
||||
|
||||
int phoneId = timeSuggestion.getPhoneId();
|
||||
LinkedList<PhoneTimeSuggestion> phoneSuggestions = mSuggestionByPhoneId.get(phoneId);
|
||||
if (phoneSuggestions == null) {
|
||||
// The first time we've seen this phoneId.
|
||||
phoneSuggestions = new LinkedList<>();
|
||||
mSuggestionByPhoneId.put(phoneId, phoneSuggestions);
|
||||
} else if (phoneSuggestions.isEmpty()) {
|
||||
Slog.w(LOG_TAG, "Suggestions unexpectedly empty when adding"
|
||||
+ " timeSuggestion=" + timeSuggestion);
|
||||
}
|
||||
|
||||
if (!phoneSuggestions.isEmpty()) {
|
||||
PhoneTimeSuggestion previousSuggestion = phoneSuggestions.getFirst();
|
||||
|
||||
// We can log / discard suggestions with obvious issues with the reference time clock.
|
||||
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
|
||||
TimestampedValue<Long> newTime = timeSuggestion.getUtcTime();
|
||||
if (elapsedRealtimeMillis < newTime.getReferenceTimeMillis()) {
|
||||
// elapsedRealtime clock went backwards?
|
||||
Slog.w(LOG_TAG, "New reference time is in the future?"
|
||||
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
|
||||
+ ", timeSuggestion=" + timeSuggestion);
|
||||
// There's probably nothing useful we can do: elsewhere we assume that reference
|
||||
// times are in the past so just stop here.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (previousSuggestion.getUtcTime() != null) {
|
||||
long referenceTimeDifference = TimestampedValue.referenceTimeDifference(
|
||||
timeSuggestion.getUtcTime(), previousSuggestion.getUtcTime());
|
||||
if (referenceTimeDifference < 0) {
|
||||
// The reference time is before the previously received suggestion. Ignore it.
|
||||
Slog.w(LOG_TAG, "Out of order phone suggestion received."
|
||||
+ " referenceTimeDifference=" + referenceTimeDifference
|
||||
+ " lastSuggestion=" + previousSuggestion
|
||||
+ " newSuggestion=" + timeSuggestion);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the latest suggestion.
|
||||
phoneSuggestions.addFirst(timeSuggestion);
|
||||
if (phoneSuggestions.size() > KEEP_SUGGESTION_HISTORY_SIZE) {
|
||||
phoneSuggestions.removeLast();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void doAutoTimeDetection(@NonNull String detectionReason) {
|
||||
if (!mCallback.isAutoTimeDetectionEnabled()) {
|
||||
// Avoid doing unnecessary work with this (race-prone) check.
|
||||
return;
|
||||
}
|
||||
|
||||
PhoneTimeSuggestion bestPhoneSuggestion = findBestPhoneSuggestion();
|
||||
|
||||
// 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);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime();
|
||||
String cause = "Found good suggestion."
|
||||
+ ", bestPhoneSuggestion=" + bestPhoneSuggestion
|
||||
+ ", detectionReason=" + detectionReason;
|
||||
setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause);
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
@Nullable
|
||||
private PhoneTimeSuggestion findBestPhoneSuggestion() {
|
||||
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
|
||||
|
||||
// Phone time suggestions are assumed to be derived from NITZ or NITZ-like signals. These
|
||||
// have a number of limitations:
|
||||
// 1) No guarantee of accuracy ("accuracy of the time information is in the order of
|
||||
// minutes") [1]
|
||||
// 2) No guarantee of regular signals ("dependent on the handset crossing radio network
|
||||
// boundaries") [1]
|
||||
//
|
||||
// [1] https://en.wikipedia.org/wiki/NITZ
|
||||
//
|
||||
// Generally, when there are suggestions from multiple phoneIds they should usually
|
||||
// approximately agree. In cases where signals *are* inaccurate we don't want to vacillate
|
||||
// between signals from two phoneIds. However, it is known for NITZ signals to be incorrect
|
||||
// occasionally, which means we also don't want to stick forever with one phoneId. Without
|
||||
// cross-referencing across sources (e.g. the current device time, NTP), or doing some kind
|
||||
// of statistical analysis of consistency within and across phoneIds, we can't know which
|
||||
// suggestions are more correct.
|
||||
//
|
||||
// For simplicity, we try to value recency, then consistency of phoneId.
|
||||
//
|
||||
// The heuristic works as follows:
|
||||
// Recency: The most recent suggestion from each phone is scored. The score is based on a
|
||||
// discrete age bucket, i.e. so signals received around the same time will be in the same
|
||||
// bucket, thus applying a loose reference time ordering. The suggestion with the highest
|
||||
// score is used.
|
||||
// Consistency: If there a multiple suggestions with the same score, the suggestion with the
|
||||
// lowest phoneId is always taken.
|
||||
//
|
||||
// In the trivial case with a single ID this will just mean that the latest received
|
||||
// suggestion is used.
|
||||
|
||||
PhoneTimeSuggestion bestSuggestion = null;
|
||||
int bestScore = PHONE_INVALID_SCORE;
|
||||
for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
|
||||
Integer phoneId = mSuggestionByPhoneId.keyAt(i);
|
||||
LinkedList<PhoneTimeSuggestion> phoneSuggestions = mSuggestionByPhoneId.valueAt(i);
|
||||
if (phoneSuggestions == null) {
|
||||
// Unexpected - map is missing a value.
|
||||
Slog.w(LOG_TAG, "Suggestions unexpectedly missing for phoneId."
|
||||
+ " phoneId=" + phoneId);
|
||||
continue;
|
||||
}
|
||||
|
||||
PhoneTimeSuggestion candidateSuggestion = phoneSuggestions.getFirst();
|
||||
if (candidateSuggestion == null) {
|
||||
// Unexpected - null suggestions should never be stored.
|
||||
Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for phoneId."
|
||||
+ " phoneId=" + phoneId);
|
||||
continue;
|
||||
} else if (candidateSuggestion.getUtcTime() == null) {
|
||||
// Unexpected - we do not store empty suggestions.
|
||||
Slog.w(LOG_TAG, "Latest suggestion unexpectedly empty. "
|
||||
+ " candidateSuggestion=" + candidateSuggestion);
|
||||
continue;
|
||||
}
|
||||
|
||||
int candidateScore = scorePhoneSuggestion(elapsedRealtimeMillis, candidateSuggestion);
|
||||
if (candidateScore == PHONE_INVALID_SCORE) {
|
||||
// Expected: This means the suggestion is obviously invalid or just too old.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Higher scores are better.
|
||||
if (bestSuggestion == null || bestScore < candidateScore) {
|
||||
bestSuggestion = candidateSuggestion;
|
||||
bestScore = candidateScore;
|
||||
} else if (bestScore == candidateScore) {
|
||||
// Tie! Use the suggestion with the lowest phoneId.
|
||||
int candidatePhoneId = candidateSuggestion.getPhoneId();
|
||||
int bestPhoneId = bestSuggestion.getPhoneId();
|
||||
if (candidatePhoneId < bestPhoneId) {
|
||||
bestSuggestion = candidateSuggestion;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestSuggestion;
|
||||
}
|
||||
|
||||
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.
|
||||
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. "
|
||||
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
|
||||
+ ", timeSuggestion=" + timeSuggestion);
|
||||
return PHONE_INVALID_SCORE;
|
||||
}
|
||||
|
||||
long ageMillis = elapsedRealtimeMillis - referenceTimeMillis;
|
||||
|
||||
// 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void setSystemClockIfRequired(
|
||||
@Origin int origin, TimestampedValue<Long> time, Object cause) {
|
||||
// Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only
|
||||
// when setting the time using NITZ.
|
||||
boolean sendNetworkBroadcast = origin == ORIGIN_PHONE;
|
||||
@Origin int origin, @NonNull TimestampedValue<Long> time, @NonNull String cause) {
|
||||
|
||||
boolean isOriginAutomatic = isOriginAutomatic(origin);
|
||||
if (isOriginAutomatic) {
|
||||
// Store the last auto time candidate we've seen in all cases so we can set the system
|
||||
// clock when/if time detection is off but later enabled.
|
||||
mLastAutoSystemClockTime = time;
|
||||
mLastAutoSystemClockTimeSendNetworkBroadcast = sendNetworkBroadcast;
|
||||
|
||||
if (!mCallback.isAutoTimeDetectionEnabled()) {
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "Auto time detection is not enabled."
|
||||
@@ -171,30 +428,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
||||
|
||||
mCallback.acquireWakeLock();
|
||||
try {
|
||||
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
|
||||
long actualTimeMillis = mCallback.systemClockMillis();
|
||||
|
||||
if (isOriginAutomatic) {
|
||||
// CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
|
||||
// may be setting the clock.
|
||||
if (mLastAutoSystemClockTimeSet != null) {
|
||||
long expectedTimeMillis = TimeDetectorStrategy.getTimeAt(
|
||||
mLastAutoSystemClockTimeSet, elapsedRealtimeMillis);
|
||||
long absSystemClockDifference = Math.abs(expectedTimeMillis - actualTimeMillis);
|
||||
if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) {
|
||||
Slog.w(LOG_TAG,
|
||||
"System clock has not tracked elapsed real time clock. A clock may"
|
||||
+ " be inaccurate or something unexpectedly set the system"
|
||||
+ " clock."
|
||||
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
|
||||
+ " expectedTimeMillis=" + expectedTimeMillis
|
||||
+ " actualTimeMillis=" + actualTimeMillis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adjustAndSetDeviceSystemClock(
|
||||
time, sendNetworkBroadcast, elapsedRealtimeMillis, actualTimeMillis, cause);
|
||||
setSystemClockUnderWakeLock(origin, time, cause);
|
||||
} finally {
|
||||
mCallback.releaseWakeLock();
|
||||
}
|
||||
@@ -204,61 +438,33 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
||||
return origin == ORIGIN_PHONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void handleAutoTimeDetectionChanged() {
|
||||
// If automatic time detection is enabled we update the system clock instantly if we can.
|
||||
// Conversely, if automatic time detection is disabled we leave the clock as it is.
|
||||
boolean enabled = mCallback.isAutoTimeDetectionEnabled();
|
||||
if (enabled) {
|
||||
if (mLastAutoSystemClockTime != null) {
|
||||
// Only send the network broadcast if the last candidate would have caused one.
|
||||
final boolean sendNetworkBroadcast = mLastAutoSystemClockTimeSendNetworkBroadcast;
|
||||
@GuardedBy("this")
|
||||
private void setSystemClockUnderWakeLock(
|
||||
int origin, @NonNull TimestampedValue<Long> newTime, @NonNull Object cause) {
|
||||
|
||||
mCallback.acquireWakeLock();
|
||||
try {
|
||||
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
|
||||
long actualTimeMillis = mCallback.systemClockMillis();
|
||||
|
||||
final String reason = "Automatic time detection enabled.";
|
||||
adjustAndSetDeviceSystemClock(mLastAutoSystemClockTime, sendNetworkBroadcast,
|
||||
elapsedRealtimeMillis, actualTimeMillis, reason);
|
||||
} finally {
|
||||
mCallback.releaseWakeLock();
|
||||
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
|
||||
boolean isOriginAutomatic = isOriginAutomatic(origin);
|
||||
long actualSystemClockMillis = mCallback.systemClockMillis();
|
||||
if (isOriginAutomatic) {
|
||||
// CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
|
||||
// may be setting the clock.
|
||||
if (mLastAutoSystemClockTimeSet != null) {
|
||||
long expectedTimeMillis = TimeDetectorStrategy.getTimeAt(
|
||||
mLastAutoSystemClockTimeSet, elapsedRealtimeMillis);
|
||||
long absSystemClockDifference =
|
||||
Math.abs(expectedTimeMillis - actualSystemClockMillis);
|
||||
if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) {
|
||||
Slog.w(LOG_TAG,
|
||||
"System clock has not tracked elapsed real time clock. A clock may"
|
||||
+ " be inaccurate or something unexpectedly set the system"
|
||||
+ " clock."
|
||||
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
|
||||
+ " expectedTimeMillis=" + expectedTimeMillis
|
||||
+ " actualTimeMillis=" + actualSystemClockMillis
|
||||
+ " cause=" + cause);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
|
||||
// it should be in future.
|
||||
mLastAutoSystemClockTimeSet = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
|
||||
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
|
||||
ipw.println("TimeDetectorStrategy:");
|
||||
ipw.increaseIndent(); // level 1
|
||||
|
||||
ipw.println("mLastPhoneSuggestion=" + mLastPhoneSuggestion);
|
||||
ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet);
|
||||
ipw.println("mLastAutoSystemClockTime=" + mLastAutoSystemClockTime);
|
||||
ipw.println("mLastAutoSystemClockTimeSendNetworkBroadcast="
|
||||
+ mLastAutoSystemClockTimeSendNetworkBroadcast);
|
||||
|
||||
|
||||
ipw.println("Time change log:");
|
||||
ipw.increaseIndent(); // level 2
|
||||
mTimeChangesLog.dump(ipw);
|
||||
ipw.decreaseIndent(); // level 2
|
||||
|
||||
ipw.decreaseIndent(); // level 1
|
||||
ipw.flush();
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void adjustAndSetDeviceSystemClock(
|
||||
TimestampedValue<Long> newTime, boolean sendNetworkBroadcast,
|
||||
long elapsedRealtimeMillis, long actualSystemClockMillis, Object cause) {
|
||||
|
||||
// Adjust for the time that has elapsed since the signal was received.
|
||||
long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis);
|
||||
@@ -290,10 +496,17 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
||||
}
|
||||
mTimeChangesLog.log(logMsg);
|
||||
|
||||
// CLOCK_PARANOIA : Record the last time this class set the system clock.
|
||||
mLastAutoSystemClockTimeSet = newTime;
|
||||
// CLOCK_PARANOIA : Record the last time this class set the system clock due to an auto-time
|
||||
// signal, or clear the record it is being done manually.
|
||||
if (isOriginAutomatic(origin)) {
|
||||
mLastAutoSystemClockTimeSet = newTime;
|
||||
} else {
|
||||
mLastAutoSystemClockTimeSet = null;
|
||||
}
|
||||
|
||||
if (sendNetworkBroadcast) {
|
||||
// Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only
|
||||
// when setting the time using NITZ.
|
||||
if (origin == ORIGIN_PHONE) {
|
||||
// Send a broadcast that telephony code used to send after setting the clock.
|
||||
// TODO Remove this broadcast as soon as there are no remaining listeners.
|
||||
Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
|
||||
@@ -302,4 +515,27 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
||||
mCallback.sendStickyBroadcast(intent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current best phone suggestion. Not intended for general use: it is used during
|
||||
* tests to check strategy behavior.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
public synchronized PhoneTimeSuggestion findBestPhoneSuggestionForTests() {
|
||||
return findBestPhoneSuggestion();
|
||||
}
|
||||
|
||||
/**
|
||||
* A method used to inspect state during tests. Not intended for general use.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) {
|
||||
LinkedList<PhoneTimeSuggestion> suggestions = mSuggestionByPhoneId.get(phoneId);
|
||||
if (suggestions == null) {
|
||||
return null;
|
||||
}
|
||||
return suggestions.getFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,12 @@ import java.time.Duration;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SimpleTimeDetectorStrategyTest {
|
||||
|
||||
private static final Scenario SCENARIO_1 = new Scenario.Builder()
|
||||
.setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
|
||||
.setInitialDeviceRealtimeMillis(123456789L)
|
||||
.setActualTimeUtc(2018, 1, 1, 12, 0, 0)
|
||||
.build();
|
||||
private static final TimestampedValue<Long> ARBITRARY_CLOCK_INITIALIZATION_INFO =
|
||||
new TimestampedValue<>(
|
||||
123456789L /* realtimeClockMillis */,
|
||||
createUtcTime(1977, 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;
|
||||
|
||||
@@ -61,116 +62,211 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
|
||||
@Test
|
||||
public void testSuggestPhoneTime_autoTimeEnabled() {
|
||||
Scenario scenario = SCENARIO_1;
|
||||
mScript.pokeFakeClocks(scenario)
|
||||
.pokeTimeDetectionEnabled(true);
|
||||
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||
.pokeAutoTimeDetectionEnabled(true);
|
||||
|
||||
int phoneId = ARBITRARY_PHONE_ID;
|
||||
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||
PhoneTimeSuggestion timeSuggestion =
|
||||
scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
|
||||
final int clockIncrement = 1000;
|
||||
long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
|
||||
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
|
||||
int clockIncrement = 1000;
|
||||
long expectedSystemClockMillis = testTimeMillis + clockIncrement;
|
||||
|
||||
mScript.simulateTimePassing(clockIncrement)
|
||||
.simulatePhoneTimeSuggestion(timeSuggestion)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectSystemClockMillis, true /* expectNetworkBroadcast */);
|
||||
expectedSystemClockMillis, true /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestPhoneTime_emptySuggestionIgnored() {
|
||||
Scenario scenario = SCENARIO_1;
|
||||
mScript.pokeFakeClocks(scenario)
|
||||
.pokeTimeDetectionEnabled(true);
|
||||
|
||||
PhoneTimeSuggestion timeSuggestion = createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, null);
|
||||
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||
.pokeAutoTimeDetectionEnabled(true);
|
||||
|
||||
int phoneId = ARBITRARY_PHONE_ID;
|
||||
PhoneTimeSuggestion timeSuggestion =
|
||||
mScript.generatePhoneTimeSuggestion(phoneId, null);
|
||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion)
|
||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
||||
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||
.assertLatestPhoneSuggestion(phoneId, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestPhoneTime_systemClockThreshold() {
|
||||
Scenario scenario = SCENARIO_1;
|
||||
final int systemClockUpdateThresholdMillis = 1000;
|
||||
mScript.pokeFakeClocks(scenario)
|
||||
int systemClockUpdateThresholdMillis = 1000;
|
||||
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||
.pokeThresholds(systemClockUpdateThresholdMillis)
|
||||
.pokeTimeDetectionEnabled(true);
|
||||
|
||||
PhoneTimeSuggestion timeSuggestion1 =
|
||||
scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
|
||||
TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
|
||||
.pokeAutoTimeDetectionEnabled(true);
|
||||
|
||||
final int clockIncrement = 100;
|
||||
// Increment the the device clocks to simulate the passage of time.
|
||||
mScript.simulateTimePassing(clockIncrement);
|
||||
|
||||
long expectSystemClockMillis1 =
|
||||
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
|
||||
int phoneId = ARBITRARY_PHONE_ID;
|
||||
|
||||
// Send the first time signal. It should be used.
|
||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectSystemClockMillis1, true /* expectNetworkBroadcast */);
|
||||
{
|
||||
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||
PhoneTimeSuggestion timeSuggestion1 =
|
||||
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
|
||||
TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
|
||||
|
||||
// Increment the the device clocks to simulate the passage of time.
|
||||
mScript.simulateTimePassing(clockIncrement);
|
||||
|
||||
long expectedSystemClockMillis1 =
|
||||
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
|
||||
|
||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
|
||||
}
|
||||
|
||||
// Now send another time signal, but one that is too similar to the last one and should be
|
||||
// ignored.
|
||||
int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
|
||||
TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
|
||||
mScript.peekElapsedRealtimeMillis(),
|
||||
mScript.peekSystemClockMillis() + underThresholdMillis);
|
||||
PhoneTimeSuggestion timeSuggestion2 =
|
||||
createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
|
||||
mScript.simulateTimePassing(clockIncrement)
|
||||
.simulatePhoneTimeSuggestion(timeSuggestion2)
|
||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
||||
// stored, but not used to set the system clock.
|
||||
{
|
||||
int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
|
||||
PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion(
|
||||
phoneId, mScript.peekSystemClockMillis() + underThresholdMillis);
|
||||
mScript.simulateTimePassing(clockIncrement)
|
||||
.simulatePhoneTimeSuggestion(timeSuggestion2)
|
||||
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
|
||||
}
|
||||
|
||||
// Now send another time signal, but one that is on the threshold and so should be used.
|
||||
TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
|
||||
mScript.peekElapsedRealtimeMillis(),
|
||||
mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
|
||||
{
|
||||
PhoneTimeSuggestion timeSuggestion3 = mScript.generatePhoneTimeSuggestion(
|
||||
phoneId,
|
||||
mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
|
||||
mScript.simulateTimePassing(clockIncrement);
|
||||
|
||||
long expectedSystemClockMillis3 =
|
||||
TimeDetectorStrategy.getTimeAt(timeSuggestion3.getUtcTime(),
|
||||
mScript.peekElapsedRealtimeMillis());
|
||||
|
||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectedSystemClockMillis3, true /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion3);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestPhoneTime_multiplePhoneIdsAndBucketing() {
|
||||
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||
.pokeAutoTimeDetectionEnabled(true);
|
||||
|
||||
// There are 2 phones in this test. Phone 2 has a different idea of the current time.
|
||||
// phone1Id < phone2Id (which is important because the strategy uses the lowest ID when
|
||||
// multiple phone suggestions are available.
|
||||
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;
|
||||
|
||||
// Make a suggestion with phone2Id.
|
||||
{
|
||||
PhoneTimeSuggestion phone2TimeSuggestion =
|
||||
mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
|
||||
mScript.simulateTimePassing(clockIncrement);
|
||||
|
||||
long expectedSystemClockMillis = phone2TimeMillis + clockIncrement;
|
||||
|
||||
mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectedSystemClockMillis, true /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phone1Id, null)
|
||||
.assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
|
||||
}
|
||||
|
||||
PhoneTimeSuggestion timeSuggestion3 =
|
||||
createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3);
|
||||
mScript.simulateTimePassing(clockIncrement);
|
||||
|
||||
long expectSystemClockMillis3 =
|
||||
TimeDetectorStrategy.getTimeAt(utcTime3, mScript.peekElapsedRealtimeMillis());
|
||||
// Now make a different suggestion with phone1Id.
|
||||
{
|
||||
PhoneTimeSuggestion phone1TimeSuggestion =
|
||||
mScript.generatePhoneTimeSuggestion(phone1Id, phone1TimeMillis);
|
||||
mScript.simulateTimePassing(clockIncrement);
|
||||
|
||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectSystemClockMillis3, true /* expectNetworkBroadcast */);
|
||||
long expectedSystemClockMillis = phone1TimeMillis + clockIncrement;
|
||||
|
||||
mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectedSystemClockMillis, true /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion);
|
||||
|
||||
}
|
||||
|
||||
mScript.simulateTimePassing(clockIncrement);
|
||||
|
||||
// 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.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
|
||||
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||
.assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
|
||||
}
|
||||
|
||||
// Let enough time pass that phone1Id's suggestion should now be too old.
|
||||
mScript.simulateTimePassing(SimpleTimeDetectorStrategy.PHONE_BUCKET_SIZE_MILLIS);
|
||||
|
||||
// Make another suggestion with phone2Id. It should be used because the phoneId1
|
||||
// is in an older "bucket".
|
||||
{
|
||||
PhoneTimeSuggestion phone2TimeSuggestion =
|
||||
mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
|
||||
mScript.simulateTimePassing(clockIncrement);
|
||||
|
||||
long expectedSystemClockMillis = phone2TimeMillis + clockIncrement;
|
||||
|
||||
mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectedSystemClockMillis, true /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestPhoneTime_autoTimeDisabled() {
|
||||
Scenario scenario = SCENARIO_1;
|
||||
mScript.pokeFakeClocks(scenario)
|
||||
.pokeTimeDetectionEnabled(false);
|
||||
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||
.pokeAutoTimeDetectionEnabled(false);
|
||||
|
||||
int phoneId = ARBITRARY_PHONE_ID;
|
||||
PhoneTimeSuggestion timeSuggestion =
|
||||
scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
|
||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion)
|
||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
||||
mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS);
|
||||
mScript.simulateTimePassing(1000)
|
||||
.simulatePhoneTimeSuggestion(timeSuggestion)
|
||||
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() {
|
||||
Scenario scenario = SCENARIO_1;
|
||||
final int systemClockUpdateThreshold = 2000;
|
||||
mScript.pokeFakeClocks(scenario)
|
||||
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||
.pokeThresholds(systemClockUpdateThreshold)
|
||||
.pokeTimeDetectionEnabled(true);
|
||||
.pokeAutoTimeDetectionEnabled(true);
|
||||
|
||||
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||
int phoneId = ARBITRARY_PHONE_ID;
|
||||
|
||||
PhoneTimeSuggestion timeSuggestion1 =
|
||||
scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
|
||||
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
|
||||
TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
|
||||
|
||||
// Initialize the strategy / device with a time set from NITZ.
|
||||
// Initialize the strategy / device with a time set from a phone suggestion.
|
||||
mScript.simulateTimePassing(100);
|
||||
long expectedSystemClockMillis1 =
|
||||
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
|
||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectedSystemClockMillis1, true /* expectNetworkBroadcast */);
|
||||
expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
|
||||
|
||||
// The UTC time increment should be larger than the system clock update threshold so we
|
||||
// know it shouldn't be ignored for other reasons.
|
||||
@@ -182,9 +278,10 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
|
||||
referenceTimeBeforeLastSignalMillis, validUtcTimeMillis);
|
||||
PhoneTimeSuggestion timeSuggestion2 =
|
||||
createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
|
||||
createPhoneTimeSuggestion(phoneId, utcTime2);
|
||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
|
||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
||||
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
|
||||
|
||||
// Now supply a new signal that has an obviously bogus reference time : substantially in the
|
||||
// future.
|
||||
@@ -193,9 +290,10 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
|
||||
referenceTimeInFutureMillis, validUtcTimeMillis);
|
||||
PhoneTimeSuggestion timeSuggestion3 =
|
||||
createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3);
|
||||
createPhoneTimeSuggestion(phoneId, utcTime3);
|
||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
|
||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
||||
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
|
||||
|
||||
// Just to prove validUtcTimeMillis is valid.
|
||||
long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100;
|
||||
@@ -204,23 +302,25 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
long expectedSystemClockMillis4 =
|
||||
TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
|
||||
PhoneTimeSuggestion timeSuggestion4 =
|
||||
createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime4);
|
||||
createPhoneTimeSuggestion(phoneId, utcTime4);
|
||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectedSystemClockMillis4, true /* expectNetworkBroadcast */);
|
||||
expectedSystemClockMillis4, true /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestPhoneTime_timeDetectionToggled() {
|
||||
Scenario scenario = SCENARIO_1;
|
||||
final int clockIncrementMillis = 100;
|
||||
final int systemClockUpdateThreshold = 2000;
|
||||
mScript.pokeFakeClocks(scenario)
|
||||
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||
.pokeThresholds(systemClockUpdateThreshold)
|
||||
.pokeTimeDetectionEnabled(false);
|
||||
.pokeAutoTimeDetectionEnabled(false);
|
||||
|
||||
int phoneId = ARBITRARY_PHONE_ID;
|
||||
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||
PhoneTimeSuggestion timeSuggestion1 =
|
||||
scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
|
||||
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
|
||||
TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
|
||||
|
||||
// Simulate time passing.
|
||||
@@ -229,7 +329,8 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
// Simulate the time signal being received. It should not be used because auto time
|
||||
// detection is off but it should be recorded.
|
||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
|
||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
||||
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
|
||||
|
||||
// Simulate more time passing.
|
||||
mScript.simulateTimePassing(clockIncrementMillis);
|
||||
@@ -240,64 +341,95 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
// Turn on auto time detection.
|
||||
mScript.simulateAutoTimeDetectionToggle()
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectedSystemClockMillis1, true /* expectNetworkBroadcast */);
|
||||
expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
|
||||
|
||||
// Turn off auto time detection.
|
||||
mScript.simulateAutoTimeDetectionToggle()
|
||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
||||
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
|
||||
|
||||
// Receive another valid time signal.
|
||||
// It should be on the threshold and accounting for the clock increments.
|
||||
TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
|
||||
mScript.peekElapsedRealtimeMillis(),
|
||||
mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
|
||||
PhoneTimeSuggestion timeSuggestion2 =
|
||||
createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
|
||||
PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion(
|
||||
phoneId, mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
|
||||
|
||||
// Simulate more time passing.
|
||||
mScript.simulateTimePassing(clockIncrementMillis);
|
||||
|
||||
long expectedSystemClockMillis2 =
|
||||
TimeDetectorStrategy.getTimeAt(utcTime2, mScript.peekElapsedRealtimeMillis());
|
||||
long expectedSystemClockMillis2 = TimeDetectorStrategy.getTimeAt(
|
||||
timeSuggestion2.getUtcTime(), mScript.peekElapsedRealtimeMillis());
|
||||
|
||||
// The new time, though valid, should not be set in the system clock because auto time is
|
||||
// disabled.
|
||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
|
||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
||||
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
|
||||
|
||||
// Turn on auto time detection.
|
||||
mScript.simulateAutoTimeDetectionToggle()
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectedSystemClockMillis2, true /* expectNetworkBroadcast */);
|
||||
expectedSystemClockMillis2, true /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestPhoneTime_maxSuggestionAge() {
|
||||
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||
.pokeAutoTimeDetectionEnabled(true);
|
||||
|
||||
int phoneId = ARBITRARY_PHONE_ID;
|
||||
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||
PhoneTimeSuggestion phoneSuggestion =
|
||||
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
|
||||
int clockIncrementMillis = 1000;
|
||||
|
||||
mScript.simulateTimePassing(clockIncrementMillis)
|
||||
.simulatePhoneTimeSuggestion(phoneSuggestion)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
testTimeMillis + clockIncrementMillis, 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(SimpleTimeDetectorStrategy.PHONE_MAX_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.
|
||||
assertNull(mScript.peekBestPhoneSuggestion());
|
||||
mScript.assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestManualTime_autoTimeDisabled() {
|
||||
Scenario scenario = SCENARIO_1;
|
||||
mScript.pokeFakeClocks(scenario)
|
||||
.pokeTimeDetectionEnabled(false);
|
||||
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||
.pokeAutoTimeDetectionEnabled(false);
|
||||
|
||||
ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual();
|
||||
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||
ManualTimeSuggestion timeSuggestion = mScript.generateManualTimeSuggestion(testTimeMillis);
|
||||
final int clockIncrement = 1000;
|
||||
long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
|
||||
long expectedSystemClockMillis = testTimeMillis + clockIncrement;
|
||||
|
||||
mScript.simulateTimePassing(clockIncrement)
|
||||
.simulateManualTimeSuggestion(timeSuggestion)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectSystemClockMillis, false /* expectNetworkBroadcast */);
|
||||
expectedSystemClockMillis, false /* expectNetworkBroadcast */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestManualTime_retainsAutoSignal() {
|
||||
Scenario scenario = SCENARIO_1;
|
||||
|
||||
// Configure the start state.
|
||||
mScript.pokeFakeClocks(scenario)
|
||||
.pokeTimeDetectionEnabled(true);
|
||||
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||
.pokeAutoTimeDetectionEnabled(true);
|
||||
|
||||
int phoneId = ARBITRARY_PHONE_ID;
|
||||
|
||||
// Simulate a phone suggestion.
|
||||
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||
PhoneTimeSuggestion phoneTimeSuggestion =
|
||||
scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
|
||||
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
|
||||
long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue();
|
||||
final int clockIncrement = 1000;
|
||||
|
||||
@@ -307,7 +439,8 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
|
||||
mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectedAutoClockMillis, true /* expectNetworkBroadcast */);
|
||||
expectedAutoClockMillis, true /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
|
||||
|
||||
// Simulate the passage of time.
|
||||
mScript.simulateTimePassing(clockIncrement);
|
||||
@@ -315,20 +448,22 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
|
||||
// Switch to manual.
|
||||
mScript.simulateAutoTimeDetectionToggle()
|
||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
||||
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
|
||||
|
||||
// Simulate the passage of time.
|
||||
mScript.simulateTimePassing(clockIncrement);
|
||||
expectedAutoClockMillis += clockIncrement;
|
||||
|
||||
|
||||
// Simulate a manual suggestion 1 day different from the auto suggestion.
|
||||
long manualTimeMillis = SCENARIO_1.getActualTimeMillis() + ONE_DAY_MILLIS;
|
||||
long manualTimeMillis = testTimeMillis + ONE_DAY_MILLIS;
|
||||
long expectedManualClockMillis = manualTimeMillis;
|
||||
ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion(manualTimeMillis);
|
||||
ManualTimeSuggestion manualTimeSuggestion =
|
||||
mScript.generateManualTimeSuggestion(manualTimeMillis);
|
||||
mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
|
||||
.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectedManualClockMillis, false /* expectNetworkBroadcast */);
|
||||
expectedManualClockMillis, false /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
|
||||
|
||||
// Simulate the passage of time.
|
||||
mScript.simulateTimePassing(clockIncrement);
|
||||
@@ -338,11 +473,13 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
mScript.simulateAutoTimeDetectionToggle();
|
||||
|
||||
mScript.verifySystemClockWasSetAndResetCallTracking(
|
||||
expectedAutoClockMillis, true /* expectNetworkBroadcast */);
|
||||
expectedAutoClockMillis, true /* expectNetworkBroadcast */)
|
||||
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
|
||||
|
||||
// Switch back to manual - nothing should happen to the clock.
|
||||
mScript.simulateAutoTimeDetectionToggle()
|
||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
||||
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,11 +487,11 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
*/
|
||||
@Test
|
||||
public void testSuggestManualTime_autoTimeEnabled() {
|
||||
Scenario scenario = SCENARIO_1;
|
||||
mScript.pokeFakeClocks(scenario)
|
||||
.pokeTimeDetectionEnabled(true);
|
||||
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||
.pokeAutoTimeDetectionEnabled(true);
|
||||
|
||||
ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual();
|
||||
ManualTimeSuggestion timeSuggestion =
|
||||
mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
|
||||
final int clockIncrement = 1000;
|
||||
|
||||
mScript.simulateTimePassing(clockIncrement)
|
||||
@@ -367,7 +504,7 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
* like the real thing should, it also asserts preconditions.
|
||||
*/
|
||||
private static class FakeCallback implements TimeDetectorStrategy.Callback {
|
||||
private boolean mTimeDetectionEnabled;
|
||||
private boolean mAutoTimeDetectionEnabled;
|
||||
private boolean mWakeLockAcquired;
|
||||
private long mElapsedRealtimeMillis;
|
||||
private long mSystemClockMillis;
|
||||
@@ -384,7 +521,7 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
|
||||
@Override
|
||||
public boolean isAutoTimeDetectionEnabled() {
|
||||
return mTimeDetectionEnabled;
|
||||
return mAutoTimeDetectionEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -397,7 +534,6 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
|
||||
@Override
|
||||
public long elapsedRealtimeMillis() {
|
||||
assertWakeLockAcquired();
|
||||
return mElapsedRealtimeMillis;
|
||||
}
|
||||
|
||||
@@ -428,57 +564,57 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
|
||||
// Methods below are for managing the fake's behavior.
|
||||
|
||||
public void pokeSystemClockUpdateThreshold(int thresholdMillis) {
|
||||
void pokeSystemClockUpdateThreshold(int thresholdMillis) {
|
||||
mSystemClockUpdateThresholdMillis = thresholdMillis;
|
||||
}
|
||||
|
||||
public void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
|
||||
void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
|
||||
mElapsedRealtimeMillis = elapsedRealtimeMillis;
|
||||
}
|
||||
|
||||
public void pokeSystemClockMillis(long systemClockMillis) {
|
||||
void pokeSystemClockMillis(long systemClockMillis) {
|
||||
mSystemClockMillis = systemClockMillis;
|
||||
}
|
||||
|
||||
public void pokeAutoTimeDetectionEnabled(boolean enabled) {
|
||||
mTimeDetectionEnabled = enabled;
|
||||
void pokeAutoTimeDetectionEnabled(boolean enabled) {
|
||||
mAutoTimeDetectionEnabled = enabled;
|
||||
}
|
||||
|
||||
public long peekElapsedRealtimeMillis() {
|
||||
long peekElapsedRealtimeMillis() {
|
||||
return mElapsedRealtimeMillis;
|
||||
}
|
||||
|
||||
public long peekSystemClockMillis() {
|
||||
long peekSystemClockMillis() {
|
||||
return mSystemClockMillis;
|
||||
}
|
||||
|
||||
public void simulateTimePassing(int incrementMillis) {
|
||||
void simulateTimePassing(long incrementMillis) {
|
||||
mElapsedRealtimeMillis += incrementMillis;
|
||||
mSystemClockMillis += incrementMillis;
|
||||
}
|
||||
|
||||
public void simulateAutoTimeZoneDetectionToggle() {
|
||||
mTimeDetectionEnabled = !mTimeDetectionEnabled;
|
||||
void simulateAutoTimeZoneDetectionToggle() {
|
||||
mAutoTimeDetectionEnabled = !mAutoTimeDetectionEnabled;
|
||||
}
|
||||
|
||||
public void verifySystemClockNotSet() {
|
||||
void verifySystemClockNotSet() {
|
||||
assertFalse(mSystemClockWasSet);
|
||||
}
|
||||
|
||||
public void verifySystemClockWasSet(long expectSystemClockMillis) {
|
||||
void verifySystemClockWasSet(long expectedSystemClockMillis) {
|
||||
assertTrue(mSystemClockWasSet);
|
||||
assertEquals(expectSystemClockMillis, mSystemClockMillis);
|
||||
assertEquals(expectedSystemClockMillis, mSystemClockMillis);
|
||||
}
|
||||
|
||||
public void verifyIntentWasBroadcast() {
|
||||
void verifyIntentWasBroadcast() {
|
||||
assertTrue(mBroadcastSent != null);
|
||||
}
|
||||
|
||||
public void verifyIntentWasNotBroadcast() {
|
||||
void verifyIntentWasNotBroadcast() {
|
||||
assertNull(mBroadcastSent);
|
||||
}
|
||||
|
||||
public void resetCallTracking() {
|
||||
void resetCallTracking() {
|
||||
mSystemClockWasSet = false;
|
||||
mBroadcastSent = null;
|
||||
}
|
||||
@@ -495,23 +631,23 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
private class Script {
|
||||
|
||||
private final FakeCallback mFakeCallback;
|
||||
private final SimpleTimeDetectorStrategy mSimpleTimeDetectorStrategy;
|
||||
private final SimpleTimeDetectorStrategy mTimeDetectorStrategy;
|
||||
|
||||
Script() {
|
||||
mFakeCallback = new FakeCallback();
|
||||
mSimpleTimeDetectorStrategy = new SimpleTimeDetectorStrategy();
|
||||
mSimpleTimeDetectorStrategy.initialize(mFakeCallback);
|
||||
mTimeDetectorStrategy = new SimpleTimeDetectorStrategy();
|
||||
mTimeDetectorStrategy.initialize(mFakeCallback);
|
||||
|
||||
}
|
||||
|
||||
Script pokeTimeDetectionEnabled(boolean enabled) {
|
||||
Script pokeAutoTimeDetectionEnabled(boolean enabled) {
|
||||
mFakeCallback.pokeAutoTimeDetectionEnabled(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
Script pokeFakeClocks(Scenario scenario) {
|
||||
mFakeCallback.pokeElapsedRealtimeMillis(scenario.getInitialRealTimeMillis());
|
||||
mFakeCallback.pokeSystemClockMillis(scenario.getInitialSystemClockMillis());
|
||||
Script pokeFakeClocks(TimestampedValue<Long> timeInfo) {
|
||||
mFakeCallback.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
|
||||
mFakeCallback.pokeSystemClockMillis(timeInfo.getValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -529,23 +665,23 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
}
|
||||
|
||||
Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) {
|
||||
mSimpleTimeDetectorStrategy.suggestPhoneTime(timeSuggestion);
|
||||
mTimeDetectorStrategy.suggestPhoneTime(timeSuggestion);
|
||||
return this;
|
||||
}
|
||||
|
||||
Script simulateManualTimeSuggestion(ManualTimeSuggestion timeSuggestion) {
|
||||
mSimpleTimeDetectorStrategy.suggestManualTime(timeSuggestion);
|
||||
mTimeDetectorStrategy.suggestManualTime(timeSuggestion);
|
||||
return this;
|
||||
}
|
||||
|
||||
Script simulateAutoTimeDetectionToggle() {
|
||||
mFakeCallback.simulateAutoTimeZoneDetectionToggle();
|
||||
mSimpleTimeDetectorStrategy.handleAutoTimeDetectionChanged();
|
||||
mTimeDetectorStrategy.handleAutoTimeDetectionChanged();
|
||||
return this;
|
||||
}
|
||||
|
||||
Script simulateTimePassing(int clockIncrement) {
|
||||
mFakeCallback.simulateTimePassing(clockIncrement);
|
||||
Script simulateTimePassing(long clockIncrementMillis) {
|
||||
mFakeCallback.simulateTimePassing(clockIncrementMillis);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -557,85 +693,52 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
}
|
||||
|
||||
Script verifySystemClockWasSetAndResetCallTracking(
|
||||
long expectSystemClockMillis, boolean expectNetworkBroadcast) {
|
||||
mFakeCallback.verifySystemClockWasSet(expectSystemClockMillis);
|
||||
long expectedSystemClockMillis, boolean expectNetworkBroadcast) {
|
||||
mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis);
|
||||
if (expectNetworkBroadcast) {
|
||||
mFakeCallback.verifyIntentWasBroadcast();
|
||||
}
|
||||
mFakeCallback.resetCallTracking();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A starting scenario used during tests. Describes a fictional "physical" reality.
|
||||
*/
|
||||
private static class Scenario {
|
||||
|
||||
private final long mInitialDeviceSystemClockMillis;
|
||||
private final long mInitialDeviceRealtimeMillis;
|
||||
private final long mActualTimeMillis;
|
||||
|
||||
Scenario(long initialDeviceSystemClock, long elapsedRealtime, long timeMillis) {
|
||||
mInitialDeviceSystemClockMillis = initialDeviceSystemClock;
|
||||
mActualTimeMillis = timeMillis;
|
||||
mInitialDeviceRealtimeMillis = elapsedRealtime;
|
||||
/**
|
||||
* White box test info: Asserts the latest suggestion for the phone ID is as expected.
|
||||
*/
|
||||
Script assertLatestPhoneSuggestion(int phoneId, PhoneTimeSuggestion expected) {
|
||||
assertEquals(expected, mTimeDetectorStrategy.getLatestPhoneSuggestion(phoneId));
|
||||
return this;
|
||||
}
|
||||
|
||||
long getInitialRealTimeMillis() {
|
||||
return mInitialDeviceRealtimeMillis;
|
||||
/**
|
||||
* White box test info: Returns the phone suggestion that would be used, if any, given the
|
||||
* current elapsed real time clock.
|
||||
*/
|
||||
PhoneTimeSuggestion peekBestPhoneSuggestion() {
|
||||
return mTimeDetectorStrategy.findBestPhoneSuggestionForTests();
|
||||
}
|
||||
|
||||
long getInitialSystemClockMillis() {
|
||||
return mInitialDeviceSystemClockMillis;
|
||||
/**
|
||||
* Generates a ManualTimeSuggestion using the current elapsed realtime clock for the
|
||||
* reference time.
|
||||
*/
|
||||
ManualTimeSuggestion generateManualTimeSuggestion(long timeMillis) {
|
||||
TimestampedValue<Long> utcTime =
|
||||
new TimestampedValue<>(mFakeCallback.peekElapsedRealtimeMillis(), timeMillis);
|
||||
return new ManualTimeSuggestion(utcTime);
|
||||
}
|
||||
|
||||
long getActualTimeMillis() {
|
||||
return mActualTimeMillis;
|
||||
}
|
||||
|
||||
PhoneTimeSuggestion createPhoneTimeSuggestionForActual(int phoneId) {
|
||||
TimestampedValue<Long> time = new TimestampedValue<>(
|
||||
mInitialDeviceRealtimeMillis, mActualTimeMillis);
|
||||
/**
|
||||
* Generates a PhoneTimeSuggestion using the current elapsed realtime clock for the
|
||||
* reference time.
|
||||
*/
|
||||
PhoneTimeSuggestion generatePhoneTimeSuggestion(int phoneId, Long timeMillis) {
|
||||
TimestampedValue<Long> time = null;
|
||||
if (timeMillis != null) {
|
||||
time = new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis);
|
||||
}
|
||||
return createPhoneTimeSuggestion(phoneId, time);
|
||||
}
|
||||
|
||||
ManualTimeSuggestion createManualTimeSuggestionForActual() {
|
||||
TimestampedValue<Long> time = new TimestampedValue<>(
|
||||
mInitialDeviceRealtimeMillis, mActualTimeMillis);
|
||||
return new ManualTimeSuggestion(time);
|
||||
}
|
||||
|
||||
static class Builder {
|
||||
|
||||
private long mInitialDeviceSystemClockMillis;
|
||||
private long mInitialDeviceRealtimeMillis;
|
||||
private long mActualTimeMillis;
|
||||
|
||||
Builder setInitialDeviceSystemClockUtc(int year, int monthInYear, int day,
|
||||
int hourOfDay, int minute, int second) {
|
||||
mInitialDeviceSystemClockMillis = createUtcTime(year, monthInYear, day, hourOfDay,
|
||||
minute, second);
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setInitialDeviceRealtimeMillis(long realtimeMillis) {
|
||||
mInitialDeviceRealtimeMillis = realtimeMillis;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setActualTimeUtc(int year, int monthInYear, int day, int hourOfDay,
|
||||
int minute, int second) {
|
||||
mActualTimeMillis =
|
||||
createUtcTime(year, monthInYear, day, hourOfDay, minute, second);
|
||||
return this;
|
||||
}
|
||||
|
||||
Scenario build() {
|
||||
return new Scenario(mInitialDeviceSystemClockMillis, mInitialDeviceRealtimeMillis,
|
||||
mActualTimeMillis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId,
|
||||
@@ -645,12 +748,6 @@ public class SimpleTimeDetectorStrategyTest {
|
||||
.build();
|
||||
}
|
||||
|
||||
private ManualTimeSuggestion createManualTimeSuggestion(long timeMillis) {
|
||||
TimestampedValue<Long> utcTime =
|
||||
new TimestampedValue<>(mScript.peekElapsedRealtimeMillis(), timeMillis);
|
||||
return new ManualTimeSuggestion(utcTime);
|
||||
}
|
||||
|
||||
private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
|
||||
int second) {
|
||||
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
|
||||
|
||||
Reference in New Issue
Block a user