2 * Copyright (C) 2012 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.server;
20 import java.io.FileDescriptor;
21 import java.io.FileInputStream;
22 import java.io.FileNotFoundException;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.PrintWriter;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
31 import android.app.AppOpsManager;
32 import android.content.Context;
33 import android.content.pm.PackageManager;
34 import android.content.pm.PackageManager.NameNotFoundException;
35 import android.os.AsyncTask;
36 import android.os.Binder;
37 import android.os.Handler;
38 import android.os.IBinder;
39 import android.os.Process;
40 import android.os.RemoteException;
41 import android.os.ServiceManager;
42 import android.os.UserHandle;
43 import android.util.AtomicFile;
44 import android.util.Log;
45 import android.util.Slog;
46 import android.util.SparseArray;
47 import android.util.TimeUtils;
48 import android.util.Xml;
50 import com.android.internal.app.IAppOpsService;
51 import com.android.internal.app.IAppOpsCallback;
52 import com.android.internal.util.FastXmlSerializer;
53 import com.android.internal.util.XmlUtils;
55 import org.xmlpull.v1.XmlPullParser;
56 import org.xmlpull.v1.XmlPullParserException;
57 import org.xmlpull.v1.XmlSerializer;
59 public class AppOpsService extends IAppOpsService.Stub {
60 static final String TAG = "AppOps";
61 static final boolean DEBUG = false;
63 // Write at most every 30 minutes.
64 static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
67 final AtomicFile mFile;
68 final Handler mHandler;
70 boolean mWriteScheduled;
71 final Runnable mWriteRunner = new Runnable() {
73 synchronized (AppOpsService.this) {
74 mWriteScheduled = false;
75 AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
76 @Override protected Void doInBackground(Void... params) {
81 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
86 final SparseArray<HashMap<String, Ops>> mUidOps
87 = new SparseArray<HashMap<String, Ops>>();
89 public final static class Ops extends SparseArray<Op> {
90 public final String packageName;
93 public Ops(String _packageName, int _uid) {
94 packageName = _packageName;
99 public final static class Op {
104 public long rejectTime;
109 mode = AppOpsManager.MODE_ALLOWED;
113 final SparseArray<ArrayList<Callback>> mOpModeWatchers
114 = new SparseArray<ArrayList<Callback>>();
115 final HashMap<String, ArrayList<Callback>> mPackageModeWatchers
116 = new HashMap<String, ArrayList<Callback>>();
117 final HashMap<IBinder, Callback> mModeWatchers
118 = new HashMap<IBinder, Callback>();
120 public final class Callback implements DeathRecipient {
121 final IAppOpsCallback mCallback;
123 public Callback(IAppOpsCallback callback) {
124 mCallback = callback;
126 mCallback.asBinder().linkToDeath(this, 0);
127 } catch (RemoteException e) {
131 public void unlinkToDeath() {
132 mCallback.asBinder().unlinkToDeath(this, 0);
136 public void binderDied() {
137 stopWatchingMode(mCallback);
141 public AppOpsService(File storagePath) {
142 mFile = new AtomicFile(storagePath);
143 mHandler = new Handler();
147 public void publish(Context context) {
149 ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
152 public void systemReady() {
153 synchronized (this) {
154 boolean changed = false;
155 for (int i=0; i<mUidOps.size(); i++) {
156 HashMap<String, Ops> pkgs = mUidOps.valueAt(i);
157 Iterator<Ops> it = pkgs.values().iterator();
158 while (it.hasNext()) {
162 curUid = mContext.getPackageManager().getPackageUid(ops.packageName,
163 UserHandle.getUserId(ops.uid));
164 } catch (NameNotFoundException e) {
167 if (curUid != ops.uid) {
168 Slog.i(TAG, "Pruning old package " + ops.packageName
169 + "/" + ops.uid + ": new uid=" + curUid);
174 if (pkgs.size() <= 0) {
179 scheduleWriteLocked();
184 public void packageRemoved(int uid, String packageName) {
185 synchronized (this) {
186 HashMap<String, Ops> pkgs = mUidOps.get(uid);
188 if (pkgs.remove(packageName) != null) {
189 if (pkgs.size() <= 0) {
192 scheduleWriteLocked();
198 public void uidRemoved(int uid) {
199 synchronized (this) {
200 if (mUidOps.indexOfKey(uid) >= 0) {
202 scheduleWriteLocked();
207 public void shutdown() {
208 Slog.w(TAG, "Writing app ops before shutdown...");
209 boolean doWrite = false;
210 synchronized (this) {
211 if (mWriteScheduled) {
212 mWriteScheduled = false;
221 private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
222 ArrayList<AppOpsManager.OpEntry> resOps = null;
224 resOps = new ArrayList<AppOpsManager.OpEntry>();
225 for (int j=0; j<pkgOps.size(); j++) {
226 Op curOp = pkgOps.valueAt(j);
227 resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.mode, curOp.time,
228 curOp.rejectTime, curOp.duration));
231 for (int j=0; j<ops.length; j++) {
232 Op curOp = pkgOps.get(ops[j]);
234 if (resOps == null) {
235 resOps = new ArrayList<AppOpsManager.OpEntry>();
237 resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.mode, curOp.time,
238 curOp.rejectTime, curOp.duration));
246 public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
247 mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
248 Binder.getCallingPid(), Binder.getCallingUid(), null);
249 ArrayList<AppOpsManager.PackageOps> res = null;
250 synchronized (this) {
251 for (int i=0; i<mUidOps.size(); i++) {
252 HashMap<String, Ops> packages = mUidOps.valueAt(i);
253 for (Ops pkgOps : packages.values()) {
254 ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
255 if (resOps != null) {
257 res = new ArrayList<AppOpsManager.PackageOps>();
259 AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
260 pkgOps.packageName, pkgOps.uid, resOps);
270 public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
272 mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
273 Binder.getCallingPid(), Binder.getCallingUid(), null);
274 synchronized (this) {
275 Ops pkgOps = getOpsLocked(uid, packageName, false);
276 if (pkgOps == null) {
279 ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
280 if (resOps == null) {
283 ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
284 AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
285 pkgOps.packageName, pkgOps.uid, resOps);
292 public void setMode(int code, int uid, String packageName, int mode) {
293 verifyIncomingUid(uid);
294 verifyIncomingOp(code);
295 ArrayList<Callback> repCbs = null;
296 code = AppOpsManager.opToSwitch(code);
297 synchronized (this) {
298 Op op = getOpLocked(code, uid, packageName, true);
300 if (op.mode != mode) {
302 ArrayList<Callback> cbs = mOpModeWatchers.get(code);
304 if (repCbs == null) {
305 repCbs = new ArrayList<Callback>();
309 cbs = mPackageModeWatchers.get(packageName);
311 if (repCbs == null) {
312 repCbs = new ArrayList<Callback>();
316 if (mode == AppOpsManager.MODE_ALLOWED) {
317 // If going into the default mode, prune this op
318 // if there is nothing else interesting in it.
319 if (op.time == 0 && op.rejectTime == 0) {
320 Ops ops = getOpsLocked(uid, packageName, false);
323 if (ops.size() <= 0) {
324 HashMap<String, Ops> pkgOps = mUidOps.get(uid);
325 if (pkgOps != null) {
326 pkgOps.remove(ops.packageName);
327 if (pkgOps.size() <= 0) {
335 scheduleWriteNowLocked();
339 if (repCbs != null) {
340 for (int i=0; i<repCbs.size(); i++) {
342 repCbs.get(i).mCallback.opChanged(code, packageName);
343 } catch (RemoteException e) {
350 public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) {
351 synchronized (this) {
352 op = AppOpsManager.opToSwitch(op);
353 Callback cb = mModeWatchers.get(callback.asBinder());
355 cb = new Callback(callback);
356 mModeWatchers.put(callback.asBinder(), cb);
358 if (op != AppOpsManager.OP_NONE) {
359 ArrayList<Callback> cbs = mOpModeWatchers.get(op);
361 cbs = new ArrayList<Callback>();
362 mOpModeWatchers.put(op, cbs);
366 if (packageName != null) {
367 ArrayList<Callback> cbs = mPackageModeWatchers.get(packageName);
369 cbs = new ArrayList<Callback>();
370 mPackageModeWatchers.put(packageName, cbs);
378 public void stopWatchingMode(IAppOpsCallback callback) {
379 synchronized (this) {
380 Callback cb = mModeWatchers.remove(callback.asBinder());
383 for (int i=0; i<mOpModeWatchers.size(); i++) {
384 ArrayList<Callback> cbs = mOpModeWatchers.valueAt(i);
386 if (cbs.size() <= 0) {
387 mOpModeWatchers.removeAt(i);
390 if (mPackageModeWatchers.size() > 0) {
391 Iterator<ArrayList<Callback>> it = mPackageModeWatchers.values().iterator();
392 while (it.hasNext()) {
393 ArrayList<Callback> cbs = it.next();
395 if (cbs.size() <= 0) {
405 public int checkOperation(int code, int uid, String packageName) {
406 verifyIncomingUid(uid);
407 verifyIncomingOp(code);
408 synchronized (this) {
409 Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, false);
411 return AppOpsManager.MODE_ALLOWED;
418 public int noteOperation(int code, int uid, String packageName) {
419 verifyIncomingUid(uid);
420 verifyIncomingOp(code);
421 synchronized (this) {
422 Ops ops = getOpsLocked(uid, packageName, true);
424 if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
425 + " package " + packageName);
426 return AppOpsManager.MODE_IGNORED;
428 Op op = getOpLocked(ops, code, true);
429 if (op.duration == -1) {
430 Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
431 + " code " + code + " time=" + op.time + " duration=" + op.duration);
434 final int switchCode = AppOpsManager.opToSwitch(code);
435 final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
436 if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
437 if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "
438 + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
439 op.rejectTime = System.currentTimeMillis();
440 return switchOp.mode;
442 if (DEBUG) Log.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
443 + " package " + packageName);
444 op.time = System.currentTimeMillis();
446 return AppOpsManager.MODE_ALLOWED;
451 public int startOperation(int code, int uid, String packageName) {
452 verifyIncomingUid(uid);
453 verifyIncomingOp(code);
454 synchronized (this) {
455 Ops ops = getOpsLocked(uid, packageName, true);
457 if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid
458 + " package " + packageName);
459 return AppOpsManager.MODE_IGNORED;
461 Op op = getOpLocked(ops, code, true);
462 final int switchCode = AppOpsManager.opToSwitch(code);
463 final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
464 if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
465 if (DEBUG) Log.d(TAG, "startOperation: reject #" + op.mode + " for code "
466 + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
467 op.rejectTime = System.currentTimeMillis();
468 return switchOp.mode;
470 if (DEBUG) Log.d(TAG, "startOperation: allowing code " + code + " uid " + uid
471 + " package " + packageName);
472 if (op.nesting == 0) {
473 op.time = System.currentTimeMillis();
478 return AppOpsManager.MODE_ALLOWED;
483 public void finishOperation(int code, int uid, String packageName) {
484 verifyIncomingUid(uid);
485 verifyIncomingOp(code);
486 synchronized (this) {
487 Op op = getOpLocked(code, uid, packageName, true);
491 if (op.nesting <= 1) {
492 if (op.nesting == 1) {
493 op.duration = (int)(System.currentTimeMillis() - op.time);
494 op.time += op.duration;
496 Slog.w(TAG, "Finishing op nesting under-run: uid " + uid + " pkg " + packageName
497 + " code " + code + " time=" + op.time + " duration=" + op.duration
498 + " nesting=" + op.nesting);
507 private void verifyIncomingUid(int uid) {
508 if (uid == Binder.getCallingUid()) {
511 if (Binder.getCallingPid() == Process.myPid()) {
514 mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
515 Binder.getCallingPid(), Binder.getCallingUid(), null);
518 private void verifyIncomingOp(int op) {
519 if (op >= 0 && op < AppOpsManager._NUM_OP) {
522 throw new IllegalArgumentException("Bad operation #" + op);
525 private Ops getOpsLocked(int uid, String packageName, boolean edit) {
526 HashMap<String, Ops> pkgOps = mUidOps.get(uid);
527 if (pkgOps == null) {
531 pkgOps = new HashMap<String, Ops>();
532 mUidOps.put(uid, pkgOps);
535 packageName = "root";
536 } else if (uid == Process.SHELL_UID) {
537 packageName = "com.android.shell";
539 Ops ops = pkgOps.get(packageName);
544 // This is the first time we have seen this package name under this uid,
545 // so let's make sure it is valid.
547 final long ident = Binder.clearCallingIdentity();
551 pkgUid = mContext.getPackageManager().getPackageUid(packageName,
552 UserHandle.getUserId(uid));
553 } catch (NameNotFoundException e) {
556 // Oops! The package name is not valid for the uid they are calling
558 Slog.w(TAG, "Bad call: specified package " + packageName
559 + " under uid " + uid + " but it is really " + pkgUid);
563 Binder.restoreCallingIdentity(ident);
566 ops = new Ops(packageName, uid);
567 pkgOps.put(packageName, ops);
572 private void scheduleWriteLocked() {
573 if (!mWriteScheduled) {
574 mWriteScheduled = true;
575 mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
579 private void scheduleWriteNowLocked() {
580 if (!mWriteScheduled) {
581 mWriteScheduled = true;
583 mHandler.removeCallbacks(mWriteRunner);
584 mHandler.post(mWriteRunner);
587 private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
588 Ops ops = getOpsLocked(uid, packageName, edit);
592 return getOpLocked(ops, code, edit);
595 private Op getOpLocked(Ops ops, int code, boolean edit) {
596 Op op = ops.get(code);
605 scheduleWriteLocked();
611 synchronized (mFile) {
612 synchronized (this) {
613 FileInputStream stream;
615 stream = mFile.openRead();
616 } catch (FileNotFoundException e) {
617 Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
620 boolean success = false;
622 XmlPullParser parser = Xml.newPullParser();
623 parser.setInput(stream, null);
625 while ((type = parser.next()) != XmlPullParser.START_TAG
626 && type != XmlPullParser.END_DOCUMENT) {
630 if (type != XmlPullParser.START_TAG) {
631 throw new IllegalStateException("no start tag found");
634 int outerDepth = parser.getDepth();
635 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
636 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
637 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
641 String tagName = parser.getName();
642 if (tagName.equals("pkg")) {
645 Slog.w(TAG, "Unknown element under <app-ops>: "
647 XmlUtils.skipCurrentTag(parser);
651 } catch (IllegalStateException e) {
652 Slog.w(TAG, "Failed parsing " + e);
653 } catch (NullPointerException e) {
654 Slog.w(TAG, "Failed parsing " + e);
655 } catch (NumberFormatException e) {
656 Slog.w(TAG, "Failed parsing " + e);
657 } catch (XmlPullParserException e) {
658 Slog.w(TAG, "Failed parsing " + e);
659 } catch (IOException e) {
660 Slog.w(TAG, "Failed parsing " + e);
661 } catch (IndexOutOfBoundsException e) {
662 Slog.w(TAG, "Failed parsing " + e);
669 } catch (IOException e) {
676 void readPackage(XmlPullParser parser) throws NumberFormatException,
677 XmlPullParserException, IOException {
678 String pkgName = parser.getAttributeValue(null, "n");
679 int outerDepth = parser.getDepth();
681 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
682 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
683 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
687 String tagName = parser.getName();
688 if (tagName.equals("uid")) {
689 readUid(parser, pkgName);
691 Slog.w(TAG, "Unknown element under <pkg>: "
693 XmlUtils.skipCurrentTag(parser);
698 void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException,
699 XmlPullParserException, IOException {
700 int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
701 int outerDepth = parser.getDepth();
703 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
704 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
705 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
709 String tagName = parser.getName();
710 if (tagName.equals("op")) {
711 Op op = new Op(Integer.parseInt(parser.getAttributeValue(null, "n")));
712 String mode = parser.getAttributeValue(null, "m");
714 op.mode = Integer.parseInt(mode);
716 String time = parser.getAttributeValue(null, "t");
718 op.time = Long.parseLong(time);
720 time = parser.getAttributeValue(null, "r");
722 op.rejectTime = Long.parseLong(time);
724 String dur = parser.getAttributeValue(null, "d");
726 op.duration = Integer.parseInt(dur);
728 HashMap<String, Ops> pkgOps = mUidOps.get(uid);
729 if (pkgOps == null) {
730 pkgOps = new HashMap<String, Ops>();
731 mUidOps.put(uid, pkgOps);
733 Ops ops = pkgOps.get(pkgName);
735 ops = new Ops(pkgName, uid);
736 pkgOps.put(pkgName, ops);
740 Slog.w(TAG, "Unknown element under <pkg>: "
742 XmlUtils.skipCurrentTag(parser);
748 synchronized (mFile) {
749 List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
751 FileOutputStream stream;
753 stream = mFile.startWrite();
754 } catch (IOException e) {
755 Slog.w(TAG, "Failed to write state: " + e);
760 XmlSerializer out = new FastXmlSerializer();
761 out.setOutput(stream, "utf-8");
762 out.startDocument(null, true);
763 out.startTag(null, "app-ops");
765 if (allOps != null) {
766 String lastPkg = null;
767 for (int i=0; i<allOps.size(); i++) {
768 AppOpsManager.PackageOps pkg = allOps.get(i);
769 if (!pkg.getPackageName().equals(lastPkg)) {
770 if (lastPkg != null) {
771 out.endTag(null, "pkg");
773 lastPkg = pkg.getPackageName();
774 out.startTag(null, "pkg");
775 out.attribute(null, "n", lastPkg);
777 out.startTag(null, "uid");
778 out.attribute(null, "n", Integer.toString(pkg.getUid()));
779 List<AppOpsManager.OpEntry> ops = pkg.getOps();
780 for (int j=0; j<ops.size(); j++) {
781 AppOpsManager.OpEntry op = ops.get(j);
782 out.startTag(null, "op");
783 out.attribute(null, "n", Integer.toString(op.getOp()));
784 if (op.getMode() != AppOpsManager.MODE_ALLOWED) {
785 out.attribute(null, "m", Integer.toString(op.getMode()));
787 long time = op.getTime();
789 out.attribute(null, "t", Long.toString(time));
791 time = op.getRejectTime();
793 out.attribute(null, "r", Long.toString(time));
795 int dur = op.getDuration();
797 out.attribute(null, "d", Integer.toString(dur));
799 out.endTag(null, "op");
801 out.endTag(null, "uid");
803 if (lastPkg != null) {
804 out.endTag(null, "pkg");
808 out.endTag(null, "app-ops");
810 mFile.finishWrite(stream);
811 } catch (IOException e) {
812 Slog.w(TAG, "Failed to write state, restoring backup.", e);
813 mFile.failWrite(stream);
819 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
820 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
821 != PackageManager.PERMISSION_GRANTED) {
822 pw.println("Permission Denial: can't dump ApOps service from from pid="
823 + Binder.getCallingPid()
824 + ", uid=" + Binder.getCallingUid());
828 synchronized (this) {
829 pw.println("Current AppOps Service state:");
830 final long now = System.currentTimeMillis();
831 for (int i=0; i<mUidOps.size(); i++) {
832 pw.print(" Uid "); UserHandle.formatUid(pw, mUidOps.keyAt(i)); pw.println(":");
833 HashMap<String, Ops> pkgOps = mUidOps.valueAt(i);
834 for (Ops ops : pkgOps.values()) {
835 pw.print(" Package "); pw.print(ops.packageName); pw.println(":");
836 for (int j=0; j<ops.size(); j++) {
837 Op op = ops.valueAt(j);
838 pw.print(" "); pw.print(AppOpsManager.opToName(op.op));
839 pw.print(": mode="); pw.print(op.mode);
841 pw.print("; time="); TimeUtils.formatDuration(now-op.time, pw);
844 if (op.rejectTime != 0) {
845 pw.print("; rejectTime="); TimeUtils.formatDuration(now-op.rejectTime, pw);
848 if (op.duration == -1) {
849 pw.println(" (running)");
851 pw.print("; duration=");
852 TimeUtils.formatDuration(op.duration, pw);