diff options
author | Dianne Hackborn <hackbod@google.com> | 2013-01-14 17:38:02 -0800 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2013-01-16 12:11:01 -0800 |
commit | 35654b61e8fe7bc85afcb076ddbb590d51c5865f (patch) | |
tree | 0f42a90b4deaa0156d84df5d79b78cd6f2ac8807 /services/java/com/android/server/AppOpsService.java | |
parent | 8a8b047f2d28f6b2d728731a7e71eeaf16f89700 (diff) |
More work on App Ops service.
Implemented reading and writing state to retain information
across boots, API to retrieve state from it, improved location
manager interaction to monitor both coarse and fine access
and only note operations when location data is being delivered
back to app (not when it is just registering to get the data at
some time in the future).
Also implement tracking of read/write ops on contacts and the
call log. This involved tweaking the content provider protocol
to pass over the name of the calling package, and some
infrastructure in the ContentProvider transport to note incoming
calls with the app ops service. The contacts provider and call
log provider turn this on for themselves.
This also implements some of the mechanics of being able to ignore
incoming provider calls... all that is left are some new APIs for
the real content provider implementation to be involved with
providing the correct behavior for query() (return an empty
cursor with the right columns) and insert() (need to figure out
what URI to return).
Change-Id: I36ebbcd63dee58264a480f3d3786891ca7cbdb4c
Diffstat (limited to 'services/java/com/android/server/AppOpsService.java')
-rw-r--r-- | services/java/com/android/server/AppOpsService.java | 366 |
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 539194cda53f..aff994c14732 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) |