summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Neil Fuller <nfuller@google.com> 2019-12-12 04:08:06 -0800
committer android-build-merger <android-build-merger@google.com> 2019-12-12 04:08:06 -0800
commit1602545f282d9466bbfd61471ccd507bf823e5c2 (patch)
tree9cedf59cb88579d89edf015efc7e7f2cafe4c588
parent31f969ba66b8062cff0bd4307b18aee804e0356d (diff)
parent51c61e8bdba4dac4499b25eb0361fe60a65d4d43 (diff)
Merge "Handle multiple phoneIds in time detection"
am: 51c61e8bdb Change-Id: I6b792aba6ca4ca2f6b43256080e6dff230aca370
-rw-r--r--core/java/android/app/timedetector/ManualTimeSuggestion.java1
-rw-r--r--core/java/android/app/timedetector/PhoneTimeSuggestion.java7
-rw-r--r--services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java488
-rw-r--r--services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java529
4 files changed, 682 insertions, 343 deletions
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java
index 471606da4d75..55f92be14cd0 100644
--- a/core/java/android/app/timedetector/ManualTimeSuggestion.java
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java
@@ -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) {
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
index dd02af7a3ac7..4a89a1245473 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
@@ -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;
}
diff --git a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
index 4e8ba078a73c..5a31bbc859c8 100644
--- a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
@@ -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;
-
- // Information about the last time signal received: Used when toggling auto-time.
- @Nullable private TimestampedValue<Long> mLastAutoSystemClockTime;
- private boolean mLastAutoSystemClockTimeSendNetworkBroadcast;
+ // 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;
- // 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();
+
+ // 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);
+ }
- // Empty suggestions are just ignored as we don't currently keep track of suggestion origin.
+ @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 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;
+ }
}
@Override
- public synchronized void suggestManualTime(ManualTimeSuggestion timeSuggestion) {
- final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
- setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, timeSuggestion);
+ public synchronized void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("TimeDetectorStrategy:");
+ ipw.increaseIndent(); // level 1
+
+ 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();
}
- private static boolean validateNewPhoneSuggestion(@NonNull PhoneTimeSuggestion newSuggestion,
- @Nullable PhoneTimeSuggestion lastSuggestion) {
-
- 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);
+ @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;
-
- 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();
+ @GuardedBy("this")
+ private void setSystemClockUnderWakeLock(
+ int origin, @NonNull TimestampedValue<Long> newTime, @NonNull Object cause) {
+
+ 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();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java
index 7a0a28dfbd16..52e7d8197842 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java
@@ -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,84 +693,51 @@ 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;
- }
-
- long getInitialRealTimeMillis() {
- return mInitialDeviceRealtimeMillis;
- }
-
- long getInitialSystemClockMillis() {
- return mInitialDeviceSystemClockMillis;
- }
-
- long getActualTimeMillis() {
- return mActualTimeMillis;
- }
-
- PhoneTimeSuggestion createPhoneTimeSuggestionForActual(int phoneId) {
- TimestampedValue<Long> time = new TimestampedValue<>(
- mInitialDeviceRealtimeMillis, mActualTimeMillis);
- return createPhoneTimeSuggestion(phoneId, time);
- }
-
- ManualTimeSuggestion createManualTimeSuggestionForActual() {
- TimestampedValue<Long> time = new TimestampedValue<>(
- mInitialDeviceRealtimeMillis, mActualTimeMillis);
- return new ManualTimeSuggestion(time);
+ /**
+ * 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;
}
- 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);
+ /**
+ * 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();
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
}
}
@@ -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"));