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 LocationAlgorithmEvent latestLocationAlgorithmEvent)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 LocationAlgorithmEvent latestLocationAlgorithmEvent) { 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 105 MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion = null; 106 if (latestLocationAlgorithmEvent != null) { 107 GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion(); 108 latestCanonicalGeolocationSuggestion = createMetricsTimeZoneSuggestion( 109 tzIdOrdinalGenerator, suggestion, includeZoneIds); 110 } 111 112 return new MetricsTimeZoneDetectorState( 113 configurationInternal, deviceTimeZoneIdOrdinal, metricDeviceTimeZoneId, 114 latestCanonicalManualSuggestion, latestCanonicalTelephonySuggestion, 115 latestCanonicalGeolocationSuggestion); 116 } 117 118 /** Returns true if the device supports telephony time zone detection. */ isTelephonyDetectionSupported()119 public boolean isTelephonyDetectionSupported() { 120 return mConfigurationInternal.isTelephonyDetectionSupported(); 121 } 122 123 /** Returns true if the device supports geolocation time zone detection. */ isGeoDetectionSupported()124 public boolean isGeoDetectionSupported() { 125 return mConfigurationInternal.isGeoDetectionSupported(); 126 } 127 128 /** Returns true if the device supports telephony time zone detection fallback. */ isTelephonyTimeZoneFallbackSupported()129 public boolean isTelephonyTimeZoneFallbackSupported() { 130 return mConfigurationInternal.isTelephonyFallbackSupported(); 131 } 132 133 /** 134 * Returns {@code true} if location time zone detection should run all the time on supported 135 * devices, even when the user has not enabled it explicitly in settings. Enabled for internal 136 * testing only. 137 */ getGeoDetectionRunInBackgroundEnabled()138 public boolean getGeoDetectionRunInBackgroundEnabled() { 139 return mConfigurationInternal.getGeoDetectionRunInBackgroundEnabledSetting(); 140 } 141 142 /** Returns true if enhanced metric collection is enabled. */ isEnhancedMetricsCollectionEnabled()143 public boolean isEnhancedMetricsCollectionEnabled() { 144 return mConfigurationInternal.isEnhancedMetricsCollectionEnabled(); 145 } 146 147 /** Returns true if user's location can be used generally. */ getUserLocationEnabledSetting()148 public boolean getUserLocationEnabledSetting() { 149 return mConfigurationInternal.getLocationEnabledSetting(); 150 } 151 152 /** Returns the value of the geolocation time zone detection enabled setting. */ getGeoDetectionEnabledSetting()153 public boolean getGeoDetectionEnabledSetting() { 154 return mConfigurationInternal.getGeoDetectionEnabledSetting(); 155 } 156 157 /** Returns the value of the auto time zone detection enabled setting. */ getAutoDetectionEnabledSetting()158 public boolean getAutoDetectionEnabledSetting() { 159 return mConfigurationInternal.getAutoDetectionEnabledSetting(); 160 } 161 162 /** 163 * Returns the detection mode the device is currently using, which can be influenced by various 164 * things besides the user's setting. 165 */ getDetectionMode()166 public @DetectionMode int getDetectionMode() { 167 switch (mConfigurationInternal.getDetectionMode()) { 168 case ConfigurationInternal.DETECTION_MODE_MANUAL: 169 return DETECTION_MODE_MANUAL; 170 case ConfigurationInternal.DETECTION_MODE_GEO: 171 return DETECTION_MODE_GEO; 172 case ConfigurationInternal.DETECTION_MODE_TELEPHONY: 173 return DETECTION_MODE_TELEPHONY; 174 default: 175 return DETECTION_MODE_UNKNOWN; 176 } 177 } 178 179 /** 180 * Returns the ordinal for the device's current time zone ID. 181 * See {@link MetricsTimeZoneDetectorState} for information about ordinals. 182 */ getDeviceTimeZoneIdOrdinal()183 public int getDeviceTimeZoneIdOrdinal() { 184 return mDeviceTimeZoneIdOrdinal; 185 } 186 187 /** 188 * Returns the device's current time zone ID. This will only be populated if {@link 189 * #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link 190 * MetricsTimeZoneDetectorState} for details. 191 */ 192 @Nullable getDeviceTimeZoneId()193 public String getDeviceTimeZoneId() { 194 return mDeviceTimeZoneId; 195 } 196 197 /** 198 * Returns a canonical form of the last manual suggestion received. 199 */ 200 @Nullable getLatestManualSuggestion()201 public MetricsTimeZoneSuggestion getLatestManualSuggestion() { 202 return mLatestManualSuggestion; 203 } 204 205 /** 206 * Returns a canonical form of the last telephony suggestion received. 207 */ 208 @Nullable getLatestTelephonySuggestion()209 public MetricsTimeZoneSuggestion getLatestTelephonySuggestion() { 210 return mLatestTelephonySuggestion; 211 } 212 213 /** 214 * Returns a canonical form of last geolocation suggestion received. 215 */ 216 @Nullable getLatestGeolocationSuggestion()217 public MetricsTimeZoneSuggestion getLatestGeolocationSuggestion() { 218 return mLatestGeolocationSuggestion; 219 } 220 221 @Override equals(Object o)222 public boolean equals(Object o) { 223 if (this == o) { 224 return true; 225 } 226 if (o == null || getClass() != o.getClass()) { 227 return false; 228 } 229 MetricsTimeZoneDetectorState that = (MetricsTimeZoneDetectorState) o; 230 return mDeviceTimeZoneIdOrdinal == that.mDeviceTimeZoneIdOrdinal 231 && Objects.equals(mDeviceTimeZoneId, that.mDeviceTimeZoneId) 232 && mConfigurationInternal.equals(that.mConfigurationInternal) 233 && Objects.equals(mLatestManualSuggestion, that.mLatestManualSuggestion) 234 && Objects.equals(mLatestTelephonySuggestion, that.mLatestTelephonySuggestion) 235 && Objects.equals(mLatestGeolocationSuggestion, that.mLatestGeolocationSuggestion); 236 } 237 238 @Override hashCode()239 public int hashCode() { 240 return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal, mDeviceTimeZoneId, 241 mLatestManualSuggestion, mLatestTelephonySuggestion, mLatestGeolocationSuggestion); 242 } 243 244 @Override toString()245 public String toString() { 246 return "MetricsTimeZoneDetectorState{" 247 + "mConfigurationInternal=" + mConfigurationInternal 248 + ", mDeviceTimeZoneIdOrdinal=" + mDeviceTimeZoneIdOrdinal 249 + ", mDeviceTimeZoneId=" + mDeviceTimeZoneId 250 + ", mLatestManualSuggestion=" + mLatestManualSuggestion 251 + ", mLatestTelephonySuggestion=" + mLatestTelephonySuggestion 252 + ", mLatestGeolocationSuggestion=" + mLatestGeolocationSuggestion 253 + '}'; 254 } 255 256 @Nullable createMetricsTimeZoneSuggestion( @onNull OrdinalGenerator<String> zoneIdOrdinalGenerator, @NonNull ManualTimeZoneSuggestion manualSuggestion, boolean includeFullZoneIds)257 private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( 258 @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator, 259 @NonNull ManualTimeZoneSuggestion manualSuggestion, 260 boolean includeFullZoneIds) { 261 if (manualSuggestion == null) { 262 return null; 263 } 264 265 String suggestionZoneId = manualSuggestion.getZoneId(); 266 String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null; 267 int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) }; 268 return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals); 269 } 270 271 @Nullable createMetricsTimeZoneSuggestion( @onNull OrdinalGenerator<String> zoneIdOrdinalGenerator, @NonNull TelephonyTimeZoneSuggestion telephonySuggestion, boolean includeFullZoneIds)272 private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( 273 @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator, 274 @NonNull TelephonyTimeZoneSuggestion telephonySuggestion, 275 boolean includeFullZoneIds) { 276 if (telephonySuggestion == null) { 277 return null; 278 } 279 String suggestionZoneId = telephonySuggestion.getZoneId(); 280 if (suggestionZoneId == null) { 281 return MetricsTimeZoneSuggestion.createUncertain(); 282 } 283 String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null; 284 int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) }; 285 return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals); 286 } 287 288 @Nullable createMetricsTimeZoneSuggestion( @onNull OrdinalGenerator<String> zoneIdOrdinalGenerator, @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion, boolean includeFullZoneIds)289 private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( 290 @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator, 291 @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion, 292 boolean includeFullZoneIds) { 293 if (geolocationSuggestion == null) { 294 return null; 295 } 296 297 List<String> zoneIds = geolocationSuggestion.getZoneIds(); 298 if (zoneIds == null) { 299 return MetricsTimeZoneSuggestion.createUncertain(); 300 } 301 String[] metricZoneIds = includeFullZoneIds ? zoneIds.toArray(new String[0]) : null; 302 int[] zoneIdOrdinals = zoneIdOrdinalGenerator.ordinals(zoneIds); 303 return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals); 304 } 305 306 /** 307 * A Java class that represents a generic time zone suggestion, i.e. one that is independent of 308 * origin-specific information. This closely matches the metrics atoms.proto 309 * MetricsTimeZoneSuggestion proto definition. 310 */ 311 public static final class MetricsTimeZoneSuggestion { 312 @Nullable private final String[] mZoneIds; 313 @Nullable private final int[] mZoneIdOrdinals; 314 MetricsTimeZoneSuggestion( @ullable String[] zoneIds, @Nullable int[] zoneIdOrdinals)315 private MetricsTimeZoneSuggestion( 316 @Nullable String[] zoneIds, @Nullable int[] zoneIdOrdinals) { 317 mZoneIds = zoneIds; 318 mZoneIdOrdinals = zoneIdOrdinals; 319 } 320 321 @NonNull createUncertain()322 static MetricsTimeZoneSuggestion createUncertain() { 323 return new MetricsTimeZoneSuggestion(null, null); 324 } 325 326 @NonNull createCertain( @ullable String[] zoneIds, @NonNull int[] zoneIdOrdinals)327 static MetricsTimeZoneSuggestion createCertain( 328 @Nullable String[] zoneIds, @NonNull int[] zoneIdOrdinals) { 329 return new MetricsTimeZoneSuggestion(zoneIds, zoneIdOrdinals); 330 } 331 isCertain()332 public boolean isCertain() { 333 return mZoneIdOrdinals != null; 334 } 335 336 /** 337 * Returns ordinals for the time zone IDs contained in the suggestion. 338 * See {@link MetricsTimeZoneDetectorState} for information about ordinals. 339 */ 340 @Nullable getZoneIdOrdinals()341 public int[] getZoneIdOrdinals() { 342 return mZoneIdOrdinals; 343 } 344 345 /** 346 * Returns the time zone IDs contained in the suggestion. This will only be populated if 347 * {@link #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link 348 * MetricsTimeZoneDetectorState} for details. 349 */ 350 @Nullable getZoneIds()351 public String[] getZoneIds() { 352 return mZoneIds; 353 } 354 355 @Override equals(Object o)356 public boolean equals(Object o) { 357 if (this == o) { 358 return true; 359 } 360 if (o == null || getClass() != o.getClass()) { 361 return false; 362 } 363 MetricsTimeZoneSuggestion that = (MetricsTimeZoneSuggestion) o; 364 return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals) 365 && Arrays.equals(mZoneIds, that.mZoneIds); 366 } 367 368 @Override hashCode()369 public int hashCode() { 370 int result = Arrays.hashCode(mZoneIds); 371 result = 31 * result + Arrays.hashCode(mZoneIdOrdinals); 372 return result; 373 } 374 375 @Override toString()376 public String toString() { 377 return "MetricsTimeZoneSuggestion{" 378 + "mZoneIdOrdinals=" + Arrays.toString(mZoneIdOrdinals) 379 + ", mZoneIds=" + Arrays.toString(mZoneIds) 380 + '}'; 381 } 382 } 383 } 384