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