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