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