1 /*
2  * Copyright (C) 2019 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.graphics.Rect;
20 import android.platform.helpers.exceptions.TestHelperException;
21 
22 import androidx.test.InstrumentationRegistry;
23 import androidx.test.uiautomator.By;
24 import androidx.test.uiautomator.Direction;
25 import androidx.test.uiautomator.UiDevice;
26 import androidx.test.uiautomator.UiObject2;
27 
28 /**
29  * This interface is intended to be inherited by AppHelper classes to add scrolling functionlity.
30  */
31 public interface Scrollable {
32     int DEFAULT_MARGIN = 5;
33     int DEFAULT_DURATION_MS = 1000;
34 
35     /**
36      * Setup expectations: None
37      *
38      * <p>return true is the corresponding app is in foreground, and false otherwise.
39      */
isAppInForeground()40     boolean isAppInForeground();
41 
42     /**
43      * Setup expectations: None.
44      *
45      * <p>Scroll up from the bottom of the scrollable region to top of the scrollable region (i.e.
46      * by one page) in <code>durationMs</code> milliseconds only if corresponding app is open.
47      *
48      * @param durationMs The duration in milliseconds to perform the scrolling gesture.
49      */
scrollUpOnePage(long durationMs)50     public default void scrollUpOnePage(long durationMs) {
51         scrollUp(100f, durationMs);
52     }
53 
54     /**
55      * Scroll up from the bottom of the scrollable region to top of the scrollable region (i.e. by
56      * one page).
57      */
scrollUpOnePage()58     public default boolean scrollUpOnePage() {
59         scrollUpOnePage(DEFAULT_DURATION_MS);
60         return true;
61     }
62 
63     /**
64      * Setup expectations: None.
65      *
66      * <p>Scroll up from the bottom of the scrollable region towards the top of the scrollable
67      * region by <code>percent</code> percent of the whole scrollable region in <code>durationMs
68      * </code> milliseconds only if corresponding app is open.
69      *
70      * @param percent The percentage of the whole scrollable region by which to scroll up, ranging
71      *     from 0 - 100. For instance, percent = 50 would scroll up by half of the screen.
72      * @param durationMs The duration in milliseconds to perform the scrolling gesture.
73      */
scrollUp(float percent, long durationMs)74     public default void scrollUp(float percent, long durationMs) {
75         scroll(Direction.UP, percent, durationMs);
76     }
77 
78     /**
79      * Setup expectations: None.
80      *
81      * <p>Scroll down from the top of the scrollable region to bottom of the scrollable region (i.e.
82      * by one page) in <code>durationMs</code> milliseconds only if corresponding app is open.
83      *
84      * @param durationMs The duration in milliseconds to perform the scrolling gesture.
85      */
scrollDownOnePage(long durationMs)86     public default void scrollDownOnePage(long durationMs) {
87         scrollDown(100f, durationMs);
88     }
89 
90     /**
91      * Scroll down from the top of the scrollable region to bottom of the scrollable region (i.e. by
92      * one page).
93      */
scrollDownOnePage()94     public default boolean scrollDownOnePage() {
95         scrollDownOnePage(DEFAULT_DURATION_MS);
96         return true;
97     }
98 
99     /**
100      * Setup expectations: None.
101      *
102      * <p>Scroll down from the top of the scrollable region towards the bottom of the scrollable
103      * region by <code>percent</code> percent of the whole scrollable region in <code>durationMs
104      * </code> milliseconds only if corresponding app is open.
105      *
106      * @param percent The percentage of the whole scrollable region by which to scroll down, ranging
107      *     from 0 - 100. For instance, percent = 50 would scroll down by half of the screen.
108      * @param durationMs The duration in milliseconds to perform the scrolling gesture.
109      */
scrollDown(float percent, long durationMs)110     public default void scrollDown(float percent, long durationMs) {
111         scroll(Direction.DOWN, percent, durationMs);
112     }
113 
114     /**
115      * Setup expectations: None.
116      *
117      * <p>This method can be implemented optionally if customized margin is required.
118      *
119      * @return the gesture margin for scrolling.
120      */
getScrollableMargin()121     public default Margin getScrollableMargin() {
122         return new Margin(DEFAULT_MARGIN);
123     }
124 
125     /**
126      * Setup expectations: None.
127      *
128      * <p>This method can be implemented optionally if customized margin is required. It sets the
129      * gesture margin returned by <code>getScrollableMargin()</code>.
130      *
131      * @param margin Left, top, right and bottom margins will all be set this this value.
132      */
setScrollableMargin(int margin)133     public default void setScrollableMargin(int margin) {
134         throw new UnsupportedOperationException("setScrollableMargin method not implemeneted.");
135     }
136 
137     /**
138      * Setup expectations: None.
139      *
140      * <p>This method can be implemented optionally if customized margin is required. It sets the
141      * gesture margin returned by <code>getScrollableMargin()</code>.
142      *
143      * @param left The value to which to set the left margin for scrollling.
144      * @param top The value to which to set the top margin for scrollling.
145      * @param right The value to which to set the right margin for scrollling.
146      * @param bottom The value to which to set the bottom margin for scrollling.
147      */
setScrollableMargin(int left, int top, int right, int bottom)148     public default void setScrollableMargin(int left, int top, int right, int bottom) {
149         throw new UnsupportedOperationException("setScrollableMargin method not implemeneted.");
150     }
151 
152     public class Margin {
153         private int mLeft;
154         private int mTop;
155         private int mRight;
156         private int mBottom;
157 
Margin(int margin)158         public Margin(int margin) {
159             mLeft = margin;
160             mTop = margin;
161             mRight = margin;
162             mBottom = margin;
163         }
164 
Margin(int left, int top, int right, int bottom)165         public Margin(int left, int top, int right, int bottom) {
166             mLeft = left;
167             mTop = top;
168             mRight = right;
169             mBottom = bottom;
170         }
171 
getLeft()172         public int getLeft() {
173             return mLeft;
174         }
175 
getTop()176         public int getTop() {
177             return mTop;
178         }
179 
getRight()180         public int getRight() {
181             return mRight;
182         }
183 
getBottom()184         public int getBottom() {
185             return mBottom;
186         }
187     }
188 
189     /**
190      * This is not part of the public interface. For internal use only.
191      *
192      * <p>Scroll in <code>direction</code> direction by <code>percent</code> percent of the whole
193      * scrollable region in <code>durationMs </code> milliseconds only if corresponding app is open.
194      *
195      * @param direction The direction in which to perform scrolling, it's either up or down.
196      * @param percent The percentage of the whole scrollable region by which to scroll, ranging from
197      *     0 - 100. For instance, percent = 50 would scroll up/down by half of the screen.
198      * @param durationMs The duration in milliseconds to perform the scrolling gesture.
199      */
scroll(Direction direction, float percent, long durationMs)200     default void scroll(Direction direction, float percent, long durationMs) {
201         if (isAppInForeground()) {
202             UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
203             UiObject2 scrollable = device.findObject(By.scrollable(true));
204 
205             if (scrollable != null) {
206                 Margin margin = getScrollableMargin();
207                 scrollable.setGestureMargins(
208                         margin.getLeft(),
209                         margin.getTop(),
210                         margin.getRight(),
211                         margin.getBottom());
212                 int scrollSpeed = calcScrollSpeed(scrollable, durationMs);
213                 scrollable.scroll(direction, percent / 100, scrollSpeed);
214             } else {
215                 throw new TestHelperException("There is nothing that can scroll.");
216             }
217         } else {
218             throw new TestHelperException("App is not open.");
219         }
220     }
221 
222     /**
223      * This is not part of the public interface. For internal use only.
224      *
225      * <p>Return the scroll speed such that it takes <code>durationMs</code> milliseconds for the
226      * device to scroll through the whole scrollable region(i.e. from the top of the scrollable
227      * region to bottom).
228      *
229      * @param scrollable The given scrollable object to scroll through.
230      * @param durationMs The duration in milliseconds to perform the scrolling gesture.
231      */
calcScrollSpeed(UiObject2 scrollable, long durationMs)232     default int calcScrollSpeed(UiObject2 scrollable, long durationMs) {
233         Rect bounds = scrollable.getVisibleBounds();
234         double durationSeconds = (double) durationMs / 1000;
235         int scrollSpeed = (int) (bounds.height() / durationSeconds);
236         return scrollSpeed;
237     }
238 }
239