Merge "Wrap all exceptions/crashes while plugins are active" into oc-mr1-dev

This commit is contained in:
Jason Monk
2017-08-31 18:50:13 +00:00
committed by Android (Google) Code Review
3 changed files with 31 additions and 7 deletions

View File

@@ -136,11 +136,12 @@ public class PluginInstanceManager<T extends Plugin> {
return disableAny; return disableAny;
} }
public void disableAll() { public boolean disableAll() {
ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins); ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
for (int i = 0; i < plugins.size(); i++) { for (int i = 0; i < plugins.size(); i++) {
disable(plugins.get(i)); disable(plugins.get(i));
} }
return plugins.size() != 0;
} }
private void disable(PluginInfo info) { private void disable(PluginInfo info) {
@@ -182,6 +183,7 @@ public class PluginInstanceManager<T extends Plugin> {
if (DEBUG) Log.d(TAG, "onPluginConnected"); if (DEBUG) Log.d(TAG, "onPluginConnected");
PluginPrefs.setHasPlugins(mContext); PluginPrefs.setHasPlugins(mContext);
PluginInfo<T> info = (PluginInfo<T>) msg.obj; PluginInfo<T> info = (PluginInfo<T>) msg.obj;
mManager.handleWtfs();
if (!(msg.obj instanceof PluginFragment)) { if (!(msg.obj instanceof PluginFragment)) {
// Only call onDestroy for plugins that aren't fragments, as fragments // Only call onDestroy for plugins that aren't fragments, as fragments
// will get the onCreate as part of the fragment lifecycle. // will get the onCreate as part of the fragment lifecycle.

View File

@@ -36,6 +36,9 @@ import android.os.UserHandle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.Log;
import android.util.Log.TerribleFailure;
import android.util.Log.TerribleFailureHandler;
import android.widget.Toast; import android.widget.Toast;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
@@ -71,10 +74,11 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
private boolean mListening; private boolean mListening;
private boolean mHasOneShot; private boolean mHasOneShot;
private Looper mLooper; private Looper mLooper;
private boolean mWtfsSet;
public PluginManagerImpl(Context context) { public PluginManagerImpl(Context context) {
this(context, new PluginInstanceManagerFactory(), this(context, new PluginInstanceManagerFactory(),
Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler()); Build.IS_DEBUGGABLE, Thread.getUncaughtExceptionPreHandler());
} }
@VisibleForTesting @VisibleForTesting
@@ -88,7 +92,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler( PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
defaultHandler); defaultHandler);
Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); Thread.setUncaughtExceptionPreHandler(uncaughtExceptionHandler);
if (isDebuggable) { if (isDebuggable) {
new Handler(mLooper).post(() -> { new Handler(mLooper).post(() -> {
// Plugin dependencies that don't have another good home can go here, but // 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; return false;
} }
public void handleWtfs() {
if (!mWtfsSet) {
mWtfsSet = true;
Log.setWtfHandler((tag, what, system) -> {
throw new CrashWhilePluginActiveException(what);
});
}
}
@VisibleForTesting @VisibleForTesting
public static class PluginInstanceManagerFactory { public static class PluginInstanceManagerFactory {
public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context, public <T extends Plugin> 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 // disable all the plugins, so we can be sure that SysUI is running as
// best as possible. // best as possible.
for (PluginInstanceManager manager : mPluginMap.values()) { 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. // Run the normal exception handler so we can crash and cleanup our state.
mHandler.uncaughtException(thread, throwable); mHandler.uncaughtException(thread, throwable);
@@ -358,4 +374,10 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
return disabledAny | checkStack(throwable.getCause()); return disabledAny | checkStack(throwable.getCause());
} }
} }
private class CrashWhilePluginActiveException extends RuntimeException {
public CrashWhilePluginActiveException(Throwable throwable) {
super(throwable);
}
}
} }

View File

@@ -67,7 +67,7 @@ public class PluginManagerTest extends SysuiTestCase {
public void setup() throws Exception { public void setup() throws Exception {
mDependency.injectTestDependency(Dependency.BG_LOOPER, mDependency.injectTestDependency(Dependency.BG_LOOPER,
TestableLooper.get(this).getLooper()); TestableLooper.get(this).getLooper());
mRealExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); mRealExceptionHandler = Thread.getUncaughtExceptionPreHandler();
mMockExceptionHandler = mock(UncaughtExceptionHandler.class); mMockExceptionHandler = mock(UncaughtExceptionHandler.class);
mMockFactory = mock(PluginInstanceManagerFactory.class); mMockFactory = mock(PluginInstanceManagerFactory.class);
mMockPluginInstance = mock(PluginInstanceManager.class); mMockPluginInstance = mock(PluginInstanceManager.class);
@@ -167,9 +167,9 @@ public class PluginManagerTest extends SysuiTestCase {
} }
private void resetExceptionHandler() { private void resetExceptionHandler() {
mPluginExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); mPluginExceptionHandler = Thread.getUncaughtExceptionPreHandler();
// Set back the real exception handler so the test can crash if it wants to. // 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) @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)