diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java index ec10d8431e741..6b5e66758d942 100644 --- a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java +++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java @@ -19,6 +19,7 @@ package android.app.time; import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING; import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED; import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN; import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusFromString; import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString; import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus; @@ -319,6 +320,40 @@ public final class LocationTimeZoneAlgorithmStatus implements Parcelable { mSecondaryProviderStatus, mSecondaryProviderReportedStatus); } + /** + * Returns {@code true} if the algorithm status could allow the time zone detector to enter + * telephony fallback mode. + */ + public boolean couldEnableTelephonyFallback() { + if (mStatus == DETECTION_ALGORITHM_STATUS_UNKNOWN + || mStatus == DETECTION_ALGORITHM_STATUS_NOT_RUNNING + || mStatus == DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED) { + // This method is not expected to be called on objects with these statuses. Fallback + // should not be enabled if it is. + return false; + } + + // mStatus == DETECTOR_STATUS_RUNNING. + + boolean primarySuggestsFallback = false; + if (mPrimaryProviderStatus == PROVIDER_STATUS_NOT_PRESENT) { + primarySuggestsFallback = true; + } else if (mPrimaryProviderStatus == PROVIDER_STATUS_IS_UNCERTAIN + && mPrimaryProviderReportedStatus != null) { + primarySuggestsFallback = mPrimaryProviderReportedStatus.couldEnableTelephonyFallback(); + } + + boolean secondarySuggestsFallback = false; + if (mSecondaryProviderStatus == PROVIDER_STATUS_NOT_PRESENT) { + secondarySuggestsFallback = true; + } else if (mSecondaryProviderStatus == PROVIDER_STATUS_IS_UNCERTAIN + && mSecondaryProviderReportedStatus != null) { + secondarySuggestsFallback = + mSecondaryProviderReportedStatus.couldEnableTelephonyFallback(); + } + return primarySuggestsFallback && secondarySuggestsFallback; + } + /** @hide */ @VisibleForTesting @NonNull diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 48cfc87d7d55e..37605e422c9c8 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -47,6 +47,7 @@ android_test { "androidx.test.ext.junit", "androidx.test.runner", "androidx.test.rules", + "junit-params", "kotlin-test", "mockito-target-minus-junit4", "ub-uiautomator", diff --git a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java index a648a885aea2b..fc69f69146587 100644 --- a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java +++ b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java @@ -17,19 +17,26 @@ package android.app.time; import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED; import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN; import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN; import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN; import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT; import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY; import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode; import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE; import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK; +import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_NOT_APPLICABLE; import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus; import android.service.timezone.TimeZoneProviderStatus; @@ -207,4 +214,114 @@ public class LocationTimeZoneAlgorithmStatusTest { assertEquals(status, LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString())); } + + @Test + public void testCouldEnableTelephonyFallback_notRunning() { + LocationTimeZoneAlgorithmStatus notRunning = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING, + PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null); + assertFalse(notRunning.couldEnableTelephonyFallback()); + } + + @Test + public void testCouldEnableTelephonyFallback_unknown() { + // DETECTION_ALGORITHM_STATUS_UNKNOWN must never allow fallback + LocationTimeZoneAlgorithmStatus unknown = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_UNKNOWN, + PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null); + assertFalse(unknown.couldEnableTelephonyFallback()); + } + + @Test + public void testCouldEnableTelephonyFallback_notSupported() { + // DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED must never allow fallback + LocationTimeZoneAlgorithmStatus notSupported = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED, + PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null); + assertFalse(notSupported.couldEnableTelephonyFallback()); + } + + @Test + public void testCouldEnableTelephonyFallback_running() { + // DETECTION_ALGORITHM_STATUS_RUNNING may allow fallback + + // Sample provider-reported statuses that do / do not enable fallback. + TimeZoneProviderStatus enableTelephonyFallbackProviderStatus = + new TimeZoneProviderStatus.Builder() + .setLocationDetectionDependencyStatus( + DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT) + .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE) + .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_NOT_APPLICABLE) + .build(); + assertTrue(enableTelephonyFallbackProviderStatus.couldEnableTelephonyFallback()); + + TimeZoneProviderStatus notEnableTelephonyFallbackProviderStatus = + new TimeZoneProviderStatus.Builder() + .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE) + .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE) + .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_NOT_APPLICABLE) + .build(); + assertFalse(notEnableTelephonyFallbackProviderStatus.couldEnableTelephonyFallback()); + + // Provider not ready: Never enable fallback + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null); + assertFalse(status.couldEnableTelephonyFallback()); + } + + // Provider uncertain without reported status: Never enable fallback + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, null, PROVIDER_STATUS_NOT_READY, null); + assertFalse(status.couldEnableTelephonyFallback()); + } + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null); + assertFalse(status.couldEnableTelephonyFallback()); + } + + // Provider uncertain with reported status: Fallback is based on the status for present + // providers that report their status. All present providers must have reported status and + // agree that fallback is a good idea. + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus, + PROVIDER_STATUS_NOT_READY, null); + assertFalse(status.couldEnableTelephonyFallback()); + } + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus, + PROVIDER_STATUS_NOT_PRESENT, null); + assertTrue(status.couldEnableTelephonyFallback()); + } + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus, + PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus); + assertTrue(status.couldEnableTelephonyFallback()); + } + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus, + PROVIDER_STATUS_IS_UNCERTAIN, notEnableTelephonyFallbackProviderStatus); + assertFalse(status.couldEnableTelephonyFallback()); + } + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_NOT_PRESENT, null, + PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus); + assertTrue(status.couldEnableTelephonyFallback()); + } + } } diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java index 9006cd91d616a..0c1630e8fefd6 100644 --- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java +++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java @@ -16,15 +16,31 @@ package android.service.timezone; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT; import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS; import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_UNKNOWN; +import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED; import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK; +import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN; import static org.junit.Assert.assertEquals; +import android.service.timezone.TimeZoneProviderStatus.DependencyStatus; +import android.service.timezone.TimeZoneProviderStatus.OperationStatus; + import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; /** Non-SDK tests. See CTS for SDK API tests. */ +@RunWith(JUnitParamsRunner.class) public class TimeZoneProviderStatusTest { @Test @@ -37,4 +53,51 @@ public class TimeZoneProviderStatusTest { assertEquals(status, TimeZoneProviderStatus.parseProviderStatus(status.toString())); } + + @Test + @Parameters(method = "couldEnableTelephonyFallbackParams") + public void couldEnableTelephonyFallback(@DependencyStatus int locationDetectionStatus, + @DependencyStatus int connectivityStatus, @OperationStatus int tzResolutionStatus) { + TimeZoneProviderStatus providerStatus = + new TimeZoneProviderStatus.Builder() + .setLocationDetectionDependencyStatus(locationDetectionStatus) + .setConnectivityDependencyStatus(connectivityStatus) + .setTimeZoneResolutionOperationStatus(tzResolutionStatus) + .build(); + boolean locationDetectionStatusCouldEnableFallback = + (locationDetectionStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT + || locationDetectionStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS); + boolean connectivityStatusCouldEnableFallback = + (connectivityStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT + || connectivityStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS); + boolean tzResolutionStatusCouldEnableFallback = false; + + assertEquals(locationDetectionStatusCouldEnableFallback + || connectivityStatusCouldEnableFallback + || tzResolutionStatusCouldEnableFallback, + providerStatus.couldEnableTelephonyFallback()); + } + + public static Integer[][] couldEnableTelephonyFallbackParams() { + List params = new ArrayList<>(); + @DependencyStatus int[] dependencyStatuses = + IntStream.rangeClosed( + DEPENDENCY_STATUS_UNKNOWN, DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS).toArray(); + @OperationStatus int[] operationStatuses = + IntStream.rangeClosed(OPERATION_STATUS_UNKNOWN, OPERATION_STATUS_FAILED).toArray(); + + // Cartesian product: dependencyStatus x dependencyStatus x operationStatus + for (@DependencyStatus int locationDetectionStatus : dependencyStatuses) { + for (@DependencyStatus int connectivityStatus : dependencyStatuses) { + for (@OperationStatus int tzResolutionStatus : operationStatuses) { + params.add(new Integer[] { + locationDetectionStatus, + connectivityStatus, + tzResolutionStatus + }); + } + } + } + return params.toArray(new Integer[0][0]); + } } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index f8c1c9269ff39..10cd5d1a0669f 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -91,7 +91,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub deviceActivityMonitor.addListener(new DeviceActivityMonitor.Listener() { @Override public void onFlightComplete() { - timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(); + timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback("onFlightComplete()"); } }); @@ -402,9 +402,9 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub * Sends a signal to enable telephony fallback. Provided for command-line access for use * during tests. This is not exposed as a binder API. */ - void enableTelephonyFallback() { + void enableTelephonyFallback(@NonNull String reason) { enforceManageTimeZoneDetectorPermission(); - mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(); + mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(reason); } /** diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java index 69274dba78250..ab68e834d3377 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java @@ -189,7 +189,7 @@ class TimeZoneDetectorShellCommand extends ShellCommand { } private int runEnableTelephonyFallback() { - mInterface.enableTelephonyFallback(); + mInterface.enableTelephonyFallback("Command line"); return 0; } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index 5768a6bfa0dcf..37e67c9216342 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -35,13 +35,13 @@ import android.util.IndentingPrintWriter; *

Devices can have zero, one or two automatic time zone detection algorithms available at any * point in time. * - *

The two automatic detection algorithms supported are "telephony" and "geolocation". Algorithm + *

The two automatic detection algorithms supported are "telephony" and "location". Algorithm * availability and use depends on several factors: *

* *

If there are no automatic time zone detections algorithms available then the user can usually @@ -56,14 +56,14 @@ import android.util.IndentingPrintWriter; * slotIndexes must have an empty suggestion submitted in order to "withdraw" their previous * suggestion otherwise it will remain in use. * - *

Geolocation detection is dependent on the current user and their settings. The device retains - * at most one geolocation suggestion. Generally, use of a device's location is dependent on the - * user's "location toggle", but even when that is enabled the user may choose to enable / disable - * the use of geolocation for device time zone detection. If the current user changes to one that - * does not have geolocation detection enabled, or the user turns off geolocation detection, then - * the strategy discards the latest geolocation suggestion. Devices that lose a location fix must - * have an empty suggestion submitted in order to "withdraw" their previous suggestion otherwise it - * will remain in use. + *

Location-based detection is dependent on the current user and their settings. The device + * retains at most one geolocation suggestion. Generally, use of a device's location is dependent on + * the user's "location toggle", but even when that is enabled the user may choose to enable / + * disable the use of location for device time zone detection. If the current user changes to one + * that does not have location-based detection enabled, or the user turns off the location-based + * detection, then the strategy will be sent an event that clears the latest suggestion. Devices + * that lose their location fix must have an empty suggestion submitted in order to "withdraw" their + * previous suggestion otherwise it will remain in use. * *

The strategy uses only one algorithm at a time and does not attempt consensus even when * more than one is available on a device. This "use only one" behavior is deliberate as different @@ -72,25 +72,27 @@ import android.util.IndentingPrintWriter; * users enter areas without the necessary signals. Ultimately, with no perfect algorithm available, * the user is left to choose which algorithm works best for their circumstances. * - *

When geolocation detection is supported and enabled, in certain circumstances, such as during - * international travel, it makes sense to prioritize speed of detection via telephony (when - * available) Vs waiting for the geolocation algorithm to reach certainty. Geolocation detection can - * sometimes be slow to get a location fix and can require network connectivity (which cannot be - * assumed when users are travelling) for server-assisted location detection or time zone lookup. - * Therefore, as a restricted form of prioritization between geolocation and telephony algorithms, - * the strategy provides "telephony fallback" behavior, which can be set to "supported" via device - * config. Fallback mode is toggled on at runtime via {@link #enableTelephonyTimeZoneFallback()} in - * response to signals outside of the scope of this class. Telephony fallback allows the use of - * telephony suggestions to help with faster detection but only until geolocation detection - * provides a concrete, "certain" suggestion. After geolocation has made the first certain - * suggestion, telephony fallback is disabled until the next call to {@link - * #enableTelephonyTimeZoneFallback()}. + *

When the location detection algorithm is supported and enabled, in certain circumstances, such + * as during international travel, it makes sense to prioritize speed of detection via telephony + * (when available) Vs waiting for the location-based detection algorithm to reach certainty. + * Location-based detection can sometimes be slow to get a location fix and can require network + * connectivity (which cannot be assumed when users are travelling) for server-assisted location + * detection or time zone lookup. Therefore, as a restricted form of prioritization between location + * and telephony algorithms, the strategy provides "telephony fallback mode" behavior, which can be + * set to "supported" via device config. Fallback mode is entered at runtime in response to signals + * from outside of the strategy, e.g. from a call to {@link + * #enableTelephonyTimeZoneFallback(String)}, or from information in the latest {@link + * LocationAlgorithmEvent}. For telephony fallback mode to actually use a telephony suggestion, the + * location algorithm must report it is uncertain. Telephony fallback allows the use of + * telephony suggestions to help with faster detection but only until the location algorithm + * provides a concrete, "certain" suggestion. After the location algorithm has made a certain + * suggestion, telephony fallback mode is disabled. * *

Threading: * *

Implementations of this class must be thread-safe as calls calls like {@link * #generateMetricsState()} and {@link #dump(IndentingPrintWriter, String[])} may be called on - * differents thread concurrently with other operations. + * different threads concurrently with other operations. * * @hide */ @@ -181,11 +183,11 @@ public interface TimeZoneDetectorStrategy extends Dumpable { void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion); /** - * Tells the strategy that it can fall back to telephony detection while geolocation detection - * remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)} can - * disable it again. See {@link TimeZoneDetectorStrategy} for details. + * Tells the strategy that it can fall back to telephony detection while the location detection + * algorithm remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)} + * can disable it again. See {@link TimeZoneDetectorStrategy} for details. */ - void enableTelephonyTimeZoneFallback(); + void enableTelephonyTimeZoneFallback(@NonNull String reason); /** Generates a state snapshot for metrics. */ @NonNull @@ -194,6 +196,6 @@ public interface TimeZoneDetectorStrategy extends Dumpable { /** Returns {@code true} if the device supports telephony time zone detection. */ boolean isTelephonyTimeZoneDetectionSupported(); - /** Returns {@code true} if the device supports geolocation time zone detection. */ + /** Returns {@code true} if the device supports location-based time zone detection. */ boolean isGeoTimeZoneDetectionSupported(); } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index fa811efcfec02..e0e3565e1b0b5 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -63,12 +63,8 @@ import java.util.Objects; public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy { /** - * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device configuration / settings - * / system properties. It can be faked for testing. - * - *

Note: Because the settings / system properties-derived values can currently be modified - * independently and from different threads (and processes!), their use is prone to race - * conditions. + * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device state besides that + * available from {@link #mServiceConfigAccessor}. It can be faked for testing. */ @VisibleForTesting public interface Environment { @@ -234,7 +230,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * allows). * *

This field is only actually used when telephony time zone fallback is supported, but the - * value is maintained even when it isn't supported as it can be turned on at any time via + * value is maintained even when it isn't supported as support can be turned on at any time via * server flags. The elapsed realtime when the mode last changed is used to help ordering * between fallback mode switches and suggestions. * @@ -421,10 +417,15 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat notifyStateChangeListenersAsynchronously(); } - // Update the mTelephonyTimeZoneFallbackEnabled state if needed: a certain suggestion - // will usually disable telephony fallback mode if it is currently enabled. - // TODO(b/236624675)Some provider status codes can be used to enable telephony fallback. - disableTelephonyFallbackIfNeeded(); + // Manage telephony fallback state. + if (event.getAlgorithmStatus().couldEnableTelephonyFallback()) { + // An event may trigger entry into telephony fallback mode if the status + // indicates the location algorithm cannot work and is likely to stay not working. + enableTelephonyTimeZoneFallback("handleLocationAlgorithmEvent(), event=" + event); + } else { + // A certain suggestion will exit telephony fallback mode. + disableTelephonyFallbackIfNeeded(); + } // Now perform auto time zone detection. The new event may be used to modify the time zone // setting. @@ -497,38 +498,41 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat } @Override - public synchronized void enableTelephonyTimeZoneFallback() { - // Only do any work if fallback is currently not enabled. + public synchronized void enableTelephonyTimeZoneFallback(@NonNull String reason) { + // Only do any work to enter fallback mode if fallback is currently not already enabled. if (!mTelephonyTimeZoneFallbackEnabled.getValue()) { ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal; final boolean fallbackEnabled = true; mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>( mEnvironment.elapsedRealtimeMillis(), fallbackEnabled); - String logMsg = "enableTelephonyTimeZoneFallbackMode: " - + " currentUserConfig=" + currentUserConfig - + ", mTelephonyTimeZoneFallbackEnabled=" - + mTelephonyTimeZoneFallbackEnabled; + String logMsg = "enableTelephonyTimeZoneFallback: " + + " reason=" + reason + + ", currentUserConfig=" + currentUserConfig + + ", mTelephonyTimeZoneFallbackEnabled=" + mTelephonyTimeZoneFallbackEnabled; logTimeZoneDebugInfo(logMsg); // mTelephonyTimeZoneFallbackEnabled and mLatestLocationAlgorithmEvent interact. - // If the latest event contains a "certain" geolocation suggestion, then the telephony - // fallback value needs to be considered after changing it. + // If the latest location algorithm event contains a "certain" geolocation suggestion, + // then the telephony fallback mode needs to be (re)considered after changing it. + // // With the way that the mTelephonyTimeZoneFallbackEnabled time is currently chosen // above, and the fact that geolocation suggestions should never have a time in the - // future, the following call will be a no-op, and telephony fallback will remain - // enabled. This comment / call is left as a reminder that it is possible for there to - // be a current, "certain" geolocation suggestion when this signal arrives and it is - // intentional that fallback stays enabled in this case. The choice to do this - // is mostly for symmetry WRT the case where fallback is enabled and an old "certain" - // geolocation is received; that would also leave telephony fallback enabled. - // This choice means that telephony fallback will remain enabled until a new "certain" - // geolocation suggestion is received. If, instead, the next geolocation is "uncertain", - // then telephony fallback will occur. + // future, the following call will usually be a no-op, and telephony fallback mode will + // remain enabled. This comment / call is left as a reminder that it is possible in some + // cases for there to be a current, "certain" geolocation suggestion when an attempt is + // made to enable telephony fallback mode and it is intentional that fallback mode stays + // enabled in this case. The choice to do this is mostly for symmetry WRT the case where + // fallback is enabled and then an old "certain" geolocation suggestion is received; + // that would also leave telephony fallback mode enabled. + // + // This choice means that telephony fallback mode remains enabled if there is an + // existing "certain" suggestion until a new "certain" geolocation suggestion is + // received. If, instead, the next geolocation suggestion is "uncertain", then telephony + // fallback, i.e. the use of a telephony suggestion, will actually occur. disableTelephonyFallbackIfNeeded(); if (currentUserConfig.isTelephonyFallbackSupported()) { - String reason = "enableTelephonyTimeZoneFallbackMode"; doAutoTimeZoneDetection(currentUserConfig, reason); } } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java index bcdc65c19330d..1e72369ac3a6e 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java @@ -111,7 +111,7 @@ public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { } @Override - public void enableTelephonyTimeZoneFallback() { + public void enableTelephonyTimeZoneFallback(String reason) { throw new UnsupportedOperationException(); } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index 74efdb5d6d989..1c014d19d7767 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -29,6 +29,9 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYP import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_UNKNOWN; +import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW; @@ -65,6 +68,7 @@ import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType; import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality; import android.os.HandlerThread; +import android.service.timezone.TimeZoneProviderStatus; import com.android.server.SystemTimeZone.TimeZoneConfidence; import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion; @@ -1148,7 +1152,7 @@ public class TimeZoneDetectorStrategyImplTest { } @Test - public void testTelephonyFallback() { + public void testTelephonyFallback_enableTelephonyTimeZoneFallbackCalled() { ConfigurationInternal config = new ConfigurationInternal.Builder( CONFIG_AUTO_ENABLED_GEO_ENABLED) .setTelephonyFallbackSupported(true) @@ -1178,19 +1182,21 @@ public class TimeZoneDetectorStrategyImplTest { // Receiving an "uncertain" geolocation suggestion should have no effect. { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(true); } // Receiving a "certain" geolocation suggestion should disable telephony fallback mode. { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createCertainLocationAlgorithmEvent("Europe/London"); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneChangedAndReset(locationAlgorithmEvent) .verifyTelephonyFallbackIsEnabled(false); } @@ -1214,17 +1220,19 @@ public class TimeZoneDetectorStrategyImplTest { // Geolocation suggestions should continue to be used as normal (previous telephony // suggestions are not used, even when the geolocation suggestion is uncertain). { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent certainLocationAlgorithmEvent = createCertainLocationAlgorithmEvent("Europe/Rome"); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent) .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent) .verifyTelephonyFallbackIsEnabled(false); + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent uncertainLocationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(false); @@ -1246,19 +1254,21 @@ public class TimeZoneDetectorStrategyImplTest { // Make the geolocation algorithm uncertain. { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneChangedAndReset(lastTelephonySuggestion) .verifyTelephonyFallbackIsEnabled(true); } // Make the geolocation algorithm certain, disabling telephony fallback. { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createCertainLocationAlgorithmEvent("Europe/Lisbon"); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneChangedAndReset(locationAlgorithmEvent) .verifyTelephonyFallbackIsEnabled(false); @@ -1267,9 +1277,10 @@ public class TimeZoneDetectorStrategyImplTest { // Demonstrate what happens when geolocation is uncertain when telephony fallback is // enabled. { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(false) .simulateEnableTelephonyFallback() @@ -1278,6 +1289,132 @@ public class TimeZoneDetectorStrategyImplTest { } } + @Test + public void testTelephonyFallback_locationAlgorithmEventSuggestsFallback() { + ConfigurationInternal config = new ConfigurationInternal.Builder( + CONFIG_AUTO_ENABLED_GEO_ENABLED) + .setTelephonyFallbackSupported(true) + .build(); + + Script script = new Script() + .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) + .simulateConfigurationInternalChange(config) + .resetConfigurationTracking(); + + // Confirm initial state is as expected. + script.verifyTelephonyFallbackIsEnabled(true) + .verifyTimeZoneNotChanged(); + + // Although geolocation detection is enabled, telephony fallback should be used initially + // and until a suitable "certain" geolocation suggestion is received. + { + TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion( + SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, + "Europe/Paris"); + script.simulateIncrementClock() + .simulateTelephonyTimeZoneSuggestion(telephonySuggestion) + .verifyTimeZoneChangedAndReset(telephonySuggestion) + .verifyTelephonyFallbackIsEnabled(true); + } + + // Receiving an "uncertain" geolocation suggestion without a status should have no effect. + { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(true); + } + + // Receiving a "certain" geolocation suggestion should disable telephony fallback mode. + { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + LocationAlgorithmEvent locationAlgorithmEvent = + createCertainLocationAlgorithmEvent("Europe/London"); + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneChangedAndReset(locationAlgorithmEvent) + .verifyTelephonyFallbackIsEnabled(false); + } + + // Used to record the last telephony suggestion received, which will be used when fallback + // takes place. + TelephonyTimeZoneSuggestion lastTelephonySuggestion; + + // Telephony suggestions should now be ignored and geolocation detection is "in control". + { + TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion( + SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, + "Europe/Berlin"); + script.simulateIncrementClock() + .simulateTelephonyTimeZoneSuggestion(telephonySuggestion) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(false); + lastTelephonySuggestion = telephonySuggestion; + } + + // Geolocation suggestions should continue to be used as normal (previous telephony + // suggestions are not used, even when the geolocation suggestion is uncertain). + { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + LocationAlgorithmEvent certainLocationAlgorithmEvent = + createCertainLocationAlgorithmEvent("Europe/Rome"); + script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent) + .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent) + .verifyTelephonyFallbackIsEnabled(false); + + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + LocationAlgorithmEvent uncertainLocationAlgorithmEvent = + createUncertainLocationAlgorithmEvent(); + script.simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(false); + + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + LocationAlgorithmEvent certainLocationAlgorithmEvent2 = + createCertainLocationAlgorithmEvent("Europe/Rome"); + script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent2) + // No change needed, device will already be set to Europe/Rome. + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(false); + } + + // Enable telephony fallback via a LocationAlgorithmEvent containing an "uncertain" + // suggestion. + { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + TimeZoneProviderStatus primaryProviderReportedStatus = + new TimeZoneProviderStatus.Builder() + .setLocationDetectionDependencyStatus( + DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS) + .setConnectivityDependencyStatus(DEPENDENCY_STATUS_UNKNOWN) + .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_UNKNOWN) + .build(); + LocationAlgorithmEvent uncertainEventBlockedBySettings = + createUncertainLocationAlgorithmEvent(primaryProviderReportedStatus); + script.simulateLocationAlgorithmEvent(uncertainEventBlockedBySettings) + .verifyTimeZoneChangedAndReset(lastTelephonySuggestion) + .verifyTelephonyFallbackIsEnabled(true); + } + + // Make the geolocation algorithm certain, disabling telephony fallback. + { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + LocationAlgorithmEvent locationAlgorithmEvent = + createCertainLocationAlgorithmEvent("Europe/Lisbon"); + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneChangedAndReset(locationAlgorithmEvent) + .verifyTelephonyFallbackIsEnabled(false); + } + } + @Test public void testTelephonyFallback_noTelephonySuggestionToFallBackTo() { ConfigurationInternal config = new ConfigurationInternal.Builder( @@ -1297,9 +1434,10 @@ public class TimeZoneDetectorStrategyImplTest { // Receiving an "uncertain" geolocation suggestion should have no effect. { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(true); } @@ -1307,9 +1445,10 @@ public class TimeZoneDetectorStrategyImplTest { // Make an uncertain geolocation suggestion, there is no telephony suggestion to fall back // to { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(true); } @@ -1319,16 +1458,18 @@ public class TimeZoneDetectorStrategyImplTest { // Geolocation suggestions should continue to be used as normal (previous telephony // suggestions are not used, even when the geolocation suggestion is uncertain). { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent certainEvent = createCertainLocationAlgorithmEvent("Europe/Rome"); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(certainEvent) + script.simulateLocationAlgorithmEvent(certainEvent) .verifyTimeZoneChangedAndReset(certainEvent) .verifyTelephonyFallbackIsEnabled(false); + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent uncertainEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(uncertainEvent) + script.simulateLocationAlgorithmEvent(uncertainEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(false); @@ -1549,9 +1690,16 @@ public class TimeZoneDetectorStrategyImplTest { } private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent() { + TimeZoneProviderStatus primaryProviderReportedStatus = null; + return createUncertainLocationAlgorithmEvent(primaryProviderReportedStatus); + } + + private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent( + TimeZoneProviderStatus primaryProviderReportedStatus) { GeolocationTimeZoneSuggestion suggestion = createUncertainGeolocationSuggestion(); LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus( - DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_UNCERTAIN, null, + DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, primaryProviderReportedStatus, PROVIDER_STATUS_NOT_PRESENT, null); LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion); event.addDebugInfo("Test uncertain event"); @@ -1744,11 +1892,12 @@ public class TimeZoneDetectorStrategyImplTest { } /** - * Simulates the time zone detection strategty receiving a signal that allows it to do + * Simulates the time zone detection strategy receiving a signal that allows it to do * telephony fallback. */ Script simulateEnableTelephonyFallback() { - mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(); + mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback( + "simulateEnableTelephonyFallback()"); return this; }