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 
getKeyName(int keycode)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      */
getKeyCode(String keyName)118     public static int getKeyCode(String keyName) {
119         return KeyEvent.keyCodeFromString(keyName);
120     }
121 
MonkeySourceRandom(Random random, List<ComponentName> MainApps, long throttle, boolean randomizeThrottle, boolean permissionTargetSystem)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      */
adjustEventFactors()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 
validateKeyCategory(String catName, int[] keys, float factor)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      */
validateKeys()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      */
setFactors(float factors[])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 
setFactors(int index, float v)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      */
generatePointerEvent(Random random, int gesture)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 
randomPoint(Random random, Display display)329     private PointF randomPoint(Random random, Display display) {
330         return new PointF(random.nextInt(display.getWidth()), random.nextInt(display.getHeight()));
331     }
332 
randomVector(Random random)333     private PointF randomVector(Random random) {
334         return new PointF((random.nextFloat() - 0.5f) * 50, (random.nextFloat() - 0.5f) * 50);
335     }
336 
randomWalk(Random random, Display display, PointF point, PointF vector)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      */
generateTrackballEvent(Random random)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      */
generateRotationEvent(Random random)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      */
generateEvents()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 
validate()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 
setVerbose(int verbose)474     public void setVerbose(int verbose) {
475         mVerbose = verbose;
476     }
477 
478     /**
479      * generate an activity event
480      */
generateActivity()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      */
getNextEvent()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