/* * 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; /** *

StrictMode lets you impose stricter rules under which your * application runs.

*/ 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 mLastViolationTime = new HashMap(); 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); } } } }