• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Surface;
27 import android.view.WindowManagerImpl;
28 
29 import java.util.ArrayList;
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_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 
getKeyName(int keycode)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      */
getKeyCode(String keyName)116     public static int getKeyCode(String keyName) {
117         return KeyEvent.keyCodeFromString(keyName);
118     }
119 
MonkeySourceRandom(Random random, ArrayList<ComponentName> MainApps, long throttle, boolean randomizeThrottle)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      */
adjustEventFactors()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 
validateKeyCategory(String catName, int[] keys, float factor)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      */
validateKeys()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      */
setFactors(float factors[])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 
setFactors(int index, float v)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      */
generatePointerEvent(Random random, int gesture)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 
randomPoint(Random random, Display display)323     private PointF randomPoint(Random random, Display display) {
324         return new PointF(random.nextInt(display.getWidth()), random.nextInt(display.getHeight()));
325     }
326 
randomVector(Random random)327     private PointF randomVector(Random random) {
328         return new PointF((random.nextFloat() - 0.5f) * 50, (random.nextFloat() - 0.5f) * 50);
329     }
330 
randomWalk(Random random, Display display, PointF point, PointF vector)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      */
generateTrackballEvent(Random random)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      */
generateRotationEvent(Random random)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      */
generateEvents()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 
validate()455     public boolean validate() {
456         //check factors
457         return adjustEventFactors();
458     }
459 
setVerbose(int verbose)460     public void setVerbose(int verbose) {
461         mVerbose = verbose;
462     }
463 
464     /**
465      * generate an activity event
466      */
generateActivity()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      */
getNextEvent()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