• 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.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 
getKeyName(int keycode)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      */
getKeyCode(String keyName)107     public static int getKeyCode(String keyName) {
108         return KeyEvent.keyCodeFromString(keyName);
109     }
110 
MonkeySourceRandom(Random random, ArrayList<ComponentName> MainApps, long throttle, boolean randomizeThrottle)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      */
adjustEventFactors()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 
validateKeyCategory(String catName, int[] keys, float factor)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      */
validateKeys()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      */
setFactors(float factors[])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 
setFactors(int index, float v)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      */
generatePointerEvent(Random random, int gesture)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 
randomPoint(Random random, Display display)312     private PointF randomPoint(Random random, Display display) {
313         return new PointF(random.nextInt(display.getWidth()), random.nextInt(display.getHeight()));
314     }
315 
randomVector(Random random)316     private PointF randomVector(Random random) {
317         return new PointF((random.nextFloat() - 0.5f) * 50, (random.nextFloat() - 0.5f) * 50);
318     }
319 
randomWalk(Random random, Display display, PointF point, PointF vector)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      */
generateTrackballEvent(Random random)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      */
generateEvents()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 
validate()429     public boolean validate() {
430         //check factors
431         return adjustEventFactors();
432     }
433 
setVerbose(int verbose)434     public void setVerbose(int verbose) {
435         mVerbose = verbose;
436     }
437 
438     /**
439      * generate an activity event
440      */
generateActivity()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      */
getNextEvent()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