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