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