am 9a3d898a: am 4bdf81a0: am 4f3d5c75: am 1eb06eaa: am 8dbd4848: Merge "Add per UID control to app ops." into mnc-dev

* commit '9a3d898a5802b4d3eb398fe618a4c2f202f96753':
  Add per UID control to app ops.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 862d235..42ac67c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1234,6 +1234,14 @@
     }
 
     /** @hide */
+    public void setUidMode(int code, int uid, int mode) {
+        try {
+            mService.setUidMode(code, uid, mode);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /** @hide */
     public void setMode(int code, int uid, String packageName, int mode) {
         try {
             mService.setMode(code, uid, packageName, mode);
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 73c4833..9fa2c23 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -38,6 +38,7 @@
     int checkPackage(int uid, String packageName);
     List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
     List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
+    void setUidMode(int code, int uid, int mode);
     void setMode(int code, int uid, String packageName, int mode);
     void resetAllModes(int reqUserId, String reqPackageName);
     int checkAudioOperation(int code, int usage, int uid, String packageName);
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 417f18d..ec02789 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -32,6 +32,7 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityThread;
+import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -56,15 +57,18 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.Xml;
 
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.os.Zygote;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
 
+import libcore.util.EmptyArray;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -98,19 +102,38 @@
         }
     };
 
-    final SparseArray<HashMap<String, Ops>> mUidOps
-            = new SparseArray<HashMap<String, Ops>>();
+    final SparseArray<UidState> mUidStates = new SparseArray<>();
 
     private final SparseArray<boolean[]> mOpRestrictions = new SparseArray<boolean[]>();
 
+    private static final class UidState {
+        public final int uid;
+        public ArrayMap<String, Ops> pkgOps;
+        public SparseIntArray opModes;
+
+        public UidState(int uid) {
+            this.uid = uid;
+        }
+
+        public void clear() {
+            pkgOps = null;
+            opModes = null;
+        }
+
+        public boolean isDefault() {
+            return (pkgOps == null || pkgOps.isEmpty())
+                    && (opModes == null || opModes.size() <= 0);
+        }
+    }
+
     public final static class Ops extends SparseArray<Op> {
         public final String packageName;
-        public final int uid;
+        public final UidState uidState;
         public final boolean isPrivileged;
 
-        public Ops(String _packageName, int _uid, boolean _isPrivileged) {
+        public Ops(String _packageName, UidState _uidState, boolean _isPrivileged) {
             packageName = _packageName;
-            uid = _uid;
+            uidState = _uidState;
             isPrivileged = _isPrivileged;
         }
     }
@@ -220,27 +243,42 @@
     public void systemReady() {
         synchronized (this) {
             boolean changed = false;
-            for (int i=0; i<mUidOps.size(); i++) {
-                HashMap<String, Ops> pkgs = mUidOps.valueAt(i);
+            for (int i = mUidStates.size() - 1; i >= 0; i--) {
+                UidState uidState = mUidStates.valueAt(i);
+
+                String[] packageNames = getPackagesForUid(uidState.uid);
+                if (ArrayUtils.isEmpty(packageNames)) {
+                    uidState.clear();
+                    mUidStates.removeAt(i);
+                    changed = true;
+                    continue;
+                }
+
+                ArrayMap<String, Ops> pkgs = uidState.pkgOps;
+                if (pkgs == null) {
+                    continue;
+                }
+
                 Iterator<Ops> it = pkgs.values().iterator();
                 while (it.hasNext()) {
                     Ops ops = it.next();
                     int curUid;
                     try {
                         curUid = mContext.getPackageManager().getPackageUid(ops.packageName,
-                                UserHandle.getUserId(ops.uid));
+                                UserHandle.getUserId(ops.uidState.uid));
                     } catch (NameNotFoundException e) {
                         curUid = -1;
                     }
-                    if (curUid != ops.uid) {
+                    if (curUid != ops.uidState.uid) {
                         Slog.i(TAG, "Pruning old package " + ops.packageName
-                                + "/" + ops.uid + ": new uid=" + curUid);
+                                + "/" + ops.uidState + ": new uid=" + curUid);
                         it.remove();
                         changed = true;
                     }
                 }
-                if (pkgs.size() <= 0) {
-                    mUidOps.removeAt(i);
+
+                if (uidState.isDefault()) {
+                    mUidStates.removeAt(i);
                 }
             }
             if (changed) {
@@ -279,22 +317,34 @@
 
     public void packageRemoved(int uid, String packageName) {
         synchronized (this) {
-            HashMap<String, Ops> pkgs = mUidOps.get(uid);
-            if (pkgs != null) {
-                if (pkgs.remove(packageName) != null) {
-                    if (pkgs.size() <= 0) {
-                        mUidOps.remove(uid);
-                    }
-                    scheduleFastWriteLocked();
-                }
+            UidState uidState = mUidStates.get(uid);
+            if (uidState == null) {
+                return;
+            }
+
+            boolean changed = false;
+
+            // Remove any package state if such.
+            if (uidState.pkgOps != null && uidState.pkgOps.remove(packageName) != null) {
+                changed = true;
+            }
+
+            // If we just nuked the last package state check if the UID is valid.
+            if (changed && uidState.pkgOps.isEmpty()
+                    && getPackagesForUid(uid).length <= 0) {
+                mUidStates.remove(uid);
+            }
+
+            if (changed) {
+                scheduleFastWriteLocked();
             }
         }
     }
 
     public void uidRemoved(int uid) {
         synchronized (this) {
-            if (mUidOps.indexOfKey(uid) >= 0) {
-                mUidOps.remove(uid);
+            if (mUidStates.indexOfKey(uid) >= 0) {
+                mUidStates.remove(uid);
                 scheduleFastWriteLocked();
             }
         }
@@ -346,16 +396,23 @@
                 Binder.getCallingPid(), Binder.getCallingUid(), null);
         ArrayList<AppOpsManager.PackageOps> res = null;
         synchronized (this) {
-            for (int i=0; i<mUidOps.size(); i++) {
-                HashMap<String, Ops> packages = mUidOps.valueAt(i);
-                for (Ops pkgOps : packages.values()) {
+            final int uidStateCount = mUidStates.size();
+            for (int i = 0; i < uidStateCount; i++) {
+                UidState uidState = mUidStates.valueAt(i);
+                if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
+                    continue;
+                }
+                ArrayMap<String, Ops> packages = uidState.pkgOps;
+                final int packageCount = packages.size();
+                for (int j = 0; j < packageCount; j++) {
+                    Ops pkgOps = packages.valueAt(j);
                     ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
                     if (resOps != null) {
                         if (res == null) {
                             res = new ArrayList<AppOpsManager.PackageOps>();
                         }
                         AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                                pkgOps.packageName, pkgOps.uid, resOps);
+                                pkgOps.packageName, pkgOps.uidState.uid, resOps);
                         res.add(resPackage);
                     }
                 }
@@ -380,7 +437,7 @@
             }
             ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
             AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                    pkgOps.packageName, pkgOps.uid, resOps);
+                    pkgOps.packageName, pkgOps.uidState.uid, resOps);
             res.add(resPackage);
             return res;
         }
@@ -392,11 +449,15 @@
             if (ops != null) {
                 ops.remove(op.op);
                 if (ops.size() <= 0) {
-                    HashMap<String, Ops> pkgOps = mUidOps.get(uid);
+                    UidState uidState = ops.uidState;
+                    ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
                     if (pkgOps != null) {
                         pkgOps.remove(ops.packageName);
-                        if (pkgOps.size() <= 0) {
-                            mUidOps.remove(uid);
+                        if (pkgOps.isEmpty()) {
+                            uidState.pkgOps = null;
+                        }
+                        if (uidState.isDefault()) {
+                            mUidStates.remove(uid);
                         }
                     }
                 }
@@ -405,6 +466,113 @@
     }
 
     @Override
+    public void setUidMode(int code, int uid, int mode) {
+        if (Binder.getCallingPid() != Process.myPid()) {
+            mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                    Binder.getCallingPid(), Binder.getCallingUid(), null);
+        }
+        verifyIncomingOp(code);
+        code = AppOpsManager.opToSwitch(code);
+
+        synchronized (this) {
+            final int defaultMode = AppOpsManager.opToDefaultMode(code);
+
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState == null) {
+                if (mode == defaultMode) {
+                    return;
+                }
+                uidState = new UidState(uid);
+                uidState.opModes = new SparseIntArray();
+                uidState.opModes.put(code, mode);
+                mUidStates.put(uid, uidState);
+                scheduleWriteLocked();
+            } else if (uidState.opModes == null) {
+                if (mode != defaultMode) {
+                    uidState.opModes = new SparseIntArray();
+                    uidState.opModes.put(code, mode);
+                    scheduleWriteLocked();
+                }
+            } else {
+                if (uidState.opModes.get(code) == mode) {
+                    return;
+                }
+                if (mode == defaultMode) {
+                    uidState.opModes.delete(code);
+                    if (uidState.opModes.size() <= 0) {
+                        uidState.opModes = null;
+                    }
+                } else {
+                    uidState.opModes.put(code, mode);
+                }
+                scheduleWriteLocked();
+            }
+        }
+
+        ArrayMap<Callback, ArraySet<String>> callbackSpecs = null;
+
+        ArrayList<Callback> callbacks = mOpModeWatchers.get(code);
+        if (callbacks != null) {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                Callback callback = callbacks.get(i);
+                callbackSpecs = new ArrayMap<>();
+                callbackSpecs.put(callback, null);
+            }
+        }
+
+        String[] uidPackageNames = getPackagesForUid(uid);
+        for (String uidPackageName : uidPackageNames) {
+            callbacks = mPackageModeWatchers.get(uidPackageName);
+            if (callbacks != null) {
+                if (callbackSpecs == null) {
+                    callbackSpecs = new ArrayMap<>();
+                }
+                final int callbackCount = callbacks.size();
+                for (int i = 0; i < callbackCount; i++) {
+                    Callback callback = callbacks.get(i);
+                    ArraySet<String> changedPackages = callbackSpecs.get(callback);
+                    if (changedPackages == null) {
+                        changedPackages = new ArraySet<>();
+                        callbackSpecs.put(callback, changedPackages);
+                    }
+                    changedPackages.add(uidPackageName);
+                }
+            }
+        }
+
+        if (callbackSpecs == null) {
+            return;
+        }
+
+        // There are components watching for mode changes such as window manager
+        // and location manager which are in our process. The callbacks in these
+        // components may require permissions our remote caller does not have.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            for (int i = 0; i < callbackSpecs.size(); i++) {
+                Callback callback = callbackSpecs.keyAt(i);
+                ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
+                try {
+                    if (reportedPackageNames == null) {
+                        callback.mCallback.opChanged(code, null);
+                    } else {
+                        final int reportedPackageCount = reportedPackageNames.size();
+                        for (int j = 0; j < reportedPackageCount; j++) {
+                            String reportedPackageName = reportedPackageNames.valueAt(j);
+                            callback.mCallback.opChanged(code, reportedPackageName);
+                        }
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Error dispatching op op change", e);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     public void setMode(int code, int uid, String packageName, int mode) {
         if (Binder.getCallingPid() != Process.myPid()) {
             mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
@@ -414,6 +582,7 @@
         ArrayList<Callback> repCbs = null;
         code = AppOpsManager.opToSwitch(code);
         synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, false);
             Op op = getOpLocked(code, uid, packageName, true);
             if (op != null) {
                 if (op.mode != mode) {
@@ -468,14 +637,26 @@
         if (callbacks == null) {
             callbacks = new HashMap<Callback, ArrayList<Pair<String, Integer>>>();
         }
+        boolean duplicate = false;
         for (int i=0; i<cbs.size(); i++) {
             Callback cb = cbs.get(i);
             ArrayList<Pair<String, Integer>> reports = callbacks.get(cb);
             if (reports == null) {
                 reports = new ArrayList<Pair<String, Integer>>();
                 callbacks.put(cb, reports);
+            } else {
+                final int reportCount = reports.size();
+                for (int j = 0; j < reportCount; j++) {
+                    Pair<String, Integer> report = reports.get(j);
+                    if (report.second == op && report.first.equals(packageName)) {
+                        duplicate = true;
+                        break;
+                    }
+                }
             }
-            reports.add(new Pair<String, Integer>(packageName, op));
+            if (!duplicate) {
+                reports.add(new Pair<>(packageName, op));
+            }
         }
         return callbacks;
     }
@@ -488,16 +669,54 @@
                 callingPid, callingUid, null);
         reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
                 true, true, "resetAllModes", null);
+
+        int reqUid = -1;
+        if (reqPackageName != null) {
+            try {
+                reqUid = AppGlobals.getPackageManager().getPackageUid(
+                        reqPackageName, reqUserId);
+            } catch (RemoteException e) {
+                /* ignore - local call */
+            }
+        }
+
         HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks = null;
         synchronized (this) {
             boolean changed = false;
-            for (int i=mUidOps.size()-1; i>=0; i--) {
-                HashMap<String, Ops> packages = mUidOps.valueAt(i);
+            for (int i = mUidStates.size() - 1; i >= 0; i--) {
+                UidState uidState = mUidStates.valueAt(i);
+
+                SparseIntArray opModes = uidState.opModes;
+                if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
+                    final int uidOpCount = opModes.size();
+                    for (int j = uidOpCount - 1; j >= 0; j--) {
+                        final int code = opModes.keyAt(j);
+                        if (AppOpsManager.opAllowsReset(code)) {
+                            opModes.removeAt(j);
+                            if (opModes.size() <= 0) {
+                                uidState.opModes = null;
+                            }
+                            for (String packageName : getPackagesForUid(uidState.uid)) {
+                                callbacks = addCallbacks(callbacks, packageName, code,
+                                        mOpModeWatchers.get(code));
+                                callbacks = addCallbacks(callbacks, packageName, code,
+                                        mPackageModeWatchers.get(packageName));
+                            }
+                        }
+                    }
+                }
+
+                if (uidState.pkgOps == null) {
+                    continue;
+                }
+
                 if (reqUserId != UserHandle.USER_ALL
-                        && reqUserId != UserHandle.getUserId(mUidOps.keyAt(i))) {
+                        && reqUserId != UserHandle.getUserId(uidState.uid)) {
                     // Skip any ops for a different user
                     continue;
                 }
+
+                Map<String, Ops> packages = uidState.pkgOps;
                 Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
                 while (it.hasNext()) {
                     Map.Entry<String, Ops> ent = it.next();
@@ -526,10 +745,11 @@
                         it.remove();
                     }
                 }
-                if (packages.size() == 0) {
-                    mUidOps.removeAt(i);
+                if (uidState.isDefault()) {
+                    mUidStates.remove(uidState.uid);
                 }
             }
+
             if (changed) {
                 scheduleFastWriteLocked();
             }
@@ -552,7 +772,7 @@
     @Override
     public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) {
         synchronized (this) {
-            op = AppOpsManager.opToSwitch(op);
+            op = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
             Callback cb = mModeWatchers.get(callback.asBinder());
             if (cb == null) {
                 cb = new Callback(callback);
@@ -621,7 +841,15 @@
             if (isOpRestricted(uid, code, packageName)) {
                 return AppOpsManager.MODE_IGNORED;
             }
-            Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, false);
+            code = AppOpsManager.opToSwitch(code);
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState != null && uidState.opModes != null) {
+                final int uidMode = uidState.opModes.get(code);
+                if (uidMode != AppOpsManager.MODE_ALLOWED) {
+                    return uidMode;
+                }
+            }
+            Op op = getOpLocked(code, uid, packageName, false);
             if (op == null) {
                 return AppOpsManager.opToDefaultMode(code);
             }
@@ -732,6 +960,17 @@
             }
             op.duration = 0;
             final int switchCode = AppOpsManager.opToSwitch(code);
+            UidState uidState = ops.uidState;
+            if (uidState.opModes != null) {
+                final int uidMode = uidState.opModes.get(switchCode);
+                if (uidMode != AppOpsManager.MODE_ALLOWED) {
+                    if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "
+                            + switchCode + " (" + code + ") uid " + uid + " package "
+                            + packageName);
+                    op.rejectTime = System.currentTimeMillis();
+                    return uidMode;
+                }
+            }
             final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
             if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
                 if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "
@@ -766,6 +1005,17 @@
                 return AppOpsManager.MODE_IGNORED;
             }
             final int switchCode = AppOpsManager.opToSwitch(code);
+            UidState uidState = ops.uidState;
+            if (uidState.opModes != null) {
+                final int uidMode = uidState.opModes.get(switchCode);
+                if (uidMode != AppOpsManager.MODE_ALLOWED) {
+                    if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "
+                            + switchCode + " (" + code + ") uid " + uid + " package "
+                            + packageName);
+                    op.rejectTime = System.currentTimeMillis();
+                    return uidMode;
+                }
+            }
             final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
             if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
                 if (DEBUG) Log.d(TAG, "startOperation: reject #" + op.mode + " for code "
@@ -847,6 +1097,18 @@
         throw new IllegalArgumentException("Bad operation #" + op);
     }
 
+    private UidState getUidStateLocked(int uid, boolean edit) {
+        UidState uidState = mUidStates.get(uid);
+        if (uidState == null) {
+            if (!edit) {
+                return null;
+            }
+            uidState = new UidState(uid);
+            mUidStates.put(uid, uidState);
+        }
+        return uidState;
+    }
+
     private Ops getOpsLocked(int uid, String packageName, boolean edit) {
         if (uid == 0) {
             packageName = "root";
@@ -857,15 +1119,19 @@
     }
 
     private Ops getOpsRawLocked(int uid, String packageName, boolean edit) {
-        HashMap<String, Ops> pkgOps = mUidOps.get(uid);
-        if (pkgOps == null) {
+        UidState uidState = getUidStateLocked(uid, edit);
+        if (uidState == null) {
+            return null;
+        }
+
+        if (uidState.pkgOps == null) {
             if (!edit) {
                 return null;
             }
-            pkgOps = new HashMap<String, Ops>();
-            mUidOps.put(uid, pkgOps);
+            uidState.pkgOps = new ArrayMap<>();
         }
-        Ops ops = pkgOps.get(packageName);
+
+        Ops ops = uidState.pkgOps.get(packageName);
         if (ops == null) {
             if (!edit) {
                 return null;
@@ -904,8 +1170,8 @@
                     Binder.restoreCallingIdentity(ident);
                 }
             }
-            ops = new Ops(packageName, uid, isPrivileged);
-            pkgOps.put(packageName, ops);
+            ops = new Ops(packageName, uidState, isPrivileged);
+            uidState.pkgOps.put(packageName, ops);
         }
         return ops;
     }
@@ -940,7 +1206,7 @@
             if (!edit) {
                 return null;
             }
-            op = new Op(ops.uid, ops.packageName, code);
+            op = new Op(ops.uidState.uid, ops.packageName, code);
             ops.put(code, op);
         }
         if (edit) {
@@ -1000,6 +1266,8 @@
                         String tagName = parser.getName();
                         if (tagName.equals("pkg")) {
                             readPackage(parser);
+                        } if (tagName.equals("uid")) {
+                            readUidOps(parser);
                         } else {
                             Slog.w(TAG, "Unknown element under <app-ops>: "
                                     + parser.getName());
@@ -1021,7 +1289,7 @@
                     Slog.w(TAG, "Failed parsing " + e);
                 } finally {
                     if (!success) {
-                        mUidOps.clear();
+                        mUidStates.clear();
                     }
                     try {
                         stream.close();
@@ -1032,6 +1300,34 @@
         }
     }
 
+    void readUidOps(XmlPullParser parser) throws NumberFormatException,
+            XmlPullParserException, IOException {
+        final int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                final int code = Integer.parseInt(parser.getAttributeValue(null, "n"));
+                final int mode = Integer.parseInt(parser.getAttributeValue(null, "m"));
+                UidState uidState = getUidStateLocked(uid, true);
+                if (uidState.opModes == null) {
+                    uidState.opModes = new SparseIntArray();
+                }
+                uidState.opModes.put(code, mode);
+            } else {
+                Slog.w(TAG, "Unknown element under <uid-ops>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
     void readPackage(XmlPullParser parser) throws NumberFormatException,
             XmlPullParserException, IOException {
         String pkgName = parser.getAttributeValue(null, "n");
@@ -1114,15 +1410,16 @@
                 if (proxyPackageName != null) {
                     op.proxyPackageName = proxyPackageName;
                 }
-                HashMap<String, Ops> pkgOps = mUidOps.get(uid);
-                if (pkgOps == null) {
-                    pkgOps = new HashMap<String, Ops>();
-                    mUidOps.put(uid, pkgOps);
+
+                UidState uidState = getUidStateLocked(uid, true);
+                if (uidState.pkgOps == null) {
+                    uidState.pkgOps = new ArrayMap<>();
                 }
-                Ops ops = pkgOps.get(pkgName);
+
+                Ops ops = uidState.pkgOps.get(pkgName);
                 if (ops == null) {
-                    ops = new Ops(pkgName, uid, isPrivileged);
-                    pkgOps.put(pkgName, ops);
+                    ops = new Ops(pkgName, uidState, isPrivileged);
+                    uidState.pkgOps.put(pkgName, ops);
                 }
                 ops.put(op.op, op);
             } else {
@@ -1149,7 +1446,27 @@
                 XmlSerializer out = new FastXmlSerializer();
                 out.setOutput(stream, StandardCharsets.UTF_8.name());
                 out.startDocument(null, true);
-                out.startTag(null, "app-ops");
+                out.startTag(null, "app");
+
+                final int uidStateCount = mUidStates.size();
+                for (int i = 0; i < uidStateCount; i++) {
+                    UidState uidState = mUidStates.valueAt(i);
+                    if (uidState.opModes != null && uidState.opModes.size() > 0) {
+                        out.startTag(null, "uid");
+                        out.attribute(null, "n", Integer.toString(uidState.uid));
+                        SparseIntArray uidOpModes = uidState.opModes;
+                        final int opCount = uidOpModes.size();
+                        for (int j = 0; j < opCount; j++) {
+                            final int op = uidOpModes.keyAt(j);
+                            final int mode = uidOpModes.valueAt(j);
+                            out.startTag(null, "op");
+                            out.attribute(null, "n", Integer.toString(op));
+                            out.attribute(null, "m", Integer.toString(mode));
+                            out.endTag(null, "op");
+                        }
+                        out.endTag(null, "uid");
+                    }
+                }
 
                 if (allOps != null) {
                     String lastPkg = null;
@@ -1316,9 +1633,27 @@
             if (needSep) {
                 pw.println();
             }
-            for (int i=0; i<mUidOps.size(); i++) {
-                pw.print("  Uid "); UserHandle.formatUid(pw, mUidOps.keyAt(i)); pw.println(":");
-                HashMap<String, Ops> pkgOps = mUidOps.valueAt(i);
+            for (int i=0; i<mUidStates.size(); i++) {
+                UidState uidState = mUidStates.valueAt(i);
+
+                pw.print("  Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
+
+                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);
+                        pw.print("      "); pw.print(AppOpsManager.opToName(code));
+                        pw.print(": mode="); pw.println(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 j=0; j<ops.size(); j++) {
@@ -1382,4 +1717,12 @@
         }
     }
 
+    private static String[] getPackagesForUid(int uid) {
+        try {
+            return AppGlobals.getPackageManager().getPackagesForUid(uid);
+        } catch (RemoteException e) {
+            /* ignore - local call */
+        }
+        return EmptyArray.STRING;
+    }
 }