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