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.graphics.PointF;
     21 import android.os.SystemClock;
     22 import android.view.Display;
     23 import android.view.KeyCharacterMap;
     24 import android.view.KeyEvent;
     25 import android.view.MotionEvent;
     26 import android.view.WindowManagerImpl;
     27 
     28 import java.util.ArrayList;
     29 import java.util.Random;
     30 
     31 /**
     32  * monkey event queue
     33  */
     34 public class MonkeySourceRandom implements MonkeyEventSource {
     35     /** Key events that move around the UI. */
     36     private static final int[] NAV_KEYS = {
     37         KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN,
     38         KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT,
     39     };
     40     /**
     41      * Key events that perform major navigation options (so shouldn't be sent
     42      * as much).
     43      */
     44     private static final int[] MAJOR_NAV_KEYS = {
     45         KeyEvent.KEYCODE_MENU, /*KeyEvent.KEYCODE_SOFT_RIGHT,*/
     46         KeyEvent.KEYCODE_DPAD_CENTER,
     47     };
     48     /** Key events that perform system operations. */
     49     private static final int[] SYS_KEYS = {
     50         KeyEvent.KEYCODE_HOME, KeyEvent.KEYCODE_BACK,
     51         KeyEvent.KEYCODE_CALL, KeyEvent.KEYCODE_ENDCALL,
     52         KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE,
     53         KeyEvent.KEYCODE_MUTE,
     54     };
     55     /** If a physical key exists? */
     56     private static final boolean[] PHYSICAL_KEY_EXISTS = new boolean[KeyEvent.getMaxKeyCode() + 1];
     57     static {
     58         for (int i = 0; i < PHYSICAL_KEY_EXISTS.length; ++i) {
     59             PHYSICAL_KEY_EXISTS[i] = true;
     60         }
     61         // Only examine SYS_KEYS
     62         for (int i = 0; i < SYS_KEYS.length; ++i) {
     63             PHYSICAL_KEY_EXISTS[SYS_KEYS[i]] = KeyCharacterMap.deviceHasKey(SYS_KEYS[i]);
     64         }
     65     }
     66 
     67     public static final int FACTOR_TOUCH        = 0;
     68     public static final int FACTOR_MOTION       = 1;
     69     public static final int FACTOR_PINCHZOOM    = 2;
     70     public static final int FACTOR_TRACKBALL    = 3;
     71     public static final int FACTOR_NAV          = 4;
     72     public static final int FACTOR_MAJORNAV     = 5;
     73     public static final int FACTOR_SYSOPS       = 6;
     74     public static final int FACTOR_APPSWITCH    = 7;
     75     public static final int FACTOR_FLIP         = 8;
     76     public static final int FACTOR_ANYTHING     = 9;
     77     public static final int FACTORZ_COUNT       = 10;    // should be last+1
     78 
     79     private static final int GESTURE_TAP = 0;
     80     private static final int GESTURE_DRAG = 1;
     81     private static final int GESTURE_PINCH_OR_ZOOM = 2;
     82 
     83     /** percentages for each type of event.  These will be remapped to working
     84      * values after we read any optional values.
     85      **/
     86     private float[] mFactors = new float[FACTORZ_COUNT];
     87     private ArrayList<ComponentName> mMainApps;
     88     private int mEventCount = 0;  //total number of events generated so far
     89     private MonkeyEventQueue mQ;
     90     private Random mRandom;
     91     private int mVerbose = 0;
     92     private long mThrottle = 0;
     93 
     94     private boolean mKeyboardOpen = false;
     95 
     96     public static String getKeyName(int keycode) {
     97         return KeyEvent.keyCodeToString(keycode);
     98     }
     99 
    100     /**
    101      * Looks up the keyCode from a given KEYCODE_NAME.  NOTE: This may
    102      * be an expensive operation.
    103      *
    104      * @param keyName the name of the KEYCODE_VALUE to lookup.
    105      * @returns the intenger keyCode value, or -1 if not found
    106      */
    107     public static int getKeyCode(String keyName) {
    108         return KeyEvent.keyCodeFromString(keyName);
    109     }
    110 
    111     public MonkeySourceRandom(Random random, ArrayList<ComponentName> MainApps,
    112             long throttle, boolean randomizeThrottle) {
    113         // default values for random distributions
    114         // note, these are straight percentages, to match user input (cmd line args)
    115         // but they will be converted to 0..1 values before the main loop runs.
    116         mFactors[FACTOR_TOUCH] = 15.0f;
    117         mFactors[FACTOR_MOTION] = 10.0f;
    118         mFactors[FACTOR_TRACKBALL] = 15.0f;
    119         mFactors[FACTOR_NAV] = 25.0f;
    120         mFactors[FACTOR_MAJORNAV] = 15.0f;
    121         mFactors[FACTOR_SYSOPS] = 2.0f;
    122         mFactors[FACTOR_APPSWITCH] = 2.0f;
    123         mFactors[FACTOR_FLIP] = 1.0f;
    124         mFactors[FACTOR_ANYTHING] = 13.0f;
    125         mFactors[FACTOR_PINCHZOOM] = 2.0f;
    126 
    127         mRandom = random;
    128         mMainApps = MainApps;
    129         mQ = new MonkeyEventQueue(random, throttle, randomizeThrottle);
    130     }
    131 
    132     /**
    133      * Adjust the percentages (after applying user values) and then normalize to a 0..1 scale.
    134      */
    135     private boolean adjustEventFactors() {
    136         // go through all values and compute totals for user & default values
    137         float userSum = 0.0f;
    138         float defaultSum = 0.0f;
    139         int defaultCount = 0;
    140         for (int i = 0; i < FACTORZ_COUNT; ++i) {
    141             if (mFactors[i] <= 0.0f) {   // user values are zero or negative
    142                 userSum -= mFactors[i];
    143             } else {
    144                 defaultSum += mFactors[i];
    145                 ++defaultCount;
    146             }
    147         }
    148 
    149         // if the user request was > 100%, reject it
    150         if (userSum > 100.0f) {
    151             System.err.println("** Event weights > 100%");
    152             return false;
    153         }
    154 
    155         // if the user specified all of the weights, then they need to be 100%
    156         if (defaultCount == 0 && (userSum < 99.9f || userSum > 100.1f)) {
    157             System.err.println("** Event weights != 100%");
    158             return false;
    159         }
    160 
    161         // compute the adjustment necessary
    162         float defaultsTarget = (100.0f - userSum);
    163         float defaultsAdjustment = defaultsTarget / defaultSum;
    164 
    165         // fix all values, by adjusting defaults, or flipping user values back to >0
    166         for (int i = 0; i < FACTORZ_COUNT; ++i) {
    167             if (mFactors[i] <= 0.0f) {   // user values are zero or negative
    168                 mFactors[i] = -mFactors[i];
    169             } else {
    170                 mFactors[i] *= defaultsAdjustment;
    171             }
    172         }
    173 
    174         // if verbose, show factors
    175         if (mVerbose > 0) {
    176             System.out.println("// Event percentages:");
    177             for (int i = 0; i < FACTORZ_COUNT; ++i) {
    178                 System.out.println("//   " + i + ": " + mFactors[i] + "%");
    179             }
    180         }
    181 
    182         if (!validateKeys()) {
    183             return false;
    184         }
    185 
    186         // finally, normalize and convert to running sum
    187         float sum = 0.0f;
    188         for (int i = 0; i < FACTORZ_COUNT; ++i) {
    189             sum += mFactors[i] / 100.0f;
    190             mFactors[i] = sum;
    191         }
    192         return true;
    193     }
    194 
    195     private static boolean validateKeyCategory(String catName, int[] keys, float factor) {
    196         if (factor < 0.1f) {
    197             return true;
    198         }
    199         for (int i = 0; i < keys.length; ++i) {
    200             if (PHYSICAL_KEY_EXISTS[keys[i]]) {
    201                 return true;
    202             }
    203         }
    204         System.err.println("** " + catName + " has no physical keys but with factor " + factor + "%.");
    205         return false;
    206     }
    207 
    208     /**
    209      * See if any key exists for non-zero factors.
    210      */
    211     private boolean validateKeys() {
    212         return validateKeyCategory("NAV_KEYS", NAV_KEYS, mFactors[FACTOR_NAV])
    213             && validateKeyCategory("MAJOR_NAV_KEYS", MAJOR_NAV_KEYS, mFactors[FACTOR_MAJORNAV])
    214             && validateKeyCategory("SYS_KEYS", SYS_KEYS, mFactors[FACTOR_SYSOPS]);
    215     }
    216 
    217     /**
    218      * set the factors
    219      *
    220      * @param factors percentages for each type of event
    221      */
    222     public void setFactors(float factors[]) {
    223         int c = FACTORZ_COUNT;
    224         if (factors.length < c) {
    225             c = factors.length;
    226         }
    227         for (int i = 0; i < c; i++)
    228             mFactors[i] = factors[i];
    229     }
    230 
    231     public void setFactors(int index, float v) {
    232         mFactors[index] = v;
    233     }
    234 
    235     /**
    236      * Generates a random motion event. This method counts a down, move, and up as multiple events.
    237      *
    238      * TODO:  Test & fix the selectors when non-zero percentages
    239      * TODO:  Longpress.
    240      * TODO:  Fling.
    241      * TODO:  Meta state
    242      * TODO:  More useful than the random walk here would be to pick a single random direction
    243      * and distance, and divvy it up into a random number of segments.  (This would serve to
    244      * generate fling gestures, which are important).
    245      *
    246      * @param random Random number source for positioning
    247      * @param gesture The gesture to perform.
    248      *
    249      */
    250     private void generatePointerEvent(Random random, int gesture){
    251         Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
    252 
    253         PointF p1 = randomPoint(random, display);
    254         PointF v1 = randomVector(random);
    255 
    256         long downAt = SystemClock.uptimeMillis();
    257 
    258         mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN)
    259                 .setDownTime(downAt)
    260                 .addPointer(0, p1.x, p1.y)
    261                 .setIntermediateNote(false));
    262 
    263         // sometimes we'll move during the touch
    264         if (gesture == GESTURE_DRAG) {
    265             int count = random.nextInt(10);
    266             for (int i = 0; i < count; i++) {
    267                 randomWalk(random, display, p1, v1);
    268 
    269                 mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_MOVE)
    270                         .setDownTime(downAt)
    271                         .addPointer(0, p1.x, p1.y)
    272                         .setIntermediateNote(true));
    273             }
    274         } else if (gesture == GESTURE_PINCH_OR_ZOOM) {
    275             PointF p2 = randomPoint(random, display);
    276             PointF v2 = randomVector(random);
    277 
    278             randomWalk(random, display, p1, v1);
    279             mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_POINTER_DOWN
    280                             | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT))
    281                     .setDownTime(downAt)
    282                     .addPointer(0, p1.x, p1.y).addPointer(1, p2.x, p2.y)
    283                     .setIntermediateNote(true));
    284 
    285             int count = random.nextInt(10);
    286             for (int i = 0; i < count; i++) {
    287                 randomWalk(random, display, p1, v1);
    288                 randomWalk(random, display, p2, v2);
    289 
    290                 mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_MOVE)
    291                         .setDownTime(downAt)
    292                         .addPointer(0, p1.x, p1.y).addPointer(1, p2.x, p2.y)
    293                         .setIntermediateNote(true));
    294             }
    295 
    296             randomWalk(random, display, p1, v1);
    297             randomWalk(random, display, p2, v2);
    298             mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_POINTER_UP
    299                             | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT))
    300                     .setDownTime(downAt)
    301                     .addPointer(0, p1.x, p1.y).addPointer(1, p2.x, p2.y)
    302                     .setIntermediateNote(true));
    303         }
    304 
    305         randomWalk(random, display, p1, v1);
    306         mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_UP)
    307                 .setDownTime(downAt)
    308                 .addPointer(0, p1.x, p1.y)
    309                 .setIntermediateNote(false));
    310     }
    311 
    312     private PointF randomPoint(Random random, Display display) {
    313         return new PointF(random.nextInt(display.getWidth()), random.nextInt(display.getHeight()));
    314     }
    315 
    316     private PointF randomVector(Random random) {
    317         return new PointF((random.nextFloat() - 0.5f) * 50, (random.nextFloat() - 0.5f) * 50);
    318     }
    319 
    320     private void randomWalk(Random random, Display display, PointF point, PointF vector) {
    321         point.x = (float) Math.max(Math.min(point.x + random.nextFloat() * vector.x,
    322                 display.getWidth()), 0);
    323         point.y = (float) Math.max(Math.min(point.y + random.nextFloat() * vector.y,
    324                 display.getHeight()), 0);
    325     }
    326 
    327     /**
    328      * Generates a random trackball event. This consists of a sequence of small moves, followed by
    329      * an optional single click.
    330      *
    331      * TODO:  Longpress.
    332      * TODO:  Meta state
    333      * TODO:  Parameterize the % clicked
    334      * TODO:  More useful than the random walk here would be to pick a single random direction
    335      * and distance, and divvy it up into a random number of segments.  (This would serve to
    336      * generate fling gestures, which are important).
    337      *
    338      * @param random Random number source for positioning
    339      *
    340      */
    341     private void generateTrackballEvent(Random random) {
    342         Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
    343 
    344         boolean drop = false;
    345         int count = random.nextInt(10);
    346         for (int i = 0; i < 10; ++i) {
    347             // generate a small random step
    348             int dX = random.nextInt(10) - 5;
    349             int dY = random.nextInt(10) - 5;
    350 
    351             mQ.addLast(new MonkeyTrackballEvent(MotionEvent.ACTION_MOVE)
    352                     .addPointer(0, dX, dY)
    353                     .setIntermediateNote(i > 0));
    354         }
    355 
    356         // 10% of trackball moves end with a click
    357         if (0 == random.nextInt(10)) {
    358             long downAt = SystemClock.uptimeMillis();
    359 
    360             mQ.addLast(new MonkeyTrackballEvent(MotionEvent.ACTION_DOWN)
    361                     .setDownTime(downAt)
    362                     .addPointer(0, 0, 0)
    363                     .setIntermediateNote(true));
    364 
    365             mQ.addLast(new MonkeyTrackballEvent(MotionEvent.ACTION_UP)
    366                     .setDownTime(downAt)
    367                     .addPointer(0, 0, 0)
    368                     .setIntermediateNote(false));
    369         }
    370     }
    371 
    372     /**
    373      * generate a random event based on mFactor
    374      */
    375     private void generateEvents() {
    376         float cls = mRandom.nextFloat();
    377         int lastKey = 0;
    378 
    379         if (cls < mFactors[FACTOR_TOUCH]) {
    380             generatePointerEvent(mRandom, GESTURE_TAP);
    381             return;
    382         } else if (cls < mFactors[FACTOR_MOTION]) {
    383             generatePointerEvent(mRandom, GESTURE_DRAG);
    384             return;
    385         } else if (cls < mFactors[FACTOR_PINCHZOOM]) {
    386             generatePointerEvent(mRandom, GESTURE_PINCH_OR_ZOOM);
    387             return;
    388         } else if (cls < mFactors[FACTOR_TRACKBALL]) {
    389             generateTrackballEvent(mRandom);
    390             return;
    391         }
    392 
    393         // The remaining event categories are injected as key events
    394         for (;;) {
    395             if (cls < mFactors[FACTOR_NAV]) {
    396                 lastKey = NAV_KEYS[mRandom.nextInt(NAV_KEYS.length)];
    397             } else if (cls < mFactors[FACTOR_MAJORNAV]) {
    398                 lastKey = MAJOR_NAV_KEYS[mRandom.nextInt(MAJOR_NAV_KEYS.length)];
    399             } else if (cls < mFactors[FACTOR_SYSOPS]) {
    400                 lastKey = SYS_KEYS[mRandom.nextInt(SYS_KEYS.length)];
    401             } else if (cls < mFactors[FACTOR_APPSWITCH]) {
    402                 MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
    403                         mRandom.nextInt(mMainApps.size())));
    404                 mQ.addLast(e);
    405                 return;
    406             } else if (cls < mFactors[FACTOR_FLIP]) {
    407                 MonkeyFlipEvent e = new MonkeyFlipEvent(mKeyboardOpen);
    408                 mKeyboardOpen = !mKeyboardOpen;
    409                 mQ.addLast(e);
    410                 return;
    411             } else {
    412                 lastKey = 1 + mRandom.nextInt(KeyEvent.getMaxKeyCode() - 1);
    413             }
    414 
    415             if (lastKey != KeyEvent.KEYCODE_POWER
    416                     && lastKey != KeyEvent.KEYCODE_ENDCALL
    417                     && PHYSICAL_KEY_EXISTS[lastKey]) {
    418                 break;
    419             }
    420         }
    421 
    422         MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, lastKey);
    423         mQ.addLast(e);
    424 
    425         e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey);
    426         mQ.addLast(e);
    427     }
    428 
    429     public boolean validate() {
    430         //check factors
    431         return adjustEventFactors();
    432     }
    433 
    434     public void setVerbose(int verbose) {
    435         mVerbose = verbose;
    436     }
    437 
    438     /**
    439      * generate an activity event
    440      */
    441     public void generateActivity() {
    442         MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
    443                 mRandom.nextInt(mMainApps.size())));
    444         mQ.addLast(e);
    445     }
    446 
    447     /**
    448      * if the queue is empty, we generate events first
    449      * @return the first event in the queue
    450      */
    451     public MonkeyEvent getNextEvent() {
    452         if (mQ.isEmpty()) {
    453             generateEvents();
    454         }
    455         mEventCount++;
    456         MonkeyEvent e = mQ.getFirst();
    457         mQ.removeFirst();
    458         return e;
    459     }
    460 }
    461