1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.timedetector; 17 18 import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; 19 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.StringDef; 23 import android.content.Context; 24 import android.provider.DeviceConfig; 25 import android.util.ArrayMap; 26 27 import com.android.internal.annotations.GuardedBy; 28 import com.android.server.timezonedetector.ServiceConfigAccessor; 29 import com.android.server.timezonedetector.StateChangeListener; 30 31 import java.lang.annotation.ElementType; 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.lang.annotation.Target; 35 import java.time.DateTimeException; 36 import java.time.Duration; 37 import java.time.Instant; 38 import java.util.ArrayList; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Objects; 43 import java.util.Optional; 44 import java.util.Set; 45 46 /** 47 * A helper class for reading / monitoring the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace 48 * for server-configured flags. 49 */ 50 public final class ServerFlags { 51 52 private static final Optional<Boolean> OPTIONAL_TRUE = Optional.of(true); 53 private static final Optional<Boolean> OPTIONAL_FALSE = Optional.of(false); 54 55 /** 56 * An annotation used to indicate when a {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} key is 57 * required. 58 */ 59 @StringDef(prefix = "KEY_", value = { 60 KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, 61 KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED, 62 KEY_PRIMARY_LTZP_MODE_OVERRIDE, 63 KEY_SECONDARY_LTZP_MODE_OVERRIDE, 64 KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, 65 KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS, 66 KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS, 67 KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, 68 KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE, 69 KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, 70 KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE, 71 KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE, 72 KEY_TIME_ZONE_DETECTOR_AUTO_DETECTION_ENABLED_DEFAULT, 73 KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED, 74 KEY_ENHANCED_METRICS_COLLECTION_ENABLED, 75 }) 76 @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) 77 @Retention(RetentionPolicy.SOURCE) 78 @interface DeviceConfigKey {} 79 80 /** 81 * Controls whether the location time zone manager service will be started. Only observed if 82 * the device build is configured to support location-based time zone detection. See 83 * {@link ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupportedInConfig()} and {@link 84 * ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupported()}. 85 */ 86 public static final @DeviceConfigKey String KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED = 87 "location_time_zone_detection_feature_supported"; 88 89 /** 90 * Controls whether location time zone detection should run all the time on supported devices, 91 * even when the user has not enabled it explicitly in settings. Enabled for internal testing 92 * only. 93 */ 94 public static final @DeviceConfigKey String 95 KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED = 96 "location_time_zone_detection_run_in_background_enabled"; 97 98 /** 99 * The key for the server flag that can override the device config for whether the primary 100 * location time zone provider is enabled, disabled, or (for testing) in simulation mode. 101 */ 102 public static final @DeviceConfigKey String KEY_PRIMARY_LTZP_MODE_OVERRIDE = 103 "primary_location_time_zone_provider_mode_override"; 104 105 /** 106 * The key for the server flag that can override the device config for whether the secondary 107 * location time zone provider is enabled or disabled, or (for testing) in simulation mode. 108 */ 109 public static final @DeviceConfigKey String KEY_SECONDARY_LTZP_MODE_OVERRIDE = 110 "secondary_location_time_zone_provider_mode_override"; 111 112 /** 113 * The key for the minimum delay after location time zone detection has been enabled before the 114 * location time zone manager can report it is uncertain about the time zone. 115 */ 116 public static final @DeviceConfigKey String 117 KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS = 118 "location_time_zone_detection_uncertainty_delay_millis"; 119 120 /** 121 * The key for the timeout passed to a location time zone provider that tells it how long it has 122 * to provide an explicit first suggestion without being declared uncertain. 123 */ 124 public static final @DeviceConfigKey String KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS = 125 "ltzp_init_timeout_millis"; 126 127 /** 128 * The key for the extra time added to {@link 129 * #KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone 130 * manager before the location time zone provider will actually be declared uncertain. 131 */ 132 public static final @DeviceConfigKey String KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS = 133 "ltzp_init_timeout_fuzz_millis"; 134 135 /** The key for the setting that controls rate limiting of provider events. */ 136 public static final @DeviceConfigKey String KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS = 137 "ltzp_event_filtering_age_threshold_millis"; 138 139 /** 140 * The key for the server flag that can override location time zone detection being enabled for 141 * a user. Only intended for use during release testing with droidfooders. The user can still 142 * disable the feature by turning off the master location switch, or by disabling automatic time 143 * zone detection. 144 */ 145 public static final @DeviceConfigKey String 146 KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE = 147 "location_time_zone_detection_setting_enabled_override"; 148 149 /** 150 * The key for the default value used to determine whether location time zone detection is 151 * enabled when the user hasn't explicitly set it yet. 152 */ 153 public static final @DeviceConfigKey String 154 KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT = 155 "location_time_zone_detection_setting_enabled_default"; 156 157 /** 158 * The key to alter a device's "automatic time zone detection enabled" setting default value. 159 * This flag is only intended for internal testing. 160 */ 161 public static final @DeviceConfigKey String 162 KEY_TIME_ZONE_DETECTOR_AUTO_DETECTION_ENABLED_DEFAULT = 163 "time_zone_detector_auto_detection_enabled_default"; 164 165 /** 166 * The key to control support for time zone detection falling back to telephony detection under 167 * certain circumstances. 168 */ 169 public static final @DeviceConfigKey String 170 KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED = 171 "time_zone_detector_telephony_fallback_supported"; 172 173 /** 174 * The key to override the time detector origin priorities configuration. A comma-separated list 175 * of strings that will be passed to {@link TimeDetectorStrategy#stringToOrigin(String)}. 176 * All values must be recognized or the override value will be ignored. 177 */ 178 public static final @DeviceConfigKey String KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE = 179 "time_detector_origin_priorities_override"; 180 181 /** 182 * The key to override the time detector lower bound configuration. The value is the number of 183 * milliseconds since the beginning of the Unix epoch. 184 */ 185 public static final @DeviceConfigKey String KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE = 186 "time_detector_lower_bound_millis_override"; 187 188 /** 189 * The key to allow extra metrics / telemetry information to be collected from internal testers. 190 */ 191 public static final @DeviceConfigKey String KEY_ENHANCED_METRICS_COLLECTION_ENABLED = 192 "enhanced_metrics_collection_enabled"; 193 194 /** 195 * The registered listeners and the keys to trigger on. The value is explicitly a HashSet to 196 * ensure O(1) lookup performance when working out whether a listener should trigger. 197 */ 198 @GuardedBy("mListeners") 199 private final ArrayMap<StateChangeListener, HashSet<String>> mListeners = new ArrayMap<>(); 200 201 private static final Object SLOCK = new Object(); 202 203 @GuardedBy("SLOCK") 204 @Nullable 205 private static ServerFlags sInstance; 206 ServerFlags(Context context)207 private ServerFlags(Context context) { 208 DeviceConfig.addOnPropertiesChangedListener( 209 NAMESPACE_SYSTEM_TIME, 210 context.getMainExecutor(), 211 this::handlePropertiesChanged); 212 } 213 214 /** Returns the singleton instance. */ getInstance(Context context)215 public static ServerFlags getInstance(Context context) { 216 synchronized (SLOCK) { 217 if (sInstance == null) { 218 sInstance = new ServerFlags(context); 219 } 220 return sInstance; 221 } 222 } 223 handlePropertiesChanged(@onNull DeviceConfig.Properties properties)224 private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) { 225 // Copy the listeners to notify under the "mListeners" lock but don't hold the lock while 226 // delivering the notifications to avoid deadlocks. 227 List<StateChangeListener> listenersToNotify; 228 synchronized (mListeners) { 229 listenersToNotify = new ArrayList<>(mListeners.size()); 230 for (Map.Entry<StateChangeListener, HashSet<String>> listenerEntry 231 : mListeners.entrySet()) { 232 // It's unclear which set of the following two Sets is going to be larger in the 233 // average case: monitoredKeys will be a subset of the set of possible keys, but 234 // only changed keys are reported. Because we guarantee the type / lookup behavior 235 // of the monitoredKeys by making that a HashSet, that is used as the haystack Set, 236 // while the changed keys is treated as the needles Iterable. At the time of 237 // writing, properties.getKeyset() actually returns a HashSet, so iteration isn't 238 // super efficient and the use of HashSet for monitoredKeys may be redundant, but 239 // neither set will be enormous. 240 HashSet<String> monitoredKeys = listenerEntry.getValue(); 241 Iterable<String> modifiedKeys = properties.getKeyset(); 242 if (containsAny(monitoredKeys, modifiedKeys)) { 243 listenersToNotify.add(listenerEntry.getKey()); 244 } 245 } 246 } 247 248 for (StateChangeListener listener : listenersToNotify) { 249 listener.onChange(); 250 } 251 } 252 containsAny( @onNull Set<String> haystack, @NonNull Iterable<String> needles)253 private static boolean containsAny( 254 @NonNull Set<String> haystack, @NonNull Iterable<String> needles) { 255 for (String needle : needles) { 256 if (haystack.contains(needle)) { 257 return true; 258 } 259 } 260 return false; 261 } 262 263 /** 264 * Adds a listener for the system_time namespace that will trigger if any of the specified keys 265 * change. Listener callbacks are delivered on the main looper thread. 266 * 267 * <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no 268 * associated remove method. 269 */ addListener(@onNull StateChangeListener listener, @NonNull Set<String> keys)270 public void addListener(@NonNull StateChangeListener listener, 271 @NonNull Set<String> keys) { 272 Objects.requireNonNull(listener); 273 Objects.requireNonNull(keys); 274 275 // Make a defensive copy and use a well-defined Set implementation to provide predictable 276 // performance on the lookup. 277 HashSet<String> keysCopy = new HashSet<>(keys); 278 synchronized (mListeners) { 279 mListeners.put(listener, keysCopy); 280 } 281 } 282 283 /** 284 * Returns an optional string value from {@link DeviceConfig} from the system_time 285 * namespace, returns {@link Optional#empty()} if there is no explicit value set. 286 */ 287 @NonNull getOptionalString(@eviceConfigKey String key)288 public Optional<String> getOptionalString(@DeviceConfigKey String key) { 289 String value = DeviceConfig.getProperty(NAMESPACE_SYSTEM_TIME, key); 290 return Optional.ofNullable(value); 291 } 292 293 /** 294 * Returns an optional string array value from {@link DeviceConfig} from the system_time 295 * namespace, returns {@link Optional#empty()} if there is no explicit value set. 296 */ 297 @NonNull getOptionalStringArray(@eviceConfigKey String key)298 public Optional<String[]> getOptionalStringArray(@DeviceConfigKey String key) { 299 Optional<String> optionalString = getOptionalString(key); 300 if (!optionalString.isPresent()) { 301 return Optional.empty(); 302 } 303 304 // DeviceConfig appears to have no way to specify an empty string, so we use "_[]_" as a 305 // special value to mean a zero-length array. 306 String value = optionalString.get(); 307 if ("_[]_".equals(value)) { 308 return Optional.of(new String[0]); 309 } 310 311 return Optional.of(value.split(",")); 312 } 313 314 /** 315 * Returns an {@link Instant} from {@link DeviceConfig} from the system_time 316 * namespace, returns {@link Optional#empty()} if there is no explicit value set. 317 */ 318 @NonNull getOptionalInstant(@eviceConfigKey String key)319 public Optional<Instant> getOptionalInstant(@DeviceConfigKey String key) { 320 String value = DeviceConfig.getProperty(NAMESPACE_SYSTEM_TIME, key); 321 if (value == null) { 322 return Optional.empty(); 323 } 324 325 try { 326 long millis = Long.parseLong(value); 327 return Optional.of(Instant.ofEpochMilli(millis)); 328 } catch (DateTimeException | NumberFormatException e) { 329 return Optional.empty(); 330 } 331 } 332 333 /** 334 * Returns an optional boolean value from {@link DeviceConfig} from the system_time 335 * namespace, returns {@link Optional#empty()} if there is no explicit value set. 336 */ 337 @NonNull getOptionalBoolean(@eviceConfigKey String key)338 public Optional<Boolean> getOptionalBoolean(@DeviceConfigKey String key) { 339 String value = DeviceConfig.getProperty(NAMESPACE_SYSTEM_TIME, key); 340 return parseOptionalBoolean(value); 341 } 342 343 @NonNull parseOptionalBoolean(@ullable String value)344 private static Optional<Boolean> parseOptionalBoolean(@Nullable String value) { 345 if (value == null) { 346 return Optional.empty(); 347 } else { 348 return Boolean.parseBoolean(value) ? OPTIONAL_TRUE : OPTIONAL_FALSE; 349 } 350 } 351 352 /** 353 * Returns a boolean value from {@link DeviceConfig} from the system_time namespace, or 354 * {@code defaultValue} if there is no explicit value set. 355 */ getBoolean(@eviceConfigKey String key, boolean defaultValue)356 public boolean getBoolean(@DeviceConfigKey String key, boolean defaultValue) { 357 return DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue); 358 } 359 360 /** 361 * Returns a positive duration from {@link DeviceConfig} from the system_time namespace, 362 * or {@code defaultValue} if there is no explicit value set or if the value is not a number or 363 * is negative. 364 */ 365 @Nullable getDurationFromMillis( @eviceConfigKey String key, @Nullable Duration defaultValue)366 public Duration getDurationFromMillis( 367 @DeviceConfigKey String key, @Nullable Duration defaultValue) { 368 long deviceConfigValue = DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1); 369 if (deviceConfigValue < 0) { 370 return defaultValue; 371 } 372 return Duration.ofMillis(deviceConfigValue); 373 } 374 } 375