1 /*
2  * Copyright (C) 2015 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  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and limitations under the
13  * License.
14  *
15  */
16 
17 package com.android.benchmark.ui.automation;
18 
19 import android.os.SystemClock;
20 import androidx.annotation.IntDef;
21 import android.view.MotionEvent;
22 
23 import java.util.ArrayList;
24 import java.util.List;
25 
26 /**
27  * Encodes a UI interaction as a series of MotionEvents
28  */
29 public class Interaction {
30     private static final int STEP_COUNT = 20;
31     // TODO: scale to device display density
32     private static final int DEFAULT_FLING_SIZE_PX = 500;
33     private static final int DEFAULT_FLING_DURATION_MS = 20;
34     private static final int DEFAULT_TAP_DURATION_MS = 20;
35     private List<MotionEvent> mEvents;
36 
37     // Interaction parameters
38     private final float[] mXPositions;
39     private final float[] mYPositions;
40     private final long mDuration;
41     private final int[] mKeyCodes;
42     private final @Interaction.Type int mType;
43 
44     @IntDef({
45             Interaction.Type.TAP,
46             Interaction.Type.FLING,
47             Interaction.Type.PINCH,
48             Interaction.Type.KEY_EVENT})
49     public @interface Type {
50         int TAP = 0;
51         int FLING = 1;
52         int PINCH = 2;
53         int KEY_EVENT = 3;
54     }
55 
newFling(float startX, float startY, float endX, float endY, long duration)56     public static Interaction newFling(float startX, float startY,
57                                        float endX, float endY, long duration) {
58        return new Interaction(Interaction.Type.FLING, new float[]{startX, endX},
59                new float[]{startY, endY}, duration);
60     }
61 
newFlingDown(float startX, float startY)62     public static Interaction newFlingDown(float startX, float startY) {
63         return new Interaction(Interaction.Type.FLING,
64                 new float[]{startX, startX},
65                 new float[]{startY, startY + DEFAULT_FLING_SIZE_PX}, DEFAULT_FLING_DURATION_MS);
66     }
67 
newFlingUp(float startX, float startY)68     public static Interaction newFlingUp(float startX, float startY) {
69         return new Interaction(Interaction.Type.FLING,
70                 new float[]{startX, startX}, new float[]{startY, startY - DEFAULT_FLING_SIZE_PX},
71                         DEFAULT_FLING_DURATION_MS);
72     }
73 
newTap(float startX, float startY)74     public static Interaction newTap(float startX, float startY) {
75         return new Interaction(Interaction.Type.TAP,
76                 new float[]{startX, startX}, new float[]{startY, startY},
77                 DEFAULT_FLING_DURATION_MS);
78     }
79 
newKeyInput(int[] keyCodes)80     public static Interaction newKeyInput(int[] keyCodes) {
81         return new Interaction(keyCodes);
82     }
83 
getEvents()84     public List<MotionEvent> getEvents() {
85         switch (mType) {
86             case Type.FLING:
87                 mEvents = createInterpolatedEventList(mXPositions, mYPositions, mDuration);
88                 break;
89             case Type.PINCH:
90                 break;
91             case Type.TAP:
92                 mEvents = createInterpolatedEventList(mXPositions, mYPositions, mDuration);
93                 break;
94         }
95 
96         return mEvents;
97     }
98 
getType()99     public int getType() {
100         return mType;
101     }
102 
getKeyCodes()103     public int[] getKeyCodes() {
104         return mKeyCodes;
105     }
106 
createInterpolatedEventList( float[] xPos, float[] yPos, long duration)107     private static List<MotionEvent> createInterpolatedEventList(
108             float[] xPos, float[] yPos, long duration) {
109         long startTime = SystemClock.uptimeMillis() + 100;
110         List<MotionEvent> result = new ArrayList<>();
111 
112         float startX = xPos[0];
113         float startY = yPos[0];
114 
115         MotionEvent downEvent = MotionEvent.obtain(
116                 startTime, startTime, MotionEvent.ACTION_DOWN, startX, startY, 0);
117         result.add(downEvent);
118 
119         for (int i = 1; i < xPos.length; i++) {
120             float endX = xPos[i];
121             float endY = yPos[i];
122             float stepX = (endX - startX) / STEP_COUNT;
123             float stepY = (endY - startY) / STEP_COUNT;
124             float stepT = duration / STEP_COUNT;
125 
126             for (int j = 0; j < STEP_COUNT; j++) {
127                 long deltaT = Math.round(j * stepT);
128                 long deltaX = Math.round(j * stepX);
129                 long deltaY = Math.round(j * stepY);
130                 MotionEvent moveEvent = MotionEvent.obtain(startTime, startTime + deltaT,
131                         MotionEvent.ACTION_MOVE, startX + deltaX, startY + deltaY, 0);
132                 result.add(moveEvent);
133             }
134 
135             startX = endX;
136             startY = endY;
137         }
138 
139         float lastX = xPos[xPos.length - 1];
140         float lastY = yPos[yPos.length - 1];
141         MotionEvent lastEvent = MotionEvent.obtain(startTime, startTime + duration,
142                 MotionEvent.ACTION_UP, lastX, lastY, 0);
143         result.add(lastEvent);
144 
145         return result;
146     }
147 
Interaction(@nteraction.Type int type, float[] xPos, float[] yPos, long duration)148     private Interaction(@Interaction.Type int type,
149                         float[] xPos, float[] yPos, long duration) {
150         mType = type;
151         mXPositions = xPos;
152         mYPositions = yPos;
153         mDuration = duration;
154         mKeyCodes = null;
155     }
156 
Interaction(int[] codes)157     private Interaction(int[] codes) {
158         mKeyCodes = codes;
159         mType = Type.KEY_EVENT;
160         mYPositions = null;
161         mXPositions = null;
162         mDuration = 0;
163     }
164 
Interaction(@nteraction.Type int type, List<Float> xPositions, List<Float> yPositions, long duration)165     private Interaction(@Interaction.Type int type,
166                         List<Float> xPositions, List<Float> yPositions, long duration) {
167         if (xPositions.size() != yPositions.size()) {
168             throw new IllegalArgumentException("must have equal number of x and y positions");
169         }
170 
171         int current = 0;
172         mXPositions = new float[xPositions.size()];
173         for (float p : xPositions) {
174             mXPositions[current++] = p;
175         }
176 
177         current = 0;
178         mYPositions = new float[yPositions.size()];
179         for (float p : xPositions) {
180             mXPositions[current++] = p;
181         }
182 
183         mType = type;
184         mDuration = duration;
185         mKeyCodes = null;
186     }
187 }
188