2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.commands.monkey;
19 import android.content.ComponentName;
20 import android.os.SystemClock;
21 import android.view.Display;
22 import android.view.KeyEvent;
23 import android.view.MotionEvent;
24 import android.view.WindowManagerImpl;
26 import java.security.SecureRandom;
27 import java.util.ArrayList;
28 import java.util.LinkedList;
29 import java.util.Random;
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,
41 * Key events that perform major navigation options (so shouldn't be sent
44 private static final int[] MAJOR_NAV_KEYS = {
45 KeyEvent.KEYCODE_MENU, /*KeyEvent.KEYCODE_SOFT_RIGHT,*/
46 KeyEvent.KEYCODE_DPAD_CENTER,
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,
53 KeyEvent.KEYCODE_MUTE,
55 /** Nice names for all key events. */
56 private static final String[] KEY_NAMES = {
80 "KEYCODE_DPAD_CENTER",
82 "KEYCODE_VOLUME_DOWN",
116 "KEYCODE_SHIFT_LEFT",
117 "KEYCODE_SHIFT_RIGHT",
128 "KEYCODE_LEFT_BRACKET",
129 "KEYCODE_RIGHT_BRACKET",
132 "KEYCODE_APOSTROPHE",
136 "KEYCODE_HEADSETHOOK",
140 "KEYCODE_NOTIFICATION",
145 "KEYCODE_PREVIOUSSONG",
150 "TAG_LAST_KEYCODE" // EOL. used to keep the lists in sync
153 public static final int FACTOR_TOUCH = 0;
154 public static final int FACTOR_MOTION = 1;
155 public static final int FACTOR_TRACKBALL = 2;
156 public static final int FACTOR_NAV = 3;
157 public static final int FACTOR_MAJORNAV = 4;
158 public static final int FACTOR_SYSOPS = 5;
159 public static final int FACTOR_APPSWITCH = 6;
160 public static final int FACTOR_FLIP = 7;
161 public static final int FACTOR_ANYTHING = 8;
162 public static final int FACTORZ_COUNT = 9; // should be last+1
165 /** percentages for each type of event. These will be remapped to working
166 * values after we read any optional values.
168 private float[] mFactors = new float[FACTORZ_COUNT];
169 private ArrayList<ComponentName> mMainApps;
170 private int mEventCount = 0; //total number of events generated so far
171 private MonkeyEventQueue mQ;
172 private Random mRandom;
173 private int mVerbose = 0;
174 private long mThrottle = 0;
176 private boolean mKeyboardOpen = false;
179 * @return the last name in the key list
181 public static String getLastKeyName() {
182 return KEY_NAMES[KeyEvent.getMaxKeyCode() + 1];
185 public static String getKeyName(int keycode) {
186 return KEY_NAMES[keycode];
190 * Looks up the keyCode from a given KEYCODE_NAME. NOTE: This may
191 * be an expensive operation.
193 * @param keyName the name of the KEYCODE_VALUE to lookup.
194 * @returns the intenger keyCode value, or -1 if not found
196 public static int getKeyCode(String keyName) {
197 for (int x = 0; x < KEY_NAMES.length; x++) {
198 if (KEY_NAMES[x].equals(keyName)) {
205 public MonkeySourceRandom(long seed, ArrayList<ComponentName> MainApps, long throttle) {
206 // default values for random distributions
207 // note, these are straight percentages, to match user input (cmd line args)
208 // but they will be converted to 0..1 values before the main loop runs.
209 mFactors[FACTOR_TOUCH] = 15.0f;
210 mFactors[FACTOR_MOTION] = 10.0f;
211 mFactors[FACTOR_TRACKBALL] = 15.0f;
212 mFactors[FACTOR_NAV] = 25.0f;
213 mFactors[FACTOR_MAJORNAV] = 15.0f;
214 mFactors[FACTOR_SYSOPS] = 2.0f;
215 mFactors[FACTOR_APPSWITCH] = 2.0f;
216 mFactors[FACTOR_FLIP] = 1.0f;
217 mFactors[FACTOR_ANYTHING] = 15.0f;
219 mRandom = new SecureRandom();
220 mRandom.setSeed((seed == 0) ? -1 : seed);
221 mMainApps = MainApps;
222 mQ = new MonkeyEventQueue(throttle);
226 * Adjust the percentages (after applying user values) and then normalize to a 0..1 scale.
228 private boolean adjustEventFactors() {
229 // go through all values and compute totals for user & default values
230 float userSum = 0.0f;
231 float defaultSum = 0.0f;
232 int defaultCount = 0;
233 for (int i = 0; i < FACTORZ_COUNT; ++i) {
234 if (mFactors[i] <= 0.0f) { // user values are zero or negative
235 userSum -= mFactors[i];
237 defaultSum += mFactors[i];
242 // if the user request was > 100%, reject it
243 if (userSum > 100.0f) {
244 System.err.println("** Event weights > 100%");
248 // if the user specified all of the weights, then they need to be 100%
249 if (defaultCount == 0 && (userSum < 99.9f || userSum > 100.1f)) {
250 System.err.println("** Event weights != 100%");
254 // compute the adjustment necessary
255 float defaultsTarget = (100.0f - userSum);
256 float defaultsAdjustment = defaultsTarget / defaultSum;
258 // fix all values, by adjusting defaults, or flipping user values back to >0
259 for (int i = 0; i < FACTORZ_COUNT; ++i) {
260 if (mFactors[i] <= 0.0f) { // user values are zero or negative
261 mFactors[i] = -mFactors[i];
263 mFactors[i] *= defaultsAdjustment;
267 // if verbose, show factors
270 System.out.println("// Event percentages:");
271 for (int i = 0; i < FACTORZ_COUNT; ++i) {
272 System.out.println("// " + i + ": " + mFactors[i] + "%");
276 // finally, normalize and convert to running sum
278 for (int i = 0; i < FACTORZ_COUNT; ++i) {
279 sum += mFactors[i] / 100.0f;
288 * @param factors: percentages for each type of event
290 public void setFactors(float factors[]) {
291 int c = FACTORZ_COUNT;
292 if (factors.length < c) {
295 for (int i = 0; i < c; i++)
296 mFactors[i] = factors[i];
299 public void setFactors(int index, float v) {
304 * Generates a random motion event. This method counts a down, move, and up as multiple events.
306 * TODO: Test & fix the selectors when non-zero percentages
310 * TODO: More useful than the random walk here would be to pick a single random direction
311 * and distance, and divvy it up into a random number of segments. (This would serve to
312 * generate fling gestures, which are important).
314 * @param random Random number source for positioning
315 * @param motionEvent If false, touch/release. If true, touch/move/release.
318 private void generateMotionEvent(Random random, boolean motionEvent){
320 Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
322 float x = Math.abs(random.nextInt() % display.getWidth());
323 float y = Math.abs(random.nextInt() % display.getHeight());
324 long downAt = SystemClock.uptimeMillis();
325 long eventTime = SystemClock.uptimeMillis();
330 MonkeyMotionEvent e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
331 downAt, MotionEvent.ACTION_DOWN, x, y, 0);
332 e.setIntermediateNote(false);
335 // sometimes we'll move during the touch
337 int count = random.nextInt(10);
338 for (int i = 0; i < count; i++) {
339 // generate some slop in the up event
340 x = (x + (random.nextInt() % 10)) % display.getWidth();
341 y = (y + (random.nextInt() % 10)) % display.getHeight();
343 e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
344 downAt, MotionEvent.ACTION_MOVE, x, y, 0);
345 e.setIntermediateNote(true);
350 // TODO generate some slop in the up event
351 e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
352 downAt, MotionEvent.ACTION_UP, x, y, 0);
353 e.setIntermediateNote(false);
358 * Generates a random trackball event. This consists of a sequence of small moves, followed by
359 * an optional single click.
363 * TODO: Parameterize the % clicked
364 * TODO: More useful than the random walk here would be to pick a single random direction
365 * and distance, and divvy it up into a random number of segments. (This would serve to
366 * generate fling gestures, which are important).
368 * @param random Random number source for positioning
371 private void generateTrackballEvent(Random random) {
372 Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
374 boolean drop = false;
375 int count = random.nextInt(10);
377 for (int i = 0; i < 10; ++i) {
378 // generate a small random step
379 int dX = random.nextInt(10) - 5;
380 int dY = random.nextInt(10) - 5;
383 e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, -1,
384 MotionEvent.ACTION_MOVE, dX, dY, 0);
385 e.setIntermediateNote(i > 0);
389 // 10% of trackball moves end with a click
390 if (0 == random.nextInt(10)) {
391 long downAt = SystemClock.uptimeMillis();
394 e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, downAt,
395 MotionEvent.ACTION_DOWN, 0, 0, 0);
396 e.setIntermediateNote(true);
400 e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, downAt,
401 MotionEvent.ACTION_UP, 0, 0, 0);
402 e.setIntermediateNote(false);
408 * generate a random event based on mFactor
410 private void generateEvents() {
411 float cls = mRandom.nextFloat();
414 boolean touchEvent = cls < mFactors[FACTOR_TOUCH];
415 boolean motionEvent = !touchEvent && (cls < mFactors[FACTOR_MOTION]);
416 if (touchEvent || motionEvent) {
417 generateMotionEvent(mRandom, motionEvent);
421 if (cls < mFactors[FACTOR_TRACKBALL]) {
422 generateTrackballEvent(mRandom);
426 // The remaining event categories are injected as key events
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(mMainApps.get(
435 mRandom.nextInt(mMainApps.size())));
438 } else if (cls < mFactors[FACTOR_FLIP]) {
439 MonkeyFlipEvent e = new MonkeyFlipEvent(mKeyboardOpen);
440 mKeyboardOpen = !mKeyboardOpen;
444 lastKey = 1 + mRandom.nextInt(KeyEvent.getMaxKeyCode() - 1);
447 MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, lastKey);
450 e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey);
454 public boolean validate() {
456 return adjustEventFactors();
459 public void setVerbose(int verbose) {
464 * generate an activity event
466 public void generateActivity() {
467 MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
468 mRandom.nextInt(mMainApps.size())));
473 * if the queue is empty, we generate events first
474 * @return the first event in the queue
476 public MonkeyEvent getNextEvent() {
481 MonkeyEvent e = mQ.getFirst();