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) {