content: /*
   * Copyright (C) 2014 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package androidx.test.espresso.action;
  
  import static androidx.test.internal.util.Checks.checkNotNull;
  
  import android.view.MotionEvent;
  import androidx.test.espresso.UiController;
  import com.google.errorprone.annotations.CanIgnoreReturnValue;
  import java.util.ArrayList;
  import java.util.List;
  
  /** Executes different swipe types to given positions. */
  public enum Swipe implements Swiper {
  
    /** Swipes quickly between the co-ordinates. */
    FAST {
      @Override
      public Swiper.Status sendSwipe(
          UiController uiController,
          float[] startCoordinates,
          float[] endCoordinates,
          float[] precision) {
        return sendLinearSwipe(
            uiController, startCoordinates, endCoordinates, precision, SWIPE_FAST_DURATION_MS);
      }
    },
  
    /** Swipes deliberately slowly between the co-ordinates, to aid in visual debugging. */
    SLOW {
      @Override
      public Swiper.Status sendSwipe(
          UiController uiController,
          float[] startCoordinates,
          float[] endCoordinates,
          float[] precision) {
        return sendLinearSwipe(
            uiController, startCoordinates, endCoordinates, precision, SWIPE_SLOW_DURATION_MS);
      }
    };
  
    private static final String TAG = Swipe.class.getSimpleName();
  
    /** The number of motion events to send for each swipe. */
    private static final int SWIPE_EVENT_COUNT = 10;
  
    /** Length of time a "fast" swipe should last for, in milliseconds. */
    private static final int SWIPE_FAST_DURATION_MS = 150;
  
    /** Length of time a "slow" swipe should last for, in milliseconds. */
    private static final int SWIPE_SLOW_DURATION_MS = 1500;
  
    private static float[][] interpolate(float[] start, float[] end, int steps) {
      checkElementIndex(1, start.length);
      checkElementIndex(1, end.length);
  
      float[][] res = new float[steps][2];
  
      for (int i = 1; i < steps + 1; i++) {
        res[i - 1][0] = start[0] + (end[0] - start[0]) * i / (steps + 2f);
        res[i - 1][1] = start[1] + (end[1] - start[1]) * i / (steps + 2f);
      }
  
      return res;
    }
  
    private static Swiper.Status sendLinearSwipe(
        UiController uiController,
        float[] startCoordinates,
        float[] endCoordinates,
        float[] precision,
        int duration) {
      checkNotNull(uiController);
      checkNotNull(startCoordinates);
      checkNotNull(endCoordinates);
      checkNotNull(precision);
  
      float[][] steps = interpolate(startCoordinates, endCoordinates, SWIPE_EVENT_COUNT);
  
      List<MotionEvent> events = new ArrayList<>();
      MotionEvent downEvent = MotionEvents.obtainDownEvent(startCoordinates, precision);
      events.add(downEvent);
  
      try {
        final long intervalMS = duration / steps.length;
        long eventTime = downEvent.getDownTime();
        for (float[] step : steps) {
          eventTime += intervalMS;
          events.add(MotionEvents.obtainMovement(downEvent, eventTime, step));
        }
        eventTime += intervalMS;
        events.add(MotionEvents.obtainUpEvent(downEvent, eventTime, endCoordinates));
        uiController.injectMotionEventSequence(events);
      } catch (Exception e) {
        return Swiper.Status.FAILURE;
      } finally {
        for (MotionEvent event : events) {
          event.recycle();
        }
      }
      return Swiper.Status.SUCCESS;
    }
  
    // copy of Guava's method to avoid the extra dependency
    private static int checkElementIndex(int index, int size) {
      return checkElementIndex(index, size, "index");
    }
  
    @CanIgnoreReturnValue
    private static int checkElementIndex(int index, int size, String desc) {
      // Carefully optimized for execution by hotspot (explanatory comment above)
      if (index < 0 || index >= size) {
        throw new IndexOutOfBoundsException(badElementIndex(index, size, desc));
      }
      return index;
    }
  
    private static String badElementIndex(int index, int size, String desc) {
      if (index < 0) {
        return String.format("%s (%s) must not be negative", desc, index);
      } else if (size < 0) {
        throw new IllegalArgumentException("negative size: " + size);
      } else { // index >= size
        return String.format("%s (%s) must be less than size (%s)", desc, index, size);
      }
    }
  }
  