1 /* 2 * Copyright (C) 2020 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 static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED; 20 import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE; 21 import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED; 22 import static android.app.time.Capabilities.CAPABILITY_POSSESSED; 23 24 import android.annotation.IntDef; 25 import android.annotation.NonNull; 26 import android.annotation.UserIdInt; 27 import android.app.time.Capabilities.CapabilityState; 28 import android.app.time.TimeZoneCapabilities; 29 import android.app.time.TimeZoneConfiguration; 30 import android.os.UserHandle; 31 32 import java.lang.annotation.ElementType; 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.lang.annotation.Target; 36 import java.util.Objects; 37 38 /** 39 * Holds configuration values that affect user-facing time zone behavior and some associated logic. 40 * Some configuration is global, some is user scoped, but this class deliberately doesn't make a 41 * distinction for simplicity. 42 */ 43 public final class ConfigurationInternal { 44 45 @IntDef(prefix = "DETECTION_MODE_", 46 value = { DETECTION_MODE_UNKNOWN, DETECTION_MODE_MANUAL, DETECTION_MODE_GEO, 47 DETECTION_MODE_TELEPHONY } 48 ) 49 @Retention(RetentionPolicy.SOURCE) 50 @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) 51 @interface DetectionMode {}; 52 53 public static final @DetectionMode int DETECTION_MODE_UNKNOWN = 0; 54 public static final @DetectionMode int DETECTION_MODE_MANUAL = 1; 55 public static final @DetectionMode int DETECTION_MODE_GEO = 2; 56 public static final @DetectionMode int DETECTION_MODE_TELEPHONY = 3; 57 58 private final boolean mTelephonyDetectionSupported; 59 private final boolean mGeoDetectionSupported; 60 private final boolean mTelephonyFallbackSupported; 61 private final boolean mGeoDetectionRunInBackgroundEnabled; 62 private final boolean mEnhancedMetricsCollectionEnabled; 63 private final boolean mAutoDetectionEnabledSetting; 64 private final @UserIdInt int mUserId; 65 private final boolean mUserConfigAllowed; 66 private final boolean mLocationEnabledSetting; 67 private final boolean mGeoDetectionEnabledSetting; 68 ConfigurationInternal(Builder builder)69 private ConfigurationInternal(Builder builder) { 70 mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported; 71 mGeoDetectionSupported = builder.mGeoDetectionSupported; 72 mTelephonyFallbackSupported = builder.mTelephonyFallbackSupported; 73 mGeoDetectionRunInBackgroundEnabled = builder.mGeoDetectionRunInBackgroundEnabled; 74 mEnhancedMetricsCollectionEnabled = builder.mEnhancedMetricsCollectionEnabled; 75 mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting; 76 77 mUserId = Objects.requireNonNull(builder.mUserId, "userId must be set"); 78 mUserConfigAllowed = builder.mUserConfigAllowed; 79 mLocationEnabledSetting = builder.mLocationEnabledSetting; 80 mGeoDetectionEnabledSetting = builder.mGeoDetectionEnabledSetting; 81 } 82 83 /** Returns true if the device supports any form of auto time zone detection. */ isAutoDetectionSupported()84 public boolean isAutoDetectionSupported() { 85 return mTelephonyDetectionSupported || mGeoDetectionSupported; 86 } 87 88 /** Returns true if the device supports telephony time zone detection. */ isTelephonyDetectionSupported()89 public boolean isTelephonyDetectionSupported() { 90 return mTelephonyDetectionSupported; 91 } 92 93 /** Returns true if the device supports geolocation time zone detection. */ isGeoDetectionSupported()94 public boolean isGeoDetectionSupported() { 95 return mGeoDetectionSupported; 96 } 97 98 /** 99 * Returns true if the device supports time zone detection falling back to telephony detection 100 * under certain circumstances. 101 */ isTelephonyFallbackSupported()102 public boolean isTelephonyFallbackSupported() { 103 return mTelephonyFallbackSupported; 104 } 105 106 /** 107 * Returns {@code true} if location time zone detection should run when auto time zone detection 108 * is enabled on supported devices, even when the user has not enabled the algorithm explicitly 109 * in settings. Enabled for internal testing only. See {@link #isGeoDetectionExecutionEnabled()} 110 * and {@link #getDetectionMode()} for details. 111 */ getGeoDetectionRunInBackgroundEnabledSetting()112 boolean getGeoDetectionRunInBackgroundEnabledSetting() { 113 return mGeoDetectionRunInBackgroundEnabled; 114 } 115 116 /** 117 * Returns {@code true} if the device can collect / report extra metrics information for QA 118 * / testers. These metrics might involve logging more expensive or more revealing data that 119 * would not be collected from the set of public users. 120 */ isEnhancedMetricsCollectionEnabled()121 public boolean isEnhancedMetricsCollectionEnabled() { 122 return mEnhancedMetricsCollectionEnabled; 123 } 124 125 /** Returns the value of the auto time zone detection enabled setting. */ getAutoDetectionEnabledSetting()126 public boolean getAutoDetectionEnabledSetting() { 127 return mAutoDetectionEnabledSetting; 128 } 129 130 /** 131 * Returns true if auto time zone detection behavior is actually enabled, which can be distinct 132 * from the raw setting value. 133 */ getAutoDetectionEnabledBehavior()134 public boolean getAutoDetectionEnabledBehavior() { 135 return isAutoDetectionSupported() && getAutoDetectionEnabledSetting(); 136 } 137 138 /** Returns the ID of the user this configuration is associated with. */ getUserId()139 public @UserIdInt int getUserId() { 140 return mUserId; 141 } 142 143 /** Returns the handle of the user this configuration is associated with. */ 144 @NonNull getUserHandle()145 public UserHandle getUserHandle() { 146 return UserHandle.of(mUserId); 147 } 148 149 /** 150 * Returns true if the user is allowed to modify time zone configuration, e.g. can be false due 151 * to device policy (enterprise). 152 * 153 * <p>See also {@link #asCapabilities(boolean)} for situations where this value is ignored. 154 */ isUserConfigAllowed()155 public boolean isUserConfigAllowed() { 156 return mUserConfigAllowed; 157 } 158 159 /** Returns true if user's location can be used generally. */ getLocationEnabledSetting()160 public boolean getLocationEnabledSetting() { 161 return mLocationEnabledSetting; 162 } 163 164 /** Returns the value of the geolocation time zone detection enabled setting. */ getGeoDetectionEnabledSetting()165 public boolean getGeoDetectionEnabledSetting() { 166 return mGeoDetectionEnabledSetting; 167 } 168 169 /** 170 * Returns the detection mode to use, i.e. which suggestions to use to determine the device's 171 * time zone. 172 */ getDetectionMode()173 public @DetectionMode int getDetectionMode() { 174 if (!isAutoDetectionSupported()) { 175 // Handle the easy case first: No auto detection algorithms supported must mean manual. 176 return DETECTION_MODE_MANUAL; 177 } else if (!getAutoDetectionEnabledSetting()) { 178 // Auto detection algorithms are supported, but disabled by the user. 179 return DETECTION_MODE_MANUAL; 180 } else if (getGeoDetectionEnabledBehavior()) { 181 return DETECTION_MODE_GEO; 182 } else if (isTelephonyDetectionSupported()) { 183 return DETECTION_MODE_TELEPHONY; 184 } else { 185 // On devices with telephony detection support, telephony is used instead of geo when 186 // geo cannot be used. This "unknown" case can occur on devices with only the location 187 // detection algorithm supported when the user's master location setting prevents its 188 // use. 189 return DETECTION_MODE_UNKNOWN; 190 } 191 } 192 getGeoDetectionEnabledBehavior()193 private boolean getGeoDetectionEnabledBehavior() { 194 // isAutoDetectionSupported() should already have been checked before calling this method. 195 if (isGeoDetectionSupported() && getLocationEnabledSetting()) { 196 if (isTelephonyDetectionSupported()) { 197 // This is the "normal" case for smartphones that have both telephony and geo 198 // detection: the user chooses which type of detection to use. 199 return getGeoDetectionEnabledSetting(); 200 } else { 201 // When only geo detection is supported then there is no choice for the user to 202 // make between detection modes, so no user setting is consulted. 203 return true; 204 } 205 } 206 return false; 207 } 208 209 /** 210 * Returns true if geolocation time zone detection behavior can execute. Typically, this will 211 * agree with {@link #getDetectionMode()}, but under rare circumstances the geolocation detector 212 * may be run in the background if the user's settings allow. 213 */ isGeoDetectionExecutionEnabled()214 public boolean isGeoDetectionExecutionEnabled() { 215 return getDetectionMode() == DETECTION_MODE_GEO 216 || getGeoDetectionRunInBackgroundEnabledBehavior(); 217 } 218 getGeoDetectionRunInBackgroundEnabledBehavior()219 private boolean getGeoDetectionRunInBackgroundEnabledBehavior() { 220 return isGeoDetectionSupported() 221 && getLocationEnabledSetting() 222 && getAutoDetectionEnabledSetting() 223 && getGeoDetectionRunInBackgroundEnabledSetting(); 224 } 225 226 @NonNull asCapabilities(boolean bypassUserPolicyChecks)227 public TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) { 228 UserHandle userHandle = UserHandle.of(mUserId); 229 TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle); 230 231 boolean allowConfigDateTime = isUserConfigAllowed() || bypassUserPolicyChecks; 232 233 // Automatic time zone detection is only supported on devices if there is a telephony 234 // network available or geolocation time zone detection is possible. 235 boolean deviceHasAutoTimeZoneDetection = isAutoDetectionSupported(); 236 237 final @CapabilityState int configureAutoDetectionEnabledCapability; 238 if (!deviceHasAutoTimeZoneDetection) { 239 configureAutoDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED; 240 } else if (!allowConfigDateTime) { 241 configureAutoDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED; 242 } else { 243 configureAutoDetectionEnabledCapability = CAPABILITY_POSSESSED; 244 } 245 builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability); 246 247 builder.setUseLocationEnabled(mLocationEnabledSetting); 248 249 boolean deviceHasLocationTimeZoneDetection = isGeoDetectionSupported(); 250 boolean deviceHasTelephonyDetection = isTelephonyDetectionSupported(); 251 252 // Note: allowConfigDateTime does not restrict the ability to change location time zone 253 // detection enabled. This is intentional as it has user privacy implications and so it 254 // makes sense to leave this under a user's control. The only time this is not true is 255 // on devices that only support location-based detection and the main auto detection setting 256 // is used to influence whether location can be used. 257 final @CapabilityState int configureGeolocationDetectionEnabledCapability; 258 if (!deviceHasLocationTimeZoneDetection || !deviceHasTelephonyDetection) { 259 // If the device doesn't have geolocation detection support OR it ONLY has geolocation 260 // detection support (no telephony) then the user doesn't need the ability to toggle the 261 // location-based detection on and off (the auto detection toggle is considered 262 // sufficient). 263 configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED; 264 } else if (!mAutoDetectionEnabledSetting || !getLocationEnabledSetting()) { 265 configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_APPLICABLE; 266 } else { 267 configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED; 268 } 269 builder.setConfigureGeoDetectionEnabledCapability( 270 configureGeolocationDetectionEnabledCapability); 271 272 // The ability to make manual time zone suggestions can also be restricted by policy. With 273 // the current logic above, this could lead to a situation where a device hardware does not 274 // support auto detection, the device has been forced into "auto" mode by an admin and the 275 // user is unable to disable auto detection. 276 final @CapabilityState int suggestManualTimeZoneCapability; 277 if (!allowConfigDateTime) { 278 suggestManualTimeZoneCapability = CAPABILITY_NOT_ALLOWED; 279 } else if (getAutoDetectionEnabledBehavior()) { 280 suggestManualTimeZoneCapability = CAPABILITY_NOT_APPLICABLE; 281 } else { 282 suggestManualTimeZoneCapability = CAPABILITY_POSSESSED; 283 } 284 builder.setSetManualTimeZoneCapability(suggestManualTimeZoneCapability); 285 286 return builder.build(); 287 } 288 289 /** Returns a {@link TimeZoneConfiguration} from the configuration values. */ asConfiguration()290 public TimeZoneConfiguration asConfiguration() { 291 return new TimeZoneConfiguration.Builder() 292 .setAutoDetectionEnabled(getAutoDetectionEnabledSetting()) 293 .setGeoDetectionEnabled(getGeoDetectionEnabledSetting()) 294 .build(); 295 } 296 297 /** 298 * Merges the configuration values from this with any properties set in {@code 299 * newConfiguration}. The new configuration has precedence. Used to apply user updates to 300 * internal configuration. 301 */ merge(TimeZoneConfiguration newConfiguration)302 public ConfigurationInternal merge(TimeZoneConfiguration newConfiguration) { 303 Builder builder = new Builder(this); 304 if (newConfiguration.hasIsAutoDetectionEnabled()) { 305 builder.setAutoDetectionEnabledSetting(newConfiguration.isAutoDetectionEnabled()); 306 } 307 if (newConfiguration.hasIsGeoDetectionEnabled()) { 308 builder.setGeoDetectionEnabledSetting(newConfiguration.isGeoDetectionEnabled()); 309 } 310 return builder.build(); 311 } 312 313 @Override equals(Object o)314 public boolean equals(Object o) { 315 if (this == o) { 316 return true; 317 } 318 if (o == null || getClass() != o.getClass()) { 319 return false; 320 } 321 ConfigurationInternal that = (ConfigurationInternal) o; 322 return mUserId == that.mUserId 323 && mUserConfigAllowed == that.mUserConfigAllowed 324 && mTelephonyDetectionSupported == that.mTelephonyDetectionSupported 325 && mGeoDetectionSupported == that.mGeoDetectionSupported 326 && mTelephonyFallbackSupported == that.mTelephonyFallbackSupported 327 && mGeoDetectionRunInBackgroundEnabled == that.mGeoDetectionRunInBackgroundEnabled 328 && mEnhancedMetricsCollectionEnabled == that.mEnhancedMetricsCollectionEnabled 329 && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting 330 && mLocationEnabledSetting == that.mLocationEnabledSetting 331 && mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting; 332 } 333 334 @Override hashCode()335 public int hashCode() { 336 return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported, 337 mGeoDetectionSupported, mTelephonyFallbackSupported, 338 mGeoDetectionRunInBackgroundEnabled, mEnhancedMetricsCollectionEnabled, 339 mAutoDetectionEnabledSetting, mLocationEnabledSetting, mGeoDetectionEnabledSetting); 340 } 341 342 @Override toString()343 public String toString() { 344 return "ConfigurationInternal{" 345 + "mUserId=" + mUserId 346 + ", mUserConfigAllowed=" + mUserConfigAllowed 347 + ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported 348 + ", mGeoDetectionSupported=" + mGeoDetectionSupported 349 + ", mTelephonyFallbackSupported=" + mTelephonyFallbackSupported 350 + ", mGeoDetectionRunInBackgroundEnabled=" + mGeoDetectionRunInBackgroundEnabled 351 + ", mEnhancedMetricsCollectionEnabled=" + mEnhancedMetricsCollectionEnabled 352 + ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting 353 + ", mLocationEnabledSetting=" + mLocationEnabledSetting 354 + ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting 355 + '}'; 356 } 357 358 /** 359 * A Builder for {@link ConfigurationInternal}. 360 */ 361 public static class Builder { 362 363 private @UserIdInt Integer mUserId; 364 private boolean mUserConfigAllowed; 365 private boolean mTelephonyDetectionSupported; 366 private boolean mGeoDetectionSupported; 367 private boolean mTelephonyFallbackSupported; 368 private boolean mGeoDetectionRunInBackgroundEnabled; 369 private boolean mEnhancedMetricsCollectionEnabled; 370 private boolean mAutoDetectionEnabledSetting; 371 private boolean mLocationEnabledSetting; 372 private boolean mGeoDetectionEnabledSetting; 373 374 /** 375 * Creates a new Builder. 376 */ Builder()377 public Builder() {} 378 379 /** 380 * Creates a new Builder by copying values from an existing instance. 381 */ Builder(ConfigurationInternal toCopy)382 public Builder(ConfigurationInternal toCopy) { 383 this.mUserId = toCopy.mUserId; 384 this.mUserConfigAllowed = toCopy.mUserConfigAllowed; 385 this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported; 386 this.mTelephonyFallbackSupported = toCopy.mTelephonyFallbackSupported; 387 this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported; 388 this.mGeoDetectionRunInBackgroundEnabled = toCopy.mGeoDetectionRunInBackgroundEnabled; 389 this.mEnhancedMetricsCollectionEnabled = toCopy.mEnhancedMetricsCollectionEnabled; 390 this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting; 391 this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting; 392 this.mGeoDetectionEnabledSetting = toCopy.mGeoDetectionEnabledSetting; 393 } 394 395 /** 396 * Sets the user ID the configuration is for. 397 */ setUserId(@serIdInt int userId)398 public Builder setUserId(@UserIdInt int userId) { 399 mUserId = userId; 400 return this; 401 } 402 403 /** 404 * Sets whether the user is allowed to configure time zone settings on this device. 405 */ setUserConfigAllowed(boolean configAllowed)406 public Builder setUserConfigAllowed(boolean configAllowed) { 407 mUserConfigAllowed = configAllowed; 408 return this; 409 } 410 411 /** 412 * Sets whether telephony time zone detection is supported on this device. 413 */ setTelephonyDetectionFeatureSupported(boolean supported)414 public Builder setTelephonyDetectionFeatureSupported(boolean supported) { 415 mTelephonyDetectionSupported = supported; 416 return this; 417 } 418 419 /** 420 * Sets whether geolocation time zone detection is supported on this device. 421 */ setGeoDetectionFeatureSupported(boolean supported)422 public Builder setGeoDetectionFeatureSupported(boolean supported) { 423 mGeoDetectionSupported = supported; 424 return this; 425 } 426 427 /** 428 * Sets whether time zone detection supports falling back to telephony detection under 429 * certain circumstances. 430 */ setTelephonyFallbackSupported(boolean supported)431 public Builder setTelephonyFallbackSupported(boolean supported) { 432 mTelephonyFallbackSupported = supported; 433 return this; 434 } 435 436 /** 437 * Sets whether location time zone detection should run when auto time zone detection is 438 * enabled on supported devices, even when the user has not enabled the algorithm explicitly 439 * in settings. Enabled for internal testing only. 440 */ setGeoDetectionRunInBackgroundEnabled(boolean enabled)441 public Builder setGeoDetectionRunInBackgroundEnabled(boolean enabled) { 442 mGeoDetectionRunInBackgroundEnabled = enabled; 443 return this; 444 } 445 446 /** 447 * Sets the value for enhanced metrics collection. 448 */ setEnhancedMetricsCollectionEnabled(boolean enabled)449 public Builder setEnhancedMetricsCollectionEnabled(boolean enabled) { 450 mEnhancedMetricsCollectionEnabled = enabled; 451 return this; 452 } 453 454 /** 455 * Sets the value of the automatic time zone detection enabled setting for this device. 456 */ setAutoDetectionEnabledSetting(boolean enabled)457 public Builder setAutoDetectionEnabledSetting(boolean enabled) { 458 mAutoDetectionEnabledSetting = enabled; 459 return this; 460 } 461 462 /** 463 * Sets the value of the location mode setting for this user. 464 */ setLocationEnabledSetting(boolean enabled)465 public Builder setLocationEnabledSetting(boolean enabled) { 466 mLocationEnabledSetting = enabled; 467 return this; 468 } 469 470 /** 471 * Sets the value of the geolocation time zone detection setting for this user. 472 */ setGeoDetectionEnabledSetting(boolean enabled)473 public Builder setGeoDetectionEnabledSetting(boolean enabled) { 474 mGeoDetectionEnabledSetting = enabled; 475 return this; 476 } 477 478 /** Returns a new {@link ConfigurationInternal}. */ 479 @NonNull build()480 public ConfigurationInternal build() { 481 return new ConfigurationInternal(this); 482 } 483 } 484 } 485