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