Home | History | Annotate | Download | only in automation
      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 android.support.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 
     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