blob: 17128062b0a102b401a846f7dabbad3bab4cfd57 [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
130 @Override
131 public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
132 mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
133 Binder.getCallingPid(), Binder.getCallingUid(), null);
134 ArrayList<AppOpsManager.PackageOps> res = null;
135 synchronized (this) {
136 for (int i=0; i<mUidOps.size(); i++) {
137 HashMap<String, Ops> packages = mUidOps.valueAt(i);
138 for (Ops pkgOps : packages.values()) {
139 ArrayList<AppOpsManager.OpEntry> resOps = null;
140 if (ops == null) {
141 resOps = new ArrayList<AppOpsManager.OpEntry>();
142 for (int j=0; j<pkgOps.size(); j++) {
143 Op curOp = pkgOps.valueAt(j);
144 resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.time,
145 curOp.duration));
146 }
147 } else {
148 for (int j=0; j<ops.length; j++) {
149 Op curOp = pkgOps.get(ops[j]);
150 if (curOp != null) {
151 if (resOps == null) {
152 resOps = new ArrayList<AppOpsManager.OpEntry>();
153 }
154 resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.time,
155 curOp.duration));
156 }
157 }
158 }
159 if (resOps != null) {
160 if (res == null) {
161 res = new ArrayList<AppOpsManager.PackageOps>();
162 }
163 AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
164 pkgOps.packageName, pkgOps.uid, resOps);
165 res.add(resPackage);
166 }
167 }
168 }
169 }
170 return res;
171 }
172
173 @Override
174 public int checkOperation(int code, int uid, String packageName) {
175 uid = handleIncomingUid(uid);
176 synchronized (this) {
177 Op op = getOpLocked(code, uid, packageName, false);
178 if (op == null) {
179 return AppOpsManager.MODE_ALLOWED;
180 }
181 }
182 return AppOpsManager.MODE_ALLOWED;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800183 }
184
185 @Override
186 public int noteOperation(int code, int uid, String packageName) {
187 uid = handleIncomingUid(uid);
188 synchronized (this) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800189 Op op = getOpLocked(code, uid, packageName, true);
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800190 if (op == null) {
191 return AppOpsManager.MODE_IGNORED;
192 }
193 if (op.duration == -1) {
194 Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
195 + " code " + code + " time=" + op.time + " duration=" + op.duration);
196 }
197 op.time = System.currentTimeMillis();
198 op.duration = 0;
199 }
200 return AppOpsManager.MODE_ALLOWED;
201 }
202
203 @Override
204 public int startOperation(int code, int uid, String packageName) {
205 uid = handleIncomingUid(uid);
206 synchronized (this) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800207 Op op = getOpLocked(code, uid, packageName, true);
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800208 if (op == null) {
209 return AppOpsManager.MODE_IGNORED;
210 }
Dianne Hackborn35654b62013-01-14 17:38:02 -0800211 if (op.nesting == 0) {
212 op.time = System.currentTimeMillis();
213 op.duration = -1;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800214 }
Dianne Hackborn35654b62013-01-14 17:38:02 -0800215 op.nesting++;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800216 }
217 return AppOpsManager.MODE_ALLOWED;
218 }
219
220 @Override
221 public void finishOperation(int code, int uid, String packageName) {
222 uid = handleIncomingUid(uid);
223 synchronized (this) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800224 Op op = getOpLocked(code, uid, packageName, true);
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800225 if (op == null) {
226 return;
227 }
Dianne Hackborn35654b62013-01-14 17:38:02 -0800228 if (op.nesting <= 1) {
229 if (op.nesting == 1) {
230 op.duration = (int)(System.currentTimeMillis() - op.time);
231 } else {
232 Slog.w(TAG, "Finishing op nesting under-run: uid " + uid + " pkg " + packageName
233 + " code " + code + " time=" + op.time + " duration=" + op.duration
234 + " nesting=" + op.nesting);
235 }
Dianne Hackborne7991752013-01-16 17:56:46 -0800236 op.nesting = 0;
Dianne Hackborn35654b62013-01-14 17:38:02 -0800237 } else {
238 op.nesting--;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800239 }
240 }
241 }
242
243 private int handleIncomingUid(int uid) {
244 if (uid == Binder.getCallingUid()) {
245 return uid;
246 }
247 if (Binder.getCallingPid() == Process.myPid()) {
248 return uid;
249 }
250 mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
251 Binder.getCallingPid(), Binder.getCallingUid(), null);
252 return uid;
253 }
254
Dianne Hackborn35654b62013-01-14 17:38:02 -0800255 private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800256 HashMap<String, Ops> pkgOps = mUidOps.get(uid);
257 if (pkgOps == null) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800258 if (!edit) {
259 return null;
260 }
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800261 pkgOps = new HashMap<String, Ops>();
262 mUidOps.put(uid, pkgOps);
263 }
264 Ops ops = pkgOps.get(packageName);
265 if (ops == null) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800266 if (!edit) {
267 return null;
268 }
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800269 // This is the first time we have seen this package name under this uid,
270 // so let's make sure it is valid.
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800271 final long ident = Binder.clearCallingIdentity();
272 try {
273 int pkgUid = -1;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800274 try {
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800275 pkgUid = mContext.getPackageManager().getPackageUid(packageName,
276 UserHandle.getUserId(uid));
277 } catch (NameNotFoundException e) {
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800278 }
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800279 if (pkgUid != uid) {
280 // Oops! The package name is not valid for the uid they are calling
281 // under. Abort.
282 Slog.w(TAG, "Bad call: specified package " + packageName
283 + " under uid " + uid + " but it is really " + pkgUid);
284 return null;
285 }
286 } finally {
287 Binder.restoreCallingIdentity(ident);
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800288 }
Dianne Hackborn35654b62013-01-14 17:38:02 -0800289 ops = new Ops(packageName, uid);
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800290 pkgOps.put(packageName, ops);
291 }
292 Op op = ops.get(code);
293 if (op == null) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800294 if (!edit) {
295 return null;
296 }
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800297 op = new Op(code);
298 ops.put(code, op);
299 }
Dianne Hackborn35654b62013-01-14 17:38:02 -0800300 if (edit && !mWriteScheduled) {
301 mWriteScheduled = true;
302 mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
303 }
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800304 return op;
305 }
306
Dianne Hackborn35654b62013-01-14 17:38:02 -0800307 void readState() {
308 synchronized (mFile) {
309 synchronized (this) {
310 FileInputStream stream;
311 try {
312 stream = mFile.openRead();
313 } catch (FileNotFoundException e) {
314 Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
315 return;
316 }
317 boolean success = false;
318 try {
319 XmlPullParser parser = Xml.newPullParser();
320 parser.setInput(stream, null);
321 int type;
322 while ((type = parser.next()) != XmlPullParser.START_TAG
323 && type != XmlPullParser.END_DOCUMENT) {
324 ;
325 }
326
327 if (type != XmlPullParser.START_TAG) {
328 throw new IllegalStateException("no start tag found");
329 }
330
331 int outerDepth = parser.getDepth();
332 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
333 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
334 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
335 continue;
336 }
337
338 String tagName = parser.getName();
339 if (tagName.equals("pkg")) {
340 readPackage(parser);
341 } else {
342 Slog.w(TAG, "Unknown element under <app-ops>: "
343 + parser.getName());
344 XmlUtils.skipCurrentTag(parser);
345 }
346 }
347 success = true;
348 } catch (IllegalStateException e) {
349 Slog.w(TAG, "Failed parsing " + e);
350 } catch (NullPointerException e) {
351 Slog.w(TAG, "Failed parsing " + e);
352 } catch (NumberFormatException e) {
353 Slog.w(TAG, "Failed parsing " + e);
354 } catch (XmlPullParserException e) {
355 Slog.w(TAG, "Failed parsing " + e);
356 } catch (IOException e) {
357 Slog.w(TAG, "Failed parsing " + e);
358 } catch (IndexOutOfBoundsException e) {
359 Slog.w(TAG, "Failed parsing " + e);
360 } finally {
361 if (!success) {
362 mUidOps.clear();
363 }
364 try {
365 stream.close();
366 } catch (IOException e) {
367 }
368 }
369 }
370 }
371 }
372
373 void readPackage(XmlPullParser parser) throws NumberFormatException,
374 XmlPullParserException, IOException {
375 String pkgName = parser.getAttributeValue(null, "n");
376 int outerDepth = parser.getDepth();
377 int type;
378 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
379 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
380 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
381 continue;
382 }
383
384 String tagName = parser.getName();
385 if (tagName.equals("uid")) {
386 readUid(parser, pkgName);
387 } else {
388 Slog.w(TAG, "Unknown element under <pkg>: "
389 + parser.getName());
390 XmlUtils.skipCurrentTag(parser);
391 }
392 }
393 }
394
395 void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException,
396 XmlPullParserException, IOException {
397 int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
398 int outerDepth = parser.getDepth();
399 int type;
400 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
401 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
402 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
403 continue;
404 }
405
406 String tagName = parser.getName();
407 if (tagName.equals("op")) {
408 Op op = new Op(Integer.parseInt(parser.getAttributeValue(null, "n")));
409 op.time = Long.parseLong(parser.getAttributeValue(null, "t"));
410 op.duration = Integer.parseInt(parser.getAttributeValue(null, "d"));
411 HashMap<String, Ops> pkgOps = mUidOps.get(uid);
412 if (pkgOps == null) {
413 pkgOps = new HashMap<String, Ops>();
414 mUidOps.put(uid, pkgOps);
415 }
416 Ops ops = pkgOps.get(pkgName);
417 if (ops == null) {
418 ops = new Ops(pkgName, uid);
419 pkgOps.put(pkgName, ops);
420 }
421 ops.put(op.op, op);
422 } else {
423 Slog.w(TAG, "Unknown element under <pkg>: "
424 + parser.getName());
425 XmlUtils.skipCurrentTag(parser);
426 }
427 }
428 }
429
430 void writeState() {
431 synchronized (mFile) {
432 List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
433
434 FileOutputStream stream;
435 try {
436 stream = mFile.startWrite();
437 } catch (IOException e) {
438 Slog.w(TAG, "Failed to write state: " + e);
439 return;
440 }
441
442 try {
443 XmlSerializer out = new FastXmlSerializer();
444 out.setOutput(stream, "utf-8");
445 out.startDocument(null, true);
446 out.startTag(null, "app-ops");
447
448 if (allOps != null) {
449 String lastPkg = null;
450 for (int i=0; i<allOps.size(); i++) {
451 AppOpsManager.PackageOps pkg = allOps.get(i);
452 if (!pkg.getPackageName().equals(lastPkg)) {
453 if (lastPkg != null) {
454 out.endTag(null, "pkg");
455 }
456 lastPkg = pkg.getPackageName();
457 out.startTag(null, "pkg");
458 out.attribute(null, "n", lastPkg);
459 }
460 out.startTag(null, "uid");
461 out.attribute(null, "n", Integer.toString(pkg.getUid()));
462 List<AppOpsManager.OpEntry> ops = pkg.getOps();
463 for (int j=0; j<ops.size(); j++) {
464 AppOpsManager.OpEntry op = ops.get(j);
465 out.startTag(null, "op");
466 out.attribute(null, "n", Integer.toString(op.getOp()));
467 out.attribute(null, "t", Long.toString(op.getTime()));
468 out.attribute(null, "d", Integer.toString(op.getDuration()));
469 out.endTag(null, "op");
470 }
471 out.endTag(null, "uid");
472 }
473 if (lastPkg != null) {
474 out.endTag(null, "pkg");
475 }
476 }
477
478 out.endTag(null, "app-ops");
479 out.endDocument();
480 mFile.finishWrite(stream);
481 } catch (IOException e) {
482 Slog.w(TAG, "Failed to write state, restoring backup.", e);
483 mFile.failWrite(stream);
484 }
485 }
486 }
487
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800488 @Override
489 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
490 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
491 != PackageManager.PERMISSION_GRANTED) {
492 pw.println("Permission Denial: can't dump ApOps service from from pid="
493 + Binder.getCallingPid()
494 + ", uid=" + Binder.getCallingUid());
495 return;
496 }
497
498 synchronized (this) {
499 pw.println("Current AppOps Service state:");
500 for (int i=0; i<mUidOps.size(); i++) {
501 pw.print(" Uid "); UserHandle.formatUid(pw, mUidOps.keyAt(i)); pw.println(":");
502 HashMap<String, Ops> pkgOps = mUidOps.valueAt(i);
503 for (Ops ops : pkgOps.values()) {
504 pw.print(" Package "); pw.print(ops.packageName); pw.println(":");
505 for (int j=0; j<ops.size(); j++) {
506 Op op = ops.valueAt(j);
507 pw.print(" "); pw.print(AppOpsManager.opToString(op.op));
508 pw.print(": time=");
509 TimeUtils.formatDuration(System.currentTimeMillis()-op.time, pw);
510 pw.print(" ago");
511 if (op.duration == -1) {
512 pw.println(" (running)");
513 } else {
514 pw.print("; duration=");
515 TimeUtils.formatDuration(op.duration, pw);
516 pw.println();
517 }
518 }
519 }
520 }
521 }
522 }
523}