/* * 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.animation.ValueAnimator; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.IActivityManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.net.TrafficStats; import android.net.Uri; import android.os.storage.IStorageManager; import android.os.strictmode.CleartextNetworkViolation; import android.os.strictmode.ContentUriWithoutPermissionViolation; import android.os.strictmode.CredentialProtectedWhileLockedViolation; import android.os.strictmode.CustomViolation; import android.os.strictmode.DiskReadViolation; import android.os.strictmode.DiskWriteViolation; import android.os.strictmode.ExplicitGcViolation; import android.os.strictmode.FileUriExposedViolation; import android.os.strictmode.ImplicitDirectBootViolation; import android.os.strictmode.IncorrectContextUseViolation; import android.os.strictmode.InstanceCountViolation; import android.os.strictmode.IntentReceiverLeakedViolation; import android.os.strictmode.LeakedClosableViolation; import android.os.strictmode.NetworkViolation; import android.os.strictmode.NonSdkApiUsedViolation; import android.os.strictmode.ResourceMismatchViolation; import android.os.strictmode.ServiceConnectionLeakedViolation; import android.os.strictmode.SqliteObjectLeakedViolation; import android.os.strictmode.UnbufferedIoViolation; import android.os.strictmode.UntaggedSocketViolation; import android.os.strictmode.Violation; import android.os.strictmode.WebViewMethodCalledOnWrongThreadViolation; import android.util.ArrayMap; import android.util.Log; import android.util.Printer; import android.util.Singleton; import android.util.Slog; import android.view.IWindowManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; import com.android.internal.os.RuntimeInit; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.HexDump; import dalvik.system.BlockGuard; import dalvik.system.CloseGuard; import dalvik.system.VMDebug; import dalvik.system.VMRuntime; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; /** * StrictMode is a developer tool which detects things you might be doing by accident and brings * them to your attention so you can fix them. * *

StrictMode is most commonly used to catch accidental disk or network access on the * application's main thread, where UI operations are received and animations take place. Keeping * disk and network operations off the main thread makes for much smoother, more responsive * applications. By keeping your application's main thread responsive, you also prevent ANR dialogs from being shown to * users. * *

Note that even though an Android device's disk is often on flash memory, many * devices run a filesystem on top of that memory with very limited concurrency. It's often the case * that almost all disk accesses are fast, but may in individual cases be dramatically slower when * certain I/O is happening in the background from other processes. If possible, it's best to assume * that such things are not fast. * *

Example code to enable from early in your {@link android.app.Application}, {@link * android.app.Activity}, or other application component's {@link android.app.Application#onCreate} * method: * *

 * public void onCreate() {
 *     if (DEVELOPER_MODE) {
 *         StrictMode.setThreadPolicy(new {@link ThreadPolicy.Builder StrictMode.ThreadPolicy.Builder}()
 *                 .detectDiskReads()
 *                 .detectDiskWrites()
 *                 .detectNetwork()   // or .detectAll() for all detectable problems
 *                 .penaltyLog()
 *                 .build());
 *         StrictMode.setVmPolicy(new {@link VmPolicy.Builder StrictMode.VmPolicy.Builder}()
 *                 .detectLeakedSqlLiteObjects()
 *                 .detectLeakedClosableObjects()
 *                 .penaltyLog()
 *                 .penaltyDeath()
 *                 .build());
 *     }
 *     super.onCreate();
 * }
 * 
* *

You can decide what should happen when a violation is detected. For example, using {@link * ThreadPolicy.Builder#penaltyLog} you can watch the output of adb logcat while you * use your application to see the violations as they happen. * *

If you find violations that you feel are problematic, there are a variety of tools to help * solve them: threads, {@link android.os.Handler}, {@link android.os.AsyncTask}, {@link * android.app.IntentService}, etc. But don't feel compelled to fix everything that StrictMode * finds. In particular, many cases of disk access are often necessary during the normal activity * lifecycle. Use StrictMode to find things you did by accident. Network requests on the UI thread * are almost always a problem, though. * *

StrictMode is not a security mechanism and is not guaranteed to find all disk or * network accesses. While it does propagate its state across process boundaries when doing {@link * android.os.Binder} calls, it's still ultimately a best effort mechanism. Notably, disk or network * access from JNI calls won't necessarily trigger it. Future versions of Android may catch more (or * fewer) operations, so you should never leave StrictMode enabled in applications distributed on * Google Play. */ public final class StrictMode { private static final String TAG = "StrictMode"; private static final boolean LOG_V = Log.isLoggable(TAG, Log.VERBOSE); /** * Boolean system property to disable strict mode checks outright. Set this to 'true' to force * disable; 'false' has no effect on other enable/disable policy. * * @hide */ public static final String DISABLE_PROPERTY = "persist.sys.strictmode.disable"; /** * The boolean system property to control screen flashes on violations. * * @hide */ public static final String VISUAL_PROPERTY = "persist.sys.strictmode.visual"; /** * Temporary property used to include {@link #DETECT_VM_CLEARTEXT_NETWORK} in {@link * VmPolicy.Builder#detectAll()}. Apps can still always opt-into detection using {@link * VmPolicy.Builder#detectCleartextNetwork()}. */ private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear"; /** * Quick feature-flag that can be used to disable the defaults provided by {@link * #initThreadDefaults(ApplicationInfo)} and {@link #initVmDefaults(ApplicationInfo)}. */ private static final boolean DISABLE = false; // Only apply VM penalties for the same violation at this interval. private static final long MIN_VM_INTERVAL_MS = 1000; // 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; // Only log a dropbox entry at most every 30 seconds private static final long MIN_DROPBOX_INTERVAL_MS = 3000; // How many Span tags (e.g. animations) to report. private static final int MAX_SPAN_TAGS = 20; // How many offending stacks to keep track of (and time) per loop // of the Looper. private static final int MAX_OFFENSES_PER_LOOP = 10; /** @hide */ @IntDef(flag = true, prefix = { "DETECT_THREAD_", "PENALTY_" }, value = { DETECT_THREAD_DISK_WRITE, DETECT_THREAD_DISK_READ, DETECT_THREAD_NETWORK, DETECT_THREAD_CUSTOM, DETECT_THREAD_RESOURCE_MISMATCH, DETECT_THREAD_UNBUFFERED_IO, DETECT_THREAD_EXPLICIT_GC, PENALTY_GATHER, PENALTY_LOG, PENALTY_DIALOG, PENALTY_DEATH, PENALTY_FLASH, PENALTY_DROPBOX, PENALTY_DEATH_ON_NETWORK, PENALTY_DEATH_ON_CLEARTEXT_NETWORK, PENALTY_DEATH_ON_FILE_URI_EXPOSURE, }) @Retention(RetentionPolicy.SOURCE) public @interface ThreadPolicyMask {} // Thread policy: bits 0-15 /** @hide */ private static final int DETECT_THREAD_DISK_WRITE = 1 << 0; /** @hide */ private static final int DETECT_THREAD_DISK_READ = 1 << 1; /** @hide */ private static final int DETECT_THREAD_NETWORK = 1 << 2; /** @hide */ private static final int DETECT_THREAD_CUSTOM = 1 << 3; /** @hide */ private static final int DETECT_THREAD_RESOURCE_MISMATCH = 1 << 4; /** @hide */ private static final int DETECT_THREAD_UNBUFFERED_IO = 1 << 5; /** @hide */ private static final int DETECT_THREAD_EXPLICIT_GC = 1 << 6; /** @hide */ private static final int DETECT_THREAD_ALL = 0x0000ffff; /** @hide */ @IntDef(flag = true, prefix = { "DETECT_THREAD_", "PENALTY_" }, value = { DETECT_VM_CURSOR_LEAKS, DETECT_VM_CLOSABLE_LEAKS, DETECT_VM_ACTIVITY_LEAKS, DETECT_VM_INSTANCE_LEAKS, DETECT_VM_REGISTRATION_LEAKS, DETECT_VM_FILE_URI_EXPOSURE, DETECT_VM_CLEARTEXT_NETWORK, DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION, DETECT_VM_UNTAGGED_SOCKET, DETECT_VM_NON_SDK_API_USAGE, DETECT_VM_IMPLICIT_DIRECT_BOOT, DETECT_VM_INCORRECT_CONTEXT_USE, PENALTY_GATHER, PENALTY_LOG, PENALTY_DIALOG, PENALTY_DEATH, PENALTY_FLASH, PENALTY_DROPBOX, PENALTY_DEATH_ON_NETWORK, PENALTY_DEATH_ON_CLEARTEXT_NETWORK, PENALTY_DEATH_ON_FILE_URI_EXPOSURE, }) @Retention(RetentionPolicy.SOURCE) public @interface VmPolicyMask {} // VM policy: bits 0-15 /** @hide */ private static final int DETECT_VM_CURSOR_LEAKS = 1 << 0; /** @hide */ private static final int DETECT_VM_CLOSABLE_LEAKS = 1 << 1; /** @hide */ private static final int DETECT_VM_ACTIVITY_LEAKS = 1 << 2; /** @hide */ private static final int DETECT_VM_INSTANCE_LEAKS = 1 << 3; /** @hide */ private static final int DETECT_VM_REGISTRATION_LEAKS = 1 << 4; /** @hide */ private static final int DETECT_VM_FILE_URI_EXPOSURE = 1 << 5; /** @hide */ private static final int DETECT_VM_CLEARTEXT_NETWORK = 1 << 6; /** @hide */ private static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 1 << 7; /** @hide */ private static final int DETECT_VM_UNTAGGED_SOCKET = 1 << 8; /** @hide */ private static final int DETECT_VM_NON_SDK_API_USAGE = 1 << 9; /** @hide */ private static final int DETECT_VM_IMPLICIT_DIRECT_BOOT = 1 << 10; /** @hide */ private static final int DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED = 1 << 11; /** @hide */ private static final int DETECT_VM_INCORRECT_CONTEXT_USE = 1 << 12; /** @hide */ private static final int DETECT_VM_ALL = 0x0000ffff; // Penalty policy: bits 16-31 /** * Non-public penalty mode which overrides all the other penalty bits and signals that we're in * a Binder call and we should ignore the other penalty bits and instead serialize back all our * offending stack traces to the caller to ultimately handle in the originating process. * *

This must be kept in sync with the constant in libs/binder/Parcel.cpp * * @hide */ public static final int PENALTY_GATHER = 1 << 31; /** {@hide} */ public static final int PENALTY_LOG = 1 << 30; /** {@hide} */ public static final int PENALTY_DIALOG = 1 << 29; /** {@hide} */ public static final int PENALTY_DEATH = 1 << 28; /** {@hide} */ public static final int PENALTY_FLASH = 1 << 27; /** {@hide} */ public static final int PENALTY_DROPBOX = 1 << 26; /** {@hide} */ public static final int PENALTY_DEATH_ON_NETWORK = 1 << 25; /** {@hide} */ public static final int PENALTY_DEATH_ON_CLEARTEXT_NETWORK = 1 << 24; /** {@hide} */ public static final int PENALTY_DEATH_ON_FILE_URI_EXPOSURE = 1 << 23; /** @hide */ public static final int PENALTY_ALL = 0xffff0000; /** {@hide} */ public static final int NETWORK_POLICY_ACCEPT = 0; /** {@hide} */ public static final int NETWORK_POLICY_LOG = 1; /** {@hide} */ public static final int NETWORK_POLICY_REJECT = 2; // TODO: wrap in some ImmutableHashMap thing. // Note: must be before static initialization of sVmPolicy. private static final HashMap EMPTY_CLASS_LIMIT_MAP = new HashMap(); /** The current VmPolicy in effect. */ private static volatile VmPolicy sVmPolicy = VmPolicy.LAX; /** {@hide} */ @TestApi public interface ViolationLogger { /** Called when penaltyLog is enabled and a violation needs logging. */ void log(ViolationInfo info); } private static final ViolationLogger LOGCAT_LOGGER = info -> { String msg; if (info.durationMillis != -1) { msg = "StrictMode policy violation; ~duration=" + info.durationMillis + " ms:"; } else { msg = "StrictMode policy violation:"; } Log.d(TAG, msg + " " + info.getStackTrace()); }; private static volatile ViolationLogger sLogger = LOGCAT_LOGGER; private static final ThreadLocal sThreadViolationListener = new ThreadLocal<>(); private static final ThreadLocal sThreadViolationExecutor = new ThreadLocal<>(); /** * When #{@link ThreadPolicy.Builder#penaltyListener} is enabled, the listener is called on the * provided executor when a Thread violation occurs. */ public interface OnThreadViolationListener { /** Called on a thread policy violation. */ void onThreadViolation(Violation v); } /** * When #{@link VmPolicy.Builder#penaltyListener} is enabled, the listener is called on the * provided executor when a VM violation occurs. */ public interface OnVmViolationListener { /** Called on a VM policy violation. */ void onVmViolation(Violation v); } /** {@hide} */ @TestApi public static void setViolationLogger(ViolationLogger listener) { if (listener == null) { listener = LOGCAT_LOGGER; } sLogger = listener; } /** * The number of threads trying to do an async dropbox write. Just to limit ourselves out of * paranoia. */ private static final AtomicInteger sDropboxCallsInFlight = new AtomicInteger(0); /** * Callback supplied to dalvik / libcore to get informed of usages of java API that are not * a part of the public SDK. */ private static final Consumer sNonSdkApiUsageConsumer = message -> onVmPolicyViolation(new NonSdkApiUsedViolation(message)); private StrictMode() {} /** * {@link StrictMode} policy applied to a certain thread. * *

The policy is enabled by {@link #setThreadPolicy}. The current policy can be retrieved * with {@link #getThreadPolicy}. * *

Note that multiple penalties may be provided and they're run in order from least to most * severe (logging before process death, for example). There's currently no mechanism to choose * different penalties for different detected actions. */ public static final class ThreadPolicy { /** The default, lax policy which doesn't catch anything. */ public static final ThreadPolicy LAX = new ThreadPolicy(0, null, null); @UnsupportedAppUsage final @ThreadPolicyMask int mask; final OnThreadViolationListener mListener; final Executor mCallbackExecutor; private ThreadPolicy(@ThreadPolicyMask int mask, OnThreadViolationListener listener, Executor executor) { this.mask = mask; mListener = listener; mCallbackExecutor = executor; } @Override public String toString() { return "[StrictMode.ThreadPolicy; mask=" + mask + "]"; } /** * Creates {@link ThreadPolicy} instances. Methods whose names start with {@code detect} * specify what problems we should look for. Methods whose names start with {@code penalty} * specify what we should do when we detect a problem. * *

You can call as many {@code detect} and {@code penalty} methods as you like. Currently * order is insignificant: all penalties apply to all detected problems. * *

For example, detect everything and log anything that's found: * *

         * StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
         *     .detectAll()
         *     .penaltyLog()
         *     .build();
         * StrictMode.setThreadPolicy(policy);
         * 
*/ public static final class Builder { private @ThreadPolicyMask int mMask = 0; private OnThreadViolationListener mListener; private Executor mExecutor; /** * Create a Builder that detects nothing and has no violations. (but note that {@link * #build} will default to enabling {@link #penaltyLog} if no other penalties are * specified) */ public Builder() { mMask = 0; } /** Initialize a Builder from an existing ThreadPolicy. */ public Builder(ThreadPolicy policy) { mMask = policy.mask; mListener = policy.mListener; mExecutor = policy.mCallbackExecutor; } /** * Detect everything that's potentially suspect. * *

As of the Gingerbread release this includes network and disk operations but will * likely expand in future releases. */ public @NonNull Builder detectAll() { detectDiskReads(); detectDiskWrites(); detectNetwork(); final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion(); if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) { detectCustomSlowCalls(); } if (targetSdk >= Build.VERSION_CODES.M) { detectResourceMismatches(); } if (targetSdk >= Build.VERSION_CODES.O) { detectUnbufferedIo(); } return this; } /** Disable the detection of everything. */ public @NonNull Builder permitAll() { return disable(DETECT_THREAD_ALL); } /** Enable detection of network operations. */ public @NonNull Builder detectNetwork() { return enable(DETECT_THREAD_NETWORK); } /** Disable detection of network operations. */ public @NonNull Builder permitNetwork() { return disable(DETECT_THREAD_NETWORK); } /** Enable detection of disk reads. */ public @NonNull Builder detectDiskReads() { return enable(DETECT_THREAD_DISK_READ); } /** Disable detection of disk reads. */ public @NonNull Builder permitDiskReads() { return disable(DETECT_THREAD_DISK_READ); } /** Enable detection of slow calls. */ public @NonNull Builder detectCustomSlowCalls() { return enable(DETECT_THREAD_CUSTOM); } /** Disable detection of slow calls. */ public @NonNull Builder permitCustomSlowCalls() { return disable(DETECT_THREAD_CUSTOM); } /** Disable detection of mismatches between defined resource types and getter calls. */ public @NonNull Builder permitResourceMismatches() { return disable(DETECT_THREAD_RESOURCE_MISMATCH); } /** Detect unbuffered input/output operations. */ public @NonNull Builder detectUnbufferedIo() { return enable(DETECT_THREAD_UNBUFFERED_IO); } /** Disable detection of unbuffered input/output operations. */ public @NonNull Builder permitUnbufferedIo() { return disable(DETECT_THREAD_UNBUFFERED_IO); } /** * Enables detection of mismatches between defined resource types and getter calls. * *

This helps detect accidental type mismatches and potentially expensive type * conversions when obtaining typed resources. * *

For example, a strict mode violation would be thrown when calling {@link * android.content.res.TypedArray#getInt(int, int)} on an index that contains a * String-type resource. If the string value can be parsed as an integer, this method * call will return a value without crashing; however, the developer should format the * resource as an integer to avoid unnecessary type conversion. */ public @NonNull Builder detectResourceMismatches() { return enable(DETECT_THREAD_RESOURCE_MISMATCH); } /** Enable detection of disk writes. */ public @NonNull Builder detectDiskWrites() { return enable(DETECT_THREAD_DISK_WRITE); } /** Disable detection of disk writes. */ public @NonNull Builder permitDiskWrites() { return disable(DETECT_THREAD_DISK_WRITE); } /** * Detect explicit GC requests, i.e. calls to Runtime.gc(). * * @hide */ @TestApi public @NonNull Builder detectExplicitGc() { // TODO(b/3400644): Un-hide this for next API update // TODO(b/3400644): Un-hide ExplicitGcViolation for next API update // TODO(b/3400644): Make DETECT_EXPLICIT_GC a @TestApi for next API update // TODO(b/3400644): Call this from detectAll in next API update return enable(DETECT_THREAD_EXPLICIT_GC); } /** * Disable detection of explicit GC requests, i.e. calls to Runtime.gc(). * * @hide */ public @NonNull Builder permitExplicitGc() { // TODO(b/3400644): Un-hide this for next API update return disable(DETECT_THREAD_EXPLICIT_GC); } /** * Show an annoying dialog to the developer on detected violations, rate-limited to be * only a little annoying. */ public @NonNull Builder penaltyDialog() { return enable(PENALTY_DIALOG); } /** * Crash the whole process on violation. This penalty runs at the end of all enabled * penalties so you'll still get see logging or other violations before the process * dies. * *

Unlike {@link #penaltyDeathOnNetwork}, this applies to disk reads, disk writes, * and network usage if their corresponding detect flags are set. */ public @NonNull Builder penaltyDeath() { return enable(PENALTY_DEATH); } /** * Crash the whole process on any network usage. Unlike {@link #penaltyDeath}, this * penalty runs before anything else. You must still have called {@link * #detectNetwork} to enable this. * *

In the Honeycomb or later SDKs, this is on by default. */ public @NonNull Builder penaltyDeathOnNetwork() { return enable(PENALTY_DEATH_ON_NETWORK); } /** Flash the screen during a violation. */ public @NonNull Builder penaltyFlashScreen() { return enable(PENALTY_FLASH); } /** Log detected violations to the system log. */ public @NonNull Builder penaltyLog() { return enable(PENALTY_LOG); } /** * Enable detected violations log a stacktrace and timing data to the {@link * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform * integrators doing beta user field data collection. */ public @NonNull Builder penaltyDropBox() { return enable(PENALTY_DROPBOX); } /** * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified * executor every violation. */ public @NonNull Builder penaltyListener( @NonNull Executor executor, @NonNull OnThreadViolationListener listener) { if (executor == null) { throw new NullPointerException("executor must not be null"); } mListener = listener; mExecutor = executor; return this; } /** @removed */ public @NonNull Builder penaltyListener( @NonNull OnThreadViolationListener listener, @NonNull Executor executor) { return penaltyListener(executor, listener); } private Builder enable(@ThreadPolicyMask int mask) { mMask |= mask; return this; } private Builder disable(@ThreadPolicyMask int mask) { mMask &= ~mask; return this; } /** * Construct the ThreadPolicy instance. * *

Note: if no penalties are enabled before calling build, {@link * #penaltyLog} is implicitly set. */ public ThreadPolicy build() { // If there are detection bits set but no violation bits // set, enable simple logging. if (mListener == null && mMask != 0 && (mMask & (PENALTY_DEATH | PENALTY_LOG | PENALTY_DROPBOX | PENALTY_DIALOG)) == 0) { penaltyLog(); } return new ThreadPolicy(mMask, mListener, mExecutor); } } } /** * {@link StrictMode} policy applied to all threads in the virtual machine's process. * *

The policy is enabled by {@link #setVmPolicy}. */ public static final class VmPolicy { /** The default, lax policy which doesn't catch anything. */ public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP, null, null); @UnsupportedAppUsage final @VmPolicyMask int mask; final OnVmViolationListener mListener; final Executor mCallbackExecutor; // Map from class to max number of allowed instances in memory. final HashMap classInstanceLimit; private VmPolicy( @VmPolicyMask int mask, HashMap classInstanceLimit, OnVmViolationListener listener, Executor executor) { if (classInstanceLimit == null) { throw new NullPointerException("classInstanceLimit == null"); } this.mask = mask; this.classInstanceLimit = classInstanceLimit; mListener = listener; mCallbackExecutor = executor; } @Override public String toString() { return "[StrictMode.VmPolicy; mask=" + mask + "]"; } /** * Creates {@link VmPolicy} instances. Methods whose names start with {@code detect} specify * what problems we should look for. Methods whose names start with {@code penalty} specify * what we should do when we detect a problem. * *

You can call as many {@code detect} and {@code penalty} methods as you like. Currently * order is insignificant: all penalties apply to all detected problems. * *

For example, detect everything and log anything that's found: * *

         * StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
         *     .detectAll()
         *     .penaltyLog()
         *     .build();
         * StrictMode.setVmPolicy(policy);
         * 
*/ public static final class Builder { @UnsupportedAppUsage private @VmPolicyMask int mMask; private OnVmViolationListener mListener; private Executor mExecutor; private HashMap mClassInstanceLimit; // null until needed private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write public Builder() { mMask = 0; } /** Build upon an existing VmPolicy. */ public Builder(VmPolicy base) { mMask = base.mask; mClassInstanceLimitNeedCow = true; mClassInstanceLimit = base.classInstanceLimit; mListener = base.mListener; mExecutor = base.mCallbackExecutor; } /** * Set an upper bound on how many instances of a class can be in memory at once. Helps * to prevent object leaks. */ public @NonNull Builder setClassInstanceLimit(Class klass, int instanceLimit) { if (klass == null) { throw new NullPointerException("klass == null"); } if (mClassInstanceLimitNeedCow) { if (mClassInstanceLimit.containsKey(klass) && mClassInstanceLimit.get(klass) == instanceLimit) { // no-op; don't break COW return this; } mClassInstanceLimitNeedCow = false; mClassInstanceLimit = (HashMap) mClassInstanceLimit.clone(); } else if (mClassInstanceLimit == null) { mClassInstanceLimit = new HashMap(); } mMask |= DETECT_VM_INSTANCE_LEAKS; mClassInstanceLimit.put(klass, instanceLimit); return this; } /** Detect leaks of {@link android.app.Activity} subclasses. */ public @NonNull Builder detectActivityLeaks() { return enable(DETECT_VM_ACTIVITY_LEAKS); } /** @hide */ public @NonNull Builder permitActivityLeaks() { synchronized (StrictMode.class) { sExpectedActivityInstanceCount.clear(); } return disable(DETECT_VM_ACTIVITY_LEAKS); } /** * Detect reflective usage of APIs that are not part of the public Android SDK. * *

Note that any non-SDK APIs that this processes accesses before this detection is * enabled may not be detected. To ensure that all such API accesses are detected, * you should apply this policy as early as possible after process creation. */ public @NonNull Builder detectNonSdkApiUsage() { return enable(DETECT_VM_NON_SDK_API_USAGE); } /** * Permit reflective usage of APIs that are not part of the public Android SDK. Note * that this only affects {@code StrictMode}, the underlying runtime may * continue to restrict or warn on access to methods that are not part of the * public SDK. */ public @NonNull Builder permitNonSdkApiUsage() { return disable(DETECT_VM_NON_SDK_API_USAGE); } /** * Detect everything that's potentially suspect. * *

In the Honeycomb release this includes leaks of SQLite cursors, Activities, and * other closable objects but will likely expand in future releases. */ public @NonNull Builder detectAll() { detectLeakedSqlLiteObjects(); final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion(); if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) { detectActivityLeaks(); detectLeakedClosableObjects(); } if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) { detectLeakedRegistrationObjects(); } if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN_MR2) { detectFileUriExposure(); } if (targetSdk >= Build.VERSION_CODES.M) { // TODO: always add DETECT_VM_CLEARTEXT_NETWORK once we have // facility for apps to mark sockets that should be ignored if (SystemProperties.getBoolean(CLEARTEXT_PROPERTY, false)) { detectCleartextNetwork(); } } if (targetSdk >= Build.VERSION_CODES.O) { detectContentUriWithoutPermission(); detectUntaggedSockets(); } if (targetSdk >= Build.VERSION_CODES.Q) { detectCredentialProtectedWhileLocked(); } if (targetSdk >= Build.VERSION_CODES.R) { detectIncorrectContextUse(); } // TODO: Decide whether to detect non SDK API usage beyond a certain API level. // TODO: enable detectImplicitDirectBoot() once system is less noisy return this; } /** * Detect when an {@link android.database.sqlite.SQLiteCursor} or other SQLite object is * finalized without having been closed. * *

You always want to explicitly close your SQLite cursors to avoid unnecessary * database contention and temporary memory leaks. */ public @NonNull Builder detectLeakedSqlLiteObjects() { return enable(DETECT_VM_CURSOR_LEAKS); } /** * Detect when an {@link java.io.Closeable} or other object with an explicit termination * method is finalized without having been closed. * *

You always want to explicitly close such objects to avoid unnecessary resources * leaks. */ public @NonNull Builder detectLeakedClosableObjects() { return enable(DETECT_VM_CLOSABLE_LEAKS); } /** * Detect when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked during * {@link Context} teardown. */ public @NonNull Builder detectLeakedRegistrationObjects() { return enable(DETECT_VM_REGISTRATION_LEAKS); } /** * Detect when the calling application exposes a {@code file://} {@link android.net.Uri} * to another app. * *

This exposure is discouraged since the receiving app may not have access to the * shared path. For example, the receiving app may not have requested the {@link * android.Manifest.permission#READ_EXTERNAL_STORAGE} runtime permission, or the * platform may be sharing the {@link android.net.Uri} across user profile boundaries. * *

Instead, apps should use {@code content://} Uris so the platform can extend * temporary permission for the receiving app to access the resource. * * @see android.support.v4.content.FileProvider * @see Intent#FLAG_GRANT_READ_URI_PERMISSION */ public @NonNull Builder detectFileUriExposure() { return enable(DETECT_VM_FILE_URI_EXPOSURE); } /** * Detect any network traffic from the calling app which is not wrapped in SSL/TLS. This * can help you detect places that your app is inadvertently sending cleartext data * across the network. * *

Using {@link #penaltyDeath()} or {@link #penaltyDeathOnCleartextNetwork()} will * block further traffic on that socket to prevent accidental data leakage, in addition * to crashing your process. * *

Using {@link #penaltyDropBox()} will log the raw contents of the packet that * triggered the violation. * *

This inspects both IPv4/IPv6 and TCP/UDP network traffic, but it may be subject to * false positives, such as when STARTTLS protocols or HTTP proxies are used. */ public @NonNull Builder detectCleartextNetwork() { return enable(DETECT_VM_CLEARTEXT_NETWORK); } /** * Detect when the calling application sends a {@code content://} {@link * android.net.Uri} to another app without setting {@link * Intent#FLAG_GRANT_READ_URI_PERMISSION} or {@link * Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. * *

Forgetting to include one or more of these flags when sending an intent is * typically an app bug. * * @see Intent#FLAG_GRANT_READ_URI_PERMISSION * @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION */ public @NonNull Builder detectContentUriWithoutPermission() { return enable(DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION); } /** * Detect any sockets in the calling app which have not been tagged using {@link * TrafficStats}. Tagging sockets can help you investigate network usage inside your * app, such as a narrowing down heavy usage to a specific library or component. * *

This currently does not detect sockets created in native code. * * @see TrafficStats#setThreadStatsTag(int) * @see TrafficStats#tagSocket(java.net.Socket) * @see TrafficStats#tagDatagramSocket(java.net.DatagramSocket) */ public @NonNull Builder detectUntaggedSockets() { return enable(DETECT_VM_UNTAGGED_SOCKET); } /** @hide */ public @NonNull Builder permitUntaggedSockets() { return disable(DETECT_VM_UNTAGGED_SOCKET); } /** * Detect any implicit reliance on Direct Boot automatic filtering * of {@link PackageManager} values. Violations are only triggered * when implicit calls are made while the user is locked. *

* Apps becoming Direct Boot aware need to carefully inspect each * query site and explicitly decide which combination of flags they * want to use: *

    *
  • {@link PackageManager#MATCH_DIRECT_BOOT_AWARE} *
  • {@link PackageManager#MATCH_DIRECT_BOOT_UNAWARE} *
  • {@link PackageManager#MATCH_DIRECT_BOOT_AUTO} *
*/ public @NonNull Builder detectImplicitDirectBoot() { return enable(DETECT_VM_IMPLICIT_DIRECT_BOOT); } /** @hide */ public @NonNull Builder permitImplicitDirectBoot() { return disable(DETECT_VM_IMPLICIT_DIRECT_BOOT); } /** * Detect access to filesystem paths stored in credential protected * storage areas while the user is locked. *

* When a user is locked, credential protected storage is * unavailable, and files stored in these locations appear to not * exist, which can result in subtle app bugs if they assume default * behaviors or empty states. Instead, apps should store data needed * while a user is locked under device protected storage areas. * * @see Context#createDeviceProtectedStorageContext() */ public @NonNull Builder detectCredentialProtectedWhileLocked() { return enable(DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED); } /** @hide */ public @NonNull Builder permitCredentialProtectedWhileLocked() { return disable(DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED); } /** * Detect attempts to invoke a method on a {@link Context} that is not suited for such * operation. *

An example of this is trying to obtain an instance of visual service (e.g. * {@link android.view.WindowManager}) from a non-visual {@link Context}. This is not * allowed, since a non-visual {@link Context} is not adjusted to any visual area, and * therefore can report incorrect metrics or resources. * @see Context#getDisplay() * @see Context#getSystemService(String) * @hide */ @TestApi public @NonNull Builder detectIncorrectContextUse() { return enable(DETECT_VM_INCORRECT_CONTEXT_USE); } /** * Disable detection of incorrect context use. * TODO(b/149790106): Fix usages and remove. * @hide */ @TestApi public @NonNull Builder permitIncorrectContextUse() { return disable(DETECT_VM_INCORRECT_CONTEXT_USE); } /** * Crashes the whole process on violation. This penalty runs at the end of all enabled * penalties so you'll still get your logging or other violations before the process * dies. */ public @NonNull Builder penaltyDeath() { return enable(PENALTY_DEATH); } /** * Crashes the whole process when cleartext network traffic is detected. * * @see #detectCleartextNetwork() */ public @NonNull Builder penaltyDeathOnCleartextNetwork() { return enable(PENALTY_DEATH_ON_CLEARTEXT_NETWORK); } /** * Crashes the whole process when a {@code file://} {@link android.net.Uri} is exposed * beyond this app. * * @see #detectFileUriExposure() */ public @NonNull Builder penaltyDeathOnFileUriExposure() { return enable(PENALTY_DEATH_ON_FILE_URI_EXPOSURE); } /** Log detected violations to the system log. */ public @NonNull Builder penaltyLog() { return enable(PENALTY_LOG); } /** * Enable detected violations log a stacktrace and timing data to the {@link * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform * integrators doing beta user field data collection. */ public @NonNull Builder penaltyDropBox() { return enable(PENALTY_DROPBOX); } /** * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation. */ public @NonNull Builder penaltyListener( @NonNull Executor executor, @NonNull OnVmViolationListener listener) { if (executor == null) { throw new NullPointerException("executor must not be null"); } mListener = listener; mExecutor = executor; return this; } /** @removed */ public @NonNull Builder penaltyListener( @NonNull OnVmViolationListener listener, @NonNull Executor executor) { return penaltyListener(executor, listener); } private Builder enable(@VmPolicyMask int mask) { mMask |= mask; return this; } Builder disable(@VmPolicyMask int mask) { mMask &= ~mask; return this; } /** * Construct the VmPolicy instance. * *

Note: if no penalties are enabled before calling build, {@link * #penaltyLog} is implicitly set. */ public VmPolicy build() { // If there are detection bits set but no violation bits // set, enable simple logging. if (mListener == null && mMask != 0 && (mMask & (PENALTY_DEATH | PENALTY_LOG | PENALTY_DROPBOX | PENALTY_DIALOG)) == 0) { penaltyLog(); } return new VmPolicy( mMask, mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP, mListener, mExecutor); } } } /** * Log of strict mode violation stack traces that have occurred during a Binder call, to be * serialized back later to the caller via Parcel.writeNoException() (amusingly) where the * caller can choose how to react. */ private static final ThreadLocal> gatheredViolations = new ThreadLocal>() { @Override protected ArrayList initialValue() { // Starts null to avoid unnecessary allocations when // checking whether there are any violations or not in // hasGatheredViolations() below. return null; } }; /** * Sets the policy for what actions on the current thread should be detected, as well as the * penalty if such actions occur. * *

Internally this sets a thread-local variable which is propagated across cross-process IPC * calls, meaning you can catch violations when a system service or another process accesses the * disk or network on your behalf. * * @param policy the policy to put into place */ public static void setThreadPolicy(final ThreadPolicy policy) { setThreadPolicyMask(policy.mask); sThreadViolationListener.set(policy.mListener); sThreadViolationExecutor.set(policy.mCallbackExecutor); } /** @hide */ public static void setThreadPolicyMask(@ThreadPolicyMask int threadPolicyMask) { // In addition to the Java-level thread-local in Dalvik's // BlockGuard, we also need to keep a native thread-local in // Binder in order to propagate the value across Binder calls, // even across native-only processes. The two are kept in // sync via the callback to onStrictModePolicyChange, below. setBlockGuardPolicy(threadPolicyMask); // And set the Android native version... Binder.setThreadStrictModePolicy(threadPolicyMask); } // Sets the policy in Dalvik/libcore (BlockGuard) private static void setBlockGuardPolicy(@ThreadPolicyMask int threadPolicyMask) { if (threadPolicyMask == 0) { BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY); return; } final BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); final AndroidBlockGuardPolicy androidPolicy; if (policy instanceof AndroidBlockGuardPolicy) { androidPolicy = (AndroidBlockGuardPolicy) policy; } else { androidPolicy = THREAD_ANDROID_POLICY.get(); BlockGuard.setThreadPolicy(androidPolicy); } androidPolicy.setThreadPolicyMask(threadPolicyMask); } private static void setBlockGuardVmPolicy(@VmPolicyMask int vmPolicyMask) { // We only need to install BlockGuard for a small subset of VM policies vmPolicyMask &= DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED; if (vmPolicyMask != 0) { BlockGuard.setVmPolicy(VM_ANDROID_POLICY); } else { BlockGuard.setVmPolicy(BlockGuard.LAX_VM_POLICY); } } // Sets up CloseGuard in Dalvik/libcore private static void setCloseGuardEnabled(boolean enabled) { if (!(CloseGuard.getReporter() instanceof AndroidCloseGuardReporter)) { CloseGuard.setReporter(new AndroidCloseGuardReporter()); } CloseGuard.setEnabled(enabled); } /** * Returns the bitmask of the current thread's policy. * * @return the bitmask of all the DETECT_* and PENALTY_* bits currently enabled * @hide */ @UnsupportedAppUsage public static @ThreadPolicyMask int getThreadPolicyMask() { final BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); if (policy instanceof AndroidBlockGuardPolicy) { return ((AndroidBlockGuardPolicy) policy).getThreadPolicyMask(); } else { return 0; } } /** Returns the current thread's policy. */ public static ThreadPolicy getThreadPolicy() { // TODO: this was a last minute Gingerbread API change (to // introduce VmPolicy cleanly) but this isn't particularly // optimal for users who might call this method often. This // should be in a thread-local and not allocate on each call. return new ThreadPolicy( getThreadPolicyMask(), sThreadViolationListener.get(), sThreadViolationExecutor.get()); } /** * A convenience wrapper that takes the current {@link ThreadPolicy} from {@link * #getThreadPolicy}, modifies it to permit both disk reads & writes, and sets the new * policy with {@link #setThreadPolicy}, returning the old policy so you can restore it at the * end of a block. * * @return the old policy, to be passed to {@link #setThreadPolicy} to restore the policy at the * end of a block */ public static ThreadPolicy allowThreadDiskWrites() { return new ThreadPolicy( allowThreadDiskWritesMask(), sThreadViolationListener.get(), sThreadViolationExecutor.get()); } /** @hide */ public static @ThreadPolicyMask int allowThreadDiskWritesMask() { int oldPolicyMask = getThreadPolicyMask(); int newPolicyMask = oldPolicyMask & ~(DETECT_THREAD_DISK_WRITE | DETECT_THREAD_DISK_READ); if (newPolicyMask != oldPolicyMask) { setThreadPolicyMask(newPolicyMask); } return oldPolicyMask; } /** * A convenience wrapper that takes the current {@link ThreadPolicy} from {@link * #getThreadPolicy}, modifies it to permit disk reads, and sets the new policy with {@link * #setThreadPolicy}, returning the old policy so you can restore it at the end of a block. * * @return the old policy, to be passed to setThreadPolicy to restore the policy. */ public static ThreadPolicy allowThreadDiskReads() { return new ThreadPolicy( allowThreadDiskReadsMask(), sThreadViolationListener.get(), sThreadViolationExecutor.get()); } /** @hide */ public static @ThreadPolicyMask int allowThreadDiskReadsMask() { int oldPolicyMask = getThreadPolicyMask(); int newPolicyMask = oldPolicyMask & ~(DETECT_THREAD_DISK_READ); if (newPolicyMask != oldPolicyMask) { setThreadPolicyMask(newPolicyMask); } return oldPolicyMask; } /** @hide */ public static ThreadPolicy allowThreadViolations() { ThreadPolicy oldPolicy = getThreadPolicy(); setThreadPolicyMask(0); return oldPolicy; } /** @hide */ public static VmPolicy allowVmViolations() { VmPolicy oldPolicy = getVmPolicy(); sVmPolicy = VmPolicy.LAX; return oldPolicy; } /** * Determine if the given app is "bundled" as part of the system image. These bundled apps are * developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to * chase any {@link StrictMode} regressions by enabling detection when running on {@link * Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds. * *

Unbundled apps included in the system image are expected to detect and triage their own * {@link StrictMode} issues separate from the OS release process, which is why we don't enable * them here. * * @hide */ public static boolean isBundledSystemApp(ApplicationInfo ai) { if (ai == null || ai.packageName == null) { // Probably system server return true; } else if (ai.isSystemApp()) { // Ignore unbundled apps living in the wrong namespace if (ai.packageName.equals("com.android.vending") || ai.packageName.equals("com.android.chrome")) { return false; } // Ignore bundled apps that are way too spammy // STOPSHIP: burn this list down to zero if (ai.packageName.equals("com.android.phone")) { return false; } if (ai.packageName.equals("android") || ai.packageName.startsWith("android.") || ai.packageName.startsWith("com.android.")) { return true; } } return false; } /** * Initialize default {@link ThreadPolicy} for the current thread. * * @hide */ public static void initThreadDefaults(ApplicationInfo ai) { final ThreadPolicy.Builder builder = new ThreadPolicy.Builder(); final int targetSdkVersion = (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT; // Starting in HC, we don't allow network usage on the main thread if (targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) { builder.detectNetwork(); builder.penaltyDeathOnNetwork(); } if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) { // Detect nothing extra } else if (Build.IS_USERDEBUG) { // Detect everything in bundled apps if (isBundledSystemApp(ai)) { builder.detectAll(); builder.penaltyDropBox(); if (SystemProperties.getBoolean(VISUAL_PROPERTY, false)) { builder.penaltyFlashScreen(); } } } else if (Build.IS_ENG) { // Detect everything in bundled apps if (isBundledSystemApp(ai)) { builder.detectAll(); builder.penaltyDropBox(); builder.penaltyLog(); builder.penaltyFlashScreen(); } } setThreadPolicy(builder.build()); } /** * Initialize default {@link VmPolicy} for the current VM. * * @hide */ public static void initVmDefaults(ApplicationInfo ai) { final VmPolicy.Builder builder = new VmPolicy.Builder(); final int targetSdkVersion = (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT; // Starting in N, we don't allow file:// Uri exposure if (targetSdkVersion >= Build.VERSION_CODES.N) { builder.detectFileUriExposure(); builder.penaltyDeathOnFileUriExposure(); } if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) { // Detect nothing extra } else if (Build.IS_USERDEBUG) { // Detect everything in bundled apps (except activity leaks, which // are expensive to track) if (isBundledSystemApp(ai)) { builder.detectAll(); builder.permitActivityLeaks(); builder.penaltyDropBox(); } } else if (Build.IS_ENG) { // Detect everything in bundled apps if (isBundledSystemApp(ai)) { builder.detectAll(); builder.penaltyDropBox(); builder.penaltyLog(); } } setVmPolicy(builder.build()); } /** * Used by the framework to make file usage a fatal error. * * @hide */ @UnsupportedAppUsage public static void enableDeathOnFileUriExposure() { sVmPolicy = new VmPolicy( sVmPolicy.mask | DETECT_VM_FILE_URI_EXPOSURE | PENALTY_DEATH_ON_FILE_URI_EXPOSURE, sVmPolicy.classInstanceLimit, sVmPolicy.mListener, sVmPolicy.mCallbackExecutor); } /** * Used by lame internal apps that haven't done the hard work to get themselves off file:// Uris * yet. * * @hide */ @UnsupportedAppUsage public static void disableDeathOnFileUriExposure() { sVmPolicy = new VmPolicy( sVmPolicy.mask & ~(DETECT_VM_FILE_URI_EXPOSURE | PENALTY_DEATH_ON_FILE_URI_EXPOSURE), sVmPolicy.classInstanceLimit, sVmPolicy.mListener, sVmPolicy.mCallbackExecutor); } @UnsupportedAppUsage private static final ThreadLocal> violationsBeingTimed = new ThreadLocal>() { @Override protected ArrayList initialValue() { return new ArrayList(); } }; // Note: only access this once verifying the thread has a Looper. private static final ThreadLocal THREAD_HANDLER = new ThreadLocal() { @Override protected Handler initialValue() { return new Handler(); } }; private static final ThreadLocal THREAD_ANDROID_POLICY = new ThreadLocal() { @Override protected AndroidBlockGuardPolicy initialValue() { return new AndroidBlockGuardPolicy(0); } }; private static boolean tooManyViolationsThisLoop() { return violationsBeingTimed.get().size() >= MAX_OFFENSES_PER_LOOP; } private static class AndroidBlockGuardPolicy implements BlockGuard.Policy { private @ThreadPolicyMask int mThreadPolicyMask; // Map from violation stacktrace hashcode -> uptimeMillis of // last violation. No locking needed, as this is only // accessed by the same thread. private ArrayMap mLastViolationTime; public AndroidBlockGuardPolicy(@ThreadPolicyMask int threadPolicyMask) { mThreadPolicyMask = threadPolicyMask; } @Override public String toString() { return "AndroidBlockGuardPolicy; mPolicyMask=" + mThreadPolicyMask; } // Part of BlockGuard.Policy interface: public int getPolicyMask() { return mThreadPolicyMask; } // Part of BlockGuard.Policy interface: public void onWriteToDisk() { if ((mThreadPolicyMask & DETECT_THREAD_DISK_WRITE) == 0) { return; } if (tooManyViolationsThisLoop()) { return; } startHandlingViolationException(new DiskWriteViolation()); } // Not part of BlockGuard.Policy; just part of StrictMode: void onCustomSlowCall(String name) { if ((mThreadPolicyMask & DETECT_THREAD_CUSTOM) == 0) { return; } if (tooManyViolationsThisLoop()) { return; } startHandlingViolationException(new CustomViolation(name)); } // Not part of BlockGuard.Policy; just part of StrictMode: void onResourceMismatch(Object tag) { if ((mThreadPolicyMask & DETECT_THREAD_RESOURCE_MISMATCH) == 0) { return; } if (tooManyViolationsThisLoop()) { return; } startHandlingViolationException(new ResourceMismatchViolation(tag)); } // Not part of BlockGuard.Policy; just part of StrictMode: public void onUnbufferedIO() { if ((mThreadPolicyMask & DETECT_THREAD_UNBUFFERED_IO) == 0) { return; } if (tooManyViolationsThisLoop()) { return; } startHandlingViolationException(new UnbufferedIoViolation()); } // Part of BlockGuard.Policy interface: public void onReadFromDisk() { if ((mThreadPolicyMask & DETECT_THREAD_DISK_READ) == 0) { return; } if (tooManyViolationsThisLoop()) { return; } startHandlingViolationException(new DiskReadViolation()); } // Part of BlockGuard.Policy interface: public void onNetwork() { if ((mThreadPolicyMask & DETECT_THREAD_NETWORK) == 0) { return; } if ((mThreadPolicyMask & PENALTY_DEATH_ON_NETWORK) != 0) { throw new NetworkOnMainThreadException(); } if (tooManyViolationsThisLoop()) { return; } startHandlingViolationException(new NetworkViolation()); } // Part of BlockGuard.Policy interface: public void onExplicitGc() { if ((mThreadPolicyMask & DETECT_THREAD_EXPLICIT_GC) == 0) { return; } if (tooManyViolationsThisLoop()) { return; } startHandlingViolationException(new ExplicitGcViolation()); } public @ThreadPolicyMask int getThreadPolicyMask() { return mThreadPolicyMask; } public void setThreadPolicyMask(@ThreadPolicyMask int threadPolicyMask) { mThreadPolicyMask = threadPolicyMask; } // Start handling a violation that just started and hasn't // actually run yet (e.g. no disk write or network operation // has yet occurred). This sees if we're in an event loop // thread and, if so, uses it to roughly measure how long the // violation took. void startHandlingViolationException(Violation e) { final int penaltyMask = (mThreadPolicyMask & PENALTY_ALL); final ViolationInfo info = new ViolationInfo(e, penaltyMask); info.violationUptimeMillis = SystemClock.uptimeMillis(); handleViolationWithTimingAttempt(info); } // Attempts to fill in the provided ViolationInfo's // durationMillis field if this thread has a Looper we can use // to measure with. We measure from the time of violation // until the time the looper is idle again (right before // the next epoll_wait) void handleViolationWithTimingAttempt(final ViolationInfo info) { Looper looper = Looper.myLooper(); // 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. Note that this case is // also hit when a violation takes place in a Binder // thread, in "gather" mode. In this case, the duration // of the violation is computed by the ultimate caller and // its Looper, if any. // // Also, as a special short-cut case when the only penalty // bit is death, we die immediately, rather than timing // the violation's duration. This makes it convenient to // use in unit tests too, rather than waiting on a Looper. // // TODO: if in gather mode, ignore Looper.myLooper() and always // go into this immediate mode? if (looper == null || (info.mPenaltyMask == PENALTY_DEATH)) { info.durationMillis = -1; // unknown (redundant, already set) onThreadPolicyViolation(info); return; } final ArrayList records = violationsBeingTimed.get(); if (records.size() >= MAX_OFFENSES_PER_LOOP) { // Not worth measuring. Too many offenses in one loop. return; } records.add(info); if (records.size() > 1) { // There's already been a violation this loop, so we've already // registered an idle handler to process the list of violations // at the end of this Looper's loop. return; } final IWindowManager windowManager = info.penaltyEnabled(PENALTY_FLASH) ? sWindowManager.get() : null; if (windowManager != null) { try { windowManager.showStrictModeViolation(true); } catch (RemoteException unused) { } } // We post a runnable to a Handler (== delay 0 ms) for // measuring the end time of a violation instead of using // an IdleHandler (as was previously used) because an // IdleHandler may not run for quite a long period of time // if an ongoing animation is happening and continually // posting ASAP (0 ms) animation steps. Animations are // throttled back to 60fps via SurfaceFlinger/View // invalidates, _not_ by posting frame updates every 16 // milliseconds. THREAD_HANDLER .get() .postAtFrontOfQueue( () -> { long loopFinishTime = SystemClock.uptimeMillis(); // Note: we do this early, before handling the // violation below, as handling the violation // may include PENALTY_DEATH and we don't want // to keep the red border on. if (windowManager != null) { try { windowManager.showStrictModeViolation(false); } catch (RemoteException unused) { } } for (int n = 0; n < records.size(); ++n) { ViolationInfo v = records.get(n); v.violationNumThisLoop = n + 1; v.durationMillis = (int) (loopFinishTime - v.violationUptimeMillis); onThreadPolicyViolation(v); } records.clear(); }); } // Note: It's possible (even quite likely) that the // thread-local policy mask 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, // hence the policy being passed around. void onThreadPolicyViolation(final ViolationInfo info) { if (LOG_V) Log.d(TAG, "onThreadPolicyViolation; penalty=" + info.mPenaltyMask); if (info.penaltyEnabled(PENALTY_GATHER)) { ArrayList violations = gatheredViolations.get(); if (violations == null) { violations = new ArrayList<>(1); gatheredViolations.set(violations); } for (ViolationInfo previous : violations) { if (info.getStackTrace().equals(previous.getStackTrace())) { // Duplicate. Don't log. return; } } violations.add(info); return; } // Not perfect, but fast and good enough for dup suppression. Integer crashFingerprint = info.hashCode(); long lastViolationTime = 0; long now = SystemClock.uptimeMillis(); if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger if (mLastViolationTime != null) { Long vtime = mLastViolationTime.get(crashFingerprint); if (vtime != null) { lastViolationTime = vtime; } clampViolationTimeMap(mLastViolationTime, Math.max(MIN_LOG_INTERVAL_MS, Math.max(MIN_DIALOG_INTERVAL_MS, MIN_DROPBOX_INTERVAL_MS))); } else { mLastViolationTime = new ArrayMap<>(1); } mLastViolationTime.put(crashFingerprint, now); } long timeSinceLastViolationMillis = lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime); if (info.penaltyEnabled(PENALTY_LOG) && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) { sLogger.log(info); } final Violation violation = info.mViolation; // Penalties that ActivityManager should execute on our behalf. int penaltyMask = 0; if (info.penaltyEnabled(PENALTY_DIALOG) && timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) { penaltyMask |= PENALTY_DIALOG; } if (info.penaltyEnabled(PENALTY_DROPBOX) && timeSinceLastViolationMillis > MIN_DROPBOX_INTERVAL_MS) { penaltyMask |= PENALTY_DROPBOX; } if (penaltyMask != 0) { final boolean justDropBox = (info.mPenaltyMask == PENALTY_DROPBOX); if (justDropBox) { // If all we're going to ask the activity manager // to do is dropbox it (the common case during // platform development), we can avoid doing this // call synchronously which Binder data suggests // isn't always super fast, despite the implementation // in the ActivityManager trying to be mostly async. dropboxViolationAsync(penaltyMask, info); } else { handleApplicationStrictModeViolation(penaltyMask, info); } } if (info.penaltyEnabled(PENALTY_DEATH)) { throw new RuntimeException("StrictMode ThreadPolicy violation", violation); } // penaltyDeath will cause penaltyCallback to no-op since we cannot guarantee the // executor finishes before crashing. final OnThreadViolationListener listener = sThreadViolationListener.get(); final Executor executor = sThreadViolationExecutor.get(); if (listener != null && executor != null) { try { executor.execute( () -> { // Lift violated policy to prevent infinite recursion. ThreadPolicy oldPolicy = StrictMode.allowThreadViolations(); try { listener.onThreadViolation(violation); } finally { StrictMode.setThreadPolicy(oldPolicy); } }); } catch (RejectedExecutionException e) { Log.e(TAG, "ThreadPolicy penaltyCallback failed", e); } } } } private static final BlockGuard.VmPolicy VM_ANDROID_POLICY = new BlockGuard.VmPolicy() { @Override public void onPathAccess(String path) { if (path == null) return; // NOTE: keep credential-protected paths in sync with Environment.java if (path.startsWith("/data/user/") || path.startsWith("/data/media/") || path.startsWith("/data/system_ce/") || path.startsWith("/data/misc_ce/") || path.startsWith("/data/vendor_ce/") || path.startsWith("/storage/emulated/")) { final int second = path.indexOf('/', 1); final int third = path.indexOf('/', second + 1); final int fourth = path.indexOf('/', third + 1); if (fourth == -1) return; try { final int userId = Integer.parseInt(path.substring(third + 1, fourth)); onCredentialProtectedPathAccess(path, userId); } catch (NumberFormatException ignored) { } } else if (path.startsWith("/data/data/")) { onCredentialProtectedPathAccess(path, UserHandle.USER_SYSTEM); } } }; /** * In the common case, as set by conditionallyEnableDebugLogging, we're just dropboxing any * violations but not showing a dialog, not loggging, and not killing the process. In these * cases we don't need to do a synchronous call to the ActivityManager. This is used by both * per-thread and vm-wide violations when applicable. */ private static void dropboxViolationAsync( final int penaltyMask, final ViolationInfo info) { int outstanding = sDropboxCallsInFlight.incrementAndGet(); if (outstanding > 20) { // What's going on? Let's not make make the situation // worse and just not log. sDropboxCallsInFlight.decrementAndGet(); return; } if (LOG_V) Log.d(TAG, "Dropboxing async; in-flight=" + outstanding); BackgroundThread.getHandler().post(() -> { handleApplicationStrictModeViolation(penaltyMask, info); int outstandingInner = sDropboxCallsInFlight.decrementAndGet(); if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstandingInner); }); } private static void handleApplicationStrictModeViolation(int penaltyMask, ViolationInfo info) { final int oldMask = getThreadPolicyMask(); try { // First, remove any policy before we call into the Activity Manager, // otherwise we'll infinite recurse as we try to log policy violations // to disk, thus violating policy, thus requiring logging, etc... // We restore the current policy below, in the finally block. setThreadPolicyMask(0); IActivityManager am = ActivityManager.getService(); if (am == null) { Log.w(TAG, "No activity manager; failed to Dropbox violation."); } else { am.handleApplicationStrictModeViolation( RuntimeInit.getApplicationObject(), penaltyMask, info); } } catch (RemoteException e) { if (e instanceof DeadObjectException) { // System process is dead; ignore } else { Log.e(TAG, "RemoteException handling StrictMode violation", e); } } finally { setThreadPolicyMask(oldMask); } } private static class AndroidCloseGuardReporter implements CloseGuard.Reporter { @Override public void report(String message, Throwable allocationSite) { onVmPolicyViolation(new LeakedClosableViolation(message, allocationSite)); } @Override public void report(String message) { onVmPolicyViolation(new LeakedClosableViolation(message)); } } /** Called from Parcel.writeNoException() */ /* package */ static boolean hasGatheredViolations() { return gatheredViolations.get() != null; } /** * Called from Parcel.writeException(), so we drop this memory and don't incorrectly attribute * it to the wrong caller on the next Binder call on this thread. */ /* package */ static void clearGatheredViolations() { gatheredViolations.set(null); } /** @hide */ @UnsupportedAppUsage @TestApi public static void conditionallyCheckInstanceCounts() { VmPolicy policy = getVmPolicy(); int policySize = policy.classInstanceLimit.size(); if (policySize == 0) { return; } System.gc(); System.runFinalization(); System.gc(); // Note: classInstanceLimit is immutable, so this is lock-free // Create the classes array. Class[] classes = policy.classInstanceLimit.keySet().toArray(new Class[policySize]); long[] instanceCounts = VMDebug.countInstancesOfClasses(classes, false); for (int i = 0; i < classes.length; ++i) { Class klass = classes[i]; int limit = policy.classInstanceLimit.get(klass); long instances = instanceCounts[i]; if (instances > limit) { onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit)); } } } private static long sLastInstanceCountCheckMillis = 0; private static boolean sIsIdlerRegistered = false; // guarded by StrictMode.class private static final MessageQueue.IdleHandler sProcessIdleHandler = new MessageQueue.IdleHandler() { public boolean queueIdle() { long now = SystemClock.uptimeMillis(); if (now - sLastInstanceCountCheckMillis > 30 * 1000) { sLastInstanceCountCheckMillis = now; conditionallyCheckInstanceCounts(); } return true; } }; /** * Sets the policy for what actions in the VM process (on any thread) should be detected, as * well as the penalty if such actions occur. * * @param policy the policy to put into place */ public static void setVmPolicy(final VmPolicy policy) { synchronized (StrictMode.class) { sVmPolicy = policy; setCloseGuardEnabled(vmClosableObjectLeaksEnabled()); Looper looper = Looper.getMainLooper(); if (looper != null) { MessageQueue mq = looper.mQueue; if (policy.classInstanceLimit.size() == 0 || (sVmPolicy.mask & PENALTY_ALL) == 0) { mq.removeIdleHandler(sProcessIdleHandler); sIsIdlerRegistered = false; } else if (!sIsIdlerRegistered) { mq.addIdleHandler(sProcessIdleHandler); sIsIdlerRegistered = true; } } int networkPolicy = NETWORK_POLICY_ACCEPT; if ((sVmPolicy.mask & DETECT_VM_CLEARTEXT_NETWORK) != 0) { if ((sVmPolicy.mask & PENALTY_DEATH) != 0 || (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0) { networkPolicy = NETWORK_POLICY_REJECT; } else { networkPolicy = NETWORK_POLICY_LOG; } } final INetworkManagementService netd = INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); if (netd != null) { try { netd.setUidCleartextNetworkPolicy(android.os.Process.myUid(), networkPolicy); } catch (RemoteException ignored) { } } else if (networkPolicy != NETWORK_POLICY_ACCEPT) { Log.w(TAG, "Dropping requested network policy due to missing service!"); } if ((sVmPolicy.mask & DETECT_VM_NON_SDK_API_USAGE) != 0) { VMRuntime.setNonSdkApiUsageConsumer(sNonSdkApiUsageConsumer); VMRuntime.setDedupeHiddenApiWarnings(false); } else { VMRuntime.setNonSdkApiUsageConsumer(null); VMRuntime.setDedupeHiddenApiWarnings(true); } setBlockGuardVmPolicy(sVmPolicy.mask); } } /** Gets the current VM policy. */ public static VmPolicy getVmPolicy() { synchronized (StrictMode.class) { return sVmPolicy; } } /** * Enable the recommended StrictMode defaults, with violations just being logged. * *

This catches disk and network access on the main thread, as well as leaked SQLite cursors * and unclosed resources. This is simply a wrapper around {@link #setVmPolicy} and {@link * #setThreadPolicy}. */ public static void enableDefaults() { setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build()); } /** @hide */ public static boolean vmSqliteObjectLeaksEnabled() { return (sVmPolicy.mask & DETECT_VM_CURSOR_LEAKS) != 0; } /** @hide */ public static boolean vmClosableObjectLeaksEnabled() { return (sVmPolicy.mask & DETECT_VM_CLOSABLE_LEAKS) != 0; } /** @hide */ public static boolean vmRegistrationLeaksEnabled() { return (sVmPolicy.mask & DETECT_VM_REGISTRATION_LEAKS) != 0; } /** @hide */ public static boolean vmFileUriExposureEnabled() { return (sVmPolicy.mask & DETECT_VM_FILE_URI_EXPOSURE) != 0; } /** @hide */ public static boolean vmCleartextNetworkEnabled() { return (sVmPolicy.mask & DETECT_VM_CLEARTEXT_NETWORK) != 0; } /** @hide */ public static boolean vmContentUriWithoutPermissionEnabled() { return (sVmPolicy.mask & DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION) != 0; } /** @hide */ public static boolean vmUntaggedSocketEnabled() { return (sVmPolicy.mask & DETECT_VM_UNTAGGED_SOCKET) != 0; } /** @hide */ public static boolean vmImplicitDirectBootEnabled() { return (sVmPolicy.mask & DETECT_VM_IMPLICIT_DIRECT_BOOT) != 0; } /** @hide */ public static boolean vmCredentialProtectedWhileLockedEnabled() { return (sVmPolicy.mask & DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED) != 0; } /** @hide */ public static boolean vmIncorrectContextUseEnabled() { return (sVmPolicy.mask & DETECT_VM_INCORRECT_CONTEXT_USE) != 0; } /** @hide */ public static void onSqliteObjectLeaked(String message, Throwable originStack) { onVmPolicyViolation(new SqliteObjectLeakedViolation(message, originStack)); } /** @hide */ @UnsupportedAppUsage public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) { onVmPolicyViolation(new WebViewMethodCalledOnWrongThreadViolation(originStack)); } /** @hide */ public static void onIntentReceiverLeaked(Throwable originStack) { onVmPolicyViolation(new IntentReceiverLeakedViolation(originStack)); } /** @hide */ public static void onServiceConnectionLeaked(Throwable originStack) { onVmPolicyViolation(new ServiceConnectionLeakedViolation(originStack)); } /** @hide */ public static void onFileUriExposed(Uri uri, String location) { final String message = uri + " exposed beyond app through " + location; if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) { throw new FileUriExposedException(message); } else { onVmPolicyViolation(new FileUriExposedViolation(message)); } } /** @hide */ public static void onContentUriWithoutPermission(Uri uri, String location) { onVmPolicyViolation(new ContentUriWithoutPermissionViolation(uri, location)); } /** @hide */ public static void onCleartextNetworkDetected(byte[] firstPacket) { byte[] rawAddr = null; if (firstPacket != null) { if (firstPacket.length >= 20 && (firstPacket[0] & 0xf0) == 0x40) { // IPv4 rawAddr = new byte[4]; System.arraycopy(firstPacket, 16, rawAddr, 0, 4); } else if (firstPacket.length >= 40 && (firstPacket[0] & 0xf0) == 0x60) { // IPv6 rawAddr = new byte[16]; System.arraycopy(firstPacket, 24, rawAddr, 0, 16); } } final int uid = android.os.Process.myUid(); String msg = "Detected cleartext network traffic from UID " + uid; if (rawAddr != null) { try { msg += " to " + InetAddress.getByAddress(rawAddr); } catch (UnknownHostException ignored) { } } msg += HexDump.dumpHexString(firstPacket).trim() + " "; final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0; onVmPolicyViolation(new CleartextNetworkViolation(msg), forceDeath); } /** @hide */ public static void onUntaggedSocket() { onVmPolicyViolation(new UntaggedSocketViolation()); } /** @hide */ public static void onImplicitDirectBoot() { onVmPolicyViolation(new ImplicitDirectBootViolation()); } /** @hide */ public static void onIncorrectContextUsed(String message, Throwable originStack) { onVmPolicyViolation(new IncorrectContextUseViolation(message, originStack)); } /** Assume locked until we hear otherwise */ private static volatile boolean sUserKeyUnlocked = false; private static boolean isUserKeyUnlocked(int userId) { final IStorageManager storage = IStorageManager.Stub .asInterface(ServiceManager.getService("mount")); if (storage != null) { try { return storage.isUserKeyUnlocked(userId); } catch (RemoteException ignored) { } } return false; } /** @hide */ private static void onCredentialProtectedPathAccess(String path, int userId) { // We can cache the unlocked state for the userId we're running as, // since any relocking of that user will always result in our // process being killed to release any CE FDs we're holding onto. if (userId == UserHandle.myUserId()) { if (sUserKeyUnlocked) { return; } else if (isUserKeyUnlocked(userId)) { sUserKeyUnlocked = true; return; } } else if (isUserKeyUnlocked(userId)) { return; } onVmPolicyViolation(new CredentialProtectedWhileLockedViolation( "Accessed credential protected path " + path + " while user " + userId + " was locked")); } // Map from VM violation fingerprint to uptime millis. @UnsupportedAppUsage private static final HashMap sLastVmViolationTime = new HashMap<>(); /** * Clamp the given map by removing elements with timestamp older than the given retainSince. */ private static void clampViolationTimeMap(final @NonNull Map violationTime, final long retainSince) { final Iterator> iterator = violationTime.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry e = iterator.next(); if (e.getValue() < retainSince) { // Remove stale entries iterator.remove(); } } // Ideally we'd cap the total size of the map, though it'll involve quickselect of topK, // seems not worth it (saving some space immediately but they will be obsoleted soon anyway) } /** @hide */ public static void onVmPolicyViolation(Violation originStack) { onVmPolicyViolation(originStack, false); } /** @hide */ public static void onVmPolicyViolation(Violation violation, boolean forceDeath) { final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0; final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath; final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0; final int penaltyMask = (sVmPolicy.mask & PENALTY_ALL); final ViolationInfo info = new ViolationInfo(violation, penaltyMask); // Erase stuff not relevant for process-wide violations info.numAnimationsRunning = 0; info.tags = null; info.broadcastIntentAction = null; final Integer fingerprint = info.hashCode(); final long now = SystemClock.uptimeMillis(); long lastViolationTime; long timeSinceLastViolationMillis = Long.MAX_VALUE; if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger synchronized (sLastVmViolationTime) { if (sLastVmViolationTime.containsKey(fingerprint)) { lastViolationTime = sLastVmViolationTime.get(fingerprint); timeSinceLastViolationMillis = now - lastViolationTime; } if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) { sLastVmViolationTime.put(fingerprint, now); } clampViolationTimeMap(sLastVmViolationTime, now - Math.max(MIN_VM_INTERVAL_MS, MIN_LOG_INTERVAL_MS)); } } if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) { // Rate limit all penalties. return; } if (penaltyLog && sLogger != null && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) { sLogger.log(info); } if (penaltyDropbox) { if (penaltyDeath) { handleApplicationStrictModeViolation(PENALTY_DROPBOX, info); } else { // Common case for userdebug/eng builds. If no death and // just dropboxing, we can do the ActivityManager call // asynchronously. dropboxViolationAsync(PENALTY_DROPBOX, info); } } if (penaltyDeath) { System.err.println("StrictMode VmPolicy violation with POLICY_DEATH; shutting down."); Process.killProcess(Process.myPid()); System.exit(10); } // If penaltyDeath, we can't guarantee this callback finishes before the process dies for // all executors. penaltyDeath supersedes penaltyCallback. if (sVmPolicy.mListener != null && sVmPolicy.mCallbackExecutor != null) { final OnVmViolationListener listener = sVmPolicy.mListener; try { sVmPolicy.mCallbackExecutor.execute( () -> { // Lift violated policy to prevent infinite recursion. VmPolicy oldPolicy = allowVmViolations(); try { listener.onVmViolation(violation); } finally { setVmPolicy(oldPolicy); } }); } catch (RejectedExecutionException e) { Log.e(TAG, "VmPolicy penaltyCallback failed", e); } } } /** Called from Parcel.writeNoException() */ /* package */ static void writeGatheredViolationsToParcel(Parcel p) { ArrayList violations = gatheredViolations.get(); if (violations == null) { p.writeInt(0); } else { // To avoid taking up too much transaction space, only include // details for the first 3 violations. Deep inside, CrashInfo // will truncate each stack trace to ~20kB. final int size = Math.min(violations.size(), 3); p.writeInt(size); for (int i = 0; i < size; i++) { violations.get(i).writeToParcel(p, 0); } } gatheredViolations.set(null); } /** * Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS, we here * read back all the encoded violations. */ /* package */ static void readAndHandleBinderCallViolations(Parcel p) { Throwable localCallSite = new Throwable(); final int policyMask = getThreadPolicyMask(); final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0; final int size = p.readInt(); for (int i = 0; i < size; i++) { final ViolationInfo info = new ViolationInfo(p, !currentlyGathering); info.addLocalStack(localCallSite); BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); if (policy instanceof AndroidBlockGuardPolicy) { ((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info); } } } /** * Called from android_util_Binder.cpp's android_os_Parcel_enforceInterface when an incoming * Binder call requires changing the StrictMode policy mask. The role of this function is to ask * Binder for its current (native) thread-local policy value and synchronize it to libcore's * (Java) thread-local policy value. */ @UnsupportedAppUsage private static void onBinderStrictModePolicyChange(@ThreadPolicyMask int newPolicy) { setBlockGuardPolicy(newPolicy); } /** * A tracked, critical time span. (e.g. during an animation.) * *

The object itself is a linked list node, to avoid any allocations during rapid span * entries and exits. * * @hide */ public static class Span { private String mName; private long mCreateMillis; private Span mNext; private Span mPrev; // not used when in freeList, only active private final ThreadSpanState mContainerState; Span(ThreadSpanState threadState) { mContainerState = threadState; } // Empty constructor for the NO_OP_SPAN protected Span() { mContainerState = null; } /** * To be called when the critical span is complete (i.e. the animation is done animating). * This can be called on any thread (even a different one from where the animation was * taking place), but that's only a defensive implementation measure. It really makes no * sense for you to call this on thread other than that where you created it. * * @hide */ @UnsupportedAppUsage public void finish() { ThreadSpanState state = mContainerState; synchronized (state) { if (mName == null) { // Duplicate finish call. Ignore. return; } // Remove ourselves from the active list. if (mPrev != null) { mPrev.mNext = mNext; } if (mNext != null) { mNext.mPrev = mPrev; } if (state.mActiveHead == this) { state.mActiveHead = mNext; } state.mActiveSize--; if (LOG_V) Log.d(TAG, "Span finished=" + mName + "; size=" + state.mActiveSize); this.mCreateMillis = -1; this.mName = null; this.mPrev = null; this.mNext = null; // Add ourselves to the freeList, if it's not already // too big. if (state.mFreeListSize < 5) { this.mNext = state.mFreeListHead; state.mFreeListHead = this; state.mFreeListSize++; } } } } // The no-op span that's used in user builds. private static final Span NO_OP_SPAN = new Span() { public void finish() { // Do nothing. } }; /** * Linked lists of active spans and a freelist. * *

Locking notes: there's one of these structures per thread and all members of this * structure (as well as the Span nodes under it) are guarded by the ThreadSpanState object * instance. While in theory there'd be no locking required because it's all local per-thread, * the finish() method above is defensive against people calling it on a different thread from * where they created the Span, hence the locking. */ private static class ThreadSpanState { public Span mActiveHead; // doubly-linked list. public int mActiveSize; public Span mFreeListHead; // singly-linked list. only changes at head. public int mFreeListSize; } private static final ThreadLocal sThisThreadSpanState = new ThreadLocal() { @Override protected ThreadSpanState initialValue() { return new ThreadSpanState(); } }; @UnsupportedAppUsage private static Singleton sWindowManager = new Singleton() { protected IWindowManager create() { return IWindowManager.Stub.asInterface(ServiceManager.getService("window")); } }; /** * Enter a named critical span (e.g. an animation) * *

The name is an arbitary label (or tag) that will be applied to any strictmode violation * that happens while this span is active. You must call finish() on the span when done. * *

This will never return null, but on devices without debugging enabled, this may return a * dummy object on which the finish() method is a no-op. * *

TODO: add CloseGuard to this, verifying callers call finish. * * @hide */ @UnsupportedAppUsage public static Span enterCriticalSpan(String name) { if (Build.IS_USER) { return NO_OP_SPAN; } if (name == null || name.isEmpty()) { throw new IllegalArgumentException("name must be non-null and non-empty"); } ThreadSpanState state = sThisThreadSpanState.get(); Span span = null; synchronized (state) { if (state.mFreeListHead != null) { span = state.mFreeListHead; state.mFreeListHead = span.mNext; state.mFreeListSize--; } else { // Shouldn't have to do this often. span = new Span(state); } span.mName = name; span.mCreateMillis = SystemClock.uptimeMillis(); span.mNext = state.mActiveHead; span.mPrev = null; state.mActiveHead = span; state.mActiveSize++; if (span.mNext != null) { span.mNext.mPrev = span; } if (LOG_V) Log.d(TAG, "Span enter=" + name + "; size=" + state.mActiveSize); } return span; } /** * For code to note that it's slow. This is a no-op unless the current thread's {@link * android.os.StrictMode.ThreadPolicy} has {@link * android.os.StrictMode.ThreadPolicy.Builder#detectCustomSlowCalls} enabled. * * @param name a short string for the exception stack trace that's built if when this fires. */ public static void noteSlowCall(String name) { BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); if (!(policy instanceof AndroidBlockGuardPolicy)) { // StrictMode not enabled. return; } ((AndroidBlockGuardPolicy) policy).onCustomSlowCall(name); } /** * For code to note that a resource was obtained using a type other than its defined type. This * is a no-op unless the current thread's {@link android.os.StrictMode.ThreadPolicy} has {@link * android.os.StrictMode.ThreadPolicy.Builder#detectResourceMismatches()} enabled. * * @param tag an object for the exception stack trace that's built if when this fires. * @hide */ public static void noteResourceMismatch(Object tag) { BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); if (!(policy instanceof AndroidBlockGuardPolicy)) { // StrictMode not enabled. return; } ((AndroidBlockGuardPolicy) policy).onResourceMismatch(tag); } /** @hide */ public static void noteUnbufferedIO() { BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); if (!(policy instanceof AndroidBlockGuardPolicy)) { // StrictMode not enabled. return; } policy.onUnbufferedIO(); } /** @hide */ public static void noteDiskRead() { BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); if (!(policy instanceof AndroidBlockGuardPolicy)) { // StrictMode not enabled. return; } policy.onReadFromDisk(); } /** @hide */ public static void noteDiskWrite() { BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); if (!(policy instanceof AndroidBlockGuardPolicy)) { // StrictMode not enabled. return; } policy.onWriteToDisk(); } @GuardedBy("StrictMode.class") private static final HashMap sExpectedActivityInstanceCount = new HashMap<>(); /** * Returns an object that is used to track instances of activites. The activity should store a * reference to the tracker object in one of its fields. * * @hide */ public static Object trackActivity(Object instance) { return new InstanceTracker(instance); } /** @hide */ @UnsupportedAppUsage public static void incrementExpectedActivityCount(Class klass) { if (klass == null) { return; } synchronized (StrictMode.class) { if ((sVmPolicy.mask & DETECT_VM_ACTIVITY_LEAKS) == 0) { return; } // Use the instance count from InstanceTracker as initial value. Integer expected = sExpectedActivityInstanceCount.get(klass); Integer newExpected = expected == null ? InstanceTracker.getInstanceCount(klass) + 1 : expected + 1; sExpectedActivityInstanceCount.put(klass, newExpected); } } /** @hide */ public static void decrementExpectedActivityCount(Class klass) { if (klass == null) { return; } final int limit; synchronized (StrictMode.class) { if ((sVmPolicy.mask & DETECT_VM_ACTIVITY_LEAKS) == 0) { return; } Integer expected = sExpectedActivityInstanceCount.get(klass); int newExpected = (expected == null || expected == 0) ? 0 : expected - 1; if (newExpected == 0) { sExpectedActivityInstanceCount.remove(klass); } else { sExpectedActivityInstanceCount.put(klass, newExpected); } // Note: adding 1 here to give some breathing room during // orientation changes. (shouldn't be necessary, though?) limit = newExpected + 1; } // Quick check. int actual = InstanceTracker.getInstanceCount(klass); if (actual <= limit) { return; } // Do a GC and explicit count to double-check. // This is the work that we are trying to avoid by tracking the object instances // explicity. Running an explicit GC can be expensive (80ms) and so can walking // the heap to count instance (30ms). This extra work can make the system feel // noticeably less responsive during orientation changes when activities are // being restarted. Granted, it is only a problem when StrictMode is enabled // but it is annoying. System.gc(); System.runFinalization(); System.gc(); long instances = VMDebug.countInstancesOfClass(klass, false); if (instances > limit) { onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit)); } } /** * Parcelable that gets sent in Binder call headers back to callers to report violations that * happened during a cross-process call. * * @hide */ @TestApi public static final class ViolationInfo implements Parcelable { /** Stack and violation details. */ private final Violation mViolation; /** Path leading to a violation that occurred across binder. */ private final Deque mBinderStack = new ArrayDeque<>(); /** Memoized stack trace of full violation. */ @Nullable private String mStackTrace; /** The strict mode penalty mask at the time of violation. */ private final int mPenaltyMask; /** The wall time duration of the violation, when known. -1 when not known. */ public int durationMillis = -1; /** The number of animations currently running. */ public int numAnimationsRunning = 0; /** List of tags from active Span instances during this violation, or null for none. */ public String[] tags; /** * Which violation number this was (1-based) since the last Looper loop, from the * perspective of the root caller (if it crossed any processes via Binder calls). The value * is 0 if the root caller wasn't on a Looper thread. */ public int violationNumThisLoop; /** The time (in terms of SystemClock.uptimeMillis()) that the violation occurred. */ public long violationUptimeMillis; /** * The action of the Intent being broadcast to somebody's onReceive on this thread right * now, or null. */ public String broadcastIntentAction; /** If this is a instance count violation, the number of instances in memory, else -1. */ public long numInstances = -1; /** Create an instance of ViolationInfo initialized from an exception. */ ViolationInfo(Violation tr, int penaltyMask) { this.mViolation = tr; this.mPenaltyMask = penaltyMask; violationUptimeMillis = SystemClock.uptimeMillis(); this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount(); Intent broadcastIntent = ActivityThread.getIntentBeingBroadcast(); if (broadcastIntent != null) { broadcastIntentAction = broadcastIntent.getAction(); } ThreadSpanState state = sThisThreadSpanState.get(); if (tr instanceof InstanceCountViolation) { this.numInstances = ((InstanceCountViolation) tr).getNumberOfInstances(); } synchronized (state) { int spanActiveCount = state.mActiveSize; if (spanActiveCount > MAX_SPAN_TAGS) { spanActiveCount = MAX_SPAN_TAGS; } if (spanActiveCount != 0) { this.tags = new String[spanActiveCount]; Span iter = state.mActiveHead; int index = 0; while (iter != null && index < spanActiveCount) { this.tags[index] = iter.mName; index++; iter = iter.mNext; } } } } /** * Equivalent output to * {@link android.app.ApplicationErrorReport.CrashInfo#stackTrace}. */ public String getStackTrace() { if (mStackTrace == null) { StringWriter sw = new StringWriter(); PrintWriter pw = new FastPrintWriter(sw, false, 256); mViolation.printStackTrace(pw); for (StackTraceElement[] traces : mBinderStack) { pw.append("# via Binder call with stack:\n"); for (StackTraceElement traceElement : traces) { pw.append("\tat "); pw.append(traceElement.toString()); pw.append('\n'); } } pw.flush(); pw.close(); mStackTrace = sw.toString(); } return mStackTrace; } public Class getViolationClass() { return mViolation.getClass(); } /** * Optional message describing this violation. * * @hide */ @TestApi public String getViolationDetails() { return mViolation.getMessage(); } boolean penaltyEnabled(int p) { return (mPenaltyMask & p) != 0; } /** * Add a {@link Throwable} from the current process that caused the underlying violation. We * only preserve the stack trace elements. * * @hide */ void addLocalStack(Throwable t) { mBinderStack.addFirst(t.getStackTrace()); } @Override public int hashCode() { int result = 17; if (mViolation != null) { result = 37 * result + mViolation.hashCode(); } if (numAnimationsRunning != 0) { result *= 37; } if (broadcastIntentAction != null) { result = 37 * result + broadcastIntentAction.hashCode(); } if (tags != null) { for (String tag : tags) { result = 37 * result + tag.hashCode(); } } return result; } /** Create an instance of ViolationInfo initialized from a Parcel. */ @UnsupportedAppUsage public ViolationInfo(Parcel in) { this(in, false); } /** * Create an instance of ViolationInfo initialized from a Parcel. * * @param unsetGatheringBit if true, the caller is the root caller and the gathering penalty * should be removed. */ public ViolationInfo(Parcel in, boolean unsetGatheringBit) { mViolation = (Violation) in.readSerializable(); int binderStackSize = in.readInt(); for (int i = 0; i < binderStackSize; i++) { StackTraceElement[] traceElements = new StackTraceElement[in.readInt()]; for (int j = 0; j < traceElements.length; j++) { StackTraceElement element = new StackTraceElement( in.readString(), in.readString(), in.readString(), in.readInt()); traceElements[j] = element; } mBinderStack.add(traceElements); } int rawPenaltyMask = in.readInt(); if (unsetGatheringBit) { mPenaltyMask = rawPenaltyMask & ~PENALTY_GATHER; } else { mPenaltyMask = rawPenaltyMask; } durationMillis = in.readInt(); violationNumThisLoop = in.readInt(); numAnimationsRunning = in.readInt(); violationUptimeMillis = in.readLong(); numInstances = in.readLong(); broadcastIntentAction = in.readString(); tags = in.readStringArray(); } /** Save a ViolationInfo instance to a parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeSerializable(mViolation); dest.writeInt(mBinderStack.size()); for (StackTraceElement[] traceElements : mBinderStack) { dest.writeInt(traceElements.length); for (StackTraceElement element : traceElements) { dest.writeString(element.getClassName()); dest.writeString(element.getMethodName()); dest.writeString(element.getFileName()); dest.writeInt(element.getLineNumber()); } } int start = dest.dataPosition(); dest.writeInt(mPenaltyMask); dest.writeInt(durationMillis); dest.writeInt(violationNumThisLoop); dest.writeInt(numAnimationsRunning); dest.writeLong(violationUptimeMillis); dest.writeLong(numInstances); dest.writeString(broadcastIntentAction); dest.writeStringArray(tags); int total = dest.dataPosition() - start; if (Binder.CHECK_PARCEL_SIZE && total > 10 * 1024) { Slog.d( TAG, "VIO: penalty=" + mPenaltyMask + " dur=" + durationMillis + " numLoop=" + violationNumThisLoop + " anim=" + numAnimationsRunning + " uptime=" + violationUptimeMillis + " numInst=" + numInstances); Slog.d(TAG, "VIO: action=" + broadcastIntentAction); Slog.d(TAG, "VIO: tags=" + Arrays.toString(tags)); Slog.d(TAG, "VIO: TOTAL BYTES WRITTEN: " + (dest.dataPosition() - start)); } } /** Dump a ViolationInfo instance to a Printer. */ public void dump(Printer pw, String prefix) { pw.println(prefix + "stackTrace: " + getStackTrace()); pw.println(prefix + "penalty: " + mPenaltyMask); if (durationMillis != -1) { pw.println(prefix + "durationMillis: " + durationMillis); } if (numInstances != -1) { pw.println(prefix + "numInstances: " + numInstances); } if (violationNumThisLoop != 0) { pw.println(prefix + "violationNumThisLoop: " + violationNumThisLoop); } if (numAnimationsRunning != 0) { pw.println(prefix + "numAnimationsRunning: " + numAnimationsRunning); } pw.println(prefix + "violationUptimeMillis: " + violationUptimeMillis); if (broadcastIntentAction != null) { pw.println(prefix + "broadcastIntentAction: " + broadcastIntentAction); } if (tags != null) { int index = 0; for (String tag : tags) { pw.println(prefix + "tag[" + (index++) + "]: " + tag); } } } @Override public int describeContents() { return 0; } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public ViolationInfo createFromParcel(Parcel in) { return new ViolationInfo(in); } @Override public ViolationInfo[] newArray(int size) { return new ViolationInfo[size]; } }; } private static final class InstanceTracker { private static final HashMap, Integer> sInstanceCounts = new HashMap, Integer>(); private final Class mKlass; public InstanceTracker(Object instance) { mKlass = instance.getClass(); synchronized (sInstanceCounts) { final Integer value = sInstanceCounts.get(mKlass); final int newValue = value != null ? value + 1 : 1; sInstanceCounts.put(mKlass, newValue); } } @Override protected void finalize() throws Throwable { try { synchronized (sInstanceCounts) { final Integer value = sInstanceCounts.get(mKlass); if (value != null) { final int newValue = value - 1; if (newValue > 0) { sInstanceCounts.put(mKlass, newValue); } else { sInstanceCounts.remove(mKlass); } } } } finally { super.finalize(); } } public static int getInstanceCount(Class klass) { synchronized (sInstanceCounts) { final Integer value = sInstanceCounts.get(klass); return value != null ? value : 0; } } } }