1 /* 2 * Copyright (C) 2013 DroidDriver committers 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 io.appium.droiddriver.actions; 18 19 import android.graphics.Rect; 20 import android.os.SystemClock; 21 import android.view.ViewConfiguration; 22 23 import io.appium.droiddriver.UiElement; 24 import io.appium.droiddriver.exceptions.ActionException; 25 import io.appium.droiddriver.scroll.Direction.PhysicalDirection; 26 import io.appium.droiddriver.util.Events; 27 import io.appium.droiddriver.util.Strings; 28 import io.appium.droiddriver.util.Strings.ToStringHelper; 29 30 /** 31 * An action that swipes the touch screen. 32 */ 33 public class SwipeAction extends EventAction implements ScrollAction { 34 // Milliseconds between synthesized ACTION_MOVE events. 35 // Note: ACTION_MOVE_INTERVAL is the minimum interval between injected events; 36 // the actual interval typically is longer. 37 private static final int ACTION_MOVE_INTERVAL = 5; 38 /** 39 * The magic number from UiAutomator. This value is empirical. If it actually 40 * results in a fling, you can change it with {@link #setScrollSteps}. 41 */ 42 private static int scrollSteps = 55; 43 private static int flingSteps = 3; 44 45 /** Returns the {@link #scrollSteps} used in {@link #toScroll}. */ getScrollSteps()46 public static int getScrollSteps() { 47 return scrollSteps; 48 } 49 50 /** Sets the {@link #scrollSteps} used in {@link #toScroll}. */ setScrollSteps(int scrollSteps)51 public static void setScrollSteps(int scrollSteps) { 52 SwipeAction.scrollSteps = scrollSteps; 53 } 54 55 /** Returns the {@link #flingSteps} used in {@link #toFling}. */ getFlingSteps()56 public static int getFlingSteps() { 57 return flingSteps; 58 } 59 60 /** Sets the {@link #flingSteps} used in {@link #toFling}. */ setFlingSteps(int flingSteps)61 public static void setFlingSteps(int flingSteps) { 62 SwipeAction.flingSteps = flingSteps; 63 } 64 65 /** 66 * Gets {@link SwipeAction} instances for scrolling. 67 * <p> 68 * Note: This may result in flinging instead of scrolling, depending on the 69 * size of the target UiElement and the SDK version of the device. If it does 70 * not behave as expected, you can change steps with {@link #setScrollSteps}. 71 * </p> 72 * 73 * @param direction specifies where the view port will move, instead of the 74 * finger. 75 * @see ViewConfiguration#getScaledMinimumFlingVelocity 76 */ toScroll(PhysicalDirection direction)77 public static SwipeAction toScroll(PhysicalDirection direction) { 78 return new SwipeAction(direction, scrollSteps); 79 } 80 81 /** 82 * Gets {@link SwipeAction} instances for flinging. 83 * <p> 84 * Note: This may not actually fling, depending on the size of the target 85 * UiElement and the SDK version of the device. If it does not behave as 86 * expected, you can change steps with {@link #setFlingSteps}. 87 * </p> 88 * 89 * @param direction specifies where the view port will move, instead of the 90 * finger. 91 * @see ViewConfiguration#getScaledMinimumFlingVelocity 92 */ toFling(PhysicalDirection direction)93 public static SwipeAction toFling(PhysicalDirection direction) { 94 return new SwipeAction(direction, flingSteps); 95 } 96 97 private final PhysicalDirection direction; 98 private final boolean drag; 99 private final int steps; 100 private final float topMarginRatio; 101 private final float leftMarginRatio; 102 private final float bottomMarginRatio; 103 private final float rightMarginRatio; 104 105 /** 106 * Defaults timeoutMillis to 1000 and no drag. 107 */ SwipeAction(PhysicalDirection direction, int steps)108 public SwipeAction(PhysicalDirection direction, int steps) { 109 this(direction, steps, false, 1000L); 110 } 111 112 /** 113 * Defaults all margin ratios to 0.1F. 114 */ SwipeAction(PhysicalDirection direction, int steps, boolean drag, long timeoutMillis)115 public SwipeAction(PhysicalDirection direction, int steps, boolean drag, long timeoutMillis) { 116 this(direction, steps, drag, timeoutMillis, 0.1F, 0.1F, 0.1F, 0.1F); 117 } 118 119 /** 120 * @param direction the scroll direction specifying where the view port will 121 * move, instead of the finger. 122 * @param steps minimum 2; (steps-1) is the number of {@code ACTION_MOVE} that 123 * will be injected between {@code ACTION_DOWN} and {@code ACTION_UP}. 124 * @param drag whether this is a drag 125 * @param timeoutMillis the value returned by {@link #getTimeoutMillis} 126 * @param topMarginRatio margin ratio from top 127 * @param leftMarginRatio margin ratio from left 128 * @param bottomMarginRatio margin ratio from bottom 129 * @param rightMarginRatio margin ratio from right 130 */ SwipeAction(PhysicalDirection direction, int steps, boolean drag, long timeoutMillis, float topMarginRatio, float leftMarginRatio, float bottomMarginRatio, float rightMarginRatio)131 public SwipeAction(PhysicalDirection direction, int steps, boolean drag, long timeoutMillis, 132 float topMarginRatio, float leftMarginRatio, float bottomMarginRatio, float rightMarginRatio) { 133 super(timeoutMillis); 134 this.direction = direction; 135 this.steps = Math.max(2, steps); 136 this.drag = drag; 137 this.topMarginRatio = topMarginRatio; 138 this.bottomMarginRatio = bottomMarginRatio; 139 this.leftMarginRatio = leftMarginRatio; 140 this.rightMarginRatio = rightMarginRatio; 141 } 142 143 @Override perform(InputInjector injector, UiElement element)144 public boolean perform(InputInjector injector, UiElement element) { 145 Rect elementRect = element.getVisibleBounds(); 146 147 int topMargin = (int) (elementRect.height() * topMarginRatio); 148 int bottomMargin = (int) (elementRect.height() * bottomMarginRatio); 149 int leftMargin = (int) (elementRect.width() * leftMarginRatio); 150 int rightMargin = (int) (elementRect.width() * rightMarginRatio); 151 int adjustedbottom = elementRect.bottom - bottomMargin; 152 int adjustedTop = elementRect.top + topMargin; 153 int adjustedLeft = elementRect.left + leftMargin; 154 int adjustedRight = elementRect.right - rightMargin; 155 int startX; 156 int startY; 157 int endX; 158 int endY; 159 160 switch (direction) { 161 case DOWN: 162 startX = elementRect.centerX(); 163 startY = adjustedbottom; 164 endX = elementRect.centerX(); 165 endY = adjustedTop; 166 break; 167 case UP: 168 startX = elementRect.centerX(); 169 startY = adjustedTop; 170 endX = elementRect.centerX(); 171 endY = adjustedbottom; 172 break; 173 case LEFT: 174 startX = adjustedLeft; 175 startY = elementRect.centerY(); 176 endX = adjustedRight; 177 endY = elementRect.centerY(); 178 break; 179 case RIGHT: 180 startX = adjustedRight; 181 startY = elementRect.centerY(); 182 endX = adjustedLeft; 183 endY = elementRect.centerY(); 184 break; 185 default: 186 throw new ActionException("Unknown scroll direction: " + direction); 187 } 188 189 double xStep = ((double) (endX - startX)) / steps; 190 double yStep = ((double) (endY - startY)) / steps; 191 192 // First touch starts exactly at the point requested 193 long downTime = Events.touchDown(injector, startX, startY); 194 SystemClock.sleep(ACTION_MOVE_INTERVAL); 195 if (drag) { 196 SystemClock.sleep((long) (ViewConfiguration.getLongPressTimeout() * 1.5f)); 197 } 198 for (int i = 1; i < steps; i++) { 199 Events.touchMove(injector, downTime, startX + (int) (xStep * i), startY + (int) (yStep * i)); 200 SystemClock.sleep(ACTION_MOVE_INTERVAL); 201 } 202 if (drag) { 203 // Hold final position for a little bit to simulate drag. 204 SystemClock.sleep(100); 205 } 206 Events.touchUp(injector, downTime, endX, endY); 207 return true; 208 } 209 210 @Override toString()211 public String toString() { 212 ToStringHelper toStringHelper = Strings.toStringHelper(this); 213 toStringHelper.addValue(direction); 214 toStringHelper.add("steps", steps); 215 if (drag) { 216 toStringHelper.addValue("drag"); 217 } 218 return toStringHelper.toString(); 219 } 220 } 221