• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.server;
18 
19 import java.io.File;
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;
30 import java.util.Map;
31 
32 import android.app.AppOpsManager;
33 import android.content.Context;
34 import android.content.pm.PackageManager;
35 import android.content.pm.PackageManager.NameNotFoundException;
36 import android.os.AsyncTask;
37 import android.os.Binder;
38 import android.os.Handler;
39 import android.os.IBinder;
40 import android.os.Process;
41 import android.os.RemoteException;
42 import android.os.ServiceManager;
43 import android.os.UserHandle;
44 import android.util.AtomicFile;
45 import android.util.Log;
46 import android.util.Pair;
47 import android.util.Slog;
48 import android.util.SparseArray;
49 import android.util.TimeUtils;
50 import android.util.Xml;
51 
52 import com.android.internal.app.IAppOpsService;
53 import com.android.internal.app.IAppOpsCallback;
54 import com.android.internal.util.FastXmlSerializer;
55 import com.android.internal.util.XmlUtils;
56 
57 import org.xmlpull.v1.XmlPullParser;
58 import org.xmlpull.v1.XmlPullParserException;
59 import org.xmlpull.v1.XmlSerializer;
60 
61 public class AppOpsService extends IAppOpsService.Stub {
62     static final String TAG = "AppOps";
63     static final boolean DEBUG = false;
64 
65     // Write at most every 30 minutes.
66     static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
67 
68     Context mContext;
69     final AtomicFile mFile;
70     final Handler mHandler;
71 
72     boolean mWriteScheduled;
73     final Runnable mWriteRunner = new Runnable() {
74         public void run() {
75             synchronized (AppOpsService.this) {
76                 mWriteScheduled = false;
77                 AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
78                     @Override protected Void doInBackground(Void... params) {
79                         writeState();
80                         return null;
81                     }
82                 };
83                 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
84             }
85         }
86     };
87 
88     final SparseArray<HashMap<String, Ops>> mUidOps
89             = new SparseArray<HashMap<String, Ops>>();
90 
91     public final static class Ops extends SparseArray<Op> {
92         public final String packageName;
93         public final int uid;
94 
Ops(String _packageName, int _uid)95         public Ops(String _packageName, int _uid) {
96             packageName = _packageName;
97             uid = _uid;
98         }
99     }
100 
101     public final static class Op {
102         public final int op;
103         public int mode;
104         public int duration;
105         public long time;
106         public long rejectTime;
107         public int nesting;
108 
Op(int _op)109         public Op(int _op) {
110             op = _op;
111             mode = AppOpsManager.MODE_ALLOWED;
112         }
113     }
114 
115     final SparseArray<ArrayList<Callback>> mOpModeWatchers
116             = new SparseArray<ArrayList<Callback>>();
117     final HashMap<String, ArrayList<Callback>> mPackageModeWatchers
118             = new HashMap<String, ArrayList<Callback>>();
119     final HashMap<IBinder, Callback> mModeWatchers
120             = new HashMap<IBinder, Callback>();
121 
122     public final class Callback implements DeathRecipient {
123         final IAppOpsCallback mCallback;
124 
Callback(IAppOpsCallback callback)125         public Callback(IAppOpsCallback callback) {
126             mCallback = callback;
127             try {
128                 mCallback.asBinder().linkToDeath(this, 0);
129             } catch (RemoteException e) {
130             }
131         }
132 
unlinkToDeath()133         public void unlinkToDeath() {
134             mCallback.asBinder().unlinkToDeath(this, 0);
135         }
136 
137         @Override
binderDied()138         public void binderDied() {
139             stopWatchingMode(mCallback);
140         }
141     }
142 
AppOpsService(File storagePath)143     public AppOpsService(File storagePath) {
144         mFile = new AtomicFile(storagePath);
145         mHandler = new Handler();
146         readState();
147     }
148 
publish(Context context)149     public void publish(Context context) {
150         mContext = context;
151         ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
152     }
153 
systemReady()154     public void systemReady() {
155         synchronized (this) {
156             boolean changed = false;
157             for (int i=0; i<mUidOps.size(); i++) {
158                 HashMap<String, Ops> pkgs = mUidOps.valueAt(i);
159                 Iterator<Ops> it = pkgs.values().iterator();
160                 while (it.hasNext()) {
161                     Ops ops = it.next();
162                     int curUid;
163                     try {
164                         curUid = mContext.getPackageManager().getPackageUid(ops.packageName,
165                                 UserHandle.getUserId(ops.uid));
166                     } catch (NameNotFoundException e) {
167                         curUid = -1;
168                     }
169                     if (curUid != ops.uid) {
170                         Slog.i(TAG, "Pruning old package " + ops.packageName
171                                 + "/" + ops.uid + ": new uid=" + curUid);
172                         it.remove();
173                         changed = true;
174                     }
175                 }
176                 if (pkgs.size() <= 0) {
177                     mUidOps.removeAt(i);
178                 }
179             }
180             if (changed) {
181                 scheduleWriteLocked();
182             }
183         }
184     }
185 
packageRemoved(int uid, String packageName)186     public void packageRemoved(int uid, String packageName) {
187         synchronized (this) {
188             HashMap<String, Ops> pkgs = mUidOps.get(uid);
189             if (pkgs != null) {
190                 if (pkgs.remove(packageName) != null) {
191                     if (pkgs.size() <= 0) {
192                         mUidOps.remove(uid);
193                     }
194                     scheduleWriteLocked();
195                 }
196             }
197         }
198     }
199 
uidRemoved(int uid)200     public void uidRemoved(int uid) {
201         synchronized (this) {
202             if (mUidOps.indexOfKey(uid) >= 0) {
203                 mUidOps.remove(uid);
204                 scheduleWriteLocked();
205             }
206         }
207     }
208 
shutdown()209     public void shutdown() {
210         Slog.w(TAG, "Writing app ops before shutdown...");
211         boolean doWrite = false;
212         synchronized (this) {
213             if (mWriteScheduled) {
214                 mWriteScheduled = false;
215                 doWrite = true;
216             }
217         }
218         if (doWrite) {
219             writeState();
220         }
221     }
222 
collectOps(Ops pkgOps, int[] ops)223     private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
224         ArrayList<AppOpsManager.OpEntry> resOps = null;
225         if (ops == null) {
226             resOps = new ArrayList<AppOpsManager.OpEntry>();
227             for (int j=0; j<pkgOps.size(); j++) {
228                 Op curOp = pkgOps.valueAt(j);
229                 resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.mode, curOp.time,
230                         curOp.rejectTime, curOp.duration));
231             }
232         } else {
233             for (int j=0; j<ops.length; j++) {
234                 Op curOp = pkgOps.get(ops[j]);
235                 if (curOp != null) {
236                     if (resOps == null) {
237                         resOps = new ArrayList<AppOpsManager.OpEntry>();
238                     }
239                     resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.mode, curOp.time,
240                             curOp.rejectTime, curOp.duration));
241                 }
242             }
243         }
244         return resOps;
245     }
246 
247     @Override
getPackagesForOps(int[] ops)248     public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
249         mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
250                 Binder.getCallingPid(), Binder.getCallingUid(), null);
251         ArrayList<AppOpsManager.PackageOps> res = null;
252         synchronized (this) {
253             for (int i=0; i<mUidOps.size(); i++) {
254                 HashMap<String, Ops> packages = mUidOps.valueAt(i);
255                 for (Ops pkgOps : packages.values()) {
256                     ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
257                     if (resOps != null) {
258                         if (res == null) {
259                             res = new ArrayList<AppOpsManager.PackageOps>();
260                         }
261                         AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
262                                 pkgOps.packageName, pkgOps.uid, resOps);
263                         res.add(resPackage);
264                     }
265                 }
266             }
267         }
268         return res;
269     }
270 
271     @Override
getOpsForPackage(int uid, String packageName, int[] ops)272     public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
273             int[] ops) {
274         mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
275                 Binder.getCallingPid(), Binder.getCallingUid(), null);
276         synchronized (this) {
277             Ops pkgOps = getOpsLocked(uid, packageName, false);
278             if (pkgOps == null) {
279                 return null;
280             }
281             ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
282             if (resOps == null) {
283                 return null;
284             }
285             ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
286             AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
287                     pkgOps.packageName, pkgOps.uid, resOps);
288             res.add(resPackage);
289             return res;
290         }
291     }
292 
pruneOp(Op op, int uid, String packageName)293     private void pruneOp(Op op, int uid, String packageName) {
294         if (op.time == 0 && op.rejectTime == 0) {
295             Ops ops = getOpsLocked(uid, packageName, false);
296             if (ops != null) {
297                 ops.remove(op.op);
298                 if (ops.size() <= 0) {
299                     HashMap<String, Ops> pkgOps = mUidOps.get(uid);
300                     if (pkgOps != null) {
301                         pkgOps.remove(ops.packageName);
302                         if (pkgOps.size() <= 0) {
303                             mUidOps.remove(uid);
304                         }
305                     }
306                 }
307             }
308         }
309     }
310 
311     @Override
setMode(int code, int uid, String packageName, int mode)312     public void setMode(int code, int uid, String packageName, int mode) {
313         verifyIncomingUid(uid);
314         verifyIncomingOp(code);
315         ArrayList<Callback> repCbs = null;
316         code = AppOpsManager.opToSwitch(code);
317         synchronized (this) {
318             Op op = getOpLocked(code, uid, packageName, true);
319             if (op != null) {
320                 if (op.mode != mode) {
321                     op.mode = mode;
322                     ArrayList<Callback> cbs = mOpModeWatchers.get(code);
323                     if (cbs != null) {
324                         if (repCbs == null) {
325                             repCbs = new ArrayList<Callback>();
326                         }
327                         repCbs.addAll(cbs);
328                     }
329                     cbs = mPackageModeWatchers.get(packageName);
330                     if (cbs != null) {
331                         if (repCbs == null) {
332                             repCbs = new ArrayList<Callback>();
333                         }
334                         repCbs.addAll(cbs);
335                     }
336                     if (mode == AppOpsManager.MODE_ALLOWED) {
337                         // If going into the default mode, prune this op
338                         // if there is nothing else interesting in it.
339                         pruneOp(op, uid, packageName);
340                     }
341                     scheduleWriteNowLocked();
342                 }
343             }
344         }
345         if (repCbs != null) {
346             for (int i=0; i<repCbs.size(); i++) {
347                 try {
348                     repCbs.get(i).mCallback.opChanged(code, packageName);
349                 } catch (RemoteException e) {
350                 }
351             }
352         }
353     }
354 
addCallbacks( HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks, String packageName, int op, ArrayList<Callback> cbs)355     private static HashMap<Callback, ArrayList<Pair<String, Integer>>> addCallbacks(
356             HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks,
357             String packageName, int op, ArrayList<Callback> cbs) {
358         if (cbs == null) {
359             return callbacks;
360         }
361         if (callbacks == null) {
362             callbacks = new HashMap<Callback, ArrayList<Pair<String, Integer>>>();
363         }
364         for (int i=0; i<cbs.size(); i++) {
365             Callback cb = cbs.get(i);
366             ArrayList<Pair<String, Integer>> reports = callbacks.get(cb);
367             if (reports == null) {
368                 reports = new ArrayList<Pair<String, Integer>>();
369                 callbacks.put(cb, reports);
370             }
371             reports.add(new Pair<String, Integer>(packageName, op));
372         }
373         return callbacks;
374     }
375 
376     @Override
resetAllModes()377     public void resetAllModes() {
378         mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
379                 Binder.getCallingPid(), Binder.getCallingUid(), null);
380         HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks = null;
381         synchronized (this) {
382             boolean changed = false;
383             for (int i=mUidOps.size()-1; i>=0; i--) {
384                 HashMap<String, Ops> packages = mUidOps.valueAt(i);
385                 Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
386                 while (it.hasNext()) {
387                     Map.Entry<String, Ops> ent = it.next();
388                     String packageName = ent.getKey();
389                     Ops pkgOps = ent.getValue();
390                     for (int j=pkgOps.size()-1; j>=0; j--) {
391                         Op curOp = pkgOps.valueAt(j);
392                         if (curOp.mode != AppOpsManager.MODE_ALLOWED) {
393                             curOp.mode = AppOpsManager.MODE_ALLOWED;
394                             changed = true;
395                             callbacks = addCallbacks(callbacks, packageName, curOp.op,
396                                     mOpModeWatchers.get(curOp.op));
397                             callbacks = addCallbacks(callbacks, packageName, curOp.op,
398                                     mPackageModeWatchers.get(packageName));
399                             if (curOp.time == 0 && curOp.rejectTime == 0) {
400                                 pkgOps.removeAt(j);
401                             }
402                         }
403                     }
404                     if (pkgOps.size() == 0) {
405                         it.remove();
406                     }
407                 }
408                 if (packages.size() == 0) {
409                     mUidOps.removeAt(i);
410                 }
411             }
412             if (changed) {
413                 scheduleWriteNowLocked();
414             }
415         }
416         if (callbacks != null) {
417             for (Map.Entry<Callback, ArrayList<Pair<String, Integer>>> ent : callbacks.entrySet()) {
418                 Callback cb = ent.getKey();
419                 ArrayList<Pair<String, Integer>> reports = ent.getValue();
420                 for (int i=0; i<reports.size(); i++) {
421                     Pair<String, Integer> rep = reports.get(i);
422                     try {
423                         cb.mCallback.opChanged(rep.second, rep.first);
424                     } catch (RemoteException e) {
425                     }
426                 }
427             }
428         }
429     }
430 
431     @Override
startWatchingMode(int op, String packageName, IAppOpsCallback callback)432     public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) {
433         synchronized (this) {
434             op = AppOpsManager.opToSwitch(op);
435             Callback cb = mModeWatchers.get(callback.asBinder());
436             if (cb == null) {
437                 cb = new Callback(callback);
438                 mModeWatchers.put(callback.asBinder(), cb);
439             }
440             if (op != AppOpsManager.OP_NONE) {
441                 ArrayList<Callback> cbs = mOpModeWatchers.get(op);
442                 if (cbs == null) {
443                     cbs = new ArrayList<Callback>();
444                     mOpModeWatchers.put(op, cbs);
445                 }
446                 cbs.add(cb);
447             }
448             if (packageName != null) {
449                 ArrayList<Callback> cbs = mPackageModeWatchers.get(packageName);
450                 if (cbs == null) {
451                     cbs = new ArrayList<Callback>();
452                     mPackageModeWatchers.put(packageName, cbs);
453                 }
454                 cbs.add(cb);
455             }
456         }
457     }
458 
459     @Override
stopWatchingMode(IAppOpsCallback callback)460     public void stopWatchingMode(IAppOpsCallback callback) {
461         synchronized (this) {
462             Callback cb = mModeWatchers.remove(callback.asBinder());
463             if (cb != null) {
464                 cb.unlinkToDeath();
465                 for (int i=0; i<mOpModeWatchers.size(); i++) {
466                     ArrayList<Callback> cbs = mOpModeWatchers.valueAt(i);
467                     cbs.remove(cb);
468                     if (cbs.size() <= 0) {
469                         mOpModeWatchers.removeAt(i);
470                     }
471                 }
472                 if (mPackageModeWatchers.size() > 0) {
473                     Iterator<ArrayList<Callback>> it = mPackageModeWatchers.values().iterator();
474                     while (it.hasNext()) {
475                         ArrayList<Callback> cbs = it.next();
476                         cbs.remove(cb);
477                         if (cbs.size() <= 0) {
478                             it.remove();
479                         }
480                     }
481                 }
482             }
483         }
484     }
485 
486     @Override
checkOperation(int code, int uid, String packageName)487     public int checkOperation(int code, int uid, String packageName) {
488         verifyIncomingUid(uid);
489         verifyIncomingOp(code);
490         synchronized (this) {
491             Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, false);
492             if (op == null) {
493                 return AppOpsManager.MODE_ALLOWED;
494             }
495             return op.mode;
496         }
497     }
498 
499     @Override
noteOperation(int code, int uid, String packageName)500     public int noteOperation(int code, int uid, String packageName) {
501         verifyIncomingUid(uid);
502         verifyIncomingOp(code);
503         synchronized (this) {
504             Ops ops = getOpsLocked(uid, packageName, true);
505             if (ops == null) {
506                 if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
507                         + " package " + packageName);
508                 return AppOpsManager.MODE_IGNORED;
509             }
510             Op op = getOpLocked(ops, code, true);
511             if (op.duration == -1) {
512                 Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
513                         + " code " + code + " time=" + op.time + " duration=" + op.duration);
514             }
515             op.duration = 0;
516             final int switchCode = AppOpsManager.opToSwitch(code);
517             final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
518             if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
519                 if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "
520                         + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
521                 op.rejectTime = System.currentTimeMillis();
522                 return switchOp.mode;
523             }
524             if (DEBUG) Log.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
525                     + " package " + packageName);
526             op.time = System.currentTimeMillis();
527             op.rejectTime = 0;
528             return AppOpsManager.MODE_ALLOWED;
529         }
530     }
531 
532     @Override
startOperation(int code, int uid, String packageName)533     public int startOperation(int code, int uid, String packageName) {
534         verifyIncomingUid(uid);
535         verifyIncomingOp(code);
536         synchronized (this) {
537             Ops ops = getOpsLocked(uid, packageName, true);
538             if (ops == null) {
539                 if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid
540                         + " package " + packageName);
541                 return AppOpsManager.MODE_IGNORED;
542             }
543             Op op = getOpLocked(ops, code, true);
544             final int switchCode = AppOpsManager.opToSwitch(code);
545             final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
546             if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
547                 if (DEBUG) Log.d(TAG, "startOperation: reject #" + op.mode + " for code "
548                         + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
549                 op.rejectTime = System.currentTimeMillis();
550                 return switchOp.mode;
551             }
552             if (DEBUG) Log.d(TAG, "startOperation: allowing code " + code + " uid " + uid
553                     + " package " + packageName);
554             if (op.nesting == 0) {
555                 op.time = System.currentTimeMillis();
556                 op.rejectTime = 0;
557                 op.duration = -1;
558             }
559             op.nesting++;
560             return AppOpsManager.MODE_ALLOWED;
561         }
562     }
563 
564     @Override
finishOperation(int code, int uid, String packageName)565     public void finishOperation(int code, int uid, String packageName) {
566         verifyIncomingUid(uid);
567         verifyIncomingOp(code);
568         synchronized (this) {
569             Op op = getOpLocked(code, uid, packageName, true);
570             if (op == null) {
571                 return;
572             }
573             if (op.nesting <= 1) {
574                 if (op.nesting == 1) {
575                     op.duration = (int)(System.currentTimeMillis() - op.time);
576                     op.time += op.duration;
577                 } else {
578                     Slog.w(TAG, "Finishing op nesting under-run: uid " + uid + " pkg " + packageName
579                         + " code " + code + " time=" + op.time + " duration=" + op.duration
580                         + " nesting=" + op.nesting);
581                 }
582                 op.nesting = 0;
583             } else {
584                 op.nesting--;
585             }
586         }
587     }
588 
verifyIncomingUid(int uid)589     private void verifyIncomingUid(int uid) {
590         if (uid == Binder.getCallingUid()) {
591             return;
592         }
593         if (Binder.getCallingPid() == Process.myPid()) {
594             return;
595         }
596         mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
597                 Binder.getCallingPid(), Binder.getCallingUid(), null);
598     }
599 
verifyIncomingOp(int op)600     private void verifyIncomingOp(int op) {
601         if (op >= 0 && op < AppOpsManager._NUM_OP) {
602             return;
603         }
604         throw new IllegalArgumentException("Bad operation #" + op);
605     }
606 
getOpsLocked(int uid, String packageName, boolean edit)607     private Ops getOpsLocked(int uid, String packageName, boolean edit) {
608         HashMap<String, Ops> pkgOps = mUidOps.get(uid);
609         if (pkgOps == null) {
610             if (!edit) {
611                 return null;
612             }
613             pkgOps = new HashMap<String, Ops>();
614             mUidOps.put(uid, pkgOps);
615         }
616         if (uid == 0) {
617             packageName = "root";
618         } else if (uid == Process.SHELL_UID) {
619             packageName = "com.android.shell";
620         }
621         Ops ops = pkgOps.get(packageName);
622         if (ops == null) {
623             if (!edit) {
624                 return null;
625             }
626             // This is the first time we have seen this package name under this uid,
627             // so let's make sure it is valid.
628             if (uid != 0) {
629                 final long ident = Binder.clearCallingIdentity();
630                 try {
631                     int pkgUid = -1;
632                     try {
633                         pkgUid = mContext.getPackageManager().getPackageUid(packageName,
634                                 UserHandle.getUserId(uid));
635                     } catch (NameNotFoundException e) {
636                     }
637                     if (pkgUid != uid) {
638                         // Oops!  The package name is not valid for the uid they are calling
639                         // under.  Abort.
640                         Slog.w(TAG, "Bad call: specified package " + packageName
641                                 + " under uid " + uid + " but it is really " + pkgUid);
642                         return null;
643                     }
644                 } finally {
645                     Binder.restoreCallingIdentity(ident);
646                 }
647             }
648             ops = new Ops(packageName, uid);
649             pkgOps.put(packageName, ops);
650         }
651         return ops;
652     }
653 
scheduleWriteLocked()654     private void scheduleWriteLocked() {
655         if (!mWriteScheduled) {
656             mWriteScheduled = true;
657             mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
658         }
659     }
660 
scheduleWriteNowLocked()661     private void scheduleWriteNowLocked() {
662         if (!mWriteScheduled) {
663             mWriteScheduled = true;
664         }
665         mHandler.removeCallbacks(mWriteRunner);
666         mHandler.post(mWriteRunner);
667     }
668 
getOpLocked(int code, int uid, String packageName, boolean edit)669     private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
670         Ops ops = getOpsLocked(uid, packageName, edit);
671         if (ops == null) {
672             return null;
673         }
674         return getOpLocked(ops, code, edit);
675     }
676 
getOpLocked(Ops ops, int code, boolean edit)677     private Op getOpLocked(Ops ops, int code, boolean edit) {
678         Op op = ops.get(code);
679         if (op == null) {
680             if (!edit) {
681                 return null;
682             }
683             op = new Op(code);
684             ops.put(code, op);
685         }
686         if (edit) {
687             scheduleWriteLocked();
688         }
689         return op;
690     }
691 
readState()692     void readState() {
693         synchronized (mFile) {
694             synchronized (this) {
695                 FileInputStream stream;
696                 try {
697                     stream = mFile.openRead();
698                 } catch (FileNotFoundException e) {
699                     Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
700                     return;
701                 }
702                 boolean success = false;
703                 try {
704                     XmlPullParser parser = Xml.newPullParser();
705                     parser.setInput(stream, null);
706                     int type;
707                     while ((type = parser.next()) != XmlPullParser.START_TAG
708                             && type != XmlPullParser.END_DOCUMENT) {
709                         ;
710                     }
711 
712                     if (type != XmlPullParser.START_TAG) {
713                         throw new IllegalStateException("no start tag found");
714                     }
715 
716                     int outerDepth = parser.getDepth();
717                     while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
718                             && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
719                         if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
720                             continue;
721                         }
722 
723                         String tagName = parser.getName();
724                         if (tagName.equals("pkg")) {
725                             readPackage(parser);
726                         } else {
727                             Slog.w(TAG, "Unknown element under <app-ops>: "
728                                     + parser.getName());
729                             XmlUtils.skipCurrentTag(parser);
730                         }
731                     }
732                     success = true;
733                 } catch (IllegalStateException e) {
734                     Slog.w(TAG, "Failed parsing " + e);
735                 } catch (NullPointerException e) {
736                     Slog.w(TAG, "Failed parsing " + e);
737                 } catch (NumberFormatException e) {
738                     Slog.w(TAG, "Failed parsing " + e);
739                 } catch (XmlPullParserException e) {
740                     Slog.w(TAG, "Failed parsing " + e);
741                 } catch (IOException e) {
742                     Slog.w(TAG, "Failed parsing " + e);
743                 } catch (IndexOutOfBoundsException e) {
744                     Slog.w(TAG, "Failed parsing " + e);
745                 } finally {
746                     if (!success) {
747                         mUidOps.clear();
748                     }
749                     try {
750                         stream.close();
751                     } catch (IOException e) {
752                     }
753                 }
754             }
755         }
756     }
757 
readPackage(XmlPullParser parser)758     void readPackage(XmlPullParser parser) throws NumberFormatException,
759             XmlPullParserException, IOException {
760         String pkgName = parser.getAttributeValue(null, "n");
761         int outerDepth = parser.getDepth();
762         int type;
763         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
764                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
765             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
766                 continue;
767             }
768 
769             String tagName = parser.getName();
770             if (tagName.equals("uid")) {
771                 readUid(parser, pkgName);
772             } else {
773                 Slog.w(TAG, "Unknown element under <pkg>: "
774                         + parser.getName());
775                 XmlUtils.skipCurrentTag(parser);
776             }
777         }
778     }
779 
readUid(XmlPullParser parser, String pkgName)780     void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException,
781             XmlPullParserException, IOException {
782         int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
783         int outerDepth = parser.getDepth();
784         int type;
785         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
786                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
787             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
788                 continue;
789             }
790 
791             String tagName = parser.getName();
792             if (tagName.equals("op")) {
793                 Op op = new Op(Integer.parseInt(parser.getAttributeValue(null, "n")));
794                 String mode = parser.getAttributeValue(null, "m");
795                 if (mode != null) {
796                     op.mode = Integer.parseInt(mode);
797                 }
798                 String time = parser.getAttributeValue(null, "t");
799                 if (time != null) {
800                     op.time = Long.parseLong(time);
801                 }
802                 time = parser.getAttributeValue(null, "r");
803                 if (time != null) {
804                     op.rejectTime = Long.parseLong(time);
805                 }
806                 String dur = parser.getAttributeValue(null, "d");
807                 if (dur != null) {
808                     op.duration = Integer.parseInt(dur);
809                 }
810                 HashMap<String, Ops> pkgOps = mUidOps.get(uid);
811                 if (pkgOps == null) {
812                     pkgOps = new HashMap<String, Ops>();
813                     mUidOps.put(uid, pkgOps);
814                 }
815                 Ops ops = pkgOps.get(pkgName);
816                 if (ops == null) {
817                     ops = new Ops(pkgName, uid);
818                     pkgOps.put(pkgName, ops);
819                 }
820                 ops.put(op.op, op);
821             } else {
822                 Slog.w(TAG, "Unknown element under <pkg>: "
823                         + parser.getName());
824                 XmlUtils.skipCurrentTag(parser);
825             }
826         }
827     }
828 
writeState()829     void writeState() {
830         synchronized (mFile) {
831             List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
832 
833             FileOutputStream stream;
834             try {
835                 stream = mFile.startWrite();
836             } catch (IOException e) {
837                 Slog.w(TAG, "Failed to write state: " + e);
838                 return;
839             }
840 
841             try {
842                 XmlSerializer out = new FastXmlSerializer();
843                 out.setOutput(stream, "utf-8");
844                 out.startDocument(null, true);
845                 out.startTag(null, "app-ops");
846 
847                 if (allOps != null) {
848                     String lastPkg = null;
849                     for (int i=0; i<allOps.size(); i++) {
850                         AppOpsManager.PackageOps pkg = allOps.get(i);
851                         if (!pkg.getPackageName().equals(lastPkg)) {
852                             if (lastPkg != null) {
853                                 out.endTag(null, "pkg");
854                             }
855                             lastPkg = pkg.getPackageName();
856                             out.startTag(null, "pkg");
857                             out.attribute(null, "n", lastPkg);
858                         }
859                         out.startTag(null, "uid");
860                         out.attribute(null, "n", Integer.toString(pkg.getUid()));
861                         List<AppOpsManager.OpEntry> ops = pkg.getOps();
862                         for (int j=0; j<ops.size(); j++) {
863                             AppOpsManager.OpEntry op = ops.get(j);
864                             out.startTag(null, "op");
865                             out.attribute(null, "n", Integer.toString(op.getOp()));
866                             if (op.getMode() != AppOpsManager.MODE_ALLOWED) {
867                                 out.attribute(null, "m", Integer.toString(op.getMode()));
868                             }
869                             long time = op.getTime();
870                             if (time != 0) {
871                                 out.attribute(null, "t", Long.toString(time));
872                             }
873                             time = op.getRejectTime();
874                             if (time != 0) {
875                                 out.attribute(null, "r", Long.toString(time));
876                             }
877                             int dur = op.getDuration();
878                             if (dur != 0) {
879                                 out.attribute(null, "d", Integer.toString(dur));
880                             }
881                             out.endTag(null, "op");
882                         }
883                         out.endTag(null, "uid");
884                     }
885                     if (lastPkg != null) {
886                         out.endTag(null, "pkg");
887                     }
888                 }
889 
890                 out.endTag(null, "app-ops");
891                 out.endDocument();
892                 mFile.finishWrite(stream);
893             } catch (IOException e) {
894                 Slog.w(TAG, "Failed to write state, restoring backup.", e);
895                 mFile.failWrite(stream);
896             }
897         }
898     }
899 
900     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)901     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
902         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
903                 != PackageManager.PERMISSION_GRANTED) {
904             pw.println("Permission Denial: can't dump ApOps service from from pid="
905                     + Binder.getCallingPid()
906                     + ", uid=" + Binder.getCallingUid());
907             return;
908         }
909 
910         synchronized (this) {
911             pw.println("Current AppOps Service state:");
912             final long now = System.currentTimeMillis();
913             for (int i=0; i<mUidOps.size(); i++) {
914                 pw.print("  Uid "); UserHandle.formatUid(pw, mUidOps.keyAt(i)); pw.println(":");
915                 HashMap<String, Ops> pkgOps = mUidOps.valueAt(i);
916                 for (Ops ops : pkgOps.values()) {
917                     pw.print("    Package "); pw.print(ops.packageName); pw.println(":");
918                     for (int j=0; j<ops.size(); j++) {
919                         Op op = ops.valueAt(j);
920                         pw.print("      "); pw.print(AppOpsManager.opToName(op.op));
921                         pw.print(": mode="); pw.print(op.mode);
922                         if (op.time != 0) {
923                             pw.print("; time="); TimeUtils.formatDuration(now-op.time, pw);
924                             pw.print(" ago");
925                         }
926                         if (op.rejectTime != 0) {
927                             pw.print("; rejectTime="); TimeUtils.formatDuration(now-op.rejectTime, pw);
928                             pw.print(" ago");
929                         }
930                         if (op.duration == -1) {
931                             pw.println(" (running)");
932                         } else {
933                             pw.print("; duration=");
934                                     TimeUtils.formatDuration(op.duration, pw);
935                                     pw.println();
936                         }
937                     }
938                 }
939             }
940         }
941     }
942 }
943