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