summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/AppOpsService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server/AppOpsService.java')
-rw-r--r--services/java/com/android/server/AppOpsService.java366
1 files changed, 316 insertions, 50 deletions
diff --git a/services/java/com/android/server/AppOpsService.java b/services/java/com/android/server/AppOpsService.java
index 539194c..aff994c 100644
--- a/services/java/com/android/server/AppOpsService.java
+++ b/services/java/com/android/server/AppOpsService.java
@@ -18,15 +18,22 @@ package com.android.server;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.AsyncTask;
import android.os.Binder;
-import android.os.Environment;
+import android.os.Handler;
import android.os.Process;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -34,23 +41,53 @@ import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
+import android.util.Xml;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
public class AppOpsService extends IAppOpsService.Stub {
static final String TAG = "AppOps";
+ static final boolean DEBUG = false;
+
+ // Write at most every 30 minutes.
+ static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
Context mContext;
final AtomicFile mFile;
+ final Handler mHandler;
+
+ boolean mWriteScheduled;
+ final Runnable mWriteRunner = new Runnable() {
+ public void run() {
+ synchronized (AppOpsService.this) {
+ mWriteScheduled = false;
+ AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+ @Override protected Void doInBackground(Void... params) {
+ writeState();
+ return null;
+ }
+ };
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
+ }
+ }
+ };
final SparseArray<HashMap<String, Ops>> mUidOps
= new SparseArray<HashMap<String, Ops>>();
final static class Ops extends SparseArray<Op> {
public final String packageName;
+ public final int uid;
- public Ops(String _packageName) {
+ public Ops(String _packageName, int _uid) {
packageName = _packageName;
+ uid = _uid;
}
}
@@ -58,14 +95,17 @@ public class AppOpsService extends IAppOpsService.Stub {
public final int op;
public int duration;
public long time;
+ public int nesting;
public Op(int _op) {
op = _op;
}
}
- public AppOpsService() {
- mFile = new AtomicFile(new File(Environment.getSecureDataDirectory(), "appops.xml"));
+ public AppOpsService(File storagePath) {
+ mFile = new AtomicFile(storagePath);
+ mHandler = new Handler();
+ readState();
}
public void publish(Context context) {
@@ -75,94 +115,126 @@ public class AppOpsService extends IAppOpsService.Stub {
public void shutdown() {
Slog.w(TAG, "Writing app ops before shutdown...");
+ boolean doWrite = false;
+ synchronized (this) {
+ if (mWriteScheduled) {
+ mWriteScheduled = false;
+ doWrite = true;
+ }
+ }
+ if (doWrite) {
+ writeState();
+ }
}
@Override
- public int noteOperation(int code, int uid, String packageName) {
- uid = handleIncomingUid(uid);
+ public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ ArrayList<AppOpsManager.PackageOps> res = null;
synchronized (this) {
- Op op = getOpLocked(code, uid, packageName);
- if (op == null) {
- return AppOpsManager.MODE_IGNORED;
- }
- if (op.duration == -1) {
- Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
- + " code " + code + " time=" + op.time + " duration=" + op.duration);
+ for (int i=0; i<mUidOps.size(); i++) {
+ HashMap<String, Ops> packages = mUidOps.valueAt(i);
+ for (Ops pkgOps : packages.values()) {
+ ArrayList<AppOpsManager.OpEntry> resOps = null;
+ if (ops == null) {
+ resOps = new ArrayList<AppOpsManager.OpEntry>();
+ for (int j=0; j<pkgOps.size(); j++) {
+ Op curOp = pkgOps.valueAt(j);
+ resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.time,
+ curOp.duration));
+ }
+ } else {
+ for (int j=0; j<ops.length; j++) {
+ Op curOp = pkgOps.get(ops[j]);
+ if (curOp != null) {
+ if (resOps == null) {
+ resOps = new ArrayList<AppOpsManager.OpEntry>();
+ }
+ resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.time,
+ curOp.duration));
+ }
+ }
+ }
+ if (resOps != null) {
+ if (res == null) {
+ res = new ArrayList<AppOpsManager.PackageOps>();
+ }
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ pkgOps.packageName, pkgOps.uid, resOps);
+ res.add(resPackage);
+ }
+ }
}
- op.time = System.currentTimeMillis();
- op.duration = 0;
}
- return AppOpsManager.MODE_ALLOWED;
+ return res;
}
@Override
- public int startOperation(int code, int uid, String packageName) {
+ public int checkOperation(int code, int uid, String packageName) {
uid = handleIncomingUid(uid);
synchronized (this) {
- Op op = getOpLocked(code, uid, packageName);
+ Op op = getOpLocked(code, uid, packageName, false);
if (op == null) {
- return AppOpsManager.MODE_IGNORED;
- }
- if (op.duration == -1) {
- Slog.w(TAG, "Starting op not finished: uid " + uid + " pkg " + packageName
- + " code " + code + " time=" + op.time + " duration=" + op.duration);
+ return AppOpsManager.MODE_ALLOWED;
}
- op.time = System.currentTimeMillis();
- op.duration = -1;
}
return AppOpsManager.MODE_ALLOWED;
}
@Override
- public void finishOperation(int code, int uid, String packageName) {
+ public int noteOperation(int code, int uid, String packageName) {
uid = handleIncomingUid(uid);
synchronized (this) {
- Op op = getOpLocked(code, uid, packageName);
+ Op op = getOpLocked(code, uid, packageName, true);
if (op == null) {
- return;
+ return AppOpsManager.MODE_IGNORED;
}
- if (op.duration != -1) {
- Slog.w(TAG, "Ignoring finishing op not started: uid " + uid + " pkg " + packageName
+ if (op.duration == -1) {
+ Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
+ " code " + code + " time=" + op.time + " duration=" + op.duration);
- return;
}
- op.duration = (int)(System.currentTimeMillis() - op.time);
+ op.time = System.currentTimeMillis();
+ op.duration = 0;
}
+ return AppOpsManager.MODE_ALLOWED;
}
@Override
- public int noteTimedOperation(int code, int uid, String packageName, int duration) {
+ public int startOperation(int code, int uid, String packageName) {
uid = handleIncomingUid(uid);
synchronized (this) {
- Op op = getOpLocked(code, uid, packageName);
+ Op op = getOpLocked(code, uid, packageName, true);
if (op == null) {
return AppOpsManager.MODE_IGNORED;
}
- if (op.duration == -1) {
- Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
- + " code " + code + " time=" + op.time + " duration=" + op.duration);
+ if (op.nesting == 0) {
+ op.time = System.currentTimeMillis();
+ op.duration = -1;
}
- op.time = System.currentTimeMillis();
- op.duration = duration;
+ op.nesting++;
}
return AppOpsManager.MODE_ALLOWED;
}
@Override
- public void earlyFinishOperation(int code, int uid, String packageName) {
+ public void finishOperation(int code, int uid, String packageName) {
uid = handleIncomingUid(uid);
synchronized (this) {
- Op op = getOpLocked(code, uid, packageName);
+ Op op = getOpLocked(code, uid, packageName, true);
if (op == null) {
return;
}
- if (op.duration != -1) {
- Slog.w(TAG, "Noting timed op not finished: uid " + uid + " pkg " + packageName
- + " code " + code + " time=" + op.time + " duration=" + op.duration);
- }
- int newDuration = (int)(System.currentTimeMillis() - op.time);
- if (newDuration < op.duration) {
- op.duration = newDuration;
+ if (op.nesting <= 1) {
+ if (op.nesting == 1) {
+ op.duration = (int)(System.currentTimeMillis() - op.time);
+ } else {
+ Slog.w(TAG, "Finishing op nesting under-run: uid " + uid + " pkg " + packageName
+ + " code " + code + " time=" + op.time + " duration=" + op.duration
+ + " nesting=" + op.nesting);
+ }
+ } else {
+ op.nesting--;
}
}
}
@@ -179,14 +251,20 @@ public class AppOpsService extends IAppOpsService.Stub {
return uid;
}
- private Op getOpLocked(int code, int uid, String packageName) {
+ private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
HashMap<String, Ops> pkgOps = mUidOps.get(uid);
if (pkgOps == null) {
+ if (!edit) {
+ return null;
+ }
pkgOps = new HashMap<String, Ops>();
mUidOps.put(uid, pkgOps);
}
Ops ops = pkgOps.get(packageName);
if (ops == null) {
+ if (!edit) {
+ return null;
+ }
// This is the first time we have seen this package name under this uid,
// so let's make sure it is valid.
final long ident = Binder.clearCallingIdentity();
@@ -207,17 +285,205 @@ public class AppOpsService extends IAppOpsService.Stub {
} finally {
Binder.restoreCallingIdentity(ident);
}
- ops = new Ops(packageName);
+ ops = new Ops(packageName, uid);
pkgOps.put(packageName, ops);
}
Op op = ops.get(code);
if (op == null) {
+ if (!edit) {
+ return null;
+ }
op = new Op(code);
ops.put(code, op);
}
+ if (edit && !mWriteScheduled) {
+ mWriteScheduled = true;
+ mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+ }
return op;
}
+ void readState() {
+ synchronized (mFile) {
+ synchronized (this) {
+ FileInputStream stream;
+ try {
+ stream = mFile.openRead();
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+ return;
+ }
+ boolean success = false;
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("no start tag found");
+ }
+
+ int outerDepth = parser.getDepth();
+ 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("pkg")) {
+ readPackage(parser);
+ } else {
+ Slog.w(TAG, "Unknown element under <app-ops>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ success = true;
+ } catch (IllegalStateException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IndexOutOfBoundsException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } finally {
+ if (!success) {
+ mUidOps.clear();
+ }
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+ }
+
+ void readPackage(XmlPullParser parser) throws NumberFormatException,
+ XmlPullParserException, IOException {
+ String pkgName = 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("uid")) {
+ readUid(parser, pkgName);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException,
+ XmlPullParserException, IOException {
+ 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")) {
+ Op op = new Op(Integer.parseInt(parser.getAttributeValue(null, "n")));
+ op.time = Long.parseLong(parser.getAttributeValue(null, "t"));
+ op.duration = Integer.parseInt(parser.getAttributeValue(null, "d"));
+ HashMap<String, Ops> pkgOps = mUidOps.get(uid);
+ if (pkgOps == null) {
+ pkgOps = new HashMap<String, Ops>();
+ mUidOps.put(uid, pkgOps);
+ }
+ Ops ops = pkgOps.get(pkgName);
+ if (ops == null) {
+ ops = new Ops(pkgName, uid);
+ pkgOps.put(pkgName, ops);
+ }
+ ops.put(op.op, op);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ void writeState() {
+ synchronized (mFile) {
+ List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
+
+ FileOutputStream stream;
+ try {
+ stream = mFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state: " + e);
+ return;
+ }
+
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, "app-ops");
+
+ if (allOps != null) {
+ String lastPkg = null;
+ for (int i=0; i<allOps.size(); i++) {
+ AppOpsManager.PackageOps pkg = allOps.get(i);
+ if (!pkg.getPackageName().equals(lastPkg)) {
+ if (lastPkg != null) {
+ out.endTag(null, "pkg");
+ }
+ lastPkg = pkg.getPackageName();
+ out.startTag(null, "pkg");
+ out.attribute(null, "n", lastPkg);
+ }
+ out.startTag(null, "uid");
+ out.attribute(null, "n", Integer.toString(pkg.getUid()));
+ List<AppOpsManager.OpEntry> ops = pkg.getOps();
+ for (int j=0; j<ops.size(); j++) {
+ AppOpsManager.OpEntry op = ops.get(j);
+ out.startTag(null, "op");
+ out.attribute(null, "n", Integer.toString(op.getOp()));
+ out.attribute(null, "t", Long.toString(op.getTime()));
+ out.attribute(null, "d", Integer.toString(op.getDuration()));
+ out.endTag(null, "op");
+ }
+ out.endTag(null, "uid");
+ }
+ if (lastPkg != null) {
+ out.endTag(null, "pkg");
+ }
+ }
+
+ out.endTag(null, "app-ops");
+ out.endDocument();
+ mFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state, restoring backup.", e);
+ mFile.failWrite(stream);
+ }
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)