Merge "Add the ability to override time origin priorities" into sc-dev

This commit is contained in:
Neil Fuller
2021-04-19 11:19:43 +00:00
committed by Android (Google) Code Review
8 changed files with 248 additions and 30 deletions

View File

@@ -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}.
*

View File

@@ -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. */

View File

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

View File

@@ -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;
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<String[]> priorityStrings = mServerFlags.getOptionalStringArray(
KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE);
return priorityStrings.orElse(null);
}
}
}

View File

@@ -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,

View File

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

View File

@@ -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;

View File

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