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

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

Add the ability to override time origin priorities

Add the ability to override time detector "origin priorities" config
with device_config. This is primarily intended for use by tests to set
the device into known states, regardless of the static xml OEM partner
config.

For example, to test the new "external" suggestion code in CTS, even a
device that does not support it "out of the box" can be set to just use
external suggestions and verify the associated code paths are working.
Previously, external time suggestions were untestable on devices that
didn't use it "out of the box".

Similarly, carrier acceptance tests could use this to just set "nitz"
from the priority list and ensure that NITZ behavior is working as
expected.

An initial TimeDetectorShellCommand implementation has also been written
to provide help that lists the device_config key, and expose settings
state. The set_auto_detection_enabled command can be added after bug
172891783 is complete.

This change also makes the origin priorities more dynamic. i.e. config
overlays like locale or MCC-based resource configs could now also affect
the origin priorities, previously the string was read once and cached
forever.

Bug: 183239968
Bug: 172891783
Bug: 172230856
Test: build / treehugger only
Change-Id: Ic67f35aa6d417bbbb4172643c10a796d484ccd1f
parent 650a27bf
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -31,6 +31,18 @@ import android.os.TimestampedValue;
@SystemService(Context.TIME_DETECTOR_SERVICE)
public interface TimeDetector {

    /**
     * The name of the service for shell commands.
     * @hide
     */
    String SHELL_COMMAND_SERVICE_NAME = "time_detector";

    /**
     * A shell command that prints the current "auto time detection" global setting value.
     * @hide
     */
    String SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED = "is_auto_detection_enabled";

    /**
     * A shared utility method to create a {@link ManualTimeSuggestion}.
     *
+3 −0
Original line number Diff line number Diff line
@@ -82,6 +82,9 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment {
                        handleAutoTimeDetectionChangedOnHandlerThread();
                    }
                });
        mServiceConfigAccessor.addListener(
                () -> mHandler.post(
                        EnvironmentImpl.this::handleAutoTimeDetectionChangedOnHandlerThread));
    }

    /** Internal method for handling the auto time setting being changed. */
+22 −0
Original line number Diff line number Diff line
@@ -135,6 +135,15 @@ public final class ServerFlags {
    public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT =
            "location_time_zone_detection_setting_enabled_default";

    /**
     * The key to override the time detector origin priorities configuration. A comma-separated list
     * of strings that will be passed to {@link TimeDetectorStrategy#stringToOrigin(String)}.
     * All values must be recognized or the override value will be ignored.
     */
    @DeviceConfigKey
    public static final String KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE =
            "time_detector_origin_priorities_override";

    @GuardedBy("mListeners")
    private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>();

@@ -208,6 +217,19 @@ public final class ServerFlags {
        return Optional.ofNullable(value);
    }

    /**
     * Returns an optional string array value from {@link DeviceConfig} from the system_time
     * namespace, returns {@link Optional#empty()} if there is no explicit value set.
     */
    @NonNull
    public Optional<String[]> getOptionalStringArray(@DeviceConfigKey String key) {
        Optional<String> string = getOptionalString(key);
        if (!string.isPresent()) {
            return Optional.empty();
        }
        return Optional.of(string.get().split(","));
    }

    /**
     * Returns an optional boolean value from {@link DeviceConfig} from the system_time
     * namespace, returns {@link Optional#empty()} if there is no explicit value set.
+102 −17
Original line number Diff line number Diff line
@@ -15,9 +15,9 @@
 */
package com.android.server.timedetector;

import static com.android.server.timedetector.ServerFlags.KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_TELEPHONY;
import static com.android.server.timedetector.TimeDetectorStrategy.stringToOrigin;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,12 +28,17 @@ import android.util.ArraySet;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.server.timedetector.TimeDetectorStrategy.Origin;
import com.android.server.timezonedetector.ConfigurationChangeListener;

import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;

/**
 * A singleton that provides access to service configuration for time detection. This hides how
@@ -48,7 +53,7 @@ final class ServiceConfigAccessor {
     * By default telephony and network only suggestions are accepted and telephony takes
     * precedence over network.
     */
    private static final @TimeDetectorStrategy.Origin int[]
    private static final @Origin int[]
            DEFAULT_AUTOMATIC_TIME_ORIGIN_PRIORITIES = { ORIGIN_TELEPHONY, ORIGIN_NETWORK };

    /**
@@ -60,6 +65,7 @@ final class ServiceConfigAccessor {

    private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Collections.unmodifiableSet(
            new ArraySet<>(new String[] {
                    KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE,
            }));

    private static final Object SLOCK = new Object();
@@ -70,8 +76,9 @@ final class ServiceConfigAccessor {
    private static ServiceConfigAccessor sInstance;

    @NonNull private final Context mContext;
    @NonNull private final ConfigOriginPrioritiesSupplier mConfigOriginPrioritiesSupplier;
    @NonNull private final ServerFlagsOriginPrioritiesSupplier mServerFlagsOriginPrioritiesSupplier;
    @NonNull private final ServerFlags mServerFlags;
    @NonNull private final int[] mOriginPriorities;

    /**
     * If a newly calculated system clock time and the current system clock time differs by this or
@@ -83,7 +90,9 @@ final class ServiceConfigAccessor {
    private ServiceConfigAccessor(@NonNull Context context) {
        mContext = Objects.requireNonNull(context);
        mServerFlags = ServerFlags.getInstance(mContext);
        mOriginPriorities = getOriginPrioritiesInternal();
        mConfigOriginPrioritiesSupplier = new ConfigOriginPrioritiesSupplier(context);
        mServerFlagsOriginPrioritiesSupplier =
                new ServerFlagsOriginPrioritiesSupplier(mServerFlags);
        mSystemClockUpdateThresholdMillis =
                SystemProperties.getInt("ro.sys.time_detector_update_diff",
                        SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT);
@@ -111,8 +120,17 @@ final class ServiceConfigAccessor {
    }

    @NonNull
    int[] getOriginPriorities() {
        return mOriginPriorities;
    @Origin int[] getOriginPriorities() {
        int[] serverFlagsValue = mServerFlagsOriginPrioritiesSupplier.get();
        if (serverFlagsValue != null) {
            return serverFlagsValue;
        }

        int[] configValue = mConfigOriginPrioritiesSupplier.get();
        if (configValue != null) {
            return configValue;
        }
        return DEFAULT_AUTOMATIC_TIME_ORIGIN_PRIORITIES;
    }

    int systemClockUpdateThresholdMillis() {
@@ -123,19 +141,86 @@ final class ServiceConfigAccessor {
        return TIME_LOWER_BOUND_DEFAULT;
    }

    private int[] getOriginPrioritiesInternal() {
        String[] originStrings =
                mContext.getResources().getStringArray(R.array.config_autoTimeSourcesPriority);
        if (originStrings.length == 0) {
            return DEFAULT_AUTOMATIC_TIME_ORIGIN_PRIORITIES;
        } else {
            int[] origins = new int[originStrings.length];
            for (int i = 0; i < originStrings.length; i++) {
                int origin = stringToOrigin(originStrings[i]);
                origins[i] = origin;
    /**
     * A base supplier of an array of time origin integers in priority order.
     * It handles memoization of the result to avoid repeated string parsing when nothing has
     * changed.
     */
    private abstract static class BaseOriginPrioritiesSupplier implements Supplier<@Origin int[]> {
        @GuardedBy("this") @Nullable private String[] mLastPriorityStrings;
        @GuardedBy("this") @Nullable private int[] mLastPriorityInts;

        /** Returns an array of {@code ORIGIN_*} values, or {@code null}. */
        @Override
        @Nullable
        public @Origin int[] get() {
            String[] priorityStrings = lookupPriorityStrings();
            synchronized (this) {
                if (Arrays.equals(mLastPriorityStrings, priorityStrings)) {
                    return mLastPriorityInts;
                }

                int[] priorityInts = null;
                if (priorityStrings != null && priorityStrings.length > 0) {
                    priorityInts = new int[priorityStrings.length];
                    try {
                        for (int i = 0; i < priorityInts.length; i++) {
                            String priorityString = priorityStrings[i];
                            Preconditions.checkArgument(priorityString != null);

                            priorityString = priorityString.trim();
                            priorityInts[i] = TimeDetectorStrategy.stringToOrigin(priorityString);
                        }
                    } catch (IllegalArgumentException e) {
                        // If any strings were bad and they were ignored then the semantics of the
                        // whole list could change, so return null.
                        priorityInts = null;
                    }
                }
                mLastPriorityStrings = priorityStrings;
                mLastPriorityInts = priorityInts;
                return priorityInts;
            }
        }

        @Nullable
        protected abstract String[] lookupPriorityStrings();
    }

            return origins;
    /** Supplies origin priorities from config_autoTimeSourcesPriority. */
    private static class ConfigOriginPrioritiesSupplier extends BaseOriginPrioritiesSupplier {

        @NonNull private final Context mContext;

        private ConfigOriginPrioritiesSupplier(Context context) {
            mContext = Objects.requireNonNull(context);
        }

        @Override
        @Nullable
        protected String[] lookupPriorityStrings() {
            return mContext.getResources().getStringArray(R.array.config_autoTimeSourcesPriority);
        }
    }

    /**
     * Supplies origin priorities from device_config (server flags), see
     * {@link ServerFlags#KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE}.
     */
    private static class ServerFlagsOriginPrioritiesSupplier extends BaseOriginPrioritiesSupplier {

        @NonNull private final ServerFlags mServerFlags;

        private ServerFlagsOriginPrioritiesSupplier(ServerFlags serverFlags) {
            mServerFlags = Objects.requireNonNull(serverFlags);
        }

        @Override
        @Nullable
        protected String[] lookupPriorityStrings() {
            Optional<String[]> priorityStrings = mServerFlags.getOptionalStringArray(
                    KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE);
            return priorityStrings.orElse(null);
        }
    }
}
+11 −1
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@ import android.app.timedetector.TelephonyTimeSuggestion;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.util.IndentingPrintWriter;

import com.android.internal.annotations.VisibleForTesting;
@@ -100,6 +102,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub {
    }

    @Override
    @NonNull
    public TimeCapabilitiesAndConfig getCapabilitiesAndConfig() {
        int userId = mCallerIdentityInjector.getCallingUserId();
        return getTimeCapabilitiesAndConfig(userId);
@@ -119,7 +122,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub {
    }

    @Override
    public boolean updateConfiguration(TimeConfiguration timeConfiguration) {
    public boolean updateConfiguration(@NonNull TimeConfiguration timeConfiguration) {
        enforceManageTimeDetectorPermission();
        // TODO(b/172891783) Add actual logic
        return false;
@@ -180,6 +183,13 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub {
        ipw.flush();
    }

    @Override
    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
        new TimeDetectorShellCommand(this).exec(
                this, in, out, err, args, callback, resultReceiver);
    }

    private void enforceSuggestTelephonyTimePermission() {
        mContext.enforceCallingPermission(
                android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE,
Loading