Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d748d91a authored by Neil Fuller's avatar Neil Fuller
Browse files

Time service / strategy config refactoring

Change the TimeDetectorService / TimeDetectorStrategy relationship so
that the service goes via the strategy to access config. The strategy
also interacts with config via the ServiceConfigAccessor rather than
hiding it behind the Environment interface. This matches the
implementation in the equivalent time zone detector code.

This refactoring revealed a bug the equals() method of
ConfigurationInternal, which has been fixed.

Test: atest services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java
Test: atest services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
Test: Treehugger
Bug: 262280071
Change-Id: I4f95ca03178baa5a5fe647f6fef40a6f1394a6cf
parent 2668168a
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -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)
@@ -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;
+17 −21
Original line number Diff line number Diff line
@@ -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;

/**
@@ -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(
@@ -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()) {
@@ -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
+12 −17
Original line number Diff line number Diff line
@@ -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.
@@ -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;

@@ -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
@@ -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);
        }
@@ -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);
        }
@@ -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.
+44 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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);

+126 −49
Original line number Diff line number Diff line
@@ -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;
@@ -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;

/**
@@ -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;
@@ -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();

@@ -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.
@@ -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:");
        }
    }

@@ -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.
@@ -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) {
@@ -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
@@ -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