Home | History | Annotate | Download | only in actions
      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 com.google.android.droiddriver.actions;
     18 
     19 import android.graphics.Rect;
     20 import android.os.SystemClock;
     21 import android.view.ViewConfiguration;
     22 
     23 import com.google.android.droiddriver.UiElement;
     24 import com.google.android.droiddriver.exceptions.ActionException;
     25 import com.google.android.droiddriver.scroll.Direction.PhysicalDirection;
     26 import com.google.android.droiddriver.util.Events;
     27 import com.google.android.droiddriver.util.Strings;
     28 import com.google.android.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}. */
     46   public static int getScrollSteps() {
     47     return scrollSteps;
     48   }
     49 
     50   /** Sets the {@link #scrollSteps} used in {@link #toScroll}. */
     51   public static void setScrollSteps(int scrollSteps) {
     52     SwipeAction.scrollSteps = scrollSteps;
     53   }
     54 
     55   /** Returns the {@link #flingSteps} used in {@link #toFling}. */
     56   public static int getFlingSteps() {
     57     return flingSteps;
     58   }
     59 
     60   /** Sets the {@link #flingSteps} used in {@link #toFling}. */
     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    */
     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    */
     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    */
    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    */
    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
    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    */
    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
    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
    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