From 1c3196398b4f99ff80ddb1d2285218b0f936df48 Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Wed, 30 Aug 2017 19:39:09 -0400 Subject: [PATCH] Wrap all exceptions/crashes while plugins are active To help differentiate them from real crashes. Test: install crashing plugin Change-Id: I88d991ab8d86f71bcc7042012d4d84d8a9af19f3 Fixes: 64527191 --- .../plugins/PluginInstanceManager.java | 4 ++- .../systemui/plugins/PluginManagerImpl.java | 28 +++++++++++++++++-- .../systemui/plugins/PluginManagerTest.java | 6 ++-- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java index f66331525ab71..82c0128c10c00 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java @@ -136,11 +136,12 @@ public class PluginInstanceManager { return disableAny; } - public void disableAll() { + public boolean disableAll() { ArrayList plugins = new ArrayList<>(mPluginHandler.mPlugins); for (int i = 0; i < plugins.size(); i++) { disable(plugins.get(i)); } + return plugins.size() != 0; } private void disable(PluginInfo info) { @@ -182,6 +183,7 @@ public class PluginInstanceManager { if (DEBUG) Log.d(TAG, "onPluginConnected"); PluginPrefs.setHasPlugins(mContext); PluginInfo info = (PluginInfo) msg.obj; + mManager.handleWtfs(); if (!(msg.obj instanceof PluginFragment)) { // Only call onDestroy for plugins that aren't fragments, as fragments // will get the onCreate as part of the fragment lifecycle. diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java index 493d244f5e991..03747d50a6fa4 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java @@ -36,6 +36,9 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; +import android.util.Log.TerribleFailure; +import android.util.Log.TerribleFailureHandler; import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; @@ -71,10 +74,11 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage private boolean mListening; private boolean mHasOneShot; private Looper mLooper; + private boolean mWtfsSet; public PluginManagerImpl(Context context) { this(context, new PluginInstanceManagerFactory(), - Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler()); + Build.IS_DEBUGGABLE, Thread.getUncaughtExceptionPreHandler()); } @VisibleForTesting @@ -88,7 +92,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler( defaultHandler); - Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); + Thread.setUncaughtExceptionPreHandler(uncaughtExceptionHandler); if (isDebuggable) { new Handler(mLooper).post(() -> { // Plugin dependencies that don't have another good home can go here, but @@ -290,6 +294,15 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage return false; } + public void handleWtfs() { + if (!mWtfsSet) { + mWtfsSet = true; + Log.setWtfHandler((tag, what, system) -> { + throw new CrashWhilePluginActiveException(what); + }); + } + } + @VisibleForTesting public static class PluginInstanceManagerFactory { public PluginInstanceManager createPluginInstanceManager(Context context, @@ -339,9 +352,12 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage // disable all the plugins, so we can be sure that SysUI is running as // best as possible. for (PluginInstanceManager manager : mPluginMap.values()) { - manager.disableAll(); + disabledAny |= manager.disableAll(); } } + if (disabledAny) { + throwable = new CrashWhilePluginActiveException(throwable); + } // Run the normal exception handler so we can crash and cleanup our state. mHandler.uncaughtException(thread, throwable); @@ -358,4 +374,10 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage return disabledAny | checkStack(throwable.getCause()); } } + + private class CrashWhilePluginActiveException extends RuntimeException { + public CrashWhilePluginActiveException(Throwable throwable) { + super(throwable); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java index b8e9fcd290969..94dbc2ad7147e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java @@ -67,7 +67,7 @@ public class PluginManagerTest extends SysuiTestCase { public void setup() throws Exception { mDependency.injectTestDependency(Dependency.BG_LOOPER, TestableLooper.get(this).getLooper()); - mRealExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + mRealExceptionHandler = Thread.getUncaughtExceptionPreHandler(); mMockExceptionHandler = mock(UncaughtExceptionHandler.class); mMockFactory = mock(PluginInstanceManagerFactory.class); mMockPluginInstance = mock(PluginInstanceManager.class); @@ -167,9 +167,9 @@ public class PluginManagerTest extends SysuiTestCase { } private void resetExceptionHandler() { - mPluginExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + mPluginExceptionHandler = Thread.getUncaughtExceptionPreHandler(); // Set back the real exception handler so the test can crash if it wants to. - Thread.setDefaultUncaughtExceptionHandler(mRealExceptionHandler); + Thread.setUncaughtExceptionPreHandler(mRealExceptionHandler); } @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)