From e1878759dd1a2ffea18697b9927a7a07aedc682b Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Fri, 6 Aug 2021 16:34:32 -0400 Subject: [PATCH] 1/N Move plugin loading into PluginInstanceManager. The PluginInstanceManager now manages plugin instances. It loads them, when instructed to do so by the PluginManager. This is an imperfect cl - the first of a few to come. Right now, the PluginInstanceManager is not focused on only loading its specific instance. This will change. Bug: 194781951 Test: manual && atest SystemUITests Change-Id: I2006d623c9d9d2d1020097c567fc7af56a06a83b --- .../shared/plugins/PluginInstanceManager.java | 356 +++++++++++------- .../shared/plugins/PluginManagerImpl.java | 113 +----- .../systemui/plugins/PluginsModule.java | 16 +- .../plugins/PluginInstanceManagerTest.java | 109 +++--- .../shared/plugins/PluginManagerTest.java | 46 +-- 5 files changed, 319 insertions(+), 321 deletions(-) diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java index e4610bacee16c..dcd3b3eb5dd41 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java @@ -14,6 +14,7 @@ package com.android.systemui.shared.plugins; +import android.app.LoadedApk; import android.app.Notification; import android.app.Notification.Action; import android.app.NotificationManager; @@ -28,6 +29,8 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.net.Uri; +import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.view.LayoutInflater; @@ -39,9 +42,12 @@ import com.android.systemui.plugins.PluginFragment; import com.android.systemui.plugins.PluginListener; import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; +import dalvik.system.PathClassLoader; + +import java.io.File; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; public class PluginInstanceManager { @@ -56,33 +62,42 @@ public class PluginInstanceManager { private final String mAction; private final boolean mAllowMultiple; private final VersionInfo mVersion; + private final NotificationManager mNotificationManager; + private final PluginEnabler mPluginEnabler; + private final InstanceFactory mInstanceFactory; + private final ArraySet mPrivilegedPlugins = new ArraySet<>(); + private final Map mClassLoaders = new ArrayMap<>(); @VisibleForTesting private final ArrayList> mPlugins = new ArrayList<>(); - private final boolean isDebuggable; + private final boolean mIsDebuggable; private final PackageManager mPm; - private final PluginManagerImpl mManager; - private final ArraySet mWhitelistedPlugins = new ArraySet<>(); private final PluginInitializer mInitializer; private final Executor mMainExecutor; private final Executor mBgExecutor; - PluginInstanceManager(Context context, PackageManager pm, String action, + private PluginManagerImpl.ClassLoaderFilter mParentClassLoader; + + private PluginInstanceManager(Context context, PackageManager pm, String action, PluginListener listener, boolean allowMultiple, Executor mainExecutor, - Executor bgExecutor, VersionInfo version, PluginManagerImpl manager, boolean debuggable, - String[] pluginWhitelist, PluginInitializer initializer) { + Executor bgExecutor, VersionInfo version, boolean debuggable, + PluginInitializer initializer, NotificationManager notificationManager, + PluginEnabler pluginEnabler, List privilegedPlugins, + InstanceFactory instanceFactory) { mInitializer = initializer; mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; - mManager = manager; mContext = context; mPm = pm; mAction = action; mListener = listener; mAllowMultiple = allowMultiple; mVersion = version; - mWhitelistedPlugins.addAll(Arrays.asList(pluginWhitelist)); - isDebuggable = debuggable; + mNotificationManager = notificationManager; + mPluginEnabler = pluginEnabler; + mInstanceFactory = instanceFactory; + mPrivilegedPlugins.addAll(privilegedPlugins); + mIsDebuggable = debuggable; } public void loadAll() { @@ -127,8 +142,22 @@ public class PluginInstanceManager { return disabledAny; } - private boolean isPluginWhitelisted(ComponentName pluginName) { - for (String componentNameOrPackage : mWhitelistedPlugins) { + private boolean isPluginPackagePrivileged(String packageName) { + for (String componentNameOrPackage : mPrivilegedPlugins) { + ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage); + if (componentName != null) { + if (componentName.getPackageName().equals(packageName)) { + return true; + } + } else if (componentNameOrPackage.equals(packageName)) { + return true; + } + } + return false; + } + + private boolean isPluginPrivileged(ComponentName pluginName) { + for (String componentNameOrPackage : mPrivilegedPlugins) { ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage); if (componentName == null) { if (componentNameOrPackage.equals(pluginName.getPackageName())) { @@ -151,12 +180,12 @@ public class PluginInstanceManager { // If a plugin is detected in the stack of a crash then this will be called for that // plugin, if the plugin causing a crash cannot be identified, they are all disabled // assuming one of them must be bad. - if (isPluginWhitelisted(pluginComponent)) { + if (isPluginPrivileged(pluginComponent)) { // Don't disable whitelisted plugins as they are a part of the OS. return false; } Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString()); - mManager.getPluginEnabler().setDisabled(pluginComponent, reason); + mPluginEnabler.setDisabled(pluginComponent, reason); return true; } @@ -228,134 +257,164 @@ public class PluginInstanceManager { } } - private void handleQueryPlugins(String pkgName) { - // This isn't actually a service and shouldn't ever be started, but is - // a convenient PM based way to manage our plugins. - Intent intent = new Intent(mAction); - if (pkgName != null) { - intent.setPackage(pkgName); - } - List result = mPm.queryIntentServices(intent, 0); - if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins"); - if (result.size() > 1 && !mAllowMultiple) { - // TODO: Show warning. - Log.w(TAG, "Multiple plugins found for " + mAction); - if (DEBUG) { - for (ResolveInfo info : result) { - ComponentName name = new ComponentName(info.serviceInfo.packageName, - info.serviceInfo.name); - Log.w(TAG, " " + name); - } - } - return; - } - for (ResolveInfo info : result) { - ComponentName name = new ComponentName(info.serviceInfo.packageName, - info.serviceInfo.name); - PluginInfo pluginInfo = handleLoadPlugin(name); - if (pluginInfo == null) continue; - - // add plugin before sending PLUGIN_CONNECTED message - mPlugins.add(pluginInfo); - mMainExecutor.execute(() -> onPluginConnected(pluginInfo)); - } + private void handleQueryPlugins(String pkgName) { + // This isn't actually a service and shouldn't ever be started, but is + // a convenient PM based way to manage our plugins. + Intent intent = new Intent(mAction); + if (pkgName != null) { + intent.setPackage(pkgName); } + List result = mPm.queryIntentServices(intent, 0); + if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins"); + if (result.size() > 1 && !mAllowMultiple) { + // TODO: Show warning. + Log.w(TAG, "Multiple plugins found for " + mAction); + if (DEBUG) { + for (ResolveInfo info : result) { + ComponentName name = new ComponentName(info.serviceInfo.packageName, + info.serviceInfo.name); + Log.w(TAG, " " + name); + } + } + return; + } + for (ResolveInfo info : result) { + ComponentName name = new ComponentName(info.serviceInfo.packageName, + info.serviceInfo.name); + PluginInfo pluginInfo = handleLoadPlugin(name); + if (pluginInfo == null) continue; - protected PluginInfo handleLoadPlugin(ComponentName component) { - // This was already checked, but do it again here to make extra extra sure, we don't - // use these on production builds. - if (!isDebuggable && !isPluginWhitelisted(component)) { - // Never ever ever allow these on production builds, they are only for prototyping. - Log.w(TAG, "Plugin cannot be loaded on production build: " + component); + // add plugin before sending PLUGIN_CONNECTED message + mPlugins.add(pluginInfo); + mMainExecutor.execute(() -> onPluginConnected(pluginInfo)); + } + } + + protected PluginInfo handleLoadPlugin(ComponentName component) { + // This was already checked, but do it again here to make extra extra sure, we don't + // use these on production builds. + if (!mIsDebuggable && !isPluginPrivileged(component)) { + // Never ever ever allow these on production builds, they are only for prototyping. + Log.w(TAG, "Plugin cannot be loaded on production build: " + component); + return null; + } + if (!mPluginEnabler.isEnabled(component)) { + if (DEBUG) Log.d(TAG, "Plugin is not enabled, aborting load: " + component); + return null; + } + String pkg = component.getPackageName(); + String cls = component.getClassName(); + try { + ApplicationInfo info = mPm.getApplicationInfo(pkg, 0); + // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on + if (mPm.checkPermission(PLUGIN_PERMISSION, pkg) + != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Plugin doesn't have permission: " + pkg); return null; } - if (!mManager.getPluginEnabler().isEnabled(component)) { - if (DEBUG) Log.d(TAG, "Plugin is not enabled, aborting load: " + component); - return null; - } - String pkg = component.getPackageName(); - String cls = component.getClassName(); + // Create our own ClassLoader so we can use our own code as the parent. + ClassLoader classLoader = getClassLoader(info); + Context pluginContext = new PluginContextWrapper( + mContext.createApplicationContext(info, 0), classLoader); + Class pluginClass = Class.forName(cls, true, classLoader); + // TODO: Only create the plugin before version check if we need it for + // legacy version check. + T plugin = mInstanceFactory.create(pluginClass); try { - ApplicationInfo info = mPm.getApplicationInfo(pkg, 0); - // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on - if (mPm.checkPermission(PLUGIN_PERMISSION, pkg) - != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Plugin doesn't have permission: " + pkg); - return null; - } - // Create our own ClassLoader so we can use our own code as the parent. - ClassLoader classLoader = mManager.getClassLoader(info); - Context pluginContext = new PluginContextWrapper( - mContext.createApplicationContext(info, 0), classLoader); - Class pluginClass = Class.forName(cls, true, classLoader); - // TODO: Only create the plugin before version check if we need it for - // legacy version check. - T plugin = (T) pluginClass.newInstance(); + VersionInfo version = checkVersion(pluginClass, plugin, mVersion); + if (DEBUG) Log.d(TAG, "createPlugin"); + return new PluginInfo<>(pkg, cls, plugin, pluginContext, version); + } catch (InvalidVersionException e) { + final int icon = Resources.getSystem().getIdentifier( + "stat_sys_warning", "drawable", "android"); + final int color = Resources.getSystem().getIdentifier( + "system_notification_accent_color", "color", "android"); + final Notification.Builder nb = new Notification.Builder(mContext, + PluginManager.NOTIFICATION_CHANNEL_ID) + .setStyle(new Notification.BigTextStyle()) + .setSmallIcon(icon) + .setWhen(0) + .setShowWhen(false) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setColor(mContext.getColor(color)); + String label = cls; try { - VersionInfo version = checkVersion(pluginClass, plugin, mVersion); - if (DEBUG) Log.d(TAG, "createPlugin"); - return new PluginInfo<>(pkg, cls, plugin, pluginContext, version); - } catch (InvalidVersionException e) { - final int icon = Resources.getSystem().getIdentifier( - "stat_sys_warning", "drawable", "android"); - final int color = Resources.getSystem().getIdentifier( - "system_notification_accent_color", "color", "android"); - final Notification.Builder nb = new Notification.Builder(mContext, - PluginManager.NOTIFICATION_CHANNEL_ID) - .setStyle(new Notification.BigTextStyle()) - .setSmallIcon(icon) - .setWhen(0) - .setShowWhen(false) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(mContext.getColor(color)); - String label = cls; - try { - label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString(); - } catch (NameNotFoundException e2) { - } - if (!e.isTooNew()) { - // Localization not required as this will never ever appear in a user build. - nb.setContentTitle("Plugin \"" + label + "\" is too old") - .setContentText("Contact plugin developer to get an updated" - + " version.\n" + e.getMessage()); - } else { - // Localization not required as this will never ever appear in a user build. - nb.setContentTitle("Plugin \"" + label + "\" is too new") - .setContentText("Check to see if an OTA is available.\n" - + e.getMessage()); - } - Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData( - Uri.parse("package://" + component.flattenToString())); - PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, - PendingIntent.FLAG_IMMUTABLE); - nb.addAction(new Action.Builder(null, "Disable plugin", pi).build()); - mContext.getSystemService(NotificationManager.class) - .notify(SystemMessage.NOTE_PLUGIN, nb.build()); - // TODO: Warn user. - Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion() - + ", expected " + mVersion); - return null; + label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString(); + } catch (NameNotFoundException e2) { } - } catch (Throwable e) { - Log.w(TAG, "Couldn't load plugin: " + pkg, e); + if (!e.isTooNew()) { + // Localization not required as this will never ever appear in a user build. + nb.setContentTitle("Plugin \"" + label + "\" is too old") + .setContentText("Contact plugin developer to get an updated" + + " version.\n" + e.getMessage()); + } else { + // Localization not required as this will never ever appear in a user build. + nb.setContentTitle("Plugin \"" + label + "\" is too new") + .setContentText("Check to see if an OTA is available.\n" + + e.getMessage()); + } + Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData( + Uri.parse("package://" + component.flattenToString())); + PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, + PendingIntent.FLAG_IMMUTABLE); + nb.addAction(new Action.Builder(null, "Disable plugin", pi).build()); + mNotificationManager.notify(SystemMessage.NOTE_PLUGIN, nb.build()); + // TODO: Warn user. + Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion() + + ", expected " + mVersion); return null; } + } catch (Throwable e) { + Log.w(TAG, "Couldn't load plugin: " + pkg, e); + return null; + } + } + + private VersionInfo checkVersion(Class pluginClass, T plugin, VersionInfo version) + throws InvalidVersionException { + VersionInfo pv = new VersionInfo().addClass(pluginClass); + if (pv.hasVersionInfo()) { + version.checkVersion(pv); + } else { + int fallbackVersion = plugin.getVersion(); + if (fallbackVersion != version.getDefaultVersion()) { + throw new InvalidVersionException("Invalid legacy version", false); + } + return null; + } + return pv; + } + + /** Returns class loader specific for the given plugin. */ + public ClassLoader getClassLoader(ApplicationInfo appInfo) { + if (!mIsDebuggable && !isPluginPackagePrivileged(appInfo.packageName)) { + Log.w(TAG, "Cannot get class loader for non-privileged plugin. Src:" + + appInfo.sourceDir + ", pkg: " + appInfo.packageName); + return null; + } + if (mClassLoaders.containsKey(appInfo.packageName)) { + return mClassLoaders.get(appInfo.packageName); } - private VersionInfo checkVersion(Class pluginClass, T plugin, VersionInfo version) - throws InvalidVersionException { - VersionInfo pv = new VersionInfo().addClass(pluginClass); - if (pv.hasVersionInfo()) { - version.checkVersion(pv); - } else { - int fallbackVersion = plugin.getVersion(); - if (fallbackVersion != version.getDefaultVersion()) { - throw new InvalidVersionException("Invalid legacy version", false); - } - return null; - } - return pv; + List zipPaths = new ArrayList<>(); + List libPaths = new ArrayList<>(); + LoadedApk.makePaths(null, true, appInfo, zipPaths, libPaths); + ClassLoader classLoader = new PathClassLoader( + TextUtils.join(File.pathSeparator, zipPaths), + TextUtils.join(File.pathSeparator, libPaths), + getParentClassLoader()); + mClassLoaders.put(appInfo.packageName, classLoader); + return classLoader; + } + + private ClassLoader getParentClassLoader() { + if (mParentClassLoader == null) { + // Lazily load this so it doesn't have any effect on devices without plugins. + mParentClassLoader = new PluginManagerImpl.ClassLoaderFilter( + getClass().getClassLoader(), "com.android.systemui.plugin"); } + return mParentClassLoader; + } /** * Construct a {@link PluginInstanceManager} @@ -366,23 +425,40 @@ public class PluginInstanceManager { private final Executor mMainExecutor; private final Executor mBgExecutor; private final PluginInitializer mInitializer; + private final NotificationManager mNotificationManager; + private final PluginEnabler mPluginEnabler; + private final List mPrivilegedPlugins; + private InstanceFactory mInstanceFactory; public Factory(Context context, PackageManager packageManager, - Executor mainExecutor, Executor bgExecutor, PluginInitializer initializer) { + Executor mainExecutor, Executor bgExecutor, PluginInitializer initializer, + NotificationManager notificationManager, PluginEnabler pluginEnabler, + List privilegedPlugins) { mContext = context; mPackageManager = packageManager; mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; mInitializer = initializer; + mNotificationManager = notificationManager; + mPluginEnabler = pluginEnabler; + mPrivilegedPlugins = privilegedPlugins; + + mInstanceFactory = new InstanceFactory<>(); + } + + @VisibleForTesting + Factory setInstanceFactory(InstanceFactory instanceFactory) { + mInstanceFactory = instanceFactory; + return this; } PluginInstanceManager create( - String action, - PluginListener listener, boolean allowMultiple, VersionInfo version, - PluginManagerImpl manager, boolean debuggable, String[] pluginWhitelist) { - return new PluginInstanceManager<>(mContext, mPackageManager, action, listener, - allowMultiple, mMainExecutor, mBgExecutor, version, manager, debuggable, - pluginWhitelist, mInitializer); + String action, PluginListener listener, boolean allowMultiple, + VersionInfo version, boolean debuggable) { + return new PluginInstanceManager(mContext, mPackageManager, action, listener, + allowMultiple, mMainExecutor, mBgExecutor, version, debuggable, + mInitializer, mNotificationManager, mPluginEnabler, + mPrivilegedPlugins, (InstanceFactory) mInstanceFactory); } } @@ -428,4 +504,10 @@ public class PluginInstanceManager { mVersion = info; } } + + static class InstanceFactory { + T create(Class cls) throws IllegalAccessException, InstantiationException { + return (T) cls.newInstance(); + } + } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java index cc37be5aa37c6..ea7b0c34136a9 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java @@ -14,24 +14,15 @@ package com.android.systemui.shared.plugins; -import android.app.LoadedApk; -import android.app.Notification; -import android.app.Notification.Action; import android.app.NotificationManager; -import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; import android.net.Uri; import android.os.Build; import android.os.SystemProperties; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -41,14 +32,9 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; -import dalvik.system.PathClassLoader; - -import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.Thread.UncaughtExceptionHandler; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; @@ -64,16 +50,13 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage private final ArrayMap, PluginInstanceManager> mPluginMap = new ArrayMap<>(); private final Map mClassLoaders = new ArrayMap<>(); - private final ArraySet mOneShotPackages = new ArraySet<>(); private final ArraySet mPrivilegedPlugins = new ArraySet<>(); private final Context mContext; private final PluginInstanceManager.Factory mInstanceManagerFactory; private final boolean mIsDebuggable; private final PluginPrefs mPluginPrefs; private final PluginEnabler mPluginEnabler; - private ClassLoaderFilter mParentClassLoader; private boolean mListening; - private boolean mHasOneShot; public PluginManagerImpl(Context context, PluginInstanceManager.Factory instanceManagerFactory, @@ -81,11 +64,11 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage Optional defaultHandlerOptional, PluginEnabler pluginEnabler, PluginPrefs pluginPrefs, - String[] privilegedPlugins) { + List privilegedPlugins) { mContext = context; mInstanceManagerFactory = instanceManagerFactory; mIsDebuggable = debuggable; - mPrivilegedPlugins.addAll(Arrays.asList(privilegedPlugins)); + mPrivilegedPlugins.addAll(privilegedPlugins); mPluginPrefs = pluginPrefs; mPluginEnabler = pluginEnabler; @@ -102,10 +85,6 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage return mPrivilegedPlugins.toArray(new String[0]); } - public PluginEnabler getPluginEnabler() { - return mPluginEnabler; - } - public void addPluginListener(PluginListener listener, Class cls) { addPluginListener(listener, cls, false); } @@ -124,8 +103,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage Class cls, boolean allowMultiple) { mPluginPrefs.addAction(action); PluginInstanceManager p = mInstanceManagerFactory.create(action, listener, allowMultiple, - new VersionInfo().addClass(cls), this, isDebuggable(), - getPrivilegedPlugins()); + new VersionInfo().addClass(cls), isDebuggable()); p.loadAll(); synchronized (this) { mPluginMap.put(listener, p); @@ -163,8 +141,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage } private void stopListening() { - // Never stop listening if a one-shot is present. - if (!mListening || mHasOneShot) return; + if (!mListening) return; mListening = false; mContext.unregisterReceiver(this); } @@ -185,42 +162,13 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage // Don't disable privileged plugins as they are a part of the OS. return; } - getPluginEnabler().setDisabled(component, PluginEnabler.DISABLED_INVALID_VERSION); + mPluginEnabler.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 = Resources.getSystem().getIdentifier( - "stat_sys_warning", "drawable", "android"); - int color = Resources.getSystem().getIdentifier( - "system_notification_accent_color", "color", "android"); - String label = pkg; - try { - PackageManager pm = mContext.getPackageManager(); - label = pm.getApplicationInfo(pkg, 0).loadLabel(pm).toString(); - } catch (NameNotFoundException e) { - } - // Localization not required as this will never ever appear in a user build. - final Notification.Builder nb = - new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) - .setSmallIcon(icon) - .setWhen(0) - .setShowWhen(false) - .setPriority(Notification.PRIORITY_MAX) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(mContext.getColor(color)) - .setContentTitle("Plugin \"" + label + "\" has updated") - .setContentText("Restart SysUI for changes to take effect."); - Intent i = new Intent("com.android.systemui.action.RESTART").setData( - Uri.parse("package://" + pkg)); - PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, PendingIntent.FLAG_MUTABLE_UNAUDITED); - nb.addAction(new Action.Builder(null, "Restart SysUI", pi).build()); - mContext.getSystemService(NotificationManager.class) - .notify(SystemMessage.NOTE_PLUGIN, nb.build()); - } if (clearClassLoader(pkg)) { if (Build.IS_ENG) { Toast.makeText(mContext, "Reloading " + pkg, Toast.LENGTH_LONG).show(); @@ -231,13 +179,13 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction()) && componentName != null) { @PluginEnabler.DisableReason int disableReason = - getPluginEnabler().getDisableReason(componentName); + mPluginEnabler.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); + mPluginEnabler.setEnabled(componentName); } } synchronized (this) { @@ -254,41 +202,10 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage } } - /** Returns class loader specific for the given plugin. */ - public ClassLoader getClassLoader(ApplicationInfo appInfo) { - if (!mIsDebuggable && !isPluginPackagePrivileged(appInfo.packageName)) { - Log.w(TAG, "Cannot get class loader for non-privileged plugin. Src:" - + appInfo.sourceDir + ", pkg: " + appInfo.packageName); - return null; - } - if (mClassLoaders.containsKey(appInfo.packageName)) { - return mClassLoaders.get(appInfo.packageName); - } - - List zipPaths = new ArrayList<>(); - List libPaths = new ArrayList<>(); - LoadedApk.makePaths(null, true, appInfo, zipPaths, libPaths); - ClassLoader classLoader = new PathClassLoader( - TextUtils.join(File.pathSeparator, zipPaths), - TextUtils.join(File.pathSeparator, libPaths), - getParentClassLoader()); - mClassLoaders.put(appInfo.packageName, classLoader); - return classLoader; - } - private boolean clearClassLoader(String pkg) { return mClassLoaders.remove(pkg) != null; } - ClassLoader getParentClassLoader() { - if (mParentClassLoader == null) { - // Lazily load this so it doesn't have any effect on devices without plugins. - mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(), - "com.android.systemui.plugin"); - } - return mParentClassLoader; - } - public boolean dependsOn(Plugin p, Class cls) { synchronized (this) { for (int i = 0; i < mPluginMap.size(); i++) { @@ -310,20 +227,6 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage } } - private boolean isPluginPackagePrivileged(String packageName) { - for (String componentNameOrPackage : mPrivilegedPlugins) { - ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage); - if (componentName != null) { - if (componentName.getPackageName().equals(packageName)) { - return true; - } - } else if (componentNameOrPackage.equals(packageName)) { - return true; - } - } - return false; - } - private boolean isPluginPrivileged(ComponentName pluginName) { for (String componentNameOrPackage : mPrivilegedPlugins) { ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage); @@ -340,7 +243,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage // This allows plugins to include any libraries or copied code they want by only including // classes from the plugin library. - private static class ClassLoaderFilter extends ClassLoader { + static class ClassLoaderFilter extends ClassLoader { private final String mPackage; private final ClassLoader mBase; diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java index 3a5b1f7747db6..1ea9b3c08b1ec 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java @@ -18,6 +18,7 @@ package com.android.systemui.plugins; import static com.android.systemui.util.concurrency.GlobalConcurrencyModule.PRE_HANDLER; +import android.app.NotificationManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; @@ -32,6 +33,8 @@ import com.android.systemui.shared.plugins.PluginPrefs; import com.android.systemui.util.concurrency.GlobalConcurrencyModule; import com.android.systemui.util.concurrency.ThreadFactory; +import java.util.Arrays; +import java.util.List; import java.util.Optional; import java.util.concurrent.Executor; @@ -70,9 +73,12 @@ public abstract class PluginsModule { @Singleton static PluginInstanceManager.Factory providePluginInstanceManagerFactory(Context context, PackageManager packageManager, @Main Executor mainExecutor, - @Named(PLUGIN_THREAD) Executor pluginExecutor, PluginInitializer initializer) { + @Named(PLUGIN_THREAD) Executor pluginExecutor, PluginInitializer initializer, + NotificationManager notificationManager, PluginEnabler pluginEnabler, + @Named(PLUGIN_PRIVILEGED) List privilegedPlugins) { return new PluginInstanceManager.Factory( - context, packageManager, mainExecutor, pluginExecutor, initializer); + context, packageManager, mainExecutor, pluginExecutor, initializer, + notificationManager, pluginEnabler, privilegedPlugins); } @Provides @@ -91,7 +97,7 @@ public abstract class PluginsModule { Optional uncaughtExceptionHandlerOptional, PluginEnabler pluginEnabler, PluginPrefs pluginPrefs, - @Named(PLUGIN_PRIVILEGED) String[] privilegedPlugins) { + @Named(PLUGIN_PRIVILEGED) List privilegedPlugins) { return new PluginManagerImpl(context, instanceManagerFactory, debug, uncaughtExceptionHandlerOptional, pluginEnabler, pluginPrefs, privilegedPlugins); @@ -104,7 +110,7 @@ public abstract class PluginsModule { @Provides @Named(PLUGIN_PRIVILEGED) - static String[] providesPrivilegedPlugins(PluginInitializer initializer, Context context) { - return initializer.getPrivilegedPlugins(context); + static List providesPrivilegedPlugins(PluginInitializer initializer, Context context) { + return Arrays.asList(initializer.getPrivilegedPlugins(context)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java index ad2cbbd1bd75d..790b4dd11825b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java @@ -31,7 +31,6 @@ import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; @@ -44,6 +43,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestableContext; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.annotations.Requires; @@ -51,12 +51,12 @@ import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; -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 org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Collections; @@ -66,43 +66,46 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class PluginInstanceManagerTest extends SysuiTestCase { - private static final String WHITELISTED_PACKAGE = "com.android.systemui"; - // Static since the plugin needs to be generated by the PluginInstanceManager using newInstance. - private static Plugin sMockPlugin; + private static final String PRIVILEGED_PACKAGE = "com.android.systemui.shared.plugins"; + private TestPlugin mMockPlugin; - private Context mContextWrapper; private PackageManager mMockPm; - private PluginListener mMockListener; - private PluginInstanceManager mPluginInstanceManager; - private PluginManagerImpl mMockManager; + private PluginListener mMockListener; + private PluginInstanceManager mPluginInstanceManager; private VersionInfo mMockVersionInfo; private PluginEnabler mMockEnabler; ComponentName mTestPluginComponentName = - new ComponentName(WHITELISTED_PACKAGE, TestPlugin.class.getName()); + new ComponentName(PRIVILEGED_PACKAGE, TestPlugin.class.getName()); private PluginInitializer mInitializer; private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + NotificationManager mNotificationManager; + private PluginInstanceManager.Factory mInstanceManagerFactory; + private final PluginInstanceManager.InstanceFactory mPluginInstanceFactory = + new PluginInstanceManager.InstanceFactory() { + @Override + Plugin create(Class cls) { + return mMockPlugin; + } + }; @Before public void setup() throws Exception { - mContextWrapper = new MyContextWrapper(getContext()); + mContext = new MyContextWrapper(mContext); mMockPm = mock(PackageManager.class); mMockListener = mock(PluginListener.class); - mMockManager = mock(PluginManagerImpl.class); - when(mMockManager.getClassLoader(any())).thenReturn(getClass().getClassLoader()); mMockEnabler = mock(PluginEnabler.class); - when(mMockManager.getPluginEnabler()).thenReturn(mMockEnabler); mMockVersionInfo = mock(VersionInfo.class); mInitializer = mock(PluginInitializer.class); - mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", - mMockListener, true, mFakeExecutor, mFakeExecutor, - mMockVersionInfo, mMockManager, true, new String[0], mInitializer); - sMockPlugin = mock(Plugin.class); - when(sMockPlugin.getVersion()).thenReturn(1); - } + mNotificationManager = mock(NotificationManager.class); + mMockPlugin = mock(TestPlugin.class); + mInstanceManagerFactory = new PluginInstanceManager.Factory(getContext(), mMockPm, + mFakeExecutor, mFakeExecutor, mInitializer, mNotificationManager, mMockEnabler, + new ArrayList<>()) + .setInstanceFactory(mPluginInstanceFactory); - @After - public void tearDown() { - sMockPlugin = null; + mPluginInstanceManager = mInstanceManagerFactory.create("myAction", mMockListener, + true, mMockVersionInfo, true); + when(mMockPlugin.getVersion()).thenReturn(1); } @Test @@ -121,7 +124,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { createPlugin(); // Verify startup lifecycle - verify(sMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(), + verify(mMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(), ArgumentCaptor.forClass(Context.class).capture()); verify(mMockListener).onPluginConnected(any(), any()); } @@ -137,13 +140,11 @@ public class PluginInstanceManagerTest extends SysuiTestCase { // Verify shutdown lifecycle verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture()); - verify(sMockPlugin).onDestroy(); + verify(mMockPlugin).onDestroy(); } @Test public void testIncorrectVersion() throws Exception { - NotificationManager nm = mock(NotificationManager.class); - mContext.addMockSystemService(Context.NOTIFICATION_SERVICE, nm); setupFakePmQuery(); doThrow(new InvalidVersionException("", false)).when(mMockVersionInfo).checkVersion(any()); @@ -151,25 +152,24 @@ public class PluginInstanceManagerTest extends SysuiTestCase { mFakeExecutor.runAllReady(); - // Plugin shouldn't be connected because it is the wrong version. verify(mMockListener, never()).onPluginConnected(any(), any()); - verify(nm).notify(eq(SystemMessage.NOTE_PLUGIN), any()); + verify(mNotificationManager).notify(eq(SystemMessage.NOTE_PLUGIN), any()); } @Test public void testReloadOnChange() throws Exception { createPlugin(); // Get into valid created state. - mPluginInstanceManager.onPackageChange("com.android.systemui"); + mPluginInstanceManager.onPackageChange(PRIVILEGED_PACKAGE); mFakeExecutor.runAllReady(); // Verify the old one was destroyed. verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture()); - verify(sMockPlugin).onDestroy(); + verify(mMockPlugin).onDestroy(); // Also verify we got a second onCreate. - verify(sMockPlugin, Mockito.times(2)).onCreate( + verify(mMockPlugin, Mockito.times(2)).onCreate( ArgumentCaptor.forClass(Context.class).capture(), ArgumentCaptor.forClass(Context.class).capture()); verify(mMockListener, Mockito.times(2)).onPluginConnected(any(), any()); @@ -178,9 +178,8 @@ public class PluginInstanceManagerTest extends SysuiTestCase { @Test public void testNonDebuggable() throws Exception { // Create a version that thinks the build is not debuggable. - mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", - mMockListener, true, mFakeExecutor, mFakeExecutor, - mMockVersionInfo, mMockManager, false, new String[0], mInitializer); + mPluginInstanceManager = mInstanceManagerFactory.create("myAction", mMockListener, + true, mMockVersionInfo, false); setupFakePmQuery(); mPluginInstanceManager.loadAll(); @@ -192,12 +191,14 @@ public class PluginInstanceManagerTest extends SysuiTestCase { } @Test - public void testNonDebuggable_whitelist() throws Exception { + public void testNonDebuggable_privileged() throws Exception { // Create a version that thinks the build is not debuggable. - mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", - mMockListener, true, mFakeExecutor, mFakeExecutor, - mMockVersionInfo, mMockManager, false, - new String[] {WHITELISTED_PACKAGE}, mInitializer); + PluginInstanceManager.Factory factory = new PluginInstanceManager.Factory(getContext(), + mMockPm, mFakeExecutor, mFakeExecutor, mInitializer, mNotificationManager, + mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE)); + factory.setInstanceFactory(mPluginInstanceFactory); + mPluginInstanceManager = factory.create("myAction", mMockListener, + true, mMockVersionInfo, false); setupFakePmQuery(); mPluginInstanceManager.loadAll(); @@ -205,7 +206,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { mFakeExecutor.runAllReady(); // Verify startup lifecycle - verify(sMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(), + verify(mMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(), ArgumentCaptor.forClass(Context.class).capture()); verify(mMockListener).onPluginConnected(any(), any()); } @@ -238,10 +239,13 @@ public class PluginInstanceManagerTest extends SysuiTestCase { @Test public void testDisableWhitelisted() throws Exception { - mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", - mMockListener, true, mFakeExecutor, mFakeExecutor, - mMockVersionInfo, mMockManager, false, new String[] {WHITELISTED_PACKAGE}, - mInitializer); + PluginInstanceManager.Factory factory = new PluginInstanceManager.Factory(getContext(), + mMockPm, mFakeExecutor, mFakeExecutor, mInitializer, mNotificationManager, + mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE)); + factory.setInstanceFactory(mPluginInstanceFactory); + mPluginInstanceManager = factory.create("myAction", mMockListener, + true, mMockVersionInfo, false); + createPlugin(); // Get into valid created state. mPluginInstanceManager.disableAll(); @@ -266,9 +270,12 @@ public class PluginInstanceManagerTest extends SysuiTestCase { when(mMockPm.checkPermission(Mockito.anyString(), Mockito.anyString())).thenReturn( PackageManager.PERMISSION_GRANTED); - ApplicationInfo appInfo = getContext().getApplicationInfo(); - when(mMockPm.getApplicationInfo(Mockito.anyString(), Mockito.anyInt())).thenReturn( - appInfo); + when(mMockPm.getApplicationInfo(Mockito.anyString(), anyInt())).thenAnswer( + (Answer) invocation -> { + ApplicationInfo appInfo = getContext().getApplicationInfo(); + appInfo.packageName = invocation.getArgument(0); + return appInfo; + }); when(mMockEnabler.isEnabled(mTestPluginComponentName)).thenReturn(true); } @@ -281,7 +288,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { } // Real context with no registering/unregistering of receivers. - private static class MyContextWrapper extends ContextWrapper { + private static class MyContextWrapper extends SysuiTestableContext { public MyContextWrapper(Context base) { super(base); } @@ -307,17 +314,15 @@ public class PluginInstanceManagerTest extends SysuiTestCase { public static class TestPlugin implements Plugin { @Override public int getVersion() { - return sMockPlugin.getVersion(); + return 1; } @Override public void onCreate(Context sysuiContext, Context pluginContext) { - sMockPlugin.onCreate(sysuiContext, pluginContext); } @Override public void onDestroy() { - sMockPlugin.onDestroy(); } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java index 8b92066823153..4590dd8295506 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java @@ -13,9 +13,8 @@ */ package com.android.systemui.shared.plugins; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,6 +43,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import java.lang.Thread.UncaughtExceptionHandler; +import java.util.ArrayList; +import java.util.Collections; import java.util.Optional; @SmallTest @@ -51,10 +52,10 @@ import java.util.Optional; @RunWithLooper public class PluginManagerTest extends SysuiTestCase { - private static final String WHITELISTED_PACKAGE = "com.android.systemui"; + private static final String PRIVILEGED_PACKAGE = "com.android.systemui"; private PluginInstanceManager.Factory mMockFactory; - private PluginInstanceManager mMockPluginInstance; + private PluginInstanceManager mMockPluginInstance; private PluginManagerImpl mPluginManager; private PluginListener mMockListener; private PackageManager mMockPackageManager; @@ -73,16 +74,14 @@ public class PluginManagerTest extends SysuiTestCase { mMockPluginInstance = mock(PluginInstanceManager.class); mPluginEnabler = mock(PluginEnabler.class); mPluginPrefs = mock(PluginPrefs.class); - when(mMockFactory.create(Mockito.any(), Mockito.any(), - Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.anyBoolean(), - Mockito.any())) + when(mMockFactory.create(any(), any(), Mockito.anyBoolean(), any(), Mockito.anyBoolean())) .thenReturn(mMockPluginInstance); mMockPackageManager = mock(PackageManager.class); mPluginManager = new PluginManagerImpl( getContext(), mMockFactory, true, Optional.of(mMockExceptionHandler), mPluginEnabler, - mPluginPrefs, new String[0]); + mPluginPrefs, new ArrayList<>()); resetExceptionHandler(); mMockListener = mock(PluginListener.class); @@ -105,46 +104,49 @@ public class PluginManagerTest extends SysuiTestCase { @Test @RunWithLooper(setAsMainLooper = true) - public void testNonDebuggable_noWhitelist() { + public void testNonDebuggable_nonPrivileged() { mPluginManager = new PluginManagerImpl( getContext(), mMockFactory, false, Optional.of(mMockExceptionHandler), mPluginEnabler, - mPluginPrefs, new String[0]); + mPluginPrefs, new ArrayList<>()); resetExceptionHandler(); String sourceDir = "myPlugin"; ApplicationInfo applicationInfo = new ApplicationInfo(); applicationInfo.sourceDir = sourceDir; - applicationInfo.packageName = WHITELISTED_PACKAGE; + applicationInfo.packageName = PRIVILEGED_PACKAGE; mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); - assertNull(mPluginManager.getClassLoader(applicationInfo)); + verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(false), + any(VersionInfo.class), eq(false)); + verify(mMockPluginInstance).loadAll(); } @Test @RunWithLooper(setAsMainLooper = true) - public void testNonDebuggable_whitelistedPkg() { + public void testNonDebuggable_privilegedPackage() { mPluginManager = new PluginManagerImpl( getContext(), mMockFactory, false, Optional.of(mMockExceptionHandler), mPluginEnabler, - mPluginPrefs, new String[] {WHITELISTED_PACKAGE}); + mPluginPrefs, Collections.singletonList(PRIVILEGED_PACKAGE)); resetExceptionHandler(); String sourceDir = "myPlugin"; - ApplicationInfo whiteListedApplicationInfo = new ApplicationInfo(); - whiteListedApplicationInfo.sourceDir = sourceDir; - whiteListedApplicationInfo.packageName = WHITELISTED_PACKAGE; + ApplicationInfo privilegedApplicationInfo = new ApplicationInfo(); + privilegedApplicationInfo.sourceDir = sourceDir; + privilegedApplicationInfo.packageName = PRIVILEGED_PACKAGE; ApplicationInfo invalidApplicationInfo = new ApplicationInfo(); invalidApplicationInfo.sourceDir = sourceDir; invalidApplicationInfo.packageName = "com.android.invalidpackage"; mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); - assertNotNull(mPluginManager.getClassLoader(whiteListedApplicationInfo)); - assertNull(mPluginManager.getClassLoader(invalidApplicationInfo)); + verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(false), + any(VersionInfo.class), eq(false)); + verify(mMockPluginInstance).loadAll(); } @Test public void testExceptionHandler_foundPlugin() { mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); - when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(true); + when(mMockPluginInstance.checkAndDisable(any())).thenReturn(true); mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable()); @@ -159,7 +161,7 @@ public class PluginManagerTest extends SysuiTestCase { @Test public void testExceptionHandler_noFoundPlugin() { mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); - when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(false); + when(mMockPluginInstance.checkAndDisable(any())).thenReturn(false); mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());