content: /*
   * 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.annotation.UnsupportedAppUsage;
  import android.app.ActivityManager;
  import android.app.ActivityThread;
  import android.app.IActivityManager;
  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.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.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.
   *
   * <p>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 <a
   * href="{@docRoot}guide/practices/design/responsiveness.html">ANR dialogs</a> from being shown to
   * users.
   *
   * <p class="note">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.
   *
   * <p>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:
   *
   * <pre>
   * 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();
   * }
   * </pre>
   *
   * <p>You can decide what should happen when a violation is detected. For example, using {@link
   * ThreadPolicy.Builder#penaltyLog} you can watch the output of <code>adb logcat</code> while you
   * use your application to see the violations as they happen.
   *
   * <p>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.
   *
   * <p class="note">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;
  
      // 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,
              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_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.
       *
       * <p>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<Class, Integer> EMPTY_CLASS_LIMIT_MAP =
              new HashMap<Class, Integer>();
  
      /** 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<OnThreadViolationListener> sThreadViolationListener =
              new ThreadLocal<>();
      private static final ThreadLocal<Executor> 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<String> sNonSdkApiUsageConsumer =
              message -> onVmPolicyViolation(new NonSdkApiUsedViolation(message));
  
      private StrictMode() {}
  
      /**
       * {@link StrictMode} policy applied to a certain thread.
       *
       * <p>The policy is enabled by {@link #setThreadPolicy}. The current policy can be retrieved
       * with {@link #getThreadPolicy}.
       *
       * <p>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.
           *
           * <p>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.
           *
           * <p>For example, detect everything and log anything that's found:
           *
           * <pre>
           * StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
           *     .detectAll()
           *     .penaltyLog()
           *     .build();
           * StrictMode.setThreadPolicy(policy);
           * </pre>
           */
          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.
               *
               * <p>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.
               *
               * <p>This helps detect accidental type mismatches and potentially expensive type
               * conversions when obtaining typed resources.
               *
               * <p>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.
               *
               * <p>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 <em>before</em> anything else. You must still have called {@link
               * #detectNetwork} to enable this.
               *
               * <p>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.
               *
               * <p>Note: if no penalties are enabled before calling <code>build</code>, {@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.
       *
       * <p>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<Class, Integer> classInstanceLimit;
  
          private VmPolicy(
                  @VmPolicyMask int mask,
                  HashMap<Class, Integer> 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.
           *
           * <p>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.
           *
           * <p>For example, detect everything and log anything that's found:
           *
           * <pre>
           * StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
           *     .detectAll()
           *     .penaltyLog()
           *     .build();
           * StrictMode.setVmPolicy(policy);
           * </pre>
           */
          public static final class Builder {
              @UnsupportedAppUsage
              private @VmPolicyMask int mMask;
              private OnVmViolationListener mListener;
              private Executor mExecutor;
  
              private HashMap<Class, Integer> 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<Class, Integer>) mClassInstanceLimit.clone();
                  } else if (mClassInstanceLimit == null) {
                      mClassInstanceLimit = new HashMap<Class, Integer>();
                  }
                  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() {
                  return disable(DETECT_VM_ACTIVITY_LEAKS);
              }
  
              /**
               * Detect reflective usage of APIs that are not part of the public Android SDK.
               *
               * <p>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 <b>only</b> 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.
               *
               * <p>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();
                  }
  
                  // 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.
               *
               * <p>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.
               *
               * <p>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.
               *
               * <p>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.
               *
               * <p>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.
               *
               * <p>Using {@link #penaltyDeath()} or {@link #penaltyDeathOnCleartextNetwork()} will
               * block further traffic on that socket to prevent accidental data leakage, in addition
               * to crashing your process.
               *
               * <p>Using {@link #penaltyDropBox()} will log the raw contents of the packet that
               * triggered the violation.
               *
               * <p>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}.
               *
               * <p>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.
               *
               * <p>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.
               * <p>
               * Apps becoming Direct Boot aware need to carefully inspect each
               * query site and explicitly decide which combination of flags they
               * want to use:
               * <ul>
               * <li>{@link PackageManager#MATCH_DIRECT_BOOT_AWARE}
               * <li>{@link PackageManager#MATCH_DIRECT_BOOT_UNAWARE}
               * <li>{@link PackageManager#MATCH_DIRECT_BOOT_AUTO}
               * </ul>
               */
              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.
               * <p>
               * 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#createCredentialProtectedStorageContext()
               * @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);
              }
  
              /**
               * 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.
               *
               * <p>Note: if no penalties are enabled before calling <code>build</code>, {@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<ArrayList<ViolationInfo>> gatheredViolations =
              new ThreadLocal<ArrayList<ViolationInfo>>() {
                  @Override
                  protected ArrayList<ViolationInfo> 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.
       *
       * <p>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 &amp; 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.
       *
       * <p>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<ArrayList<ViolationInfo>> violationsBeingTimed =
              new ThreadLocal<ArrayList<ViolationInfo>>() {
                  @Override
                  protected ArrayList<ViolationInfo> initialValue() {
                      return new ArrayList<ViolationInfo>();
                  }
              };
  
      // Note: only access this once verifying the thread has a Looper.
      private static final ThreadLocal<Handler> THREAD_HANDLER =
              new ThreadLocal<Handler>() {
                  @Override
                  protected Handler initialValue() {
                      return new Handler();
                  }
              };
  
      private static final ThreadLocal<AndroidBlockGuardPolicy> THREAD_ANDROID_POLICY =
              new ThreadLocal<AndroidBlockGuardPolicy>() {
                  @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<Integer, Long> 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<ViolationInfo> 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<ViolationInfo> 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;
              if (mLastViolationTime != null) {
                  Long vtime = mLastViolationTime.get(crashFingerprint);
                  if (vtime != null) {
                      lastViolationTime = vtime;
                  }
              } else {
                  mLastViolationTime = new ArrayMap<>(1);
              }
              long now = SystemClock.uptimeMillis();
              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) && lastViolationTime == 0) {
                  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 {
          public void report(String message, Throwable allocationSite) {
              onVmPolicyViolation(new LeakedClosableViolation(message, allocationSite));
          }
      }
  
      /** 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 */
      @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.
       *
       * <p>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 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());
      }
  
      /** 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<Integer, Long> sLastVmViolationTime = new HashMap<>();
  
      /** @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;
          synchronized (sLastVmViolationTime) {
              if (sLastVmViolationTime.containsKey(fingerprint)) {
                  lastViolationTime = sLastVmViolationTime.get(fingerprint);
                  timeSinceLastViolationMillis = now - lastViolationTime;
              }
              if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) {
                  sLastVmViolationTime.put(fingerprint, now);
              }
          }
          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<ViolationInfo> 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.)
       *
       * <p>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.
       *
       * <p>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<ThreadSpanState> sThisThreadSpanState =
              new ThreadLocal<ThreadSpanState>() {
                  @Override
                  protected ThreadSpanState initialValue() {
                      return new ThreadSpanState();
                  }
              };
  
      @UnsupportedAppUsage
      private static Singleton<IWindowManager> sWindowManager =
              new Singleton<IWindowManager>() {
                  protected IWindowManager create() {
                      return IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
                  }
              };
  
      /**
       * Enter a named critical span (e.g. an animation)
       *
       * <p>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.
       *
       * <p>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.
       *
       * <p>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<Class, Integer> 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;
              }
  
              Integer expected = sExpectedActivityInstanceCount.get(klass);
              Integer newExpected = expected == null ? 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<StackTraceElement[]> 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<? extends Violation> 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. */
          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<ViolationInfo> CREATOR =
                  new Parcelable.Creator<ViolationInfo>() {
                      @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<Class<?>, Integer> sInstanceCounts =
                  new HashMap<Class<?>, 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;
              }
          }
      }
  }
  