Handle multiple phoneIds in time detection
Android has dual sim devices, which means multiple "phone" time signals. NITZ is generally trusted and considered "good enough" in the absence of better signals, but it is provided by carriers and it's not unusual for the signals to be incorrect by minutes. We don't want the device system clock just flicking to the latest signal received. The changes to the SimpleTimeDetectorStrategy try to balance recency and provide some consistency / determinism. See comments in the code for details. The time zone detection works in a similar way, particularly with respect to choosing the lowest phoneId in the event of a tie. There will be a follow-up change to remove the word "Simple" from SimpleTimeDetectorStrategy as it no longer applies. Test: atest services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java Test: atest android.app.timedetector Bug: 140712361 Change-Id: I228aff8709eabfcec910af22f7ab08fee32d566a
This commit is contained in:
@@ -56,6 +56,7 @@ public final class ManualTimeSuggestion implements Parcelable {
|
|||||||
|
|
||||||
public ManualTimeSuggestion(@NonNull TimestampedValue<Long> utcTime) {
|
public ManualTimeSuggestion(@NonNull TimestampedValue<Long> utcTime) {
|
||||||
mUtcTime = Objects.requireNonNull(utcTime);
|
mUtcTime = Objects.requireNonNull(utcTime);
|
||||||
|
Objects.requireNonNull(utcTime.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ManualTimeSuggestion createFromParcel(Parcel in) {
|
private static ManualTimeSuggestion createFromParcel(Parcel in) {
|
||||||
|
|||||||
@@ -166,7 +166,12 @@ public final class PhoneTimeSuggestion implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the builder for call chaining. */
|
/** 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;
|
mUtcTime = utcTime;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,21 +23,26 @@ import android.app.AlarmManager;
|
|||||||
import android.app.timedetector.ManualTimeSuggestion;
|
import android.app.timedetector.ManualTimeSuggestion;
|
||||||
import android.app.timedetector.PhoneTimeSuggestion;
|
import android.app.timedetector.PhoneTimeSuggestion;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.util.ArrayMap;
|
||||||
import android.util.LocalLog;
|
import android.util.LocalLog;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.util.TimestampedValue;
|
import android.util.TimestampedValue;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.telephony.TelephonyIntents;
|
import com.android.internal.telephony.TelephonyIntents;
|
||||||
import com.android.internal.util.IndentingPrintWriter;
|
import com.android.internal.util.IndentingPrintWriter;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of TimeDetectorStrategy that passes only NITZ suggestions to
|
* An implementation of TimeDetectorStrategy that passes phone and manual suggestions to
|
||||||
* {@link AlarmManager}.
|
* {@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.
|
* <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 boolean DBG = false;
|
||||||
private static final String LOG_TAG = "SimpleTimeDetectorStrategy";
|
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 })
|
@IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
public @interface Origin {}
|
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
|
* 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
|
||||||
* 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;
|
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.
|
// A log for changes made to the system clock and why.
|
||||||
@NonNull
|
@NonNull
|
||||||
private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
|
private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
|
||||||
@@ -72,15 +91,22 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
|||||||
// @NonNull after initialize()
|
// @NonNull after initialize()
|
||||||
private Callback mCallback;
|
private Callback mCallback;
|
||||||
|
|
||||||
// Last phone suggestion.
|
// Used to store the last time the system clock state was set automatically. It is used to
|
||||||
@Nullable private PhoneTimeSuggestion mLastPhoneSuggestion;
|
// 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;
|
* A mapping from phoneId to a linked list of time suggestions (the "first" being the latest).
|
||||||
private boolean mLastAutoSystemClockTimeSendNetworkBroadcast;
|
* 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
|
||||||
// System clock state.
|
* the ID will not exceed {@link #KEEP_SUGGESTION_HISTORY_SIZE} in size.
|
||||||
@Nullable private TimestampedValue<Long> mLastAutoSystemClockTimeSet;
|
*/
|
||||||
|
@GuardedBy("this")
|
||||||
|
private ArrayMap<Integer, LinkedList<PhoneTimeSuggestion>> mSuggestionByPhoneId =
|
||||||
|
new ArrayMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(@NonNull Callback callback) {
|
public void initialize(@NonNull Callback callback) {
|
||||||
@@ -88,66 +114,297 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
|
public synchronized void suggestManualTime(ManualTimeSuggestion suggestion) {
|
||||||
// NITZ logic
|
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) {
|
if (timeSuggestion.getUtcTime() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean timeSuggestionIsValid =
|
// Perform validation / input filtering and record the validated suggestion against the
|
||||||
validateNewPhoneSuggestion(timeSuggestion, mLastPhoneSuggestion);
|
// phoneId.
|
||||||
if (!timeSuggestionIsValid) {
|
if (!validateAndStorePhoneSuggestion(timeSuggestion)) {
|
||||||
return;
|
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.
|
// Now perform auto time detection. The new suggestion may be used to modify the system
|
||||||
final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
|
// clock.
|
||||||
setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, timeSuggestion);
|
String reason = "New phone time suggested. timeSuggestion=" + timeSuggestion;
|
||||||
|
doAutoTimeDetection(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void suggestManualTime(ManualTimeSuggestion timeSuggestion) {
|
public synchronized void handleAutoTimeDetectionChanged() {
|
||||||
final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
|
boolean enabled = mCallback.isAutoTimeDetectionEnabled();
|
||||||
setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, timeSuggestion);
|
// 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,
|
@Override
|
||||||
@Nullable PhoneTimeSuggestion lastSuggestion) {
|
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) {
|
ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet);
|
||||||
long referenceTimeDifference = TimestampedValue.referenceTimeDifference(
|
|
||||||
newSuggestion.getUtcTime(), lastSuggestion.getUtcTime());
|
ipw.println("Time change log:");
|
||||||
if (referenceTimeDifference < 0 || referenceTimeDifference > Integer.MAX_VALUE) {
|
ipw.increaseIndent(); // level 2
|
||||||
// Out of order or bogus.
|
mTimeChangesLog.dump(ipw);
|
||||||
Slog.w(LOG_TAG, "Bad NITZ signal received."
|
ipw.decreaseIndent(); // level 2
|
||||||
+ " referenceTimeDifference=" + referenceTimeDifference
|
|
||||||
+ " lastSuggestion=" + lastSuggestion
|
ipw.println("Phone suggestion history:");
|
||||||
+ " newSuggestion=" + newSuggestion);
|
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;
|
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;
|
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")
|
@GuardedBy("this")
|
||||||
private void setSystemClockIfRequired(
|
private void setSystemClockIfRequired(
|
||||||
@Origin int origin, TimestampedValue<Long> time, Object cause) {
|
@Origin int origin, @NonNull TimestampedValue<Long> time, @NonNull String cause) {
|
||||||
// Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only
|
|
||||||
// when setting the time using NITZ.
|
|
||||||
boolean sendNetworkBroadcast = origin == ORIGIN_PHONE;
|
|
||||||
|
|
||||||
boolean isOriginAutomatic = isOriginAutomatic(origin);
|
boolean isOriginAutomatic = isOriginAutomatic(origin);
|
||||||
if (isOriginAutomatic) {
|
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 (!mCallback.isAutoTimeDetectionEnabled()) {
|
||||||
if (DBG) {
|
if (DBG) {
|
||||||
Slog.d(LOG_TAG, "Auto time detection is not enabled."
|
Slog.d(LOG_TAG, "Auto time detection is not enabled."
|
||||||
@@ -171,30 +428,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
|||||||
|
|
||||||
mCallback.acquireWakeLock();
|
mCallback.acquireWakeLock();
|
||||||
try {
|
try {
|
||||||
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
|
setSystemClockUnderWakeLock(origin, time, cause);
|
||||||
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);
|
|
||||||
} finally {
|
} finally {
|
||||||
mCallback.releaseWakeLock();
|
mCallback.releaseWakeLock();
|
||||||
}
|
}
|
||||||
@@ -204,61 +438,33 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
|||||||
return origin == ORIGIN_PHONE;
|
return origin == ORIGIN_PHONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@GuardedBy("this")
|
||||||
public synchronized void handleAutoTimeDetectionChanged() {
|
private void setSystemClockUnderWakeLock(
|
||||||
// If automatic time detection is enabled we update the system clock instantly if we can.
|
int origin, @NonNull TimestampedValue<Long> newTime, @NonNull Object cause) {
|
||||||
// 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;
|
|
||||||
|
|
||||||
mCallback.acquireWakeLock();
|
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
|
||||||
try {
|
boolean isOriginAutomatic = isOriginAutomatic(origin);
|
||||||
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
|
long actualSystemClockMillis = mCallback.systemClockMillis();
|
||||||
long actualTimeMillis = mCallback.systemClockMillis();
|
if (isOriginAutomatic) {
|
||||||
|
// CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
|
||||||
final String reason = "Automatic time detection enabled.";
|
// may be setting the clock.
|
||||||
adjustAndSetDeviceSystemClock(mLastAutoSystemClockTime, sendNetworkBroadcast,
|
if (mLastAutoSystemClockTimeSet != null) {
|
||||||
elapsedRealtimeMillis, actualTimeMillis, reason);
|
long expectedTimeMillis = TimeDetectorStrategy.getTimeAt(
|
||||||
} finally {
|
mLastAutoSystemClockTimeSet, elapsedRealtimeMillis);
|
||||||
mCallback.releaseWakeLock();
|
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.
|
// Adjust for the time that has elapsed since the signal was received.
|
||||||
long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis);
|
long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis);
|
||||||
@@ -290,10 +496,17 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
|||||||
}
|
}
|
||||||
mTimeChangesLog.log(logMsg);
|
mTimeChangesLog.log(logMsg);
|
||||||
|
|
||||||
// CLOCK_PARANOIA : Record the last time this class set the system clock.
|
// CLOCK_PARANOIA : Record the last time this class set the system clock due to an auto-time
|
||||||
mLastAutoSystemClockTimeSet = newTime;
|
// 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.
|
// 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.
|
// TODO Remove this broadcast as soon as there are no remaining listeners.
|
||||||
Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
|
Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
|
||||||
@@ -302,4 +515,27 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
|||||||
mCallback.sendStickyBroadcast(intent);
|
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)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class SimpleTimeDetectorStrategyTest {
|
public class SimpleTimeDetectorStrategyTest {
|
||||||
|
|
||||||
private static final Scenario SCENARIO_1 = new Scenario.Builder()
|
private static final TimestampedValue<Long> ARBITRARY_CLOCK_INITIALIZATION_INFO =
|
||||||
.setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
|
new TimestampedValue<>(
|
||||||
.setInitialDeviceRealtimeMillis(123456789L)
|
123456789L /* realtimeClockMillis */,
|
||||||
.setActualTimeUtc(2018, 1, 1, 12, 0, 0)
|
createUtcTime(1977, 1, 1, 12, 0, 0));
|
||||||
.build();
|
|
||||||
|
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;
|
||||||
|
|
||||||
@@ -61,116 +62,211 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSuggestPhoneTime_autoTimeEnabled() {
|
public void testSuggestPhoneTime_autoTimeEnabled() {
|
||||||
Scenario scenario = SCENARIO_1;
|
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||||
mScript.pokeFakeClocks(scenario)
|
.pokeAutoTimeDetectionEnabled(true);
|
||||||
.pokeTimeDetectionEnabled(true);
|
|
||||||
|
|
||||||
|
int phoneId = ARBITRARY_PHONE_ID;
|
||||||
|
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||||
PhoneTimeSuggestion timeSuggestion =
|
PhoneTimeSuggestion timeSuggestion =
|
||||||
scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
|
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
|
||||||
final int clockIncrement = 1000;
|
int clockIncrement = 1000;
|
||||||
long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
|
long expectedSystemClockMillis = testTimeMillis + clockIncrement;
|
||||||
|
|
||||||
mScript.simulateTimePassing(clockIncrement)
|
mScript.simulateTimePassing(clockIncrement)
|
||||||
.simulatePhoneTimeSuggestion(timeSuggestion)
|
.simulatePhoneTimeSuggestion(timeSuggestion)
|
||||||
.verifySystemClockWasSetAndResetCallTracking(
|
.verifySystemClockWasSetAndResetCallTracking(
|
||||||
expectSystemClockMillis, true /* expectNetworkBroadcast */);
|
expectedSystemClockMillis, true /* expectNetworkBroadcast */)
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, timeSuggestion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSuggestPhoneTime_emptySuggestionIgnored() {
|
public void testSuggestPhoneTime_emptySuggestionIgnored() {
|
||||||
Scenario scenario = SCENARIO_1;
|
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||||
mScript.pokeFakeClocks(scenario)
|
.pokeAutoTimeDetectionEnabled(true);
|
||||||
.pokeTimeDetectionEnabled(true);
|
|
||||||
|
|
||||||
PhoneTimeSuggestion timeSuggestion = createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, null);
|
|
||||||
|
|
||||||
|
int phoneId = ARBITRARY_PHONE_ID;
|
||||||
|
PhoneTimeSuggestion timeSuggestion =
|
||||||
|
mScript.generatePhoneTimeSuggestion(phoneId, null);
|
||||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion)
|
mScript.simulatePhoneTimeSuggestion(timeSuggestion)
|
||||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSuggestPhoneTime_systemClockThreshold() {
|
public void testSuggestPhoneTime_systemClockThreshold() {
|
||||||
Scenario scenario = SCENARIO_1;
|
int systemClockUpdateThresholdMillis = 1000;
|
||||||
final int systemClockUpdateThresholdMillis = 1000;
|
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||||
mScript.pokeFakeClocks(scenario)
|
|
||||||
.pokeThresholds(systemClockUpdateThresholdMillis)
|
.pokeThresholds(systemClockUpdateThresholdMillis)
|
||||||
.pokeTimeDetectionEnabled(true);
|
.pokeAutoTimeDetectionEnabled(true);
|
||||||
|
|
||||||
PhoneTimeSuggestion timeSuggestion1 =
|
|
||||||
scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
|
|
||||||
TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
|
|
||||||
|
|
||||||
final int clockIncrement = 100;
|
final int clockIncrement = 100;
|
||||||
// Increment the the device clocks to simulate the passage of time.
|
int phoneId = ARBITRARY_PHONE_ID;
|
||||||
mScript.simulateTimePassing(clockIncrement);
|
|
||||||
|
|
||||||
long expectSystemClockMillis1 =
|
|
||||||
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
|
|
||||||
|
|
||||||
// Send the first time signal. It should be used.
|
// Send the first time signal. It should be used.
|
||||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
|
{
|
||||||
.verifySystemClockWasSetAndResetCallTracking(
|
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||||
expectSystemClockMillis1, true /* expectNetworkBroadcast */);
|
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
|
// Now send another time signal, but one that is too similar to the last one and should be
|
||||||
// ignored.
|
// stored, but not used to set the system clock.
|
||||||
int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
|
{
|
||||||
TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
|
int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
|
||||||
mScript.peekElapsedRealtimeMillis(),
|
PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion(
|
||||||
mScript.peekSystemClockMillis() + underThresholdMillis);
|
phoneId, mScript.peekSystemClockMillis() + underThresholdMillis);
|
||||||
PhoneTimeSuggestion timeSuggestion2 =
|
mScript.simulateTimePassing(clockIncrement)
|
||||||
createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
|
.simulatePhoneTimeSuggestion(timeSuggestion2)
|
||||||
mScript.simulateTimePassing(clockIncrement)
|
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||||
.simulatePhoneTimeSuggestion(timeSuggestion2)
|
.assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
|
||||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
}
|
||||||
|
|
||||||
// Now send another time signal, but one that is on the threshold and so should be used.
|
// Now send another time signal, but one that is on the threshold and so should be used.
|
||||||
TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
|
{
|
||||||
mScript.peekElapsedRealtimeMillis(),
|
PhoneTimeSuggestion timeSuggestion3 = mScript.generatePhoneTimeSuggestion(
|
||||||
mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
|
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);
|
mScript.simulateTimePassing(clockIncrement);
|
||||||
|
|
||||||
long expectSystemClockMillis3 =
|
// Now make a different suggestion with phone1Id.
|
||||||
TimeDetectorStrategy.getTimeAt(utcTime3, mScript.peekElapsedRealtimeMillis());
|
{
|
||||||
|
PhoneTimeSuggestion phone1TimeSuggestion =
|
||||||
|
mScript.generatePhoneTimeSuggestion(phone1Id, phone1TimeMillis);
|
||||||
|
mScript.simulateTimePassing(clockIncrement);
|
||||||
|
|
||||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
|
long expectedSystemClockMillis = phone1TimeMillis + clockIncrement;
|
||||||
.verifySystemClockWasSetAndResetCallTracking(
|
|
||||||
expectSystemClockMillis3, true /* expectNetworkBroadcast */);
|
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
|
@Test
|
||||||
public void testSuggestPhoneTime_autoTimeDisabled() {
|
public void testSuggestPhoneTime_autoTimeDisabled() {
|
||||||
Scenario scenario = SCENARIO_1;
|
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||||
mScript.pokeFakeClocks(scenario)
|
.pokeAutoTimeDetectionEnabled(false);
|
||||||
.pokeTimeDetectionEnabled(false);
|
|
||||||
|
|
||||||
|
int phoneId = ARBITRARY_PHONE_ID;
|
||||||
PhoneTimeSuggestion timeSuggestion =
|
PhoneTimeSuggestion timeSuggestion =
|
||||||
scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
|
mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS);
|
||||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion)
|
mScript.simulateTimePassing(1000)
|
||||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
.simulatePhoneTimeSuggestion(timeSuggestion)
|
||||||
|
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, timeSuggestion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() {
|
public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() {
|
||||||
Scenario scenario = SCENARIO_1;
|
|
||||||
final int systemClockUpdateThreshold = 2000;
|
final int systemClockUpdateThreshold = 2000;
|
||||||
mScript.pokeFakeClocks(scenario)
|
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||||
.pokeThresholds(systemClockUpdateThreshold)
|
.pokeThresholds(systemClockUpdateThreshold)
|
||||||
.pokeTimeDetectionEnabled(true);
|
.pokeAutoTimeDetectionEnabled(true);
|
||||||
|
|
||||||
|
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||||
|
int phoneId = ARBITRARY_PHONE_ID;
|
||||||
|
|
||||||
PhoneTimeSuggestion timeSuggestion1 =
|
PhoneTimeSuggestion timeSuggestion1 =
|
||||||
scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
|
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
|
||||||
TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
|
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);
|
mScript.simulateTimePassing(100);
|
||||||
long expectedSystemClockMillis1 =
|
long expectedSystemClockMillis1 =
|
||||||
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
|
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
|
||||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
|
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
|
||||||
.verifySystemClockWasSetAndResetCallTracking(
|
.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
|
// The UTC time increment should be larger than the system clock update threshold so we
|
||||||
// know it shouldn't be ignored for other reasons.
|
// know it shouldn't be ignored for other reasons.
|
||||||
@@ -182,9 +278,10 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
|
TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
|
||||||
referenceTimeBeforeLastSignalMillis, validUtcTimeMillis);
|
referenceTimeBeforeLastSignalMillis, validUtcTimeMillis);
|
||||||
PhoneTimeSuggestion timeSuggestion2 =
|
PhoneTimeSuggestion timeSuggestion2 =
|
||||||
createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
|
createPhoneTimeSuggestion(phoneId, utcTime2);
|
||||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
|
mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
|
||||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
|
||||||
|
|
||||||
// Now supply a new signal that has an obviously bogus reference time : substantially in the
|
// Now supply a new signal that has an obviously bogus reference time : substantially in the
|
||||||
// future.
|
// future.
|
||||||
@@ -193,9 +290,10 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
|
TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
|
||||||
referenceTimeInFutureMillis, validUtcTimeMillis);
|
referenceTimeInFutureMillis, validUtcTimeMillis);
|
||||||
PhoneTimeSuggestion timeSuggestion3 =
|
PhoneTimeSuggestion timeSuggestion3 =
|
||||||
createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3);
|
createPhoneTimeSuggestion(phoneId, utcTime3);
|
||||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
|
mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
|
||||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
|
||||||
|
|
||||||
// Just to prove validUtcTimeMillis is valid.
|
// Just to prove validUtcTimeMillis is valid.
|
||||||
long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100;
|
long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100;
|
||||||
@@ -204,23 +302,25 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
long expectedSystemClockMillis4 =
|
long expectedSystemClockMillis4 =
|
||||||
TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
|
TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
|
||||||
PhoneTimeSuggestion timeSuggestion4 =
|
PhoneTimeSuggestion timeSuggestion4 =
|
||||||
createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime4);
|
createPhoneTimeSuggestion(phoneId, utcTime4);
|
||||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
|
mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
|
||||||
.verifySystemClockWasSetAndResetCallTracking(
|
.verifySystemClockWasSetAndResetCallTracking(
|
||||||
expectedSystemClockMillis4, true /* expectNetworkBroadcast */);
|
expectedSystemClockMillis4, true /* expectNetworkBroadcast */)
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, timeSuggestion4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSuggestPhoneTime_timeDetectionToggled() {
|
public void testSuggestPhoneTime_timeDetectionToggled() {
|
||||||
Scenario scenario = SCENARIO_1;
|
|
||||||
final int clockIncrementMillis = 100;
|
final int clockIncrementMillis = 100;
|
||||||
final int systemClockUpdateThreshold = 2000;
|
final int systemClockUpdateThreshold = 2000;
|
||||||
mScript.pokeFakeClocks(scenario)
|
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||||
.pokeThresholds(systemClockUpdateThreshold)
|
.pokeThresholds(systemClockUpdateThreshold)
|
||||||
.pokeTimeDetectionEnabled(false);
|
.pokeAutoTimeDetectionEnabled(false);
|
||||||
|
|
||||||
|
int phoneId = ARBITRARY_PHONE_ID;
|
||||||
|
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||||
PhoneTimeSuggestion timeSuggestion1 =
|
PhoneTimeSuggestion timeSuggestion1 =
|
||||||
scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
|
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
|
||||||
TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
|
TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
|
||||||
|
|
||||||
// Simulate time passing.
|
// Simulate time passing.
|
||||||
@@ -229,7 +329,8 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
// Simulate the time signal being received. It should not be used because auto time
|
// Simulate the time signal being received. It should not be used because auto time
|
||||||
// detection is off but it should be recorded.
|
// detection is off but it should be recorded.
|
||||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
|
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
|
||||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
|
||||||
|
|
||||||
// Simulate more time passing.
|
// Simulate more time passing.
|
||||||
mScript.simulateTimePassing(clockIncrementMillis);
|
mScript.simulateTimePassing(clockIncrementMillis);
|
||||||
@@ -240,64 +341,95 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
// Turn on auto time detection.
|
// Turn on auto time detection.
|
||||||
mScript.simulateAutoTimeDetectionToggle()
|
mScript.simulateAutoTimeDetectionToggle()
|
||||||
.verifySystemClockWasSetAndResetCallTracking(
|
.verifySystemClockWasSetAndResetCallTracking(
|
||||||
expectedSystemClockMillis1, true /* expectNetworkBroadcast */);
|
expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
|
||||||
|
|
||||||
// Turn off auto time detection.
|
// Turn off auto time detection.
|
||||||
mScript.simulateAutoTimeDetectionToggle()
|
mScript.simulateAutoTimeDetectionToggle()
|
||||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
|
||||||
|
|
||||||
// Receive another valid time signal.
|
// Receive another valid time signal.
|
||||||
// It should be on the threshold and accounting for the clock increments.
|
// It should be on the threshold and accounting for the clock increments.
|
||||||
TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
|
PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion(
|
||||||
mScript.peekElapsedRealtimeMillis(),
|
phoneId, mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
|
||||||
mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
|
|
||||||
PhoneTimeSuggestion timeSuggestion2 =
|
|
||||||
createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
|
|
||||||
|
|
||||||
// Simulate more time passing.
|
// Simulate more time passing.
|
||||||
mScript.simulateTimePassing(clockIncrementMillis);
|
mScript.simulateTimePassing(clockIncrementMillis);
|
||||||
|
|
||||||
long expectedSystemClockMillis2 =
|
long expectedSystemClockMillis2 = TimeDetectorStrategy.getTimeAt(
|
||||||
TimeDetectorStrategy.getTimeAt(utcTime2, mScript.peekElapsedRealtimeMillis());
|
timeSuggestion2.getUtcTime(), mScript.peekElapsedRealtimeMillis());
|
||||||
|
|
||||||
// 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.
|
||||||
mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
|
mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
|
||||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
|
||||||
|
|
||||||
// Turn on auto time detection.
|
// Turn on auto time detection.
|
||||||
mScript.simulateAutoTimeDetectionToggle()
|
mScript.simulateAutoTimeDetectionToggle()
|
||||||
.verifySystemClockWasSetAndResetCallTracking(
|
.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
|
@Test
|
||||||
public void testSuggestManualTime_autoTimeDisabled() {
|
public void testSuggestManualTime_autoTimeDisabled() {
|
||||||
Scenario scenario = SCENARIO_1;
|
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||||
mScript.pokeFakeClocks(scenario)
|
.pokeAutoTimeDetectionEnabled(false);
|
||||||
.pokeTimeDetectionEnabled(false);
|
|
||||||
|
|
||||||
ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual();
|
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||||
|
ManualTimeSuggestion timeSuggestion = mScript.generateManualTimeSuggestion(testTimeMillis);
|
||||||
final int clockIncrement = 1000;
|
final int clockIncrement = 1000;
|
||||||
long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
|
long expectedSystemClockMillis = testTimeMillis + clockIncrement;
|
||||||
|
|
||||||
mScript.simulateTimePassing(clockIncrement)
|
mScript.simulateTimePassing(clockIncrement)
|
||||||
.simulateManualTimeSuggestion(timeSuggestion)
|
.simulateManualTimeSuggestion(timeSuggestion)
|
||||||
.verifySystemClockWasSetAndResetCallTracking(
|
.verifySystemClockWasSetAndResetCallTracking(
|
||||||
expectSystemClockMillis, false /* expectNetworkBroadcast */);
|
expectedSystemClockMillis, false /* expectNetworkBroadcast */);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSuggestManualTime_retainsAutoSignal() {
|
public void testSuggestManualTime_retainsAutoSignal() {
|
||||||
Scenario scenario = SCENARIO_1;
|
|
||||||
|
|
||||||
// Configure the start state.
|
// Configure the start state.
|
||||||
mScript.pokeFakeClocks(scenario)
|
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||||
.pokeTimeDetectionEnabled(true);
|
.pokeAutoTimeDetectionEnabled(true);
|
||||||
|
|
||||||
|
int phoneId = ARBITRARY_PHONE_ID;
|
||||||
|
|
||||||
// Simulate a phone suggestion.
|
// Simulate a phone suggestion.
|
||||||
|
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
|
||||||
PhoneTimeSuggestion phoneTimeSuggestion =
|
PhoneTimeSuggestion phoneTimeSuggestion =
|
||||||
scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
|
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
|
||||||
long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue();
|
long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue();
|
||||||
final int clockIncrement = 1000;
|
final int clockIncrement = 1000;
|
||||||
|
|
||||||
@@ -307,7 +439,8 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
|
|
||||||
mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
|
mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
|
||||||
.verifySystemClockWasSetAndResetCallTracking(
|
.verifySystemClockWasSetAndResetCallTracking(
|
||||||
expectedAutoClockMillis, true /* expectNetworkBroadcast */);
|
expectedAutoClockMillis, true /* expectNetworkBroadcast */)
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
|
||||||
|
|
||||||
// Simulate the passage of time.
|
// Simulate the passage of time.
|
||||||
mScript.simulateTimePassing(clockIncrement);
|
mScript.simulateTimePassing(clockIncrement);
|
||||||
@@ -315,20 +448,22 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
|
|
||||||
// Switch to manual.
|
// Switch to manual.
|
||||||
mScript.simulateAutoTimeDetectionToggle()
|
mScript.simulateAutoTimeDetectionToggle()
|
||||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
|
||||||
|
|
||||||
// Simulate the passage of time.
|
// Simulate the passage of time.
|
||||||
mScript.simulateTimePassing(clockIncrement);
|
mScript.simulateTimePassing(clockIncrement);
|
||||||
expectedAutoClockMillis += clockIncrement;
|
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 = SCENARIO_1.getActualTimeMillis() + ONE_DAY_MILLIS;
|
long manualTimeMillis = testTimeMillis + ONE_DAY_MILLIS;
|
||||||
long expectedManualClockMillis = manualTimeMillis;
|
long expectedManualClockMillis = manualTimeMillis;
|
||||||
ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion(manualTimeMillis);
|
ManualTimeSuggestion manualTimeSuggestion =
|
||||||
|
mScript.generateManualTimeSuggestion(manualTimeMillis);
|
||||||
mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
|
mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
|
||||||
.verifySystemClockWasSetAndResetCallTracking(
|
.verifySystemClockWasSetAndResetCallTracking(
|
||||||
expectedManualClockMillis, false /* expectNetworkBroadcast */);
|
expectedManualClockMillis, false /* expectNetworkBroadcast */)
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
|
||||||
|
|
||||||
// Simulate the passage of time.
|
// Simulate the passage of time.
|
||||||
mScript.simulateTimePassing(clockIncrement);
|
mScript.simulateTimePassing(clockIncrement);
|
||||||
@@ -338,11 +473,13 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
mScript.simulateAutoTimeDetectionToggle();
|
mScript.simulateAutoTimeDetectionToggle();
|
||||||
|
|
||||||
mScript.verifySystemClockWasSetAndResetCallTracking(
|
mScript.verifySystemClockWasSetAndResetCallTracking(
|
||||||
expectedAutoClockMillis, true /* expectNetworkBroadcast */);
|
expectedAutoClockMillis, true /* expectNetworkBroadcast */)
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
|
||||||
|
|
||||||
// Switch back to manual - nothing should happen to the clock.
|
// Switch back to manual - nothing should happen to the clock.
|
||||||
mScript.simulateAutoTimeDetectionToggle()
|
mScript.simulateAutoTimeDetectionToggle()
|
||||||
.verifySystemClockWasNotSetAndResetCallTracking();
|
.verifySystemClockWasNotSetAndResetCallTracking()
|
||||||
|
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -350,11 +487,11 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testSuggestManualTime_autoTimeEnabled() {
|
public void testSuggestManualTime_autoTimeEnabled() {
|
||||||
Scenario scenario = SCENARIO_1;
|
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
|
||||||
mScript.pokeFakeClocks(scenario)
|
.pokeAutoTimeDetectionEnabled(true);
|
||||||
.pokeTimeDetectionEnabled(true);
|
|
||||||
|
|
||||||
ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual();
|
ManualTimeSuggestion timeSuggestion =
|
||||||
|
mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
|
||||||
final int clockIncrement = 1000;
|
final int clockIncrement = 1000;
|
||||||
|
|
||||||
mScript.simulateTimePassing(clockIncrement)
|
mScript.simulateTimePassing(clockIncrement)
|
||||||
@@ -367,7 +504,7 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
* like the real thing should, it also asserts preconditions.
|
* like the real thing should, it also asserts preconditions.
|
||||||
*/
|
*/
|
||||||
private static class FakeCallback implements TimeDetectorStrategy.Callback {
|
private static class FakeCallback implements TimeDetectorStrategy.Callback {
|
||||||
private boolean mTimeDetectionEnabled;
|
private boolean mAutoTimeDetectionEnabled;
|
||||||
private boolean mWakeLockAcquired;
|
private boolean mWakeLockAcquired;
|
||||||
private long mElapsedRealtimeMillis;
|
private long mElapsedRealtimeMillis;
|
||||||
private long mSystemClockMillis;
|
private long mSystemClockMillis;
|
||||||
@@ -384,7 +521,7 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAutoTimeDetectionEnabled() {
|
public boolean isAutoTimeDetectionEnabled() {
|
||||||
return mTimeDetectionEnabled;
|
return mAutoTimeDetectionEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -397,7 +534,6 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long elapsedRealtimeMillis() {
|
public long elapsedRealtimeMillis() {
|
||||||
assertWakeLockAcquired();
|
|
||||||
return mElapsedRealtimeMillis;
|
return mElapsedRealtimeMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,57 +564,57 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
|
|
||||||
// Methods below are for managing the fake's behavior.
|
// Methods below are for managing the fake's behavior.
|
||||||
|
|
||||||
public void pokeSystemClockUpdateThreshold(int thresholdMillis) {
|
void pokeSystemClockUpdateThreshold(int thresholdMillis) {
|
||||||
mSystemClockUpdateThresholdMillis = thresholdMillis;
|
mSystemClockUpdateThresholdMillis = thresholdMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
|
void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
|
||||||
mElapsedRealtimeMillis = elapsedRealtimeMillis;
|
mElapsedRealtimeMillis = elapsedRealtimeMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pokeSystemClockMillis(long systemClockMillis) {
|
void pokeSystemClockMillis(long systemClockMillis) {
|
||||||
mSystemClockMillis = systemClockMillis;
|
mSystemClockMillis = systemClockMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pokeAutoTimeDetectionEnabled(boolean enabled) {
|
void pokeAutoTimeDetectionEnabled(boolean enabled) {
|
||||||
mTimeDetectionEnabled = enabled;
|
mAutoTimeDetectionEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long peekElapsedRealtimeMillis() {
|
long peekElapsedRealtimeMillis() {
|
||||||
return mElapsedRealtimeMillis;
|
return mElapsedRealtimeMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long peekSystemClockMillis() {
|
long peekSystemClockMillis() {
|
||||||
return mSystemClockMillis;
|
return mSystemClockMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void simulateTimePassing(int incrementMillis) {
|
void simulateTimePassing(long incrementMillis) {
|
||||||
mElapsedRealtimeMillis += incrementMillis;
|
mElapsedRealtimeMillis += incrementMillis;
|
||||||
mSystemClockMillis += incrementMillis;
|
mSystemClockMillis += incrementMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void simulateAutoTimeZoneDetectionToggle() {
|
void simulateAutoTimeZoneDetectionToggle() {
|
||||||
mTimeDetectionEnabled = !mTimeDetectionEnabled;
|
mAutoTimeDetectionEnabled = !mAutoTimeDetectionEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void verifySystemClockNotSet() {
|
void verifySystemClockNotSet() {
|
||||||
assertFalse(mSystemClockWasSet);
|
assertFalse(mSystemClockWasSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void verifySystemClockWasSet(long expectSystemClockMillis) {
|
void verifySystemClockWasSet(long expectedSystemClockMillis) {
|
||||||
assertTrue(mSystemClockWasSet);
|
assertTrue(mSystemClockWasSet);
|
||||||
assertEquals(expectSystemClockMillis, mSystemClockMillis);
|
assertEquals(expectedSystemClockMillis, mSystemClockMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void verifyIntentWasBroadcast() {
|
void verifyIntentWasBroadcast() {
|
||||||
assertTrue(mBroadcastSent != null);
|
assertTrue(mBroadcastSent != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void verifyIntentWasNotBroadcast() {
|
void verifyIntentWasNotBroadcast() {
|
||||||
assertNull(mBroadcastSent);
|
assertNull(mBroadcastSent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetCallTracking() {
|
void resetCallTracking() {
|
||||||
mSystemClockWasSet = false;
|
mSystemClockWasSet = false;
|
||||||
mBroadcastSent = null;
|
mBroadcastSent = null;
|
||||||
}
|
}
|
||||||
@@ -495,23 +631,23 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
private class Script {
|
private class Script {
|
||||||
|
|
||||||
private final FakeCallback mFakeCallback;
|
private final FakeCallback mFakeCallback;
|
||||||
private final SimpleTimeDetectorStrategy mSimpleTimeDetectorStrategy;
|
private final SimpleTimeDetectorStrategy mTimeDetectorStrategy;
|
||||||
|
|
||||||
Script() {
|
Script() {
|
||||||
mFakeCallback = new FakeCallback();
|
mFakeCallback = new FakeCallback();
|
||||||
mSimpleTimeDetectorStrategy = new SimpleTimeDetectorStrategy();
|
mTimeDetectorStrategy = new SimpleTimeDetectorStrategy();
|
||||||
mSimpleTimeDetectorStrategy.initialize(mFakeCallback);
|
mTimeDetectorStrategy.initialize(mFakeCallback);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Script pokeTimeDetectionEnabled(boolean enabled) {
|
Script pokeAutoTimeDetectionEnabled(boolean enabled) {
|
||||||
mFakeCallback.pokeAutoTimeDetectionEnabled(enabled);
|
mFakeCallback.pokeAutoTimeDetectionEnabled(enabled);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Script pokeFakeClocks(Scenario scenario) {
|
Script pokeFakeClocks(TimestampedValue<Long> timeInfo) {
|
||||||
mFakeCallback.pokeElapsedRealtimeMillis(scenario.getInitialRealTimeMillis());
|
mFakeCallback.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
|
||||||
mFakeCallback.pokeSystemClockMillis(scenario.getInitialSystemClockMillis());
|
mFakeCallback.pokeSystemClockMillis(timeInfo.getValue());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,23 +665,23 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) {
|
Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) {
|
||||||
mSimpleTimeDetectorStrategy.suggestPhoneTime(timeSuggestion);
|
mTimeDetectorStrategy.suggestPhoneTime(timeSuggestion);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Script simulateManualTimeSuggestion(ManualTimeSuggestion timeSuggestion) {
|
Script simulateManualTimeSuggestion(ManualTimeSuggestion timeSuggestion) {
|
||||||
mSimpleTimeDetectorStrategy.suggestManualTime(timeSuggestion);
|
mTimeDetectorStrategy.suggestManualTime(timeSuggestion);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Script simulateAutoTimeDetectionToggle() {
|
Script simulateAutoTimeDetectionToggle() {
|
||||||
mFakeCallback.simulateAutoTimeZoneDetectionToggle();
|
mFakeCallback.simulateAutoTimeZoneDetectionToggle();
|
||||||
mSimpleTimeDetectorStrategy.handleAutoTimeDetectionChanged();
|
mTimeDetectorStrategy.handleAutoTimeDetectionChanged();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Script simulateTimePassing(int clockIncrement) {
|
Script simulateTimePassing(long clockIncrementMillis) {
|
||||||
mFakeCallback.simulateTimePassing(clockIncrement);
|
mFakeCallback.simulateTimePassing(clockIncrementMillis);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,85 +693,52 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Script verifySystemClockWasSetAndResetCallTracking(
|
Script verifySystemClockWasSetAndResetCallTracking(
|
||||||
long expectSystemClockMillis, boolean expectNetworkBroadcast) {
|
long expectedSystemClockMillis, boolean expectNetworkBroadcast) {
|
||||||
mFakeCallback.verifySystemClockWasSet(expectSystemClockMillis);
|
mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis);
|
||||||
if (expectNetworkBroadcast) {
|
if (expectNetworkBroadcast) {
|
||||||
mFakeCallback.verifyIntentWasBroadcast();
|
mFakeCallback.verifyIntentWasBroadcast();
|
||||||
}
|
}
|
||||||
mFakeCallback.resetCallTracking();
|
mFakeCallback.resetCallTracking();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A starting scenario used during tests. Describes a fictional "physical" reality.
|
* White box test info: Asserts the latest suggestion for the phone ID is as expected.
|
||||||
*/
|
*/
|
||||||
private static class Scenario {
|
Script assertLatestPhoneSuggestion(int phoneId, PhoneTimeSuggestion expected) {
|
||||||
|
assertEquals(expected, mTimeDetectorStrategy.getLatestPhoneSuggestion(phoneId));
|
||||||
private final long mInitialDeviceSystemClockMillis;
|
return this;
|
||||||
private final long mInitialDeviceRealtimeMillis;
|
|
||||||
private final long mActualTimeMillis;
|
|
||||||
|
|
||||||
Scenario(long initialDeviceSystemClock, long elapsedRealtime, long timeMillis) {
|
|
||||||
mInitialDeviceSystemClockMillis = initialDeviceSystemClock;
|
|
||||||
mActualTimeMillis = timeMillis;
|
|
||||||
mInitialDeviceRealtimeMillis = elapsedRealtime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
* Generates a PhoneTimeSuggestion using the current elapsed realtime clock for the
|
||||||
}
|
* reference time.
|
||||||
|
*/
|
||||||
PhoneTimeSuggestion createPhoneTimeSuggestionForActual(int phoneId) {
|
PhoneTimeSuggestion generatePhoneTimeSuggestion(int phoneId, Long timeMillis) {
|
||||||
TimestampedValue<Long> time = new TimestampedValue<>(
|
TimestampedValue<Long> time = null;
|
||||||
mInitialDeviceRealtimeMillis, mActualTimeMillis);
|
if (timeMillis != null) {
|
||||||
|
time = new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis);
|
||||||
|
}
|
||||||
return createPhoneTimeSuggestion(phoneId, time);
|
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,
|
private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId,
|
||||||
@@ -645,12 +748,6 @@ public class SimpleTimeDetectorStrategyTest {
|
|||||||
.build();
|
.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,
|
private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
|
||||||
int second) {
|
int second) {
|
||||||
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
|
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
|
||||||
|
|||||||
Reference in New Issue
Block a user