1 /* 2 * Copyright (C) 2014 The Android Open Source Project 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 androidx.test.uiautomator; 18 19 import android.graphics.Point; 20 21 import org.jspecify.annotations.NonNull; 22 23 import java.util.ArrayDeque; 24 import java.util.Deque; 25 26 /** 27 * A {@link PointerGesture} represents the actions of a single pointer when performing a gesture. 28 */ 29 class PointerGesture { 30 // The list of actions that make up this gesture. 31 private final Deque<PointerAction> mActions = new ArrayDeque<>(); 32 private final long mDelay; 33 private final int mDisplayId; 34 private final int mWindowId; 35 private long mDuration; 36 37 /** 38 * Constructs a PointerGesture which touches down at the given start point on the given display. 39 */ PointerGesture(Point startPoint, int displayId, int windowId)40 PointerGesture(Point startPoint, int displayId, int windowId) { 41 this(startPoint, 0, displayId, windowId); 42 } 43 44 /** 45 * Constructs a PointerGesture which touches down at the given start point on the give display 46 * after a given delay. Used in multi-point gestures when the pointers do not all touch down at 47 * the same time. 48 */ PointerGesture(Point startPoint, long initialDelay, int displayId, int windowId)49 PointerGesture(Point startPoint, long initialDelay, int displayId, int windowId) { 50 if (initialDelay < 0) { 51 throw new IllegalArgumentException("initialDelay cannot be negative"); 52 } 53 mActions.addFirst(new PointerPauseAction(startPoint, 0)); 54 mDelay = initialDelay; 55 mDisplayId = displayId; 56 mWindowId = windowId; 57 } 58 displayId()59 public int displayId() { 60 return mDisplayId; 61 } 62 windowId()63 public int windowId() { 64 return mWindowId; 65 } 66 67 /** Adds an action which pauses for the specified amount of {@code time} in milliseconds. */ pause(long time)68 public PointerGesture pause(long time) { 69 if (time < 0) { 70 throw new IllegalArgumentException("time cannot be negative"); 71 } 72 mActions.addLast(new PointerPauseAction(mActions.peekLast().end, time)); 73 mDuration += mActions.peekLast().duration; 74 return this; 75 } 76 77 /** Adds an action that moves the pointer to {@code dest} at {@code speed} pixels per second. */ move(Point dest, int speed)78 public PointerGesture move(Point dest, int speed) { 79 mActions.addLast(new PointerLinearMoveAction(mActions.peekLast().end, dest, speed)); 80 mDuration += mActions.peekLast().duration; 81 return this; 82 } 83 84 /** Returns the start point of this gesture. */ start()85 public Point start() { 86 return mActions.peekFirst().start; 87 } 88 89 /** Returns the end point of this gesture. */ end()90 public Point end() { 91 return mActions.peekLast().end; 92 } 93 94 /** Returns the duration of this gesture. */ duration()95 public long duration() { 96 return mDuration; 97 } 98 99 /** Returns the amount of delay before this gesture starts. */ delay()100 public long delay() { 101 return mDelay; 102 } 103 104 /** Returns the pointer location at {@code time} milliseconds into this gesture. */ pointAt(long time)105 public Point pointAt(long time) { 106 if (time < 0) { 107 throw new IllegalArgumentException("Time cannot be negative"); 108 } 109 time -= mDelay; 110 for (PointerAction action : mActions) { 111 if (time < action.duration) { 112 return action.interpolate((float)time / action.duration); 113 } 114 time -= action.duration; 115 } 116 return mActions.peekLast().end; 117 } 118 119 @Override toString()120 public @NonNull String toString() { 121 return mActions.toString(); 122 } 123 124 /** A {@link PointerAction} represents part of a {@link PointerGesture}. */ 125 private static abstract class PointerAction { 126 final Point start; 127 final Point end; 128 final long duration; 129 PointerAction(Point startPoint, Point endPoint, long time)130 public PointerAction(Point startPoint, Point endPoint, long time) { 131 start = startPoint; 132 end = endPoint; 133 duration = time; 134 } 135 interpolate(float fraction)136 public abstract Point interpolate(float fraction); 137 } 138 139 /** A {@link PointerPauseAction} holds the pointer steady for the given amount of time. */ 140 private static class PointerPauseAction extends PointerAction { 141 PointerPauseAction(Point startPoint, long time)142 public PointerPauseAction(Point startPoint, long time) { 143 super(startPoint, startPoint, time); 144 } 145 146 @Override interpolate(float fraction)147 public Point interpolate(float fraction) { 148 return new Point(start); 149 } 150 151 @Override toString()152 public @NonNull String toString() { 153 return String.format("Pause(point=%s, duration=%dms)", start, duration); 154 } 155 } 156 157 /** Action that moves the pointer between two points at a constant speed. */ 158 private static class PointerLinearMoveAction extends PointerAction { 159 PointerLinearMoveAction(Point startPoint, Point endPoint, int speed)160 public PointerLinearMoveAction(Point startPoint, Point endPoint, int speed) { 161 super(startPoint, endPoint, (long)(1000 * calcDistance(startPoint, endPoint) / speed)); 162 } 163 164 @Override interpolate(float fraction)165 public Point interpolate(float fraction) { 166 Point ret = new Point(start); 167 ret.offset((int)(fraction * (end.x - start.x)), (int)(fraction * (end.y - start.y))); 168 return ret; 169 } 170 171 @Override toString()172 public @NonNull String toString() { 173 return String.format("Move(start=%s, end=%s, duration=%dms)", start, end, duration); 174 } 175 calcDistance(final Point a, final Point b)176 private static double calcDistance(final Point a, final Point b) { 177 return Math.sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y)); 178 } 179 } 180 } 181