StrictMode.java revision 46d42387464a651268648659e91d022566d4844c
1/*
2 * Copyright (C) 2010 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 */
16package android.os;
17
18import android.app.ActivityManagerNative;
19import android.app.ApplicationErrorReport;
20import android.util.Log;
21
22import com.android.internal.os.RuntimeInit;
23
24import dalvik.system.BlockGuard;
25
26import java.util.HashMap;
27
28/**
29 * <p>StrictMode lets you impose stricter rules under which your
30 * application runs.</p>
31 */
32public final class StrictMode {
33    private static final String TAG = "StrictMode";
34
35    // Only log a duplicate stack trace to the logs every second.
36    private static final long MIN_LOG_INTERVAL_MS = 1000;
37
38    // Only show an annoying dialog at most every 30 seconds
39    private static final long MIN_DIALOG_INTERVAL_MS = 30000;
40
41    private StrictMode() {}
42
43    public static final int DISALLOW_DISK_WRITE = 0x01;
44    public static final int DISALLOW_DISK_READ = 0x02;
45    public static final int DISALLOW_NETWORK = 0x04;
46
47    /** @hide */
48    public static final int DISALLOW_MASK =
49            DISALLOW_DISK_WRITE | DISALLOW_DISK_READ | DISALLOW_NETWORK;
50
51    /**
52     * Flag to log to the system log.
53     */
54    public static final int PENALTY_LOG = 0x10;  // normal android.util.Log
55
56    /**
57     * Show an annoying dialog to the user.  Will be rate-limited to be only
58     * a little annoying.
59     */
60    public static final int PENALTY_DIALOG = 0x20;
61
62    /**
63     * Crash hard if policy is violated.
64     */
65    public static final int PENALTY_DEATH = 0x40;
66
67    /**
68     * Log a stacktrace to the DropBox on policy violation.
69     */
70    public static final int PENALTY_DROPBOX = 0x80;
71
72    /** @hide */
73    public static final int PENALTY_MASK =
74            PENALTY_LOG | PENALTY_DIALOG |
75            PENALTY_DROPBOX | PENALTY_DEATH;
76
77    /**
78     * Sets the policy for what actions the current thread is denied,
79     * as well as the penalty for violating the policy.
80     *
81     * @param policyMask a bitmask of DISALLOW_* and PENALTY_* values.
82     */
83    public static void setThreadBlockingPolicy(final int policyMask) {
84        if (policyMask == 0) {
85            BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY);
86            return;
87        }
88        BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
89        if (!(policy instanceof AndroidBlockGuardPolicy)) {
90            BlockGuard.setThreadPolicy(new AndroidBlockGuardPolicy(policyMask));
91        } else {
92            AndroidBlockGuardPolicy androidPolicy = (AndroidBlockGuardPolicy) policy;
93            androidPolicy.setPolicyMask(policyMask);
94        }
95    }
96
97    /**
98     * Returns the bitmask of the current thread's blocking policy.
99     *
100     * @return the bitmask of all the DISALLOW_* and PENALTY_* bits currently enabled
101     */
102    public static int getThreadBlockingPolicy() {
103        return BlockGuard.getThreadPolicy().getPolicyMask();
104    }
105
106    private static class AndroidBlockGuardPolicy implements BlockGuard.Policy {
107        private int mPolicyMask;
108
109        // Map from violation stacktrace hashcode -> uptimeMillis of
110        // last violation.  No locking needed, as this is only
111        // accessed by the same thread.
112        private final HashMap<Integer, Long> mLastViolationTime = new HashMap<Integer, Long>();
113
114        public AndroidBlockGuardPolicy(final int policyMask) {
115            mPolicyMask = policyMask;
116        }
117
118        // Part of BlockGuard.Policy interface:
119        public int getPolicyMask() {
120            return mPolicyMask;
121        }
122
123        // Part of BlockGuard.Policy interface:
124        public void onWriteToDisk() {
125            if ((mPolicyMask & DISALLOW_DISK_WRITE) == 0) {
126                return;
127            }
128            handleViolation(DISALLOW_DISK_WRITE);
129        }
130
131        // Part of BlockGuard.Policy interface:
132        public void onReadFromDisk() {
133            if ((mPolicyMask & DISALLOW_DISK_READ) == 0) {
134                return;
135            }
136            handleViolation(DISALLOW_DISK_READ);
137        }
138
139        // Part of BlockGuard.Policy interface:
140        public void onNetwork() {
141            if ((mPolicyMask & DISALLOW_NETWORK) == 0) {
142                return;
143            }
144            handleViolation(DISALLOW_NETWORK);
145        }
146
147        public void setPolicyMask(int policyMask) {
148            mPolicyMask = policyMask;
149        }
150
151        private void handleViolation(int violationBit) {
152            final BlockGuard.BlockGuardPolicyException violation =
153                    new BlockGuard.BlockGuardPolicyException(mPolicyMask, violationBit);
154            violation.fillInStackTrace();
155
156            Looper looper = Looper.myLooper();
157            if (looper == null) {
158                // Without a Looper, we're unable to time how long the
159                // violation takes place.  This case should be rare,
160                // as most users will care about timing violations
161                // that happen on their main UI thread.
162                handleViolationWithTime(violation, -1L /* no time */);
163            } else {
164                MessageQueue queue = Looper.myQueue();
165                final long violationTime = SystemClock.uptimeMillis();
166                queue.addIdleHandler(new MessageQueue.IdleHandler() {
167                        public boolean queueIdle() {
168                            long afterViolationTime = SystemClock.uptimeMillis();
169                            handleViolationWithTime(violation, afterViolationTime - violationTime);
170                            return false;  // remove this idle handler from the array
171                        }
172                    });
173            }
174        }
175
176        private void handleViolationWithTime(
177            BlockGuard.BlockGuardPolicyException violation,
178            long durationMillis) {
179
180            // It's possible (even quite likely) that mPolicyMask has
181            // changed from the time the violation fired and now
182            // (after the violating code ran) due to people who
183            // push/pop temporary policy in regions of code.  So use
184            // the old policy here.
185            int policy = violation.getPolicy();
186
187            // Not _really_ a Crash, but we use the same data structure...
188            ApplicationErrorReport.CrashInfo crashInfo =
189                    new ApplicationErrorReport.CrashInfo(violation);
190
191            // Not perfect, but fast and good enough for dup suppression.
192            Integer crashFingerprint = crashInfo.stackTrace.hashCode();
193            long lastViolationTime = 0;
194            if (mLastViolationTime.containsKey(crashFingerprint)) {
195                lastViolationTime = mLastViolationTime.get(crashFingerprint);
196            }
197            long now = SystemClock.uptimeMillis();
198            mLastViolationTime.put(crashFingerprint, now);
199            long timeSinceLastViolationMillis = lastViolationTime == 0 ?
200                    Long.MAX_VALUE : (now - lastViolationTime);
201
202            if ((policy & PENALTY_LOG) != 0 &&
203                timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
204                if (durationMillis != -1) {
205                    Log.d(TAG, "StrictMode policy violation; ~duration=" + durationMillis + " ms",
206                          violation);
207                } else {
208                    Log.d(TAG, "StrictMode policy violation.", violation);
209                }
210            }
211
212            // The violationMask, passed to ActivityManager, is a
213            // subset of the original StrictMode policy bitmask, with
214            // only the bit violated and penalty bits to be executed
215            // by the ActivityManagerService remaining set.
216            int violationMask = 0;
217
218            if ((policy & PENALTY_DIALOG) != 0 &&
219                timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) {
220                violationMask |= PENALTY_DIALOG;
221            }
222
223            if ((policy & PENALTY_DROPBOX) != 0 && lastViolationTime == 0) {
224                violationMask |= PENALTY_DROPBOX;
225            }
226
227            if (violationMask != 0) {
228                violationMask |= violation.getPolicyViolation();
229                try {
230                    ActivityManagerNative.getDefault().handleApplicationStrictModeViolation(
231                        RuntimeInit.getApplicationObject(),
232                        violationMask,
233                        new ApplicationErrorReport.CrashInfo(violation));
234                } catch (RemoteException e) {
235                    Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
236                }
237            }
238
239            if ((policy & PENALTY_DEATH) != 0) {
240                System.err.println("StrictMode policy violation with POLICY_DEATH; shutting down.");
241                Process.killProcess(Process.myPid());
242                System.exit(10);
243            }
244        }
245    }
246}
247