Merge "Add more behavior to TimeDetectorService" am: b1fc5a4c7e am: 325ecf7df4

am: 55b29baea1

Change-Id: I4fe5fe32ffbd807680d688f4415e5dc9e47069d5
This commit is contained in:
Neil Fuller
2018-06-26 05:33:35 -07:00
committed by android-build-merger
8 changed files with 942 additions and 74 deletions

View File

@@ -126,4 +126,12 @@ public final class TimestampedValue<T> {
dest.writeLong(timestampedValue.mReferenceTimeMillis);
dest.writeValue(timestampedValue.mValue);
}
/**
* Returns the difference in milliseconds between two instance's reference times.
*/
public static long referenceTimeDifference(
@NonNull TimestampedValue<?> one, @NonNull TimestampedValue<?> two) {
return one.mReferenceTimeMillis - two.mReferenceTimeMillis;
}
}

View File

@@ -116,4 +116,14 @@ public class TimestampedValueTest {
parcel.recycle();
}
}
@Test
public void testReferenceTimeDifference() {
TimestampedValue<Long> value1 = new TimestampedValue<>(1000, 123L);
assertEquals(0, TimestampedValue.referenceTimeDifference(value1, value1));
TimestampedValue<Long> value2 = new TimestampedValue<>(1, 321L);
assertEquals(999, TimestampedValue.referenceTimeDifference(value1, value2));
assertEquals(-999, TimestampedValue.referenceTimeDifference(value2, value1));
}
}

View File

@@ -20,38 +20,213 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.timedetector.TimeSignal;
import android.content.Intent;
import android.util.Slog;
import android.util.TimestampedValue;
import com.android.internal.telephony.TelephonyIntents;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* A placeholder implementation of TimeDetectorStrategy that passes NITZ suggestions immediately
* to {@link AlarmManager}.
* An implementation of TimeDetectorStrategy that passes only NITZ suggestions to
* {@link AlarmManager}. The TimeDetectorService handles thread safety: all calls to
* this class can be assumed to be single threaded (though the thread used may vary).
*/
// @NotThreadSafe
public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
private final static String TAG = "timedetector.SimpleTimeDetectorStrategy";
private Callback mHelper;
/**
* 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.
*/
private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000;
// @NonNull after initialize()
private Callback mCallback;
// NITZ state.
@Nullable private TimestampedValue<Long> mLastNitzTime;
// Information about the last time signal received: Used when toggling auto-time.
@Nullable private TimestampedValue<Long> mLastSystemClockTime;
private boolean mLastSystemClockTimeSendNetworkBroadcast;
// System clock state.
@Nullable private TimestampedValue<Long> mLastSystemClockTimeSet;
@Override
public void initialize(@NonNull Callback callback) {
mHelper = callback;
mCallback = callback;
}
@Override
public void suggestTime(@NonNull TimeSignal timeSignal) {
if (!TimeSignal.SOURCE_ID_NITZ.equals(timeSignal.getSourceId())) {
Slog.w(TAG, "Ignoring signal from unknown source: " + timeSignal);
Slog.w(TAG, "Ignoring signal from unsupported source: " + timeSignal);
return;
}
mHelper.setTime(timeSignal.getUtcTime());
// NITZ logic
TimestampedValue<Long> newNitzUtcTime = timeSignal.getUtcTime();
boolean nitzTimeIsValid = validateNewNitzTime(newNitzUtcTime, mLastNitzTime);
if (!nitzTimeIsValid) {
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 NITZ signals.
mLastNitzTime = newNitzUtcTime;
// System clock update logic.
// Historically, Android has sent a telephony broadcast only when setting the time using
// NITZ.
final boolean sendNetworkBroadcast =
TimeSignal.SOURCE_ID_NITZ.equals(timeSignal.getSourceId());
final TimestampedValue<Long> newUtcTime = newNitzUtcTime;
setSystemClockIfRequired(newUtcTime, sendNetworkBroadcast);
}
private static boolean validateNewNitzTime(TimestampedValue<Long> newNitzUtcTime,
TimestampedValue<Long> lastNitzTime) {
if (lastNitzTime != null) {
long referenceTimeDifference =
TimestampedValue.referenceTimeDifference(newNitzUtcTime, lastNitzTime);
if (referenceTimeDifference < 0 || referenceTimeDifference > Integer.MAX_VALUE) {
// Out of order or bogus.
Slog.w(TAG, "validateNewNitzTime: Bad NITZ signal received."
+ " referenceTimeDifference=" + referenceTimeDifference
+ " lastNitzTime=" + lastNitzTime
+ " newNitzUtcTime=" + newNitzUtcTime);
return false;
}
}
return true;
}
private void setSystemClockIfRequired(
TimestampedValue<Long> time, boolean sendNetworkBroadcast) {
// Store the last candidate we've seen in all cases so we can set the system clock
// when/if time detection is enabled.
mLastSystemClockTime = time;
mLastSystemClockTimeSendNetworkBroadcast = sendNetworkBroadcast;
if (!mCallback.isTimeDetectionEnabled()) {
Slog.d(TAG, "setSystemClockIfRequired: Time detection is not enabled. time=" + time);
return;
}
mCallback.acquireWakeLock();
try {
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
long actualTimeMillis = mCallback.systemClockMillis();
// CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
// may be setting the clock.
if (mLastSystemClockTimeSet != null) {
long expectedTimeMillis = TimeDetectorStrategy.getTimeAt(
mLastSystemClockTimeSet, elapsedRealtimeMillis);
long absSystemClockDifference = Math.abs(expectedTimeMillis - actualTimeMillis);
if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) {
Slog.w(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);
}
}
final String reason = "New time signal";
adjustAndSetDeviceSystemClock(
time, sendNetworkBroadcast, elapsedRealtimeMillis, actualTimeMillis, reason);
} finally {
mCallback.releaseWakeLock();
}
}
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) {
// No state to dump.
public void handleAutoTimeDetectionToggle(boolean enabled) {
// 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.
if (enabled) {
if (mLastSystemClockTime != null) {
// Only send the network broadcast if the last candidate would have caused one.
final boolean sendNetworkBroadcast = mLastSystemClockTimeSendNetworkBroadcast;
mCallback.acquireWakeLock();
try {
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
long actualTimeMillis = mCallback.systemClockMillis();
final String reason = "Automatic time detection enabled.";
adjustAndSetDeviceSystemClock(mLastSystemClockTime, sendNetworkBroadcast,
elapsedRealtimeMillis, actualTimeMillis, reason);
} finally {
mCallback.releaseWakeLock();
}
}
} else {
// CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
// it should be in future.
mLastSystemClockTimeSet = null;
}
}
@Override
public void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
pw.println("mLastNitzTime=" + mLastNitzTime);
pw.println("mLastSystemClockTimeSet=" + mLastSystemClockTimeSet);
pw.println("mLastSystemClockTime=" + mLastSystemClockTime);
pw.println("mLastSystemClockTimeSendNetworkBroadcast="
+ mLastSystemClockTimeSendNetworkBroadcast);
}
private void adjustAndSetDeviceSystemClock(
TimestampedValue<Long> newTime, boolean sendNetworkBroadcast,
long elapsedRealtimeMillis, long actualSystemClockMillis, String reason) {
// Adjust for the time that has elapsed since the signal was received.
long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis);
// Check if the new signal would make sufficient difference to the system clock. If it's
// below the threshold then ignore it.
long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis);
long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis();
if (absTimeDifference < systemClockUpdateThreshold) {
Slog.d(TAG, "adjustAndSetDeviceSystemClock: Not setting system clock. New time and"
+ " system clock are close enough."
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ " newTime=" + newTime
+ " reason=" + reason
+ " systemClockUpdateThreshold=" + systemClockUpdateThreshold
+ " absTimeDifference=" + absTimeDifference);
return;
}
Slog.d(TAG, "Setting system clock using time=" + newTime
+ " reason=" + reason
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ " newTimeMillis=" + newSystemClockMillis);
mCallback.setSystemClock(newSystemClockMillis);
// CLOCK_PARANOIA : Record the last time this class set the system clock.
mLastSystemClockTimeSet = newTime;
if (sendNetworkBroadcast) {
// 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);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("time", newSystemClockMillis);
mCallback.sendStickyBroadcast(intent);
}
}
}

View File

@@ -20,24 +20,29 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.TimeSignal;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Binder;
import android.provider.Settings;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.SystemService;
import com.android.server.timedetector.TimeDetectorStrategy.Callback;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;
public final class TimeDetectorService extends ITimeDetectorService.Stub {
private static final String TAG = "timedetector.TimeDetectorService";
public static class Lifecycle extends SystemService {
public Lifecycle(Context context) {
public Lifecycle(@NonNull Context context) {
super(context);
}
@@ -51,31 +56,65 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub {
}
}
private final Context mContext;
private final TimeDetectorStrategy mTimeDetectorStrategy;
@NonNull private final Context mContext;
@NonNull private final Callback mCallback;
private static TimeDetectorService create(Context context) {
TimeDetectorStrategy timeDetector = new SimpleTimeDetectorStrategy();
timeDetector.initialize(new TimeDetectorStrategyCallbackImpl(context));
return new TimeDetectorService(context, timeDetector);
// The lock used when call the strategy to ensure thread safety.
@NonNull private final Object mStrategyLock = new Object();
@GuardedBy("mStrategyLock")
@NonNull private final TimeDetectorStrategy mTimeDetectorStrategy;
private static TimeDetectorService create(@NonNull Context context) {
final TimeDetectorStrategy timeDetector = new SimpleTimeDetectorStrategy();
final TimeDetectorStrategyCallbackImpl callback =
new TimeDetectorStrategyCallbackImpl(context);
timeDetector.initialize(callback);
TimeDetectorService timeDetectorService =
new TimeDetectorService(context, callback, timeDetector);
// Wire up event listening.
ContentResolver contentResolver = context.getContentResolver();
contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
new ContentObserver(FgThread.getHandler()) {
public void onChange(boolean selfChange) {
timeDetectorService.handleAutoTimeDetectionToggle();
}
});
return timeDetectorService;
}
@VisibleForTesting
public TimeDetectorService(@NonNull Context context,
public TimeDetectorService(@NonNull Context context, @NonNull Callback callback,
@NonNull TimeDetectorStrategy timeDetectorStrategy) {
mContext = Objects.requireNonNull(context);
mCallback = Objects.requireNonNull(callback);
mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
}
@Override
public void suggestTime(@NonNull TimeSignal timeSignal) {
enforceSetTimePermission();
Objects.requireNonNull(timeSignal);
long callerIdToken = Binder.clearCallingIdentity();
long idToken = Binder.clearCallingIdentity();
try {
mTimeDetectorStrategy.suggestTime(timeSignal);
synchronized (mStrategyLock) {
mTimeDetectorStrategy.suggestTime(timeSignal);
}
} finally {
Binder.restoreCallingIdentity(callerIdToken);
Binder.restoreCallingIdentity(idToken);
}
}
@VisibleForTesting
public void handleAutoTimeDetectionToggle() {
synchronized (mStrategyLock) {
final boolean timeDetectionEnabled = mCallback.isTimeDetectionEnabled();
mTimeDetectorStrategy.handleAutoTimeDetectionToggle(timeDetectionEnabled);
}
}
@@ -84,7 +123,9 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub {
@Nullable String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
mTimeDetectorStrategy.dump(fd, pw, args);
synchronized (mStrategyLock) {
mTimeDetectorStrategy.dump(pw, args);
}
}
private void enforceSetTimePermission() {

View File

@@ -19,26 +19,66 @@ package com.android.server.timedetector;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.timedetector.TimeSignal;
import android.content.Intent;
import android.util.TimestampedValue;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* The interface for classes that implement the time detection algorithm used by the
* TimeDetectorService.
* TimeDetectorService. The TimeDetectorService handles thread safety: all calls to implementations
* of this interface can be assumed to be single threaded (though the thread used may vary).
*
* @hide
*/
// @NotThreadSafe
public interface TimeDetectorStrategy {
/**
* The interface used by the strategy to interact with the surrounding service.
*/
interface Callback {
void setTime(TimestampedValue<Long> time);
/**
* The absolute threshold below which the system clock need not be updated. i.e. if setting
* the system clock would adjust it by less than this (either backwards or forwards) then it
* need not be set.
*/
int systemClockUpdateThresholdMillis();
/** Returns true if automatic time detection is enabled. */
boolean isTimeDetectionEnabled();
/** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
void acquireWakeLock();
/** Returns the elapsedRealtimeMillis clock value. The WakeLock must be held. */
long elapsedRealtimeMillis();
/** Returns the system clock value. The WakeLock must be held. */
long systemClockMillis();
/** Sets the device system clock. The WakeLock must be held. */
void setSystemClock(long newTimeMillis);
/** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */
void releaseWakeLock();
/** Send the supplied intent as a stick broadcast. */
void sendStickyBroadcast(@NonNull Intent intent);
}
/** Initialize the strategy. */
void initialize(@NonNull Callback callback);
/** Process the suggested time. */
void suggestTime(@NonNull TimeSignal timeSignal);
void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args);
/** Handle the auto-time setting being toggled on or off. */
void handleAutoTimeDetectionToggle(boolean enabled);
/** Dump debug information. */
void dump(@NonNull PrintWriter pw, @Nullable String[] args);
// Utility methods below are to be moved to a better home when one becomes more obvious.

View File

@@ -18,41 +18,108 @@ package com.android.server.timedetector;
import android.annotation.NonNull;
import android.app.AlarmManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.util.TimestampedValue;
import java.util.Objects;
/**
* The real implementation of {@link TimeDetectorStrategy.Callback} used on device.
*/
public class TimeDetectorStrategyCallbackImpl implements TimeDetectorStrategy.Callback {
public final class TimeDetectorStrategyCallbackImpl implements TimeDetectorStrategy.Callback {
private final static String TAG = "timedetector.TimeDetectorStrategyCallbackImpl";
@NonNull private PowerManager.WakeLock mWakeLock;
@NonNull private AlarmManager mAlarmManager;
private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000;
/**
* If a newly calculated system clock time and the current system clock time differs by this or
* more the system clock will actually be updated. Used to prevent the system clock being set
* for only minor differences.
*/
private final int mSystemClockUpdateThresholdMillis;
@NonNull private final Context mContext;
@NonNull private final ContentResolver mContentResolver;
@NonNull private final PowerManager.WakeLock mWakeLock;
@NonNull private final AlarmManager mAlarmManager;
public TimeDetectorStrategyCallbackImpl(@NonNull Context context) {
mContext = Objects.requireNonNull(context);
mContentResolver = Objects.requireNonNull(context.getContentResolver());
public TimeDetectorStrategyCallbackImpl(Context context) {
PowerManager powerManager = context.getSystemService(PowerManager.class);
mWakeLock = Objects.requireNonNull(
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG));
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mAlarmManager = Objects.requireNonNull(context.getSystemService(AlarmManager.class));
mAlarmManager = context.getSystemService(AlarmManager.class);
mSystemClockUpdateThresholdMillis =
SystemProperties.getInt("ro.sys.time_detector_update_diff",
SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT);
}
@Override
public void setTime(TimestampedValue<Long> time) {
mWakeLock.acquire();
public int systemClockUpdateThresholdMillis() {
return mSystemClockUpdateThresholdMillis;
}
@Override
public boolean isTimeDetectionEnabled() {
try {
long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
long currentTimeMillis = TimeDetectorStrategy.getTimeAt(time, elapsedRealtimeMillis);
Slog.d(TAG, "Setting system clock using time=" + time
+ ", elapsedRealtimeMillis=" + elapsedRealtimeMillis);
mAlarmManager.setTime(currentTimeMillis);
} finally {
mWakeLock.release();
return Settings.Global.getInt(mContentResolver, Settings.Global.AUTO_TIME) != 0;
} catch (Settings.SettingNotFoundException snfe) {
return true;
}
}
@Override
public void acquireWakeLock() {
if (mWakeLock.isHeld()) {
Slog.wtf(TAG, "WakeLock " + mWakeLock + " already held");
}
mWakeLock.acquire();
}
@Override
public long elapsedRealtimeMillis() {
checkWakeLockHeld();
return SystemClock.elapsedRealtime();
}
@Override
public long systemClockMillis() {
checkWakeLockHeld();
return System.currentTimeMillis();
}
@Override
public void setSystemClock(long newTimeMillis) {
checkWakeLockHeld();
mAlarmManager.setTime(newTimeMillis);
}
@Override
public void releaseWakeLock() {
checkWakeLockHeld();
mWakeLock.release();
}
@Override
public void sendStickyBroadcast(@NonNull Intent intent) {
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private void checkWakeLockHeld() {
if (!mWakeLock.isHeld()) {
Slog.wtf(TAG, "WakeLock " + mWakeLock + " not held");
}
}
}

View File

@@ -16,12 +16,18 @@
package com.android.server.timedetector;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.timedetector.TimeSignal;
import android.content.Intent;
import android.icu.util.Calendar;
import android.icu.util.GregorianCalendar;
import android.icu.util.TimeZone;
import android.support.test.runner.AndroidJUnit4;
import android.util.TimestampedValue;
@@ -32,37 +38,476 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class SimpleTimeZoneDetectorStrategyTest {
private TimeDetectorStrategy.Callback mMockCallback;
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 SimpleTimeDetectorStrategy mSimpleTimeZoneDetectorStrategy;
private Script mScript;
@Before
public void setUp() {
mMockCallback = mock(TimeDetectorStrategy.Callback.class);
mSimpleTimeZoneDetectorStrategy = new SimpleTimeDetectorStrategy();
mSimpleTimeZoneDetectorStrategy.initialize(mMockCallback);
mScript = new Script();
}
@Test
public void testSuggestTime_nitz() {
TimestampedValue<Long> utcTime = createUtcTime();
TimeSignal timeSignal = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime);
public void testSuggestTime_nitz_timeDetectionEnabled() {
Scenario scenario = SCENARIO_1;
mScript.pokeFakeClocks(scenario)
.pokeTimeDetectionEnabled(true);
mSimpleTimeZoneDetectorStrategy.suggestTime(timeSignal);
TimeSignal timeSignal = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
final int clockIncrement = 1000;
long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
verify(mMockCallback).setTime(utcTime);
mScript.simulateTimePassing(clockIncrement)
.simulateTimeSignalReceived(timeSignal)
.verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis);
}
@Test
public void testSuggestTime_systemClockThreshold() {
Scenario scenario = SCENARIO_1;
final int systemClockUpdateThresholdMillis = 1000;
mScript.pokeFakeClocks(scenario)
.pokeThresholds(systemClockUpdateThresholdMillis)
.pokeTimeDetectionEnabled(true);
TimeSignal timeSignal1 = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
TimestampedValue<Long> utcTime1 = timeSignal1.getUtcTime();
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());
// Send the first time signal. It should be used.
mScript.simulateTimeSignalReceived(timeSignal1)
.verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis1);
// 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);
TimeSignal timeSignal2 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime2);
mScript.simulateTimePassing(clockIncrement)
.simulateTimeSignalReceived(timeSignal2)
.verifySystemClockWasNotSetAndResetCallTracking();
// 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);
TimeSignal timeSignal3 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime3);
mScript.simulateTimePassing(clockIncrement);
long expectSystemClockMillis3 =
TimeDetectorStrategy.getTimeAt(utcTime3, mScript.peekElapsedRealtimeMillis());
mScript.simulateTimeSignalReceived(timeSignal3)
.verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis3);
}
@Test
public void testSuggestTime_nitz_timeDetectionDisabled() {
Scenario scenario = SCENARIO_1;
mScript.pokeFakeClocks(scenario)
.pokeTimeDetectionEnabled(false);
TimeSignal timeSignal = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
mScript.simulateTimeSignalReceived(timeSignal)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
public void testSuggestTime_nitz_invalidNitzReferenceTimesIgnored() {
Scenario scenario = SCENARIO_1;
final int systemClockUpdateThreshold = 2000;
mScript.pokeFakeClocks(scenario)
.pokeThresholds(systemClockUpdateThreshold)
.pokeTimeDetectionEnabled(true);
TimeSignal timeSignal1 = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
TimestampedValue<Long> utcTime1 = timeSignal1.getUtcTime();
// Initialize the strategy / device with a time set from NITZ.
mScript.simulateTimePassing(100);
long expectedSystemClockMillis1 =
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
mScript.simulateTimeSignalReceived(timeSignal1)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1);
// The UTC time increment should be larger than the system clock update threshold so we
// know it shouldn't be ignored for other reasons.
long validUtcTimeMillis = utcTime1.getValue() + (2 * systemClockUpdateThreshold);
// Now supply a new signal that has an obviously bogus reference time : older than the last
// one.
long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1;
TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
referenceTimeBeforeLastSignalMillis, validUtcTimeMillis);
TimeSignal timeSignal2 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime2);
mScript.simulateTimeSignalReceived(timeSignal2)
.verifySystemClockWasNotSetAndResetCallTracking();
// Now supply a new signal that has an obviously bogus reference time : substantially in the
// future.
long referenceTimeInFutureMillis =
utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1;
TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
referenceTimeInFutureMillis, validUtcTimeMillis);
TimeSignal timeSignal3 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime3);
mScript.simulateTimeSignalReceived(timeSignal3)
.verifySystemClockWasNotSetAndResetCallTracking();
// Just to prove validUtcTimeMillis is valid.
long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100;
TimestampedValue<Long> utcTime4 = new TimestampedValue<>(
validReferenceTimeMillis, validUtcTimeMillis);
long expectedSystemClockMillis4 =
TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
TimeSignal timeSignal4 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime4);
mScript.simulateTimeSignalReceived(timeSignal4)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4);
}
@Test
public void testSuggestTime_timeDetectionToggled() {
Scenario scenario = SCENARIO_1;
final int clockIncrementMillis = 100;
final int systemClockUpdateThreshold = 2000;
mScript.pokeFakeClocks(scenario)
.pokeThresholds(systemClockUpdateThreshold)
.pokeTimeDetectionEnabled(false);
TimeSignal timeSignal1 = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
TimestampedValue<Long> utcTime1 = timeSignal1.getUtcTime();
// Simulate time passing.
mScript.simulateTimePassing(clockIncrementMillis);
// Simulate the time signal being received. It should not be used because auto time
// detection is off but it should be recorded.
mScript.simulateTimeSignalReceived(timeSignal1)
.verifySystemClockWasNotSetAndResetCallTracking();
// Simulate more time passing.
mScript.simulateTimePassing(clockIncrementMillis);
long expectedSystemClockMillis1 =
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
// Turn on auto time detection.
mScript.simulateAutoTimeDetectionToggle()
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1);
// Turn off auto time detection.
mScript.simulateAutoTimeDetectionToggle()
.verifySystemClockWasNotSetAndResetCallTracking();
// 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);
TimeSignal timeSignal2 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime2);
// Simulate more time passing.
mScript.simulateTimePassing(clockIncrementMillis);
long expectedSystemClockMillis2 =
TimeDetectorStrategy.getTimeAt(utcTime2, mScript.peekElapsedRealtimeMillis());
// The new time, though valid, should not be set in the system clock because auto time is
// disabled.
mScript.simulateTimeSignalReceived(timeSignal2)
.verifySystemClockWasNotSetAndResetCallTracking();
// Turn on auto time detection.
mScript.simulateAutoTimeDetectionToggle()
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2);
}
@Test
public void testSuggestTime_unknownSource() {
TimestampedValue<Long> utcTime = createUtcTime();
TimeSignal timeSignal = new TimeSignal("unknown", utcTime);
mSimpleTimeZoneDetectorStrategy.suggestTime(timeSignal);
Scenario scenario = SCENARIO_1;
mScript.pokeFakeClocks(scenario)
.pokeTimeDetectionEnabled(true);
verify(mMockCallback, never()).setTime(any());
TimeSignal timeSignal = scenario.createTimeSignalForActual("unknown");
mScript.simulateTimeSignalReceived(timeSignal)
.verifySystemClockWasNotSetAndResetCallTracking();
}
private static TimestampedValue<Long> createUtcTime() {
return new TimestampedValue<>(321L, 123456L);
/**
* A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
* like the real thing should, it also asserts preconditions.
*/
private static class FakeCallback implements TimeDetectorStrategy.Callback {
private boolean mTimeDetectionEnabled;
private boolean mWakeLockAcquired;
private long mElapsedRealtimeMillis;
private long mSystemClockMillis;
private int mSystemClockUpdateThresholdMillis = 2000;
// Tracking operations.
private boolean mSystemClockWasSet;
private Intent mBroadcastSent;
@Override
public int systemClockUpdateThresholdMillis() {
return mSystemClockUpdateThresholdMillis;
}
@Override
public boolean isTimeDetectionEnabled() {
return mTimeDetectionEnabled;
}
@Override
public void acquireWakeLock() {
if (mWakeLockAcquired) {
fail("Wake lock already acquired");
}
mWakeLockAcquired = true;
}
@Override
public long elapsedRealtimeMillis() {
assertWakeLockAcquired();
return mElapsedRealtimeMillis;
}
@Override
public long systemClockMillis() {
assertWakeLockAcquired();
return mSystemClockMillis;
}
@Override
public void setSystemClock(long newTimeMillis) {
assertWakeLockAcquired();
mSystemClockWasSet = true;
mSystemClockMillis = newTimeMillis;
}
@Override
public void releaseWakeLock() {
assertWakeLockAcquired();
mWakeLockAcquired = false;
}
@Override
public void sendStickyBroadcast(Intent intent) {
assertNotNull(intent);
mBroadcastSent = intent;
}
// Methods below are for managing the fake's behavior.
public void pokeSystemClockUpdateThreshold(int thresholdMillis) {
mSystemClockUpdateThresholdMillis = thresholdMillis;
}
public void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
mElapsedRealtimeMillis = elapsedRealtimeMillis;
}
public void pokeSystemClockMillis(long systemClockMillis) {
mSystemClockMillis = systemClockMillis;
}
public void pokeTimeDetectionEnabled(boolean enabled) {
mTimeDetectionEnabled = enabled;
}
public long peekElapsedRealtimeMillis() {
return mElapsedRealtimeMillis;
}
public long peekSystemClockMillis() {
return mSystemClockMillis;
}
public void simulateTimePassing(int incrementMillis) {
mElapsedRealtimeMillis += incrementMillis;
mSystemClockMillis += incrementMillis;
}
public void verifySystemClockNotSet() {
assertFalse(mSystemClockWasSet);
}
public void verifySystemClockWasSet(long expectSystemClockMillis) {
assertTrue(mSystemClockWasSet);
assertEquals(expectSystemClockMillis, mSystemClockMillis);
}
public void verifyIntentWasBroadcast() {
assertTrue(mBroadcastSent != null);
}
public void verifyIntentWasNotBroadcast() {
assertNull(mBroadcastSent);
}
public void resetCallTracking() {
mSystemClockWasSet = false;
mBroadcastSent = null;
}
private void assertWakeLockAcquired() {
assertTrue("The operation must be performed only after acquiring the wakelock",
mWakeLockAcquired);
}
}
/**
* A fluent helper class for tests.
*/
private class Script {
private final FakeCallback mFakeCallback;
private final SimpleTimeDetectorStrategy mSimpleTimeDetectorStrategy;
public Script() {
mFakeCallback = new FakeCallback();
mSimpleTimeDetectorStrategy = new SimpleTimeDetectorStrategy();
mSimpleTimeDetectorStrategy.initialize(mFakeCallback);
}
Script pokeTimeDetectionEnabled(boolean enabled) {
mFakeCallback.pokeTimeDetectionEnabled(enabled);
return this;
}
Script pokeFakeClocks(Scenario scenario) {
mFakeCallback.pokeElapsedRealtimeMillis(scenario.getInitialRealTimeMillis());
mFakeCallback.pokeSystemClockMillis(scenario.getInitialSystemClockMillis());
return this;
}
Script pokeThresholds(int systemClockUpdateThreshold) {
mFakeCallback.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold);
return this;
}
long peekElapsedRealtimeMillis() {
return mFakeCallback.peekElapsedRealtimeMillis();
}
long peekSystemClockMillis() {
return mFakeCallback.peekSystemClockMillis();
}
Script simulateTimeSignalReceived(TimeSignal timeSignal) {
mSimpleTimeDetectorStrategy.suggestTime(timeSignal);
return this;
}
Script simulateAutoTimeDetectionToggle() {
boolean enabled = !mFakeCallback.isTimeDetectionEnabled();
mFakeCallback.pokeTimeDetectionEnabled(enabled);
mSimpleTimeDetectorStrategy.handleAutoTimeDetectionToggle(enabled);
return this;
}
Script simulateTimePassing(int clockIncrement) {
mFakeCallback.simulateTimePassing(clockIncrement);
return this;
}
Script verifySystemClockWasNotSetAndResetCallTracking() {
mFakeCallback.verifySystemClockNotSet();
mFakeCallback.verifyIntentWasNotBroadcast();
mFakeCallback.resetCallTracking();
return this;
}
Script verifySystemClockWasSetAndResetCallTracking(long expectSystemClockMillis) {
mFakeCallback.verifySystemClockWasSet(expectSystemClockMillis);
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;
}
TimeSignal createTimeSignalForActual(String sourceId) {
TimestampedValue<Long> time = new TimestampedValue<>(
mInitialDeviceRealtimeMillis, mActualTimeMillis);
return new TimeSignal(sourceId, 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 long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
int second) {
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
cal.clear();
cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
return cal.getTimeInMillis();
}
}

View File

@@ -16,6 +16,9 @@
package com.android.server.timedetector;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -23,36 +26,40 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.timedetector.TimeSignal;
import android.content.Context;
import android.content.pm.PackageManager;
import android.support.test.runner.AndroidJUnit4;
import android.util.TimestampedValue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.android.server.timedetector.TimeDetectorStrategy.Callback;
import java.io.PrintWriter;
@RunWith(AndroidJUnit4.class)
public class TimeDetectorServiceTest {
private TimeDetectorService mTimeDetectorService;
private Context mMockContext;
private TimeDetectorStrategy mMockTimeDetectorStrategy;
private StubbedTimeDetectorStrategy mStubbedTimeDetectorStrategy;
private Callback mMockCallback;
private TimeDetectorService mTimeDetectorService;
@Before
public void setUp() {
mMockContext = mock(Context.class);
mMockTimeDetectorStrategy = mock(TimeDetectorStrategy.class);
mTimeDetectorService = new TimeDetectorService(mMockContext, mMockTimeDetectorStrategy);
}
mMockCallback = mock(Callback.class);
mStubbedTimeDetectorStrategy = new StubbedTimeDetectorStrategy();
@After
public void tearDown() {
verifyNoMoreInteractions(mMockContext, mMockTimeDetectorStrategy);
mTimeDetectorService = new TimeDetectorService(
mMockContext, mMockCallback,
mStubbedTimeDetectorStrategy);
}
@Test(expected=SecurityException.class)
@@ -78,11 +85,86 @@ public class TimeDetectorServiceTest {
verify(mMockContext)
.enforceCallingPermission(eq(android.Manifest.permission.SET_TIME), anyString());
verify(mMockTimeDetectorStrategy).suggestTime(timeSignal);
mStubbedTimeDetectorStrategy.verifySuggestTimeCalled(timeSignal);
}
@Test
public void testDump() {
when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
.thenReturn(PackageManager.PERMISSION_GRANTED);
mTimeDetectorService.dump(null, null, null);
verify(mMockContext).checkCallingOrSelfPermission(eq(android.Manifest.permission.DUMP));
mStubbedTimeDetectorStrategy.verifyDumpCalled();
}
@Test
public void testAutoTimeDetectionToggle() {
when(mMockCallback.isTimeDetectionEnabled()).thenReturn(true);
mTimeDetectorService.handleAutoTimeDetectionToggle();
mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(true);
when(mMockCallback.isTimeDetectionEnabled()).thenReturn(false);
mTimeDetectorService.handleAutoTimeDetectionToggle();
mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(false);
}
private static TimeSignal createNitzTimeSignal() {
TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
return new TimeSignal(TimeSignal.SOURCE_ID_NITZ, timeValue);
}
private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy {
// Call tracking.
private TimeSignal mLastSuggestedTime;
private Boolean mLastAutoTimeDetectionToggle;
private boolean mDumpCalled;
@Override
public void initialize(Callback ignored) {
}
@Override
public void suggestTime(TimeSignal timeSignal) {
resetCallTracking();
mLastSuggestedTime = timeSignal;
}
@Override
public void handleAutoTimeDetectionToggle(boolean enabled) {
resetCallTracking();
mLastAutoTimeDetectionToggle = enabled;
}
@Override
public void dump(PrintWriter pw, String[] args) {
resetCallTracking();
mDumpCalled = true;
}
void resetCallTracking() {
mLastSuggestedTime = null;
mLastAutoTimeDetectionToggle = null;
mDumpCalled = false;
}
void verifySuggestTimeCalled(TimeSignal expectedSignal) {
assertEquals(expectedSignal, mLastSuggestedTime);
}
void verifyHandleAutoTimeDetectionToggleCalled(boolean expectedEnable) {
assertNotNull(mLastAutoTimeDetectionToggle);
assertEquals(expectedEnable, mLastAutoTimeDetectionToggle);
}
void verifyDumpCalled() {
assertTrue(mDumpCalled);
}
}
}