Introduce "StrictMode"

This is a new public API for developers to opt-in to strict rules
about what they're allowed to do on certain threads.  (this is the
public face of the @hide dalvik.system.BlockGuard, added recently...)

In practice this will be used for developers to opt-in to declaring
that they don't want to be allowed to do various operations (such as
disk I/O or network operations) on their main UI threads.  (these
operations are often accidental, or even when they are fast come with
a good chance of being slow or very slow in some cases....)

Implementation wise, this is just a thread-local integer that has a
bitmask of the things that aren't allowed, and more bits for saying
what the violation penalty is.  The penalties, of which multiple can
be chosen, include:

  * logging
  * dropbox uploading for analysis/reporting
  * annoying dialog
  * full-on crashing

These are all only very roughly implemented at this point, but all
parts now minimally work end-to-end now, so this is a good checkpoint
commit before this gets too large.

Future CLs will polish all the above 4 penalties, including
checksumming of stacktraces and minimizing penalties for duplicate
violations.

Change-Id: Icbe61a2e950119519e7364030b10c3c28d243abe
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index b78d22f..b4c7edc 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1062,6 +1062,15 @@
             return true;
         }
 
+        case HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder app = data.readStrongBinder();
+            ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(data);
+            handleApplicationStrictModeViolation(app, ci);
+            reply.writeNoException();
+            return true;
+        }
+
         case SIGNAL_PERSISTENT_PROCESSES_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             int sig = data.readInt();
@@ -2523,6 +2532,7 @@
         reply.recycle();
         data.recycle();
     }
+
     public boolean handleApplicationWtf(IBinder app, String tag,
             ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException
     {
@@ -2540,6 +2550,20 @@
         return res;
     }
 
+    public void handleApplicationStrictModeViolation(IBinder app,
+            ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(app);
+        crashInfo.writeToParcel(data, 0);
+        mRemote.transact(HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        reply.recycle();
+        data.recycle();
+    }
+
     public void signalPersistentProcesses(int sig) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 773c344..49f1a8f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -42,6 +42,7 @@
 import android.database.sqlite.SQLiteDebug.DbStats;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.Handler;
@@ -53,6 +54,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.StrictMode;
 import android.os.SystemClock;
 import android.util.AndroidRuntimeException;
 import android.util.Config;
@@ -4132,6 +4134,20 @@
         data.info = getPackageInfoNoCheck(data.appInfo);
 
         /**
+         * For system applications on userdebug/eng builds, log stack
+         * traces of disk and network access to dropbox for analysis.
+         */
+        if ((data.appInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0 &&
+            !"user".equals(Build.TYPE)) {
+            StrictMode.setDropBoxManager(ContextImpl.createDropBoxManager());
+            StrictMode.setThreadBlockingPolicy(
+                StrictMode.DISALLOW_DISK_WRITE |
+                StrictMode.DISALLOW_DISK_READ |
+                StrictMode.DISALLOW_NETWORK |
+                StrictMode.PENALTY_DROPBOX);
+        }
+
+        /**
          * Switch this process to density compatibility mode if needed.
          */
         if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 11b7b02..bcdfe59 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1157,12 +1157,16 @@
         return mAudioManager;
     }
 
+    /* package */ static DropBoxManager createDropBoxManager() {
+        IBinder b = ServiceManager.getService(DROPBOX_SERVICE);
+        IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b);
+        return new DropBoxManager(service);
+    }
+
     private DropBoxManager getDropBoxManager() {
         synchronized (mSync) {
             if (mDropBoxManager == null) {
-                IBinder b = ServiceManager.getService(DROPBOX_SERVICE);
-                IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b);
-                mDropBoxManager = new DropBoxManager(service);
+                mDropBoxManager = createDropBoxManager();
             }
         }
         return mDropBoxManager;
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index cd24fa6..ca09290 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -256,7 +256,9 @@
             ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException;
     public boolean handleApplicationWtf(IBinder app, String tag,
             ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException;
-    
+    public void handleApplicationStrictModeViolation(IBinder app,
+            ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException;
+
     /*
      * This will deliver the specified signal to all the persistent processes. Currently only 
      * SIGUSR1 is delivered. All others are ignored.
@@ -516,4 +518,5 @@
     int START_ACTIVITY_WITH_CONFIG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+106;
     int GET_RUNNING_EXTERNAL_APPLICATIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+107;
     int FINISH_HEAVY_WEIGHT_APP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+108;
+    int HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+109;
 }
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
new file mode 100644
index 0000000..876ec39
--- /dev/null
+++ b/core/java/android/os/StrictMode.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.app.ActivityManagerNative;
+import android.app.ApplicationErrorReport;
+import android.util.Log;
+
+import com.android.internal.os.RuntimeInit;
+
+import dalvik.system.BlockGuard;
+
+/**
+ * <p>StrictMode lets you impose stricter rules under which your
+ * application runs.</p>
+ */
+public final class StrictMode {
+    private static final String TAG = "StrictMode";
+
+    private StrictMode() {}
+
+    public static final int DISALLOW_DISK_WRITE = 0x01;
+    public static final int DISALLOW_DISK_READ = 0x02;
+    public static final int DISALLOW_NETWORK = 0x04;
+
+    /** @hide */
+    public static final int DISALLOW_MASK =
+            DISALLOW_DISK_WRITE | DISALLOW_DISK_READ | DISALLOW_NETWORK;
+
+    /**
+     * Flag to log to the system log.
+     */
+    public static final int PENALTY_LOG = 0x10;  // normal android.util.Log
+
+    /**
+     * Show an annoying dialog to the user.  Will be rate-limited to be only
+     * a little annoying.
+     */
+    public static final int PENALTY_DIALOG = 0x20;
+
+    /**
+     * Crash hard if policy is violated.
+     */
+    public static final int PENALTY_DEATH = 0x40;
+
+    /**
+     * Log a stacktrace to the DropBox on policy violation.
+     */
+    public static final int PENALTY_DROPBOX = 0x80;
+
+    /** @hide */
+    public static final int PENALTY_MASK =
+            PENALTY_LOG | PENALTY_DIALOG |
+            PENALTY_DROPBOX | PENALTY_DEATH;
+
+    /**
+     * Sets the policy for what actions the current thread is denied,
+     * as well as the penalty for violating the policy.
+     *
+     * @param policyMask a bitmask of DISALLOW_* and PENALTY_* values.
+     */
+    public static void setThreadBlockingPolicy(final int policyMask) {
+        BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+        if (!(policy instanceof AndroidBlockGuardPolicy)) {
+            BlockGuard.setThreadPolicy(new AndroidBlockGuardPolicy(policyMask));
+        } else {
+            AndroidBlockGuardPolicy androidPolicy = (AndroidBlockGuardPolicy) policy;
+            androidPolicy.setPolicyMask(policyMask);
+        }
+    }
+
+    /**
+     * Returns the bitmask of the current thread's blocking policy.
+     *
+     * @return the bitmask of all the DISALLOW_* and PENALTY_* bits currently enabled
+     */
+    public static int getThreadBlockingPolicy() {
+        return BlockGuard.getThreadPolicy().getPolicyMask();
+    }
+
+    /** @hide */
+    public static void setDropBoxManager(DropBoxManager dropBoxManager) {
+        BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+        if (!(policy instanceof AndroidBlockGuardPolicy)) {
+            policy = new AndroidBlockGuardPolicy(0);
+            BlockGuard.setThreadPolicy(policy);
+        }
+        ((AndroidBlockGuardPolicy) policy).setDropBoxManager(dropBoxManager);
+    }
+
+    private static class AndroidBlockGuardPolicy implements BlockGuard.Policy {
+        private int mPolicyMask;
+        private DropBoxManager mDropBoxManager = null;
+
+        public AndroidBlockGuardPolicy(final int policyMask) {
+            mPolicyMask = policyMask;
+        }
+
+        // Part of BlockGuard.Policy interface:
+        public int getPolicyMask() {
+            return mPolicyMask;
+        }
+
+        // Part of BlockGuard.Policy interface:
+        public void onWriteToDisk() {
+            if ((mPolicyMask & DISALLOW_DISK_WRITE) == 0) {
+                return;
+            }
+            handleViolation(DISALLOW_DISK_WRITE);
+        }
+
+        // Part of BlockGuard.Policy interface:
+        public void onReadFromDisk() {
+            if ((mPolicyMask & DISALLOW_DISK_READ) == 0) {
+                return;
+            }
+            handleViolation(DISALLOW_DISK_READ);
+        }
+
+        // Part of BlockGuard.Policy interface:
+        public void onNetwork() {
+            if ((mPolicyMask & DISALLOW_NETWORK) == 0) {
+                return;
+            }
+            handleViolation(DISALLOW_NETWORK);
+        }
+
+        public void setPolicyMask(int policyMask) {
+            mPolicyMask = policyMask;
+        }
+
+        public void setDropBoxManager(DropBoxManager dropBoxManager) {
+            mDropBoxManager = dropBoxManager;
+        }
+
+        private void handleViolation(int violationBit) {
+            final BlockGuard.BlockGuardPolicyException violation =
+                    new BlockGuard.BlockGuardPolicyException(mPolicyMask, violationBit);
+            violation.fillInStackTrace();
+
+            Looper looper = Looper.myLooper();
+            if (looper == null) {
+                // Without a Looper, we're unable to time how long the
+                // violation takes place.  This case should be rare,
+                // as most users will care about timing violations
+                // that happen on their main UI thread.
+                handleViolationWithTime(violation, -1L /* no time */);
+            } else {
+                MessageQueue queue = Looper.myQueue();
+                final long violationTime = SystemClock.uptimeMillis();
+                queue.addIdleHandler(new MessageQueue.IdleHandler() {
+                        public boolean queueIdle() {
+                            long afterViolationTime = SystemClock.uptimeMillis();
+                            handleViolationWithTime(violation, afterViolationTime - violationTime);
+                            return false;  // remove this idle handler from the array
+                        }
+                    });
+            }
+        }
+
+        private void handleViolationWithTime(
+            BlockGuard.BlockGuardPolicyException violation,
+            long durationMillis) {
+
+            // It's possible (even quite likely) that mPolicyMask has
+            // changed from the time the violation fired and now
+            // (after the violating code ran) due to people who
+            // push/pop temporary policy in regions of code.  So use
+            // the old policy here.
+            int policy = violation.getPolicy();
+
+            if ((policy & PENALTY_LOG) != 0) {
+                if (durationMillis != -1) {
+                    Log.d(TAG, "StrictMode policy violation; ~duration=" + durationMillis + " ms",
+                          violation);
+                } else {
+                    Log.d(TAG, "StrictMode policy violation.", violation);
+                }
+            }
+
+            if ((policy & PENALTY_DIALOG) != 0) {
+                // Currently this is just used for the dialog.
+                try {
+                    ActivityManagerNative.getDefault().handleApplicationStrictModeViolation(
+                        RuntimeInit.getApplicationObject(),
+                        new ApplicationErrorReport.CrashInfo(violation));
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException trying to open strict mode dialog", e);
+                }
+            }
+
+            if ((policy & PENALTY_DROPBOX) != 0) {
+                // TODO: call into ActivityManagerNative to do the dropboxing.
+                // But do the first-layer signature dup-checking first client-side.
+                // This conditional should be combined with the above, too, along
+                // with PENALTY_DEATH below.
+            }
+
+            if ((policy & PENALTY_DEATH) != 0) {
+                System.err.println("StrictMode policy violation with POLICY_DEATH; shutting down.");
+                Process.killProcess(Process.myPid());
+                System.exit(10);
+            }
+        }
+    }
+}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index ac23832..bf751f5 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -238,7 +238,7 @@
  *
  * <p>The screen density of a device is based on the screen resolution. A screen with low density
  * has fewer available pixels per inch, where a screen with high density
- * has more — sometimes significantly more — pixels per inch. The density of a
+ * has more &mdash; sometimes significantly more &mdash; pixels per inch. The density of a
  * screen is important because, other things being equal, a UI element (such as a button) whose
  * height and width are defined in terms of screen pixels will appear larger on the lower density
  * screen and smaller on the higher density screen.
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 599a7fe..59600dc 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -342,6 +342,10 @@
         mApplicationObject = app;
     }
 
+    public static final IBinder getApplicationObject() {
+        return mApplicationObject;
+    }
+
     /**
      * Enable debugging features.
      */
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fc1db18..3c1fdbc 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1917,7 +1917,7 @@
     <string name="noApplications">No applications can perform this action.</string>
     <!-- Title of the alert when an application has crashed. -->
     <string name="aerr_title">Sorry!</string>
-    <!-- Text of the alert that is displayed when an application is not responding. -->
+    <!-- Text of the alert that is displayed when an application has crashed. -->
     <string name="aerr_application">The application <xliff:g id="application">%1$s</xliff:g>
         (process <xliff:g id="process">%2$s</xliff:g>) has stopped unexpectedly. Please try again.</string>
     <!-- Text of the alert that is displayed when an application has crashed. -->
@@ -1940,6 +1940,13 @@
     <!-- Button allowing the user to choose to wait for an application that is not responding to become responsive again. -->
     <string name="wait">Wait</string>
 
+    <!-- Text of the alert that is displayed when an application has violated StrictMode. -->
+    <string name="smv_application">The application <xliff:g id="application">%1$s</xliff:g>
+        (process <xliff:g id="process">%2$s</xliff:g>) has violated its self-enforced StrictMode policy.</string>
+    <!-- Text of the alert that is displayed when an application has violated StrictMode. -->
+    <string name="smv_process">The process <xliff:g id="process">%1$s</xliff:g> has
+      has violated its self-enforced StrictMode policy.</string>
+
     <!-- Notification text to tell the user that a heavy-weight application is running. -->
     <string name="heavy_weight_notification"><xliff:g id="app">%1$s</xliff:g> running</string>