title: Android UI Test Long Click and Drag
body: <p>In my application, I have a feature such that a user is able to re-order items in a list, by long clicking to create a drag shadow, which the user can then drag and insert at a position of choice. Once dropped, the items are re-ordered.</p>
  
  <p>I am struggling to develop a UI test for this. I am able to either successfully long click on the item, to create the drag shadow OR implement a dragging motion. I seem unable to combine the two, into one motion.</p>
  
  <p>I am using Espresso and Barista in my Android UI Tests. </p>
  
  <p>For the long click I used Barista's API:</p>
  
  <pre><code>longClickOn("ITEM");
  </code></pre>
  
  <p>For the dragging motion, I attempted to create my own Espresso ViewAction: </p>
  
  <pre><code>return new ViewAction() {
          @Override
          public Matcher&lt;View&gt; getConstraints() {
              return isAssignableFrom(ViewGroup.class);
          }
  
          @Override
          public String getDescription() {
              return "Swiping child " + srcIndex + " to child " + destIndex;
          }
  
          @Override
          public void perform(UiController uiController, View view) {
              ViewGroup parent = (ViewGroup) view;
  
              final View srcChild = parent.getChildAt(srcIndex);
              final View destChild = parent.getChildAt(destIndex);
  
              final CoordinatesProvider srcCoordinatesProvider = new CoordinatesProvider() {
                  @Override
                  public float[] calculateCoordinates(View view) {
                      int[] location = new int[2];
                      srcChild.getLocationInWindow(location);
                      float x = location[0] + (view.getMeasuredWidth() / 2);
                      float y = location[1] + (view.getMeasuredHeight() / 2);
  
                      return new float[] {x, y};
                  }
              };
  
              final CoordinatesProvider destCoordinatesProvider = new CoordinatesProvider() {
                  @Override
                  public float[] calculateCoordinates(View view) {
                      int[] location = new int[2];
                      destChild.getLocationInWindow(location);
                      float x = location[0] + (view.getMeasuredWidth() / 2);
                      float y = location[1] + (view.getMeasuredHeight() / 2);
  
                      return new float[] {x, y};
                  }
              };
  
              GeneralSwipeAction swipe = new GeneralSwipeAction(Swipe.FAST,
                      srcCoordinatesProvider, destCoordinatesProvider, Press.FINGER);
              swipe.perform(uiController, parent);
          }
      };
  </code></pre>
  
  <hr>
  
  <p>EDIT:</p>
  
  <p>Following the answer from @Be_Negative, I've customised the given answer and came up with this:</p>
  
  <pre><code>private static ViewAction drag(final int srcIndex, final int destIndex) {
      return new ViewAction() {
          @Override
          public Matcher&lt;View&gt; getConstraints() {
              return isAssignableFrom(ViewGroup.class);
          }
  
          @Override
          public String getDescription() {
              return "Swiping child " + srcIndex + " to child " + destIndex;
          }
  
          @Override
          public void perform(UiController uiController, View view) {
              ViewGroup parent = (ViewGroup) view;
  
              uiController.loopMainThreadUntilIdle();
              final View srcChild = parent.getChildAt(srcIndex);
              final View destChild = parent.getChildAt(destIndex);
  
              final CoordinatesProvider coordinatesProvider = getCoordinatesProdvider();
  
              float[] precision = Press.PINPOINT.describePrecision();
              MotionEvent downEvent = MotionEvents.sendDown(uiController, coordinatesProvider.calculateCoordinates(srcChild), precision).down;
  
              try {
                  long longPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5);
                  uiController.loopMainThreadForAtLeast(longPressTimeout);
  
                  float[][] steps = interpolateDragging(
                          coordinatesProvider.calculateCoordinates(srcChild),
                          coordinatesProvider.calculateCoordinates(destChild)
                  );
  
                  uiController.loopMainThreadUntilIdle();
  
                  for(float[] step : steps) {
                      if( !MotionEvents.sendMovement(uiController, downEvent, step)) {
                          MotionEvents.sendCancel(uiController, downEvent);
                      }
                  }
  
                  if(!MotionEvents.sendUp(uiController, downEvent, coordinatesProvider.calculateCoordinates(destChild))) {
                      MotionEvents.sendCancel(uiController, downEvent);
                  }
              } catch(Exception e) {
                  System.out.println(e);
              } finally {
                  downEvent.recycle();
              }
          }
      };
  }
  </code></pre>
  
  <p>Unforunately, it throws an error at <code>MotionEvents.sendMovement</code> as follows: </p>
  
  <pre><code>Error performing 'inject motion event (corresponding down event: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=540.0, y[0]=302.0, toolType[0]=TOOL_TYPE_UNKNOWN, buttonState=BUTTON_PRIMARY, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=34586525, downTime=34586525, deviceId=0, source=0x1002 })' on view 'unknown'.
  </code></pre>