Loading services/core/java/com/android/server/timedetector/ConfigurationInternal.java +4 −1 Original line number Diff line number Diff line Loading @@ -244,6 +244,8 @@ public final class ConfigurationInternal { && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting && mUserId == that.mUserId && mUserConfigAllowed == that.mUserConfigAllowed && mSystemClockUpdateThresholdMillis == that.mSystemClockUpdateThresholdMillis && mSystemClockConfidenceThresholdMillis == that.mSystemClockConfidenceThresholdMillis && mAutoSuggestionLowerBound.equals(that.mAutoSuggestionLowerBound) && mManualSuggestionLowerBound.equals(that.mManualSuggestionLowerBound) && mSuggestionUpperBound.equals(that.mSuggestionUpperBound) Loading @@ -253,7 +255,8 @@ public final class ConfigurationInternal { @Override public int hashCode() { int result = Objects.hash(mAutoDetectionSupported, mAutoDetectionEnabledSetting, mUserId, mUserConfigAllowed, mSystemClockUpdateThresholdMillis, mAutoSuggestionLowerBound, mUserConfigAllowed, mSystemClockUpdateThresholdMillis, mSystemClockConfidenceThresholdMillis, mAutoSuggestionLowerBound, mManualSuggestionLowerBound, mSuggestionUpperBound); result = 31 * result + Arrays.hashCode(mOriginPriorities); return result; Loading services/core/java/com/android/server/timedetector/EnvironmentImpl.java +17 −21 Original line number Diff line number Diff line Loading @@ -22,15 +22,16 @@ import android.content.Context; import android.os.Handler; import android.os.PowerManager; import android.os.SystemClock; import android.util.IndentingPrintWriter; import android.util.Slog; import com.android.server.AlarmManagerInternal; import com.android.server.LocalServices; import com.android.server.SystemClockTime; import com.android.server.SystemClockTime.TimeConfidence; import com.android.server.timezonedetector.StateChangeListener; import java.io.PrintWriter; import java.time.Duration; import java.time.Instant; import java.util.Objects; /** Loading @@ -41,14 +42,11 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { private static final String LOG_TAG = TimeDetectorService.TAG; @NonNull private final Handler mHandler; @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final PowerManager.WakeLock mWakeLock; @NonNull private final AlarmManagerInternal mAlarmManagerInternal; EnvironmentImpl(@NonNull Context context, @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) { EnvironmentImpl(@NonNull Context context, @NonNull Handler handler) { mHandler = Objects.requireNonNull(handler); mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); PowerManager powerManager = context.getSystemService(PowerManager.class); mWakeLock = Objects.requireNonNull( Loading @@ -58,19 +56,6 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { LocalServices.getService(AlarmManagerInternal.class)); } @Override public void setConfigurationInternalChangeListener( @NonNull StateChangeListener listener) { StateChangeListener stateChangeListener = () -> mHandler.post(listener::onChange); mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener); } @Override public ConfigurationInternal getCurrentUserConfigurationInternal() { return mServiceConfigAccessor.getCurrentUserConfigurationInternal(); } @Override public void acquireWakeLock() { if (mWakeLock.isHeld()) { Loading Loading @@ -126,8 +111,19 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { } @Override public void dumpDebugLog(@NonNull PrintWriter printWriter) { SystemClockTime.dump(printWriter); public void dumpDebugLog(@NonNull IndentingPrintWriter pw) { long elapsedRealtimeMillis = elapsedRealtimeMillis(); pw.printf("elapsedRealtimeMillis()=%s (%s)\n", Duration.ofMillis(elapsedRealtimeMillis), elapsedRealtimeMillis); long systemClockMillis = systemClockMillis(); pw.printf("systemClockMillis()=%s (%s)\n", Instant.ofEpochMilli(systemClockMillis), systemClockMillis); pw.println("systemClockConfidence()=" + systemClockConfidence()); pw.println("SystemClockTime debug log:"); pw.increaseIndent(); SystemClockTime.dump(pw); pw.decreaseIndent(); } @Override Loading services/core/java/com/android/server/timedetector/TimeDetectorService.java +12 −17 Original line number Diff line number Diff line Loading @@ -96,8 +96,8 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL; TimeDetectorService service = new TimeDetectorService( context, handler, callerIdentityInjector, serviceConfigAccessor, timeDetectorStrategy, NtpTrustedTime.getInstance(context)); context, handler, callerIdentityInjector, timeDetectorStrategy, NtpTrustedTime.getInstance(context)); // Publish the binder service so it can be accessed from other (appropriately // permissioned) processes. Loading @@ -108,7 +108,6 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub @NonNull private final Handler mHandler; @NonNull private final Context mContext; @NonNull private final CallerIdentityInjector mCallerIdentityInjector; @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy; @NonNull private final NtpTrustedTime mNtpTrustedTime; Loading @@ -123,20 +122,18 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub @VisibleForTesting public TimeDetectorService(@NonNull Context context, @NonNull Handler handler, @NonNull CallerIdentityInjector callerIdentityInjector, @NonNull ServiceConfigAccessor serviceConfigAccessor, @NonNull TimeDetectorStrategy timeDetectorStrategy, @NonNull NtpTrustedTime ntpTrustedTime) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector); mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy); mNtpTrustedTime = Objects.requireNonNull(ntpTrustedTime); // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when // the configuration changes for any reason. mServiceConfigAccessor.addConfigurationInternalChangeListener( () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread)); // Wire up a change listener so that ITimeDetectorListeners can be notified when the // detector state changes for any reason. mTimeDetectorStrategy.addChangeListener( () -> mHandler.post(this::handleChangeOnHandlerThread)); } @Override Loading @@ -151,10 +148,8 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub final long token = mCallerIdentityInjector.clearCallingIdentity(); try { ConfigurationInternal configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId); final boolean bypassUserPolicyCheck = false; return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyCheck); final boolean bypassUserPolicyChecks = false; return mTimeDetectorStrategy.getCapabilitiesAndConfig(userId, bypassUserPolicyChecks); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } Loading @@ -180,9 +175,9 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub final long token = mCallerIdentityInjector.clearCallingIdentity(); try { final boolean bypassUserPolicyCheck = false; return mServiceConfigAccessor.updateConfiguration( resolvedUserId, configuration, bypassUserPolicyCheck); final boolean bypassUserPolicyChecks = false; return mTimeDetectorStrategy.updateConfiguration( resolvedUserId, configuration, bypassUserPolicyChecks); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } Loading Loading @@ -262,7 +257,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub } } private void handleConfigurationInternalChangedOnHandlerThread() { private void handleChangeOnHandlerThread() { // 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. Loading services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +44 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; import android.app.time.TimeState; import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; Loading Loading @@ -87,6 +89,48 @@ public interface TimeDetectorStrategy extends Dumpable { */ boolean confirmTime(@NonNull UnixEpochTime confirmationTime); /** * Adds a listener that will be triggered when something changes that could affect the result * of the {@link #getCapabilitiesAndConfig} call for the <em>current user only</em>. This * includes the current user changing. This is exposed so that (indirect) users like SettingsUI * can monitor for changes to data derived from {@link TimeCapabilitiesAndConfig} and update * the UI accordingly. */ void addChangeListener(@NonNull StateChangeListener listener); /** * Returns a {@link TimeCapabilitiesAndConfig} object for the specified user. * * <p>The strategy is dependent on device state like current user, settings and device config. * These updates are usually handled asynchronously, so callers should expect some delay between * a change being made directly to services like settings and the strategy becoming aware of * them. Changes made via {@link #updateConfiguration} will be visible immediately. * * @param userId the user ID to retrieve the information for * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device * policy restrictions that should apply to actual users can be ignored */ TimeCapabilitiesAndConfig getCapabilitiesAndConfig( @UserIdInt int userId, boolean bypassUserPolicyChecks); /** * Updates the configuration properties that control a device's time behavior. * * <p>This method returns {@code true} if the configuration was changed, {@code false} * otherwise. * * <p>See {@link #getCapabilitiesAndConfig} for guarantees about visibility of updates to * subsequent calls. * * @param userId the current user ID, supplied to make sure that the asynchronous process * that happens when users switch is completed when the call is made * @param configuration the configuration changes * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device * policy restrictions that should apply to actual users can be ignored */ boolean updateConfiguration(@UserIdInt int userId, @NonNull TimeConfiguration configuration, boolean bypassUserPolicyChecks); /** Processes the suggested time from telephony sources. */ void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion suggestion); Loading services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +126 −49 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; import android.app.time.TimeCapabilities; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; import android.app.time.TimeState; import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; Loading @@ -48,10 +49,10 @@ import com.android.server.timezonedetector.ArrayMapWithHistory; import com.android.server.timezonedetector.ReferenceWithHistory; import com.android.server.timezonedetector.StateChangeListener; import java.io.PrintWriter; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; /** Loading Loading @@ -94,6 +95,12 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { @NonNull private final Environment mEnvironment; @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @GuardedBy("this") @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>(); @GuardedBy("this") @NonNull private ConfigurationInternal mCurrentConfigurationInternal; Loading Loading @@ -139,16 +146,6 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { */ public interface Environment { /** * Sets a {@link StateChangeListener} that will be invoked when there are any changes that * could affect the content of {@link ConfigurationInternal}. * This is invoked during system server setup. */ void setConfigurationInternalChangeListener(@NonNull StateChangeListener listener); /** Returns the {@link ConfigurationInternal} for the current user. */ @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */ void acquireWakeLock(); Loading @@ -174,16 +171,15 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { /** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */ void releaseWakeLock(); /** * Adds a standalone entry to the time debug log. */ void addDebugLogEntry(@NonNull String logMsg); /** * Dumps the time debug log to the supplied {@link PrintWriter}. * Dumps the time debug log to the supplied {@link IndentingPrintWriter}. */ void dumpDebugLog(PrintWriter printWriter); void dumpDebugLog(IndentingPrintWriter ipw); /** * Requests that the supplied runnable is invoked asynchronously. Loading @@ -195,19 +191,23 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { @NonNull Context context, @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) { TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context, handler, serviceConfigAccessor); return new TimeDetectorStrategyImpl(environment); TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context, handler); return new TimeDetectorStrategyImpl(environment, serviceConfigAccessor); } @VisibleForTesting TimeDetectorStrategyImpl(@NonNull Environment environment) { TimeDetectorStrategyImpl(@NonNull Environment environment, @NonNull ServiceConfigAccessor serviceConfigAccessor) { mEnvironment = Objects.requireNonNull(environment); mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); synchronized (this) { mEnvironment.setConfigurationInternalChangeListener( this::handleConfigurationInternalChanged); mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal(); // Listen for config and user changes and get an initial snapshot of configuration. StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged; mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener); // Initialize mCurrentConfigurationInternal with a starting value. updateCurrentConfigurationInternalIfRequired("TimeDetectorStrategyImpl:"); } } Loading Loading @@ -421,6 +421,57 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } } @GuardedBy("this") private void notifyStateChangeListenersAsynchronously() { for (StateChangeListener listener : mStateChangeListeners) { // This is queuing asynchronous notification, so no need to surrender the "this" lock. mEnvironment.runAsync(listener::onChange); } } @Override public synchronized void addChangeListener(@NonNull StateChangeListener listener) { mStateChangeListeners.add(listener); } @Override public synchronized TimeCapabilitiesAndConfig getCapabilitiesAndConfig(@UserIdInt int userId, boolean bypassUserPolicyChecks) { ConfigurationInternal configurationInternal; if (mCurrentConfigurationInternal.getUserId() == userId) { // Use the cached snapshot we have. configurationInternal = mCurrentConfigurationInternal; } else { // This is not a common case: It would be unusual to want the configuration for a user // other than the "current" user, but it is supported because it is trivial to do so. // Unlike the current user config, there's no cached copy to worry about so read it // directly from mServiceConfigAccessor. configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId); } return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks); } @Override public synchronized boolean updateConfiguration(@UserIdInt int userId, @NonNull TimeConfiguration configuration, boolean bypassUserPolicyChecks) { // Write-through boolean updateSuccessful = mServiceConfigAccessor.updateConfiguration( userId, configuration, bypassUserPolicyChecks); // The update above will trigger config update listeners asynchronously if they are needed, // but that could mean an immediate call to getCapabilitiesAndConfig() for the current user // wouldn't see the update. So, handle the cache update and notifications here. When the // async update listener triggers it will find everything already up to date and do nothing. if (updateSuccessful) { String logMsg = "updateConfiguration:" + " userId=" + userId + ", configuration=" + configuration + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks; updateCurrentConfigurationInternalIfRequired(logMsg); } return updateSuccessful; } @Override public synchronized void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion suggestion) { // Empty time suggestion means that telephony network connectivity has been lost. Loading Loading @@ -448,28 +499,51 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { doAutoTimeDetection(reason); } private synchronized void handleConfigurationInternalChanged() { ConfigurationInternal currentUserConfig = mEnvironment.getCurrentUserConfigurationInternal(); String logMsg = "handleConfigurationInternalChanged:" + " oldConfiguration=" + mCurrentConfigurationInternal + ", newConfiguration=" + currentUserConfig; /** * Handles a configuration change notification. */ private synchronized void handleConfigurationInternalMaybeChanged() { String logMsg = "handleConfigurationInternalMaybeChanged:"; updateCurrentConfigurationInternalIfRequired(logMsg); } @GuardedBy("this") private void updateCurrentConfigurationInternalIfRequired(@NonNull String logMsg) { ConfigurationInternal newCurrentConfigurationInternal = mServiceConfigAccessor.getCurrentUserConfigurationInternal(); // mCurrentConfigurationInternal is null the first time this method is called. ConfigurationInternal oldCurrentConfigurationInternal = mCurrentConfigurationInternal; // If the configuration actually changed, update the cached copy synchronously and do // other necessary house-keeping / (async) listener notifications. if (!newCurrentConfigurationInternal.equals(oldCurrentConfigurationInternal)) { mCurrentConfigurationInternal = newCurrentConfigurationInternal; logMsg = new StringBuilder(logMsg) .append(" [oldConfiguration=").append(oldCurrentConfigurationInternal) .append(", newConfiguration=").append(newCurrentConfigurationInternal) .append("]") .toString(); addDebugLogEntry(logMsg); mCurrentConfigurationInternal = currentUserConfig; // The configuration and maybe the status changed so notify listeners. notifyStateChangeListenersAsynchronously(); boolean autoDetectionEnabled = mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior(); // When automatic time detection is enabled we update the system clock instantly if we can. // Conversely, when automatic time detection is disabled we leave the clock as it is. // When automatic time detection is enabled we update the system clock instantly if we // can. Conversely, when automatic time detection is disabled we leave the clock as it // is. if (autoDetectionEnabled) { String reason = "Auto time zone detection config changed."; doAutoTimeDetection(reason); } else { // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what // it should be in future. // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict // what it should be in future. mLastAutoSystemClockTimeSet = null; } } } private void addDebugLogEntry(@NonNull String logMsg) { if (DBG) { Loading @@ -489,13 +563,11 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { ipw.println("[Capabilities=" + mCurrentConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks) + "]"); long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis(); ipw.printf("mEnvironment.elapsedRealtimeMillis()=%s (%s)\n", Duration.ofMillis(elapsedRealtimeMillis), elapsedRealtimeMillis); long systemClockMillis = mEnvironment.systemClockMillis(); ipw.printf("mEnvironment.systemClockMillis()=%s (%s)\n", Instant.ofEpochMilli(systemClockMillis), systemClockMillis); ipw.println("mEnvironment.systemClockConfidence()=" + mEnvironment.systemClockConfidence()); ipw.println("mEnvironment:"); ipw.increaseIndent(); mEnvironment.dumpDebugLog(ipw); ipw.decreaseIndent(); ipw.println("Time change log:"); ipw.increaseIndent(); // level 2 Loading Loading @@ -525,6 +597,11 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { ipw.decreaseIndent(); // level 1 } @VisibleForTesting public synchronized ConfigurationInternal getCachedCapabilitiesAndConfigForTests() { return mCurrentConfigurationInternal; } @GuardedBy("this") private boolean storeTelephonySuggestion(@NonNull TelephonyTimeSuggestion suggestion) { UnixEpochTime newUnixEpochTime = suggestion.getUnixEpochTime(); Loading Loading
services/core/java/com/android/server/timedetector/ConfigurationInternal.java +4 −1 Original line number Diff line number Diff line Loading @@ -244,6 +244,8 @@ public final class ConfigurationInternal { && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting && mUserId == that.mUserId && mUserConfigAllowed == that.mUserConfigAllowed && mSystemClockUpdateThresholdMillis == that.mSystemClockUpdateThresholdMillis && mSystemClockConfidenceThresholdMillis == that.mSystemClockConfidenceThresholdMillis && mAutoSuggestionLowerBound.equals(that.mAutoSuggestionLowerBound) && mManualSuggestionLowerBound.equals(that.mManualSuggestionLowerBound) && mSuggestionUpperBound.equals(that.mSuggestionUpperBound) Loading @@ -253,7 +255,8 @@ public final class ConfigurationInternal { @Override public int hashCode() { int result = Objects.hash(mAutoDetectionSupported, mAutoDetectionEnabledSetting, mUserId, mUserConfigAllowed, mSystemClockUpdateThresholdMillis, mAutoSuggestionLowerBound, mUserConfigAllowed, mSystemClockUpdateThresholdMillis, mSystemClockConfidenceThresholdMillis, mAutoSuggestionLowerBound, mManualSuggestionLowerBound, mSuggestionUpperBound); result = 31 * result + Arrays.hashCode(mOriginPriorities); return result; Loading
services/core/java/com/android/server/timedetector/EnvironmentImpl.java +17 −21 Original line number Diff line number Diff line Loading @@ -22,15 +22,16 @@ import android.content.Context; import android.os.Handler; import android.os.PowerManager; import android.os.SystemClock; import android.util.IndentingPrintWriter; import android.util.Slog; import com.android.server.AlarmManagerInternal; import com.android.server.LocalServices; import com.android.server.SystemClockTime; import com.android.server.SystemClockTime.TimeConfidence; import com.android.server.timezonedetector.StateChangeListener; import java.io.PrintWriter; import java.time.Duration; import java.time.Instant; import java.util.Objects; /** Loading @@ -41,14 +42,11 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { private static final String LOG_TAG = TimeDetectorService.TAG; @NonNull private final Handler mHandler; @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final PowerManager.WakeLock mWakeLock; @NonNull private final AlarmManagerInternal mAlarmManagerInternal; EnvironmentImpl(@NonNull Context context, @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) { EnvironmentImpl(@NonNull Context context, @NonNull Handler handler) { mHandler = Objects.requireNonNull(handler); mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); PowerManager powerManager = context.getSystemService(PowerManager.class); mWakeLock = Objects.requireNonNull( Loading @@ -58,19 +56,6 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { LocalServices.getService(AlarmManagerInternal.class)); } @Override public void setConfigurationInternalChangeListener( @NonNull StateChangeListener listener) { StateChangeListener stateChangeListener = () -> mHandler.post(listener::onChange); mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener); } @Override public ConfigurationInternal getCurrentUserConfigurationInternal() { return mServiceConfigAccessor.getCurrentUserConfigurationInternal(); } @Override public void acquireWakeLock() { if (mWakeLock.isHeld()) { Loading Loading @@ -126,8 +111,19 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { } @Override public void dumpDebugLog(@NonNull PrintWriter printWriter) { SystemClockTime.dump(printWriter); public void dumpDebugLog(@NonNull IndentingPrintWriter pw) { long elapsedRealtimeMillis = elapsedRealtimeMillis(); pw.printf("elapsedRealtimeMillis()=%s (%s)\n", Duration.ofMillis(elapsedRealtimeMillis), elapsedRealtimeMillis); long systemClockMillis = systemClockMillis(); pw.printf("systemClockMillis()=%s (%s)\n", Instant.ofEpochMilli(systemClockMillis), systemClockMillis); pw.println("systemClockConfidence()=" + systemClockConfidence()); pw.println("SystemClockTime debug log:"); pw.increaseIndent(); SystemClockTime.dump(pw); pw.decreaseIndent(); } @Override Loading
services/core/java/com/android/server/timedetector/TimeDetectorService.java +12 −17 Original line number Diff line number Diff line Loading @@ -96,8 +96,8 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL; TimeDetectorService service = new TimeDetectorService( context, handler, callerIdentityInjector, serviceConfigAccessor, timeDetectorStrategy, NtpTrustedTime.getInstance(context)); context, handler, callerIdentityInjector, timeDetectorStrategy, NtpTrustedTime.getInstance(context)); // Publish the binder service so it can be accessed from other (appropriately // permissioned) processes. Loading @@ -108,7 +108,6 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub @NonNull private final Handler mHandler; @NonNull private final Context mContext; @NonNull private final CallerIdentityInjector mCallerIdentityInjector; @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy; @NonNull private final NtpTrustedTime mNtpTrustedTime; Loading @@ -123,20 +122,18 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub @VisibleForTesting public TimeDetectorService(@NonNull Context context, @NonNull Handler handler, @NonNull CallerIdentityInjector callerIdentityInjector, @NonNull ServiceConfigAccessor serviceConfigAccessor, @NonNull TimeDetectorStrategy timeDetectorStrategy, @NonNull NtpTrustedTime ntpTrustedTime) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector); mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy); mNtpTrustedTime = Objects.requireNonNull(ntpTrustedTime); // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when // the configuration changes for any reason. mServiceConfigAccessor.addConfigurationInternalChangeListener( () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread)); // Wire up a change listener so that ITimeDetectorListeners can be notified when the // detector state changes for any reason. mTimeDetectorStrategy.addChangeListener( () -> mHandler.post(this::handleChangeOnHandlerThread)); } @Override Loading @@ -151,10 +148,8 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub final long token = mCallerIdentityInjector.clearCallingIdentity(); try { ConfigurationInternal configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId); final boolean bypassUserPolicyCheck = false; return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyCheck); final boolean bypassUserPolicyChecks = false; return mTimeDetectorStrategy.getCapabilitiesAndConfig(userId, bypassUserPolicyChecks); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } Loading @@ -180,9 +175,9 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub final long token = mCallerIdentityInjector.clearCallingIdentity(); try { final boolean bypassUserPolicyCheck = false; return mServiceConfigAccessor.updateConfiguration( resolvedUserId, configuration, bypassUserPolicyCheck); final boolean bypassUserPolicyChecks = false; return mTimeDetectorStrategy.updateConfiguration( resolvedUserId, configuration, bypassUserPolicyChecks); } finally { mCallerIdentityInjector.restoreCallingIdentity(token); } Loading Loading @@ -262,7 +257,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub } } private void handleConfigurationInternalChangedOnHandlerThread() { private void handleChangeOnHandlerThread() { // 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. Loading
services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +44 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; import android.app.time.TimeState; import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; Loading Loading @@ -87,6 +89,48 @@ public interface TimeDetectorStrategy extends Dumpable { */ boolean confirmTime(@NonNull UnixEpochTime confirmationTime); /** * Adds a listener that will be triggered when something changes that could affect the result * of the {@link #getCapabilitiesAndConfig} call for the <em>current user only</em>. This * includes the current user changing. This is exposed so that (indirect) users like SettingsUI * can monitor for changes to data derived from {@link TimeCapabilitiesAndConfig} and update * the UI accordingly. */ void addChangeListener(@NonNull StateChangeListener listener); /** * Returns a {@link TimeCapabilitiesAndConfig} object for the specified user. * * <p>The strategy is dependent on device state like current user, settings and device config. * These updates are usually handled asynchronously, so callers should expect some delay between * a change being made directly to services like settings and the strategy becoming aware of * them. Changes made via {@link #updateConfiguration} will be visible immediately. * * @param userId the user ID to retrieve the information for * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device * policy restrictions that should apply to actual users can be ignored */ TimeCapabilitiesAndConfig getCapabilitiesAndConfig( @UserIdInt int userId, boolean bypassUserPolicyChecks); /** * Updates the configuration properties that control a device's time behavior. * * <p>This method returns {@code true} if the configuration was changed, {@code false} * otherwise. * * <p>See {@link #getCapabilitiesAndConfig} for guarantees about visibility of updates to * subsequent calls. * * @param userId the current user ID, supplied to make sure that the asynchronous process * that happens when users switch is completed when the call is made * @param configuration the configuration changes * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device * policy restrictions that should apply to actual users can be ignored */ boolean updateConfiguration(@UserIdInt int userId, @NonNull TimeConfiguration configuration, boolean bypassUserPolicyChecks); /** Processes the suggested time from telephony sources. */ void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion suggestion); Loading
services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +126 −49 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.annotation.UserIdInt; import android.app.time.ExternalTimeSuggestion; import android.app.time.TimeCapabilities; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; import android.app.time.TimeState; import android.app.time.UnixEpochTime; import android.app.timedetector.ManualTimeSuggestion; Loading @@ -48,10 +49,10 @@ import com.android.server.timezonedetector.ArrayMapWithHistory; import com.android.server.timezonedetector.ReferenceWithHistory; import com.android.server.timezonedetector.StateChangeListener; import java.io.PrintWriter; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; /** Loading Loading @@ -94,6 +95,12 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { @NonNull private final Environment mEnvironment; @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @GuardedBy("this") @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>(); @GuardedBy("this") @NonNull private ConfigurationInternal mCurrentConfigurationInternal; Loading Loading @@ -139,16 +146,6 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { */ public interface Environment { /** * Sets a {@link StateChangeListener} that will be invoked when there are any changes that * could affect the content of {@link ConfigurationInternal}. * This is invoked during system server setup. */ void setConfigurationInternalChangeListener(@NonNull StateChangeListener listener); /** Returns the {@link ConfigurationInternal} for the current user. */ @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */ void acquireWakeLock(); Loading @@ -174,16 +171,15 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { /** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */ void releaseWakeLock(); /** * Adds a standalone entry to the time debug log. */ void addDebugLogEntry(@NonNull String logMsg); /** * Dumps the time debug log to the supplied {@link PrintWriter}. * Dumps the time debug log to the supplied {@link IndentingPrintWriter}. */ void dumpDebugLog(PrintWriter printWriter); void dumpDebugLog(IndentingPrintWriter ipw); /** * Requests that the supplied runnable is invoked asynchronously. Loading @@ -195,19 +191,23 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { @NonNull Context context, @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) { TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context, handler, serviceConfigAccessor); return new TimeDetectorStrategyImpl(environment); TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context, handler); return new TimeDetectorStrategyImpl(environment, serviceConfigAccessor); } @VisibleForTesting TimeDetectorStrategyImpl(@NonNull Environment environment) { TimeDetectorStrategyImpl(@NonNull Environment environment, @NonNull ServiceConfigAccessor serviceConfigAccessor) { mEnvironment = Objects.requireNonNull(environment); mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); synchronized (this) { mEnvironment.setConfigurationInternalChangeListener( this::handleConfigurationInternalChanged); mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal(); // Listen for config and user changes and get an initial snapshot of configuration. StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged; mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener); // Initialize mCurrentConfigurationInternal with a starting value. updateCurrentConfigurationInternalIfRequired("TimeDetectorStrategyImpl:"); } } Loading Loading @@ -421,6 +421,57 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } } @GuardedBy("this") private void notifyStateChangeListenersAsynchronously() { for (StateChangeListener listener : mStateChangeListeners) { // This is queuing asynchronous notification, so no need to surrender the "this" lock. mEnvironment.runAsync(listener::onChange); } } @Override public synchronized void addChangeListener(@NonNull StateChangeListener listener) { mStateChangeListeners.add(listener); } @Override public synchronized TimeCapabilitiesAndConfig getCapabilitiesAndConfig(@UserIdInt int userId, boolean bypassUserPolicyChecks) { ConfigurationInternal configurationInternal; if (mCurrentConfigurationInternal.getUserId() == userId) { // Use the cached snapshot we have. configurationInternal = mCurrentConfigurationInternal; } else { // This is not a common case: It would be unusual to want the configuration for a user // other than the "current" user, but it is supported because it is trivial to do so. // Unlike the current user config, there's no cached copy to worry about so read it // directly from mServiceConfigAccessor. configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId); } return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks); } @Override public synchronized boolean updateConfiguration(@UserIdInt int userId, @NonNull TimeConfiguration configuration, boolean bypassUserPolicyChecks) { // Write-through boolean updateSuccessful = mServiceConfigAccessor.updateConfiguration( userId, configuration, bypassUserPolicyChecks); // The update above will trigger config update listeners asynchronously if they are needed, // but that could mean an immediate call to getCapabilitiesAndConfig() for the current user // wouldn't see the update. So, handle the cache update and notifications here. When the // async update listener triggers it will find everything already up to date and do nothing. if (updateSuccessful) { String logMsg = "updateConfiguration:" + " userId=" + userId + ", configuration=" + configuration + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks; updateCurrentConfigurationInternalIfRequired(logMsg); } return updateSuccessful; } @Override public synchronized void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion suggestion) { // Empty time suggestion means that telephony network connectivity has been lost. Loading Loading @@ -448,28 +499,51 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { doAutoTimeDetection(reason); } private synchronized void handleConfigurationInternalChanged() { ConfigurationInternal currentUserConfig = mEnvironment.getCurrentUserConfigurationInternal(); String logMsg = "handleConfigurationInternalChanged:" + " oldConfiguration=" + mCurrentConfigurationInternal + ", newConfiguration=" + currentUserConfig; /** * Handles a configuration change notification. */ private synchronized void handleConfigurationInternalMaybeChanged() { String logMsg = "handleConfigurationInternalMaybeChanged:"; updateCurrentConfigurationInternalIfRequired(logMsg); } @GuardedBy("this") private void updateCurrentConfigurationInternalIfRequired(@NonNull String logMsg) { ConfigurationInternal newCurrentConfigurationInternal = mServiceConfigAccessor.getCurrentUserConfigurationInternal(); // mCurrentConfigurationInternal is null the first time this method is called. ConfigurationInternal oldCurrentConfigurationInternal = mCurrentConfigurationInternal; // If the configuration actually changed, update the cached copy synchronously and do // other necessary house-keeping / (async) listener notifications. if (!newCurrentConfigurationInternal.equals(oldCurrentConfigurationInternal)) { mCurrentConfigurationInternal = newCurrentConfigurationInternal; logMsg = new StringBuilder(logMsg) .append(" [oldConfiguration=").append(oldCurrentConfigurationInternal) .append(", newConfiguration=").append(newCurrentConfigurationInternal) .append("]") .toString(); addDebugLogEntry(logMsg); mCurrentConfigurationInternal = currentUserConfig; // The configuration and maybe the status changed so notify listeners. notifyStateChangeListenersAsynchronously(); boolean autoDetectionEnabled = mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior(); // When automatic time detection is enabled we update the system clock instantly if we can. // Conversely, when automatic time detection is disabled we leave the clock as it is. // When automatic time detection is enabled we update the system clock instantly if we // can. Conversely, when automatic time detection is disabled we leave the clock as it // is. if (autoDetectionEnabled) { String reason = "Auto time zone detection config changed."; doAutoTimeDetection(reason); } else { // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what // it should be in future. // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict // what it should be in future. mLastAutoSystemClockTimeSet = null; } } } private void addDebugLogEntry(@NonNull String logMsg) { if (DBG) { Loading @@ -489,13 +563,11 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { ipw.println("[Capabilities=" + mCurrentConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks) + "]"); long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis(); ipw.printf("mEnvironment.elapsedRealtimeMillis()=%s (%s)\n", Duration.ofMillis(elapsedRealtimeMillis), elapsedRealtimeMillis); long systemClockMillis = mEnvironment.systemClockMillis(); ipw.printf("mEnvironment.systemClockMillis()=%s (%s)\n", Instant.ofEpochMilli(systemClockMillis), systemClockMillis); ipw.println("mEnvironment.systemClockConfidence()=" + mEnvironment.systemClockConfidence()); ipw.println("mEnvironment:"); ipw.increaseIndent(); mEnvironment.dumpDebugLog(ipw); ipw.decreaseIndent(); ipw.println("Time change log:"); ipw.increaseIndent(); // level 2 Loading Loading @@ -525,6 +597,11 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { ipw.decreaseIndent(); // level 1 } @VisibleForTesting public synchronized ConfigurationInternal getCachedCapabilitiesAndConfigForTests() { return mCurrentConfigurationInternal; } @GuardedBy("this") private boolean storeTelephonySuggestion(@NonNull TelephonyTimeSuggestion suggestion) { UnixEpochTime newUnixEpochTime = suggestion.getUnixEpochTime(); Loading