Track when plugins are disabled due to crashes.
Bug: 120901833 Change-Id: I41e5c54d059b631befac235b1d3929200764860c Test: atest SystemUITests
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user