Track when plugins are disabled due to crashes.

Bug: 120901833
Change-Id: I41e5c54d059b631befac235b1d3929200764860c
Test: atest SystemUITests
This commit is contained in:
Dave Mankoff
2018-12-12 17:11:30 -05:00
parent b3c1557892
commit fd2c695e05
7 changed files with 123 additions and 56 deletions

View File

@@ -14,12 +14,40 @@
package com.android.systemui.shared.plugins;
import android.annotation.IntDef;
import android.content.ComponentName;
/**
* Enables and disables plugins.
*/
public interface PluginEnabler {
void setEnabled(ComponentName component, boolean enabled);
int ENABLED = 0;
int DISABLED_MANUALLY = 1;
int DISABLED_INVALID_VERSION = 1;
int DISABLED_FROM_EXPLICIT_CRASH = 2;
int DISABLED_FROM_SYSTEM_CRASH = 3;
@IntDef({ENABLED, DISABLED_MANUALLY, DISABLED_INVALID_VERSION, DISABLED_FROM_EXPLICIT_CRASH,
DISABLED_FROM_SYSTEM_CRASH})
@interface DisableReason {
}
/** Enables plugin via the PackageManager. */
void setEnabled(ComponentName component);
/** Disables a plugin via the PackageManager and records the reason for disabling. */
void setDisabled(ComponentName component, @DisableReason int reason);
/** Returns true if the plugin is enabled in the PackageManager. */
boolean isEnabled(ComponentName component);
/**
* Returns the reason that a plugin is disabled, (if it is).
*
* It should return {@link #ENABLED} if the plugin is turned on.
* It should return {@link #DISABLED_MANUALLY} if the plugin is off but the reason is unknown.
*/
@DisableReason
int getDisableReason(ComponentName componentName);
}

View File

@@ -136,7 +136,7 @@ public class PluginInstanceManager<T extends Plugin> {
ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins);
for (PluginInfo info : plugins) {
if (className.startsWith(info.mPackage)) {
disable(info);
disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
disableAny = true;
}
}
@@ -146,12 +146,13 @@ public class PluginInstanceManager<T extends Plugin> {
public boolean disableAll() {
ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins);
for (int i = 0; i < plugins.size(); i++) {
disable(plugins.get(i));
disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
}
return plugins.size() != 0;
}
private void disable(PluginInfo info) {
private void disable(PluginInfo info,
@PluginEnabler.DisableReason int reason) {
// Live by the sword, die by the sword.
// Misbehaving plugins get disabled and won't come back until uninstall/reinstall.
@@ -162,9 +163,9 @@ public class PluginInstanceManager<T extends Plugin> {
// Don't disable whitelisted plugins as they are a part of the OS.
return;
}
Log.w(TAG, "Disabling plugin " + info.mPackage + "/" + info.mClass);
mManager.getPluginEnabler().setEnabled(new ComponentName(info.mPackage, info.mClass),
false);
ComponentName pluginComponent = new ComponentName(info.mPackage, info.mClass);
Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString());
mManager.getPluginEnabler().setDisabled(pluginComponent, reason);
}
public <T> boolean dependsOn(Plugin p, Class<T> cls) {

View File

@@ -184,6 +184,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
mListening = true;
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(PLUGIN_CHANGED);
filter.addAction(DISABLE_PLUGIN);
@@ -214,12 +215,13 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
// Don't disable whitelisted plugins as they are a part of the OS.
return;
}
getPluginEnabler().setEnabled(component, false);
getPluginEnabler().setDisabled(component, PluginEnabler.DISABLED_INVALID_VERSION);
mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(),
SystemMessage.NOTE_PLUGIN);
} else {
Uri data = intent.getData();
String pkg = data.getEncodedSchemeSpecificPart();
ComponentName componentName = ComponentName.unflattenFromString(pkg);
if (mOneShotPackages.contains(pkg)) {
int icon = mContext.getResources().getIdentifier("tuner", "drawable",
mContext.getPackageName());
@@ -256,6 +258,17 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
Log.v(TAG, "Reloading " + pkg);
}
}
if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
@PluginEnabler.DisableReason int disableReason =
getPluginEnabler().getDisableReason(componentName);
if (disableReason == PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH
|| disableReason == PluginEnabler.DISABLED_FROM_SYSTEM_CRASH
|| disableReason == PluginEnabler.DISABLED_INVALID_VERSION) {
Log.i(TAG, "Re-enabling previously disabled plugin that has been "
+ "updated: " + componentName.flattenToShortString());
getPluginEnabler().setEnabled(componentName);
}
}
if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
for (PluginInstanceManager manager : mPluginMap.values()) {
manager.onPackageChange(pkg);

View File

@@ -16,28 +16,44 @@ package com.android.systemui.plugins;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.shared.plugins.PluginEnabler;
public class PluginEnablerImpl implements PluginEnabler {
private static final String CRASH_DISABLED_PLUGINS_PREF_FILE = "auto_disabled_plugins_prefs";
final private PackageManager mPm;
private PackageManager mPm;
private final SharedPreferences mAutoDisabledPrefs;
public PluginEnablerImpl(Context context) {
this(context.getPackageManager());
this(context, context.getPackageManager());
}
@VisibleForTesting public PluginEnablerImpl(PackageManager pm) {
@VisibleForTesting public PluginEnablerImpl(Context context, PackageManager pm) {
mAutoDisabledPrefs = context.getSharedPreferences(
CRASH_DISABLED_PLUGINS_PREF_FILE, Context.MODE_PRIVATE);
mPm = pm;
}
@Override
public void setEnabled(ComponentName component, boolean enabled) {
public void setEnabled(ComponentName component) {
setDisabled(component, ENABLED);
}
@Override
public void setDisabled(ComponentName component, @DisableReason int reason) {
boolean enabled = reason == ENABLED;
final int desiredState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
mPm.setComponentEnabledSetting(component, desiredState, PackageManager.DONT_KILL_APP);
if (enabled) {
mAutoDisabledPrefs.edit().remove(component.flattenToString()).apply();
} else {
mAutoDisabledPrefs.edit().putInt(component.flattenToString(), reason).apply();
}
}
@Override
@@ -45,4 +61,12 @@ public class PluginEnablerImpl implements PluginEnabler {
return mPm.getComponentEnabledSetting(component)
!= PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
}
@Override
public @DisableReason int getDisableReason(ComponentName componentName) {
if (isEnabled(componentName)) {
return ENABLED;
}
return mAutoDisabledPrefs.getInt(componentName.flattenToString(), DISABLED_MANUALLY);
}
}

View File

@@ -184,7 +184,11 @@ public class PluginFragment extends PreferenceFragment {
mInfo.services[i].name);
if (mPluginEnabler.isEnabled(componentName) != isEnabled) {
mPluginEnabler.setEnabled(componentName, isEnabled);
if (isEnabled) {
mPluginEnabler.setEnabled(componentName);
} else {
mPluginEnabler.setDisabled(componentName, PluginEnabler.DISABLED_MANUALLY);
}
shouldSendBroadcast = true;
}
}

View File

@@ -17,6 +17,7 @@ package com.android.systemui.shared.plugins;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
@@ -26,22 +27,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginEnablerImpl;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo;
import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
import com.android.systemui.plugins.annotations.Requires;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
@@ -60,6 +45,21 @@ import android.support.test.annotation.UiThreadTest;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.annotations.Requires;
import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo;
import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -79,6 +79,9 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
private PluginInstanceManager mPluginInstanceManager;
private PluginManagerImpl mMockManager;
private VersionInfo mMockVersionInfo;
private PluginEnabler mMockEnabler;
ComponentName mTestPluginComponentName =
new ComponentName(WHITELISTED_PACKAGE, TestPlugin.class.getName());
@Before
public void setup() throws Exception {
@@ -88,9 +91,9 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
mMockPm = mock(PackageManager.class);
mMockListener = mock(PluginListener.class);
mMockManager = mock(PluginManagerImpl.class);
when(mMockManager.getClassLoader(any(), any()))
.thenReturn(getClass().getClassLoader());
when(mMockManager.getPluginEnabler()).thenReturn(new PluginEnablerImpl(mMockPm));
when(mMockManager.getClassLoader(any(), any())).thenReturn(getClass().getClassLoader());
mMockEnabler = mock(PluginEnabler.class);
when(mMockManager.getPluginEnabler()).thenReturn(mMockEnabler);
mMockVersionInfo = mock(VersionInfo.class);
mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
@@ -230,18 +233,13 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
// Start with an unrelated class.
boolean result = mPluginInstanceManager.checkAndDisable(Activity.class.getName());
assertFalse(result);
verify(mMockPm, never()).setComponentEnabledSetting(
ArgumentCaptor.forClass(ComponentName.class).capture(),
ArgumentCaptor.forClass(int.class).capture(),
ArgumentCaptor.forClass(int.class).capture());
verify(mMockEnabler, never()).setDisabled(any(ComponentName.class), anyInt());
// Now hand it a real class and make sure it disables the plugin.
result = mPluginInstanceManager.checkAndDisable(TestPlugin.class.getName());
assertTrue(result);
verify(mMockPm).setComponentEnabledSetting(
ArgumentCaptor.forClass(ComponentName.class).capture(),
ArgumentCaptor.forClass(int.class).capture(),
ArgumentCaptor.forClass(int.class).capture());
verify(mMockEnabler).setDisabled(
mTestPluginComponentName, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
}
@Test
@@ -250,10 +248,8 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
mPluginInstanceManager.disableAll();
verify(mMockPm).setComponentEnabledSetting(
ArgumentCaptor.forClass(ComponentName.class).capture(),
ArgumentCaptor.forClass(int.class).capture(),
ArgumentCaptor.forClass(int.class).capture());
verify(mMockEnabler).setDisabled(
mTestPluginComponentName, PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
}
@Test
@@ -275,8 +271,8 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
List<ResolveInfo> list = new ArrayList<>();
ResolveInfo info = new ResolveInfo();
info.serviceInfo = mock(ServiceInfo.class);
info.serviceInfo.packageName = "com.android.systemui";
info.serviceInfo.name = TestPlugin.class.getName();
info.serviceInfo.packageName = mTestPluginComponentName.getPackageName();
info.serviceInfo.name = mTestPluginComponentName.getClassName();
when(info.serviceInfo.loadLabel(any())).thenReturn("Test Plugin");
list.add(info);
when(mMockPm.queryIntentServices(any(), Mockito.anyInt())).thenReturn(list);
@@ -288,6 +284,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
ApplicationInfo appInfo = getContext().getApplicationInfo();
when(mMockPm.getApplicationInfo(Mockito.anyString(), Mockito.anyInt())).thenReturn(
appInfo);
when(mMockEnabler.isEnabled(mTestPluginComponentName)).thenReturn(true);
}
private void createPlugin() throws Exception {

View File

@@ -27,7 +27,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -82,17 +81,18 @@ public class PluginManagerTest extends SysuiTestCase {
.thenReturn(mMockPluginInstance);
mMockPackageManager = mock(PackageManager.class);
mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, true,
mPluginManager = new PluginManagerImpl(
getContext(), mMockFactory, true,
mMockExceptionHandler, new PluginInitializerImpl() {
@Override
public String[] getWhitelistedPlugins(Context context) {
return new String[0];
}
@Override
public String[] getWhitelistedPlugins(Context context) {
return new String[0];
}
@Override
public PluginEnabler getPluginEnabler(Context context) {
return new PluginEnablerImpl(mMockPackageManager);
}
@Override
public PluginEnabler getPluginEnabler(Context context) {
return new PluginEnablerImpl(context, mMockPackageManager);
}
});
resetExceptionHandler();
mMockListener = mock(PluginListener.class);