Merge "1/N Move plugin loading into PluginInstanceManager." into sc-v2-dev am: 498d436d54 am: 875d917932

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15499818

Change-Id: I9ea7f2299d8ac728b4b2b97edc3bf2e8298e61ae
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 e4610ba..dcd3b3e 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.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.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<T extends Plugin> {
@@ -56,33 +62,42 @@
     private final String mAction;
     private final boolean mAllowMultiple;
     private final VersionInfo mVersion;
+    private final NotificationManager mNotificationManager;
+    private final PluginEnabler mPluginEnabler;
+    private final InstanceFactory<T> mInstanceFactory;
+    private final ArraySet<String> mPrivilegedPlugins = new ArraySet<>();
+    private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>();
 
     @VisibleForTesting
     private final ArrayList<PluginInfo<T>> mPlugins = new ArrayList<>();
-    private final boolean isDebuggable;
+    private final boolean mIsDebuggable;
     private final PackageManager mPm;
-    private final PluginManagerImpl mManager;
-    private final ArraySet<String> 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<T> 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<String> privilegedPlugins,
+            InstanceFactory<T> 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 @@
         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 @@
         // 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 @@
         }
     }
 
-        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<ResolveInfo> 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<T> 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<ResolveInfo> 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<T> pluginInfo = handleLoadPlugin(name);
+            if (pluginInfo == null) continue;
 
-        protected PluginInfo<T> 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<T> 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<String> zipPaths = new ArrayList<>();
+        List<String> 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 @@
         private final Executor mMainExecutor;
         private final Executor mBgExecutor;
         private final PluginInitializer mInitializer;
+        private final NotificationManager mNotificationManager;
+        private final PluginEnabler mPluginEnabler;
+        private final List<String> 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<String> privilegedPlugins) {
             mContext = context;
             mPackageManager = packageManager;
             mMainExecutor = mainExecutor;
             mBgExecutor = bgExecutor;
             mInitializer = initializer;
+            mNotificationManager = notificationManager;
+            mPluginEnabler = pluginEnabler;
+            mPrivilegedPlugins = privilegedPlugins;
+
+            mInstanceFactory = new InstanceFactory<>();
+        }
+
+        @VisibleForTesting
+        <T extends Plugin> Factory setInstanceFactory(InstanceFactory<T> instanceFactory) {
+            mInstanceFactory = instanceFactory;
+            return this;
         }
 
         <T extends Plugin> PluginInstanceManager<T> create(
-                String action,
-                PluginListener<T> 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<T> listener, boolean allowMultiple,
+                VersionInfo version, boolean debuggable) {
+            return new PluginInstanceManager<T>(mContext, mPackageManager, action, listener,
+                    allowMultiple, mMainExecutor, mBgExecutor, version, debuggable,
+                    mInitializer, mNotificationManager, mPluginEnabler,
+                    mPrivilegedPlugins, (InstanceFactory<T>) mInstanceFactory);
         }
     }
 
@@ -428,4 +504,10 @@
             mVersion = info;
         }
     }
+
+    static class InstanceFactory<T extends Plugin> {
+        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 cc37be5..ea7b0c3 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.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 @@
     private final ArrayMap<PluginListener<?>, PluginInstanceManager<?>> mPluginMap
             = new ArrayMap<>();
     private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>();
-    private final ArraySet<String> mOneShotPackages = new ArraySet<>();
     private final ArraySet<String> 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 @@
             Optional<UncaughtExceptionHandler> defaultHandlerOptional,
             PluginEnabler pluginEnabler,
             PluginPrefs pluginPrefs,
-            String[] privilegedPlugins) {
+            List<String> privilegedPlugins) {
         mContext = context;
         mInstanceManagerFactory = instanceManagerFactory;
         mIsDebuggable = debuggable;
-        mPrivilegedPlugins.addAll(Arrays.asList(privilegedPlugins));
+        mPrivilegedPlugins.addAll(privilegedPlugins);
         mPluginPrefs = pluginPrefs;
         mPluginEnabler = pluginEnabler;
 
@@ -102,10 +85,6 @@
         return mPrivilegedPlugins.toArray(new String[0]);
     }
 
-    public PluginEnabler getPluginEnabler() {
-        return mPluginEnabler;
-    }
-
     public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) {
         addPluginListener(listener, cls, false);
     }
@@ -124,8 +103,7 @@
             Class<?> cls, boolean allowMultiple) {
         mPluginPrefs.addAction(action);
         PluginInstanceManager<T> 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 @@
     }
 
     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 @@
                 // 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 @@
             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 @@
         }
     }
 
-    /** 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<String> zipPaths = new ArrayList<>();
-        List<String> 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 <T> boolean dependsOn(Plugin p, Class<T> cls) {
         synchronized (this) {
             for (int i = 0; i < mPluginMap.size(); i++) {
@@ -310,20 +227,6 @@
         }
     }
 
-    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 @@
 
     // 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 3a5b1f7..1ea9b3c 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 @@
 
 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.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 @@
     @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<String> privilegedPlugins) {
         return new PluginInstanceManager.Factory(
-                context, packageManager, mainExecutor, pluginExecutor, initializer);
+                context, packageManager, mainExecutor, pluginExecutor, initializer,
+                notificationManager, pluginEnabler, privilegedPlugins);
     }
 
     @Provides
@@ -91,7 +97,7 @@
                     Optional<Thread.UncaughtExceptionHandler> uncaughtExceptionHandlerOptional,
             PluginEnabler pluginEnabler,
             PluginPrefs pluginPrefs,
-            @Named(PLUGIN_PRIVILEGED) String[] privilegedPlugins) {
+            @Named(PLUGIN_PRIVILEGED) List<String> privilegedPlugins) {
         return new PluginManagerImpl(context, instanceManagerFactory, debug,
                 uncaughtExceptionHandlerOptional, pluginEnabler, pluginPrefs,
                 privilegedPlugins);
@@ -104,7 +110,7 @@
 
     @Provides
     @Named(PLUGIN_PRIVILEGED)
-    static String[] providesPrivilegedPlugins(PluginInitializer initializer, Context context) {
-        return initializer.getPrivilegedPlugins(context);
+    static List<String> 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 ad2cbbd..790b4dd 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.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 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.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 @@
 @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<Plugin> mMockListener;
+    private PluginInstanceManager<Plugin> 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<Plugin> mPluginInstanceFactory =
+            new PluginInstanceManager.InstanceFactory<Plugin>() {
+        @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 @@
         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 @@
 
         // 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 @@
 
         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 @@
     @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 @@
     }
 
     @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 @@
         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 @@
 
     @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 @@
         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<ApplicationInfo>) invocation -> {
+                    ApplicationInfo appInfo = getContext().getApplicationInfo();
+                    appInfo.packageName = invocation.getArgument(0);
+                    return appInfo;
+                });
         when(mMockEnabler.isEnabled(mTestPluginComponentName)).thenReturn(true);
     }
 
@@ -281,7 +288,7 @@
     }
 
     // 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 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 8b92066..4590dd8 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.Mockito;
 
 import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Optional;
 
 @SmallTest
@@ -51,10 +52,10 @@
 @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<Plugin> mMockPluginInstance;
     private PluginManagerImpl mPluginManager;
     private PluginListener<?> mMockListener;
     private PackageManager mMockPackageManager;
@@ -73,16 +74,14 @@
         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 @@
 
     @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 @@
     @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());