diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 862d2356ecdaa..42ac67c7ae446 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1233,6 +1233,14 @@ public class AppOpsManager { return null; } + /** @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 { diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 73c48334b1d14..9fa2c23dc9532 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -38,6 +38,7 @@ interface IAppOpsService { int checkPackage(int uid, String packageName); List getPackagesForOps(in int[] ops); List 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 417f18db9d76c..ec027895ce99f 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -32,6 +32,7 @@ import java.util.Map; 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.Log; 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 @@ public class AppOpsService extends IAppOpsService.Stub { } }; - final SparseArray> mUidOps - = new SparseArray>(); + final SparseArray mUidStates = new SparseArray<>(); private final SparseArray mOpRestrictions = new SparseArray(); + private static final class UidState { + public final int uid; + public ArrayMap 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 { 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 class AppOpsService extends IAppOpsService.Stub { public void systemReady() { synchronized (this) { boolean changed = false; - for (int i=0; i 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 pkgs = uidState.pkgOps; + if (pkgs == null) { + continue; + } + Iterator 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 class AppOpsService extends IAppOpsService.Stub { public void packageRemoved(int uid, String packageName) { synchronized (this) { - HashMap 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 @@ public class AppOpsService extends IAppOpsService.Stub { Binder.getCallingPid(), Binder.getCallingUid(), null); ArrayList res = null; synchronized (this) { - for (int i=0; i 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 packages = uidState.pkgOps; + final int packageCount = packages.size(); + for (int j = 0; j < packageCount; j++) { + Ops pkgOps = packages.valueAt(j); ArrayList resOps = collectOps(pkgOps, ops); if (resOps != null) { if (res == null) { res = new ArrayList(); } AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps( - pkgOps.packageName, pkgOps.uid, resOps); + pkgOps.packageName, pkgOps.uidState.uid, resOps); res.add(resPackage); } } @@ -380,7 +437,7 @@ public class AppOpsService extends IAppOpsService.Stub { } ArrayList res = new ArrayList(); 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 @@ public class AppOpsService extends IAppOpsService.Stub { if (ops != null) { ops.remove(op.op); if (ops.size() <= 0) { - HashMap pkgOps = mUidOps.get(uid); + UidState uidState = ops.uidState; + ArrayMap 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); } } } @@ -404,6 +465,113 @@ public class AppOpsService extends IAppOpsService.Stub { } } + @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> callbackSpecs = null; + + ArrayList 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 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 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()) { @@ -414,6 +582,7 @@ public class AppOpsService extends IAppOpsService.Stub { ArrayList 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 @@ public class AppOpsService extends IAppOpsService.Stub { if (callbacks == null) { callbacks = new HashMap>>(); } + boolean duplicate = false; for (int i=0; i> reports = callbacks.get(cb); if (reports == null) { reports = new ArrayList>(); callbacks.put(cb, reports); + } else { + final int reportCount = reports.size(); + for (int j = 0; j < reportCount; j++) { + Pair report = reports.get(j); + if (report.second == op && report.first.equals(packageName)) { + duplicate = true; + break; + } + } + } + if (!duplicate) { + reports.add(new Pair<>(packageName, op)); } - reports.add(new Pair(packageName, op)); } return callbacks; } @@ -488,16 +669,54 @@ public class AppOpsService extends IAppOpsService.Stub { 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>> callbacks = null; synchronized (this) { boolean changed = false; - for (int i=mUidOps.size()-1; i>=0; i--) { - HashMap 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 packages = uidState.pkgOps; Iterator> it = packages.entrySet().iterator(); while (it.hasNext()) { Map.Entry ent = it.next(); @@ -526,10 +745,11 @@ public class AppOpsService extends IAppOpsService.Stub { it.remove(); } } - if (packages.size() == 0) { - mUidOps.removeAt(i); + if (uidState.isDefault()) { + mUidStates.remove(uidState.uid); } } + if (changed) { scheduleFastWriteLocked(); } @@ -552,7 +772,7 @@ public class AppOpsService extends IAppOpsService.Stub { @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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { } 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { } private Ops getOpsRawLocked(int uid, String packageName, boolean edit) { - HashMap 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(); - 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { String tagName = parser.getName(); if (tagName.equals("pkg")) { readPackage(parser); + } if (tagName.equals("uid")) { + readUidOps(parser); } else { Slog.w(TAG, "Unknown element under : " + parser.getName()); @@ -1021,7 +1289,7 @@ public class AppOpsService extends IAppOpsService.Stub { Slog.w(TAG, "Failed parsing " + e); } finally { if (!success) { - mUidOps.clear(); + mUidStates.clear(); } try { stream.close(); @@ -1032,6 +1300,34 @@ public class AppOpsService extends IAppOpsService.Stub { } } + 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 : " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + } + void readPackage(XmlPullParser parser) throws NumberFormatException, XmlPullParserException, IOException { String pkgName = parser.getAttributeValue(null, "n"); @@ -1114,15 +1410,16 @@ public class AppOpsService extends IAppOpsService.Stub { if (proxyPackageName != null) { op.proxyPackageName = proxyPackageName; } - HashMap pkgOps = mUidOps.get(uid); - if (pkgOps == null) { - pkgOps = new HashMap(); - 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { if (needSep) { pw.println(); } - for (int i=0; i pkgOps = mUidOps.valueAt(i); + for (int i=0; i