• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.ActivityNotFoundException;
21 import android.os.SystemClock;
22 import android.util.Log;
23 
24 import android.graphics.Rect;
25 
26 import android.support.test.uiautomator.By;
27 import android.support.test.uiautomator.BySelector;
28 import android.support.test.uiautomator.Direction;
29 import android.support.test.uiautomator.UiDevice;
30 import android.support.test.uiautomator.UiObject2;
31 import android.support.test.uiautomator.Until;
32 
33 import java.io.IOException;
34 import java.util.List;
35 import java.util.regex.Pattern;
36 
37 public abstract class AbstractAutoStandardAppHelper extends AbstractStandardAppHelper {
38     private static final String LOG_TAG = AbstractAutoStandardAppHelper.class.getSimpleName();
39 
40     protected Instrumentation mInstrumentation;
41     protected UiDevice mDevice;
42 
43     private AutoJsonUtility mAutoJsonUtil;
44 
45     private static final int UI_RESPONSE_WAIT_MS = 5000;
46     private static final float DEFAULT_SCROLL_PERCENT = 100f;
47     private static final int DEFAULT_SCROLL_TIME_MS = 500;
48 
49     private static final int MAX_SCROLLS = 5;
50 
AbstractAutoStandardAppHelper(Instrumentation instrumentation)51     public AbstractAutoStandardAppHelper(Instrumentation instrumentation) {
52         super(instrumentation);
53         mInstrumentation = instrumentation;
54         mDevice = UiDevice.getInstance(instrumentation);
55         mAutoJsonUtil = AutoJsonUtility.getInstance();
56     }
57 
58     /** {@inheritDoc} */
59     @Override
open()60     public void open() {
61         // Launch the application as normal.
62         String pkg = getPackage();
63 
64         String output = null;
65         try {
66             Log.i(LOG_TAG, String.format("Sending command to launch: %s", pkg));
67             mInstrumentation.getContext().startActivity(getOpenAppIntent());
68         } catch (ActivityNotFoundException e) {
69             throw new RuntimeException(String.format("Failed to find package: %s", pkg), e);
70         }
71 
72         // Ensure the package is in the foreground for success.
73         if (!mDevice.wait(Until.hasObject(By.pkg(pkg).depth(0)), 30000)) {
74             throw new IllegalStateException(
75                     String.format("Did not find package, %s, in foreground.", pkg));
76         }
77     }
78 
79     /** {@inheritDoc} */
80     @Override
exit()81     public void exit() {
82         pressHome();
83         waitForIdle();
84     }
85 
86     /** {@inheritDoc} */
87     @Override
dismissInitialDialogs()88     public void dismissInitialDialogs() {
89         // Nothing to dismiss
90     }
91 
92     /** {@inheritDoc} */
93     @Override
getLauncherName()94     public String getLauncherName() {
95         throw new UnsupportedOperationException("Operation not supported.");
96     }
97 
98     /** {@inheritDoc} */
99     @Override
getPackage()100     public String getPackage() {
101         throw new UnsupportedOperationException("Operation not supported.");
102     }
103 
104     /**
105      * Executes a shell command on device, and return the standard output in string.
106      *
107      * @param command the command to run
108      * @return the standard output of the command, or empty string if failed without throwing an
109      *     IOException
110      */
executeShellCommand(String command)111     protected String executeShellCommand(String command) {
112         try {
113             return mDevice.executeShellCommand(command);
114         } catch (IOException e) {
115             // ignore
116             Log.e(
117                     LOG_TAG,
118                     String.format(
119                             "The shell command failed to run: %s exception: %s",
120                             command, e.getMessage()));
121             return "";
122         }
123     }
124 
125     /** Press Home Button on the Device */
pressHome()126     protected void pressHome() {
127         mDevice.pressHome();
128     }
129 
130     /** Press Back Button on the Device */
pressBack()131     protected void pressBack() {
132         mDevice.pressBack();
133     }
134 
135     /** Press Enter Button on the Device */
pressEnter()136     protected void pressEnter() {
137         mDevice.pressEnter();
138     }
139 
140     /** Press power button */
pressPowerButton()141     protected void pressPowerButton() {
142         executeShellCommand("input keyevent KEYCODE_POWER");
143         SystemClock.sleep(UI_RESPONSE_WAIT_MS);
144     }
145 
146     /** Wait for the device to be idle */
waitForIdle()147     protected void waitForIdle() {
148         mDevice.waitForIdle();
149     }
150 
151     /** Wait for the given selector to be gone */
waitForGone(BySelector selector)152     protected void waitForGone(BySelector selector) {
153         mDevice.wait(Until.gone(selector), UI_RESPONSE_WAIT_MS);
154     }
155 
156     /** Wait for window change on the device */
waitForWindowUpdate(String applicationPackage)157     protected void waitForWindowUpdate(String applicationPackage) {
158         mDevice.waitForWindowUpdate(applicationPackage, UI_RESPONSE_WAIT_MS);
159     }
160 
161     /**
162      * Scroll in given direction by specified percent of the whole scrollable region in given time.
163      *
164      * @param direction The direction in which to perform scrolling, it's either up or down.
165      * @param percent The percentage of the whole scrollable region by which to scroll, ranging from
166      *     0 - 100. For instance, percent = 50 would scroll up/down by half of the screen.
167      * @param timeMs The duration in milliseconds to perform the scrolling gesture.
168      * @param index Index required for split screen.
169      */
scroll(Direction direction, float percent, long timeMs, int index)170     private boolean scroll(Direction direction, float percent, long timeMs, int index) {
171         boolean canScrollMoreInGivenDircetion = false;
172         List<UiObject2> upButtons =
173                 findUiObjects(
174                         getResourceFromConfig(
175                                 AutoConfigConstants.SETTINGS,
176                                 AutoConfigConstants.FULL_SETTINGS,
177                                 AutoConfigConstants.UP_BUTTON));
178         List<UiObject2> downButtons =
179                 findUiObjects(
180                         getResourceFromConfig(
181                                 AutoConfigConstants.SETTINGS,
182                                 AutoConfigConstants.FULL_SETTINGS,
183                                 AutoConfigConstants.DOWN_BUTTON));
184         List<UiObject2> scrollableObjects = findUiObjects(By.scrollable(true));
185         if (scrollableObjects == null || upButtons == null || scrollableObjects.size() == 0) {
186             return canScrollMoreInGivenDircetion;
187         }
188         if (upButtons.size() == 1 || (scrollableObjects.size() - 1) < index) {
189             // reset index as it is invalid
190             index = 0;
191         }
192         if (upButtons != null) {
193             UiObject2 upButton = upButtons.get(index);
194             UiObject2 downButton = downButtons.get(index);
195             if (direction == Direction.UP) {
196                 clickAndWaitForIdleScreen(upButton);
197             } else if (direction == Direction.DOWN) {
198                 clickAndWaitForIdleScreen(downButton);
199             }
200         } else {
201             UiObject2 scrollable = scrollableObjects.get(index);
202             if (scrollable != null) {
203                 scrollable.setGestureMargins(
204                         getScrollableMargin(scrollable, false), // left
205                         getScrollableMargin(scrollable, true), // top
206                         getScrollableMargin(scrollable, false), // right
207                         getScrollableMargin(scrollable, true)); // bottom
208                 int scrollSpeed = getScrollSpeed(scrollable, timeMs);
209                 canScrollMoreInGivenDircetion =
210                         scrollable.scroll(direction, percent / 100, scrollSpeed);
211             }
212         }
213         return canScrollMoreInGivenDircetion;
214     }
215 
216     /**
217      * Return the margin for scrolling.
218      *
219      * @param scrollable The given scrollable object to scroll through.
220      * @param isVertical If true, then vertical else horizontal
221      */
getScrollableMargin(UiObject2 scrollable, boolean isVertical)222     private int getScrollableMargin(UiObject2 scrollable, boolean isVertical) {
223         Rect bounds = scrollable.getVisibleBounds();
224         int margin = (int) (Math.abs(bounds.width()) / 4);
225         if (isVertical) {
226             margin = (int) (Math.abs(bounds.height()) / 4);
227         }
228         return margin;
229     }
230 
231     /**
232      * Return the scroll speed such that it takes given time for the device to scroll through the
233      * whole scrollable region(i.e. from the top of the scrollable region to bottom).
234      *
235      * @param scrollable The given scrollable object to scroll through.
236      * @param timeMs The duration in milliseconds to perform the scrolling gesture.
237      */
getScrollSpeed(UiObject2 scrollable, long timeMs)238     private int getScrollSpeed(UiObject2 scrollable, long timeMs) {
239         Rect bounds = scrollable.getVisibleBounds();
240         double timeSeconds = (double) timeMs / 1000;
241         int scrollSpeed = (int) (bounds.height() / timeSeconds);
242         return scrollSpeed;
243     }
244 
245     /**
246      * Scroll down from the top of the scrollable region to bottom of the scrollable region (i.e. by
247      * one page).
248      */
scrollDownOnePage()249     public boolean scrollDownOnePage() {
250         return scrollDownOnePage(0);
251     }
252 
253     /**
254      * Scroll down from the top of the scrollable region to bottom of the scrollable region (i.e. by
255      * one page). Index - required for split screen
256      */
scrollDownOnePage(int index)257     protected boolean scrollDownOnePage(int index) {
258         return scroll(Direction.DOWN, DEFAULT_SCROLL_PERCENT, DEFAULT_SCROLL_TIME_MS, index);
259     }
260 
261     /**
262      * Scroll up from the bottom of the scrollable region to top of the scrollable region (i.e. by
263      * one page).
264      */
scrollUpOnePage()265     public boolean scrollUpOnePage() {
266         return scrollUpOnePage(0);
267     }
268 
269     /**
270      * Scroll up from the bottom of the scrollable region to top of the scrollable region (i.e. by
271      * one page). Index - required for split screen
272      */
scrollUpOnePage(int index)273     protected boolean scrollUpOnePage(int index) {
274         return scroll(Direction.UP, DEFAULT_SCROLL_PERCENT, DEFAULT_SCROLL_TIME_MS, index);
275     }
276 
277     /** Find UI Object in a Scrollable view */
scrollAndFindUiObject(BySelector selector)278     protected UiObject2 scrollAndFindUiObject(BySelector selector) {
279         return scrollAndFindUiObject(selector, 0);
280     }
281 
282     /** Find UI Object in a Scrollable view with Index ( required for split screen ) */
scrollAndFindUiObject(BySelector selector, int index)283     protected UiObject2 scrollAndFindUiObject(BySelector selector, int index) {
284         if (selector == null) {
285             return null;
286         }
287         // Find the object on current page
288         UiObject2 uiObject = findUiObject(selector);
289         if (uiObject != null) {
290             return uiObject;
291         }
292         // Scroll To Top
293         scrollToTop(index);
294         // Find UI Object on the first page
295         uiObject = findUiObject(selector);
296         // Try finding UI Object until it's found or all the pages are checked
297         int scrollCount = 0;
298         while (uiObject == null && scrollCount < MAX_SCROLLS) {
299             // Scroll down to next page
300             scrollDownOnePage(index);
301 
302             // Find UI Object
303             uiObject = findUiObject(selector);
304 
305             scrollCount++;
306         }
307         return uiObject;
308     }
309 
310     /** Scroll to top of the scrollable region. */
scrollToTop()311     protected void scrollToTop() {
312         scrollToTop(0);
313     }
314 
315     /** Scroll to top of the scrollable region with index. ( Required for Split Screen ) */
scrollToTop(int index)316     protected void scrollToTop(int index) {
317         int scrollCount = 0;
318         while (scrollCount < MAX_SCROLLS) {
319             scrollUpOnePage(index);
320             scrollCount++;
321         }
322     }
323 
324     /** Find the UI Object that matches the given selector */
findUiObject(BySelector selector)325     protected UiObject2 findUiObject(BySelector selector) {
326         if (selector == null) {
327             return null;
328         }
329         UiObject2 uiObject = mDevice.wait(Until.findObject(selector), UI_RESPONSE_WAIT_MS);
330         return uiObject;
331     }
332 
333     /** Find the list of UI object that matches the given selector */
findUiObjects(BySelector selector)334     protected List<UiObject2> findUiObjects(BySelector selector) {
335         if (selector == null) {
336             return null;
337         }
338         List<UiObject2> uiObjects = mDevice.wait(Until.findObjects(selector), UI_RESPONSE_WAIT_MS);
339         return uiObjects;
340     }
341 
342     /** Find the list of UI object that matches the given selector for given depth */
findUiObjects(BySelector selector, int depth)343     protected List<UiObject2> findUiObjects(BySelector selector, int depth) {
344         if (selector == null) {
345             return null;
346         }
347         List<UiObject2> uiObjects =
348                 mDevice.wait(Until.findObjects(selector.maxDepth(depth)), UI_RESPONSE_WAIT_MS);
349         return uiObjects;
350     }
351 
352     /**
353      * This method is used to click on an UiObject2 and wait for device idle after click.
354      *
355      * @param uiObject UiObject2 to click.
356      */
clickAndWaitForIdleScreen(UiObject2 uiObject2)357     protected void clickAndWaitForIdleScreen(UiObject2 uiObject2) {
358         uiObject2.click();
359         waitForIdle();
360     }
361 
362     /**
363      * This method is used to click on an UiObject2 and wait for window update.
364      *
365      * @param appPackage application package for window update
366      * @param uiObject2 UiObject2 to click.
367      */
clickAndWaitForWindowUpdate(String appPackage, UiObject2 uiObject2)368     protected void clickAndWaitForWindowUpdate(String appPackage, UiObject2 uiObject2) {
369         uiObject2.click();
370         waitForWindowUpdate(appPackage);
371         waitForIdle();
372     }
373 
374     /**
375      * This method is used to click on an UiObject2 and wait until it is gone
376      *
377      * @param uiObject2 uiObject to be clicked
378      * @param selector BySelector to be gone
379      */
clickAndWaitForGone(UiObject2 uiObject2, BySelector selector)380     protected void clickAndWaitForGone(UiObject2 uiObject2, BySelector selector) {
381         uiObject2.click();
382         waitForGone(selector);
383     }
384 
385     /**
386      * This method is used check if given object is visible on the device screen
387      *
388      * @param selector BySelector to be gone
389      */
hasUiObject(BySelector selector)390     protected boolean hasUiObject(BySelector selector) {
391         return mDevice.hasObject(selector);
392     }
393 
394     /** Get path for the given setting */
getSettingPath(String setting)395     protected String[] getSettingPath(String setting) {
396         return mAutoJsonUtil.getSettingPath(setting);
397     }
398 
399     /** Get available options for given settings */
getSettingOptions(String setting)400     protected String[] getSettingOptions(String setting) {
401         return mAutoJsonUtil.getSettingOptions(setting);
402     }
403 
404     /** Get application config value for given configuration */
getApplicationConfig(String config)405     protected String getApplicationConfig(String config) {
406         return mAutoJsonUtil.getApplicationConfig(config);
407     }
408 
409     /** Get resource for given configuration resource in given application */
getResourceFromConfig( String appName, String appConfig, String appResource)410     protected BySelector getResourceFromConfig(
411             String appName, String appConfig, String appResource) {
412         AutoConfigResource configResource =
413                 mAutoJsonUtil.getResourceFromConfig(appName, appConfig, appResource);
414 
415         // RESOURCE_ID
416         if (configResource != null
417                 && AutoConfigConstants.RESOURCE_ID.equals(configResource.getResourceType())) {
418             return By.res(configResource.getResourcePackage(), configResource.getResourceValue());
419         }
420 
421         // TEXT
422         if (configResource != null
423                 && AutoConfigConstants.TEXT.equals(configResource.getResourceType())) {
424             return By.text(
425                     Pattern.compile(configResource.getResourceValue(), Pattern.CASE_INSENSITIVE));
426         }
427 
428         // TEXT_CONTAINS
429         if (configResource != null
430                 && AutoConfigConstants.TEXT_CONTAINS.equals(configResource.getResourceType())) {
431             return By.textContains(configResource.getResourceValue());
432         }
433 
434         // DESCRIPTION
435         if (configResource != null
436                 && AutoConfigConstants.DESCRIPTION.equals(configResource.getResourceType())) {
437             return By.desc(
438                     Pattern.compile(configResource.getResourceValue(), Pattern.CASE_INSENSITIVE));
439         }
440 
441         // CLASS
442         if (configResource != null
443                 && AutoConfigConstants.CLASS.equals(configResource.getResourceType())) {
444             if (configResource.getResourcePackage() != null
445                     && !configResource.getResourcePackage().isEmpty()) {
446                 return By.clazz(
447                         configResource.getResourcePackage(), configResource.getResourceValue());
448             }
449             return By.clazz(configResource.getResourceValue());
450         }
451 
452         return null;
453     }
454 
455     /** Get resource value for given configuration resource in given application */
getResourceValue(String appName, String appConfig, String appResource)456     protected String getResourceValue(String appName, String appConfig, String appResource) {
457         AutoConfigResource configResource =
458                 mAutoJsonUtil.getResourceFromConfig(appName, appConfig, appResource);
459 
460         if (configResource != null) {
461             return configResource.getResourceValue();
462         }
463 
464         return null;
465     }
466 
467     /** Get resource package for given configuration resource in given application */
getResourcePackage(String appName, String appConfig, String appResource)468     protected String getResourcePackage(String appName, String appConfig, String appResource) {
469         AutoConfigResource configResource =
470                 mAutoJsonUtil.getResourceFromConfig(appName, appConfig, appResource);
471 
472         if (configResource != null) {
473             return configResource.getResourcePackage();
474         }
475 
476         return null;
477     }
478 
479     /** Check for Split Screen UI in Settings Application */
hasSplitScreenSettingsUI()480     protected boolean hasSplitScreenSettingsUI() {
481         boolean isSplitScreen = false;
482         if ("TRUE"
483                 .equalsIgnoreCase(
484                         mAutoJsonUtil.getApplicationConfig(AutoConfigConstants.SPLIT_SCREEN_UI))) {
485             isSplitScreen = true;
486         }
487         return isSplitScreen;
488     }
489 }
490