From d13937900cce9f9a5f90981d1ca81814346736e8 Mon Sep 17 00:00:00 2001 From: Neil Fuller Date: Fri, 16 Apr 2021 13:21:27 +0100 Subject: [PATCH] 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 --- .../app/timedetector/TimeDetector.java | 12 ++ .../server/timedetector/EnvironmentImpl.java | 3 + .../server/timedetector/ServerFlags.java | 22 ++++ .../timedetector/ServiceConfigAccessor.java | 121 +++++++++++++++--- .../timedetector/TimeDetectorService.java | 12 +- .../TimeDetectorShellCommand.java | 82 ++++++++++++ .../timedetector/TimeDetectorStrategy.java | 11 +- .../TimeZoneDetectorService.java | 15 +-- 8 files changed, 248 insertions(+), 30 deletions(-) create mode 100644 services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index 52016b65688b0..a3562301cde72 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -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}. * diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java index 072cc16f680b9..7649958fe6c99 100644 --- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java +++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java @@ -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. */ diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java index d91e9c2585481..fda0e3cd947c6 100644 --- a/services/core/java/com/android/server/timedetector/ServerFlags.java +++ b/services/core/java/com/android/server/timedetector/ServerFlags.java @@ -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> 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 getOptionalStringArray(@DeviceConfigKey String key) { + Optional 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. diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java index be4432ab61bfc..381b77e5d1d2e 100644 --- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java +++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java @@ -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 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; - return origins; + /** 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(); + } + + /** 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 priorityStrings = mServerFlags.getOptionalStringArray( + KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE); + return priorityStrings.orElse(null); } } } diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 14cab382d4055..0f14af4b296b4 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -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, diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java new file mode 100644 index 0000000000000..3cb21aaf85ce7 --- /dev/null +++ b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.timedetector; + +import static android.app.timedetector.TimeDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED; +import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; + +import static com.android.server.timedetector.ServerFlags.KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE; + +import android.os.ShellCommand; + +import java.io.PrintWriter; + +/** Implements the shell command interface for {@link TimeDetectorService}. */ +class TimeDetectorShellCommand extends ShellCommand { + + private final TimeDetectorService mInterface; + + TimeDetectorShellCommand(TimeDetectorService timeDetectorService) { + mInterface = timeDetectorService; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + + switch (cmd) { + case SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED: + return runIsAutoDetectionEnabled(); + default: { + return handleDefaultCommands(cmd); + } + } + } + + private int runIsAutoDetectionEnabled() { + final PrintWriter pw = getOutPrintWriter(); + boolean enabled = mInterface.getCapabilitiesAndConfig() + .getTimeConfiguration() + .isAutoDetectionEnabled(); + pw.println(enabled); + return 0; + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("Time Detector (time_detector) commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.printf(" %s\n", SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED); + pw.println(" Prints true/false according to the automatic time detection setting"); + pw.println(); + pw.printf("This service is also affected by the following device_config flags in the" + + " %s namespace:\n", NAMESPACE_SYSTEM_TIME); + pw.printf(" %s - [default=null], a comma separated list of origins. See" + + " TimeDetectorStrategy for details\n", + KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE); + pw.println(); + pw.println("Example:"); + pw.printf(" $ adb shell cmd device_config put %s %s %s\n", + NAMESPACE_SYSTEM_TIME, KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE, + "external"); + pw.println("See adb shell cmd device_config for more information."); + pw.println(); + } +} diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index be382f0409b1b..ff5060e6618e8 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -27,10 +27,13 @@ import android.app.timedetector.TelephonyTimeSuggestion; import android.os.TimestampedValue; import android.util.IndentingPrintWriter; +import com.android.internal.util.Preconditions; import com.android.server.timezonedetector.Dumpable; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** * The interface for the class that implements the time detection algorithm used by the @@ -44,9 +47,9 @@ import java.lang.annotation.RetentionPolicy; */ public interface TimeDetectorStrategy extends Dumpable { - @IntDef({ ORIGIN_TELEPHONY, ORIGIN_MANUAL, ORIGIN_NETWORK, ORIGIN_GNSS, - ORIGIN_EXTERNAL }) + @IntDef({ ORIGIN_TELEPHONY, ORIGIN_MANUAL, ORIGIN_NETWORK, ORIGIN_GNSS, ORIGIN_EXTERNAL }) @Retention(RetentionPolicy.SOURCE) + @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) @interface Origin {} /** Used when a time value originated from a telephony signal. */ @@ -126,9 +129,11 @@ public interface TimeDetectorStrategy extends Dumpable { /** * Converts a human readable config string to one of the {@code ORIGIN_} constants. - * Throws an {@link IllegalArgumentException} if the value is unrecognized. + * Throws an {@link IllegalArgumentException} if the value is unrecognized or {@code null}. */ static @Origin int stringToOrigin(String originString) { + Preconditions.checkArgument(originString != null); + switch (originString) { case "manual": return ORIGIN_MANUAL; diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index c20400ae7a4b9..457dc4325e62e 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -321,6 +321,13 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub ipw.flush(); } + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + new TimeZoneDetectorShellCommand(this).exec( + this, in, out, err, args, callback, resultReceiver); + } + private void enforceManageTimeZoneDetectorPermission() { mContext.enforceCallingPermission( android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION, @@ -346,13 +353,5 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE, "suggest manual time and time zone"); } - - @Override - public void onShellCommand(FileDescriptor in, FileDescriptor out, - FileDescriptor err, String[] args, ShellCallback callback, - ResultReceiver resultReceiver) { - new TimeZoneDetectorShellCommand(this).exec( - this, in, out, err, args, callback, resultReceiver); - } }