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