Merge "Add more behavior to TimeDetectorService"
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user