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