diff --git a/res/values/strings.xml b/res/values/strings.xml index fa1dd362cd3..e61dab05b8e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -426,6 +426,8 @@ Set time automatically Set automatically + + Location will be used for setting the time zone when this toggle is on Use locale default @@ -2990,6 +2992,16 @@ Use location + + Cannot set the time zone automatically + + + + Location or Location Services are off + + + + Device location needed @@ -2999,6 +3011,8 @@ Location settings + + Fix this Cancel diff --git a/res/xml/date_time_prefs.xml b/res/xml/date_time_prefs.xml index e3d0a7e2e23..593c4285786 100644 --- a/res/xml/date_time_prefs.xml +++ b/res/xml/date_time_prefs.xml @@ -47,6 +47,11 @@ android:title="@string/zone_auto_title" settings:userRestriction="no_config_date_time"/> + + launchLocationSettings()); + } + + @Override + public int getAvailabilityStatus() { + // Checks that the summary is non-empty as most status strings are optional. If a status + // string is empty, we ignore the status. + if (!TextUtils.isEmpty(getSummary())) { + return AVAILABLE_UNSEARCHABLE; + } + return CONDITIONALLY_UNAVAILABLE; + } + + private void launchLocationSettings() { + new SubSettingLauncher(mContext) + .setDestination(LocationSettings.class.getName()) + .setSourceMetricsCategory(getMetricsCategory()) + .launch(); + } + + // Android has up to two location time zone providers (LTZPs) which can + // (optionally) report their status along several dimensions. Typically there is + // only one LTZP on a device, the primary. The UI here only reports status for one + // LTZP. This UI logic prioritizes the primary if there is a "bad" status for both. + @Nullable + private TimeZoneProviderStatus getLtzpStatus() { + LocationTimeZoneAlgorithmStatus status = + mTimeManager.getTimeZoneCapabilitiesAndConfig().getDetectorStatus() + .getLocationTimeZoneAlgorithmStatus(); + TimeZoneProviderStatus primary = status.getPrimaryProviderReportedStatus(); + TimeZoneProviderStatus secondary = status.getSecondaryProviderReportedStatus(); + if (primary == null && secondary == null) { + return null; + } + + if (primary == null) { + return secondary; + } else if (secondary == null) { + return primary; + } + + if (status.getPrimaryProviderStatus() + != LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN) { + return secondary; + } + + return primary; + } + + @Override + public void onChange() { + if (mPreference != null) { + mPreference.setVisible(getAvailabilityStatus() == AVAILABLE_UNSEARCHABLE); + refreshSummary(mPreference); + } + } + + @Override + public CharSequence getSummary() { + boolean locationEnabled = mLocationManager.isLocationEnabled(); + final TimeZoneDetectorStatus detectorStatus = + mTimeManager.getTimeZoneCapabilitiesAndConfig().getDetectorStatus(); + + if (!locationEnabled && hasLocationTimeZoneNoTelephonyFallback(detectorStatus)) { + return mContext.getResources().getString( + R.string.location_time_zone_detection_status_summary_blocked_by_settings); + } + + TimeZoneProviderStatus ltzpStatus = getLtzpStatus(); + if (ltzpStatus == null) { + return ""; + } + + int status = ltzpStatus.getLocationDetectionDependencyStatus(); + + if (status == TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT) { + return mContext.getResources().getString( + R.string.location_time_zone_detection_status_summary_blocked_by_environment); + } + if (status == TimeZoneProviderStatus.DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS) { + return mContext.getResources().getString( + R.string.location_time_zone_detection_status_summary_degraded_by_settings); + } + if (status == TimeZoneProviderStatus.DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE) { + return mContext.getResources().getString( + R.string.location_time_zone_detection_status_summary_temporarily_unavailable); + } + if (status == TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS) { + return mContext.getResources().getString( + R.string.location_time_zone_detection_status_summary_blocked_by_settings); + } + + return ""; + } + + /** package */ + static boolean hasLocationTimeZoneNoTelephonyFallback(TimeZoneDetectorStatus detectorStatus) { + final LocationTimeZoneAlgorithmStatus locationStatus = + detectorStatus.getLocationTimeZoneAlgorithmStatus(); + final TelephonyTimeZoneAlgorithmStatus telephonyStatus = + detectorStatus.getTelephonyTimeZoneAlgorithmStatus(); + return telephonyStatus.getAlgorithmStatus() + == DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED + && locationStatus.getStatus() + != DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED; + } +} diff --git a/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java index 61fca2c2a13..41aca50f1af 100644 --- a/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java @@ -16,6 +16,7 @@ package com.android.settings.datetime; +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.DETECTOR_STATUS_RUNNING; import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT; @@ -36,10 +37,13 @@ import android.app.time.TimeZoneCapabilitiesAndConfig; import android.app.time.TimeZoneConfiguration; import android.app.time.TimeZoneDetectorStatus; import android.content.Context; +import android.location.LocationManager; import android.os.UserHandle; import androidx.preference.Preference; +import com.android.settings.R; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -58,6 +62,8 @@ public class AutoTimeZonePreferenceControllerTest { private Preference mPreference; @Mock private TimeManager mTimeManager; + @Mock + private LocationManager mLocationManager; @Before public void setUp() { @@ -67,6 +73,9 @@ public class AutoTimeZonePreferenceControllerTest { mPreference = new Preference(mContext); when(mContext.getSystemService(TimeManager.class)).thenReturn(mTimeManager); + when(mContext.getSystemService(LocationManager.class)).thenReturn(mLocationManager); + + when(mLocationManager.isLocationEnabled()).thenReturn(true); } @Test @@ -221,10 +230,35 @@ public class AutoTimeZonePreferenceControllerTest { assertThat(controller.isEnabled()).isFalse(); } + @Test + public void getSummary() { + AutoTimeZonePreferenceController controller = new AutoTimeZonePreferenceController( + mContext, mCallback, false /* fromSUW */); + + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig( + /* autoSupported= */true, /* autoEnabled= */true, /* telephonySupported= */ + true); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + when(mTimeManager.updateTimeZoneConfiguration(Mockito.any())).thenReturn(true); + + assertThat(controller.getSummary()).isEqualTo(""); + + capabilitiesAndConfig = createCapabilitiesAndConfig( + /* autoSupported= */true, /* autoEnabled= */true, /* telephonySupported= */ + false); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + when(mTimeManager.updateTimeZoneConfiguration(Mockito.any())).thenReturn(true); + + assertThat(controller.getSummary()).isEqualTo( + mContext.getString(R.string.auto_zone_requires_location_summary)); + } + private static TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig( - boolean autoSupported, boolean autoEnabled) { + boolean autoSupported, boolean autoEnabled, boolean telephonySupported) { TimeZoneDetectorStatus status = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING, - new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING), + new TelephonyTimeZoneAlgorithmStatus( + telephonySupported ? DETECTION_ALGORITHM_STATUS_RUNNING + : DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED), new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_PRESENT, null)); @@ -242,4 +276,9 @@ public class AutoTimeZonePreferenceControllerTest { .build(); return new TimeZoneCapabilitiesAndConfig(status, capabilities, config); } + + private static TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig( + boolean autoSupported, boolean autoEnabled) { + return createCapabilitiesAndConfig(autoSupported, autoEnabled, false); + } } diff --git a/tests/robotests/src/com/android/settings/datetime/LocationProviderStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/LocationProviderStatusPreferenceControllerTest.java new file mode 100644 index 00000000000..4f9f1cc2df1 --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/LocationProviderStatusPreferenceControllerTest.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.datetime; + +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN; +import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.time.Capabilities; +import android.app.time.LocationTimeZoneAlgorithmStatus; +import android.app.time.TelephonyTimeZoneAlgorithmStatus; +import android.app.time.TimeManager; +import android.app.time.TimeZoneCapabilities; +import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneConfiguration; +import android.app.time.TimeZoneDetectorStatus; +import android.content.Context; +import android.location.LocationManager; +import android.os.UserHandle; +import android.service.timezone.TimeZoneProviderStatus; + +import androidx.annotation.Nullable; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class LocationProviderStatusPreferenceControllerTest { + + private Context mContext; + @Mock + private TimeManager mTimeManager; + @Mock + private LocationManager mLocationManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + + when(mContext.getSystemService(TimeManager.class)).thenReturn(mTimeManager); + when(mContext.getSystemService(LocationManager.class)).thenReturn(mLocationManager); + when(mLocationManager.isLocationEnabled()).thenReturn(true); + when(mContext.getString( + R.string.location_time_zone_detection_status_summary_blocked_by_settings)) + .thenReturn("BBS"); + } + + @Test + public void testCapabilityStatus() { + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(false, + DEPENDENCY_STATUS_OK, DEPENDENCY_STATUS_OK); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + LocationProviderStatusPreferenceController controller = + new LocationProviderStatusPreferenceController(mContext, "LPSPC"); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + + capabilitiesAndConfig = createCapabilitiesAndConfig(true, DEPENDENCY_STATUS_OK, + DEPENDENCY_STATUS_OK); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + + capabilitiesAndConfig = createCapabilitiesAndConfig(false, + DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, DEPENDENCY_STATUS_OK); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE_UNSEARCHABLE); + + capabilitiesAndConfig = createCapabilitiesAndConfig(true, + DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, DEPENDENCY_STATUS_OK); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE_UNSEARCHABLE); + } + + @Test + public void testProviderStatus_primaryCertain() { + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(false, + DEPENDENCY_STATUS_OK, DEPENDENCY_STATUS_OK); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + LocationProviderStatusPreferenceController controller = + new LocationProviderStatusPreferenceController(mContext, "LPSPC"); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + + capabilitiesAndConfig = createCapabilitiesAndConfig(false, + DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, DEPENDENCY_STATUS_OK); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE_UNSEARCHABLE); + + capabilitiesAndConfig = createCapabilitiesAndConfig(false, DEPENDENCY_STATUS_OK, + DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + + capabilitiesAndConfig = createCapabilitiesAndConfig(false, DEPENDENCY_STATUS_OK, + DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void testProviderStatus_primaryUncertain() { + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(false, + DEPENDENCY_STATUS_OK, DEPENDENCY_STATUS_OK, PROVIDER_STATUS_IS_CERTAIN); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + LocationProviderStatusPreferenceController controller = + new LocationProviderStatusPreferenceController(mContext, "LPSPC"); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + + capabilitiesAndConfig = createCapabilitiesAndConfig(false, + DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, DEPENDENCY_STATUS_OK, + PROVIDER_STATUS_IS_CERTAIN); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE_UNSEARCHABLE); + + capabilitiesAndConfig = createCapabilitiesAndConfig(false, DEPENDENCY_STATUS_OK, + DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, PROVIDER_STATUS_IS_UNCERTAIN); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE_UNSEARCHABLE); + + capabilitiesAndConfig = createCapabilitiesAndConfig(false, DEPENDENCY_STATUS_OK, + DEPENDENCY_STATUS_OK, PROVIDER_STATUS_IS_UNCERTAIN); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void testProviderStatus_nullProviders() { + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(false, + null, null); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + LocationProviderStatusPreferenceController controller = + new LocationProviderStatusPreferenceController(mContext, "LPSPC"); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + + capabilitiesAndConfig = createCapabilitiesAndConfig(false, + DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, null); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE_UNSEARCHABLE); + + capabilitiesAndConfig = createCapabilitiesAndConfig(false, DEPENDENCY_STATUS_OK, null); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + + capabilitiesAndConfig = createCapabilitiesAndConfig(false, null, + DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(controller.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE_UNSEARCHABLE); + } + + private TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(boolean capabilitySupported, + @Nullable Integer primary, @Nullable Integer secondary) { + return createCapabilitiesAndConfig(capabilitySupported, primary, secondary, + PROVIDER_STATUS_IS_CERTAIN); + } + + private TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(boolean capabilitySupported, + @Nullable Integer primary, @Nullable Integer secondary, int primaryProviderStatus) { + TimeZoneDetectorStatus status = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING, + new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING), + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + primaryProviderStatus, primary != null + ? new TimeZoneProviderStatus.Builder().setLocationDetectionDependencyStatus( + primary).build() : null, PROVIDER_STATUS_IS_CERTAIN, secondary != null + ? new TimeZoneProviderStatus.Builder().setLocationDetectionDependencyStatus( + secondary).build() : null)); + + TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder( + UserHandle.SYSTEM).setConfigureAutoDetectionEnabledCapability( + Capabilities.CAPABILITY_POSSESSED).setConfigureGeoDetectionEnabledCapability( + capabilitySupported ? Capabilities.CAPABILITY_POSSESSED + : Capabilities.CAPABILITY_NOT_SUPPORTED).setSetManualTimeZoneCapability( + Capabilities.CAPABILITY_POSSESSED).build(); + + return new TimeZoneCapabilitiesAndConfig(status, capabilities, + new TimeZoneConfiguration.Builder().build()); + } +}