Merge "Add more behavior to TimeDetectorService"
am: b1fc5a4c7e
Change-Id: Ibbda2d253bf807cc480a4218aa1d898d6737ec7b
This commit is contained in:
@@ -126,4 +126,12 @@ public final class TimestampedValue<T> {
|
|||||||
dest.writeLong(timestampedValue.mReferenceTimeMillis);
|
dest.writeLong(timestampedValue.mReferenceTimeMillis);
|
||||||
dest.writeValue(timestampedValue.mValue);
|
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();
|
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.annotation.Nullable;
|
||||||
import android.app.AlarmManager;
|
import android.app.AlarmManager;
|
||||||
import android.app.timedetector.TimeSignal;
|
import android.app.timedetector.TimeSignal;
|
||||||
|
import android.content.Intent;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
import android.util.TimestampedValue;
|
||||||
|
|
||||||
|
import com.android.internal.telephony.TelephonyIntents;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A placeholder implementation of TimeDetectorStrategy that passes NITZ suggestions immediately
|
* An implementation of TimeDetectorStrategy that passes only NITZ suggestions to
|
||||||
* to {@link AlarmManager}.
|
* {@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 {
|
public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
|
||||||
|
|
||||||
private final static String TAG = "timedetector.SimpleTimeDetectorStrategy";
|
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
|
@Override
|
||||||
public void initialize(@NonNull Callback callback) {
|
public void initialize(@NonNull Callback callback) {
|
||||||
mHelper = callback;
|
mCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void suggestTime(@NonNull TimeSignal timeSignal) {
|
public void suggestTime(@NonNull TimeSignal timeSignal) {
|
||||||
if (!TimeSignal.SOURCE_ID_NITZ.equals(timeSignal.getSourceId())) {
|
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;
|
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
|
@Override
|
||||||
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) {
|
public void handleAutoTimeDetectionToggle(boolean enabled) {
|
||||||
// No state to dump.
|
// 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.annotation.Nullable;
|
||||||
import android.app.timedetector.ITimeDetectorService;
|
import android.app.timedetector.ITimeDetectorService;
|
||||||
import android.app.timedetector.TimeSignal;
|
import android.app.timedetector.TimeSignal;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.ContentObserver;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
import android.provider.Settings;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.util.DumpUtils;
|
import com.android.internal.util.DumpUtils;
|
||||||
|
import com.android.server.FgThread;
|
||||||
import com.android.server.SystemService;
|
import com.android.server.SystemService;
|
||||||
|
import com.android.server.timedetector.TimeDetectorStrategy.Callback;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public final class TimeDetectorService extends ITimeDetectorService.Stub {
|
public final class TimeDetectorService extends ITimeDetectorService.Stub {
|
||||||
|
|
||||||
private static final String TAG = "timedetector.TimeDetectorService";
|
private static final String TAG = "timedetector.TimeDetectorService";
|
||||||
|
|
||||||
public static class Lifecycle extends SystemService {
|
public static class Lifecycle extends SystemService {
|
||||||
|
|
||||||
public Lifecycle(Context context) {
|
public Lifecycle(@NonNull Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,31 +56,65 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Context mContext;
|
@NonNull private final Context mContext;
|
||||||
private final TimeDetectorStrategy mTimeDetectorStrategy;
|
@NonNull private final Callback mCallback;
|
||||||
|
|
||||||
private static TimeDetectorService create(Context context) {
|
// The lock used when call the strategy to ensure thread safety.
|
||||||
TimeDetectorStrategy timeDetector = new SimpleTimeDetectorStrategy();
|
@NonNull private final Object mStrategyLock = new Object();
|
||||||
timeDetector.initialize(new TimeDetectorStrategyCallbackImpl(context));
|
|
||||||
return new TimeDetectorService(context, timeDetector);
|
@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
|
@VisibleForTesting
|
||||||
public TimeDetectorService(@NonNull Context context,
|
public TimeDetectorService(@NonNull Context context, @NonNull Callback callback,
|
||||||
@NonNull TimeDetectorStrategy timeDetectorStrategy) {
|
@NonNull TimeDetectorStrategy timeDetectorStrategy) {
|
||||||
mContext = Objects.requireNonNull(context);
|
mContext = Objects.requireNonNull(context);
|
||||||
|
mCallback = Objects.requireNonNull(callback);
|
||||||
mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
|
mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void suggestTime(@NonNull TimeSignal timeSignal) {
|
public void suggestTime(@NonNull TimeSignal timeSignal) {
|
||||||
enforceSetTimePermission();
|
enforceSetTimePermission();
|
||||||
|
Objects.requireNonNull(timeSignal);
|
||||||
|
|
||||||
long callerIdToken = Binder.clearCallingIdentity();
|
long idToken = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
mTimeDetectorStrategy.suggestTime(timeSignal);
|
synchronized (mStrategyLock) {
|
||||||
|
mTimeDetectorStrategy.suggestTime(timeSignal);
|
||||||
|
}
|
||||||
} finally {
|
} 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) {
|
@Nullable String[] args) {
|
||||||
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
|
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
|
||||||
|
|
||||||
mTimeDetectorStrategy.dump(fd, pw, args);
|
synchronized (mStrategyLock) {
|
||||||
|
mTimeDetectorStrategy.dump(pw, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enforceSetTimePermission() {
|
private void enforceSetTimePermission() {
|
||||||
|
|||||||
@@ -19,26 +19,66 @@ package com.android.server.timedetector;
|
|||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.app.timedetector.TimeSignal;
|
import android.app.timedetector.TimeSignal;
|
||||||
|
import android.content.Intent;
|
||||||
import android.util.TimestampedValue;
|
import android.util.TimestampedValue;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The interface for classes that implement the time detection algorithm used by the
|
* 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
|
* @hide
|
||||||
*/
|
*/
|
||||||
|
// @NotThreadSafe
|
||||||
public interface TimeDetectorStrategy {
|
public interface TimeDetectorStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface used by the strategy to interact with the surrounding service.
|
||||||
|
*/
|
||||||
interface Callback {
|
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);
|
void initialize(@NonNull Callback callback);
|
||||||
|
|
||||||
|
/** Process the suggested time. */
|
||||||
void suggestTime(@NonNull TimeSignal timeSignal);
|
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.
|
// 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.annotation.NonNull;
|
||||||
import android.app.AlarmManager;
|
import android.app.AlarmManager;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import android.os.SystemProperties;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.util.TimestampedValue;
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The real implementation of {@link TimeDetectorStrategy.Callback} used on device.
|
* 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";
|
private final static String TAG = "timedetector.TimeDetectorStrategyCallbackImpl";
|
||||||
|
|
||||||
@NonNull private PowerManager.WakeLock mWakeLock;
|
private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000;
|
||||||
@NonNull private AlarmManager mAlarmManager;
|
|
||||||
|
/**
|
||||||
|
* 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);
|
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
|
@Override
|
||||||
public void setTime(TimestampedValue<Long> time) {
|
public int systemClockUpdateThresholdMillis() {
|
||||||
mWakeLock.acquire();
|
return mSystemClockUpdateThresholdMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTimeDetectionEnabled() {
|
||||||
try {
|
try {
|
||||||
long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
|
return Settings.Global.getInt(mContentResolver, Settings.Global.AUTO_TIME) != 0;
|
||||||
long currentTimeMillis = TimeDetectorStrategy.getTimeAt(time, elapsedRealtimeMillis);
|
} catch (Settings.SettingNotFoundException snfe) {
|
||||||
Slog.d(TAG, "Setting system clock using time=" + time
|
return true;
|
||||||
+ ", elapsedRealtimeMillis=" + elapsedRealtimeMillis);
|
}
|
||||||
mAlarmManager.setTime(currentTimeMillis);
|
}
|
||||||
} finally {
|
|
||||||
mWakeLock.release();
|
@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;
|
package com.android.server.timedetector;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.mockito.Mockito.verify;
|
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.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.support.test.runner.AndroidJUnit4;
|
||||||
import android.util.TimestampedValue;
|
import android.util.TimestampedValue;
|
||||||
|
|
||||||
@@ -32,37 +38,476 @@ import org.junit.runner.RunWith;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class SimpleTimeZoneDetectorStrategyTest {
|
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
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
mMockCallback = mock(TimeDetectorStrategy.Callback.class);
|
mScript = new Script();
|
||||||
mSimpleTimeZoneDetectorStrategy = new SimpleTimeDetectorStrategy();
|
|
||||||
mSimpleTimeZoneDetectorStrategy.initialize(mMockCallback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSuggestTime_nitz() {
|
public void testSuggestTime_nitz_timeDetectionEnabled() {
|
||||||
TimestampedValue<Long> utcTime = createUtcTime();
|
Scenario scenario = SCENARIO_1;
|
||||||
TimeSignal timeSignal = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime);
|
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
|
@Test
|
||||||
public void testSuggestTime_unknownSource() {
|
public void testSuggestTime_unknownSource() {
|
||||||
TimestampedValue<Long> utcTime = createUtcTime();
|
Scenario scenario = SCENARIO_1;
|
||||||
TimeSignal timeSignal = new TimeSignal("unknown", utcTime);
|
mScript.pokeFakeClocks(scenario)
|
||||||
mSimpleTimeZoneDetectorStrategy.suggestTime(timeSignal);
|
.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;
|
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.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
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.doThrow;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
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.app.timedetector.TimeSignal;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import android.util.TimestampedValue;
|
import android.util.TimestampedValue;
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import com.android.server.timedetector.TimeDetectorStrategy.Callback;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class TimeDetectorServiceTest {
|
public class TimeDetectorServiceTest {
|
||||||
|
|
||||||
private TimeDetectorService mTimeDetectorService;
|
|
||||||
|
|
||||||
private Context mMockContext;
|
private Context mMockContext;
|
||||||
private TimeDetectorStrategy mMockTimeDetectorStrategy;
|
private StubbedTimeDetectorStrategy mStubbedTimeDetectorStrategy;
|
||||||
|
private Callback mMockCallback;
|
||||||
|
|
||||||
|
private TimeDetectorService mTimeDetectorService;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
mMockContext = mock(Context.class);
|
mMockContext = mock(Context.class);
|
||||||
mMockTimeDetectorStrategy = mock(TimeDetectorStrategy.class);
|
mMockCallback = mock(Callback.class);
|
||||||
mTimeDetectorService = new TimeDetectorService(mMockContext, mMockTimeDetectorStrategy);
|
mStubbedTimeDetectorStrategy = new StubbedTimeDetectorStrategy();
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
mTimeDetectorService = new TimeDetectorService(
|
||||||
public void tearDown() {
|
mMockContext, mMockCallback,
|
||||||
verifyNoMoreInteractions(mMockContext, mMockTimeDetectorStrategy);
|
mStubbedTimeDetectorStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=SecurityException.class)
|
@Test(expected=SecurityException.class)
|
||||||
@@ -78,11 +85,86 @@ public class TimeDetectorServiceTest {
|
|||||||
|
|
||||||
verify(mMockContext)
|
verify(mMockContext)
|
||||||
.enforceCallingPermission(eq(android.Manifest.permission.SET_TIME), anyString());
|
.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() {
|
private static TimeSignal createNitzTimeSignal() {
|
||||||
TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
|
TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
|
||||||
return new TimeSignal(TimeSignal.SOURCE_ID_NITZ, timeValue);
|
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