Home | History | Annotate | Download | only in server
      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 
     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 
    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 
    125         public Callback(IAppOpsCallback callback) {
    126             mCallback = callback;
    127             try {
    128                 mCallback.asBinder().linkToDeath(this, 0);
    129             } catch (RemoteException e) {
    130             }
    131         }
    132 
    133         public void unlinkToDeath() {
    134             mCallback.asBinder().unlinkToDeath(this, 0);
    135         }
    136 
    137         @Override
    138         public void binderDied() {
    139             stopWatchingMode(mCallback);
    140         }
    141     }
    142 
    143     public AppOpsService(File storagePath) {
    144         mFile = new AtomicFile(storagePath);
    145         mHandler = new Handler();
    146         readState();
    147     }
    148 
    149     public void publish(Context context) {
    150         mContext = context;
    151         ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
    152     }
    153 
    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 
    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 
    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 
    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 
    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
    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
    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 
    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
    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 
    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
    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=0; i<mUidOps.size(); i++) {
    384                 HashMap<String, Ops> packages = mUidOps.valueAt(i);
    385                 for (Map.Entry<String, Ops> ent : packages.entrySet()) {
    386                     String packageName = ent.getKey();
    387                     Ops pkgOps = ent.getValue();
    388                     for (int j=0; j<pkgOps.size(); j++) {
    389                         Op curOp = pkgOps.valueAt(j);
    390                         if (curOp.mode != AppOpsManager.MODE_ALLOWED) {
    391                             curOp.mode = AppOpsManager.MODE_ALLOWED;
    392                             changed = true;
    393                             callbacks = addCallbacks(callbacks, packageName, curOp.op,
    394                                     mOpModeWatchers.get(curOp.op));
    395                             callbacks = addCallbacks(callbacks, packageName, curOp.op,
    396                                     mPackageModeWatchers.get(packageName));
    397                             pruneOp(curOp, mUidOps.keyAt(i), packageName);
    398                         }
    399                     }
    400                 }
    401             }
    402             if (changed) {
    403                 scheduleWriteNowLocked();
    404             }
    405         }
    406         if (callbacks != null) {
    407             for (Map.Entry<Callback, ArrayList<Pair<String, Integer>>> ent : callbacks.entrySet()) {
    408                 Callback cb = ent.getKey();
    409                 ArrayList<Pair<String, Integer>> reports = ent.getValue();
    410                 for (int i=0; i<reports.size(); i++) {
    411                     Pair<String, Integer> rep = reports.get(i);
    412                     try {
    413                         cb.mCallback.opChanged(rep.second, rep.first);
    414                     } catch (RemoteException e) {
    415                     }
    416                 }
    417             }
    418         }
    419     }
    420 
    421     @Override
    422     public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) {
    423         synchronized (this) {
    424             op = AppOpsManager.opToSwitch(op);
    425             Callback cb = mModeWatchers.get(callback.asBinder());
    426             if (cb == null) {
    427                 cb = new Callback(callback);
    428                 mModeWatchers.put(callback.asBinder(), cb);
    429             }
    430             if (op != AppOpsManager.OP_NONE) {
    431                 ArrayList<Callback> cbs = mOpModeWatchers.get(op);
    432                 if (cbs == null) {
    433                     cbs = new ArrayList<Callback>();
    434                     mOpModeWatchers.put(op, cbs);
    435                 }
    436                 cbs.add(cb);
    437             }
    438             if (packageName != null) {
    439                 ArrayList<Callback> cbs = mPackageModeWatchers.get(packageName);
    440                 if (cbs == null) {
    441                     cbs = new ArrayList<Callback>();
    442                     mPackageModeWatchers.put(packageName, cbs);
    443                 }
    444                 cbs.add(cb);
    445             }
    446         }
    447     }
    448 
    449     @Override
    450     public void stopWatchingMode(IAppOpsCallback callback) {
    451         synchronized (this) {
    452             Callback cb = mModeWatchers.remove(callback.asBinder());
    453             if (cb != null) {
    454                 cb.unlinkToDeath();
    455                 for (int i=0; i<mOpModeWatchers.size(); i++) {
    456                     ArrayList<Callback> cbs = mOpModeWatchers.valueAt(i);
    457                     cbs.remove(cb);
    458                     if (cbs.size() <= 0) {
    459                         mOpModeWatchers.removeAt(i);
    460                     }
    461                 }
    462                 if (mPackageModeWatchers.size() > 0) {
    463                     Iterator<ArrayList<Callback>> it = mPackageModeWatchers.values().iterator();
    464                     while (it.hasNext()) {
    465                         ArrayList<Callback> cbs = it.next();
    466                         cbs.remove(cb);
    467                         if (cbs.size() <= 0) {
    468                             it.remove();
    469                         }
    470                     }
    471                 }
    472             }
    473         }
    474     }
    475 
    476     @Override
    477     public int checkOperation(int code, int uid, String packageName) {
    478         verifyIncomingUid(uid);
    479         verifyIncomingOp(code);
    480         synchronized (this) {
    481             Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, false);
    482             if (op == null) {
    483                 return AppOpsManager.MODE_ALLOWED;
    484             }
    485             return op.mode;
    486         }
    487     }
    488 
    489     @Override
    490     public int noteOperation(int code, int uid, String packageName) {
    491         verifyIncomingUid(uid);
    492         verifyIncomingOp(code);
    493         synchronized (this) {
    494             Ops ops = getOpsLocked(uid, packageName, true);
    495             if (ops == null) {
    496                 if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
    497                         + " package " + packageName);
    498                 return AppOpsManager.MODE_IGNORED;
    499             }
    500             Op op = getOpLocked(ops, code, true);
    501             if (op.duration == -1) {
    502                 Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
    503                         + " code " + code + " time=" + op.time + " duration=" + op.duration);
    504             }
    505             op.duration = 0;
    506             final int switchCode = AppOpsManager.opToSwitch(code);
    507             final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
    508             if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
    509                 if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "
    510                         + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
    511                 op.rejectTime = System.currentTimeMillis();
    512                 return switchOp.mode;
    513             }
    514             if (DEBUG) Log.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
    515                     + " package " + packageName);
    516             op.time = System.currentTimeMillis();
    517             op.rejectTime = 0;
    518             return AppOpsManager.MODE_ALLOWED;
    519         }
    520     }
    521 
    522     @Override
    523     public int startOperation(int code, int uid, String packageName) {
    524         verifyIncomingUid(uid);
    525         verifyIncomingOp(code);
    526         synchronized (this) {
    527             Ops ops = getOpsLocked(uid, packageName, true);
    528             if (ops == null) {
    529                 if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid
    530                         + " package " + packageName);
    531                 return AppOpsManager.MODE_IGNORED;
    532             }
    533             Op op = getOpLocked(ops, code, true);
    534             final int switchCode = AppOpsManager.opToSwitch(code);
    535             final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
    536             if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
    537                 if (DEBUG) Log.d(TAG, "startOperation: reject #" + op.mode + " for code "
    538                         + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
    539                 op.rejectTime = System.currentTimeMillis();
    540                 return switchOp.mode;
    541             }
    542             if (DEBUG) Log.d(TAG, "startOperation: allowing code " + code + " uid " + uid
    543                     + " package " + packageName);
    544             if (op.nesting == 0) {
    545                 op.time = System.currentTimeMillis();
    546                 op.rejectTime = 0;
    547                 op.duration = -1;
    548             }
    549             op.nesting++;
    550             return AppOpsManager.MODE_ALLOWED;
    551         }
    552     }
    553 
    554     @Override
    555     public void finishOperation(int code, int uid, String packageName) {
    556         verifyIncomingUid(uid);
    557         verifyIncomingOp(code);
    558         synchronized (this) {
    559             Op op = getOpLocked(code, uid, packageName, true);
    560             if (op == null) {
    561                 return;
    562             }
    563             if (op.nesting <= 1) {
    564                 if (op.nesting == 1) {
    565                     op.duration = (int)(System.currentTimeMillis() - op.time);
    566                     op.time += op.duration;
    567                 } else {
    568                     Slog.w(TAG, "Finishing op nesting under-run: uid " + uid + " pkg " + packageName
    569                         + " code " + code + " time=" + op.time + " duration=" + op.duration
    570                         + " nesting=" + op.nesting);
    571                 }
    572                 op.nesting = 0;
    573             } else {
    574                 op.nesting--;
    575             }
    576         }
    577     }
    578 
    579     private void verifyIncomingUid(int uid) {
    580         if (uid == Binder.getCallingUid()) {
    581             return;
    582         }
    583         if (Binder.getCallingPid() == Process.myPid()) {
    584             return;
    585         }
    586         mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
    587                 Binder.getCallingPid(), Binder.getCallingUid(), null);
    588     }
    589 
    590     private void verifyIncomingOp(int op) {
    591         if (op >= 0 && op < AppOpsManager._NUM_OP) {
    592             return;
    593         }
    594         throw new IllegalArgumentException("Bad operation #" + op);
    595     }
    596 
    597     private Ops getOpsLocked(int uid, String packageName, boolean edit) {
    598         HashMap<String, Ops> pkgOps = mUidOps.get(uid);
    599         if (pkgOps == null) {
    600             if (!edit) {
    601                 return null;
    602             }
    603             pkgOps = new HashMap<String, Ops>();
    604             mUidOps.put(uid, pkgOps);
    605         }
    606         if (uid == 0) {
    607             packageName = "root";
    608         } else if (uid == Process.SHELL_UID) {
    609             packageName = "com.android.shell";
    610         }
    611         Ops ops = pkgOps.get(packageName);
    612         if (ops == null) {
    613             if (!edit) {
    614                 return null;
    615             }
    616             // This is the first time we have seen this package name under this uid,
    617             // so let's make sure it is valid.
    618             if (uid != 0) {
    619                 final long ident = Binder.clearCallingIdentity();
    620                 try {
    621                     int pkgUid = -1;
    622                     try {
    623                         pkgUid = mContext.getPackageManager().getPackageUid(packageName,
    624                                 UserHandle.getUserId(uid));
    625                     } catch (NameNotFoundException e) {
    626                     }
    627                     if (pkgUid != uid) {
    628                         // Oops!  The package name is not valid for the uid they are calling
    629                         // under.  Abort.
    630                         Slog.w(TAG, "Bad call: specified package " + packageName
    631                                 + " under uid " + uid + " but it is really " + pkgUid);
    632                         return null;
    633                     }
    634                 } finally {
    635                     Binder.restoreCallingIdentity(ident);
    636                 }
    637             }
    638             ops = new Ops(packageName, uid);
    639             pkgOps.put(packageName, ops);
    640         }
    641         return ops;
    642     }
    643 
    644     private void scheduleWriteLocked() {
    645         if (!mWriteScheduled) {
    646             mWriteScheduled = true;
    647             mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
    648         }
    649     }
    650 
    651     private void scheduleWriteNowLocked() {
    652         if (!mWriteScheduled) {
    653             mWriteScheduled = true;
    654         }
    655         mHandler.removeCallbacks(mWriteRunner);
    656         mHandler.post(mWriteRunner);
    657     }
    658 
    659     private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
    660         Ops ops = getOpsLocked(uid, packageName, edit);
    661         if (ops == null) {
    662             return null;
    663         }
    664         return getOpLocked(ops, code, edit);
    665     }
    666 
    667     private Op getOpLocked(Ops ops, int code, boolean edit) {
    668         Op op = ops.get(code);
    669         if (op == null) {
    670             if (!edit) {
    671                 return null;
    672             }
    673             op = new Op(code);
    674             ops.put(code, op);
    675         }
    676         if (edit) {
    677             scheduleWriteLocked();
    678         }
    679         return op;
    680     }
    681 
    682     void readState() {
    683         synchronized (mFile) {
    684             synchronized (this) {
    685                 FileInputStream stream;
    686                 try {
    687                     stream = mFile.openRead();
    688                 } catch (FileNotFoundException e) {
    689                     Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
    690                     return;
    691                 }
    692                 boolean success = false;
    693                 try {
    694                     XmlPullParser parser = Xml.newPullParser();
    695                     parser.setInput(stream, null);
    696                     int type;
    697                     while ((type = parser.next()) != XmlPullParser.START_TAG
    698                             && type != XmlPullParser.END_DOCUMENT) {
    699                         ;
    700                     }
    701 
    702                     if (type != XmlPullParser.START_TAG) {
    703                         throw new IllegalStateException("no start tag found");
    704                     }
    705 
    706                     int outerDepth = parser.getDepth();
    707                     while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    708                             && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    709                         if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    710                             continue;
    711                         }
    712 
    713                         String tagName = parser.getName();
    714                         if (tagName.equals("pkg")) {
    715                             readPackage(parser);
    716                         } else {
    717                             Slog.w(TAG, "Unknown element under <app-ops>: "
    718                                     + parser.getName());
    719                             XmlUtils.skipCurrentTag(parser);
    720                         }
    721                     }
    722                     success = true;
    723                 } catch (IllegalStateException e) {
    724                     Slog.w(TAG, "Failed parsing " + e);
    725                 } catch (NullPointerException e) {
    726                     Slog.w(TAG, "Failed parsing " + e);
    727                 } catch (NumberFormatException e) {
    728                     Slog.w(TAG, "Failed parsing " + e);
    729                 } catch (XmlPullParserException e) {
    730                     Slog.w(TAG, "Failed parsing " + e);
    731                 } catch (IOException e) {
    732                     Slog.w(TAG, "Failed parsing " + e);
    733                 } catch (IndexOutOfBoundsException e) {
    734                     Slog.w(TAG, "Failed parsing " + e);
    735                 } finally {
    736                     if (!success) {
    737                         mUidOps.clear();
    738                     }
    739                     try {
    740                         stream.close();
    741                     } catch (IOException e) {
    742                     }
    743                 }
    744             }
    745         }
    746     }
    747 
    748     void readPackage(XmlPullParser parser) throws NumberFormatException,
    749             XmlPullParserException, IOException {
    750         String pkgName = parser.getAttributeValue(null, "n");
    751         int outerDepth = parser.getDepth();
    752         int type;
    753         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    754                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    755             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    756                 continue;
    757             }
    758 
    759             String tagName = parser.getName();
    760             if (tagName.equals("uid")) {
    761                 readUid(parser, pkgName);
    762             } else {
    763                 Slog.w(TAG, "Unknown element under <pkg>: "
    764                         + parser.getName());
    765                 XmlUtils.skipCurrentTag(parser);
    766             }
    767         }
    768     }
    769 
    770     void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException,
    771             XmlPullParserException, IOException {
    772         int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
    773         int outerDepth = parser.getDepth();
    774         int type;
    775         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    776                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    777             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    778                 continue;
    779             }
    780 
    781             String tagName = parser.getName();
    782             if (tagName.equals("op")) {
    783                 Op op = new Op(Integer.parseInt(parser.getAttributeValue(null, "n")));
    784                 String mode = parser.getAttributeValue(null, "m");
    785                 if (mode != null) {
    786                     op.mode = Integer.parseInt(mode);
    787                 }
    788                 String time = parser.getAttributeValue(null, "t");
    789                 if (time != null) {
    790                     op.time = Long.parseLong(time);
    791                 }
    792                 time = parser.getAttributeValue(null, "r");
    793                 if (time != null) {
    794                     op.rejectTime = Long.parseLong(time);
    795                 }
    796                 String dur = parser.getAttributeValue(null, "d");
    797                 if (dur != null) {
    798                     op.duration = Integer.parseInt(dur);
    799                 }
    800                 HashMap<String, Ops> pkgOps = mUidOps.get(uid);
    801                 if (pkgOps == null) {
    802                     pkgOps = new HashMap<String, Ops>();
    803                     mUidOps.put(uid, pkgOps);
    804                 }
    805                 Ops ops = pkgOps.get(pkgName);
    806                 if (ops == null) {
    807                     ops = new Ops(pkgName, uid);
    808                     pkgOps.put(pkgName, ops);
    809                 }
    810                 ops.put(op.op, op);
    811             } else {
    812                 Slog.w(TAG, "Unknown element under <pkg>: "
    813                         + parser.getName());
    814                 XmlUtils.skipCurrentTag(parser);
    815             }
    816         }
    817     }
    818 
    819     void writeState() {
    820         synchronized (mFile) {
    821             List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
    822 
    823             FileOutputStream stream;
    824             try {
    825                 stream = mFile.startWrite();
    826             } catch (IOException e) {
    827                 Slog.w(TAG, "Failed to write state: " + e);
    828                 return;
    829             }
    830 
    831             try {
    832                 XmlSerializer out = new FastXmlSerializer();
    833                 out.setOutput(stream, "utf-8");
    834                 out.startDocument(null, true);
    835                 out.startTag(null, "app-ops");
    836 
    837                 if (allOps != null) {
    838                     String lastPkg = null;
    839                     for (int i=0; i<allOps.size(); i++) {
    840                         AppOpsManager.PackageOps pkg = allOps.get(i);
    841                         if (!pkg.getPackageName().equals(lastPkg)) {
    842                             if (lastPkg != null) {
    843                                 out.endTag(null, "pkg");
    844                             }
    845                             lastPkg = pkg.getPackageName();
    846                             out.startTag(null, "pkg");
    847                             out.attribute(null, "n", lastPkg);
    848                         }
    849                         out.startTag(null, "uid");
    850                         out.attribute(null, "n", Integer.toString(pkg.getUid()));
    851                         List<AppOpsManager.OpEntry> ops = pkg.getOps();
    852                         for (int j=0; j<ops.size(); j++) {
    853                             AppOpsManager.OpEntry op = ops.get(j);
    854                             out.startTag(null, "op");
    855                             out.attribute(null, "n", Integer.toString(op.getOp()));
    856                             if (op.getMode() != AppOpsManager.MODE_ALLOWED) {
    857                                 out.attribute(null, "m", Integer.toString(op.getMode()));
    858                             }
    859                             long time = op.getTime();
    860                             if (time != 0) {
    861                                 out.attribute(null, "t", Long.toString(time));
    862                             }
    863                             time = op.getRejectTime();
    864                             if (time != 0) {
    865                                 out.attribute(null, "r", Long.toString(time));
    866                             }
    867                             int dur = op.getDuration();
    868                             if (dur != 0) {
    869                                 out.attribute(null, "d", Integer.toString(dur));
    870                             }
    871                             out.endTag(null, "op");
    872                         }
    873                         out.endTag(null, "uid");
    874                     }
    875                     if (lastPkg != null) {
    876                         out.endTag(null, "pkg");
    877                     }
    878                 }
    879 
    880                 out.endTag(null, "app-ops");
    881                 out.endDocument();
    882                 mFile.finishWrite(stream);
    883             } catch (IOException e) {
    884                 Slog.w(TAG, "Failed to write state, restoring backup.", e);
    885                 mFile.failWrite(stream);
    886             }
    887         }
    888     }
    889 
    890     @Override
    891     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    892         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
    893                 != PackageManager.PERMISSION_GRANTED) {
    894             pw.println("Permission Denial: can't dump ApOps service from from pid="
    895                     + Binder.getCallingPid()
    896                     + ", uid=" + Binder.getCallingUid());
    897             return;
    898         }
    899 
    900         synchronized (this) {
    901             pw.println("Current AppOps Service state:");
    902             final long now = System.currentTimeMillis();
    903             for (int i=0; i<mUidOps.size(); i++) {
    904                 pw.print("  Uid "); UserHandle.formatUid(pw, mUidOps.keyAt(i)); pw.println(":");
    905                 HashMap<String, Ops> pkgOps = mUidOps.valueAt(i);
    906                 for (Ops ops : pkgOps.values()) {
    907                     pw.print("    Package "); pw.print(ops.packageName); pw.println(":");
    908                     for (int j=0; j<ops.size(); j++) {
    909                         Op op = ops.valueAt(j);
    910                         pw.print("      "); pw.print(AppOpsManager.opToName(op.op));
    911                         pw.print(": mode="); pw.print(op.mode);
    912                         if (op.time != 0) {
    913                             pw.print("; time="); TimeUtils.formatDuration(now-op.time, pw);
    914                             pw.print(" ago");
    915                         }
    916                         if (op.rejectTime != 0) {
    917                             pw.print("; rejectTime="); TimeUtils.formatDuration(now-op.rejectTime, pw);
    918                             pw.print(" ago");
    919                         }
    920                         if (op.duration == -1) {
    921                             pw.println(" (running)");
    922                         } else {
    923                             pw.print("; duration=");
    924                                     TimeUtils.formatDuration(op.duration, pw);
    925                                     pw.println();
    926                         }
    927                     }
    928                 }
    929             }
    930         }
    931     }
    932 }
    933