blob: 3d9ddaebf2d1e0a17b52f39d05f97d88db58dffe [file] [log] [blame]
Dianne Hackborna06de0f2012-12-11 16:34:47 -08001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17package com.android.server;
18
19import java.io.File;
20import java.io.FileDescriptor;
Dianne Hackborn35654b62013-01-14 17:38:02 -080021import java.io.FileInputStream;
22import java.io.FileNotFoundException;
23import java.io.FileOutputStream;
24import java.io.IOException;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080025import java.io.PrintWriter;
Dianne Hackborn35654b62013-01-14 17:38:02 -080026import java.util.ArrayList;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080027import java.util.HashMap;
Dianne Hackborn35654b62013-01-14 17:38:02 -080028import java.util.List;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080029
30import android.app.AppOpsManager;
31import android.content.Context;
32import android.content.pm.PackageManager;
33import android.content.pm.PackageManager.NameNotFoundException;
Dianne Hackborn35654b62013-01-14 17:38:02 -080034import android.os.AsyncTask;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080035import android.os.Binder;
Dianne Hackborn35654b62013-01-14 17:38:02 -080036import android.os.Handler;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080037import android.os.Process;
38import android.os.ServiceManager;
39import android.os.UserHandle;
40import android.util.AtomicFile;
41import android.util.Slog;
42import android.util.SparseArray;
43import android.util.TimeUtils;
Dianne Hackborn35654b62013-01-14 17:38:02 -080044import android.util.Xml;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080045
46import com.android.internal.app.IAppOpsService;
Dianne Hackborn35654b62013-01-14 17:38:02 -080047import com.android.internal.util.FastXmlSerializer;
48import com.android.internal.util.XmlUtils;
49
50import org.xmlpull.v1.XmlPullParser;
51import org.xmlpull.v1.XmlPullParserException;
52import org.xmlpull.v1.XmlSerializer;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080053
54public class AppOpsService extends IAppOpsService.Stub {
55 static final String TAG = "AppOps";
Dianne Hackborn35654b62013-01-14 17:38:02 -080056 static final boolean DEBUG = false;
57
58 // Write at most every 30 minutes.
59 static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080060
61 Context mContext;
62 final AtomicFile mFile;
Dianne Hackborn35654b62013-01-14 17:38:02 -080063 final Handler mHandler;
64
65 boolean mWriteScheduled;
66 final Runnable mWriteRunner = new Runnable() {
67 public void run() {
68 synchronized (AppOpsService.this) {
69 mWriteScheduled = false;
70 AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
71 @Override protected Void doInBackground(Void... params) {
72 writeState();
73 return null;
74 }
75 };
76 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
77 }
78 }
79 };
Dianne Hackborna06de0f2012-12-11 16:34:47 -080080
81 final SparseArray<HashMap<String, Ops>> mUidOps
82 = new SparseArray<HashMap<String, Ops>>();
83
84 final static class Ops extends SparseArray<Op> {
85 public final String packageName;
Dianne Hackborn35654b62013-01-14 17:38:02 -080086 public final int uid;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080087
Dianne Hackborn35654b62013-01-14 17:38:02 -080088 public Ops(String _packageName, int _uid) {
Dianne Hackborna06de0f2012-12-11 16:34:47 -080089 packageName = _packageName;
Dianne Hackborn35654b62013-01-14 17:38:02 -080090 uid = _uid;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080091 }
92 }
93
94 final static class Op {
95 public final int op;
96 public int duration;
97 public long time;
Dianne Hackborn35654b62013-01-14 17:38:02 -080098 public int nesting;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080099
100 public Op(int _op) {
101 op = _op;
102 }
103 }
104
Dianne Hackborn35654b62013-01-14 17:38:02 -0800105 public AppOpsService(File storagePath) {
106 mFile = new AtomicFile(storagePath);
107 mHandler = new Handler();
108 readState();
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800109 }
110
111 public void publish(Context context) {
112 mContext = context;
113 ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
114 }
115
116 public void shutdown() {
117 Slog.w(TAG, "Writing app ops before shutdown...");
Dianne Hackborn35654b62013-01-14 17:38:02 -0800118 boolean doWrite = false;
119 synchronized (this) {
120 if (mWriteScheduled) {
121 mWriteScheduled = false;
122 doWrite = true;
123 }
124 }
125 if (doWrite) {
126 writeState();
127 }
128 }
129
Dianne Hackborn72e39832013-01-18 18:36:09 -0800130 private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
131 ArrayList<AppOpsManager.OpEntry> resOps = null;
132 if (ops == null) {
133 resOps = new ArrayList<AppOpsManager.OpEntry>();
134 for (int j=0; j<pkgOps.size(); j++) {
135 Op curOp = pkgOps.valueAt(j);
136 resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.time,
137 curOp.duration));
138 }
139 } else {
140 for (int j=0; j<ops.length; j++) {
141 Op curOp = pkgOps.get(ops[j]);
142 if (curOp != null) {
143 if (resOps == null) {
144 resOps = new ArrayList<AppOpsManager.OpEntry>();
145 }
146 resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.time,
147 curOp.duration));
148 }
149 }
150 }
151 return resOps;
152 }
153
Dianne Hackborn35654b62013-01-14 17:38:02 -0800154 @Override
155 public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
156 mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
157 Binder.getCallingPid(), Binder.getCallingUid(), null);
158 ArrayList<AppOpsManager.PackageOps> res = null;
159 synchronized (this) {
160 for (int i=0; i<mUidOps.size(); i++) {
161 HashMap<String, Ops> packages = mUidOps.valueAt(i);
162 for (Ops pkgOps : packages.values()) {
Dianne Hackborn72e39832013-01-18 18:36:09 -0800163 ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800164 if (resOps != null) {
165 if (res == null) {
166 res = new ArrayList<AppOpsManager.PackageOps>();
167 }
168 AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
169 pkgOps.packageName, pkgOps.uid, resOps);
170 res.add(resPackage);
171 }
172 }
173 }
174 }
175 return res;
176 }
177
178 @Override
Dianne Hackborn72e39832013-01-18 18:36:09 -0800179 public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
180 int[] ops) {
181 mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
182 Binder.getCallingPid(), Binder.getCallingUid(), null);
183 synchronized (this) {
184 Ops pkgOps = getOpsLocked(uid, packageName, false);
185 if (pkgOps == null) {
186 return null;
187 }
188 ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
189 if (resOps == null) {
190 return null;
191 }
192 ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
193 AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
194 pkgOps.packageName, pkgOps.uid, resOps);
195 res.add(resPackage);
196 return res;
197 }
198 }
199
200 @Override
Dianne Hackborn35654b62013-01-14 17:38:02 -0800201 public int checkOperation(int code, int uid, String packageName) {
202 uid = handleIncomingUid(uid);
203 synchronized (this) {
204 Op op = getOpLocked(code, uid, packageName, false);
205 if (op == null) {
206 return AppOpsManager.MODE_ALLOWED;
207 }
208 }
209 return AppOpsManager.MODE_ALLOWED;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800210 }
211
212 @Override
213 public int noteOperation(int code, int uid, String packageName) {
214 uid = handleIncomingUid(uid);
215 synchronized (this) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800216 Op op = getOpLocked(code, uid, packageName, true);
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800217 if (op == null) {
218 return AppOpsManager.MODE_IGNORED;
219 }
220 if (op.duration == -1) {
221 Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
222 + " code " + code + " time=" + op.time + " duration=" + op.duration);
223 }
224 op.time = System.currentTimeMillis();
225 op.duration = 0;
226 }
227 return AppOpsManager.MODE_ALLOWED;
228 }
229
230 @Override
231 public int startOperation(int code, int uid, String packageName) {
232 uid = handleIncomingUid(uid);
233 synchronized (this) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800234 Op op = getOpLocked(code, uid, packageName, true);
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800235 if (op == null) {
236 return AppOpsManager.MODE_IGNORED;
237 }
Dianne Hackborn35654b62013-01-14 17:38:02 -0800238 if (op.nesting == 0) {
239 op.time = System.currentTimeMillis();
240 op.duration = -1;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800241 }
Dianne Hackborn35654b62013-01-14 17:38:02 -0800242 op.nesting++;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800243 }
244 return AppOpsManager.MODE_ALLOWED;
245 }
246
247 @Override
248 public void finishOperation(int code, int uid, String packageName) {
249 uid = handleIncomingUid(uid);
250 synchronized (this) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800251 Op op = getOpLocked(code, uid, packageName, true);
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800252 if (op == null) {
253 return;
254 }
Dianne Hackborn35654b62013-01-14 17:38:02 -0800255 if (op.nesting <= 1) {
256 if (op.nesting == 1) {
257 op.duration = (int)(System.currentTimeMillis() - op.time);
258 } else {
259 Slog.w(TAG, "Finishing op nesting under-run: uid " + uid + " pkg " + packageName
260 + " code " + code + " time=" + op.time + " duration=" + op.duration
261 + " nesting=" + op.nesting);
262 }
Dianne Hackborne7991752013-01-16 17:56:46 -0800263 op.nesting = 0;
Dianne Hackborn35654b62013-01-14 17:38:02 -0800264 } else {
265 op.nesting--;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800266 }
267 }
268 }
269
270 private int handleIncomingUid(int uid) {
271 if (uid == Binder.getCallingUid()) {
272 return uid;
273 }
274 if (Binder.getCallingPid() == Process.myPid()) {
275 return uid;
276 }
277 mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
278 Binder.getCallingPid(), Binder.getCallingUid(), null);
279 return uid;
280 }
281
Dianne Hackborn72e39832013-01-18 18:36:09 -0800282 private Ops getOpsLocked(int uid, String packageName, boolean edit) {
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800283 HashMap<String, Ops> pkgOps = mUidOps.get(uid);
284 if (pkgOps == null) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800285 if (!edit) {
286 return null;
287 }
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800288 pkgOps = new HashMap<String, Ops>();
289 mUidOps.put(uid, pkgOps);
290 }
291 Ops ops = pkgOps.get(packageName);
292 if (ops == null) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800293 if (!edit) {
294 return null;
295 }
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800296 // This is the first time we have seen this package name under this uid,
297 // so let's make sure it is valid.
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800298 final long ident = Binder.clearCallingIdentity();
299 try {
300 int pkgUid = -1;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800301 try {
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800302 pkgUid = mContext.getPackageManager().getPackageUid(packageName,
303 UserHandle.getUserId(uid));
304 } catch (NameNotFoundException e) {
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800305 }
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800306 if (pkgUid != uid) {
307 // Oops! The package name is not valid for the uid they are calling
308 // under. Abort.
309 Slog.w(TAG, "Bad call: specified package " + packageName
310 + " under uid " + uid + " but it is really " + pkgUid);
311 return null;
312 }
313 } finally {
314 Binder.restoreCallingIdentity(ident);
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800315 }
Dianne Hackborn35654b62013-01-14 17:38:02 -0800316 ops = new Ops(packageName, uid);
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800317 pkgOps.put(packageName, ops);
318 }
Dianne Hackborn72e39832013-01-18 18:36:09 -0800319 return ops;
320 }
321
322 private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
323 Ops ops = getOpsLocked(uid, packageName, edit);
324 if (ops == null) {
325 return null;
326 }
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800327 Op op = ops.get(code);
328 if (op == null) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800329 if (!edit) {
330 return null;
331 }
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800332 op = new Op(code);
333 ops.put(code, op);
334 }
Dianne Hackborn35654b62013-01-14 17:38:02 -0800335 if (edit && !mWriteScheduled) {
336 mWriteScheduled = true;
337 mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
338 }
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800339 return op;
340 }
341
Dianne Hackborn35654b62013-01-14 17:38:02 -0800342 void readState() {
343 synchronized (mFile) {
344 synchronized (this) {
345 FileInputStream stream;
346 try {
347 stream = mFile.openRead();
348 } catch (FileNotFoundException e) {
349 Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
350 return;
351 }
352 boolean success = false;
353 try {
354 XmlPullParser parser = Xml.newPullParser();
355 parser.setInput(stream, null);
356 int type;
357 while ((type = parser.next()) != XmlPullParser.START_TAG
358 && type != XmlPullParser.END_DOCUMENT) {
359 ;
360 }
361
362 if (type != XmlPullParser.START_TAG) {
363 throw new IllegalStateException("no start tag found");
364 }
365
366 int outerDepth = parser.getDepth();
367 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
368 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
369 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
370 continue;
371 }
372
373 String tagName = parser.getName();
374 if (tagName.equals("pkg")) {
375 readPackage(parser);
376 } else {
377 Slog.w(TAG, "Unknown element under <app-ops>: "
378 + parser.getName());
379 XmlUtils.skipCurrentTag(parser);
380 }
381 }
382 success = true;
383 } catch (IllegalStateException e) {
384 Slog.w(TAG, "Failed parsing " + e);
385 } catch (NullPointerException e) {
386 Slog.w(TAG, "Failed parsing " + e);
387 } catch (NumberFormatException e) {
388 Slog.w(TAG, "Failed parsing " + e);
389 } catch (XmlPullParserException e) {
390 Slog.w(TAG, "Failed parsing " + e);
391 } catch (IOException e) {
392 Slog.w(TAG, "Failed parsing " + e);
393 } catch (IndexOutOfBoundsException e) {
394 Slog.w(TAG, "Failed parsing " + e);
395 } finally {
396 if (!success) {
397 mUidOps.clear();
398 }
399 try {
400 stream.close();
401 } catch (IOException e) {
402 }
403 }
404 }
405 }
406 }
407
408 void readPackage(XmlPullParser parser) throws NumberFormatException,
409 XmlPullParserException, IOException {
410 String pkgName = parser.getAttributeValue(null, "n");
411 int outerDepth = parser.getDepth();
412 int type;
413 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
414 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
415 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
416 continue;
417 }
418
419 String tagName = parser.getName();
420 if (tagName.equals("uid")) {
421 readUid(parser, pkgName);
422 } else {
423 Slog.w(TAG, "Unknown element under <pkg>: "
424 + parser.getName());
425 XmlUtils.skipCurrentTag(parser);
426 }
427 }
428 }
429
430 void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException,
431 XmlPullParserException, IOException {
432 int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
433 int outerDepth = parser.getDepth();
434 int type;
435 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
436 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
437 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
438 continue;
439 }
440
441 String tagName = parser.getName();
442 if (tagName.equals("op")) {
443 Op op = new Op(Integer.parseInt(parser.getAttributeValue(null, "n")));
444 op.time = Long.parseLong(parser.getAttributeValue(null, "t"));
445 op.duration = Integer.parseInt(parser.getAttributeValue(null, "d"));
446 HashMap<String, Ops> pkgOps = mUidOps.get(uid);
447 if (pkgOps == null) {
448 pkgOps = new HashMap<String, Ops>();
449 mUidOps.put(uid, pkgOps);
450 }
451 Ops ops = pkgOps.get(pkgName);
452 if (ops == null) {
453 ops = new Ops(pkgName, uid);
454 pkgOps.put(pkgName, ops);
455 }
456 ops.put(op.op, op);
457 } else {
458 Slog.w(TAG, "Unknown element under <pkg>: "
459 + parser.getName());
460 XmlUtils.skipCurrentTag(parser);
461 }
462 }
463 }
464
465 void writeState() {
466 synchronized (mFile) {
467 List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
468
469 FileOutputStream stream;
470 try {
471 stream = mFile.startWrite();
472 } catch (IOException e) {
473 Slog.w(TAG, "Failed to write state: " + e);
474 return;
475 }
476
477 try {
478 XmlSerializer out = new FastXmlSerializer();
479 out.setOutput(stream, "utf-8");
480 out.startDocument(null, true);
481 out.startTag(null, "app-ops");
482
483 if (allOps != null) {
484 String lastPkg = null;
485 for (int i=0; i<allOps.size(); i++) {
486 AppOpsManager.PackageOps pkg = allOps.get(i);
487 if (!pkg.getPackageName().equals(lastPkg)) {
488 if (lastPkg != null) {
489 out.endTag(null, "pkg");
490 }
491 lastPkg = pkg.getPackageName();
492 out.startTag(null, "pkg");
493 out.attribute(null, "n", lastPkg);
494 }
495 out.startTag(null, "uid");
496 out.attribute(null, "n", Integer.toString(pkg.getUid()));
497 List<AppOpsManager.OpEntry> ops = pkg.getOps();
498 for (int j=0; j<ops.size(); j++) {
499 AppOpsManager.OpEntry op = ops.get(j);
500 out.startTag(null, "op");
501 out.attribute(null, "n", Integer.toString(op.getOp()));
502 out.attribute(null, "t", Long.toString(op.getTime()));
503 out.attribute(null, "d", Integer.toString(op.getDuration()));
504 out.endTag(null, "op");
505 }
506 out.endTag(null, "uid");
507 }
508 if (lastPkg != null) {
509 out.endTag(null, "pkg");
510 }
511 }
512
513 out.endTag(null, "app-ops");
514 out.endDocument();
515 mFile.finishWrite(stream);
516 } catch (IOException e) {
517 Slog.w(TAG, "Failed to write state, restoring backup.", e);
518 mFile.failWrite(stream);
519 }
520 }
521 }
522
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800523 @Override
524 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
525 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
526 != PackageManager.PERMISSION_GRANTED) {
527 pw.println("Permission Denial: can't dump ApOps service from from pid="
528 + Binder.getCallingPid()
529 + ", uid=" + Binder.getCallingUid());
530 return;
531 }
532
533 synchronized (this) {
534 pw.println("Current AppOps Service state:");
535 for (int i=0; i<mUidOps.size(); i++) {
536 pw.print(" Uid "); UserHandle.formatUid(pw, mUidOps.keyAt(i)); pw.println(":");
537 HashMap<String, Ops> pkgOps = mUidOps.valueAt(i);
538 for (Ops ops : pkgOps.values()) {
539 pw.print(" Package "); pw.print(ops.packageName); pw.println(":");
540 for (int j=0; j<ops.size(); j++) {
541 Op op = ops.valueAt(j);
542 pw.print(" "); pw.print(AppOpsManager.opToString(op.op));
543 pw.print(": time=");
544 TimeUtils.formatDuration(System.currentTimeMillis()-op.time, pw);
545 pw.print(" ago");
546 if (op.duration == -1) {
547 pw.println(" (running)");
548 } else {
549 pw.print("; duration=");
550 TimeUtils.formatDuration(op.duration, pw);
551 pw.println();
552 }
553 }
554 }
555 }
556 }
557 }
558}