Interaction.java revision b5fbd41b23bf309e6b420a3df4641603d55dcb68
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
17package com.android.benchmark.ui.automation;
18
19import android.os.SystemClock;
20import android.support.annotation.IntDef;
21import android.view.MotionEvent;
22
23import java.util.ArrayList;
24import java.util.List;
25
26/**
27 * Encodes a UI interaction as a series of MotionEvents
28 */
29public 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
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
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
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
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
80    public static Interaction newKeyInput(int[] keyCodes) {
81        return new Interaction(keyCodes);
82    }
83
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
99    public int getType() {
100        return mType;
101    }
102
103    public int[] getKeyCodes() {
104        return mKeyCodes;
105    }
106
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
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
157    private Interaction(int[] codes) {
158        mKeyCodes = codes;
159        mType = Type.KEY_EVENT;
160        mYPositions = null;
161        mXPositions = null;
162        mDuration = 0;
163    }
164
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