1 /*
2  * Copyright (C) 2021 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 
17 package android.platform.helpers;
18 
19 import android.app.Instrumentation;
20 import android.app.UiModeManager;
21 import android.bluetooth.BluetoothAdapter;
22 import android.content.Context;
23 import android.net.wifi.WifiManager;
24 import android.os.SystemClock;
25 import android.support.test.uiautomator.By;
26 import android.support.test.uiautomator.BySelector;
27 import android.support.test.uiautomator.UiObject2;
28 import android.support.test.uiautomator.UiObjectNotFoundException;
29 import android.support.test.uiautomator.UiScrollable;
30 import android.support.test.uiautomator.UiSelector;
31 import androidx.test.InstrumentationRegistry;
32 
33 import java.util.List;
34 import java.util.regex.Pattern;
35 
36 public class SettingHelperImpl extends AbstractAutoStandardAppHelper implements IAutoSettingHelper {
37 
38     private static final String LOG_TAG = SettingHelperImpl.class.getSimpleName();
39 
40     // Wait Time
41     private static final int UI_RESPONSE_WAIT_MS = 5000;
42 
43     private UiModeManager mUiModeManager;
44     private Context mContext;
45 
SettingHelperImpl(Instrumentation instr)46     public SettingHelperImpl(Instrumentation instr) {
47         super(instr);
48         mUiModeManager =
49                 InstrumentationRegistry.getInstrumentation()
50                         .getContext()
51                         .getSystemService(UiModeManager.class);
52         mContext = InstrumentationRegistry.getContext();
53     }
54 
55     /** {@inheritDoc} */
56     @Override
open()57     public void open() {
58         openFullSettings();
59     }
60 
61     /** {@inheritDoc} */
62     @Override
getPackage()63     public String getPackage() {
64         return getApplicationConfig(AutoConfigConstants.SETTINGS_PACKAGE);
65     }
66 
67     /** {@inheritDoc} */
68     @Override
stopSettingsApplication()69     public void stopSettingsApplication() {
70         String cmd =
71                 String.format(
72                         "am force-stop %s",
73                         getApplicationConfig(AutoConfigConstants.SETTINGS_PACKAGE));
74         executeShellCommand(cmd);
75     }
76 
77     /** {@inheritDoc} */
78     @Override
getLauncherName()79     public String getLauncherName() {
80         return "Settings";
81     }
82 
83     /** {@inheritDoc} */
84     @Override
openSetting(String setting)85     public void openSetting(String setting) {
86         openFullSettings();
87         openMenuWith(getSettingPath(setting));
88         verifyAvailableOptions(setting);
89     }
90 
91     /** {@inheritDoc} */
92     @Override
findSettingMenu(String setting)93     public UiObject2 findSettingMenu(String setting) {
94         UiObject2 menuObject = null;
95         String[] menuOptions = getSettingPath(setting);
96         int currentIndex = 0;
97         int lastIndex = menuOptions.length - 1;
98         for (String menu : menuOptions) {
99             int scrollableScreenIndex = 0;
100             if (hasSplitScreenSettingsUI() && currentIndex > 0) {
101                 scrollableScreenIndex = 1;
102             }
103             menuObject = getMenu(menu, scrollableScreenIndex);
104             if (currentIndex == lastIndex) {
105                 return menuObject;
106             }
107             clickAndWaitForIdleScreen(menuObject);
108             waitForIdle();
109             currentIndex++;
110         }
111         return menuObject;
112     }
113 
114     @Override
findSettingMenuAndClick(String setting)115     public void findSettingMenuAndClick(String setting) {
116         UiObject2 settingMenu = findSettingMenu(setting);
117         if (settingMenu != null) {
118             clickAndWaitForIdleScreen(settingMenu);
119         } else {
120             throw new RuntimeException("Unable to find setting menu");
121         }
122     }
123 
124     @Override
getPageTitleText()125     public String getPageTitleText() {
126         UiObject2 pageToolbarTitle = getPageTitle();
127         return pageToolbarTitle.getText();
128     }
129 
130     /** {@inheritDoc} */
131     @Override
openFullSettings()132     public void openFullSettings() {
133         executeShellCommand(getApplicationConfig(AutoConfigConstants.OPEN_SETTINGS_COMMAND));
134     }
135 
136     /** {@inheritDoc} */
137     @Override
openQuickSettings()138     public void openQuickSettings() {
139         pressHome();
140         executeShellCommand(getApplicationConfig(AutoConfigConstants.OPEN_QUICK_SETTINGS_COMMAND));
141         UiObject2 settingObject =
142                 findUiObject(
143                         getResourceFromConfig(
144                                 AutoConfigConstants.SETTINGS,
145                                 AutoConfigConstants.QUICK_SETTINGS,
146                                 AutoConfigConstants.OPEN_MORE_SETTINGS));
147         if (settingObject == null) {
148             throw new RuntimeException("Failed to open quick settings.");
149         }
150     }
151 
verifyAvailableOptions(String setting)152     private void verifyAvailableOptions(String setting) {
153         String[] expectedOptions = getSettingOptions(setting);
154         if (expectedOptions == null) {
155             return;
156         }
157         for (String option : expectedOptions) {
158             if (mDevice.hasObject(By.clickable(false).textContains(option))) {
159                 continue;
160             }
161             Pattern menuPattern = Pattern.compile(option, Pattern.CASE_INSENSITIVE);
162             BySelector selector = By.text(menuPattern);
163             int scrollScreenIndex = 0;
164             if (hasSplitScreenSettingsUI()) {
165                 scrollScreenIndex = 1;
166             }
167             if (scrollAndFindUiObject(selector, scrollScreenIndex) == null) {
168                 throw new RuntimeException("Cannot find settings option: " + option);
169             }
170         }
171     }
172 
173     /** {@inheritDoc} */
174     @Override
turnOnOffWifi(boolean onOff)175     public void turnOnOffWifi(boolean onOff) {
176         boolean isOn = isWifiOn();
177         if (isOn != onOff) {
178             UiObject2 enableOption =
179                     findUiObject(
180                             getResourceFromConfig(
181                                     AutoConfigConstants.SETTINGS,
182                                     AutoConfigConstants.NETWORK_AND_INTERNET_SETTINGS,
183                                     AutoConfigConstants.TOGGLE_WIFI));
184             clickAndWaitForWindowUpdate(
185                     getApplicationConfig(AutoConfigConstants.SETTINGS_PACKAGE), enableOption);
186         } else {
187             throw new RuntimeException("Wi-Fi enabled state is already " + (onOff ? "on" : "off"));
188         }
189     }
190 
191     /** {@inheritDoc} */
192     @Override
isWifiOn()193     public boolean isWifiOn() {
194         WifiManager wifi = (WifiManager) this.mContext.getSystemService(Context.WIFI_SERVICE);
195         return wifi.isWifiEnabled();
196     }
197 
198     /** {@inheritDoc} */
199     @Override
turnOnOffHotspot(boolean onOff)200     public void turnOnOffHotspot(boolean onOff) {
201         boolean isOn = isHotspotOn();
202         if (isOn != onOff) {
203             UiObject2 enableOption =
204                     findUiObject(
205                             getResourceFromConfig(
206                                     AutoConfigConstants.SETTINGS,
207                                     AutoConfigConstants.NETWORK_AND_INTERNET_SETTINGS,
208                                     AutoConfigConstants.TOGGLE_HOTSPOT));
209             clickAndWaitForWindowUpdate(
210                     getApplicationConfig(AutoConfigConstants.SETTINGS_PACKAGE), enableOption);
211         } else {
212             throw new RuntimeException(
213                     "Hotspot enabled state is already " + (onOff ? "on" : "off"));
214         }
215     }
216 
217     /** {@inheritDoc} */
218     @Override
isHotspotOn()219     public boolean isHotspotOn() {
220         UiObject2 enableOption =
221                 findUiObject(
222                         getResourceFromConfig(
223                                 AutoConfigConstants.SETTINGS,
224                                 AutoConfigConstants.NETWORK_AND_INTERNET_SETTINGS,
225                                 AutoConfigConstants.TOGGLE_HOTSPOT));
226         return enableOption.isChecked();
227     }
228 
229     /** {@inheritDoc} */
230     @Override
turnOnOffBluetooth(boolean onOff)231     public void turnOnOffBluetooth(boolean onOff) {
232         boolean isOn = isBluetoothOn();
233         if (isOn != onOff) {
234             UiObject2 enableOption =
235                     findUiObject(
236                             getResourceFromConfig(
237                                     AutoConfigConstants.SETTINGS,
238                                     AutoConfigConstants.BLUETOOTH_SETTINGS,
239                                     AutoConfigConstants.TOGGLE_BLUETOOTH));
240             clickAndWaitForWindowUpdate(
241                     getApplicationConfig(AutoConfigConstants.SETTINGS_PACKAGE), enableOption);
242         } else {
243             throw new RuntimeException(
244                     "Bluetooth enabled state is already " + (onOff ? "on" : "off"));
245         }
246     }
247 
248     /** {@inheritDoc} */
249     @Override
isBluetoothOn()250     public boolean isBluetoothOn() {
251         BluetoothAdapter ba = BluetoothAdapter.getDefaultAdapter();
252         return ba.isEnabled();
253     }
254 
255     /** {@inheritDoc} */
256     @Override
searchAndSelect(String item)257     public void searchAndSelect(String item) {
258         searchAndSelect(item, 0);
259     }
260 
261     /** {@inheritDoc} */
262     @Override
searchAndSelect(String item, int selectedIndex)263     public void searchAndSelect(String item, int selectedIndex) {
264         UiObject2 search_button =
265                 findUiObject(
266                         getResourceFromConfig(
267                                 AutoConfigConstants.SETTINGS,
268                                 AutoConfigConstants.FULL_SETTINGS,
269                                 AutoConfigConstants.SEARCH));
270         clickAndWaitForIdleScreen(search_button);
271         UiObject2 search_box =
272                 findUiObject(
273                         getResourceFromConfig(
274                                 AutoConfigConstants.SETTINGS,
275                                 AutoConfigConstants.FULL_SETTINGS,
276                                 AutoConfigConstants.SEARCH_BOX));
277         search_box.setText(item);
278         SystemClock.sleep(UI_RESPONSE_WAIT_MS);
279         // close the keyboard to reveal all search results.
280         mDevice.pressBack();
281 
282         UiObject2 searchResults =
283                 findUiObject(
284                         getResourceFromConfig(
285                                 AutoConfigConstants.SETTINGS,
286                                 AutoConfigConstants.FULL_SETTINGS,
287                                 AutoConfigConstants.SEARCH_RESULTS));
288         int numberOfResults = searchResults.getChildren().size();
289         if (numberOfResults == 0) {
290             throw new RuntimeException("No results found");
291         }
292         clickAndWaitForIdleScreen(searchResults.getChildren().get(selectedIndex));
293         SystemClock.sleep(UI_RESPONSE_WAIT_MS);
294 
295         UiObject2 object = findUiObject(By.textContains(item));
296         if (object == null) {
297             throw new RuntimeException("Opened page does not contain searched item");
298         }
299     }
300 
301     /** {@inheritDoc} */
302     @Override
isValidPageTitle(String item)303     public boolean isValidPageTitle(String item) {
304         UiObject2 pageTitle = getPageTitle();
305         return pageTitle.getText().contains(item);
306     }
307 
getPageTitle()308     private UiObject2 getPageTitle() {
309         BySelector[] selectors =
310                 new BySelector[] {
311                     getResourceFromConfig(
312                             AutoConfigConstants.SETTINGS,
313                             AutoConfigConstants.FULL_SETTINGS,
314                             AutoConfigConstants.PAGE_TITLE),
315                     getResourceFromConfig(
316                             AutoConfigConstants.SETTINGS,
317                             AutoConfigConstants.APPS_SETTINGS,
318                             AutoConfigConstants.PERMISSIONS_PAGE_TITLE)
319                 };
320         for (BySelector selector : selectors) {
321             List<UiObject2> pageTitles = findUiObjects(selector);
322             if (pageTitles != null && pageTitles.size() > 0) {
323                 return pageTitles.get(pageTitles.size() - 1);
324             }
325         }
326         throw new RuntimeException("Unable to find page title");
327     }
328 
329     /** {@inheritDoc} */
330     @Override
goBackToSettingsScreen()331     public void goBackToSettingsScreen() {
332         // count is used to avoid infinite loop in case someone invokes
333         // after exiting settings application
334         int count = 5;
335         while (count > 0
336                 && isAppInForeground()
337                 && findUiObject(
338                                 By.text(
339                                         getApplicationConfig(
340                                                 AutoConfigConstants.SETTINGS_TITLE_TEXT)))
341                         == null) {
342             pressBack();
343             SystemClock.sleep(UI_RESPONSE_WAIT_MS); // to avoid stale object error
344             count--;
345         }
346     }
347 
348     /** {@inheritDoc} */
349     @Override
openMenuWith(String... menuOptions)350     public void openMenuWith(String... menuOptions) {
351         for (String menu : menuOptions) {
352             Pattern menuPattern = Pattern.compile(menu, Pattern.CASE_INSENSITIVE);
353             UiObject2 menuButton = scrollAndFindUiObject(By.text(menuPattern));
354             if (menuButton == null) {
355                 throw new RuntimeException("Unable to find menu item");
356             }
357             clickAndWaitForIdleScreen(menuButton);
358             waitForIdle();
359         }
360     }
361 
362     /**
363      * Checks whether a setting menu is enabled or not. When not enabled, the menu item cannot be
364      * clicked.
365      */
366     @Override
isSettingMenuEnabled(String menu)367     public boolean isSettingMenuEnabled(String menu) {
368         boolean isSettingMenuEnabled = false;
369         String[] menuOptions = getSettingPath(menu);
370         int currentIndex = 0;
371         int lastIndex = menuOptions.length - 1;
372         for (String menuOption : menuOptions) {
373             int scrollableScreenIndex = 0;
374             if (hasSplitScreenSettingsUI() && currentIndex > 0) {
375                 scrollableScreenIndex = 1;
376             }
377             UiObject2 menuObject = getMenu(menuOption, scrollableScreenIndex);
378             if (currentIndex == lastIndex) {
379                 return menuObject.isEnabled();
380             }
381             if (!menuObject.isEnabled()) {
382                 return isSettingMenuEnabled;
383             }
384             clickAndWaitForIdleScreen(menuObject);
385             waitForIdle();
386             currentIndex++;
387         }
388         return isSettingMenuEnabled;
389     }
390 
getMenu(String menu, int index)391     private UiObject2 getMenu(String menu, int index) {
392         Pattern menuPattern = Pattern.compile(menu, Pattern.CASE_INSENSITIVE);
393         UiObject2 menuButton = scrollAndFindUiObject(By.text(menuPattern), index);
394         if (menuButton == null) {
395             throw new RuntimeException("Unable to find menu item");
396         }
397         return menuButton;
398     }
399 
400     /** {@inheritDoc} */
401     @Override
getValue(String setting)402     public int getValue(String setting) {
403         String cmd = String.format("settings get system %s", setting);
404         String value = executeShellCommand(cmd);
405         return Integer.parseInt(value.replaceAll("\\s", ""));
406     }
407 
408     /** {@inheritDoc} */
409     @Override
setValue(String setting, int value)410     public void setValue(String setting, int value) {
411         String cmd = String.format("settings put system %s %d", setting, value);
412         executeShellCommand(cmd);
413     }
414 
415     /** {@inheritDoc} */
416     @Override
changeSeekbarLevel(int index, ChangeType changeType)417     public void changeSeekbarLevel(int index, ChangeType changeType) {
418         try {
419             String seekBar =
420                     String.format(
421                             "%s:id/%s",
422                             getResourcePackage(
423                                     AutoConfigConstants.SETTINGS,
424                                     AutoConfigConstants.DISPLAY_SETTINGS,
425                                     AutoConfigConstants.BRIGHTNESS_LEVEL),
426                             getResourceValue(
427                                     AutoConfigConstants.SETTINGS,
428                                     AutoConfigConstants.DISPLAY_SETTINGS,
429                                     AutoConfigConstants.BRIGHTNESS_LEVEL));
430             UiScrollable seekbar =
431                     new UiScrollable(new UiSelector().resourceId(seekBar).instance(index));
432             if (changeType == ChangeType.INCREASE) {
433                 seekbar.scrollForward(1);
434             } else {
435                 seekbar.scrollBackward(1);
436             }
437             waitForWindowUpdate(getApplicationConfig(AutoConfigConstants.SETTINGS_PACKAGE));
438         } catch (UiObjectNotFoundException exception) {
439             throw new RuntimeException("Unable to find seekbar");
440         }
441     }
442 
443     /** {@inheritDoc} */
444     @Override
setDayNightMode(DayNightMode mode)445     public void setDayNightMode(DayNightMode mode) {
446         if (mode == DayNightMode.DAY_MODE
447                         && getDayNightModeStatus().getValue() == mUiModeManager.MODE_NIGHT_YES
448                 || mode == DayNightMode.NIGHT_MODE
449                         && getDayNightModeStatus().getValue() != mUiModeManager.MODE_NIGHT_YES) {
450             clickAndWaitForWindowUpdate(
451                     getApplicationConfig(AutoConfigConstants.SETTINGS_PACKAGE),
452                     getNightModeButton());
453         }
454     }
455 
getNightModeButton()456     private UiObject2 getNightModeButton() {
457         UiObject2 nightModeButton =
458                 scrollAndFindUiObject(
459                         getResourceFromConfig(
460                                 AutoConfigConstants.SETTINGS,
461                                 AutoConfigConstants.QUICK_SETTINGS,
462                                 AutoConfigConstants.NIGHT_MODE));
463         if (nightModeButton == null) {
464             throw new RuntimeException("Unable to find night mode button");
465         }
466         return nightModeButton.getParent();
467     }
468 
469     /** {@inheritDoc} */
470     @Override
getDayNightModeStatus()471     public DayNightMode getDayNightModeStatus() {
472         return mUiModeManager.getNightMode() == mUiModeManager.MODE_NIGHT_YES
473                 ? DayNightMode.NIGHT_MODE
474                 : DayNightMode.DAY_MODE;
475     }
476 }
477