summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/AppOpsService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/AppOpsService.java')
-rw-r--r--services/core/java/com/android/server/AppOpsService.java1083
1 files changed, 1083 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
new file mode 100644
index 0000000..a1a0d47
--- /dev/null
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -0,0 +1,1083 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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.Iterator;
+import java.util.List;
+import java.util.Map;
+
+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.Handler;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Pair;
+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.app.IAppOpsCallback;
+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>>();
+
+ public final static class Ops extends SparseArray<Op> {
+ public final String packageName;
+ public final int uid;
+
+ public Ops(String _packageName, int _uid) {
+ packageName = _packageName;
+ uid = _uid;
+ }
+ }
+
+ public final static class Op {
+ public final int uid;
+ public final String packageName;
+ public final int op;
+ public int mode;
+ public int duration;
+ public long time;
+ public long rejectTime;
+ public int nesting;
+
+ public Op(int _uid, String _packageName, int _op) {
+ uid = _uid;
+ packageName = _packageName;
+ op = _op;
+ mode = AppOpsManager.opToDefaultMode(op);
+ }
+ }
+
+ final SparseArray<ArrayList<Callback>> mOpModeWatchers
+ = new SparseArray<ArrayList<Callback>>();
+ final ArrayMap<String, ArrayList<Callback>> mPackageModeWatchers
+ = new ArrayMap<String, ArrayList<Callback>>();
+ final ArrayMap<IBinder, Callback> mModeWatchers
+ = new ArrayMap<IBinder, Callback>();
+
+ public final class Callback implements DeathRecipient {
+ final IAppOpsCallback mCallback;
+
+ public Callback(IAppOpsCallback callback) {
+ mCallback = callback;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void unlinkToDeath() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingMode(mCallback);
+ }
+ }
+
+ final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<IBinder, ClientState>();
+
+ public final class ClientState extends Binder implements DeathRecipient {
+ final IBinder mAppToken;
+ final int mPid;
+ final ArrayList<Op> mStartedOps;
+
+ public ClientState(IBinder appToken) {
+ mAppToken = appToken;
+ mPid = Binder.getCallingPid();
+ if (appToken instanceof Binder) {
+ // For local clients, there is no reason to track them.
+ mStartedOps = null;
+ } else {
+ mStartedOps = new ArrayList<Op>();
+ try {
+ mAppToken.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ClientState{" +
+ "mAppToken=" + mAppToken +
+ ", " + (mStartedOps != null ? ("pid=" + mPid) : "local") +
+ '}';
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (AppOpsService.this) {
+ for (int i=mStartedOps.size()-1; i>=0; i--) {
+ finishOperationLocked(mStartedOps.get(i));
+ }
+ mClients.remove(mAppToken);
+ }
+ }
+ }
+
+ public AppOpsService(File storagePath) {
+ mFile = new AtomicFile(storagePath);
+ mHandler = new Handler();
+ readState();
+ }
+
+ public void publish(Context context) {
+ mContext = context;
+ ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
+ }
+
+ public void systemReady() {
+ synchronized (this) {
+ boolean changed = false;
+ for (int i=0; i<mUidOps.size(); i++) {
+ HashMap<String, Ops> pkgs = mUidOps.valueAt(i);
+ 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));
+ } catch (NameNotFoundException e) {
+ curUid = -1;
+ }
+ if (curUid != ops.uid) {
+ Slog.i(TAG, "Pruning old package " + ops.packageName
+ + "/" + ops.uid + ": new uid=" + curUid);
+ it.remove();
+ changed = true;
+ }
+ }
+ if (pkgs.size() <= 0) {
+ mUidOps.removeAt(i);
+ }
+ }
+ if (changed) {
+ scheduleWriteLocked();
+ }
+ }
+ }
+
+ 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);
+ }
+ scheduleWriteLocked();
+ }
+ }
+ }
+ }
+
+ public void uidRemoved(int uid) {
+ synchronized (this) {
+ if (mUidOps.indexOfKey(uid) >= 0) {
+ mUidOps.remove(uid);
+ scheduleWriteLocked();
+ }
+ }
+ }
+
+ 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();
+ }
+ }
+
+ private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
+ 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.mode, curOp.time,
+ curOp.rejectTime, 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.mode, curOp.time,
+ curOp.rejectTime, curOp.duration));
+ }
+ }
+ }
+ return resOps;
+ }
+
+ @Override
+ 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) {
+ for (int i=0; i<mUidOps.size(); i++) {
+ HashMap<String, Ops> packages = mUidOps.valueAt(i);
+ for (Ops pkgOps : packages.values()) {
+ 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);
+ res.add(resPackage);
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ @Override
+ public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+ int[] ops) {
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ synchronized (this) {
+ Ops pkgOps = getOpsLocked(uid, packageName, false);
+ if (pkgOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+ if (resOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ pkgOps.packageName, pkgOps.uid, resOps);
+ res.add(resPackage);
+ return res;
+ }
+ }
+
+ private void pruneOp(Op op, int uid, String packageName) {
+ if (op.time == 0 && op.rejectTime == 0) {
+ Ops ops = getOpsLocked(uid, packageName, false);
+ if (ops != null) {
+ ops.remove(op.op);
+ if (ops.size() <= 0) {
+ HashMap<String, Ops> pkgOps = mUidOps.get(uid);
+ if (pkgOps != null) {
+ pkgOps.remove(ops.packageName);
+ if (pkgOps.size() <= 0) {
+ mUidOps.remove(uid);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setMode(int code, int uid, String packageName, int mode) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ ArrayList<Callback> repCbs = null;
+ code = AppOpsManager.opToSwitch(code);
+ synchronized (this) {
+ Op op = getOpLocked(code, uid, packageName, true);
+ if (op != null) {
+ if (op.mode != mode) {
+ op.mode = mode;
+ ArrayList<Callback> cbs = mOpModeWatchers.get(code);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArrayList<Callback>();
+ }
+ repCbs.addAll(cbs);
+ }
+ cbs = mPackageModeWatchers.get(packageName);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArrayList<Callback>();
+ }
+ repCbs.addAll(cbs);
+ }
+ if (mode == AppOpsManager.opToDefaultMode(op.op)) {
+ // If going into the default mode, prune this op
+ // if there is nothing else interesting in it.
+ pruneOp(op, uid, packageName);
+ }
+ scheduleWriteNowLocked();
+ }
+ }
+ }
+ if (repCbs != null) {
+ for (int i=0; i<repCbs.size(); i++) {
+ try {
+ repCbs.get(i).mCallback.opChanged(code, packageName);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ private static HashMap<Callback, ArrayList<Pair<String, Integer>>> addCallbacks(
+ HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks,
+ String packageName, int op, ArrayList<Callback> cbs) {
+ if (cbs == null) {
+ return callbacks;
+ }
+ if (callbacks == null) {
+ callbacks = new HashMap<Callback, ArrayList<Pair<String, Integer>>>();
+ }
+ 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);
+ }
+ reports.add(new Pair<String, Integer>(packageName, op));
+ }
+ return callbacks;
+ }
+
+ @Override
+ public void resetAllModes() {
+ mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ 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);
+ Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, Ops> ent = it.next();
+ String packageName = ent.getKey();
+ Ops pkgOps = ent.getValue();
+ for (int j=pkgOps.size()-1; j>=0; j--) {
+ Op curOp = pkgOps.valueAt(j);
+ if (AppOpsManager.opAllowsReset(curOp.op)
+ && curOp.mode != AppOpsManager.opToDefaultMode(curOp.op)) {
+ curOp.mode = AppOpsManager.opToDefaultMode(curOp.op);
+ changed = true;
+ callbacks = addCallbacks(callbacks, packageName, curOp.op,
+ mOpModeWatchers.get(curOp.op));
+ callbacks = addCallbacks(callbacks, packageName, curOp.op,
+ mPackageModeWatchers.get(packageName));
+ if (curOp.time == 0 && curOp.rejectTime == 0) {
+ pkgOps.removeAt(j);
+ }
+ }
+ }
+ if (pkgOps.size() == 0) {
+ it.remove();
+ }
+ }
+ if (packages.size() == 0) {
+ mUidOps.removeAt(i);
+ }
+ }
+ if (changed) {
+ scheduleWriteNowLocked();
+ }
+ }
+ if (callbacks != null) {
+ for (Map.Entry<Callback, ArrayList<Pair<String, Integer>>> ent : callbacks.entrySet()) {
+ Callback cb = ent.getKey();
+ ArrayList<Pair<String, Integer>> reports = ent.getValue();
+ for (int i=0; i<reports.size(); i++) {
+ Pair<String, Integer> rep = reports.get(i);
+ try {
+ cb.mCallback.opChanged(rep.second, rep.first);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) {
+ synchronized (this) {
+ op = AppOpsManager.opToSwitch(op);
+ Callback cb = mModeWatchers.get(callback.asBinder());
+ if (cb == null) {
+ cb = new Callback(callback);
+ mModeWatchers.put(callback.asBinder(), cb);
+ }
+ if (op != AppOpsManager.OP_NONE) {
+ ArrayList<Callback> cbs = mOpModeWatchers.get(op);
+ if (cbs == null) {
+ cbs = new ArrayList<Callback>();
+ mOpModeWatchers.put(op, cbs);
+ }
+ cbs.add(cb);
+ }
+ if (packageName != null) {
+ ArrayList<Callback> cbs = mPackageModeWatchers.get(packageName);
+ if (cbs == null) {
+ cbs = new ArrayList<Callback>();
+ mPackageModeWatchers.put(packageName, cbs);
+ }
+ cbs.add(cb);
+ }
+ }
+ }
+
+ @Override
+ public void stopWatchingMode(IAppOpsCallback callback) {
+ synchronized (this) {
+ Callback cb = mModeWatchers.remove(callback.asBinder());
+ if (cb != null) {
+ cb.unlinkToDeath();
+ for (int i=mOpModeWatchers.size()-1; i>=0; i--) {
+ ArrayList<Callback> cbs = mOpModeWatchers.valueAt(i);
+ cbs.remove(cb);
+ if (cbs.size() <= 0) {
+ mOpModeWatchers.removeAt(i);
+ }
+ }
+ for (int i=mPackageModeWatchers.size()-1; i>=0; i--) {
+ ArrayList<Callback> cbs = mPackageModeWatchers.valueAt(i);
+ cbs.remove(cb);
+ if (cbs.size() <= 0) {
+ mPackageModeWatchers.removeAt(i);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public IBinder getToken(IBinder clientToken) {
+ synchronized (this) {
+ ClientState cs = mClients.get(clientToken);
+ if (cs == null) {
+ cs = new ClientState(clientToken);
+ mClients.put(clientToken, cs);
+ }
+ return cs;
+ }
+ }
+
+ @Override
+ public int checkOperation(int code, int uid, String packageName) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ synchronized (this) {
+ Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, false);
+ if (op == null) {
+ return AppOpsManager.opToDefaultMode(code);
+ }
+ return op.mode;
+ }
+ }
+
+ @Override
+ public int checkPackage(int uid, String packageName) {
+ synchronized (this) {
+ if (getOpsLocked(uid, packageName, true) != null) {
+ return AppOpsManager.MODE_ALLOWED;
+ } else {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ }
+ }
+
+ @Override
+ public int noteOperation(int code, int uid, String packageName) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ synchronized (this) {
+ Ops ops = getOpsLocked(uid, packageName, true);
+ if (ops == null) {
+ if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ + " package " + packageName);
+ return AppOpsManager.MODE_ERRORED;
+ }
+ Op op = getOpLocked(ops, code, true);
+ if (op.duration == -1) {
+ Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
+ + " code " + code + " time=" + op.time + " duration=" + op.duration);
+ }
+ op.duration = 0;
+ final int switchCode = AppOpsManager.opToSwitch(code);
+ 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 "
+ + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
+ op.rejectTime = System.currentTimeMillis();
+ return switchOp.mode;
+ }
+ if (DEBUG) Log.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
+ + " package " + packageName);
+ op.time = System.currentTimeMillis();
+ op.rejectTime = 0;
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ }
+
+ @Override
+ public int startOperation(IBinder token, int code, int uid, String packageName) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ ClientState client = (ClientState)token;
+ synchronized (this) {
+ Ops ops = getOpsLocked(uid, packageName, true);
+ if (ops == null) {
+ if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ + " package " + packageName);
+ return AppOpsManager.MODE_ERRORED;
+ }
+ Op op = getOpLocked(ops, code, true);
+ final int switchCode = AppOpsManager.opToSwitch(code);
+ 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 "
+ + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
+ op.rejectTime = System.currentTimeMillis();
+ return switchOp.mode;
+ }
+ if (DEBUG) Log.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+ + " package " + packageName);
+ if (op.nesting == 0) {
+ op.time = System.currentTimeMillis();
+ op.rejectTime = 0;
+ op.duration = -1;
+ }
+ op.nesting++;
+ if (client.mStartedOps != null) {
+ client.mStartedOps.add(op);
+ }
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ }
+
+ @Override
+ public void finishOperation(IBinder token, int code, int uid, String packageName) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ ClientState client = (ClientState)token;
+ synchronized (this) {
+ Op op = getOpLocked(code, uid, packageName, true);
+ if (op == null) {
+ return;
+ }
+ if (client.mStartedOps != null) {
+ if (!client.mStartedOps.remove(op)) {
+ throw new IllegalStateException("Operation not started: uid" + op.uid
+ + " pkg=" + op.packageName + " op=" + op.op);
+ }
+ }
+ finishOperationLocked(op);
+ }
+ }
+
+ void finishOperationLocked(Op op) {
+ if (op.nesting <= 1) {
+ if (op.nesting == 1) {
+ op.duration = (int)(System.currentTimeMillis() - op.time);
+ op.time += op.duration;
+ } else {
+ Slog.w(TAG, "Finishing op nesting under-run: uid " + op.uid + " pkg "
+ + op.packageName + " code " + op.op + " time=" + op.time
+ + " duration=" + op.duration + " nesting=" + op.nesting);
+ }
+ op.nesting = 0;
+ } else {
+ op.nesting--;
+ }
+ }
+
+ private void verifyIncomingUid(int uid) {
+ if (uid == Binder.getCallingUid()) {
+ return;
+ }
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return;
+ }
+ mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ }
+
+ private void verifyIncomingOp(int op) {
+ if (op >= 0 && op < AppOpsManager._NUM_OP) {
+ return;
+ }
+ throw new IllegalArgumentException("Bad operation #" + op);
+ }
+
+ private Ops getOpsLocked(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);
+ }
+ if (uid == 0) {
+ packageName = "root";
+ } else if (uid == Process.SHELL_UID) {
+ packageName = "com.android.shell";
+ }
+ 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.
+ if (uid != 0) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ int pkgUid = -1;
+ try {
+ pkgUid = mContext.getPackageManager().getPackageUid(packageName,
+ UserHandle.getUserId(uid));
+ } catch (NameNotFoundException e) {
+ if ("media".equals(packageName)) {
+ pkgUid = Process.MEDIA_UID;
+ }
+ }
+ if (pkgUid != uid) {
+ // Oops! The package name is not valid for the uid they are calling
+ // under. Abort.
+ Slog.w(TAG, "Bad call: specified package " + packageName
+ + " under uid " + uid + " but it is really " + pkgUid);
+ return null;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ ops = new Ops(packageName, uid);
+ pkgOps.put(packageName, ops);
+ }
+ return ops;
+ }
+
+ private void scheduleWriteLocked() {
+ if (!mWriteScheduled) {
+ mWriteScheduled = true;
+ mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+ }
+ }
+
+ private void scheduleWriteNowLocked() {
+ if (!mWriteScheduled) {
+ mWriteScheduled = true;
+ }
+ mHandler.removeCallbacks(mWriteRunner);
+ mHandler.post(mWriteRunner);
+ }
+
+ private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
+ Ops ops = getOpsLocked(uid, packageName, edit);
+ if (ops == null) {
+ return null;
+ }
+ return getOpLocked(ops, code, edit);
+ }
+
+ private Op getOpLocked(Ops ops, int code, boolean edit) {
+ Op op = ops.get(code);
+ if (op == null) {
+ if (!edit) {
+ return null;
+ }
+ op = new Op(ops.uid, ops.packageName, code);
+ ops.put(code, op);
+ }
+ if (edit) {
+ scheduleWriteLocked();
+ }
+ 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(uid, pkgName, Integer.parseInt(parser.getAttributeValue(null, "n")));
+ String mode = parser.getAttributeValue(null, "m");
+ if (mode != null) {
+ op.mode = Integer.parseInt(mode);
+ }
+ String time = parser.getAttributeValue(null, "t");
+ if (time != null) {
+ op.time = Long.parseLong(time);
+ }
+ time = parser.getAttributeValue(null, "r");
+ if (time != null) {
+ op.rejectTime = Long.parseLong(time);
+ }
+ String dur = parser.getAttributeValue(null, "d");
+ if (dur != null) {
+ op.duration = Integer.parseInt(dur);
+ }
+ 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()));
+ if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
+ out.attribute(null, "m", Integer.toString(op.getMode()));
+ }
+ long time = op.getTime();
+ if (time != 0) {
+ out.attribute(null, "t", Long.toString(time));
+ }
+ time = op.getRejectTime();
+ if (time != 0) {
+ out.attribute(null, "r", Long.toString(time));
+ }
+ int dur = op.getDuration();
+ if (dur != 0) {
+ out.attribute(null, "d", Integer.toString(dur));
+ }
+ 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)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump ApOps service from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ synchronized (this) {
+ pw.println("Current AppOps Service state:");
+ final long now = System.currentTimeMillis();
+ boolean needSep = false;
+ if (mOpModeWatchers.size() > 0) {
+ needSep = true;
+ pw.println(" Op mode watchers:");
+ for (int i=0; i<mOpModeWatchers.size(); i++) {
+ pw.print(" Op "); pw.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
+ pw.println(":");
+ ArrayList<Callback> callbacks = mOpModeWatchers.valueAt(i);
+ for (int j=0; j<callbacks.size(); j++) {
+ pw.print(" #"); pw.print(j); pw.print(": ");
+ pw.println(callbacks.get(j));
+ }
+ }
+ }
+ if (mPackageModeWatchers.size() > 0) {
+ needSep = true;
+ pw.println(" Package mode watchers:");
+ for (int i=0; i<mPackageModeWatchers.size(); i++) {
+ pw.print(" Pkg "); pw.print(mPackageModeWatchers.keyAt(i));
+ pw.println(":");
+ ArrayList<Callback> callbacks = mPackageModeWatchers.valueAt(i);
+ for (int j=0; j<callbacks.size(); j++) {
+ pw.print(" #"); pw.print(j); pw.print(": ");
+ pw.println(callbacks.get(j));
+ }
+ }
+ }
+ if (mModeWatchers.size() > 0) {
+ needSep = true;
+ pw.println(" All mode watchers:");
+ for (int i=0; i<mModeWatchers.size(); i++) {
+ pw.print(" "); pw.print(mModeWatchers.keyAt(i));
+ pw.print(" -> "); pw.println(mModeWatchers.valueAt(i));
+ }
+ }
+ if (mClients.size() > 0) {
+ needSep = true;
+ pw.println(" Clients:");
+ for (int i=0; i<mClients.size(); i++) {
+ pw.print(" "); pw.print(mClients.keyAt(i)); pw.println(":");
+ ClientState cs = mClients.valueAt(i);
+ pw.print(" "); pw.println(cs);
+ if (cs.mStartedOps != null && cs.mStartedOps.size() > 0) {
+ pw.println(" Started ops:");
+ for (int j=0; j<cs.mStartedOps.size(); j++) {
+ Op op = cs.mStartedOps.get(j);
+ 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));
+ }
+ }
+ }
+ }
+ 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 (Ops ops : pkgOps.values()) {
+ pw.print(" Package "); pw.print(ops.packageName); pw.println(":");
+ for (int j=0; j<ops.size(); j++) {
+ Op op = ops.valueAt(j);
+ pw.print(" "); pw.print(AppOpsManager.opToName(op.op));
+ pw.print(": mode="); pw.print(op.mode);
+ if (op.time != 0) {
+ pw.print("; time="); TimeUtils.formatDuration(now-op.time, pw);
+ pw.print(" ago");
+ }
+ if (op.rejectTime != 0) {
+ pw.print("; rejectTime="); TimeUtils.formatDuration(now-op.rejectTime, pw);
+ pw.print(" ago");
+ }
+ if (op.duration == -1) {
+ pw.println(" (running)");
+ } else {
+ pw.print("; duration=");
+ TimeUtils.formatDuration(op.duration, pw);
+ pw.println();
+ }
+ }
+ }
+ }
+ }
+ }
+}