blob: 9b18719670fbb2a8d8091b593eadae2870b029c7 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.app.ActivityManagerNative;
import android.app.ApplicationErrorReport;
import android.util.Log;
import com.android.internal.os.RuntimeInit;
import dalvik.system.BlockGuard;
import java.util.HashMap;
/**
* <p>StrictMode lets you impose stricter rules under which your
* application runs.</p>
*/
public final class StrictMode {
private static final String TAG = "StrictMode";
// Only log a duplicate stack trace to the logs every second.
private static final long MIN_LOG_INTERVAL_MS = 1000;
// Only show an annoying dialog at most every 30 seconds
private static final long MIN_DIALOG_INTERVAL_MS = 30000;
private StrictMode() {}
public static final int DISALLOW_DISK_WRITE = 0x01;
public static final int DISALLOW_DISK_READ = 0x02;
public static final int DISALLOW_NETWORK = 0x04;
/** @hide */
public static final int DISALLOW_MASK =
DISALLOW_DISK_WRITE | DISALLOW_DISK_READ | DISALLOW_NETWORK;
/**
* Flag to log to the system log.
*/
public static final int PENALTY_LOG = 0x10; // normal android.util.Log
/**
* Show an annoying dialog to the user. Will be rate-limited to be only
* a little annoying.
*/
public static final int PENALTY_DIALOG = 0x20;
/**
* Crash hard if policy is violated.
*/
public static final int PENALTY_DEATH = 0x40;
/**
* Log a stacktrace to the DropBox on policy violation.
*/
public static final int PENALTY_DROPBOX = 0x80;
/** @hide */
public static final int PENALTY_MASK =
PENALTY_LOG | PENALTY_DIALOG |
PENALTY_DROPBOX | PENALTY_DEATH;
/**
* Sets the policy for what actions the current thread is denied,
* as well as the penalty for violating the policy.
*
* @param policyMask a bitmask of DISALLOW_* and PENALTY_* values.
*/
public static void setThreadBlockingPolicy(final int policyMask) {
if (policyMask == 0) {
BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY);
return;
}
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
if (!(policy instanceof AndroidBlockGuardPolicy)) {
BlockGuard.setThreadPolicy(new AndroidBlockGuardPolicy(policyMask));
} else {
AndroidBlockGuardPolicy androidPolicy = (AndroidBlockGuardPolicy) policy;
androidPolicy.setPolicyMask(policyMask);
}
}
/**
* Returns the bitmask of the current thread's blocking policy.
*
* @return the bitmask of all the DISALLOW_* and PENALTY_* bits currently enabled
*/
public static int getThreadBlockingPolicy() {
return BlockGuard.getThreadPolicy().getPolicyMask();
}
private static class AndroidBlockGuardPolicy implements BlockGuard.Policy {
private int mPolicyMask;
// Map from violation stacktrace hashcode -> uptimeMillis of
// last violation. No locking needed, as this is only
// accessed by the same thread.
private final HashMap<Integer, Long> mLastViolationTime = new HashMap<Integer, Long>();
public AndroidBlockGuardPolicy(final int policyMask) {
mPolicyMask = policyMask;
}
// Part of BlockGuard.Policy interface:
public int getPolicyMask() {
return mPolicyMask;
}
// Part of BlockGuard.Policy interface:
public void onWriteToDisk() {
if ((mPolicyMask & DISALLOW_DISK_WRITE) == 0) {
return;
}
handleViolation(DISALLOW_DISK_WRITE);
}
// Part of BlockGuard.Policy interface:
public void onReadFromDisk() {
if ((mPolicyMask & DISALLOW_DISK_READ) == 0) {
return;
}
handleViolation(DISALLOW_DISK_READ);
}
// Part of BlockGuard.Policy interface:
public void onNetwork() {
if ((mPolicyMask & DISALLOW_NETWORK) == 0) {
return;
}
handleViolation(DISALLOW_NETWORK);
}
public void setPolicyMask(int policyMask) {
mPolicyMask = policyMask;
}
private void handleViolation(int violationBit) {
final BlockGuard.BlockGuardPolicyException violation =
new BlockGuard.BlockGuardPolicyException(mPolicyMask, violationBit);
violation.fillInStackTrace();
Looper looper = Looper.myLooper();
if (looper == null) {
// Without a Looper, we're unable to time how long the
// violation takes place. This case should be rare,
// as most users will care about timing violations
// that happen on their main UI thread.
handleViolationWithTime(violation, -1L /* no time */);
} else {
MessageQueue queue = Looper.myQueue();
final long violationTime = SystemClock.uptimeMillis();
queue.addIdleHandler(new MessageQueue.IdleHandler() {
public boolean queueIdle() {
long afterViolationTime = SystemClock.uptimeMillis();
handleViolationWithTime(violation, afterViolationTime - violationTime);
return false; // remove this idle handler from the array
}
});
}
}
private void handleViolationWithTime(
BlockGuard.BlockGuardPolicyException violation,
long durationMillis) {
// It's possible (even quite likely) that mPolicyMask has
// changed from the time the violation fired and now
// (after the violating code ran) due to people who
// push/pop temporary policy in regions of code. So use
// the old policy here.
int policy = violation.getPolicy();
// Not _really_ a Crash, but we use the same data structure...
ApplicationErrorReport.CrashInfo crashInfo =
new ApplicationErrorReport.CrashInfo(violation);
crashInfo.durationMillis = durationMillis;
// Not perfect, but fast and good enough for dup suppression.
Integer crashFingerprint = crashInfo.stackTrace.hashCode();
long lastViolationTime = 0;
if (mLastViolationTime.containsKey(crashFingerprint)) {
lastViolationTime = mLastViolationTime.get(crashFingerprint);
}
long now = SystemClock.uptimeMillis();
mLastViolationTime.put(crashFingerprint, now);
long timeSinceLastViolationMillis = lastViolationTime == 0 ?
Long.MAX_VALUE : (now - lastViolationTime);
if ((policy & PENALTY_LOG) != 0 &&
timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
if (durationMillis != -1) {
Log.d(TAG, "StrictMode policy violation; ~duration=" + durationMillis + " ms",
violation);
} else {
Log.d(TAG, "StrictMode policy violation.", violation);
}
}
// The violationMask, passed to ActivityManager, is a
// subset of the original StrictMode policy bitmask, with
// only the bit violated and penalty bits to be executed
// by the ActivityManagerService remaining set.
int violationMask = 0;
if ((policy & PENALTY_DIALOG) != 0 &&
timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) {
violationMask |= PENALTY_DIALOG;
}
if ((policy & PENALTY_DROPBOX) != 0 && lastViolationTime == 0) {
violationMask |= PENALTY_DROPBOX;
}
if (violationMask != 0) {
violationMask |= violation.getPolicyViolation();
try {
ActivityManagerNative.getDefault().handleApplicationStrictModeViolation(
RuntimeInit.getApplicationObject(),
violationMask,
new ApplicationErrorReport.CrashInfo(violation));
} catch (RemoteException e) {
Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
}
}
if ((policy & PENALTY_DEATH) != 0) {
System.err.println("StrictMode policy violation with POLICY_DEATH; shutting down.");
Process.killProcess(Process.myPid());
System.exit(10);
}
}
}
}