• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 
17 package com.android.server.timezonedetector;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.timezonedetector.ManualTimeZoneSuggestion;
23 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
24 
25 import java.lang.annotation.ElementType;
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.lang.annotation.Target;
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.Objects;
32 
33 /**
34  * A class that provides time zone detector state information for metrics.
35  *
36  * <p>
37  * Regarding the use of time zone ID ordinals in metrics / telemetry:
38  * <p>
39  * For general metrics, we don't want to leak user location information by reporting time zone
40  * IDs. Instead, time zone IDs are consistently identified within a given instance of this class by
41  * a numeric ID (ordinal). This allows comparison of IDs without revealing what those IDs are.
42  * See {@link #isEnhancedMetricsCollectionEnabled()} for the setting that enables actual IDs to be
43  * collected.
44  */
45 public final class MetricsTimeZoneDetectorState {
46 
47     @IntDef(prefix = "DETECTION_MODE_",
48             value = { DETECTION_MODE_UNKNOWN, DETECTION_MODE_MANUAL, DETECTION_MODE_GEO,
49                     DETECTION_MODE_TELEPHONY }
50     )
51     @Retention(RetentionPolicy.SOURCE)
52     @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
53     public @interface DetectionMode {};
54 
55     public static final @DetectionMode int DETECTION_MODE_UNKNOWN = 0;
56     public static final @DetectionMode int DETECTION_MODE_MANUAL = 1;
57     public static final @DetectionMode int DETECTION_MODE_GEO = 2;
58     public static final @DetectionMode int DETECTION_MODE_TELEPHONY = 3;
59 
60     @NonNull private final ConfigurationInternal mConfigurationInternal;
61     private final int mDeviceTimeZoneIdOrdinal;
62     @Nullable private final String mDeviceTimeZoneId;
63     @Nullable private final MetricsTimeZoneSuggestion mLatestManualSuggestion;
64     @Nullable private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion;
65     @Nullable private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion;
66 
MetricsTimeZoneDetectorState( @onNull ConfigurationInternal configurationInternal, int deviceTimeZoneIdOrdinal, @Nullable String deviceTimeZoneId, @Nullable MetricsTimeZoneSuggestion latestManualSuggestion, @Nullable MetricsTimeZoneSuggestion latestTelephonySuggestion, @Nullable MetricsTimeZoneSuggestion latestGeolocationSuggestion)67     private MetricsTimeZoneDetectorState(
68             @NonNull ConfigurationInternal configurationInternal,
69             int deviceTimeZoneIdOrdinal,
70             @Nullable String deviceTimeZoneId,
71             @Nullable MetricsTimeZoneSuggestion latestManualSuggestion,
72             @Nullable MetricsTimeZoneSuggestion latestTelephonySuggestion,
73             @Nullable MetricsTimeZoneSuggestion latestGeolocationSuggestion) {
74         mConfigurationInternal = Objects.requireNonNull(configurationInternal);
75         mDeviceTimeZoneIdOrdinal = deviceTimeZoneIdOrdinal;
76         mDeviceTimeZoneId = deviceTimeZoneId;
77         mLatestManualSuggestion = latestManualSuggestion;
78         mLatestTelephonySuggestion = latestTelephonySuggestion;
79         mLatestGeolocationSuggestion = latestGeolocationSuggestion;
80     }
81 
82     /**
83      * Creates {@link MetricsTimeZoneDetectorState} from the supplied parameters, using the {@link
84      * OrdinalGenerator} to generate time zone ID ordinals.
85      */
create( @onNull OrdinalGenerator<String> tzIdOrdinalGenerator, @NonNull ConfigurationInternal configurationInternal, @NonNull String deviceTimeZoneId, @Nullable ManualTimeZoneSuggestion latestManualSuggestion, @Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion, @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion)86     public static MetricsTimeZoneDetectorState create(
87             @NonNull OrdinalGenerator<String> tzIdOrdinalGenerator,
88             @NonNull ConfigurationInternal configurationInternal,
89             @NonNull String deviceTimeZoneId,
90             @Nullable ManualTimeZoneSuggestion latestManualSuggestion,
91             @Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion,
92             @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) {
93 
94         boolean includeZoneIds = configurationInternal.isEnhancedMetricsCollectionEnabled();
95         String metricDeviceTimeZoneId = includeZoneIds ? deviceTimeZoneId : null;
96         int deviceTimeZoneIdOrdinal =
97                 tzIdOrdinalGenerator.ordinal(Objects.requireNonNull(deviceTimeZoneId));
98         MetricsTimeZoneSuggestion latestCanonicalManualSuggestion =
99                 createMetricsTimeZoneSuggestion(
100                         tzIdOrdinalGenerator, latestManualSuggestion, includeZoneIds);
101         MetricsTimeZoneSuggestion latestCanonicalTelephonySuggestion =
102                 createMetricsTimeZoneSuggestion(
103                         tzIdOrdinalGenerator, latestTelephonySuggestion, includeZoneIds);
104         MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion =
105                 createMetricsTimeZoneSuggestion(
106                         tzIdOrdinalGenerator, latestGeolocationSuggestion, includeZoneIds);
107 
108         return new MetricsTimeZoneDetectorState(
109                 configurationInternal, deviceTimeZoneIdOrdinal, metricDeviceTimeZoneId,
110                 latestCanonicalManualSuggestion, latestCanonicalTelephonySuggestion,
111                 latestCanonicalGeolocationSuggestion);
112     }
113 
114     /** Returns true if the device supports telephony time zone detection. */
isTelephonyDetectionSupported()115     public boolean isTelephonyDetectionSupported() {
116         return mConfigurationInternal.isTelephonyDetectionSupported();
117     }
118 
119     /** Returns true if the device supports geolocation time zone detection. */
isGeoDetectionSupported()120     public boolean isGeoDetectionSupported() {
121         return mConfigurationInternal.isGeoDetectionSupported();
122     }
123 
124     /** Returns true if the device supports telephony time zone detection fallback. */
isTelephonyTimeZoneFallbackSupported()125     public boolean isTelephonyTimeZoneFallbackSupported() {
126         return mConfigurationInternal.isTelephonyFallbackSupported();
127     }
128 
129     /**
130      * Returns {@code true} if location time zone detection should run all the time on supported
131      * devices, even when the user has not enabled it explicitly in settings. Enabled for internal
132      * testing only.
133      */
getGeoDetectionRunInBackgroundEnabled()134     public boolean getGeoDetectionRunInBackgroundEnabled() {
135         return mConfigurationInternal.getGeoDetectionRunInBackgroundEnabled();
136     }
137 
138     /** Returns true if enhanced metric collection is enabled. */
isEnhancedMetricsCollectionEnabled()139     public boolean isEnhancedMetricsCollectionEnabled() {
140         return mConfigurationInternal.isEnhancedMetricsCollectionEnabled();
141     }
142 
143     /** Returns true if user's location can be used generally. */
getUserLocationEnabledSetting()144     public boolean getUserLocationEnabledSetting() {
145         return mConfigurationInternal.getLocationEnabledSetting();
146     }
147 
148     /** Returns the value of the geolocation time zone detection enabled setting. */
getGeoDetectionEnabledSetting()149     public boolean getGeoDetectionEnabledSetting() {
150         return mConfigurationInternal.getGeoDetectionEnabledSetting();
151     }
152 
153     /** Returns the value of the auto time zone detection enabled setting. */
getAutoDetectionEnabledSetting()154     public boolean getAutoDetectionEnabledSetting() {
155         return mConfigurationInternal.getAutoDetectionEnabledSetting();
156     }
157 
158     /**
159      * Returns the detection mode the device is currently using, which can be influenced by various
160      * things besides the user's setting.
161      */
getDetectionMode()162     public @DetectionMode int getDetectionMode() {
163         switch (mConfigurationInternal.getDetectionMode()) {
164             case ConfigurationInternal.DETECTION_MODE_MANUAL:
165                 return DETECTION_MODE_MANUAL;
166             case ConfigurationInternal.DETECTION_MODE_GEO:
167                 return DETECTION_MODE_GEO;
168             case ConfigurationInternal.DETECTION_MODE_TELEPHONY:
169                 return DETECTION_MODE_TELEPHONY;
170             default:
171                 return DETECTION_MODE_UNKNOWN;
172         }
173     }
174 
175     /**
176      * Returns the ordinal for the device's current time zone ID.
177      * See {@link MetricsTimeZoneDetectorState} for information about ordinals.
178      */
getDeviceTimeZoneIdOrdinal()179     public int getDeviceTimeZoneIdOrdinal() {
180         return mDeviceTimeZoneIdOrdinal;
181     }
182 
183     /**
184      * Returns the device's current time zone ID. This will only be populated if {@link
185      * #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link
186      * MetricsTimeZoneDetectorState} for details.
187      */
188     @Nullable
getDeviceTimeZoneId()189     public String getDeviceTimeZoneId() {
190         return mDeviceTimeZoneId;
191     }
192 
193     /**
194      * Returns a canonical form of the last manual suggestion received.
195      */
196     @Nullable
getLatestManualSuggestion()197     public MetricsTimeZoneSuggestion getLatestManualSuggestion() {
198         return mLatestManualSuggestion;
199     }
200 
201     /**
202      * Returns a canonical form of the last telephony suggestion received.
203      */
204     @Nullable
getLatestTelephonySuggestion()205     public MetricsTimeZoneSuggestion getLatestTelephonySuggestion() {
206         return mLatestTelephonySuggestion;
207     }
208 
209     /**
210      * Returns a canonical form of last geolocation suggestion received.
211      */
212     @Nullable
getLatestGeolocationSuggestion()213     public MetricsTimeZoneSuggestion getLatestGeolocationSuggestion() {
214         return mLatestGeolocationSuggestion;
215     }
216 
217     @Override
equals(Object o)218     public boolean equals(Object o) {
219         if (this == o) {
220             return true;
221         }
222         if (o == null || getClass() != o.getClass()) {
223             return false;
224         }
225         MetricsTimeZoneDetectorState that = (MetricsTimeZoneDetectorState) o;
226         return mDeviceTimeZoneIdOrdinal == that.mDeviceTimeZoneIdOrdinal
227                 && Objects.equals(mDeviceTimeZoneId, that.mDeviceTimeZoneId)
228                 && mConfigurationInternal.equals(that.mConfigurationInternal)
229                 && Objects.equals(mLatestManualSuggestion, that.mLatestManualSuggestion)
230                 && Objects.equals(mLatestTelephonySuggestion, that.mLatestTelephonySuggestion)
231                 && Objects.equals(mLatestGeolocationSuggestion, that.mLatestGeolocationSuggestion);
232     }
233 
234     @Override
hashCode()235     public int hashCode() {
236         return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal, mDeviceTimeZoneId,
237                 mLatestManualSuggestion, mLatestTelephonySuggestion, mLatestGeolocationSuggestion);
238     }
239 
240     @Override
toString()241     public String toString() {
242         return "MetricsTimeZoneDetectorState{"
243                 + "mConfigurationInternal=" + mConfigurationInternal
244                 + ", mDeviceTimeZoneIdOrdinal=" + mDeviceTimeZoneIdOrdinal
245                 + ", mDeviceTimeZoneId=" + mDeviceTimeZoneId
246                 + ", mLatestManualSuggestion=" + mLatestManualSuggestion
247                 + ", mLatestTelephonySuggestion=" + mLatestTelephonySuggestion
248                 + ", mLatestGeolocationSuggestion=" + mLatestGeolocationSuggestion
249                 + '}';
250     }
251 
252     @Nullable
createMetricsTimeZoneSuggestion( @onNull OrdinalGenerator<String> zoneIdOrdinalGenerator, @NonNull ManualTimeZoneSuggestion manualSuggestion, boolean includeFullZoneIds)253     private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
254             @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
255             @NonNull ManualTimeZoneSuggestion manualSuggestion,
256             boolean includeFullZoneIds) {
257         if (manualSuggestion == null) {
258             return null;
259         }
260 
261         String suggestionZoneId = manualSuggestion.getZoneId();
262         String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null;
263         int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) };
264         return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
265     }
266 
267     @Nullable
createMetricsTimeZoneSuggestion( @onNull OrdinalGenerator<String> zoneIdOrdinalGenerator, @NonNull TelephonyTimeZoneSuggestion telephonySuggestion, boolean includeFullZoneIds)268     private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
269             @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
270             @NonNull TelephonyTimeZoneSuggestion telephonySuggestion,
271             boolean includeFullZoneIds) {
272         if (telephonySuggestion == null) {
273             return null;
274         }
275         String suggestionZoneId = telephonySuggestion.getZoneId();
276         if (suggestionZoneId == null) {
277             return MetricsTimeZoneSuggestion.createUncertain();
278         }
279         String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null;
280         int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) };
281         return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
282     }
283 
284     @Nullable
createMetricsTimeZoneSuggestion( @onNull OrdinalGenerator<String> zoneIdOrdinalGenerator, @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion, boolean includeFullZoneIds)285     private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
286             @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
287             @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion,
288             boolean includeFullZoneIds) {
289         if (geolocationSuggestion == null) {
290             return null;
291         }
292 
293         List<String> zoneIds = geolocationSuggestion.getZoneIds();
294         if (zoneIds == null) {
295             return MetricsTimeZoneSuggestion.createUncertain();
296         }
297         String[] metricZoneIds = includeFullZoneIds ? zoneIds.toArray(new String[0]) : null;
298         int[] zoneIdOrdinals = zoneIdOrdinalGenerator.ordinals(zoneIds);
299         return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
300     }
301 
302     /**
303      * A Java class that represents a generic time zone suggestion, i.e. one that is independent of
304      * origin-specific information. This closely matches the metrics atoms.proto
305      * MetricsTimeZoneSuggestion proto definition.
306      */
307     public static final class MetricsTimeZoneSuggestion {
308         @Nullable private final String[] mZoneIds;
309         @Nullable private final int[] mZoneIdOrdinals;
310 
MetricsTimeZoneSuggestion( @ullable String[] zoneIds, @Nullable int[] zoneIdOrdinals)311         private MetricsTimeZoneSuggestion(
312                 @Nullable String[] zoneIds, @Nullable int[] zoneIdOrdinals) {
313             mZoneIds = zoneIds;
314             mZoneIdOrdinals = zoneIdOrdinals;
315         }
316 
317         @NonNull
createUncertain()318         static MetricsTimeZoneSuggestion createUncertain() {
319             return new MetricsTimeZoneSuggestion(null, null);
320         }
321 
322         @NonNull
createCertain( @ullable String[] zoneIds, @NonNull int[] zoneIdOrdinals)323         static MetricsTimeZoneSuggestion createCertain(
324                 @Nullable String[] zoneIds, @NonNull int[] zoneIdOrdinals) {
325             return new MetricsTimeZoneSuggestion(zoneIds, zoneIdOrdinals);
326         }
327 
isCertain()328         public boolean isCertain() {
329             return mZoneIdOrdinals != null;
330         }
331 
332         /**
333          * Returns ordinals for the time zone IDs contained in the suggestion.
334          * See {@link MetricsTimeZoneDetectorState} for information about ordinals.
335          */
336         @Nullable
getZoneIdOrdinals()337         public int[] getZoneIdOrdinals() {
338             return mZoneIdOrdinals;
339         }
340 
341         /**
342          * Returns the time zone IDs contained in the suggestion. This will only be populated if
343          * {@link #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link
344          * MetricsTimeZoneDetectorState} for details.
345          */
346         @Nullable
getZoneIds()347         public String[] getZoneIds() {
348             return mZoneIds;
349         }
350 
351         @Override
equals(Object o)352         public boolean equals(Object o) {
353             if (this == o) {
354                 return true;
355             }
356             if (o == null || getClass() != o.getClass()) {
357                 return false;
358             }
359             MetricsTimeZoneSuggestion that = (MetricsTimeZoneSuggestion) o;
360             return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals)
361                     && Arrays.equals(mZoneIds, that.mZoneIds);
362         }
363 
364         @Override
hashCode()365         public int hashCode() {
366             int result = Arrays.hashCode(mZoneIds);
367             result = 31 * result + Arrays.hashCode(mZoneIdOrdinals);
368             return result;
369         }
370 
371         @Override
toString()372         public String toString() {
373             return "MetricsTimeZoneSuggestion{"
374                     + "mZoneIdOrdinals=" + Arrays.toString(mZoneIdOrdinals)
375                     + ", mZoneIds=" + Arrays.toString(mZoneIds)
376                     + '}';
377         }
378     }
379 }
380