diff options
8 files changed, 247 insertions, 29 deletions
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index 52016b65688b..a3562301cde7 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -32,6 +32,18 @@ import android.os.TimestampedValue; 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}. * * @hide diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java index 072cc16f680b..7649958fe6c9 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 d91e9c258548..fda0e3cd947c 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<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>(); @@ -209,6 +218,19 @@ public final class ServerFlags { } /** + * 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. */ diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java index be4432ab61bf..381b77e5d1d2 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<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(); + } + + /** 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); + } - return origins; + @Override + @Nullable + protected String[] lookupPriorityStrings() { + Optional<String[]> 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 14cab382d405..0f14af4b296b 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 000000000000..3cb21aaf85ce --- /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 be382f0409b1..ff5060e6618e 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 c20400ae7a4b..457dc4325e62 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); - } } |