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