teach monkey to flip permissions on apps

1. added a new MonkeyEvent type for permission events
2. added monkey permission utility for randomized permission
   events generation against targeted packages
3. refactored package whitelist/blacklist into MonkeyUtils class

Change-Id: I8f7998d74c3e28d02f5bcd47a0f9cc6167b93c93
diff --git a/cmds/monkey/src/com/android/commands/monkey/Monkey.java b/cmds/monkey/src/com/android/commands/monkey/Monkey.java
index dfcb4b0..93bfcf0 100644
--- a/cmds/monkey/src/com/android/commands/monkey/Monkey.java
+++ b/cmds/monkey/src/com/android/commands/monkey/Monkey.java
@@ -31,15 +31,12 @@
 import android.os.ServiceManager;
 import android.os.StrictMode;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.view.IWindowManager;
 import android.view.Surface;
 
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
 import java.io.File;
 import java.io.FileReader;
 import java.io.FileWriter;
@@ -47,12 +44,12 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Writer;
-import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Random;
+import java.util.Set;
 
 /**
  * Application that injects random key events and other actions into the system.
@@ -181,12 +178,6 @@
     /** Package whitelist file. */
     private String mPkgWhitelistFile;
 
-    /** Packages we are allowed to run, or empty if no restriction. */
-    private HashSet<String> mValidPackages = new HashSet<String>();
-
-    /** Packages we are not allowed to run. */
-    private HashSet<String> mInvalidPackages = new HashSet<String>();
-
     /** Categories we are allowed to launch **/
     private ArrayList<String> mMainCategories = new ArrayList<String>();
 
@@ -251,36 +242,20 @@
 
     private MonkeyNetworkMonitor mNetworkMonitor = new MonkeyNetworkMonitor();
 
+    private boolean mPermissionTargetSystem = false;
+
     // information on the current activity.
     public static Intent currentIntent;
 
     public static String currentPackage;
 
     /**
-     * Check whether we should run against the givn package.
-     *
-     * @param pkg The package name.
-     * @return Returns true if we should run against pkg.
-     */
-    private boolean checkEnteringPackage(String pkg) {
-        if (mInvalidPackages.size() > 0) {
-            if (mInvalidPackages.contains(pkg)) {
-                return false;
-            }
-        } else if (mValidPackages.size() > 0) {
-            if (!mValidPackages.contains(pkg)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
      * Monitor operations happening in the system.
      */
     private class ActivityController extends IActivityController.Stub {
         public boolean activityStarting(Intent intent, String pkg) {
-            boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_STARTS != 0);
+            boolean allow = MonkeyUtils.getPackageFilter().checkEnteringPackage(pkg)
+                    || (DEBUG_ALLOW_ANY_STARTS != 0);
             if (mVerbose > 0) {
                 // StrictMode's disk checks end up catching this on
                 // userdebug/eng builds due to PrintStream going to a
@@ -301,7 +276,8 @@
         public boolean activityResuming(String pkg) {
             StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
             System.out.println("    // activityResuming(" + pkg + ")");
-            boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_RESTARTS != 0);
+            boolean allow = MonkeyUtils.getPackageFilter().checkEnteringPackage(pkg)
+                    || (DEBUG_ALLOW_ANY_RESTARTS != 0);
             if (!allow) {
                 if (mVerbose > 0) {
                     System.out.println("    // " + (allow ? "Allowing" : "Rejecting")
@@ -559,18 +535,7 @@
 
         if (mVerbose > 0) {
             System.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
-            if (mValidPackages.size() > 0) {
-                Iterator<String> it = mValidPackages.iterator();
-                while (it.hasNext()) {
-                    System.out.println(":AllowPackage: " + it.next());
-                }
-            }
-            if (mInvalidPackages.size() > 0) {
-                Iterator<String> it = mInvalidPackages.iterator();
-                while (it.hasNext()) {
-                    System.out.println(":DisallowPackage: " + it.next());
-                }
-            }
+            MonkeyUtils.getPackageFilter().dump();
             if (mMainCategories.size() != 0) {
                 Iterator<String> it = mMainCategories.iterator();
                 while (it.hasNext()) {
@@ -626,7 +591,8 @@
             if (mVerbose >= 2) { // check seeding performance
                 System.out.println("// Seeded: " + mSeed);
             }
-            mEventSource = new MonkeySourceRandom(mRandom, mMainApps, mThrottle, mRandomizeThrottle);
+            mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
+                    mThrottle, mRandomizeThrottle, mPermissionTargetSystem);
             mEventSource.setVerbose(mVerbose);
             // set any of the factors that has been set
             for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
@@ -756,11 +722,12 @@
 
         try {
             String opt;
+            Set<String> validPackages = new HashSet<>();
             while ((opt = nextOption()) != null) {
                 if (opt.equals("-s")) {
                     mSeed = nextOptionLong("Seed");
                 } else if (opt.equals("-p")) {
-                    mValidPackages.add(nextOptionData());
+                    validPackages.add(nextOptionData());
                 } else if (opt.equals("-c")) {
                     mMainCategories.add(nextOptionData());
                 } else if (opt.equals("-v")) {
@@ -812,6 +779,9 @@
                 } else if (opt.equals("--pct-pinchzoom")) {
                     int i = MonkeySourceRandom.FACTOR_PINCHZOOM;
                     mFactors[i] = -nextOptionLong("pinch zoom events percentage");
+                } else if (opt.equals("--pct-permission")) {
+                    int i = MonkeySourceRandom.FACTOR_PERMISSION;
+                    mFactors[i] = -nextOptionLong("runtime permission toggle events percentage");
                 } else if (opt.equals("--pkg-blacklist-file")) {
                     mPkgBlacklistFile = nextOptionData();
                 } else if (opt.equals("--pkg-whitelist-file")) {
@@ -845,6 +815,8 @@
                 } else if (opt.equals("--periodic-bugreport")){
                     mGetPeriodicBugreport = true;
                     mBugreportFrequency = nextOptionLong("Number of iterations");
+                } else if (opt.equals("--permission-target-system")){
+                    mPermissionTargetSystem = true;
                 } else if (opt.equals("-h")) {
                     showUsage();
                     return false;
@@ -854,6 +826,7 @@
                     return false;
                 }
             }
+            MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
         } catch (RuntimeException ex) {
             System.err.println("** Error: " + ex.toString());
             showUsage();
@@ -889,7 +862,7 @@
      * @param list The destination list.
      * @return Returns false if any error occurs.
      */
-    private static boolean loadPackageListFromFile(String fileName, HashSet<String> list) {
+    private static boolean loadPackageListFromFile(String fileName, Set<String> list) {
         BufferedReader reader = null;
         try {
             reader = new BufferedReader(new FileReader(fileName));
@@ -921,20 +894,24 @@
      * @return Returns false if any error occurs.
      */
     private boolean loadPackageLists() {
-        if (((mPkgWhitelistFile != null) || (mValidPackages.size() > 0))
+        if (((mPkgWhitelistFile != null) || (MonkeyUtils.getPackageFilter().hasValidPackages()))
                 && (mPkgBlacklistFile != null)) {
             System.err.println("** Error: you can not specify a package blacklist "
                     + "together with a whitelist or individual packages (via -p).");
             return false;
         }
+        Set<String> validPackages = new HashSet<>();
         if ((mPkgWhitelistFile != null)
-                && (!loadPackageListFromFile(mPkgWhitelistFile, mValidPackages))) {
+                && (!loadPackageListFromFile(mPkgWhitelistFile, validPackages))) {
             return false;
         }
+        MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
+        Set<String> invalidPackages = new HashSet<>();
         if ((mPkgBlacklistFile != null)
-                && (!loadPackageListFromFile(mPkgBlacklistFile, mInvalidPackages))) {
+                && (!loadPackageListFromFile(mPkgBlacklistFile, invalidPackages))) {
             return false;
         }
+        MonkeyUtils.getPackageFilter().addInvalidPackages(invalidPackages);
         return true;
     }
 
@@ -1014,7 +991,7 @@
                 for (int a = 0; a < NA; a++) {
                     ResolveInfo r = mainApps.get(a);
                     String packageName = r.activityInfo.applicationInfo.packageName;
-                    if (checkEnteringPackage(packageName)) {
+                    if (MonkeyUtils.getPackageFilter().checkEnteringPackage(packageName)) {
                         if (mVerbose >= 2) { // very verbose
                             System.out.println("//   + Using main activity " + r.activityInfo.name
                                     + " (from package " + packageName + ")");
@@ -1352,6 +1329,7 @@
         usage.append("              [--pct-nav PERCENT] [--pct-majornav PERCENT]\n");
         usage.append("              [--pct-appswitch PERCENT] [--pct-flip PERCENT]\n");
         usage.append("              [--pct-anyevent PERCENT] [--pct-pinchzoom PERCENT]\n");
+        usage.append("              [--pct-permission PERCENT]\n");
         usage.append("              [--pkg-blacklist-file PACKAGE_BLACKLIST_FILE]\n");
         usage.append("              [--pkg-whitelist-file PACKAGE_WHITELIST_FILE]\n");
         usage.append("              [--wait-dbg] [--dbg-no-events]\n");
@@ -1365,6 +1343,7 @@
         usage.append("              [--script-log]\n");
         usage.append("              [--bugreport]\n");
         usage.append("              [--periodic-bugreport]\n");
+        usage.append("              [--permission-target-system]\n");
         usage.append("              COUNT\n");
         System.err.println(usage.toString());
     }
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyEvent.java
index 0a06604..ccae799 100644
--- a/cmds/monkey/src/com/android/commands/monkey/MonkeyEvent.java
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyEvent.java
@@ -31,7 +31,8 @@
     public static final int EVENT_TYPE_ACTIVITY = 4;
     public static final int EVENT_TYPE_FLIP = 5; // Keyboard flip
     public static final int EVENT_TYPE_THROTTLE = 6;
-    public static final int EVENT_TYPE_NOOP = 7;
+    public static final int EVENT_TYPE_PERMISSION = 7;
+    public static final int EVENT_TYPE_NOOP = 8;
 
     public static final int INJECT_SUCCESS = 1;
     public static final int INJECT_FAIL = 0;
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyPermissionEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyPermissionEvent.java
new file mode 100644
index 0000000..130827b
--- /dev/null
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyPermissionEvent.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.monkey;
+
+import android.app.IActivityManager;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.view.IWindowManager;
+
+public class MonkeyPermissionEvent extends MonkeyEvent {
+    private String mPkg;
+    private PermissionInfo mPermissionInfo;
+
+    public MonkeyPermissionEvent(String pkg, PermissionInfo permissionInfo) {
+        super(EVENT_TYPE_PERMISSION);
+        mPkg = pkg;
+        mPermissionInfo = permissionInfo;
+    }
+
+    @Override
+    public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
+        IPackageManager pm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+        try {
+            // determine if we should grant or revoke permission
+            int perm = pm.checkPermission(mPermissionInfo.name, mPkg, UserHandle.myUserId());
+            boolean grant = perm == PackageManager.PERMISSION_DENIED;
+            // log before calling pm in case we hit an error
+            System.out.println(String.format(":Permission %s %s to package %s",
+                    grant ? "grant" : "revoke", mPermissionInfo.name, mPkg));
+            if (grant) {
+                pm.grantRuntimePermission(mPkg, mPermissionInfo.name, UserHandle.myUserId());
+            } else {
+                pm.revokeRuntimePermission(mPkg, mPermissionInfo.name, UserHandle.myUserId());
+            }
+            return MonkeyEvent.INJECT_SUCCESS;
+        } catch (RemoteException re) {
+            return MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION;
+        }
+    }
+}
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyPermissionUtil.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyPermissionUtil.java
new file mode 100644
index 0000000..8ab4937
--- /dev/null
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyPermissionUtil.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.monkey;
+
+import android.Manifest;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * Utility class that encapsulates runtime permission related methods for monkey
+ *
+ */
+public class MonkeyPermissionUtil {
+
+    private static final String PERMISSION_PREFIX = "android.permission.";
+    private static final String PERMISSION_GROUP_PREFIX = "android.permission-group.";
+
+    // from com.android.packageinstaller.permission.utils
+    private static final String[] MODERN_PERMISSION_GROUPS = {
+            Manifest.permission_group.CALENDAR, Manifest.permission_group.CAMERA,
+            Manifest.permission_group.CONTACTS, Manifest.permission_group.LOCATION,
+            Manifest.permission_group.SENSORS, Manifest.permission_group.SMS,
+            Manifest.permission_group.PHONE, Manifest.permission_group.MICROPHONE,
+            Manifest.permission_group.STORAGE
+    };
+
+    // from com.android.packageinstaller.permission.utils
+    private static boolean isModernPermissionGroup(String name) {
+        for (String modernGroup : MODERN_PERMISSION_GROUPS) {
+            if (modernGroup.equals(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * actual list of packages to target, with invalid packages excluded, and may optionally include
+     * system packages
+     */
+    private List<String> mTargetedPackages;
+    /** if we should target system packages regardless if they are listed */
+    private boolean mTargetSystemPackages;
+    private IPackageManager mPm;
+
+    /** keep track of runtime permissions requested for each package targeted */
+    private Map<String, List<PermissionInfo>> mPermissionMap;
+
+    public MonkeyPermissionUtil() {
+        mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+    }
+
+    public void setTargetSystemPackages(boolean targetSystemPackages) {
+        mTargetSystemPackages = targetSystemPackages;
+    }
+
+    /**
+     * Decide if a package should be targeted by permission monkey
+     * @param info
+     * @return
+     */
+    private boolean shouldTargetPackage(PackageInfo info) {
+        // target if permitted by white listing / black listing rules
+        if (MonkeyUtils.getPackageFilter().checkEnteringPackage(info.packageName)) {
+            return true;
+        }
+        if (mTargetSystemPackages
+                // not explicitly black listed
+                && !MonkeyUtils.getPackageFilter().isPackageInvalid(info.packageName)
+                // is a system app
+                && (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean shouldTargetPermission(String pkg, PermissionInfo pi) throws RemoteException {
+        int flags = mPm.getPermissionFlags(pi.name, pkg, UserHandle.myUserId());
+        int fixedPermFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+                | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+        return pi.group != null && pi.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS
+                && ((flags & fixedPermFlags) == 0)
+                && isModernPermissionGroup(pi.group);
+    }
+
+    public boolean populatePermissionsMapping() {
+        mPermissionMap = new HashMap<>();
+        try {
+            List<?> pkgInfos = mPm.getInstalledPackages(
+                    PackageManager.GET_PERMISSIONS, UserHandle.myUserId()).getList();
+            for (Object o : pkgInfos) {
+                PackageInfo info = (PackageInfo)o;
+                if (!shouldTargetPackage(info)) {
+                    continue;
+                }
+                List<PermissionInfo> permissions = new ArrayList<>();
+                if (info.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+                    // skip apps targetting lower API level
+                    continue;
+                }
+                if (info.requestedPermissions == null) {
+                    continue;
+                }
+                for (String perm : info.requestedPermissions) {
+                    PermissionInfo pi = mPm.getPermissionInfo(perm, 0);
+                    if (pi != null && shouldTargetPermission(info.packageName, pi)) {
+                        permissions.add(pi);
+                    }
+                }
+                if (!permissions.isEmpty()) {
+                    mPermissionMap.put(info.packageName, permissions);
+                }
+            }
+        } catch (RemoteException re) {
+            System.err.println("** Failed talking with package manager!");
+            return false;
+        }
+        if (!mPermissionMap.isEmpty()) {
+            mTargetedPackages = new ArrayList<>(mPermissionMap.keySet());
+        }
+        return true;
+    }
+
+    public void dump() {
+        System.out.println("// Targeted packages and permissions:");
+        for (Map.Entry<String, List<PermissionInfo>> e : mPermissionMap.entrySet()) {
+            System.out.println(String.format("//  + Using %s", e.getKey()));
+            for (PermissionInfo pi : e.getValue()) {
+                String name = pi.name;
+                if (name != null) {
+                    if (name.startsWith(PERMISSION_PREFIX)) {
+                        name = name.substring(PERMISSION_PREFIX.length());
+                    }
+                }
+                String group = pi.group;
+                if (group != null) {
+                    if (group.startsWith(PERMISSION_GROUP_PREFIX)) {
+                        group = group.substring(PERMISSION_GROUP_PREFIX.length());
+                    }
+                }
+                System.out.println(String.format("//    Permission: %s [%s]", name, group));
+            }
+        }
+    }
+
+    public MonkeyPermissionEvent generateRandomPermissionEvent(Random random) {
+        String pkg = mTargetedPackages.get(random.nextInt(mTargetedPackages.size()));
+        List<PermissionInfo> infos = mPermissionMap.get(pkg);
+        return new MonkeyPermissionEvent(pkg, infos.get(random.nextInt(infos.size())));
+    }
+}
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
index af6a231..1762f17 100644
--- a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
@@ -26,7 +26,7 @@
 import android.view.MotionEvent;
 import android.view.Surface;
 
-import java.util.ArrayList;
+import java.util.List;
 import java.util.Random;
 
 /**
@@ -82,8 +82,9 @@
     public static final int FACTOR_SYSOPS       = 7;
     public static final int FACTOR_APPSWITCH    = 8;
     public static final int FACTOR_FLIP         = 9;
-    public static final int FACTOR_ANYTHING     = 10;
-    public static final int FACTORZ_COUNT       = 11;    // should be last+1
+    public static final int FACTOR_PERMISSION   = 10;
+    public static final int FACTOR_ANYTHING     = 11;
+    public static final int FACTORZ_COUNT       = 12;    // should be last+1
 
     private static final int GESTURE_TAP = 0;
     private static final int GESTURE_DRAG = 1;
@@ -93,12 +94,13 @@
      * values after we read any optional values.
      **/
     private float[] mFactors = new float[FACTORZ_COUNT];
-    private ArrayList<ComponentName> mMainApps;
+    private List<ComponentName> mMainApps;
     private int mEventCount = 0;  //total number of events generated so far
     private MonkeyEventQueue mQ;
     private Random mRandom;
     private int mVerbose = 0;
     private long mThrottle = 0;
+    private MonkeyPermissionUtil mPermissionUtil;
 
     private boolean mKeyboardOpen = false;
 
@@ -117,8 +119,8 @@
         return KeyEvent.keyCodeFromString(keyName);
     }
 
-    public MonkeySourceRandom(Random random, ArrayList<ComponentName> MainApps,
-            long throttle, boolean randomizeThrottle) {
+    public MonkeySourceRandom(Random random, List<ComponentName> MainApps,
+            long throttle, boolean randomizeThrottle, boolean permissionTargetSystem) {
         // default values for random distributions
         // note, these are straight percentages, to match user input (cmd line args)
         // but they will be converted to 0..1 values before the main loop runs.
@@ -132,12 +134,16 @@
         mFactors[FACTOR_SYSOPS] = 2.0f;
         mFactors[FACTOR_APPSWITCH] = 2.0f;
         mFactors[FACTOR_FLIP] = 1.0f;
+        // disbale permission by default
+        mFactors[FACTOR_PERMISSION] = 0.0f;
         mFactors[FACTOR_ANYTHING] = 13.0f;
         mFactors[FACTOR_PINCHZOOM] = 2.0f;
 
         mRandom = random;
         mMainApps = MainApps;
         mQ = new MonkeyEventQueue(random, throttle, randomizeThrottle);
+        mPermissionUtil = new MonkeyPermissionUtil();
+        mPermissionUtil.setTargetSystemPackages(permissionTargetSystem);
     }
 
     /**
@@ -410,6 +416,9 @@
         } else if (cls < mFactors[FACTOR_ROTATION]) {
             generateRotationEvent(mRandom);
             return;
+        } else if (cls < mFactors[FACTOR_PERMISSION]) {
+            mQ.add(mPermissionUtil.generateRandomPermissionEvent(mRandom));
+            return;
         }
 
         // The remaining event categories are injected as key events
@@ -450,8 +459,15 @@
     }
 
     public boolean validate() {
-        //check factors
-        return adjustEventFactors();
+        boolean ret = true;
+        // only populate & dump permissions if enabled
+        if (mFactors[FACTOR_PERMISSION] != 0.0f) {
+            ret &= mPermissionUtil.populatePermissionsMapping();
+            if (ret && mVerbose >= 2) {
+                mPermissionUtil.dump();
+            }
+        }
+        return ret & adjustEventFactors();
     }
 
     public void setVerbose(int verbose) {
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyUtils.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyUtils.java
index aa7a666..8b447c0 100644
--- a/cmds/monkey/src/com/android/commands/monkey/MonkeyUtils.java
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyUtils.java
@@ -17,6 +17,9 @@
 package com.android.commands.monkey;
 
 import java.text.SimpleDateFormat;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
 
 /**
  * Misc utilities.
@@ -26,6 +29,7 @@
     private static final java.util.Date DATE = new java.util.Date();
     private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat(
         "yyyy-MM-dd HH:mm:ss.SSS ");
+    private static PackageFilter sFilter;
 
     private MonkeyUtils() {
     }
@@ -38,4 +42,72 @@
         return DATE_FORMATTER.format(DATE);
     }
 
+    public static PackageFilter getPackageFilter() {
+        if (sFilter == null) {
+            sFilter = new PackageFilter();
+        }
+        return sFilter;
+    }
+
+    public static class PackageFilter {
+        private Set<String> mValidPackages = new HashSet<>();
+        private Set<String> mInvalidPackages = new HashSet<>();
+
+        private PackageFilter() {
+        }
+
+        public void addValidPackages(Set<String> validPackages) {
+            mValidPackages.addAll(validPackages);
+        }
+
+        public void addInvalidPackages(Set<String> invalidPackages) {
+            mInvalidPackages.addAll(invalidPackages);
+        }
+
+        public boolean hasValidPackages() {
+            return mValidPackages.size() > 0;
+        }
+
+        public boolean isPackageValid(String pkg) {
+            return mValidPackages.contains(pkg);
+        }
+
+        public boolean isPackageInvalid(String pkg) {
+            return mInvalidPackages.contains(pkg);
+        }
+
+        /**
+         * Check whether we should run against the given package.
+         *
+         * @param pkg The package name.
+         * @return Returns true if we should run against pkg.
+         */
+        public boolean checkEnteringPackage(String pkg) {
+            if (mInvalidPackages.size() > 0) {
+                if (mInvalidPackages.contains(pkg)) {
+                    return false;
+                }
+            } else if (mValidPackages.size() > 0) {
+                if (!mValidPackages.contains(pkg)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public void dump() {
+            if (mValidPackages.size() > 0) {
+                Iterator<String> it = mValidPackages.iterator();
+                while (it.hasNext()) {
+                    System.out.println(":AllowPackage: " + it.next());
+                }
+            }
+            if (mInvalidPackages.size() > 0) {
+                Iterator<String> it = mInvalidPackages.iterator();
+                while (it.hasNext()) {
+                    System.out.println(":DisallowPackage: " + it.next());
+                }
+            }
+        }
+    }
 }