Help for the debugging help for issue #8734824.

Add a new "hang" am command that lets you hang the system
process.  Useful for testing.

Change-Id: Ice0fc52b49d80e5189f016108b03f9fd549b58a7
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 93658e1..61fe340 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -31,6 +31,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -94,6 +95,7 @@
                 "       am set-debug-app [-w] [--persistent] <PACKAGE>\n" +
                 "       am clear-debug-app\n" +
                 "       am monitor [--gdb <port>]\n" +
+                "       am hang [--allow-restart]\n" +
                 "       am screen-compat [on|off] <PACKAGE>\n" +
                 "       am to-uri [INTENT]\n" +
                 "       am to-intent-uri [INTENT]\n" +
@@ -169,6 +171,9 @@
                 "am monitor: start monitoring for crashes or ANRs.\n" +
                 "    --gdb: start gdbserv on the given port at crash/ANR\n" +
                 "\n" +
+                "am hang: hang the system.\n" +
+                "    --allow-restart: allow watchdog to perform normal system restart\n" +
+                "\n" +
                 "am screen-compat: control screen compatibility mode of <PACKAGE>.\n" +
                 "\n" +
                 "am to-uri: print the given Intent specification as a URI.\n" +
@@ -249,6 +254,8 @@
             runBugReport();
         } else if (op.equals("monitor")) {
             runMonitor();
+        } else if (op.equals("hang")) {
+            runHang();
         } else if (op.equals("screen-compat")) {
             runScreenCompat();
         } else if (op.equals("to-uri")) {
@@ -1304,6 +1311,22 @@
         controller.run();
     }
 
+    private void runHang() throws Exception {
+        String opt;
+        boolean allowRestart = false;
+        while ((opt=nextOption()) != null) {
+            if (opt.equals("--allow-restart")) {
+                allowRestart = true;
+            } else {
+                System.err.println("Error: Unknown option: " + opt);
+                return;
+            }
+        }
+
+        System.out.println("Hanging the system...");
+        mAm.hang(new Binder(), allowRestart);
+    }
+
     private void runScreenCompat() throws Exception {
         String mode = nextArgRequired();
         boolean enabled;
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 98baa0e..d4478bf 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1870,6 +1870,15 @@
             return true;
         }
 
+        case HANG_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder who = data.readStrongBinder();
+            boolean allowRestart = data.readInt() != 0;
+            hang(who, allowRestart);
+            reply.writeNoException();
+            return true;
+        }
+
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -4270,5 +4279,17 @@
         reply.recycle();
     }
 
+    public void hang(IBinder who, boolean allowRestart) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(who);
+        data.writeInt(allowRestart ? 1 : 0);
+        mRemote.transact(HANG_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 33a2770..a21caee 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -377,6 +377,8 @@
 
     public void killUid(int uid, String reason) throws RemoteException;
 
+    public void hang(IBinder who, boolean allowRestart) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -638,4 +640,5 @@
     int GET_LAUNCHED_FROM_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+163;
     int KILL_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+164;
     int SET_USER_IS_MONKEY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+165;
+    int HANG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+166;
 }
diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java
index e784cf2..3aec4ea 100644
--- a/services/java/com/android/server/Watchdog.java
+++ b/services/java/com/android/server/Watchdog.java
@@ -95,6 +95,7 @@
 
     int mPhonePid;
     IActivityController mController;
+    boolean mAllowRestart = true;
 
     final Calendar mCalendar = Calendar.getInstance();
     int mMinScreenOff = MEMCHECK_DEFAULT_MIN_SCREEN_OFF;
@@ -233,6 +234,12 @@
         }
     }
 
+    public void setAllowRestart(boolean allowRestart) {
+        synchronized (this) {
+            mAllowRestart = allowRestart;
+        }
+    }
+
     public void addMonitor(Monitor monitor) {
         synchronized (this) {
             if (isAlive()) {
@@ -401,6 +408,7 @@
 
 
             final String name;
+            final boolean allowRestart;
             synchronized (this) {
                 long timeout = TIME_TO_WAIT;
 
@@ -437,6 +445,7 @@
 
                 name = (mCurrentMonitor != null) ?
                     mCurrentMonitor.getClass().getName() : "null";
+                allowRestart = mAllowRestart;
             }
 
             // If we got here, that means that the system is most likely hung.
@@ -506,12 +515,14 @@
             }
 
             // Only kill the process if the debugger is not attached.
-            if (!Debug.isDebuggerConnected()) {
+            if (Debug.isDebuggerConnected()) {
+                Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
+            } else if (!allowRestart) {
+                Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process");
+            } else {
                 Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + name);
                 Process.killProcess(Process.myPid());
                 System.exit(10);
-            } else {
-                Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
             }
 
             waitedHalf = false;
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index cbeed7b..0081dfc 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -7813,6 +7813,45 @@
         return killed;
     }
 
+    @Override
+    public void hang(final IBinder who, boolean allowRestart) {
+        if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+        }
+
+        final IBinder.DeathRecipient death = new DeathRecipient() {
+            @Override
+            public void binderDied() {
+                synchronized (this) {
+                    notifyAll();
+                }
+            }
+        };
+
+        try {
+            who.linkToDeath(death, 0);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "hang: given caller IBinder is already dead.");
+            return;
+        }
+
+        synchronized (this) {
+            Watchdog.getInstance().setAllowRestart(allowRestart);
+            Slog.i(TAG, "Hanging system process at request of pid " + Binder.getCallingPid());
+            synchronized (death) {
+                while (who.isBinderAlive()) {
+                    try {
+                        death.wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+            Watchdog.getInstance().setAllowRestart(true);
+        }
+    }
+
     public final void startRunning(String pkg, String cls, String action,
             String data) {
         synchronized(this) {