Loading services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java +58 −26 Original line number Diff line number Diff line Loading @@ -23,10 +23,13 @@ import android.app.AlarmManager; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; import android.content.Intent; import android.util.LocalLog; import android.util.Slog; import android.util.TimestampedValue; import com.android.internal.annotations.GuardedBy; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.util.IndentingPrintWriter; import java.io.PrintWriter; import java.lang.annotation.Retention; Loading @@ -34,13 +37,14 @@ import java.lang.annotation.RetentionPolicy; /** * 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). * {@link AlarmManager}. * * <p>Most public methods are marked synchronized to ensure thread safety around internal state. */ // @NotThreadSafe public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { private final static String TAG = "timedetector.SimpleTimeDetectorStrategy"; private static final boolean DBG = false; private static final String LOG_TAG = "SimpleTimeDetectorStrategy"; @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL }) @Retention(RetentionPolicy.SOURCE) Loading @@ -61,6 +65,9 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { */ private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000; // A log for changes made to the system clock and why. @NonNull private final LocalLog mTimeChangesLog = new LocalLog(30); // @NonNull after initialize() private Callback mCallback; Loading @@ -80,7 +87,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { } @Override public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { // NITZ logic // Empty suggestions are just ignored as we don't currently keep track of suggestion origin. Loading @@ -103,7 +110,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { } @Override public void suggestManualTime(ManualTimeSuggestion timeSuggestion) { public synchronized void suggestManualTime(ManualTimeSuggestion timeSuggestion) { final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime(); setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, timeSuggestion); } Loading @@ -116,7 +123,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { newSuggestion.getUtcTime(), lastSuggestion.getUtcTime()); if (referenceTimeDifference < 0 || referenceTimeDifference > Integer.MAX_VALUE) { // Out of order or bogus. Slog.w(TAG, "validateNewNitzTime: Bad NITZ signal received." Slog.w(LOG_TAG, "Bad NITZ signal received." + " referenceTimeDifference=" + referenceTimeDifference + " lastSuggestion=" + lastSuggestion + " newSuggestion=" + newSuggestion); Loading @@ -126,6 +133,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { return true; } @GuardedBy("this") private void setSystemClockIfRequired( @Origin int origin, TimestampedValue<Long> time, Object cause) { // Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only Loading @@ -140,16 +148,20 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { mLastAutoSystemClockTimeSendNetworkBroadcast = sendNetworkBroadcast; if (!mCallback.isAutoTimeDetectionEnabled()) { Slog.d(TAG, "setSystemClockIfRequired: Auto time detection is not enabled." if (DBG) { Slog.d(LOG_TAG, "Auto time detection is not enabled." + " time=" + time + ", cause=" + cause); } return; } } else { if (mCallback.isAutoTimeDetectionEnabled()) { Slog.d(TAG, "setSystemClockIfRequired: Auto time detection is enabled." if (DBG) { Slog.d(LOG_TAG, "Auto time detection is enabled." + " time=" + time + ", cause=" + cause); } return; } } Loading @@ -167,7 +179,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { mLastAutoSystemClockTimeSet, elapsedRealtimeMillis); long absSystemClockDifference = Math.abs(expectedTimeMillis - actualTimeMillis); if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) { Slog.w(TAG, Slog.w(LOG_TAG, "System clock has not tracked elapsed real time clock. A clock may" + " be inaccurate or something unexpectedly set the system" + " clock." Loading @@ -190,9 +202,10 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { } @Override public void handleAutoTimeDetectionToggle(boolean enabled) { public synchronized void handleAutoTimeDetectionChanged() { // If automatic time detection is enabled we update the system clock instantly if we can. // Conversely, if automatic time detection is disabled we leave the clock as it is. boolean enabled = mCallback.isAutoTimeDetectionEnabled(); if (enabled) { if (mLastAutoSystemClockTime != null) { // Only send the network broadcast if the last candidate would have caused one. Loading @@ -218,14 +231,27 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { } @Override public void dump(@NonNull PrintWriter pw, @Nullable String[] args) { public synchronized void dump(@NonNull PrintWriter pw, @Nullable String[] args) { pw.println("mLastPhoneSuggestion=" + mLastPhoneSuggestion); pw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet); pw.println("mLastAutoSystemClockTime=" + mLastAutoSystemClockTime); pw.println("mLastAutoSystemClockTimeSendNetworkBroadcast=" + mLastAutoSystemClockTimeSendNetworkBroadcast); IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); ipw.println("TimeDetectorStrategyImpl logs:"); ipw.increaseIndent(); // level 1 ipw.println("Time change log:"); ipw.increaseIndent(); // level 2 mTimeChangesLog.dump(ipw); ipw.decreaseIndent(); // level 2 ipw.decreaseIndent(); // level 1 } @GuardedBy("this") private void adjustAndSetDeviceSystemClock( TimestampedValue<Long> newTime, boolean sendNetworkBroadcast, long elapsedRealtimeMillis, long actualSystemClockMillis, Object cause) { Loading @@ -238,21 +264,27 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis); long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis(); if (absTimeDifference < systemClockUpdateThreshold) { Slog.d(TAG, "adjustAndSetDeviceSystemClock: Not setting system clock. New time and" if (DBG) { Slog.d(LOG_TAG, "Not setting system clock. New time and" + " system clock are close enough." + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + " newTime=" + newTime + " cause=" + cause + " systemClockUpdateThreshold=" + systemClockUpdateThreshold + " absTimeDifference=" + absTimeDifference); } return; } Slog.d(TAG, "Setting system clock using time=" + newTime mCallback.setSystemClock(newSystemClockMillis); String logMsg = "Set system clock using time=" + newTime + " cause=" + cause + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + " newTimeMillis=" + newSystemClockMillis); mCallback.setSystemClock(newSystemClockMillis); + " newSystemClockMillis=" + newSystemClockMillis; if (DBG) { Slog.d(LOG_TAG, logMsg); } mTimeChangesLog.log(logMsg); // CLOCK_PARANOIA : Record the last time this class set the system clock. mLastAutoSystemClockTimeSet = newTime; Loading services/core/java/com/android/server/timedetector/TimeDetectorService.java +15 −38 Original line number Diff line number Diff line Loading @@ -24,10 +24,9 @@ import android.app.timedetector.PhoneTimeSuggestion; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.os.Binder; import android.os.Handler; 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; Loading @@ -39,7 +38,7 @@ import java.io.PrintWriter; import java.util.Objects; public final class TimeDetectorService extends ITimeDetectorService.Stub { private static final String TAG = "timedetector.TimeDetectorService"; private static final String TAG = "TimeDetectorService"; public static class Lifecycle extends SystemService { Loading @@ -57,29 +56,25 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { } } @NonNull private final Handler mHandler; @NonNull private final Context mContext; @NonNull private final Callback mCallback; // 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); TimeDetectorStrategy timeDetector = new SimpleTimeDetectorStrategy(); TimeDetectorStrategyCallbackImpl callback = new TimeDetectorStrategyCallbackImpl(context); timeDetector.initialize(callback); Handler handler = FgThread.getHandler(); TimeDetectorService timeDetectorService = new TimeDetectorService(context, callback, timeDetector); new TimeDetectorService(context, handler, callback, timeDetector); // Wire up event listening. ContentResolver contentResolver = context.getContentResolver(); contentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true, new ContentObserver(FgThread.getHandler()) { new ContentObserver(handler) { public void onChange(boolean selfChange) { timeDetectorService.handleAutoTimeDetectionToggle(); } Loading @@ -89,9 +84,10 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { } @VisibleForTesting public TimeDetectorService(@NonNull Context context, @NonNull Callback callback, @NonNull TimeDetectorStrategy timeDetectorStrategy) { public TimeDetectorService(@NonNull Context context, @NonNull Handler handler, @NonNull Callback callback, @NonNull TimeDetectorStrategy timeDetectorStrategy) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); mCallback = Objects.requireNonNull(callback); mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy); } Loading @@ -101,14 +97,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { enforceSuggestPhoneTimePermission(); Objects.requireNonNull(timeSignal); long idToken = Binder.clearCallingIdentity(); try { synchronized (mStrategyLock) { mTimeDetectorStrategy.suggestPhoneTime(timeSignal); } } finally { Binder.restoreCallingIdentity(idToken); } mHandler.post(() -> mTimeDetectorStrategy.suggestPhoneTime(timeSignal)); } @Override Loading @@ -116,22 +105,12 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { enforceSuggestManualTimePermission(); Objects.requireNonNull(timeSignal); long idToken = Binder.clearCallingIdentity(); try { synchronized (mStrategyLock) { mTimeDetectorStrategy.suggestManualTime(timeSignal); } } finally { Binder.restoreCallingIdentity(idToken); } mHandler.post(() -> mTimeDetectorStrategy.suggestManualTime(timeSignal)); } @VisibleForTesting public void handleAutoTimeDetectionToggle() { synchronized (mStrategyLock) { final boolean timeDetectionEnabled = mCallback.isAutoTimeDetectionEnabled(); mTimeDetectorStrategy.handleAutoTimeDetectionToggle(timeDetectionEnabled); } mHandler.post(mTimeDetectorStrategy::handleAutoTimeDetectionChanged); } @Override Loading @@ -139,10 +118,8 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { @Nullable String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; synchronized (mStrategyLock) { mTimeDetectorStrategy.dump(pw, args); } } private void enforceSuggestPhoneTimePermission() { mContext.enforceCallingPermission(android.Manifest.permission.SET_TIME, "set time"); Loading services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +6 −4 Original line number Diff line number Diff line Loading @@ -27,12 +27,14 @@ import java.io.PrintWriter; /** * The interface for classes that implement the time detection algorithm used by the * 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). * TimeDetectorService. * * <p>Most calls will be handled by a single thread but that is not true for all calls. For example * {@link #dump(PrintWriter, String[])}) may be called on a different thread so implementations must * handle thread safety. * * @hide */ // @NotThreadSafe public interface TimeDetectorStrategy { /** Loading Loading @@ -79,7 +81,7 @@ public interface TimeDetectorStrategy { void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion); /** Handle the auto-time setting being toggled on or off. */ void handleAutoTimeDetectionToggle(boolean enabled); void handleAutoTimeDetectionChanged(); /** Dump debug information. */ void dump(@NonNull PrintWriter pw, @Nullable String[] args); Loading services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java→services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java +10 −7 Original line number Diff line number Diff line Loading @@ -40,7 +40,7 @@ import org.junit.runner.RunWith; import java.time.Duration; @RunWith(AndroidJUnit4.class) public class SimpleTimeZoneDetectorStrategyTest { public class SimpleTimeDetectorStrategyTest { private static final Scenario SCENARIO_1 = new Scenario.Builder() .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0) Loading Loading @@ -440,7 +440,7 @@ public class SimpleTimeZoneDetectorStrategyTest { mSystemClockMillis = systemClockMillis; } public void pokeTimeDetectionEnabled(boolean enabled) { public void pokeAutoTimeDetectionEnabled(boolean enabled) { mTimeDetectionEnabled = enabled; } Loading @@ -457,6 +457,10 @@ public class SimpleTimeZoneDetectorStrategyTest { mSystemClockMillis += incrementMillis; } public void simulateAutoTimeZoneDetectionToggle() { mTimeDetectionEnabled = !mTimeDetectionEnabled; } public void verifySystemClockNotSet() { assertFalse(mSystemClockWasSet); } Loading Loading @@ -493,7 +497,7 @@ public class SimpleTimeZoneDetectorStrategyTest { private final FakeCallback mFakeCallback; private final SimpleTimeDetectorStrategy mSimpleTimeDetectorStrategy; public Script() { Script() { mFakeCallback = new FakeCallback(); mSimpleTimeDetectorStrategy = new SimpleTimeDetectorStrategy(); mSimpleTimeDetectorStrategy.initialize(mFakeCallback); Loading @@ -501,7 +505,7 @@ public class SimpleTimeZoneDetectorStrategyTest { } Script pokeTimeDetectionEnabled(boolean enabled) { mFakeCallback.pokeTimeDetectionEnabled(enabled); mFakeCallback.pokeAutoTimeDetectionEnabled(enabled); return this; } Loading Loading @@ -535,9 +539,8 @@ public class SimpleTimeZoneDetectorStrategyTest { } Script simulateAutoTimeDetectionToggle() { boolean enabled = !mFakeCallback.isAutoTimeDetectionEnabled(); mFakeCallback.pokeTimeDetectionEnabled(enabled); mSimpleTimeDetectorStrategy.handleAutoTimeDetectionToggle(enabled); mFakeCallback.simulateAutoTimeZoneDetectionToggle(); mSimpleTimeDetectorStrategy.handleAutoTimeDetectionChanged(); return this; } Loading services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +76 −35 Original line number Diff line number Diff line Loading @@ -17,13 +17,11 @@ 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; 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.when; Loading @@ -32,12 +30,17 @@ import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; import android.content.Context; import android.content.pm.PackageManager; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.util.TimestampedValue; import androidx.test.runner.AndroidJUnit4; import com.android.server.timedetector.TimeDetectorStrategy.Callback; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; Loading @@ -52,54 +55,62 @@ public class TimeDetectorServiceTest { private Callback mMockCallback; private TimeDetectorService mTimeDetectorService; private HandlerThread mHandlerThread; private TestHandler mTestHandler; @Before public void setUp() { mMockContext = mock(Context.class); // Create a thread + handler for processing the work that the service posts. mHandlerThread = new HandlerThread("TimeDetectorServiceTest"); mHandlerThread.start(); mTestHandler = new TestHandler(mHandlerThread.getLooper()); mMockCallback = mock(Callback.class); mStubbedTimeDetectorStrategy = new StubbedTimeDetectorStrategy(); mTimeDetectorService = new TimeDetectorService( mMockContext, mMockCallback, mMockContext, mTestHandler, mMockCallback, mStubbedTimeDetectorStrategy); } @Test(expected=SecurityException.class) public void testStubbedCall_withoutPermission() { doThrow(new SecurityException("Mock")) .when(mMockContext).enforceCallingPermission(anyString(), any()); PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion(); try { mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion); } finally { verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.SET_TIME), anyString()); } @After public void tearDown() throws Exception { mHandlerThread.quit(); mHandlerThread.join(); } @Test public void testSuggestPhoneTime() { public void testSuggestPhoneTime() throws Exception { doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion(); mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion); mTestHandler.assertTotalMessagesEnqueued(1); verify(mMockContext) .enforceCallingPermission(eq(android.Manifest.permission.SET_TIME), anyString()); verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.SET_TIME), anyString()); mTestHandler.waitForEmptyQueue(); mStubbedTimeDetectorStrategy.verifySuggestPhoneTimeCalled(phoneTimeSuggestion); } @Test public void testSuggestManualTime() { public void testSuggestManualTime() throws Exception { doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion(); mTimeDetectorService.suggestManualTime(manualTimeSuggestion); mTestHandler.assertTotalMessagesEnqueued(1); verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.SET_TIME), anyString()); mTestHandler.waitForEmptyQueue(); mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion); } Loading @@ -115,18 +126,16 @@ public class TimeDetectorServiceTest { } @Test public void testAutoTimeDetectionToggle() { when(mMockCallback.isAutoTimeDetectionEnabled()).thenReturn(true); public void testAutoTimeDetectionToggle() throws Exception { mTimeDetectorService.handleAutoTimeDetectionToggle(); mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(true); when(mMockCallback.isAutoTimeDetectionEnabled()).thenReturn(false); mTestHandler.assertTotalMessagesEnqueued(1); mTestHandler.waitForEmptyQueue(); mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(); mTimeDetectorService.handleAutoTimeDetectionToggle(); mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(false); mTestHandler.assertTotalMessagesEnqueued(2); mTestHandler.waitForEmptyQueue(); mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(); } private static PhoneTimeSuggestion createPhoneTimeSuggestion() { Loading @@ -147,7 +156,7 @@ public class TimeDetectorServiceTest { // Call tracking. private PhoneTimeSuggestion mLastPhoneSuggestion; private ManualTimeSuggestion mLastManualSuggestion; private Boolean mLastAutoTimeDetectionToggle; private boolean mLastAutoTimeDetectionToggleCalled; private boolean mDumpCalled; @Override Loading @@ -167,9 +176,9 @@ public class TimeDetectorServiceTest { } @Override public void handleAutoTimeDetectionToggle(boolean enabled) { public void handleAutoTimeDetectionChanged() { resetCallTracking(); mLastAutoTimeDetectionToggle = enabled; mLastAutoTimeDetectionToggleCalled = true; } @Override Loading @@ -181,7 +190,7 @@ public class TimeDetectorServiceTest { void resetCallTracking() { mLastPhoneSuggestion = null; mLastManualSuggestion = null; mLastAutoTimeDetectionToggle = null; mLastAutoTimeDetectionToggleCalled = false; mDumpCalled = false; } Loading @@ -193,13 +202,45 @@ public class TimeDetectorServiceTest { assertEquals(expectedSuggestion, mLastManualSuggestion); } void verifyHandleAutoTimeDetectionToggleCalled(boolean expectedEnable) { assertNotNull(mLastAutoTimeDetectionToggle); assertEquals(expectedEnable, mLastAutoTimeDetectionToggle); void verifyHandleAutoTimeDetectionToggleCalled() { assertTrue(mLastAutoTimeDetectionToggleCalled); } void verifyDumpCalled() { assertTrue(mDumpCalled); } } /** * A Handler that can track posts/sends and wait for work to be completed. */ private static class TestHandler extends Handler { private int mMessagesSent; TestHandler(Looper looper) { super(looper); } @Override public boolean sendMessageAtTime(Message msg, long uptimeMillis) { mMessagesSent++; return super.sendMessageAtTime(msg, uptimeMillis); } /** Asserts the number of messages posted or sent is as expected. */ void assertTotalMessagesEnqueued(int expected) { assertEquals(expected, mMessagesSent); } /** * Waits for all currently enqueued work due to be processed to be completed before * returning. */ void waitForEmptyQueue() throws InterruptedException { while (!getLooper().getQueue().isIdle()) { Thread.sleep(100); } } } } Loading
services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java +58 −26 Original line number Diff line number Diff line Loading @@ -23,10 +23,13 @@ import android.app.AlarmManager; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; import android.content.Intent; import android.util.LocalLog; import android.util.Slog; import android.util.TimestampedValue; import com.android.internal.annotations.GuardedBy; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.util.IndentingPrintWriter; import java.io.PrintWriter; import java.lang.annotation.Retention; Loading @@ -34,13 +37,14 @@ import java.lang.annotation.RetentionPolicy; /** * 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). * {@link AlarmManager}. * * <p>Most public methods are marked synchronized to ensure thread safety around internal state. */ // @NotThreadSafe public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { private final static String TAG = "timedetector.SimpleTimeDetectorStrategy"; private static final boolean DBG = false; private static final String LOG_TAG = "SimpleTimeDetectorStrategy"; @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL }) @Retention(RetentionPolicy.SOURCE) Loading @@ -61,6 +65,9 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { */ private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000; // A log for changes made to the system clock and why. @NonNull private final LocalLog mTimeChangesLog = new LocalLog(30); // @NonNull after initialize() private Callback mCallback; Loading @@ -80,7 +87,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { } @Override public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { // NITZ logic // Empty suggestions are just ignored as we don't currently keep track of suggestion origin. Loading @@ -103,7 +110,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { } @Override public void suggestManualTime(ManualTimeSuggestion timeSuggestion) { public synchronized void suggestManualTime(ManualTimeSuggestion timeSuggestion) { final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime(); setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, timeSuggestion); } Loading @@ -116,7 +123,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { newSuggestion.getUtcTime(), lastSuggestion.getUtcTime()); if (referenceTimeDifference < 0 || referenceTimeDifference > Integer.MAX_VALUE) { // Out of order or bogus. Slog.w(TAG, "validateNewNitzTime: Bad NITZ signal received." Slog.w(LOG_TAG, "Bad NITZ signal received." + " referenceTimeDifference=" + referenceTimeDifference + " lastSuggestion=" + lastSuggestion + " newSuggestion=" + newSuggestion); Loading @@ -126,6 +133,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { return true; } @GuardedBy("this") private void setSystemClockIfRequired( @Origin int origin, TimestampedValue<Long> time, Object cause) { // Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only Loading @@ -140,16 +148,20 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { mLastAutoSystemClockTimeSendNetworkBroadcast = sendNetworkBroadcast; if (!mCallback.isAutoTimeDetectionEnabled()) { Slog.d(TAG, "setSystemClockIfRequired: Auto time detection is not enabled." if (DBG) { Slog.d(LOG_TAG, "Auto time detection is not enabled." + " time=" + time + ", cause=" + cause); } return; } } else { if (mCallback.isAutoTimeDetectionEnabled()) { Slog.d(TAG, "setSystemClockIfRequired: Auto time detection is enabled." if (DBG) { Slog.d(LOG_TAG, "Auto time detection is enabled." + " time=" + time + ", cause=" + cause); } return; } } Loading @@ -167,7 +179,7 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { mLastAutoSystemClockTimeSet, elapsedRealtimeMillis); long absSystemClockDifference = Math.abs(expectedTimeMillis - actualTimeMillis); if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) { Slog.w(TAG, Slog.w(LOG_TAG, "System clock has not tracked elapsed real time clock. A clock may" + " be inaccurate or something unexpectedly set the system" + " clock." Loading @@ -190,9 +202,10 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { } @Override public void handleAutoTimeDetectionToggle(boolean enabled) { public synchronized void handleAutoTimeDetectionChanged() { // If automatic time detection is enabled we update the system clock instantly if we can. // Conversely, if automatic time detection is disabled we leave the clock as it is. boolean enabled = mCallback.isAutoTimeDetectionEnabled(); if (enabled) { if (mLastAutoSystemClockTime != null) { // Only send the network broadcast if the last candidate would have caused one. Loading @@ -218,14 +231,27 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { } @Override public void dump(@NonNull PrintWriter pw, @Nullable String[] args) { public synchronized void dump(@NonNull PrintWriter pw, @Nullable String[] args) { pw.println("mLastPhoneSuggestion=" + mLastPhoneSuggestion); pw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet); pw.println("mLastAutoSystemClockTime=" + mLastAutoSystemClockTime); pw.println("mLastAutoSystemClockTimeSendNetworkBroadcast=" + mLastAutoSystemClockTimeSendNetworkBroadcast); IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); ipw.println("TimeDetectorStrategyImpl logs:"); ipw.increaseIndent(); // level 1 ipw.println("Time change log:"); ipw.increaseIndent(); // level 2 mTimeChangesLog.dump(ipw); ipw.decreaseIndent(); // level 2 ipw.decreaseIndent(); // level 1 } @GuardedBy("this") private void adjustAndSetDeviceSystemClock( TimestampedValue<Long> newTime, boolean sendNetworkBroadcast, long elapsedRealtimeMillis, long actualSystemClockMillis, Object cause) { Loading @@ -238,21 +264,27 @@ public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis); long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis(); if (absTimeDifference < systemClockUpdateThreshold) { Slog.d(TAG, "adjustAndSetDeviceSystemClock: Not setting system clock. New time and" if (DBG) { Slog.d(LOG_TAG, "Not setting system clock. New time and" + " system clock are close enough." + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + " newTime=" + newTime + " cause=" + cause + " systemClockUpdateThreshold=" + systemClockUpdateThreshold + " absTimeDifference=" + absTimeDifference); } return; } Slog.d(TAG, "Setting system clock using time=" + newTime mCallback.setSystemClock(newSystemClockMillis); String logMsg = "Set system clock using time=" + newTime + " cause=" + cause + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + " newTimeMillis=" + newSystemClockMillis); mCallback.setSystemClock(newSystemClockMillis); + " newSystemClockMillis=" + newSystemClockMillis; if (DBG) { Slog.d(LOG_TAG, logMsg); } mTimeChangesLog.log(logMsg); // CLOCK_PARANOIA : Record the last time this class set the system clock. mLastAutoSystemClockTimeSet = newTime; Loading
services/core/java/com/android/server/timedetector/TimeDetectorService.java +15 −38 Original line number Diff line number Diff line Loading @@ -24,10 +24,9 @@ import android.app.timedetector.PhoneTimeSuggestion; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.os.Binder; import android.os.Handler; 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; Loading @@ -39,7 +38,7 @@ import java.io.PrintWriter; import java.util.Objects; public final class TimeDetectorService extends ITimeDetectorService.Stub { private static final String TAG = "timedetector.TimeDetectorService"; private static final String TAG = "TimeDetectorService"; public static class Lifecycle extends SystemService { Loading @@ -57,29 +56,25 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { } } @NonNull private final Handler mHandler; @NonNull private final Context mContext; @NonNull private final Callback mCallback; // 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); TimeDetectorStrategy timeDetector = new SimpleTimeDetectorStrategy(); TimeDetectorStrategyCallbackImpl callback = new TimeDetectorStrategyCallbackImpl(context); timeDetector.initialize(callback); Handler handler = FgThread.getHandler(); TimeDetectorService timeDetectorService = new TimeDetectorService(context, callback, timeDetector); new TimeDetectorService(context, handler, callback, timeDetector); // Wire up event listening. ContentResolver contentResolver = context.getContentResolver(); contentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true, new ContentObserver(FgThread.getHandler()) { new ContentObserver(handler) { public void onChange(boolean selfChange) { timeDetectorService.handleAutoTimeDetectionToggle(); } Loading @@ -89,9 +84,10 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { } @VisibleForTesting public TimeDetectorService(@NonNull Context context, @NonNull Callback callback, @NonNull TimeDetectorStrategy timeDetectorStrategy) { public TimeDetectorService(@NonNull Context context, @NonNull Handler handler, @NonNull Callback callback, @NonNull TimeDetectorStrategy timeDetectorStrategy) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); mCallback = Objects.requireNonNull(callback); mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy); } Loading @@ -101,14 +97,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { enforceSuggestPhoneTimePermission(); Objects.requireNonNull(timeSignal); long idToken = Binder.clearCallingIdentity(); try { synchronized (mStrategyLock) { mTimeDetectorStrategy.suggestPhoneTime(timeSignal); } } finally { Binder.restoreCallingIdentity(idToken); } mHandler.post(() -> mTimeDetectorStrategy.suggestPhoneTime(timeSignal)); } @Override Loading @@ -116,22 +105,12 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { enforceSuggestManualTimePermission(); Objects.requireNonNull(timeSignal); long idToken = Binder.clearCallingIdentity(); try { synchronized (mStrategyLock) { mTimeDetectorStrategy.suggestManualTime(timeSignal); } } finally { Binder.restoreCallingIdentity(idToken); } mHandler.post(() -> mTimeDetectorStrategy.suggestManualTime(timeSignal)); } @VisibleForTesting public void handleAutoTimeDetectionToggle() { synchronized (mStrategyLock) { final boolean timeDetectionEnabled = mCallback.isAutoTimeDetectionEnabled(); mTimeDetectorStrategy.handleAutoTimeDetectionToggle(timeDetectionEnabled); } mHandler.post(mTimeDetectorStrategy::handleAutoTimeDetectionChanged); } @Override Loading @@ -139,10 +118,8 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { @Nullable String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; synchronized (mStrategyLock) { mTimeDetectorStrategy.dump(pw, args); } } private void enforceSuggestPhoneTimePermission() { mContext.enforceCallingPermission(android.Manifest.permission.SET_TIME, "set time"); Loading
services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +6 −4 Original line number Diff line number Diff line Loading @@ -27,12 +27,14 @@ import java.io.PrintWriter; /** * The interface for classes that implement the time detection algorithm used by the * 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). * TimeDetectorService. * * <p>Most calls will be handled by a single thread but that is not true for all calls. For example * {@link #dump(PrintWriter, String[])}) may be called on a different thread so implementations must * handle thread safety. * * @hide */ // @NotThreadSafe public interface TimeDetectorStrategy { /** Loading Loading @@ -79,7 +81,7 @@ public interface TimeDetectorStrategy { void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion); /** Handle the auto-time setting being toggled on or off. */ void handleAutoTimeDetectionToggle(boolean enabled); void handleAutoTimeDetectionChanged(); /** Dump debug information. */ void dump(@NonNull PrintWriter pw, @Nullable String[] args); Loading
services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java→services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java +10 −7 Original line number Diff line number Diff line Loading @@ -40,7 +40,7 @@ import org.junit.runner.RunWith; import java.time.Duration; @RunWith(AndroidJUnit4.class) public class SimpleTimeZoneDetectorStrategyTest { public class SimpleTimeDetectorStrategyTest { private static final Scenario SCENARIO_1 = new Scenario.Builder() .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0) Loading Loading @@ -440,7 +440,7 @@ public class SimpleTimeZoneDetectorStrategyTest { mSystemClockMillis = systemClockMillis; } public void pokeTimeDetectionEnabled(boolean enabled) { public void pokeAutoTimeDetectionEnabled(boolean enabled) { mTimeDetectionEnabled = enabled; } Loading @@ -457,6 +457,10 @@ public class SimpleTimeZoneDetectorStrategyTest { mSystemClockMillis += incrementMillis; } public void simulateAutoTimeZoneDetectionToggle() { mTimeDetectionEnabled = !mTimeDetectionEnabled; } public void verifySystemClockNotSet() { assertFalse(mSystemClockWasSet); } Loading Loading @@ -493,7 +497,7 @@ public class SimpleTimeZoneDetectorStrategyTest { private final FakeCallback mFakeCallback; private final SimpleTimeDetectorStrategy mSimpleTimeDetectorStrategy; public Script() { Script() { mFakeCallback = new FakeCallback(); mSimpleTimeDetectorStrategy = new SimpleTimeDetectorStrategy(); mSimpleTimeDetectorStrategy.initialize(mFakeCallback); Loading @@ -501,7 +505,7 @@ public class SimpleTimeZoneDetectorStrategyTest { } Script pokeTimeDetectionEnabled(boolean enabled) { mFakeCallback.pokeTimeDetectionEnabled(enabled); mFakeCallback.pokeAutoTimeDetectionEnabled(enabled); return this; } Loading Loading @@ -535,9 +539,8 @@ public class SimpleTimeZoneDetectorStrategyTest { } Script simulateAutoTimeDetectionToggle() { boolean enabled = !mFakeCallback.isAutoTimeDetectionEnabled(); mFakeCallback.pokeTimeDetectionEnabled(enabled); mSimpleTimeDetectorStrategy.handleAutoTimeDetectionToggle(enabled); mFakeCallback.simulateAutoTimeZoneDetectionToggle(); mSimpleTimeDetectorStrategy.handleAutoTimeDetectionChanged(); return this; } Loading
services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +76 −35 Original line number Diff line number Diff line Loading @@ -17,13 +17,11 @@ 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; 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.when; Loading @@ -32,12 +30,17 @@ import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; import android.content.Context; import android.content.pm.PackageManager; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.util.TimestampedValue; import androidx.test.runner.AndroidJUnit4; import com.android.server.timedetector.TimeDetectorStrategy.Callback; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; Loading @@ -52,54 +55,62 @@ public class TimeDetectorServiceTest { private Callback mMockCallback; private TimeDetectorService mTimeDetectorService; private HandlerThread mHandlerThread; private TestHandler mTestHandler; @Before public void setUp() { mMockContext = mock(Context.class); // Create a thread + handler for processing the work that the service posts. mHandlerThread = new HandlerThread("TimeDetectorServiceTest"); mHandlerThread.start(); mTestHandler = new TestHandler(mHandlerThread.getLooper()); mMockCallback = mock(Callback.class); mStubbedTimeDetectorStrategy = new StubbedTimeDetectorStrategy(); mTimeDetectorService = new TimeDetectorService( mMockContext, mMockCallback, mMockContext, mTestHandler, mMockCallback, mStubbedTimeDetectorStrategy); } @Test(expected=SecurityException.class) public void testStubbedCall_withoutPermission() { doThrow(new SecurityException("Mock")) .when(mMockContext).enforceCallingPermission(anyString(), any()); PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion(); try { mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion); } finally { verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.SET_TIME), anyString()); } @After public void tearDown() throws Exception { mHandlerThread.quit(); mHandlerThread.join(); } @Test public void testSuggestPhoneTime() { public void testSuggestPhoneTime() throws Exception { doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion(); mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion); mTestHandler.assertTotalMessagesEnqueued(1); verify(mMockContext) .enforceCallingPermission(eq(android.Manifest.permission.SET_TIME), anyString()); verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.SET_TIME), anyString()); mTestHandler.waitForEmptyQueue(); mStubbedTimeDetectorStrategy.verifySuggestPhoneTimeCalled(phoneTimeSuggestion); } @Test public void testSuggestManualTime() { public void testSuggestManualTime() throws Exception { doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion(); mTimeDetectorService.suggestManualTime(manualTimeSuggestion); mTestHandler.assertTotalMessagesEnqueued(1); verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.SET_TIME), anyString()); mTestHandler.waitForEmptyQueue(); mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion); } Loading @@ -115,18 +126,16 @@ public class TimeDetectorServiceTest { } @Test public void testAutoTimeDetectionToggle() { when(mMockCallback.isAutoTimeDetectionEnabled()).thenReturn(true); public void testAutoTimeDetectionToggle() throws Exception { mTimeDetectorService.handleAutoTimeDetectionToggle(); mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(true); when(mMockCallback.isAutoTimeDetectionEnabled()).thenReturn(false); mTestHandler.assertTotalMessagesEnqueued(1); mTestHandler.waitForEmptyQueue(); mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(); mTimeDetectorService.handleAutoTimeDetectionToggle(); mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(false); mTestHandler.assertTotalMessagesEnqueued(2); mTestHandler.waitForEmptyQueue(); mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(); } private static PhoneTimeSuggestion createPhoneTimeSuggestion() { Loading @@ -147,7 +156,7 @@ public class TimeDetectorServiceTest { // Call tracking. private PhoneTimeSuggestion mLastPhoneSuggestion; private ManualTimeSuggestion mLastManualSuggestion; private Boolean mLastAutoTimeDetectionToggle; private boolean mLastAutoTimeDetectionToggleCalled; private boolean mDumpCalled; @Override Loading @@ -167,9 +176,9 @@ public class TimeDetectorServiceTest { } @Override public void handleAutoTimeDetectionToggle(boolean enabled) { public void handleAutoTimeDetectionChanged() { resetCallTracking(); mLastAutoTimeDetectionToggle = enabled; mLastAutoTimeDetectionToggleCalled = true; } @Override Loading @@ -181,7 +190,7 @@ public class TimeDetectorServiceTest { void resetCallTracking() { mLastPhoneSuggestion = null; mLastManualSuggestion = null; mLastAutoTimeDetectionToggle = null; mLastAutoTimeDetectionToggleCalled = false; mDumpCalled = false; } Loading @@ -193,13 +202,45 @@ public class TimeDetectorServiceTest { assertEquals(expectedSuggestion, mLastManualSuggestion); } void verifyHandleAutoTimeDetectionToggleCalled(boolean expectedEnable) { assertNotNull(mLastAutoTimeDetectionToggle); assertEquals(expectedEnable, mLastAutoTimeDetectionToggle); void verifyHandleAutoTimeDetectionToggleCalled() { assertTrue(mLastAutoTimeDetectionToggleCalled); } void verifyDumpCalled() { assertTrue(mDumpCalled); } } /** * A Handler that can track posts/sends and wait for work to be completed. */ private static class TestHandler extends Handler { private int mMessagesSent; TestHandler(Looper looper) { super(looper); } @Override public boolean sendMessageAtTime(Message msg, long uptimeMillis) { mMessagesSent++; return super.sendMessageAtTime(msg, uptimeMillis); } /** Asserts the number of messages posted or sent is as expected. */ void assertTotalMessagesEnqueued(int expected) { assertEquals(expected, mMessagesSent); } /** * Waits for all currently enqueued work due to be processed to be completed before * returning. */ void waitForEmptyQueue() throws InterruptedException { while (!getLooper().getQueue().isIdle()) { Thread.sleep(100); } } } }