OSDN Git Service

Work on issue #78480444: Start tracking uid state in app ops
authorDianne Hackborn <hackbod@google.com>
Thu, 26 Apr 2018 20:46:22 +0000 (13:46 -0700)
committerDianne Hackborn <hackbod@google.com>
Wed, 2 May 2018 00:36:28 +0000 (17:36 -0700)
Introduce new app op mode that uses uid state to determine whether
the caller has access.  This will determine what noteOp() and
startOp() return, based on the state of the uid.

Bug: 78480444
Test: atest FrameworksServicesTests:AppOpsServiceTest
Test: atest CtsPermissionTestCases:AppOpsTest
Change-Id: I12b744b74f3129782dbda9567043f5170919b5d3
Merged-In: I55fd74023cc4dae8151372e28c3afc7d259c7a1c

core/java/android/app/AppOpsManager.java
services/core/java/com/android/server/AppOpsService.java

index dcf4eec..796e406 100644 (file)
@@ -110,6 +110,27 @@ public class AppOpsManager {
     public static final int MODE_DEFAULT = 3;
 
     /**
+     * Special mode that means "allow only when app is in foreground."  This is <b>not</b>
+     * returned from {@link #checkOp}, {@link #noteOp}, {@link #startOp}; rather, when this
+     * mode is set, these functions will return {@link #MODE_ALLOWED} when the app being
+     * checked is currently in the foreground, otherwise {@link #MODE_IGNORED}.
+     * @hide
+     */
+    public static final int MODE_FOREGROUND = 4;
+
+
+    /**
+     * @hide
+     */
+    public static final String[] MODE_NAMES = new String[] {
+            "allow",        // MODE_ALLOWED
+            "ignore",       // MODE_IGNORED
+            "deny",         // MODE_ERRORED
+            "default",      // MODE_DEFAULT
+            "foreground",   // MODE_FOREGROUND
+    };
+
+    /**
      * Metrics about an op when its uid is persistent.
      * @hide
      */
@@ -619,83 +640,83 @@ public class AppOpsManager {
      * make them all controlled by the same single operation.
      */
     private static int[] sOpToSwitch = new int[] {
-            OP_COARSE_LOCATION,
-            OP_COARSE_LOCATION,
-            OP_COARSE_LOCATION,
-            OP_VIBRATE,
-            OP_READ_CONTACTS,
-            OP_WRITE_CONTACTS,
-            OP_READ_CALL_LOG,
-            OP_WRITE_CALL_LOG,
-            OP_READ_CALENDAR,
-            OP_WRITE_CALENDAR,
-            OP_COARSE_LOCATION,
-            OP_POST_NOTIFICATION,
-            OP_COARSE_LOCATION,
-            OP_CALL_PHONE,
-            OP_READ_SMS,
-            OP_WRITE_SMS,
-            OP_RECEIVE_SMS,
-            OP_RECEIVE_SMS,
-            OP_RECEIVE_MMS,
-            OP_RECEIVE_WAP_PUSH,
-            OP_SEND_SMS,
-            OP_READ_SMS,
-            OP_WRITE_SMS,
-            OP_WRITE_SETTINGS,
-            OP_SYSTEM_ALERT_WINDOW,
-            OP_ACCESS_NOTIFICATIONS,
-            OP_CAMERA,
-            OP_RECORD_AUDIO,
-            OP_PLAY_AUDIO,
-            OP_READ_CLIPBOARD,
-            OP_WRITE_CLIPBOARD,
-            OP_TAKE_MEDIA_BUTTONS,
-            OP_TAKE_AUDIO_FOCUS,
-            OP_AUDIO_MASTER_VOLUME,
-            OP_AUDIO_VOICE_VOLUME,
-            OP_AUDIO_RING_VOLUME,
-            OP_AUDIO_MEDIA_VOLUME,
-            OP_AUDIO_ALARM_VOLUME,
-            OP_AUDIO_NOTIFICATION_VOLUME,
-            OP_AUDIO_BLUETOOTH_VOLUME,
-            OP_WAKE_LOCK,
-            OP_COARSE_LOCATION,
-            OP_COARSE_LOCATION,
-            OP_GET_USAGE_STATS,
-            OP_MUTE_MICROPHONE,
-            OP_TOAST_WINDOW,
-            OP_PROJECT_MEDIA,
-            OP_ACTIVATE_VPN,
-            OP_WRITE_WALLPAPER,
-            OP_ASSIST_STRUCTURE,
-            OP_ASSIST_SCREENSHOT,
-            OP_READ_PHONE_STATE,
-            OP_ADD_VOICEMAIL,
-            OP_USE_SIP,
-            OP_PROCESS_OUTGOING_CALLS,
-            OP_USE_FINGERPRINT,
-            OP_BODY_SENSORS,
-            OP_READ_CELL_BROADCASTS,
-            OP_MOCK_LOCATION,
-            OP_READ_EXTERNAL_STORAGE,
-            OP_WRITE_EXTERNAL_STORAGE,
-            OP_TURN_SCREEN_ON,
-            OP_GET_ACCOUNTS,
-            OP_RUN_IN_BACKGROUND,
-            OP_AUDIO_ACCESSIBILITY_VOLUME,
-            OP_READ_PHONE_NUMBERS,
-            OP_REQUEST_INSTALL_PACKAGES,
-            OP_PICTURE_IN_PICTURE,
-            OP_INSTANT_APP_START_FOREGROUND,
-            OP_ANSWER_PHONE_CALLS,
-            OP_RUN_ANY_IN_BACKGROUND,
-            OP_CHANGE_WIFI_STATE,
-            OP_REQUEST_DELETE_PACKAGES,
-            OP_BIND_ACCESSIBILITY_SERVICE,
-            OP_ACCEPT_HANDOVER,
-            OP_MANAGE_IPSEC_TUNNELS,
-            OP_START_FOREGROUND,
+            OP_COARSE_LOCATION,                 // COARSE_LOCATION
+            OP_COARSE_LOCATION,                 // FINE_LOCATION
+            OP_COARSE_LOCATION,                 // GPS
+            OP_VIBRATE,                         // VIBRATE
+            OP_READ_CONTACTS,                   // READ_CONTACTS
+            OP_WRITE_CONTACTS,                  // WRITE_CONTACTS
+            OP_READ_CALL_LOG,                   // READ_CALL_LOG
+            OP_WRITE_CALL_LOG,                  // WRITE_CALL_LOG
+            OP_READ_CALENDAR,                   // READ_CALENDAR
+            OP_WRITE_CALENDAR,                  // WRITE_CALENDAR
+            OP_COARSE_LOCATION,                 // WIFI_SCAN
+            OP_POST_NOTIFICATION,               // POST_NOTIFICATION
+            OP_COARSE_LOCATION,                 // NEIGHBORING_CELLS
+            OP_CALL_PHONE,                      // CALL_PHONE
+            OP_READ_SMS,                        // READ_SMS
+            OP_WRITE_SMS,                       // WRITE_SMS
+            OP_RECEIVE_SMS,                     // RECEIVE_SMS
+            OP_RECEIVE_SMS,                     // RECEIVE_EMERGECY_SMS
+            OP_RECEIVE_MMS,                     // RECEIVE_MMS
+            OP_RECEIVE_WAP_PUSH,                // RECEIVE_WAP_PUSH
+            OP_SEND_SMS,                        // SEND_SMS
+            OP_READ_SMS,                        // READ_ICC_SMS
+            OP_WRITE_SMS,                       // WRITE_ICC_SMS
+            OP_WRITE_SETTINGS,                  // WRITE_SETTINGS
+            OP_SYSTEM_ALERT_WINDOW,             // SYSTEM_ALERT_WINDOW
+            OP_ACCESS_NOTIFICATIONS,            // ACCESS_NOTIFICATIONS
+            OP_CAMERA,                          // CAMERA
+            OP_RECORD_AUDIO,                    // RECORD_AUDIO
+            OP_PLAY_AUDIO,                      // PLAY_AUDIO
+            OP_READ_CLIPBOARD,                  // READ_CLIPBOARD
+            OP_WRITE_CLIPBOARD,                 // WRITE_CLIPBOARD
+            OP_TAKE_MEDIA_BUTTONS,              // TAKE_MEDIA_BUTTONS
+            OP_TAKE_AUDIO_FOCUS,                // TAKE_AUDIO_FOCUS
+            OP_AUDIO_MASTER_VOLUME,             // AUDIO_MASTER_VOLUME
+            OP_AUDIO_VOICE_VOLUME,              // AUDIO_VOICE_VOLUME
+            OP_AUDIO_RING_VOLUME,               // AUDIO_RING_VOLUME
+            OP_AUDIO_MEDIA_VOLUME,              // AUDIO_MEDIA_VOLUME
+            OP_AUDIO_ALARM_VOLUME,              // AUDIO_ALARM_VOLUME
+            OP_AUDIO_NOTIFICATION_VOLUME,       // AUDIO_NOTIFICATION_VOLUME
+            OP_AUDIO_BLUETOOTH_VOLUME,          // AUDIO_BLUETOOTH_VOLUME
+            OP_WAKE_LOCK,                       // WAKE_LOCK
+            OP_COARSE_LOCATION,                 // MONITOR_LOCATION
+            OP_COARSE_LOCATION,                 // MONITOR_HIGH_POWER_LOCATION
+            OP_GET_USAGE_STATS,                 // GET_USAGE_STATS
+            OP_MUTE_MICROPHONE,                 // MUTE_MICROPHONE
+            OP_TOAST_WINDOW,                    // TOAST_WINDOW
+            OP_PROJECT_MEDIA,                   // PROJECT_MEDIA
+            OP_ACTIVATE_VPN,                    // ACTIVATE_VPN
+            OP_WRITE_WALLPAPER,                 // WRITE_WALLPAPER
+            OP_ASSIST_STRUCTURE,                // ASSIST_STRUCTURE
+            OP_ASSIST_SCREENSHOT,               // ASSIST_SCREENSHOT
+            OP_READ_PHONE_STATE,                // READ_PHONE_STATE
+            OP_ADD_VOICEMAIL,                   // ADD_VOICEMAIL
+            OP_USE_SIP,                         // USE_SIP
+            OP_PROCESS_OUTGOING_CALLS,          // PROCESS_OUTGOING_CALLS
+            OP_USE_FINGERPRINT,                 // USE_FINGERPRINT
+            OP_BODY_SENSORS,                    // BODY_SENSORS
+            OP_READ_CELL_BROADCASTS,            // READ_CELL_BROADCASTS
+            OP_MOCK_LOCATION,                   // MOCK_LOCATION
+            OP_READ_EXTERNAL_STORAGE,           // READ_EXTERNAL_STORAGE
+            OP_WRITE_EXTERNAL_STORAGE,          // WRITE_EXTERNAL_STORAGE
+            OP_TURN_SCREEN_ON,                  // TURN_SCREEN_ON
+            OP_GET_ACCOUNTS,                    // GET_ACCOUNTS
+            OP_RUN_IN_BACKGROUND,               // RUN_IN_BACKGROUND
+            OP_AUDIO_ACCESSIBILITY_VOLUME,      // AUDIO_ACCESSIBILITY_VOLUME
+            OP_READ_PHONE_NUMBERS,              // READ_PHONE_NUMBERS
+            OP_REQUEST_INSTALL_PACKAGES,        // REQUEST_INSTALL_PACKAGES
+            OP_PICTURE_IN_PICTURE,              // ENTER_PICTURE_IN_PICTURE_ON_HIDE
+            OP_INSTANT_APP_START_FOREGROUND,    // INSTANT_APP_START_FOREGROUND
+            OP_ANSWER_PHONE_CALLS,              // ANSWER_PHONE_CALLS
+            OP_RUN_ANY_IN_BACKGROUND,           // OP_RUN_ANY_IN_BACKGROUND
+            OP_CHANGE_WIFI_STATE,               // OP_CHANGE_WIFI_STATE
+            OP_REQUEST_DELETE_PACKAGES,         // OP_REQUEST_DELETE_PACKAGES
+            OP_BIND_ACCESSIBILITY_SERVICE,      // OP_BIND_ACCESSIBILITY_SERVICE
+            OP_ACCEPT_HANDOVER,                 // ACCEPT_HANDOVER
+            OP_MANAGE_IPSEC_TUNNELS,            // MANAGE_IPSEC_HANDOVERS
+            OP_START_FOREGROUND,                // START_FOREGROUND
     };
 
     /**
@@ -1420,19 +1441,11 @@ public class AppOpsManager {
      * Retrieve the human readable mode.
      * @hide
      */
-    public static String modeToString(int mode) {
-        switch (mode) {
-            case MODE_ALLOWED:
-                return "allow";
-            case MODE_IGNORED:
-                return "ignore";
-            case MODE_ERRORED:
-                return "deny";
-            case MODE_DEFAULT:
-                return "default";
-            default:
-                return "mode=" + mode;
+    public static String modeToName(int mode) {
+        if (mode >= 0 && mode < MODE_NAMES.length) {
+            return MODE_NAMES[mode];
         }
+        return "mode=" + mode;
     }
 
     /**
@@ -1554,30 +1567,42 @@ public class AppOpsManager {
         }
 
         public long getTime() {
-            long time = 0;
-            for (int i = 0; i < _NUM_UID_STATE; i++) {
-                if (mTimes[i] > time) {
-                    time = mTimes[i];
-                }
-            }
-            return time;
+            return maxTime(mTimes, 0, _NUM_UID_STATE);
+        }
+
+        public long getLastAccessTime() {
+            return maxTime(mTimes, 0, _NUM_UID_STATE);
+        }
+
+        public long getLastAccessForegroundTime() {
+            return maxTime(mTimes, UID_STATE_PERSISTENT, UID_STATE_FOREGROUND_SERVICE + 1);
         }
 
-        public long getTimeFor(int uidState) {
+        public long getLastAccessBackgroundTime() {
+            return maxTime(mTimes, UID_STATE_FOREGROUND_SERVICE + 1, _NUM_UID_STATE);
+        }
+
+        public long getLastTimeFor(int uidState) {
             return mTimes[uidState];
         }
 
         public long getRejectTime() {
-            long time = 0;
-            for (int i = 0; i < _NUM_UID_STATE; i++) {
-                if (mRejectTimes[i] > time) {
-                    time = mRejectTimes[i];
-                }
-            }
-            return time;
+            return maxTime(mRejectTimes, 0, _NUM_UID_STATE);
+        }
+
+        public long getLastRejectTime() {
+            return maxTime(mRejectTimes, 0, _NUM_UID_STATE);
+        }
+
+        public long getLastRejectForegroundTime() {
+            return maxTime(mRejectTimes, UID_STATE_PERSISTENT, UID_STATE_FOREGROUND_SERVICE + 1);
+        }
+
+        public long getLastRejectBackgroundTime() {
+            return maxTime(mRejectTimes, UID_STATE_FOREGROUND_SERVICE + 1, _NUM_UID_STATE);
         }
 
-        public long getRejectTimeFor(int uidState) {
+        public long getLastRejectTimeFor(int uidState) {
             return mRejectTimes[uidState];
         }
 
@@ -1680,6 +1705,7 @@ public class AppOpsManager {
      * @param ops The set of operations you are interested in, or null if you want all of them.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
     public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
         try {
             return mService.getPackagesForOps(ops);
@@ -1696,6 +1722,7 @@ public class AppOpsManager {
      * @param ops The set of operations you are interested in, or null if you want all of them.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
     public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) {
         try {
             return mService.getOpsForPackage(uid, packageName, ops);
@@ -2009,6 +2036,17 @@ public class AppOpsManager {
      * used for a quick check to see if an operation has been disabled for the application,
      * as an early reject of some work.  This does not modify the time stamp or other data
      * about the operation.
+     *
+     * <p>Important things this will not do (which you need to ultimate use
+     * {@link #noteOp(String, int, String)} or {@link #startOp(String, int, String)} to cover):</p>
+     * <ul>
+     *     <li>Verifying the uid and package are consistent, so callers can't spoof
+     *     their identity.</li>
+     *     <li>Taking into account the current foreground/background state of the
+     *     app; apps whose mode varies by this state will always be reported
+     *     as {@link #MODE_ALLOWED}.</li>
+     * </ul>
+     *
      * @param op The operation to check.  One of the OPSTR_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
@@ -2128,6 +2166,17 @@ public class AppOpsManager {
      * used for a quick check to see if an operation has been disabled for the application,
      * as an early reject of some work.  This does not modify the time stamp or other data
      * about the operation.
+     *
+     * <p>Important things this will not do (which you need to ultimate use
+     * {@link #noteOp(int, int, String)} or {@link #startOp(int, int, String)} to cover):</p>
+     * <ul>
+     *     <li>Verifying the uid and package are consistent, so callers can't spoof
+     *     their identity.</li>
+     *     <li>Taking into account the current foreground/background state of the
+     *     app; apps whose mode varies by this state will always be reported
+     *     as {@link #MODE_ALLOWED}.</li>
+     * </ul>
+     *
      * @param op The operation to check.  One of the OP_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
@@ -2445,4 +2494,17 @@ public class AppOpsManager {
     public static String[] getOpStrs() {
         return Arrays.copyOf(sOpToString, sOpToString.length);
     }
+
+    /**
+     * @hide
+     */
+    public static long maxTime(long[] times, int start, int end) {
+        long time = 0;
+        for (int i = start; i < end; i++) {
+            if (times[i] > time) {
+                time = times[i];
+            }
+        }
+        return time;
+    }
 }
index d818bd6..13503e6 100644 (file)
@@ -48,6 +48,7 @@ import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.Xml;
@@ -108,6 +109,9 @@ public class AppOpsService extends IAppOpsService.Stub {
     // Write at most every 30 minutes.
     static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
 
+    // How long we want for a drop in uid state to settle before applying it.
+    static final long STATE_SETTLE_TIME = 10*1000;
+
     // Constant meaning that any UID should be matched when dispatching callbacks
     private static final int UID_ANY = -2;
 
@@ -162,13 +166,6 @@ public class AppOpsService extends IAppOpsService.Stub {
             "rc",       // UID_STATE_CACHED
     };
 
-    static final String[] MODE_NAMES = new String[] {
-            "allow",        // MODE_ALLOWED
-            "ignore",       // MODE_IGNORED
-            "deny",         // MODE_ERRORED
-            "default",      // MODE_DEFAULT
-    };
-
     Context mContext;
     final AtomicFile mFile;
     final Handler mHandler;
@@ -194,6 +191,8 @@ public class AppOpsService extends IAppOpsService.Stub {
     @VisibleForTesting
     final SparseArray<UidState> mUidStates = new SparseArray<>();
 
+    long mLastUptime;
+
     /*
      * These are app op restrictions imposed per user from various parties.
      */
@@ -202,11 +201,17 @@ public class AppOpsService extends IAppOpsService.Stub {
     @VisibleForTesting
     static final class UidState {
         public final int uid;
+
         public int state = UID_STATE_CACHED;
+        public int pendingState = UID_STATE_CACHED;
+        public long pendingStateCommitTime;
+
         public int startNesting;
         public ArrayMap<String, Ops> pkgOps;
         public SparseIntArray opModes;
 
+        public SparseBooleanArray foregroundOps;
+
         public UidState(int uid) {
             this.uid = uid;
         }
@@ -220,6 +225,32 @@ public class AppOpsService extends IAppOpsService.Stub {
             return (pkgOps == null || pkgOps.isEmpty())
                     && (opModes == null || opModes.size() <= 0);
         }
+
+        int evalMode(int mode) {
+            if (mode == AppOpsManager.MODE_FOREGROUND) {
+                return state <= UID_STATE_FOREGROUND_SERVICE
+                        ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
+            }
+            return mode;
+        }
+
+        public void evalForegroundOps() {
+            SparseBooleanArray which = null;
+            if (pkgOps != null) {
+                for (int i = pkgOps.size() - 1; i >= 0; i--) {
+                    Ops ops = pkgOps.valueAt(i);
+                    for (int j = ops.size() - 1; j >= 0; j--) {
+                        if (ops.valueAt(j).mode == AppOpsManager.MODE_FOREGROUND) {
+                            if (which == null) {
+                                which = new SparseBooleanArray();
+                            }
+                            which.put(ops.keyAt(j), true);
+                        }
+                    }
+                }
+            }
+            foregroundOps = which;
+        }
     }
 
     final static class Ops extends SparseArray<Op> {
@@ -267,6 +298,10 @@ public class AppOpsService extends IAppOpsService.Stub {
             }
             return false;
         }
+
+        int getMode() {
+            return uidState.evalMode(mode);
+        }
     }
 
     final SparseArray<ArraySet<ModeCallback>> mOpModeWatchers = new SparseArray<>();
@@ -575,7 +610,16 @@ public class AppOpsService extends IAppOpsService.Stub {
         synchronized (this) {
             final UidState uidState = getUidStateLocked(uid, true);
             final int newState = PROCESS_STATE_TO_UID_STATE[procState];
-            if (uidState != null && uidState.state != newState) {
+            if (uidState != null && uidState.pendingState != newState) {
+                if (newState < uidState.state) {
+                    // We are moving to a more important state, always do it immediately.
+                    uidState.state = newState;
+                    uidState.pendingStateCommitTime = 0;
+                } else if (uidState.pendingStateCommitTime == 0) {
+                    // We are moving to a less important state for the first time,
+                    // delay the application for a bit.
+                    uidState.pendingStateCommitTime = SystemClock.uptimeMillis() + STATE_SETTLE_TIME;
+                }
                 if (uidState.startNesting != 0) {
                     // There is some actively running operation...  need to find it
                     // and appropriately update its state.
@@ -585,13 +629,13 @@ public class AppOpsService extends IAppOpsService.Stub {
                         for (int j = ops.size() - 1; j >= 0; j--) {
                             final Op op = ops.valueAt(j);
                             if (op.startNesting > 0) {
-                                op.time[uidState.state] = now;
+                                op.time[uidState.pendingState] = now;
                                 op.time[newState] = now;
                             }
                         }
                     }
                 }
-                uidState.state = newState;
+                uidState.pendingState = newState;
             }
         }
     }
@@ -887,6 +931,9 @@ public class AppOpsService extends IAppOpsService.Stub {
             if (op != null) {
                 if (op.mode != mode) {
                     op.mode = mode;
+                    if (uidState != null) {
+                        uidState.evalForegroundOps();
+                    }
                     ArraySet<ModeCallback> cbs = mOpModeWatchers.get(code);
                     if (cbs != null) {
                         if (repCbs == null) {
@@ -1046,6 +1093,7 @@ public class AppOpsService extends IAppOpsService.Stub {
 
                 Map<String, Ops> packages = uidState.pkgOps;
                 Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+                boolean uidChanged = false;
                 while (it.hasNext()) {
                     Map.Entry<String, Ops> ent = it.next();
                     String packageName = ent.getKey();
@@ -1060,6 +1108,7 @@ public class AppOpsService extends IAppOpsService.Stub {
                                 && curOp.mode != AppOpsManager.opToDefaultMode(curOp.op)) {
                             curOp.mode = AppOpsManager.opToDefaultMode(curOp.op);
                             changed = true;
+                            uidChanged = true;
                             callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
                                     mOpModeWatchers.get(curOp.op));
                             callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
@@ -1076,6 +1125,9 @@ public class AppOpsService extends IAppOpsService.Stub {
                 if (uidState.isDefault()) {
                     mUidStates.remove(uidState.uid);
                 }
+                if (uidChanged) {
+                    uidState.evalForegroundOps();
+                }
             }
 
             if (changed) {
@@ -1197,7 +1249,7 @@ public class AppOpsService extends IAppOpsService.Stub {
             if (op == null) {
                 return AppOpsManager.opToDefaultMode(code);
             }
-            return op.mode;
+            return op.mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : op.mode;
         }
     }
 
@@ -1352,9 +1404,9 @@ public class AppOpsService extends IAppOpsService.Stub {
             // If there is a non-default per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
             if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
-                final int uidMode = uidState.opModes.get(switchCode);
+                final int uidMode = uidState.evalMode(uidState.opModes.get(switchCode));
                 if (uidMode != AppOpsManager.MODE_ALLOWED) {
-                    if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + op.mode + " for code "
+                    if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + packageName);
                     op.rejectTime[uidState.state] = System.currentTimeMillis();
@@ -1362,12 +1414,13 @@ public class AppOpsService extends IAppOpsService.Stub {
                 }
             } else {
                 final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
-                if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
-                    if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + op.mode + " for code "
+                final int mode = switchOp.getMode();
+                if (mode != AppOpsManager.MODE_ALLOWED) {
+                    if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + packageName);
                     op.rejectTime[uidState.state] = System.currentTimeMillis();
-                    return switchOp.mode;
+                    return mode;
                 }
             }
             if (DEBUG) Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
@@ -1458,10 +1511,10 @@ public class AppOpsService extends IAppOpsService.Stub {
             // If there is a non-default per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
             if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
-                final int uidMode = uidState.opModes.get(switchCode);
+                final int uidMode = uidState.evalMode(uidState.opModes.get(switchCode));
                 if (uidMode != AppOpsManager.MODE_ALLOWED
                         && (!startIfModeDefault || uidMode != AppOpsManager.MODE_DEFAULT)) {
-                    if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + op.mode + " for code "
+                    if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + resolvedPackageName);
                     op.rejectTime[uidState.state] = System.currentTimeMillis();
@@ -1469,13 +1522,14 @@ public class AppOpsService extends IAppOpsService.Stub {
                 }
             } else {
                 final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
-                if (switchOp.mode != AppOpsManager.MODE_ALLOWED
-                        && (!startIfModeDefault || switchOp.mode != AppOpsManager.MODE_DEFAULT)) {
-                    if (DEBUG) Slog.d(TAG, "startOperation: reject #" + op.mode + " for code "
+                final int mode = switchOp.getMode();
+                if (mode != AppOpsManager.MODE_ALLOWED
+                        && (!startIfModeDefault || mode != AppOpsManager.MODE_DEFAULT)) {
+                    if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + resolvedPackageName);
                     op.rejectTime[uidState.state] = System.currentTimeMillis();
-                    return switchOp.mode;
+                    return mode;
                 }
             }
             if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
@@ -1642,6 +1696,19 @@ public class AppOpsService extends IAppOpsService.Stub {
             }
             uidState = new UidState(uid);
             mUidStates.put(uid, uidState);
+        } else {
+            if (uidState.pendingStateCommitTime != 0) {
+                if (uidState.pendingStateCommitTime < mLastUptime) {
+                    uidState.state = uidState.pendingState;
+                    uidState.pendingStateCommitTime = 0;
+                } else {
+                    mLastUptime = SystemClock.uptimeMillis();
+                    if (uidState.pendingStateCommitTime < mLastUptime) {
+                        uidState.state = uidState.pendingState;
+                        uidState.pendingStateCommitTime = 0;
+                    }
+                }
+            }
         }
         return uidState;
     }
@@ -1870,6 +1937,7 @@ public class AppOpsService extends IAppOpsService.Stub {
             if (uidState.pkgOps == null) {
                 continue;
             }
+            boolean changed = false;
             for (int j = 0; j < uidState.pkgOps.size(); j++) {
                 Ops ops = uidState.pkgOps.valueAt(j);
                 if (ops != null) {
@@ -1879,9 +1947,13 @@ public class AppOpsService extends IAppOpsService.Stub {
                                 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
                         copy.mode = op.mode;
                         ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
+                        changed = true;
                     }
                 }
             }
+            if (changed) {
+                uidState.evalForegroundOps();
+            }
         }
     }
 
@@ -2077,6 +2149,10 @@ public class AppOpsService extends IAppOpsService.Stub {
                 XmlUtils.skipCurrentTag(parser);
             }
         }
+        UidState uidState = getUidStateLocked(uid, false);
+        if (uidState != null) {
+            uidState.evalForegroundOps();
+        }
     }
 
     void writeState() {
@@ -2152,12 +2228,12 @@ public class AppOpsService extends IAppOpsService.Stub {
                                 out.attribute(null, "m", Integer.toString(op.getMode()));
                             }
                             for (int k = 0; k < _NUM_UID_STATE; k++) {
-                                final long time = op.getTimeFor(k);
+                                final long time = op.getLastTimeFor(k);
                                 if (time != 0) {
                                     out.attribute(null, UID_STATE_TIME_ATTRS[k],
                                             Long.toString(time));
                                 }
-                                final long rejectTime = op.getRejectTimeFor(k);
+                                final long rejectTime = op.getLastRejectTimeFor(k);
                                 if (rejectTime != 0) {
                                     out.attribute(null, UID_STATE_REJECT_ATTRS[k],
                                             Long.toString(rejectTime));
@@ -2229,7 +2305,7 @@ public class AppOpsService extends IAppOpsService.Stub {
             dumpCommandHelp(pw);
         }
 
-        private int strOpToOp(String op, PrintWriter err) {
+        static private int strOpToOp(String op, PrintWriter err) {
             try {
                 return AppOpsManager.strOpToOp(op);
             } catch (IllegalArgumentException e) {
@@ -2247,8 +2323,8 @@ public class AppOpsService extends IAppOpsService.Stub {
         }
 
         int strModeToMode(String modeStr, PrintWriter err) {
-            for (int i = MODE_NAMES.length - 1; i >= 0; i--) {
-                if (MODE_NAMES[i].equals(modeStr)) {
+            for (int i = AppOpsManager.MODE_NAMES.length - 1; i >= 0; i--) {
+                if (AppOpsManager.MODE_NAMES[i].equals(modeStr)) {
                     return i;
                 }
             }
@@ -2470,7 +2546,7 @@ public class AppOpsService extends IAppOpsService.Stub {
                     if (ops == null || ops.size() <= 0) {
                         pw.println("No operations.");
                         if (shell.op > AppOpsManager.OP_NONE && shell.op < AppOpsManager._NUM_OP) {
-                            pw.println("Default mode: " + AppOpsManager.modeToString(
+                            pw.println("Default mode: " + AppOpsManager.modeToName(
                                     AppOpsManager.opToDefaultMode(shell.op)));
                         }
                         return 0;
@@ -2482,7 +2558,7 @@ public class AppOpsService extends IAppOpsService.Stub {
                             AppOpsManager.OpEntry ent = entries.get(j);
                             pw.print(AppOpsManager.opToName(ent.getOp()));
                             pw.print(": ");
-                            pw.print(AppOpsManager.modeToString(ent.getMode()));
+                            pw.print(AppOpsManager.modeToName(ent.getMode()));
                             if (ent.getTime() != 0) {
                                 pw.print("; time=");
                                 TimeUtils.formatDuration(now - ent.getTime(), pw);
@@ -2636,7 +2712,10 @@ public class AppOpsService extends IAppOpsService.Stub {
 
     private void dumpHelp(PrintWriter pw) {
         pw.println("AppOps service (appops) dump options:");
-        pw.println("  none");
+        pw.println("  -h");
+        pw.println("    Print this help text.");
+        pw.println("  --op [OP]");
+        pw.println("    Limit output to data associated with the given app op code.");
     }
 
     private void dumpTimesLocked(PrintWriter pw, String firstPrefix, String prefix, long[] times,
@@ -2671,6 +2750,8 @@ public class AppOpsService extends IAppOpsService.Stub {
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
 
+        int dumpOp = -1;
+
         if (args != null) {
             for (int i=0; i<args.length; i++) {
                 String arg = args[i];
@@ -2679,6 +2760,16 @@ public class AppOpsService extends IAppOpsService.Stub {
                     return;
                 } else if ("-a".equals(arg)) {
                     // dump all data
+                } else if ("--op".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --op option");
+                        return;
+                    }
+                    dumpOp = Shell.strOpToOp(args[i], pw);
+                    if (dumpOp < 0) {
+                        return;
+                    }
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
                     pw.println("Unknown option: " + arg);
                     return;
@@ -2693,13 +2784,21 @@ public class AppOpsService extends IAppOpsService.Stub {
             pw.println("Current AppOps Service state:");
             final long now = System.currentTimeMillis();
             final long nowElapsed = SystemClock.elapsedRealtime();
+            final long nowUptime = SystemClock.uptimeMillis();
             final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
             final Date date = new Date();
             boolean needSep = false;
             if (mOpModeWatchers.size() > 0) {
                 needSep = true;
-                pw.println("  Op mode watchers:");
+                boolean printedHeader = false;
                 for (int i=0; i<mOpModeWatchers.size(); i++) {
+                    if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) {
+                        continue;
+                    }
+                    if (!printedHeader) {
+                        pw.println("  Op mode watchers:");
+                        printedHeader = true;
+                    }
                     pw.print("    Op "); pw.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
                     pw.println(":");
                     ArraySet<ModeCallback> callbacks = mOpModeWatchers.valueAt(i);
@@ -2722,7 +2821,7 @@ public class AppOpsService extends IAppOpsService.Stub {
                     }
                 }
             }
-            if (mModeWatchers.size() > 0) {
+            if (mModeWatchers.size() > 0 && dumpOp < 0) {
                 needSep = true;
                 pw.println("  All op mode watchers:");
                 for (int i=0; i<mModeWatchers.size(); i++) {
@@ -2733,12 +2832,19 @@ public class AppOpsService extends IAppOpsService.Stub {
             }
             if (mActiveWatchers.size() > 0) {
                 needSep = true;
-                pw.println("  All op active watchers:");
+                boolean printedHeader = false;
                 for (int i = 0; i < mActiveWatchers.size(); i++) {
                     final SparseArray<ActiveCallback> activeWatchers = mActiveWatchers.valueAt(i);
                     if (activeWatchers.size() <= 0) {
                         continue;
                     }
+                    if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+                    if (!printedHeader) {
+                        pw.println("  All op active watchers:");
+                        printedHeader = true;
+                    }
                     pw.print("    ");
                     pw.print(Integer.toHexString(System.identityHashCode(
                             mActiveWatchers.keyAt(i))));
@@ -2746,6 +2852,9 @@ public class AppOpsService extends IAppOpsService.Stub {
                     pw.print("        [");
                     final int opCount = activeWatchers.size();
                     for (i = 0; i < opCount; i++) {
+                        if (i > 0) {
+                            pw.print(' ');
+                        }
                         pw.print(AppOpsManager.opToName(activeWatchers.keyAt(i)));
                         if (i < opCount - 1) {
                             pw.print(',');
@@ -2758,15 +2867,30 @@ public class AppOpsService extends IAppOpsService.Stub {
             }
             if (mClients.size() > 0) {
                 needSep = true;
-                pw.println("  Clients:");
+                boolean printedHeader = false;
                 for (int i=0; i<mClients.size(); i++) {
-                    pw.print("    "); pw.print(mClients.keyAt(i)); pw.println(":");
+                    boolean printedClient = false;
                     ClientState cs = mClients.valueAt(i);
-                    pw.print("      "); pw.println(cs);
                     if (cs.mStartedOps.size() > 0) {
-                        pw.println("      Started ops:");
+                        boolean printedStarted = false;
                         for (int j=0; j<cs.mStartedOps.size(); j++) {
                             Op op = cs.mStartedOps.get(j);
+                            if (dumpOp >= 0 && op.op != dumpOp) {
+                                continue;
+                            }
+                            if (!printedHeader) {
+                                pw.println("  Clients:");
+                                printedHeader = true;
+                            }
+                            if (!printedClient) {
+                                pw.print("    "); pw.print(mClients.keyAt(i)); pw.println(":");
+                                pw.print("      "); pw.println(cs);
+                                printedClient = true;
+                            }
+                            if (!printedStarted) {
+                                pw.println("      Started ops:");
+                                printedStarted = true;
+                            }
                             pw.print("        "); pw.print("uid="); pw.print(op.uid);
                             pw.print(" pkg="); pw.print(op.packageName);
                             pw.print(" op="); pw.println(AppOpsManager.opToName(op.op));
@@ -2774,7 +2898,7 @@ public class AppOpsService extends IAppOpsService.Stub {
                     }
                 }
             }
-            if (mAudioRestrictions.size() > 0) {
+            if (mAudioRestrictions.size() > 0 && dumpOp < 0) {
                 boolean printedHeader = false;
                 for (int o=0; o<mAudioRestrictions.size(); o++) {
                     final String op = AppOpsManager.opToName(mAudioRestrictions.keyAt(o));
@@ -2789,7 +2913,7 @@ public class AppOpsService extends IAppOpsService.Stub {
                         pw.print("    "); pw.print(op);
                         pw.print(" usage="); pw.print(AudioAttributes.usageToString(usage));
                         Restriction r = restrictions.valueAt(i);
-                        pw.print(": mode="); pw.println(MODE_NAMES[r.mode]);
+                        pw.print(": mode="); pw.println(AppOpsManager.modeToName(r.mode));
                         if (!r.exceptionPackages.isEmpty()) {
                             pw.println("      Exceptions:");
                             for (int j=0; j<r.exceptionPackages.size(); j++) {
@@ -2804,38 +2928,83 @@ public class AppOpsService extends IAppOpsService.Stub {
             }
             for (int i=0; i<mUidStates.size(); i++) {
                 UidState uidState = mUidStates.valueAt(i);
+                final SparseIntArray opModes = uidState.opModes;
+                final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+
+                if (dumpOp >= 0) {
+                    boolean hasOp = uidState.opModes != null
+                            && uidState.opModes.indexOfKey(dumpOp) >= 0;
+                    if (pkgOps != null) {
+                        for (int pkgi = 0; !hasOp && pkgi < pkgOps.size(); pkgi++) {
+                            Ops ops = pkgOps.valueAt(pkgi);
+                            if (ops != null && ops.indexOfKey(dumpOp) >= 0) {
+                                hasOp = true;
+                                continue;
+                            }
+                        }
+                    }
+                    if (!hasOp) {
+                        continue;
+                    }
+                }
 
                 pw.print("  Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
                 pw.print("    state=");
                 pw.println(UID_STATE_NAMES[uidState.state]);
+                if (uidState.state != uidState.pendingState) {
+                    pw.print("    pendingState=");
+                    pw.println(UID_STATE_NAMES[uidState.pendingState]);
+                }
+                if (uidState.pendingStateCommitTime != 0) {
+                    pw.print("    pendingStateCommitTime=");
+                    TimeUtils.formatDuration(uidState.pendingStateCommitTime, nowUptime, pw);
+                    pw.println();
+                }
                 if (uidState.startNesting != 0) {
                     pw.print("    startNesting=");
                     pw.println(uidState.startNesting);
                 }
+                if (uidState.foregroundOps != null) {
+                    pw.println("    foregroundOps:");
+                    for (int j = 0; j < uidState.foregroundOps.size(); j++) {
+                        pw.print("    ");
+                        pw.println(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
+                    }
+                }
                 needSep = true;
 
-                SparseIntArray opModes = uidState.opModes;
                 if (opModes != null) {
                     final int opModeCount = opModes.size();
                     for (int j = 0; j < opModeCount; j++) {
                         final int code = opModes.keyAt(j);
                         final int mode = opModes.valueAt(j);
+                        if (dumpOp >= 0 && dumpOp != code) {
+                            continue;
+                        }
                         pw.print("      "); pw.print(AppOpsManager.opToName(code));
-                        pw.print(": mode="); pw.println(MODE_NAMES[mode]);
+                        pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
                     }
                 }
 
-                ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
                 if (pkgOps == null) {
                     continue;
                 }
 
-                for (Ops ops : pkgOps.values()) {
-                    pw.print("    Package "); pw.print(ops.packageName); pw.println(":");
+                for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
+                    Ops ops = pkgOps.valueAt(pkgi);
+                    boolean printedPackage = false;
                     for (int j=0; j<ops.size(); j++) {
                         Op op = ops.valueAt(j);
+                        if (dumpOp >= 0 && dumpOp != op.op) {
+                            continue;
+                        }
+                        if (!printedPackage) {
+                            pw.print("    Package "); pw.print(ops.packageName); pw.println(":");
+                            printedPackage = true;
+                        }
                         pw.print("      "); pw.print(AppOpsManager.opToName(op.op));
-                        pw.print(" ("); pw.print(MODE_NAMES[op.mode]); pw.println("): ");
+                        pw.print(" ("); pw.print(AppOpsManager.modeToName(op.mode));
+                        pw.println("): ");
                         dumpTimesLocked(pw,
                                 "          Access: ",
                                 "                  ", op.time, now, sdf, date);