Add a new method to set time

Before this change there are a various components that set the system
clock by directly calling AlarmManager.setTime(). This change exposes a
new method on TimeDetector to use when setting the device time manually
(e.g. via settings) and modifies some callers to use it.

The intent is to later restrict the number of distinct processes that
can manipulate the device system clock directly so that all time changes
go through the time detector service, which can enforce policy, log
the reasons for changes, and so on.

Bug: 140712361
Test: atest com.android.server.timedetector
Change-Id: I9300dba868ed61249d0848b0dd4b953996161bda
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 64ea59d..f86bacf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -22,6 +22,7 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.backup.IBackupManager;
+import android.app.timedetector.TimeDetector;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.Context;
 import android.content.Intent;
@@ -229,6 +230,11 @@
         AlarmManager getAlarmManager() {return services.alarmManager;}
 
         @Override
+        TimeDetector getTimeDetector() {
+            return services.timeDetector;
+        }
+
+        @Override
         LockPatternUtils newLockPatternUtils() {
             return services.lockPatternUtils;
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ed55aeb..b6ed22b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -66,6 +66,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.PasswordMetrics;
+import android.app.timedetector.ManualTimeSuggestion;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -3588,7 +3589,19 @@
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
         dpm.setTime(admin1, 0);
-        verify(getServices().alarmManager).setTime(0);
+
+        BaseMatcher<ManualTimeSuggestion> hasZeroTime = new BaseMatcher<ManualTimeSuggestion>() {
+            @Override
+            public boolean matches(Object item) {
+                final ManualTimeSuggestion suggestion = (ManualTimeSuggestion) item;
+                return suggestion.getUtcTime().getValue() == 0;
+            }
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("ManualTimeSuggestion{utcTime.value=0}");
+            }
+        };
+        verify(getServices().timeDetector).suggestManualTime(argThat(hasZeroTime));
     }
 
     public void testSetTimeFailWithPO() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index bd513dc..1a67576 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -207,6 +207,8 @@
         switch (name) {
             case Context.ALARM_SERVICE:
                 return mMockSystemServices.alarmManager;
+            case Context.TIME_DETECTOR_SERVICE:
+                return mMockSystemServices.timeDetector;
             case Context.USER_SERVICE:
                 return mMockSystemServices.userManager;
             case Context.POWER_SERVICE:
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index b0d0303..c927364 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -31,6 +31,7 @@
 import android.app.IActivityTaskManager;
 import android.app.NotificationManager;
 import android.app.backup.IBackupManager;
+import android.app.timedetector.TimeDetector;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ContentValues;
@@ -111,6 +112,7 @@
     public final TelephonyManager telephonyManager;
     public final AccountManager accountManager;
     public final AlarmManager alarmManager;
+    public final TimeDetector timeDetector;
     public final KeyChain.KeyChainConnection keyChainConnection;
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
@@ -152,6 +154,7 @@
         telephonyManager = mock(TelephonyManager.class);
         accountManager = mock(AccountManager.class);
         alarmManager = mock(AlarmManager.class);
+        timeDetector = mock(TimeDetector.class);
         keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
 
         // Package manager is huge, so we use a partial mock instead.
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
index b49845a..317fd4d 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Intent;
 import android.icu.util.Calendar;
@@ -36,6 +37,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Duration;
+
 @RunWith(AndroidJUnit4.class)
 public class SimpleTimeZoneDetectorStrategyTest {
 
@@ -47,6 +50,8 @@
 
     private static final int ARBITRARY_PHONE_ID = 123456;
 
+    private static final long ONE_DAY_MILLIS = Duration.ofDays(1).toMillis();
+
     private Script mScript;
 
     @Before
@@ -55,7 +60,7 @@
     }
 
     @Test
-    public void testSuggestPhoneTime_nitz_timeDetectionEnabled() {
+    public void testSuggestPhoneTime_autoTimeEnabled() {
         Scenario scenario = SCENARIO_1;
         mScript.pokeFakeClocks(scenario)
                 .pokeTimeDetectionEnabled(true);
@@ -67,7 +72,8 @@
 
         mScript.simulateTimePassing(clockIncrement)
                 .simulatePhoneTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectSystemClockMillis, true /* expectNetworkBroadcast */);
     }
 
     @Test
@@ -103,7 +109,8 @@
 
         // Send the first time signal. It should be used.
         mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                .verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis1);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectSystemClockMillis1, true /* expectNetworkBroadcast */);
 
         // Now send another time signal, but one that is too similar to the last one and should be
         // ignored.
@@ -130,11 +137,12 @@
                 TimeDetectorStrategy.getTimeAt(utcTime3, mScript.peekElapsedRealtimeMillis());
 
         mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
-                .verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis3);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectSystemClockMillis3, true /* expectNetworkBroadcast */);
     }
 
     @Test
-    public void testSuggestPhoneTime_nitz_timeDetectionDisabled() {
+    public void testSuggestPhoneTime_autoTimeDisabled() {
         Scenario scenario = SCENARIO_1;
         mScript.pokeFakeClocks(scenario)
                 .pokeTimeDetectionEnabled(false);
@@ -146,7 +154,7 @@
     }
 
     @Test
-    public void testSuggestPhoneTime_nitz_invalidNitzReferenceTimesIgnored() {
+    public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() {
         Scenario scenario = SCENARIO_1;
         final int systemClockUpdateThreshold = 2000;
         mScript.pokeFakeClocks(scenario)
@@ -161,7 +169,8 @@
         long expectedSystemClockMillis1 =
                 TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
         mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */);
 
         // The UTC time increment should be larger than the system clock update threshold so we
         // know it shouldn't be ignored for other reasons.
@@ -197,7 +206,8 @@
         PhoneTimeSuggestion timeSuggestion4 =
                 createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime4);
         mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
-                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis4, true /* expectNetworkBroadcast */);
     }
 
     @Test
@@ -229,7 +239,8 @@
 
         // Turn on auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */);
 
         // Turn off auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
@@ -256,7 +267,99 @@
 
         // Turn on auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis2, true /* expectNetworkBroadcast */);
+    }
+
+    @Test
+    public void testSuggestManualTime_autoTimeDisabled() {
+        Scenario scenario = SCENARIO_1;
+        mScript.pokeFakeClocks(scenario)
+                .pokeTimeDetectionEnabled(false);
+
+        ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual();
+        final int clockIncrement = 1000;
+        long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
+
+        mScript.simulateTimePassing(clockIncrement)
+                .simulateManualTimeSuggestion(timeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectSystemClockMillis, false /* expectNetworkBroadcast */);
+    }
+
+    @Test
+    public void testSuggestManualTime_retainsAutoSignal() {
+        Scenario scenario = SCENARIO_1;
+
+        // Configure the start state.
+        mScript.pokeFakeClocks(scenario)
+                .pokeTimeDetectionEnabled(true);
+
+        // Simulate a phone suggestion.
+        PhoneTimeSuggestion phoneTimeSuggestion =
+                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
+        long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue();
+        final int clockIncrement = 1000;
+
+        // Simulate the passage of time.
+        mScript.simulateTimePassing(clockIncrement);
+        expectedAutoClockMillis += clockIncrement;
+
+        mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedAutoClockMillis, true /* expectNetworkBroadcast */);
+
+        // Simulate the passage of time.
+        mScript.simulateTimePassing(clockIncrement);
+        expectedAutoClockMillis += clockIncrement;
+
+        // Switch to manual.
+        mScript.simulateAutoTimeDetectionToggle()
+                .verifySystemClockWasNotSetAndResetCallTracking();
+
+        // Simulate the passage of time.
+        mScript.simulateTimePassing(clockIncrement);
+        expectedAutoClockMillis += clockIncrement;
+
+
+        // Simulate a manual suggestion 1 day different from the auto suggestion.
+        long manualTimeMillis = SCENARIO_1.getActualTimeMillis() + ONE_DAY_MILLIS;
+        long expectedManualClockMillis = manualTimeMillis;
+        ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion(manualTimeMillis);
+        mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedManualClockMillis, false /* expectNetworkBroadcast */);
+
+        // Simulate the passage of time.
+        mScript.simulateTimePassing(clockIncrement);
+        expectedAutoClockMillis += clockIncrement;
+
+        // Switch back to auto.
+        mScript.simulateAutoTimeDetectionToggle();
+
+        mScript.verifySystemClockWasSetAndResetCallTracking(
+                        expectedAutoClockMillis, true /* expectNetworkBroadcast */);
+
+        // Switch back to manual - nothing should happen to the clock.
+        mScript.simulateAutoTimeDetectionToggle()
+                .verifySystemClockWasNotSetAndResetCallTracking();
+    }
+
+    /**
+     * Manual suggestions should be ignored if auto time is enabled.
+     */
+    @Test
+    public void testSuggestManualTime_autoTimeEnabled() {
+        Scenario scenario = SCENARIO_1;
+        mScript.pokeFakeClocks(scenario)
+                .pokeTimeDetectionEnabled(true);
+
+        ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual();
+        final int clockIncrement = 1000;
+
+        mScript.simulateTimePassing(clockIncrement)
+                .simulateManualTimeSuggestion(timeSuggestion)
+                .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
     /**
@@ -280,7 +383,7 @@
         }
 
         @Override
-        public boolean isTimeDetectionEnabled() {
+        public boolean isAutoTimeDetectionEnabled() {
             return mTimeDetectionEnabled;
         }
 
@@ -426,8 +529,13 @@
             return this;
         }
 
+        Script simulateManualTimeSuggestion(ManualTimeSuggestion timeSuggestion) {
+            mSimpleTimeDetectorStrategy.suggestManualTime(timeSuggestion);
+            return this;
+        }
+
         Script simulateAutoTimeDetectionToggle() {
-            boolean enabled = !mFakeCallback.isTimeDetectionEnabled();
+            boolean enabled = !mFakeCallback.isAutoTimeDetectionEnabled();
             mFakeCallback.pokeTimeDetectionEnabled(enabled);
             mSimpleTimeDetectorStrategy.handleAutoTimeDetectionToggle(enabled);
             return this;
@@ -445,9 +553,12 @@
             return this;
         }
 
-        Script verifySystemClockWasSetAndResetCallTracking(long expectSystemClockMillis) {
+        Script verifySystemClockWasSetAndResetCallTracking(
+                long expectSystemClockMillis, boolean expectNetworkBroadcast) {
             mFakeCallback.verifySystemClockWasSet(expectSystemClockMillis);
-            mFakeCallback.verifyIntentWasBroadcast();
+            if (expectNetworkBroadcast) {
+                mFakeCallback.verifyIntentWasBroadcast();
+            }
             mFakeCallback.resetCallTracking();
             return this;
         }
@@ -486,6 +597,12 @@
             return createPhoneTimeSuggestion(phoneId, time);
         }
 
+        ManualTimeSuggestion createManualTimeSuggestionForActual() {
+            TimestampedValue<Long> time = new TimestampedValue<>(
+                    mInitialDeviceRealtimeMillis, mActualTimeMillis);
+            return new ManualTimeSuggestion(time);
+        }
+
         static class Builder {
 
             private long mInitialDeviceSystemClockMillis;
@@ -525,6 +642,12 @@
         return timeSuggestion;
     }
 
+    private ManualTimeSuggestion createManualTimeSuggestion(long timeMillis) {
+        TimestampedValue<Long> utcTime =
+                new TimestampedValue<>(mScript.peekElapsedRealtimeMillis(), timeMillis);
+        return new ManualTimeSuggestion(utcTime);
+    }
+
     private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
             int second) {
         Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index cfd8a45..4efe771 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -90,6 +91,19 @@
     }
 
     @Test
+    public void testSuggestManualTime() {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion();
+        mTimeDetectorService.suggestManualTime(manualTimeSuggestion);
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SET_TIME),
+                anyString());
+        mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion);
+    }
+
+    @Test
     public void testDump() {
         when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -102,13 +116,13 @@
 
     @Test
     public void testAutoTimeDetectionToggle() {
-        when(mMockCallback.isTimeDetectionEnabled()).thenReturn(true);
+        when(mMockCallback.isAutoTimeDetectionEnabled()).thenReturn(true);
 
         mTimeDetectorService.handleAutoTimeDetectionToggle();
 
         mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(true);
 
-        when(mMockCallback.isTimeDetectionEnabled()).thenReturn(false);
+        when(mMockCallback.isAutoTimeDetectionEnabled()).thenReturn(false);
 
         mTimeDetectorService.handleAutoTimeDetectionToggle();
 
@@ -123,10 +137,16 @@
         return suggestion;
     }
 
+    private static ManualTimeSuggestion createManualTimeSuggestion() {
+        TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
+        return new ManualTimeSuggestion(timeValue);
+    }
+
     private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy {
 
         // Call tracking.
         private PhoneTimeSuggestion mLastPhoneSuggestion;
+        private ManualTimeSuggestion mLastManualSuggestion;
         private Boolean mLastAutoTimeDetectionToggle;
         private boolean mDumpCalled;
 
@@ -141,6 +161,12 @@
         }
 
         @Override
+        public void suggestManualTime(ManualTimeSuggestion timeSuggestion) {
+            resetCallTracking();
+            mLastManualSuggestion = timeSuggestion;
+        }
+
+        @Override
         public void handleAutoTimeDetectionToggle(boolean enabled) {
             resetCallTracking();
             mLastAutoTimeDetectionToggle = enabled;
@@ -154,12 +180,17 @@
 
         void resetCallTracking() {
             mLastPhoneSuggestion = null;
+            mLastManualSuggestion = null;
             mLastAutoTimeDetectionToggle = null;
             mDumpCalled = false;
         }
 
-        void verifySuggestPhoneTimeCalled(PhoneTimeSuggestion expectedSignal) {
-            assertEquals(expectedSignal, mLastPhoneSuggestion);
+        void verifySuggestPhoneTimeCalled(PhoneTimeSuggestion expectedSuggestion) {
+            assertEquals(expectedSuggestion, mLastPhoneSuggestion);
+        }
+
+        public void verifySuggestManualTimeCalled(ManualTimeSuggestion expectedSuggestion) {
+            assertEquals(expectedSuggestion, mLastManualSuggestion);
         }
 
         void verifyHandleAutoTimeDetectionToggleCalled(boolean expectedEnable) {