Home | History | Annotate | Download | only in monkey
      1 /*
      2  * Copyright (C) 2008 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 com.android.commands.monkey;
     18 
     19 import android.content.ComponentName;
     20 import android.os.SystemClock;
     21 import android.view.Display;
     22 import android.view.KeyCharacterMap;
     23 import android.view.KeyEvent;
     24 import android.view.MotionEvent;
     25 import android.view.WindowManagerImpl;
     26 
     27 import java.util.ArrayList;
     28 import java.util.Random;
     29 
     30 /**
     31  * monkey event queue
     32  */
     33 public class MonkeySourceRandom implements MonkeyEventSource {
     34     /** Key events that move around the UI. */
     35     private static final int[] NAV_KEYS = {
     36         KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN,
     37         KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT,
     38     };
     39     /**
     40      * Key events that perform major navigation options (so shouldn't be sent
     41      * as much).
     42      */
     43     private static final int[] MAJOR_NAV_KEYS = {
     44         KeyEvent.KEYCODE_MENU, /*KeyEvent.KEYCODE_SOFT_RIGHT,*/
     45         KeyEvent.KEYCODE_DPAD_CENTER,
     46     };
     47     /** Key events that perform system operations. */
     48     private static final int[] SYS_KEYS = {
     49         KeyEvent.KEYCODE_HOME, KeyEvent.KEYCODE_BACK,
     50         KeyEvent.KEYCODE_CALL, KeyEvent.KEYCODE_ENDCALL,
     51         KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN,
     52         KeyEvent.KEYCODE_MUTE,
     53     };
     54     /** If a physical key exists? */
     55     private static final boolean[] PHYSICAL_KEY_EXISTS = new boolean[KeyEvent.getMaxKeyCode() + 1];
     56     static {
     57         for (int i = 0; i < PHYSICAL_KEY_EXISTS.length; ++i) {
     58             PHYSICAL_KEY_EXISTS[i] = true;
     59         }
     60         // Only examine SYS_KEYS
     61         for (int i = 0; i < SYS_KEYS.length; ++i) {
     62             PHYSICAL_KEY_EXISTS[SYS_KEYS[i]] = KeyCharacterMap.deviceHasKey(SYS_KEYS[i]);
     63         }
     64     }
     65 
     66     /** Nice names for all key events. */
     67     private static final String[] KEY_NAMES = {
     68         "KEYCODE_UNKNOWN",
     69         "KEYCODE_SOFT_LEFT",
     70         "KEYCODE_SOFT_RIGHT",
     71         "KEYCODE_HOME",
     72         "KEYCODE_BACK",
     73         "KEYCODE_CALL",
     74         "KEYCODE_ENDCALL",
     75         "KEYCODE_0",
     76         "KEYCODE_1",
     77         "KEYCODE_2",
     78         "KEYCODE_3",
     79         "KEYCODE_4",
     80         "KEYCODE_5",
     81         "KEYCODE_6",
     82         "KEYCODE_7",
     83         "KEYCODE_8",
     84         "KEYCODE_9",
     85         "KEYCODE_STAR",
     86         "KEYCODE_POUND",
     87         "KEYCODE_DPAD_UP",
     88         "KEYCODE_DPAD_DOWN",
     89         "KEYCODE_DPAD_LEFT",
     90         "KEYCODE_DPAD_RIGHT",
     91         "KEYCODE_DPAD_CENTER",
     92         "KEYCODE_VOLUME_UP",
     93         "KEYCODE_VOLUME_DOWN",
     94         "KEYCODE_POWER",
     95         "KEYCODE_CAMERA",
     96         "KEYCODE_CLEAR",
     97         "KEYCODE_A",
     98         "KEYCODE_B",
     99         "KEYCODE_C",
    100         "KEYCODE_D",
    101         "KEYCODE_E",
    102         "KEYCODE_F",
    103         "KEYCODE_G",
    104         "KEYCODE_H",
    105         "KEYCODE_I",
    106         "KEYCODE_J",
    107         "KEYCODE_K",
    108         "KEYCODE_L",
    109         "KEYCODE_M",
    110         "KEYCODE_N",
    111         "KEYCODE_O",
    112         "KEYCODE_P",
    113         "KEYCODE_Q",
    114         "KEYCODE_R",
    115         "KEYCODE_S",
    116         "KEYCODE_T",
    117         "KEYCODE_U",
    118         "KEYCODE_V",
    119         "KEYCODE_W",
    120         "KEYCODE_X",
    121         "KEYCODE_Y",
    122         "KEYCODE_Z",
    123         "KEYCODE_COMMA",
    124         "KEYCODE_PERIOD",
    125         "KEYCODE_ALT_LEFT",
    126         "KEYCODE_ALT_RIGHT",
    127         "KEYCODE_SHIFT_LEFT",
    128         "KEYCODE_SHIFT_RIGHT",
    129         "KEYCODE_TAB",
    130         "KEYCODE_SPACE",
    131         "KEYCODE_SYM",
    132         "KEYCODE_EXPLORER",
    133         "KEYCODE_ENVELOPE",
    134         "KEYCODE_ENTER",
    135         "KEYCODE_DEL",
    136         "KEYCODE_GRAVE",
    137         "KEYCODE_MINUS",
    138         "KEYCODE_EQUALS",
    139         "KEYCODE_LEFT_BRACKET",
    140         "KEYCODE_RIGHT_BRACKET",
    141         "KEYCODE_BACKSLASH",
    142         "KEYCODE_SEMICOLON",
    143         "KEYCODE_APOSTROPHE",
    144         "KEYCODE_SLASH",
    145         "KEYCODE_AT",
    146         "KEYCODE_NUM",
    147         "KEYCODE_HEADSETHOOK",
    148         "KEYCODE_FOCUS",
    149         "KEYCODE_PLUS",
    150         "KEYCODE_MENU",
    151         "KEYCODE_NOTIFICATION",
    152         "KEYCODE_SEARCH",
    153         "KEYCODE_PLAYPAUSE",
    154         "KEYCODE_STOP",
    155         "KEYCODE_NEXTSONG",
    156         "KEYCODE_PREVIOUSSONG",
    157         "KEYCODE_REWIND",
    158         "KEYCODE_FORWARD",
    159         "KEYCODE_MUTE",
    160         "KEYCODE_PAGE_UP",
    161         "KEYCODE_PAGE_DOWN",
    162         "KEYCODE_PICTSYMBOLS",
    163         "KEYCODE_SWITCH_CHARSET",
    164         "KEYCODE_BUTTON_A",
    165         "KEYCODE_BUTTON_B",
    166         "KEYCODE_BUTTON_C",
    167         "KEYCODE_BUTTON_X",
    168         "KEYCODE_BUTTON_Y",
    169         "KEYCODE_BUTTON_Z",
    170         "KEYCODE_BUTTON_L1",
    171         "KEYCODE_BUTTON_R1",
    172         "KEYCODE_BUTTON_L2",
    173         "KEYCODE_BUTTON_R2",
    174         "KEYCODE_BUTTON_THUMBL",
    175         "KEYCODE_BUTTON_THUMBR",
    176         "KEYCODE_BUTTON_START",
    177         "KEYCODE_BUTTON_SELECT",
    178         "KEYCODE_BUTTON_MODE",
    179 
    180         "TAG_LAST_KEYCODE"      // EOL.  used to keep the lists in sync
    181     };
    182 
    183     public static final int FACTOR_TOUCH        = 0;
    184     public static final int FACTOR_MOTION       = 1;
    185     public static final int FACTOR_TRACKBALL    = 2;
    186     public static final int FACTOR_NAV          = 3;
    187     public static final int FACTOR_MAJORNAV     = 4;
    188     public static final int FACTOR_SYSOPS       = 5;
    189     public static final int FACTOR_APPSWITCH    = 6;
    190     public static final int FACTOR_FLIP         = 7;
    191     public static final int FACTOR_ANYTHING     = 8;
    192     public static final int FACTORZ_COUNT       = 9;    // should be last+1
    193 
    194 
    195     /** percentages for each type of event.  These will be remapped to working
    196      * values after we read any optional values.
    197      **/
    198     private float[] mFactors = new float[FACTORZ_COUNT];
    199     private ArrayList<ComponentName> mMainApps;
    200     private int mEventCount = 0;  //total number of events generated so far
    201     private MonkeyEventQueue mQ;
    202     private Random mRandom;
    203     private int mVerbose = 0;
    204     private long mThrottle = 0;
    205 
    206     private boolean mKeyboardOpen = false;
    207 
    208     /**
    209      * @return the last name in the key list
    210      */
    211     public static String getLastKeyName() {
    212         return KEY_NAMES[KeyEvent.getMaxKeyCode() + 1];
    213     }
    214 
    215     public static String getKeyName(int keycode) {
    216         return KEY_NAMES[keycode];
    217     }
    218 
    219     /**
    220      * Looks up the keyCode from a given KEYCODE_NAME.  NOTE: This may
    221      * be an expensive operation.
    222      *
    223      * @param keyName the name of the KEYCODE_VALUE to lookup.
    224      * @returns the intenger keyCode value, or -1 if not found
    225      */
    226     public static int getKeyCode(String keyName) {
    227         for (int x = 0; x < KEY_NAMES.length; x++) {
    228             if (KEY_NAMES[x].equals(keyName)) {
    229                 return x;
    230             }
    231         }
    232         return -1;
    233     }
    234 
    235     public MonkeySourceRandom(Random random, ArrayList<ComponentName> MainApps,
    236             long throttle, boolean randomizeThrottle) {
    237         // default values for random distributions
    238         // note, these are straight percentages, to match user input (cmd line args)
    239         // but they will be converted to 0..1 values before the main loop runs.
    240         mFactors[FACTOR_TOUCH] = 15.0f;
    241         mFactors[FACTOR_MOTION] = 10.0f;
    242         mFactors[FACTOR_TRACKBALL] = 15.0f;
    243         mFactors[FACTOR_NAV] = 25.0f;
    244         mFactors[FACTOR_MAJORNAV] = 15.0f;
    245         mFactors[FACTOR_SYSOPS] = 2.0f;
    246         mFactors[FACTOR_APPSWITCH] = 2.0f;
    247         mFactors[FACTOR_FLIP] = 1.0f;
    248         mFactors[FACTOR_ANYTHING] = 15.0f;
    249 
    250         mRandom = random;
    251         mMainApps = MainApps;
    252         mQ = new MonkeyEventQueue(random, throttle, randomizeThrottle);
    253     }
    254 
    255     /**
    256      * Adjust the percentages (after applying user values) and then normalize to a 0..1 scale.
    257      */
    258     private boolean adjustEventFactors() {
    259         // go through all values and compute totals for user & default values
    260         float userSum = 0.0f;
    261         float defaultSum = 0.0f;
    262         int defaultCount = 0;
    263         for (int i = 0; i < FACTORZ_COUNT; ++i) {
    264             if (mFactors[i] <= 0.0f) {   // user values are zero or negative
    265                 userSum -= mFactors[i];
    266             } else {
    267                 defaultSum += mFactors[i];
    268                 ++defaultCount;
    269             }
    270         }
    271 
    272         // if the user request was > 100%, reject it
    273         if (userSum > 100.0f) {
    274             System.err.println("** Event weights > 100%");
    275             return false;
    276         }
    277 
    278         // if the user specified all of the weights, then they need to be 100%
    279         if (defaultCount == 0 && (userSum < 99.9f || userSum > 100.1f)) {
    280             System.err.println("** Event weights != 100%");
    281             return false;
    282         }
    283 
    284         // compute the adjustment necessary
    285         float defaultsTarget = (100.0f - userSum);
    286         float defaultsAdjustment = defaultsTarget / defaultSum;
    287 
    288         // fix all values, by adjusting defaults, or flipping user values back to >0
    289         for (int i = 0; i < FACTORZ_COUNT; ++i) {
    290             if (mFactors[i] <= 0.0f) {   // user values are zero or negative
    291                 mFactors[i] = -mFactors[i];
    292             } else {
    293                 mFactors[i] *= defaultsAdjustment;
    294             }
    295         }
    296 
    297         // if verbose, show factors
    298         if (mVerbose > 0) {
    299             System.out.println("// Event percentages:");
    300             for (int i = 0; i < FACTORZ_COUNT; ++i) {
    301                 System.out.println("//   " + i + ": " + mFactors[i] + "%");
    302             }
    303         }
    304 
    305         if (!validateKeys()) {
    306             return false;
    307         }
    308 
    309         // finally, normalize and convert to running sum
    310         float sum = 0.0f;
    311         for (int i = 0; i < FACTORZ_COUNT; ++i) {
    312             sum += mFactors[i] / 100.0f;
    313             mFactors[i] = sum;
    314         }
    315         return true;
    316     }
    317 
    318     private static boolean validateKeyCategory(String catName, int[] keys, float factor) {
    319         if (factor < 0.1f) {
    320             return true;
    321         }
    322         for (int i = 0; i < keys.length; ++i) {
    323             if (PHYSICAL_KEY_EXISTS[keys[i]]) {
    324                 return true;
    325             }
    326         }
    327         System.err.println("** " + catName + " has no physical keys but with factor " + factor + "%.");
    328         return false;
    329     }
    330 
    331     /**
    332      * See if any key exists for non-zero factors.
    333      */
    334     private boolean validateKeys() {
    335         return validateKeyCategory("NAV_KEYS", NAV_KEYS, mFactors[FACTOR_NAV])
    336             && validateKeyCategory("MAJOR_NAV_KEYS", MAJOR_NAV_KEYS, mFactors[FACTOR_MAJORNAV])
    337             && validateKeyCategory("SYS_KEYS", SYS_KEYS, mFactors[FACTOR_SYSOPS]);
    338     }
    339 
    340     /**
    341      * set the factors
    342      *
    343      * @param factors percentages for each type of event
    344      */
    345     public void setFactors(float factors[]) {
    346         int c = FACTORZ_COUNT;
    347         if (factors.length < c) {
    348             c = factors.length;
    349         }
    350         for (int i = 0; i < c; i++)
    351             mFactors[i] = factors[i];
    352     }
    353 
    354     public void setFactors(int index, float v) {
    355         mFactors[index] = v;
    356     }
    357 
    358     /**
    359      * Generates a random motion event. This method counts a down, move, and up as multiple events.
    360      *
    361      * TODO:  Test & fix the selectors when non-zero percentages
    362      * TODO:  Longpress.
    363      * TODO:  Fling.
    364      * TODO:  Meta state
    365      * TODO:  More useful than the random walk here would be to pick a single random direction
    366      * and distance, and divvy it up into a random number of segments.  (This would serve to
    367      * generate fling gestures, which are important).
    368      *
    369      * @param random Random number source for positioning
    370      * @param motionEvent If false, touch/release.  If true, touch/move/release.
    371      *
    372      */
    373     private void generateMotionEvent(Random random, boolean motionEvent){
    374 
    375         Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
    376 
    377         float x = Math.abs(random.nextInt() % display.getWidth());
    378         float y = Math.abs(random.nextInt() % display.getHeight());
    379         long downAt = SystemClock.uptimeMillis();
    380         long eventTime = SystemClock.uptimeMillis();
    381         if (downAt == -1) {
    382             downAt = eventTime;
    383         }
    384 
    385         MonkeyMotionEvent e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
    386                 downAt, MotionEvent.ACTION_DOWN, x, y, 0);
    387         e.setIntermediateNote(false);
    388         mQ.addLast(e);
    389 
    390         // sometimes we'll move during the touch
    391         if (motionEvent) {
    392             int count = random.nextInt(10);
    393             for (int i = 0; i < count; i++) {
    394                 // generate some slop in the up event
    395                 x = (x + (random.nextInt() % 10)) % display.getWidth();
    396                 y = (y + (random.nextInt() % 10)) % display.getHeight();
    397 
    398                 e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
    399                         downAt, MotionEvent.ACTION_MOVE, x, y, 0);
    400                 e.setIntermediateNote(true);
    401                 mQ.addLast(e);
    402             }
    403         }
    404 
    405         // TODO generate some slop in the up event
    406         e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
    407                 downAt, MotionEvent.ACTION_UP, x, y, 0);
    408         e.setIntermediateNote(false);
    409         mQ.addLast(e);
    410     }
    411 
    412     /**
    413      * Generates a random trackball event. This consists of a sequence of small moves, followed by
    414      * an optional single click.
    415      *
    416      * TODO:  Longpress.
    417      * TODO:  Meta state
    418      * TODO:  Parameterize the % clicked
    419      * TODO:  More useful than the random walk here would be to pick a single random direction
    420      * and distance, and divvy it up into a random number of segments.  (This would serve to
    421      * generate fling gestures, which are important).
    422      *
    423      * @param random Random number source for positioning
    424      *
    425      */
    426     private void generateTrackballEvent(Random random) {
    427         Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
    428 
    429         boolean drop = false;
    430         int count = random.nextInt(10);
    431         MonkeyMotionEvent e;
    432         for (int i = 0; i < 10; ++i) {
    433             // generate a small random step
    434             int dX = random.nextInt(10) - 5;
    435             int dY = random.nextInt(10) - 5;
    436 
    437 
    438             e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, -1,
    439                     MotionEvent.ACTION_MOVE, dX, dY, 0);
    440             e.setIntermediateNote(i > 0);
    441             mQ.addLast(e);
    442         }
    443 
    444         // 10% of trackball moves end with a click
    445         if (0 == random.nextInt(10)) {
    446             long downAt = SystemClock.uptimeMillis();
    447 
    448 
    449             e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, downAt,
    450                     MotionEvent.ACTION_DOWN, 0, 0, 0);
    451             e.setIntermediateNote(true);
    452             mQ.addLast(e);
    453 
    454 
    455             e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, downAt,
    456                     MotionEvent.ACTION_UP, 0, 0, 0);
    457             e.setIntermediateNote(false);
    458             mQ.addLast(e);
    459         }
    460     }
    461 
    462     /**
    463      * generate a random event based on mFactor
    464      */
    465     private void generateEvents() {
    466         float cls = mRandom.nextFloat();
    467         int lastKey = 0;
    468 
    469         boolean touchEvent = cls < mFactors[FACTOR_TOUCH];
    470         boolean motionEvent = !touchEvent && (cls < mFactors[FACTOR_MOTION]);
    471         if (touchEvent || motionEvent) {
    472             generateMotionEvent(mRandom, motionEvent);
    473             return;
    474         }
    475 
    476         if (cls < mFactors[FACTOR_TRACKBALL]) {
    477             generateTrackballEvent(mRandom);
    478             return;
    479         }
    480 
    481         // The remaining event categories are injected as key events
    482         do {
    483             if (cls < mFactors[FACTOR_NAV]) {
    484                 lastKey = NAV_KEYS[mRandom.nextInt(NAV_KEYS.length)];
    485             } else if (cls < mFactors[FACTOR_MAJORNAV]) {
    486                 lastKey = MAJOR_NAV_KEYS[mRandom.nextInt(MAJOR_NAV_KEYS.length)];
    487             } else if (cls < mFactors[FACTOR_SYSOPS]) {
    488                 lastKey = SYS_KEYS[mRandom.nextInt(SYS_KEYS.length)];
    489             } else if (cls < mFactors[FACTOR_APPSWITCH]) {
    490                 MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
    491                         mRandom.nextInt(mMainApps.size())));
    492                 mQ.addLast(e);
    493                 return;
    494             } else if (cls < mFactors[FACTOR_FLIP]) {
    495                 MonkeyFlipEvent e = new MonkeyFlipEvent(mKeyboardOpen);
    496                 mKeyboardOpen = !mKeyboardOpen;
    497                 mQ.addLast(e);
    498                 return;
    499             } else {
    500                 lastKey = 1 + mRandom.nextInt(KeyEvent.getMaxKeyCode() - 1);
    501             }
    502         } while (!PHYSICAL_KEY_EXISTS[lastKey]);
    503 
    504         MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, lastKey);
    505         mQ.addLast(e);
    506 
    507         e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey);
    508         mQ.addLast(e);
    509     }
    510 
    511     public boolean validate() {
    512         //check factors
    513         return adjustEventFactors();
    514     }
    515 
    516     public void setVerbose(int verbose) {
    517         mVerbose = verbose;
    518     }
    519 
    520     /**
    521      * generate an activity event
    522      */
    523     public void generateActivity() {
    524         MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
    525                 mRandom.nextInt(mMainApps.size())));
    526         mQ.addLast(e);
    527     }
    528 
    529     /**
    530      * if the queue is empty, we generate events first
    531      * @return the first event in the queue
    532      */
    533     public MonkeyEvent getNextEvent() {
    534         if (mQ.isEmpty()) {
    535             generateEvents();
    536         }
    537         mEventCount++;
    538         MonkeyEvent e = mQ.getFirst();
    539         mQ.removeFirst();
    540         return e;
    541     }
    542 }
    543