diff --git a/core/java/android/app/time/UnixEpochTime.java b/core/java/android/app/time/UnixEpochTime.java index 61cbc5ed79770..0b8f7ee45a963 100644 --- a/core/java/android/app/time/UnixEpochTime.java +++ b/core/java/android/app/time/UnixEpochTime.java @@ -124,7 +124,7 @@ public final class UnixEpochTime implements Parcelable { @Override public String toString() { return "UnixEpochTime{" - + "mElapsedRealtimeTimeMillis=" + mElapsedRealtimeMillis + + "mElapsedRealtimeMillis=" + mElapsedRealtimeMillis + ", mUnixEpochTimeMillis=" + mUnixEpochTimeMillis + '}'; } diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index f95d6d3bb0563..50a7da1cede57 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -72,6 +72,18 @@ public interface TimeDetector { */ String SHELL_COMMAND_SUGGEST_NETWORK_TIME = "suggest_network_time"; + /** + * A shell command that prints the current network time information. + * @hide + */ + String SHELL_COMMAND_GET_NETWORK_TIME = "get_network_time"; + + /** + * A shell command that clears the detector's network time information. + * @hide + */ + String SHELL_COMMAND_CLEAR_NETWORK_TIME = "clear_network_time"; + /** * A shell command that injects a GNSS time suggestion. * @hide diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 1be9074e079a9..3e2395303354a 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -143,7 +143,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub return getTimeCapabilitiesAndConfig(userId); } - TimeCapabilitiesAndConfig getTimeCapabilitiesAndConfig(@UserIdInt int userId) { + private TimeCapabilitiesAndConfig getTimeCapabilitiesAndConfig(@UserIdInt int userId) { enforceManageTimeDetectorPermission(); final long token = mCallerIdentityInjector.clearCallingIdentity(); @@ -163,6 +163,9 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub return updateConfiguration(callingUserId, configuration); } + /** + * Updates the user's configuration. Exposed for use by {@link TimeDetectorShellCommand}. + */ boolean updateConfiguration(@UserIdInt int userId, @NonNull TimeConfiguration configuration) { // Resolve constants like USER_CURRENT to the true user ID as needed. int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), @@ -256,7 +259,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub } } - void handleConfigurationInternalChangedOnHandlerThread() { + private void handleConfigurationInternalChangedOnHandlerThread() { // Configuration has changed, but each user may have a different view of the configuration. // It's possible that this will cause unnecessary notifications but that shouldn't be a // problem. @@ -287,6 +290,10 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub } } + /** + * Sets the system time state. See {@link TimeState} for details. For use by {@link + * TimeDetectorShellCommand}. + */ void setTimeState(@NonNull TimeState timeState) { enforceManageTimeDetectorPermission(); @@ -353,6 +360,9 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub } } + /** + * Suggests network time with permission checks. For use by {@link TimeDetectorShellCommand}. + */ void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSignal) { enforceSuggestNetworkTimePermission(); Objects.requireNonNull(timeSignal); @@ -360,6 +370,23 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(timeSignal)); } + /** + * Clears the cached network time information. For use during tests to simulate when no network + * time has been made available. For use by {@link TimeDetectorShellCommand}. + * + *

This operation takes place in the calling thread. + */ + void clearNetworkTime() { + enforceSuggestNetworkTimePermission(); + + final long token = Binder.clearCallingIdentity(); + try { + mTimeDetectorStrategy.clearLatestNetworkSuggestion(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override public UnixEpochTime latestNetworkTime() { NetworkTimeSuggestion suggestion = getLatestNetworkSuggestion(); @@ -388,6 +415,9 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub } } + /** + * Suggests GNSS time with permission checks. For use by {@link TimeDetectorShellCommand}. + */ void suggestGnssTime(@NonNull GnssTimeSuggestion timeSignal) { enforceSuggestGnssTimePermission(); Objects.requireNonNull(timeSignal); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java index 990c00feae16b..cce5709861689 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java @@ -15,7 +15,9 @@ */ package com.android.server.timedetector; +import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CLEAR_NETWORK_TIME; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CONFIRM_TIME; +import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_NETWORK_TIME; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_TIME_STATE; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SERVICE_NAME; @@ -70,6 +72,10 @@ class TimeDetectorShellCommand extends ShellCommand { return runSuggestTelephonyTime(); case SHELL_COMMAND_SUGGEST_NETWORK_TIME: return runSuggestNetworkTime(); + case SHELL_COMMAND_GET_NETWORK_TIME: + return runGetNetworkTime(); + case SHELL_COMMAND_CLEAR_NETWORK_TIME: + return runClearNetworkTime(); case SHELL_COMMAND_SUGGEST_GNSS_TIME: return runSuggestGnssTime(); case SHELL_COMMAND_SUGGEST_EXTERNAL_TIME: @@ -122,6 +128,18 @@ class TimeDetectorShellCommand extends ShellCommand { mInterface::suggestNetworkTime); } + private int runGetNetworkTime() { + NetworkTimeSuggestion networkTimeSuggestion = mInterface.getLatestNetworkSuggestion(); + final PrintWriter pw = getOutPrintWriter(); + pw.println(networkTimeSuggestion); + return 0; + } + + private int runClearNetworkTime() { + mInterface.clearNetworkTime(); + return 0; + } + private int runSuggestGnssTime() { return runSuggestTime( () -> GnssTimeSuggestion.parseCommandLineArg(this), @@ -196,6 +214,10 @@ class TimeDetectorShellCommand extends ShellCommand { pw.printf(" Sets the current time state for tests.\n"); pw.printf(" %s \n", SHELL_COMMAND_CONFIRM_TIME); pw.printf(" Tries to confirms the time, raising the confidence.\n"); + pw.printf(" %s\n", SHELL_COMMAND_GET_NETWORK_TIME); + pw.printf(" Prints the network time information held by the detector.\n"); + pw.printf(" %s\n", SHELL_COMMAND_CLEAR_NETWORK_TIME); + pw.printf(" Clears the network time information held by the detector.\n"); pw.println(); ManualTimeSuggestion.printCommandLineOpts(pw); pw.println(); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index 03f236d9b30d2..9dca6ec26d298 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -18,6 +18,7 @@ package com.android.server.timedetector; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; import android.app.time.TimeState; @@ -103,6 +104,20 @@ public interface TimeDetectorStrategy extends Dumpable { /** Processes the suggested time from network sources. */ void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSuggestion); + /** + * Returns the latest (accepted) network time suggestion. Returns {@code null} if there isn't + * one. + */ + @Nullable + NetworkTimeSuggestion getLatestNetworkSuggestion(); + + /** + * Clears the latest network time suggestion, leaving none. The remaining time signals from + * other sources will be reassessed causing the device's time to be updated if config and + * settings allow. + */ + void clearLatestNetworkSuggestion(); + /** Processes the suggested time from gnss sources. */ void suggestGnssTime(@NonNull GnssTimeSuggestion timeSuggestion); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index 13ec75329e39a..09bb8036406d8 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -315,6 +315,21 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { doAutoTimeDetection(reason); } + @Override + @Nullable + public synchronized NetworkTimeSuggestion getLatestNetworkSuggestion() { + return mLastNetworkSuggestion.get(); + } + + @Override + public synchronized void clearLatestNetworkSuggestion() { + mLastNetworkSuggestion.set(null); + + // The loss of network time may change the time signal to use to set the system clock. + String reason = "Network time cleared"; + doAutoTimeDetection(reason); + } + @Override @NonNull public synchronized TimeState getTimeState() { @@ -1063,15 +1078,6 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { return mSuggestionBySlotIndex.get(slotIndex); } - /** - * A method used to inspect state during tests. Not intended for general use. - */ - @VisibleForTesting - @Nullable - public synchronized NetworkTimeSuggestion getLatestNetworkSuggestion() { - return mLastNetworkSuggestion.get(); - } - /** * A method used to inspect state during tests. Not intended for general use. */ diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java index 856df359b3269..50040b70c47ee 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java @@ -61,6 +61,15 @@ public class FakeTimeDetectorStrategy implements TimeDetectorStrategy { public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) { } + @Override + public NetworkTimeSuggestion getLatestNetworkSuggestion() { + return null; + } + + @Override + public void clearLatestNetworkSuggestion() { + } + @Override public void suggestGnssTime(GnssTimeSuggestion timeSuggestion) { } 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 a8572381208d0..0b339ad52eda3 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -379,6 +379,28 @@ public class TimeDetectorServiceTest { verify(mFakeTimeDetectorStrategySpy).suggestExternalTime(externalTimeSuggestion); } + @Test + public void testClearNetworkTime_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingPermission(anyString(), any()); + + assertThrows(SecurityException.class, + () -> mTimeDetectorService.clearNetworkTime()); + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.SET_TIME), anyString()); + } + + @Test + public void testClearNetworkTime() throws Exception { + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + + mTimeDetectorService.clearNetworkTime(); + + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.SET_TIME), anyString()); + verify(mFakeTimeDetectorStrategySpy).clearLatestNetworkSuggestion(); + } + @Test public void testLatestNetworkTime() { NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult( diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index caef4943118b2..37da2a28a892f 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -874,6 +874,7 @@ public class TimeDetectorStrategyImplTest { long expectedSystemClockMillis = script.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime()); script.simulateNetworkTimeSuggestion(timeSuggestion) + .assertLatestNetworkSuggestion(timeSuggestion) .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis); } @@ -891,9 +892,54 @@ public class TimeDetectorStrategyImplTest { script.simulateTimePassing() .simulateNetworkTimeSuggestion(timeSuggestion) + .assertLatestNetworkSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking(); } + @Test + public void testClearLatestNetworkSuggestion() { + ConfigurationInternal configInternal = + new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED) + .setOriginPriorities(ORIGIN_NETWORK, ORIGIN_EXTERNAL) + .build(); + Script script = new Script().simulateConfigurationInternalChange(configInternal); + + // Create two different time suggestions for the current elapsedRealtimeMillis. + ExternalTimeSuggestion externalTimeSuggestion = + script.generateExternalTimeSuggestion(ARBITRARY_TEST_TIME); + NetworkTimeSuggestion networkTimeSuggestion = + script.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME.plus(Duration.ofHours(5))); + script.simulateTimePassing(); + + // Suggest an external time: This should cause the device to change time. + { + long expectedSystemClockMillis = + script.calculateTimeInMillisForNow(externalTimeSuggestion.getUnixEpochTime()); + script.simulateExternalTimeSuggestion(externalTimeSuggestion) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis); + } + + // Suggest a network time: This should cause the device to change time because + // network > external. + { + long expectedSystemClockMillis = + script.calculateTimeInMillisForNow(networkTimeSuggestion.getUnixEpochTime()); + script.simulateNetworkTimeSuggestion(networkTimeSuggestion) + .assertLatestNetworkSuggestion(networkTimeSuggestion) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis); + } + + // Clear the network time. This should cause the device to change back to the external time, + // which is now the best time available. + { + long expectedSystemClockMillis = + script.calculateTimeInMillisForNow(externalTimeSuggestion.getUnixEpochTime()); + script.simulateClearLatestNetworkSuggestion() + .assertLatestNetworkSuggestion(null) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis); + } + } + @Test public void testSuggestNetworkTime_rejectedBelowLowerBound() { ConfigurationInternal configInternal = @@ -908,6 +954,7 @@ public class TimeDetectorStrategyImplTest { NetworkTimeSuggestion timeSuggestion = script.generateNetworkTimeSuggestion(belowLowerBound); script.simulateNetworkTimeSuggestion(timeSuggestion) + .assertLatestNetworkSuggestion(null) .verifySystemClockConfidence(TIME_CONFIDENCE_LOW) .verifySystemClockWasNotSetAndResetCallTracking(); } @@ -926,6 +973,7 @@ public class TimeDetectorStrategyImplTest { NetworkTimeSuggestion timeSuggestion = script.generateNetworkTimeSuggestion(aboveLowerBound); script.simulateNetworkTimeSuggestion(timeSuggestion) + .assertLatestNetworkSuggestion(timeSuggestion) .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH) .verifySystemClockWasSetAndResetCallTracking(aboveLowerBound.toEpochMilli()); } @@ -944,6 +992,7 @@ public class TimeDetectorStrategyImplTest { NetworkTimeSuggestion timeSuggestion = script.generateNetworkTimeSuggestion(aboveUpperBound); script.simulateNetworkTimeSuggestion(timeSuggestion) + .assertLatestNetworkSuggestion(null) .verifySystemClockConfidence(TIME_CONFIDENCE_LOW) .verifySystemClockWasNotSetAndResetCallTracking(); } @@ -962,6 +1011,7 @@ public class TimeDetectorStrategyImplTest { NetworkTimeSuggestion timeSuggestion = script.generateNetworkTimeSuggestion(belowUpperBound); script.simulateNetworkTimeSuggestion(timeSuggestion) + .assertLatestNetworkSuggestion(timeSuggestion) .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH) .verifySystemClockWasSetAndResetCallTracking(belowUpperBound.toEpochMilli()); } @@ -1745,7 +1795,7 @@ public class TimeDetectorStrategyImplTest { } @Test - public void suggestionsFromNetworkOriginNotInPriorityList_areIgnored() { + public void suggestionsFromNetworkOriginNotInPriorityList_areNotUsed() { ConfigurationInternal configInternal = new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED) .setOriginPriorities(ORIGIN_TELEPHONY) @@ -1756,12 +1806,13 @@ public class TimeDetectorStrategyImplTest { ARBITRARY_TEST_TIME); script.simulateNetworkTimeSuggestion(timeSuggestion) + .assertLatestNetworkSuggestion(timeSuggestion) .assertLatestNetworkSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking(); } @Test - public void suggestionsFromGnssOriginNotInPriorityList_areIgnored() { + public void suggestionsFromGnssOriginNotInPriorityList_areNotUsed() { ConfigurationInternal configInternal = new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED) .setOriginPriorities(ORIGIN_TELEPHONY) @@ -1777,7 +1828,7 @@ public class TimeDetectorStrategyImplTest { } @Test - public void suggestionsFromExternalOriginNotInPriorityList_areIgnored() { + public void suggestionsFromExternalOriginNotInPriorityList_areNotUsed() { ConfigurationInternal configInternal = new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED) .setOriginPriorities(ORIGIN_TELEPHONY) @@ -2015,6 +2066,11 @@ public class TimeDetectorStrategyImplTest { return this; } + Script simulateClearLatestNetworkSuggestion() { + mTimeDetectorStrategy.clearLatestNetworkSuggestion(); + return this; + } + Script simulateGnssTimeSuggestion(GnssTimeSuggestion timeSuggestion) { mTimeDetectorStrategy.suggestGnssTime(timeSuggestion); return this; @@ -2056,6 +2112,12 @@ public class TimeDetectorStrategyImplTest { return this; } + /** Calls {@link TimeDetectorStrategy#confirmTime(UnixEpochTime)}. */ + Script simulateConfirmTime(UnixEpochTime confirmationTime, boolean expectedReturnValue) { + assertEquals(expectedReturnValue, mTimeDetectorStrategy.confirmTime(confirmationTime)); + return this; + } + Script verifySystemClockWasNotSetAndResetCallTracking() { mFakeEnvironment.verifySystemClockNotSet(); mFakeEnvironment.resetCallTracking(); @@ -2218,11 +2280,6 @@ public class TimeDetectorStrategyImplTest { long calculateTimeInMillisForNow(UnixEpochTime unixEpochTime) { return unixEpochTime.at(peekElapsedRealtimeMillis()).getUnixEpochTimeMillis(); } - - Script simulateConfirmTime(UnixEpochTime confirmationTime, boolean expectedReturnValue) { - assertEquals(expectedReturnValue, mTimeDetectorStrategy.confirmTime(confirmationTime)); - return this; - } } private static TelephonyTimeSuggestion createTelephonyTimeSuggestion(int slotIndex,