1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.settings.ui;
17 
18 import static com.android.settings.ui.testutils.SettingsTestUtils.SETTINGS_PACKAGE;
19 import static com.android.settings.ui.testutils.SettingsTestUtils.TIMEOUT;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertTrue;
24 
25 import android.os.RemoteException;
26 import android.os.SystemProperties;
27 import android.provider.Settings;
28 import android.support.test.uiautomator.By;
29 import android.support.test.uiautomator.BySelector;
30 import android.support.test.uiautomator.UiDevice;
31 import android.support.test.uiautomator.UiObject;
32 import android.support.test.uiautomator.UiObject2;
33 import android.support.test.uiautomator.UiObjectNotFoundException;
34 import android.support.test.uiautomator.UiScrollable;
35 import android.support.test.uiautomator.UiSelector;
36 import android.support.test.uiautomator.Until;
37 import android.system.helpers.SettingsHelper;
38 import android.system.helpers.SettingsHelper.SettingsType;
39 
40 import androidx.test.InstrumentationRegistry;
41 import androidx.test.filters.MediumTest;
42 import androidx.test.runner.AndroidJUnit4;
43 
44 import org.junit.After;
45 import org.junit.Before;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 
49 import java.util.TimeZone;
50 
51 @MediumTest
52 @RunWith(AndroidJUnit4.class)
53 public class ZonePickerSettingsTest {
54 
55     private static final BySelector SELECTOR_SELECT_TIME_ZONE =
56             By.hasChild(By.text("Select time zone"));
57 
58     private UiDevice mDevice;
59     private SettingsHelper mHelper;
60     private String mIsV2EnabledByDefault;
61     private int mIsAutoZoneEnabled;
62 
63     @Before
setUp()64     public void setUp() throws Exception {
65         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
66         mHelper = SettingsHelper.getInstance();
67         try {
68             mDevice.setOrientationNatural();
69         } catch (RemoteException e) {
70             throw new RuntimeException("failed to freeze device orientation", e);
71         }
72         mIsV2EnabledByDefault = mHelper.getStringSetting(SettingsType.GLOBAL,
73                 "settings_zone_picker_v2");
74         mHelper.setStringSetting(SettingsType.GLOBAL, "settings_zone_picker_v2", "true");
75         mIsAutoZoneEnabled = mHelper.getIntSetting(SettingsType.GLOBAL,
76                 Settings.Global.AUTO_TIME_ZONE);
77     }
78 
79     @After
tearDown()80     public void tearDown() throws Exception {
81         // Go back to home for next test.
82         mDevice.pressBack();
83         mDevice.pressBack();
84         mDevice.pressHome();
85         mDevice.waitForIdle(TIMEOUT * 2);
86         mHelper.setStringSetting(SettingsType.GLOBAL, "settings_zone_picker_v2",
87                 mIsV2EnabledByDefault);
88         mHelper.setIntSetting(SettingsType.GLOBAL, Settings.Global.AUTO_TIME_ZONE,
89                 mIsAutoZoneEnabled);
90     }
91 
92     @Test
zonePickerDisabled()93     public void zonePickerDisabled() throws Exception {
94         mHelper.setIntSetting(SettingsType.GLOBAL, Settings.Global.AUTO_TIME_ZONE, 1);
95 
96         SettingsHelper.launchSettingsPage(
97                 InstrumentationRegistry.getContext(), Settings.ACTION_DATE_SETTINGS);
98         UiObject2 selectTimeZone = wait(SELECTOR_SELECT_TIME_ZONE);
99         assertFalse(selectTimeZone.isEnabled());
100     }
101 
102     // Test 2 time zones with no DST
103     @Test
testSelectReykjavik()104     public void testSelectReykjavik() throws Exception {
105         testSelectTimeZone("Iceland", "Reykjavik", "GMT+00:00", "Atlantic/Reykjavik", true);
106     }
107 
108     @Test
testSelectPhoenix()109     public void testSelectPhoenix() throws Exception {
110         testSelectTimeZone("United States", "Phoenix", "GMT-07:00", "America/Phoenix", false);
111     }
112 
testSelectTimeZone(String region, String timezone, String expectedTimeZoneOffset, String expectedTimeZoneId, boolean assumeOneTimeZoneInRegion)113     private void testSelectTimeZone(String region, String timezone, String expectedTimeZoneOffset,
114             String expectedTimeZoneId, boolean assumeOneTimeZoneInRegion) throws Exception {
115         mHelper.setIntSetting(SettingsType.GLOBAL, Settings.Global.AUTO_TIME_ZONE, 0);
116 
117         SettingsHelper.launchSettingsPage(
118                 InstrumentationRegistry.getContext(), Settings.ACTION_DATE_SETTINGS);
119 
120         UiObject2 selectTimeZone = wait(SELECTOR_SELECT_TIME_ZONE);
121         assertTrue(selectTimeZone.isEnabled());
122         selectTimeZone.click();
123 
124         wait(By.text("Region")).click();
125         // Speed-up the test by searching with the first 2 characters of the region name
126         wait(By.res("android", "search_src_text")).setText(region.substring(0, 2));
127         // Select region in the list
128         selectItemInList(new UiSelector().textContains(region))
129                 .click();
130 
131         // Only select time zone explicitly if there are more than one time zones in a region
132         if (!assumeOneTimeZoneInRegion) {
133             wait(By.text("Time zone"));
134             selectItemInList(new UiSelector().textContains(timezone))
135                     .click();
136         }
137 
138         mDevice.pressBack();
139         // The select button should include the GMT offset in the summary
140         BySelector summarySelector = By.res("android:id/summary");
141         UiObject2 selectedTimeZone = selectTimeZone.findObject(summarySelector);
142         assertUiObjectFound(selectedTimeZone, summarySelector);
143         assertTrue("Expect " + expectedTimeZoneOffset + " is shown for " + timezone,
144                 selectedTimeZone.getText().startsWith(expectedTimeZoneOffset));
145 
146         waitAndAssertTimeGetDefault(expectedTimeZoneId);
147         assertEquals("Time zone change in Settings should update persist.sys.timezone",
148                 expectedTimeZoneId, SystemProperties.get("persist.sys.timezone"));
149     }
150 
151     private static final long CHECK_DEFAULT_TIMEZONE_INTERVAL = 200L;
152     private static final long CHECK_DEFAULT_TIMEZONE_TIMEOUT = 3000L;
153 
154     /**
155      * Wait for the broadcast ACTION_TIMEZONE_CHANGED propagated, and update the default TimeZone
156      * by ApplicationThread.
157      */
waitAndAssertTimeGetDefault(String expectedTimeZoneId)158     private static void waitAndAssertTimeGetDefault(String expectedTimeZoneId)
159             throws InterruptedException {
160         for (int i = 0; i < CHECK_DEFAULT_TIMEZONE_TIMEOUT / CHECK_DEFAULT_TIMEZONE_INTERVAL; i++) {
161             if (expectedTimeZoneId.equals(TimeZone.getDefault().getID())) {
162                 return;
163             }
164             Thread.sleep(CHECK_DEFAULT_TIMEZONE_INTERVAL);
165         }
166 
167         assertEquals(expectedTimeZoneId, TimeZone.getDefault().getID());
168     }
169 
selectItemInList(UiSelector childSelector)170     private UiObject selectItemInList(UiSelector childSelector) throws UiObjectNotFoundException {
171         UiScrollable recyclerView = new UiScrollable(
172                 new UiSelector().resourceId(SETTINGS_PACKAGE + ":id/recycler_view"));
173         return selectScrollableItem(recyclerView, childSelector);
174     }
175 
176     /**
177      * Select the child object in the UiScrollable
178      * @throws UiObjectNotFoundException if scrollable or child is not found
179      */
selectScrollableItem(UiScrollable scrollable, UiSelector childSelector)180     private UiObject selectScrollableItem(UiScrollable scrollable, UiSelector childSelector)
181             throws UiObjectNotFoundException {
182         if (!scrollable.waitForExists(TIMEOUT)) {
183             throw newUiObjectNotFoundException(scrollable.getSelector());
184         }
185         scrollable.scrollIntoView(childSelector);
186 
187         UiObject child = mDevice.findObject(childSelector);
188         assertUiObjectFound(child, childSelector);
189         return child;
190     }
191 
192     /**
193      * @throws UiObjectNotFoundException if UiDevice.wait returns null
194      */
wait(BySelector selector)195     private UiObject2 wait(BySelector selector) throws UiObjectNotFoundException {
196         UiObject2 item = mDevice.wait(Until.findObject(selector), TIMEOUT);
197         assertUiObjectFound(item, selector);
198         return item;
199     }
200 
assertUiObjectFound(UiObject2 obj, BySelector selector)201     private static void assertUiObjectFound(UiObject2 obj, BySelector selector)
202             throws UiObjectNotFoundException {
203         if (obj == null) {
204             throw newUiObjectNotFoundException(selector);
205         }
206     }
207 
208 
assertUiObjectFound(UiObject obj, UiSelector selector)209     private static void assertUiObjectFound(UiObject obj, UiSelector selector)
210             throws UiObjectNotFoundException {
211         if (obj == null) {
212             throw newUiObjectNotFoundException(selector);
213         }
214     }
215 
newUiObjectNotFoundException(Object selector)216     private static UiObjectNotFoundException newUiObjectNotFoundException(Object selector) {
217         return new UiObjectNotFoundException(
218                 String.format("UI object not found: %s", selector.toString()));
219     }
220 }
221