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

Commit 0f9db7e4 authored by Neil Fuller's avatar Neil Fuller Committed by Android (Google) Code Review
Browse files

Merge "Time service / strategy config refactoring" into udc-qpr-dev

parents 0ea52b6b a9e51700
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