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