From 4a6663c10a8d80405a48a4a884cc7438ae49b799 Mon Sep 17 00:00:00 2001 From: Neil Fuller Date: Thu, 10 Sep 2020 15:06:39 +0100 Subject: [PATCH] Changes to location settings for geotz This change adds a new entry for geolocation-based time zone detection (shorthand: "geotz") to the location settings. The UI code uses information retrieved via the TimeManager API to keep the conditional logic in one place, i.e. to avoid the UI needing to know too much about the various conditions that affect the availability / applicability of the toggle. Screen variations / conditions / other info: Location settings screen: 1) The entry only shows up on a device at all if geotz detection is enabled for the device (currently a compile time setting, off by default). 2) The new entry is hidden on the screen when the user first opens it. To see it, the user has to expand "Advanced". The setting is in the shared (non-GMS) section of the screen. 3) When visible, the entry has a summary that shows the current status: either a reason why the setting is not applicable, or the user's current setting value. 4) Selecting the entry opens a sub-screen. The code included here distinguishes between different cases with the summary message (i.e. the text underneath the entry). See strings.xml for the placeholder text. Some strings will not show up currently, but are included in case the rules change as they are valid possibilities from the TimeManager API. The same changes have been made for the "personal" tab when a work profile is enabled. Location Time Zone Detection settings screen: 1) The toggle is always enabled and always shows the user's current configuration setting. All manual tests run with the geotz feature enabled with: adb shell setprop \ persist.sys.location_time_zone_detection_feature_enabled 1 Bug: 152746236 Test: Manual: build / boot / toggle switch in SettingsUI / inspect output of adb shell dumpsys time_zone_detector Test: Manual: build / boot / install TestDPC / toggle switch in work profile in SettingsUI / inspect output of adb shell dumpsys time_zone_detector Test: m -j30 RunSettingsRoboTests ROBOTEST_FILTER="com.android.settings.location.TimeZoneDetectionPreferenceControllerTest" Test: m -j30 RunSettingsRoboTests ROBOTEST_FILTER="com.android.settings.location.LocationTimeZoneDetectionPreferenceControllerTest" Test: m -j30 RunSettingsRoboTests ROBOTEST_FILTER="com.android.settings.location.TimeZoneDetectionSettingsTest" Change-Id: Ia853d30bc54a113000b30f48776418f262d98358 --- res/values/strings.xml | 26 +++ res/xml/location_settings.xml | 7 + res/xml/location_settings_personal.xml | 7 + res/xml/location_time_zone_detection.xml | 25 +++ .../location/LocationPersonalSettings.java | 2 + .../settings/location/LocationSettings.java | 1 + ...TimeZoneDetectionPreferenceController.java | 163 ++++++++++++++++ ...TimeZoneDetectionPreferenceController.java | 97 ++++++++++ .../location/TimeZoneDetectionSettings.java | 75 ++++++++ ...ZoneDetectionPreferenceControllerTest.java | 121 ++++++++++++ ...ZoneDetectionPreferenceControllerTest.java | 174 ++++++++++++++++++ .../TimeZoneDetectionSettingsTest.java | 53 ++++++ 12 files changed, 751 insertions(+) create mode 100644 res/xml/location_time_zone_detection.xml create mode 100644 src/com/android/settings/location/LocationTimeZoneDetectionPreferenceController.java create mode 100644 src/com/android/settings/location/TimeZoneDetectionPreferenceController.java create mode 100644 src/com/android/settings/location/TimeZoneDetectionSettings.java create mode 100644 tests/robotests/src/com/android/settings/location/LocationTimeZoneDetectionPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/location/TimeZoneDetectionPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/location/TimeZoneDetectionSettingsTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index c3e5d8172d8..d53b0790ec2 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4002,6 +4002,32 @@ Location services for work + + Location time zone detection + + Location time zone detection + + Allows the device\u2019s location to be used to detect the current time zone. Other location settings such as Wi\u2011Fi scanning can affect the accuracy of time zone detection. + + + On + + Off + + Automatic time zone detection is disabled + + Location time zone detection is disabled + + Location time zone detection is not supported + + Location time zone detection changes are not allowed + Wi\u2011Fi & mobile network location diff --git a/res/xml/location_settings.xml b/res/xml/location_settings.xml index fb03f4c7f3d..80f4973539b 100644 --- a/res/xml/location_settings.xml +++ b/res/xml/location_settings.xml @@ -65,6 +65,13 @@ settings:forWork="true" settings:useAdminDisabledSummary="true"/> + + + + + + + + + + + + + diff --git a/src/com/android/settings/location/LocationPersonalSettings.java b/src/com/android/settings/location/LocationPersonalSettings.java index 92796a4bd66..3f90896fba2 100644 --- a/src/com/android/settings/location/LocationPersonalSettings.java +++ b/src/com/android/settings/location/LocationPersonalSettings.java @@ -58,6 +58,8 @@ public class LocationPersonalSettings extends DashboardFragment { RecentLocationRequestPreferenceController.class); controller.init(this); controller.setProfileType(profileType); + + use(LocationTimeZoneDetectionPreferenceController.class); } @Override diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index 43918111cca..248620b3fef 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -88,6 +88,7 @@ public class LocationSettings extends DashboardFragment { use(LocationFooterPreferenceController.class).init(this); use(LocationForWorkPreferenceController.class).init(this); use(LocationServiceForWorkPreferenceController.class).init(this); + use(LocationTimeZoneDetectionPreferenceController.class); } @Override diff --git a/src/com/android/settings/location/LocationTimeZoneDetectionPreferenceController.java b/src/com/android/settings/location/LocationTimeZoneDetectionPreferenceController.java new file mode 100644 index 00000000000..a16c9bbd792 --- /dev/null +++ b/src/com/android/settings/location/LocationTimeZoneDetectionPreferenceController.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2020 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.location; + +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED; + +import android.app.time.TimeManager; +import android.app.time.TimeZoneCapabilities; +import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneConfiguration; +import android.content.Context; +import android.location.LocationManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +import java.util.concurrent.Executor; + +/** + * The controller for the "location time zone detection" entry in the Location settings + * screen. + */ +public class LocationTimeZoneDetectionPreferenceController + extends BasePreferenceController + implements LifecycleObserver, OnStart, OnStop, TimeManager.TimeZoneDetectorListener { + + private final TimeManager mTimeManager; + private final LocationManager mLocationManager; + private TimeZoneCapabilitiesAndConfig mTimeZoneCapabilitiesAndConfig; + private Preference mPreference; + + public LocationTimeZoneDetectionPreferenceController(Context context, String key) { + super(context, key); + mTimeManager = context.getSystemService(TimeManager.class); + mLocationManager = context.getSystemService(LocationManager.class); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public void onStart() { + // Register for updates to the user's time zone capabilities or configuration which could + // require UI changes. + Executor mainExecutor = mContext.getMainExecutor(); + mTimeManager.addTimeZoneDetectorListener(mainExecutor, this); + // Setup the initial state of the summary. + refreshUi(); + } + + @Override + public void onStop() { + mTimeManager.removeTimeZoneDetectorListener(this); + } + + @Override + public int getAvailabilityStatus() { + TimeZoneCapabilities timeZoneCapabilities = + getTimeZoneCapabilitiesAndConfig(/* forceRefresh= */ false).getCapabilities(); + int capability = timeZoneCapabilities.getConfigureGeoDetectionEnabledCapability(); + + // The preference only has two states: present and not present. The preference is never + // present but disabled. + if (capability == CAPABILITY_NOT_SUPPORTED || capability == CAPABILITY_NOT_ALLOWED) { + return UNSUPPORTED_ON_DEVICE; + } else if (capability == CAPABILITY_NOT_APPLICABLE || capability == CAPABILITY_POSSESSED) { + return AVAILABLE; + } else { + throw new IllegalStateException("Unknown capability=" + capability); + } + } + + @Override + public CharSequence getSummary() { + TimeZoneCapabilitiesAndConfig timeZoneCapabilitiesAndConfig = + getTimeZoneCapabilitiesAndConfig(/* forceRefresh= */ false); + TimeZoneCapabilities capabilities = timeZoneCapabilitiesAndConfig.getCapabilities(); + int configureGeoDetectionEnabledCapability = + capabilities.getConfigureGeoDetectionEnabledCapability(); + TimeZoneConfiguration configuration = timeZoneCapabilitiesAndConfig.getConfiguration(); + + int summaryResId; + if (configureGeoDetectionEnabledCapability == CAPABILITY_NOT_SUPPORTED) { + // The preference should not be visible, but text is referenced in case this changes. + summaryResId = R.string.location_time_zone_detection_not_supported; + } else if (configureGeoDetectionEnabledCapability == CAPABILITY_NOT_ALLOWED) { + // The preference should not be visible, but text is referenced in case this changes. + summaryResId = R.string.location_time_zone_detection_not_allowed; + } else if (configureGeoDetectionEnabledCapability == CAPABILITY_NOT_APPLICABLE) { + // The TimeZoneCapabilities deliberately doesn't provide information about why the user + // doesn't have the capability, but the user's "location enabled" being off and the + // global automatic detection setting will always be considered overriding reasons why + // location time zone detection cannot be used. + if (!mLocationManager.isLocationEnabled()) { + summaryResId = R.string.location_app_permission_summary_location_off; + } else if (!configuration.isAutoDetectionEnabled()) { + summaryResId = R.string.location_time_zone_detection_auto_is_off; + } else { + // This is in case there are other reasons in future why location time zone + // detection is not applicable. + summaryResId = R.string.location_time_zone_detection_not_applicable; + } + } else if (configureGeoDetectionEnabledCapability == CAPABILITY_POSSESSED) { + boolean isGeoDetectionEnabled = configuration.isGeoDetectionEnabled(); + summaryResId = isGeoDetectionEnabled + ? R.string.location_time_zone_detection_on + : R.string.location_time_zone_detection_off; + } else { + // This is unexpected: getAvailabilityStatus() should ensure that the UI element isn't + // even shown for known cases, or the capability is unknown. + throw new IllegalStateException("Unexpected configureGeoDetectionEnabledCapability=" + + configureGeoDetectionEnabledCapability); + } + return mContext.getString(summaryResId); + } + + @Override + public void onChange() { + refreshUi(); + } + + private void refreshUi() { + // Force a refresh of cached user capabilities and config before refreshing the summary. + getTimeZoneCapabilitiesAndConfig(/* forceRefresh= */ true); + refreshSummary(mPreference); + } + + /** + * Returns the current user capabilities and configuration. {@code forceRefresh} can be {@code + * true} to discard any cached copy. + */ + private TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig(boolean forceRefresh) { + if (forceRefresh || mTimeZoneCapabilitiesAndConfig == null) { + mTimeZoneCapabilitiesAndConfig = mTimeManager.getTimeZoneCapabilitiesAndConfig(); + } + return mTimeZoneCapabilitiesAndConfig; + } +} diff --git a/src/com/android/settings/location/TimeZoneDetectionPreferenceController.java b/src/com/android/settings/location/TimeZoneDetectionPreferenceController.java new file mode 100644 index 00000000000..946376d77fa --- /dev/null +++ b/src/com/android/settings/location/TimeZoneDetectionPreferenceController.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 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.location; + +import android.app.time.TimeManager; +import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneConfiguration; +import android.content.Context; +import android.text.TextUtils; + +import androidx.preference.Preference; +import androidx.preference.SwitchPreference; + +import com.android.settingslib.core.AbstractPreferenceController; + +/** + * The controller for the "location time zone detection" switch on the location time zone detection + * screen. + */ +public class TimeZoneDetectionPreferenceController extends AbstractPreferenceController { + + private static final String KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED = + "location_time_zone_detection_enabled"; + + private final TimeManager mTimeManager; + + public TimeZoneDetectionPreferenceController(Context context) { + super(context); + mTimeManager = context.getSystemService(TimeManager.class); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED; + } + + @Override + public void updateState(Preference preference) { + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + mTimeManager.getTimeZoneCapabilitiesAndConfig(); + setPreferenceUiState((SwitchPreference) preference, capabilitiesAndConfig); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (TextUtils.equals(preference.getKey(), KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED)) { + SwitchPreference switchPreference = (SwitchPreference) preference; + final boolean switchState = switchPreference.isChecked(); + + // Update the settings to match the UI. + TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder() + .setGeoDetectionEnabled(switchState) + .build(); + + // The return value is ignored, but the current state is read back below ensuring it + // does not matter. + mTimeManager.updateTimeZoneConfiguration(configuration); + + // Configure the UI preference state from the configuration. This means that even in the + // unlikely event that the update failed, the UI should reflect current settings. + setPreferenceUiState(switchPreference, mTimeManager.getTimeZoneCapabilitiesAndConfig()); + + return true; + } + return false; + } + + /** + * Sets the switch's checked state from the supplied {@link TimeZoneCapabilitiesAndConfig}. + */ + @android.annotation.UiThread + private void setPreferenceUiState(SwitchPreference preference, + TimeZoneCapabilitiesAndConfig timeZoneCapabilitiesAndConfig) { + TimeZoneConfiguration configuration = timeZoneCapabilitiesAndConfig.getConfiguration(); + boolean checked = configuration.isGeoDetectionEnabled(); + preference.setChecked(checked); + } +} diff --git a/src/com/android/settings/location/TimeZoneDetectionSettings.java b/src/com/android/settings/location/TimeZoneDetectionSettings.java new file mode 100644 index 00000000000..555d9d47092 --- /dev/null +++ b/src/com/android/settings/location/TimeZoneDetectionSettings.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 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.location; + +import android.app.settings.SettingsEnums; +import android.content.Context; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.search.SearchIndexable; + +import java.util.ArrayList; +import java.util.List; + +/** + * The controller for the "location time zone detection" screen. + */ +@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) +public class TimeZoneDetectionSettings extends DashboardFragment { + private static final String TAG = "LTZDetectionSettings"; + + @Override + public int getMetricsCategory() { + return SettingsEnums.LOCATION_TIME_ZONE_DETECTION; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.location_time_zone_detection; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected List createPreferenceControllers(Context context) { + return buildPreferenceControllers(context); + } + + private static List buildPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + controllers.add(new TimeZoneDetectionPreferenceController(context)); + return controllers; + } + + /** + * For Search. + */ + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.location_time_zone_detection) { + + @Override + public List createPreferenceControllers(Context + context) { + return buildPreferenceControllers(context); + } + }; +} diff --git a/tests/robotests/src/com/android/settings/location/LocationTimeZoneDetectionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationTimeZoneDetectionPreferenceControllerTest.java new file mode 100644 index 00000000000..03e684b6aa3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/LocationTimeZoneDetectionPreferenceControllerTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2018 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.location; + +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.time.TimeManager; +import android.app.time.TimeZoneCapabilities; +import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneConfiguration; +import android.content.Context; +import android.location.LocationManager; +import android.os.UserHandle; + +import com.android.settings.R; + +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 LocationTimeZoneDetectionPreferenceControllerTest { + @Mock + private TimeManager mTimeManager; + @Mock + private LocationManager mLocationManager; + private Context mContext; + private LocationTimeZoneDetectionPreferenceController mController; + + @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); + mController = new LocationTimeZoneDetectionPreferenceController(mContext, "key"); + } + + @Test + public void testLocationTimeZoneDetection_supported_shouldBeShown() { + TimeZoneCapabilities capabilities = + createTimeZoneCapabilities(/* geoDetectionSupported= */ true); + TimeZoneConfiguration configuration = createTimeZoneConfig(/* geoDetectionEnabled= */ true); + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + new TimeZoneCapabilitiesAndConfig(capabilities, configuration); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void testLocationTimeZoneDetection_unsupported_shouldNotBeShown() { + TimeZoneCapabilities capabilities = + createTimeZoneCapabilities(/* geoDetectionSupported= */ false); + TimeZoneConfiguration configuration = createTimeZoneConfig(/* geoDetectionEnabled= */ true); + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + new TimeZoneCapabilitiesAndConfig(capabilities, configuration); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(mController.isAvailable()).isFalse(); + } + + /** + * Tests that the summary is set in just one of many cases. Exhaustive testing would be brittle. + */ + @Test + public void testLocationTimeZoneDetection_summary_geoDetectionEnabled() { + TimeZoneCapabilities capabilities = + createTimeZoneCapabilities(/* geoDetectionSupported= */ true); + TimeZoneConfiguration configuration = createTimeZoneConfig(/* geoDetectionEnabled= */ true); + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + new TimeZoneCapabilitiesAndConfig(capabilities, configuration); + + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + assertThat(mController.getSummary()).isEqualTo( + mContext.getString(R.string.location_time_zone_detection_on)); + } + + private static TimeZoneCapabilities createTimeZoneCapabilities(boolean geoDetectionSupported) { + UserHandle arbitraryUserHandle = UserHandle.of(123); + int geoDetectionCapability = + geoDetectionSupported ? CAPABILITY_POSSESSED : CAPABILITY_NOT_SUPPORTED; + return new TimeZoneCapabilities.Builder(arbitraryUserHandle) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setConfigureGeoDetectionEnabledCapability(geoDetectionCapability) + .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_APPLICABLE) + .build(); + } + + private static TimeZoneConfiguration createTimeZoneConfig(boolean geoDetectionEnabled) { + return new TimeZoneConfiguration.Builder() + .setAutoDetectionEnabled(true) + .setGeoDetectionEnabled(geoDetectionEnabled) + .build(); + } +} diff --git a/tests/robotests/src/com/android/settings/location/TimeZoneDetectionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/TimeZoneDetectionPreferenceControllerTest.java new file mode 100644 index 00000000000..de6be5618cc --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/TimeZoneDetectionPreferenceControllerTest.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2017 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.location; + +import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE; +import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.time.TimeManager; +import android.app.time.TimeZoneCapabilities; +import android.app.time.TimeZoneCapabilitiesAndConfig; +import android.app.time.TimeZoneConfiguration; +import android.content.ContentResolver; +import android.content.Context; +import android.os.UserHandle; + +import androidx.preference.SwitchPreference; + +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 TimeZoneDetectionPreferenceControllerTest { + + @Mock + private SwitchPreference mPreference; + @Mock + private TimeManager mTimeManager; + + private Context mContext; + private ContentResolver mContentResolver; + private TimeZoneDetectionPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContentResolver = RuntimeEnvironment.application.getContentResolver(); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(TimeManager.class)).thenReturn(mTimeManager); + + mController = new TimeZoneDetectionPreferenceController(mContext); + when(mPreference.getKey()).thenReturn(mController.getPreferenceKey()); + } + + @Test + public void updateState_locationDetectionEnabled_shouldCheckPreference() { + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + createTimeZoneCapabilitiesAndConfig(/* geoDetectionEnabled= */ true); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(true); + } + + @Test + public void updateState_locationDetectionDisabled_shouldUncheckPreference() { + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + createTimeZoneCapabilitiesAndConfig(/* geoDetectionEnabled= */ false); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(false); + } + + @Test + public void handlePreferenceTreeClick_unchecked_shouldDisableGeoDetection() { + // getTimeZoneCapabilitiesAndConfig() is called after updateTimeZoneConfiguration() to + // obtain the new state. + boolean postUpdateResponseValue = false; + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createTimeZoneCapabilitiesAndConfig( + /* geoDetectionEnabled= */postUpdateResponseValue); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + // Simulate the UI being clicked. + boolean preferenceCheckedState = false; + when(mPreference.isChecked()).thenReturn(preferenceCheckedState); + mController.handlePreferenceTreeClick(mPreference); + + // Verify the TimeManager was updated with the UI value. + TimeZoneConfiguration expectedConfiguration = new TimeZoneConfiguration.Builder() + .setGeoDetectionEnabled(preferenceCheckedState) + .build(); + verify(mTimeManager).updateTimeZoneConfiguration(expectedConfiguration); + + // Confirm the UI state was reset using the getTimeZoneCapabilitiesAndConfig() response. + verify(mPreference).setChecked(postUpdateResponseValue); + } + + @Test + public void handlePreferenceTreeClick_checked_shouldEnableGeoDetection() { + // getTimeZoneCapabilitiesAndConfig() is called after updateTimeZoneConfiguration() to + // obtain the new state. + boolean postUpdateResponseValue = true; + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createTimeZoneCapabilitiesAndConfig( + /* geoDetectionEnabled= */ postUpdateResponseValue); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + // Simulate the UI being clicked. + boolean preferenceCheckedState = true; + when(mPreference.isChecked()).thenReturn(preferenceCheckedState); + mController.handlePreferenceTreeClick(mPreference); + + // Verify the TimeManager was updated with the UI value. + TimeZoneConfiguration expectedConfiguration = new TimeZoneConfiguration.Builder() + .setGeoDetectionEnabled(preferenceCheckedState) + .build(); + verify(mTimeManager).updateTimeZoneConfiguration(expectedConfiguration); + + // Confirm the UI state was reset using the getTimeZoneCapabilitiesAndConfig() response. + verify(mPreference).setChecked(postUpdateResponseValue); + } + + @Test + public void handlePreferenceTreeClick_checked_shouldEnableGeoDetection_updateRefused() { + // getTimeZoneCapabilitiesAndConfig() is called after updateTimeZoneConfiguration() to + // obtain the new state. + boolean postUpdateResponseValue = false; + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createTimeZoneCapabilitiesAndConfig( + /* geoDetectionEnabled= */ postUpdateResponseValue); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + // Simulate the UI being clicked. + boolean preferenceCheckedState = true; + when(mPreference.isChecked()).thenReturn(preferenceCheckedState); + mController.handlePreferenceTreeClick(mPreference); + + // Verify the TimeManager was updated with the UI value. + TimeZoneConfiguration expectedConfiguration = new TimeZoneConfiguration.Builder() + .setGeoDetectionEnabled(preferenceCheckedState) + .build(); + verify(mTimeManager).updateTimeZoneConfiguration(expectedConfiguration); + + // Confirm the UI state was reset using the getTimeZoneCapabilitiesAndConfig() response. + verify(mPreference).setChecked(postUpdateResponseValue); + } + + private static TimeZoneCapabilitiesAndConfig createTimeZoneCapabilitiesAndConfig( + boolean geoDetectionEnabled) { + UserHandle arbitraryUserHandle = UserHandle.of(123); + TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(arbitraryUserHandle) + .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED) + .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_APPLICABLE) + .build(); + TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder() + .setAutoDetectionEnabled(true) + .setGeoDetectionEnabled(geoDetectionEnabled) + .build(); + return new TimeZoneCapabilitiesAndConfig(capabilities, configuration); + } +} diff --git a/tests/robotests/src/com/android/settings/location/TimeZoneDetectionSettingsTest.java b/tests/robotests/src/com/android/settings/location/TimeZoneDetectionSettingsTest.java new file mode 100644 index 00000000000..a8104bd73b4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/TimeZoneDetectionSettingsTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 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.location; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.provider.SearchIndexableResource; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class TimeZoneDetectionSettingsTest { + + private Context mContext; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + } + + @Test + public void searchProvider_shouldIndexDefaultXml() { + final List sir = + TimeZoneDetectionSettings.SEARCH_INDEX_DATA_PROVIDER.getXmlResourcesToIndex( + mContext, /* enabled= */ true); + + assertThat(sir).hasSize(1); + assertThat(sir.get(0).xmlResId).isEqualTo(R.xml.location_time_zone_detection); + } +}