18e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le/*
21194ec356a16f3c6dcf408289e36e42c149d6dc8Kevin Jin * Copyright (C) 2013 DroidDriver committers
38e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le *
48e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le * Licensed under the Apache License, Version 2.0 (the "License");
58e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le * you may not use this file except in compliance with the License.
68e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le * You may obtain a copy of the License at
78e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le *
88e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le *      http://www.apache.org/licenses/LICENSE-2.0
98e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le *
108e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le * Unless required by applicable law or agreed to in writing, software
118e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le * distributed under the License is distributed on an "AS IS" BASIS,
128e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le * See the License for the specific language governing permissions and
148e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le * limitations under the License.
158e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le */
168e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le
174b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinpackage io.appium.droiddriver.actions;
188e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le
19f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jinimport android.graphics.Rect;
20f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jinimport android.os.SystemClock;
21f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jinimport android.view.ViewConfiguration;
22f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
234b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinimport io.appium.droiddriver.UiElement;
244b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinimport io.appium.droiddriver.exceptions.ActionException;
254b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinimport io.appium.droiddriver.scroll.Direction.PhysicalDirection;
264b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinimport io.appium.droiddriver.util.Events;
274b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinimport io.appium.droiddriver.util.Strings;
284b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinimport io.appium.droiddriver.util.Strings.ToStringHelper;
298e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le
308e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le/**
31a738fe74f57f48dde2dd7a28479bab3f5441dadbKevin Jin * An action that swipes the touch screen.
328e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le */
33cf1203b8078bed407ed0035c201746fae136439aKevin Jinpublic class SwipeAction extends EventAction implements ScrollAction {
34f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  // Milliseconds between synthesized ACTION_MOVE events.
35ef176eeb3b29df478522c46cc100f421365b008eKevin Jin  // Note: ACTION_MOVE_INTERVAL is the minimum interval between injected events;
36ef176eeb3b29df478522c46cc100f421365b008eKevin Jin  // the actual interval typically is longer.
377c8b54f99e678a1b40b98fc3069217877ec5199cKevin Jin  private static final int ACTION_MOVE_INTERVAL = 5;
38b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin  /**
39f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * The magic number from UiAutomator. This value is empirical. If it actually
40f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * results in a fling, you can change it with {@link #setScrollSteps}.
41b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin   */
42f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  private static int scrollSteps = 55;
43f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  private static int flingSteps = 3;
4470e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin
45f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  /** Returns the {@link #scrollSteps} used in {@link #toScroll}. */
46f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  public static int getScrollSteps() {
47f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin    return scrollSteps;
48f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  }
49f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin
50f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  /** Sets the {@link #scrollSteps} used in {@link #toScroll}. */
51f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  public static void setScrollSteps(int scrollSteps) {
52f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin    SwipeAction.scrollSteps = scrollSteps;
53f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  }
54f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin
55f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  /** Returns the {@link #flingSteps} used in {@link #toFling}. */
56f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  public static int getFlingSteps() {
57f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin    return flingSteps;
58f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  }
59f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin
60f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  /** Sets the {@link #flingSteps} used in {@link #toFling}. */
61f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  public static void setFlingSteps(int flingSteps) {
62f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin    SwipeAction.flingSteps = flingSteps;
63f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin  }
64b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin
65b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin  /**
66f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * Gets {@link SwipeAction} instances for scrolling.
67f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * <p>
68f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * Note: This may result in flinging instead of scrolling, depending on the
69f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * size of the target UiElement and the SDK version of the device. If it does
70f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * not behave as expected, you can change steps with {@link #setScrollSteps}.
71f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * </p>
72f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   *
73f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * @param direction specifies where the view port will move, instead of the
74f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   *        finger.
75f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * @see ViewConfiguration#getScaledMinimumFlingVelocity
76b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin   */
7729d66eeee5d30f7db747cceeb84defec961b4125Kevin Jin  public static SwipeAction toScroll(PhysicalDirection direction) {
78f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin    return new SwipeAction(direction, scrollSteps);
79b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin  }
80b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin
81b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin  /**
82f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * Gets {@link SwipeAction} instances for flinging.
83ef176eeb3b29df478522c46cc100f421365b008eKevin Jin   * <p>
84ef176eeb3b29df478522c46cc100f421365b008eKevin Jin   * Note: This may not actually fling, depending on the size of the target
85ef176eeb3b29df478522c46cc100f421365b008eKevin Jin   * UiElement and the SDK version of the device. If it does not behave as
86f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * expected, you can change steps with {@link #setFlingSteps}.
87ef176eeb3b29df478522c46cc100f421365b008eKevin Jin   * </p>
88ef176eeb3b29df478522c46cc100f421365b008eKevin Jin   *
89f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * @param direction specifies where the view port will move, instead of the
90f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   *        finger.
91ef176eeb3b29df478522c46cc100f421365b008eKevin Jin   * @see ViewConfiguration#getScaledMinimumFlingVelocity
92b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin   */
93b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin  public static SwipeAction toFling(PhysicalDirection direction) {
94f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin    return new SwipeAction(direction, flingSteps);
9570e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin  }
968e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le
9729d66eeee5d30f7db747cceeb84defec961b4125Kevin Jin  private final PhysicalDirection direction;
988e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le  private final boolean drag;
99b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin  private final int steps;
1009031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin  private final float topMarginRatio;
1019031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin  private final float leftMarginRatio;
1029031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin  private final float bottomMarginRatio;
1039031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin  private final float rightMarginRatio;
1048e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le
10521a0001e2426644dd68e6140b5873ebaeafcc3dcKevin Jin  /**
106b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin   * Defaults timeoutMillis to 1000 and no drag.
107b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin   */
108b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin  public SwipeAction(PhysicalDirection direction, int steps) {
109b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin    this(direction, steps, false, 1000L);
110b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin  }
111b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin
112b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin  /**
1139031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin   * Defaults all margin ratios to 0.1F.
1149031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin   */
1159031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin  public SwipeAction(PhysicalDirection direction, int steps, boolean drag, long timeoutMillis) {
116f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin    this(direction, steps, drag, timeoutMillis, 0.1F, 0.1F, 0.1F, 0.1F);
1179031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin  }
1189031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin
1199031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin  /**
120f1fd9d00b1c6add0647f8cb7a272cff75ec8e2c7Kevin Jin   * @param direction the scroll direction specifying where the view port will
121b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin   *        move, instead of the finger.
122ef176eeb3b29df478522c46cc100f421365b008eKevin Jin   * @param steps minimum 2; (steps-1) is the number of {@code ACTION_MOVE} that
123ef176eeb3b29df478522c46cc100f421365b008eKevin Jin   *        will be injected between {@code ACTION_DOWN} and {@code ACTION_UP}.
124b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin   * @param drag whether this is a drag
125e66c531bb9bd973a8dfd76cf5c404b5dc03facfeKevin Jin   * @param timeoutMillis the value returned by {@link #getTimeoutMillis}
1269031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin   * @param topMarginRatio margin ratio from top
1279031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin   * @param leftMarginRatio margin ratio from left
1289031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin   * @param bottomMarginRatio margin ratio from bottom
1299031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin   * @param rightMarginRatio margin ratio from right
130b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin   */
1319031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin  public SwipeAction(PhysicalDirection direction, int steps, boolean drag, long timeoutMillis,
1329031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin      float topMarginRatio, float leftMarginRatio, float bottomMarginRatio, float rightMarginRatio) {
13321a0001e2426644dd68e6140b5873ebaeafcc3dcKevin Jin    super(timeoutMillis);
1348e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    this.direction = direction;
13517342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin    this.steps = Math.max(2, steps);
1368e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    this.drag = drag;
1379031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin    this.topMarginRatio = topMarginRatio;
1389031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin    this.bottomMarginRatio = bottomMarginRatio;
1399031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin    this.leftMarginRatio = leftMarginRatio;
1409031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin    this.rightMarginRatio = rightMarginRatio;
1418e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le  }
1428e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le
1438e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le  @Override
1448e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le  public boolean perform(InputInjector injector, UiElement element) {
145f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    Rect elementRect = element.getVisibleBounds();
1468e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le
1479031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin    int topMargin = (int) (elementRect.height() * topMarginRatio);
1489031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin    int bottomMargin = (int) (elementRect.height() * bottomMarginRatio);
1499031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin    int leftMargin = (int) (elementRect.width() * leftMarginRatio);
1509031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin    int rightMargin = (int) (elementRect.width() * rightMarginRatio);
1519031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin    int adjustedbottom = elementRect.bottom - bottomMargin;
1529031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin    int adjustedTop = elementRect.top + topMargin;
1539031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin    int adjustedLeft = elementRect.left + leftMargin;
1549031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin    int adjustedRight = elementRect.right - rightMargin;
1558e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    int startX;
1568e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    int startY;
1578e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    int endX;
1588e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    int endY;
1598e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le
1608e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    switch (direction) {
1618e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le      case DOWN:
1628e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        startX = elementRect.centerX();
1639031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin        startY = adjustedbottom;
1648e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        endX = elementRect.centerX();
1659031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin        endY = adjustedTop;
1668e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        break;
1678e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le      case UP:
1688e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        startX = elementRect.centerX();
1699031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin        startY = adjustedTop;
1708e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        endX = elementRect.centerX();
1719031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin        endY = adjustedbottom;
1728e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        break;
1738e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le      case LEFT:
1749031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin        startX = adjustedLeft;
1758e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        startY = elementRect.centerY();
1769031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin        endX = adjustedRight;
1778e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        endY = elementRect.centerY();
1788e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        break;
1798e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le      case RIGHT:
1809031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin        startX = adjustedRight;
1818e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        startY = elementRect.centerY();
1829031ed9b636ccd3b942eefb85dbfae2aed9e4f11Kevin Jin        endX = adjustedLeft;
1838e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        endY = elementRect.centerY();
1848e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        break;
1858e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le      default:
1868e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le        throw new ActionException("Unknown scroll direction: " + direction);
1878e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    }
1888e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le
189f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    double xStep = ((double) (endX - startX)) / steps;
190f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    double yStep = ((double) (endY - startY)) / steps;
1918e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le
1928e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    // First touch starts exactly at the point requested
193f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    long downTime = Events.touchDown(injector, startX, startY);
1948d19bb634c670a49f7a58636a2a535c86b57d538Kevin Jin    SystemClock.sleep(ACTION_MOVE_INTERVAL);
1958e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    if (drag) {
1968e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le      SystemClock.sleep((long) (ViewConfiguration.getLongPressTimeout() * 1.5f));
1978e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    }
1988e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    for (int i = 1; i < steps; i++) {
199f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      Events.touchMove(injector, downTime, startX + (int) (xStep * i), startY + (int) (yStep * i));
2007c8b54f99e678a1b40b98fc3069217877ec5199cKevin Jin      SystemClock.sleep(ACTION_MOVE_INTERVAL);
2018e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    }
2028e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    if (drag) {
2038e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le      // Hold final position for a little bit to simulate drag.
2048e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le      SystemClock.sleep(100);
2058e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le    }
206f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    Events.touchUp(injector, downTime, endX, endY);
207f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    return true;
2088e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le  }
20970e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin
21070e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin  @Override
21170e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin  public String toString() {
21217342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin    ToStringHelper toStringHelper = Strings.toStringHelper(this);
213b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin    toStringHelper.addValue(direction);
214b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin    toStringHelper.add("steps", steps);
21570e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin    if (drag) {
21670e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin      toStringHelper.addValue("drag");
21770e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin    }
21870e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin    return toStringHelper.toString();
21970e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin  }
2208e610ed585685c55e2cfd010b4233eafc7d568c2Thanh Le}
221