Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 03262298 authored by Dave Mankoff's avatar Dave Mankoff
Browse files

Daggerize Plugin code.

Restructure the dependencies within all the plugin code
and make it suitable for injection.

There is still a lot of gnarly code in the plugin architecture,
and this is the first step towards cleaning it up.

This is also the first step towards being able to load plugins
(like the FlagReaderPlugin) _before_ the dagger graph is setup. With
this change, it is theoretically possible to directly construct
the plugin infrastructure, rather than relying on calls to
Dependency#get and other SystemUI idiosyncracies.

Bug: 194781951
Test: manual && atest SystemUITests
Change-Id: I04da3d12211d9f9d1a5c2c5cd27b6a2d81c3907e
parent 70fb2655
Loading
Loading
Loading
Loading
+3 −14
Original line number Diff line number Diff line
@@ -15,31 +15,20 @@
package com.android.systemui.shared.plugins;

import android.content.Context;
import android.os.Looper;

/**
 * Provides necessary components for initializing {@link PluginManagerImpl}.
 */
public interface PluginInitializer {

    Looper getBgLooper();

    /**
     * Called from the bg looper during initialization of {@link PluginManagerImpl}.
     * Return a list of plugins that don't get disabled when an exception occurs.
     */
    void onPluginManagerInit();

    String[] getWhitelistedPlugins(Context context);
    String[] getPrivilegedPlugins(Context context);

    PluginEnabler getPluginEnabler(Context context);

    /**
     * Called from {@link PluginManagerImpl#handleWtfs()}.
     * Called from {@link PluginInstanceManager}.
     */
    void handleWtfs();

    /**
     * Returns if pluging manager should run in debug mode.
     */
    boolean isDebuggable();
}
+32 −9
Original line number Diff line number Diff line
@@ -67,17 +67,13 @@ public class PluginInstanceManager<T extends Plugin> {
    private final PackageManager mPm;
    private final PluginManagerImpl mManager;
    private final ArraySet<String> mWhitelistedPlugins = new ArraySet<>();
    private final PluginInitializer mInitializer;

    PluginInstanceManager(Context context, String action, PluginListener<T> listener,
            boolean allowMultiple, Looper looper, VersionInfo version, PluginManagerImpl manager) {
        this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version,
                manager, manager.isDebuggable(), manager.getWhitelistedPlugins());
    }

    @VisibleForTesting
    PluginInstanceManager(Context context, PackageManager pm, String action,
            PluginListener<T> listener, boolean allowMultiple, Looper looper, VersionInfo version,
            PluginManagerImpl manager, boolean debuggable, String[] pluginWhitelist) {
            PluginManagerImpl manager, boolean debuggable, String[] pluginWhitelist,
            PluginInitializer initializer) {
        mInitializer = initializer;
        mMainHandler = new MainHandler(Looper.getMainLooper());
        mPluginHandler = new PluginHandler(looper);
        mManager = manager;
@@ -214,7 +210,7 @@ public class PluginInstanceManager<T extends Plugin> {
                    if (DEBUG) Log.d(TAG, "onPluginConnected");
                    PluginPrefs.setHasPlugins(mContext);
                    PluginInfo<T> info = (PluginInfo<T>) msg.obj;
                    mManager.handleWtfs();
                    mInitializer.handleWtfs();
                    if (!(msg.obj instanceof PluginFragment)) {
                        // Only call onDestroy for plugins that aren't fragments, as fragments
                        // will get the onCreate as part of the fragment lifecycle.
@@ -417,6 +413,33 @@ public class PluginInstanceManager<T extends Plugin> {
        }
    }

    /**
     * Construct a {@link PluginInstanceManager}
     */
    public static class Factory {
        private final Context mContext;
        private final PackageManager mPackageManager;
        private final Looper mLooper;
        private final PluginInitializer mInitializer;

        public Factory(Context context, PackageManager packageManager, Looper looper,
                PluginInitializer initializer) {
            mContext = context;
            mPackageManager = packageManager;
            mLooper = looper;
            mInitializer = initializer;
        }

        <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, mLooper, version, manager, debuggable, pluginWhitelist,
                    mInitializer);
        }
    }

    public static class PluginContextWrapper extends ContextWrapper {
        private final ClassLoader mClassLoader;
        private LayoutInflater mInflater;
+3 −2
Original line number Diff line number Diff line
@@ -27,7 +27,8 @@ public interface PluginManager {
    // must be one of the channels created in NotificationChannels.java
    String NOTIFICATION_CHANNEL_ID = "ALR";

    String[] getWhitelistedPlugins();
    /** Returns plugins that don't get disabled when an exceptoin occurs. */
    String[] getPrivilegedPlugins();

    <T extends Plugin> T getOneShotPlugin(Class<T> cls);
    <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls);
@@ -38,7 +39,7 @@ public interface PluginManager {
    <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
            Class<?> cls);
    <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
            Class cls, boolean allowMultiple);
            Class<?> cls, boolean allowMultiple);

    void removePluginListener(PluginListener<?> listener);

+52 −70
Original line number Diff line number Diff line
@@ -30,7 +30,6 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemProperties;
import android.text.TextUtils;
@@ -39,7 +38,6 @@ import android.util.ArraySet;
import android.util.Log;
import android.widget.Toast;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
@@ -56,6 +54,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @see Plugin
 */
@@ -64,57 +64,45 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
    private static final String TAG = PluginManagerImpl.class.getSimpleName();
    static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN";

    private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap
    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> mWhitelistedPlugins = new ArraySet<>();
    private final ArraySet<String> mPrivilegedPlugins = new ArraySet<>();
    private final Context mContext;
    private final PluginInstanceManagerFactory mFactory;
    private final PluginInstanceManager.Factory mInstanceManagerFactory;
    private final boolean mIsDebuggable;
    private final PluginPrefs mPluginPrefs;
    private final PluginEnabler mPluginEnabler;
    private final PluginInitializer mPluginInitializer;
    private ClassLoaderFilter mParentClassLoader;
    private boolean mListening;
    private boolean mHasOneShot;
    private Looper mLooper;

    public PluginManagerImpl(Context context, PluginInitializer initializer) {
        this(context, new PluginInstanceManagerFactory(), initializer.isDebuggable(),
                Thread.getUncaughtExceptionPreHandler(), initializer);
    }

    @VisibleForTesting
    PluginManagerImpl(Context context, PluginInstanceManagerFactory factory, boolean debuggable,
            UncaughtExceptionHandler defaultHandler, final PluginInitializer initializer) {
    public PluginManagerImpl(Context context,
            PluginInstanceManager.Factory instanceManagerFactory,
            boolean debuggable,
            Optional<UncaughtExceptionHandler> defaultHandlerOptional,
            PluginEnabler pluginEnabler,
            PluginPrefs pluginPrefs,
            String[] privilegedPlugins) {
        mContext = context;
        mFactory = factory;
        mLooper = initializer.getBgLooper();
        mInstanceManagerFactory = instanceManagerFactory;
        mIsDebuggable = debuggable;
        mWhitelistedPlugins.addAll(Arrays.asList(initializer.getWhitelistedPlugins(mContext)));
        mPluginPrefs = new PluginPrefs(mContext);
        mPluginEnabler = initializer.getPluginEnabler(mContext);
        mPluginInitializer = initializer;
        mPrivilegedPlugins.addAll(Arrays.asList(privilegedPlugins));
        mPluginPrefs = pluginPrefs;
        mPluginEnabler = pluginEnabler;

        PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
                defaultHandler);
                defaultHandlerOptional);
        Thread.setUncaughtExceptionPreHandler(uncaughtExceptionHandler);

        new Handler(mLooper).post(new Runnable() {
            @Override
            public void run() {
                initializer.onPluginManagerInit();
            }
        });
    }

    public boolean isDebuggable() {
        return mIsDebuggable;
    }

    public String[] getWhitelistedPlugins() {
        return mWhitelistedPlugins.toArray(new String[0]);
    public String[] getPrivilegedPlugins() {
        return mPrivilegedPlugins.toArray(new String[0]);
    }

    public PluginEnabler getPluginEnabler() {
@@ -138,9 +126,10 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
            throw new RuntimeException("Must be called from UI thread");
        }
        // Passing null causes compiler to complain about incompatible (generic) types.
        PluginListener<Plugin> dummy = null;
        PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, dummy,
                false, mLooper, cls, this);
        PluginListener<T> dummy = null;
        PluginInstanceManager<T> p = mInstanceManagerFactory.create(
                action, dummy, false, new VersionInfo().addClass(cls), this,
                isDebuggable(), getPrivilegedPlugins());
        mPluginPrefs.addAction(action);
        PluginInfo<T> info = p.getPlugin();
        if (info != null) {
@@ -167,10 +156,11 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
    }

    public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
            Class cls, boolean allowMultiple) {
            Class<?> cls, boolean allowMultiple) {
        mPluginPrefs.addAction(action);
        PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
                allowMultiple, mLooper, cls, this);
        PluginInstanceManager<T> p = mInstanceManagerFactory.create(action, listener, allowMultiple,
                new VersionInfo().addClass(cls), this, isDebuggable(),
                getPrivilegedPlugins());
        p.loadAll();
        synchronized (this) {
            mPluginMap.put(listener, p);
@@ -218,7 +208,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
            synchronized (this) {
                for (PluginInstanceManager manager : mPluginMap.values()) {
                for (PluginInstanceManager<?> manager : mPluginMap.values()) {
                    manager.loadAll();
                }
            }
@@ -226,8 +216,8 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
            Uri uri = intent.getData();
            ComponentName component = ComponentName.unflattenFromString(
                    uri.toString().substring(10));
            if (isPluginWhitelisted(component)) {
                // Don't disable whitelisted plugins as they are a part of the OS.
            if (isPluginPrivileged(component)) {
                // Don't disable privileged plugins as they are a part of the OS.
                return;
            }
            getPluginEnabler().setDisabled(component, PluginEnabler.DISABLED_INVALID_VERSION);
@@ -287,11 +277,11 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
            }
            synchronized (this) {
                if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
                    for (PluginInstanceManager manager : mPluginMap.values()) {
                    for (PluginInstanceManager<?> manager : mPluginMap.values()) {
                        manager.onPackageChange(pkg);
                    }
                } else {
                    for (PluginInstanceManager manager : mPluginMap.values()) {
                    for (PluginInstanceManager<?> manager : mPluginMap.values()) {
                        manager.onPackageRemoved(pkg);
                    }
                }
@@ -301,8 +291,8 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage

    /** Returns class loader specific for the given plugin. */
    public ClassLoader getClassLoader(ApplicationInfo appInfo) {
        if (!mIsDebuggable && !isPluginPackageWhitelisted(appInfo.packageName)) {
            Log.w(TAG, "Cannot get class loader for non-whitelisted plugin. Src:"
        if (!mIsDebuggable && !isPluginPackagePrivileged(appInfo.packageName)) {
            Log.w(TAG, "Cannot get class loader for non-privileged plugin. Src:"
                    + appInfo.sourceDir + ", pkg: " + appInfo.packageName);
            return null;
        }
@@ -345,32 +335,18 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
        return false;
    }

    public void handleWtfs() {
        mPluginInitializer.handleWtfs();
    }

    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        synchronized (this) {
            pw.println(String.format("  plugin map (%d):", mPluginMap.size()));
            for (PluginListener listener : mPluginMap.keySet()) {
            for (PluginListener<?> listener : mPluginMap.keySet()) {
                pw.println(String.format("    %s -> %s",
                        listener, mPluginMap.get(listener)));
            }
        }
    }

    @VisibleForTesting
    public static class PluginInstanceManagerFactory {
        public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
                String action, PluginListener<T> listener, boolean allowMultiple, Looper looper,
                Class<?> cls, PluginManagerImpl manager) {
            return new PluginInstanceManager(context, action, listener, allowMultiple, looper,
                    new VersionInfo().addClass(cls), manager);
        }
    }

    private boolean isPluginPackageWhitelisted(String packageName) {
        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)) {
@@ -383,8 +359,8 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
        return false;
    }

    private boolean isPluginWhitelisted(ComponentName pluginName) {
        for (String componentNameOrPackage : mWhitelistedPlugins) {
    private boolean isPluginPrivileged(ComponentName pluginName) {
        for (String componentNameOrPackage : mPrivilegedPlugins) {
            ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage);
            if (componentName != null) {
                if (componentName.equals(pluginName)) {
@@ -417,16 +393,20 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
    }

    private class PluginExceptionHandler implements UncaughtExceptionHandler {
        private final UncaughtExceptionHandler mHandler;
        private final Optional<UncaughtExceptionHandler> mExceptionHandlerOptional;

        private PluginExceptionHandler(UncaughtExceptionHandler handler) {
            mHandler = handler;
        private PluginExceptionHandler(
                Optional<UncaughtExceptionHandler> exceptionHandlerOptional) {
            mExceptionHandlerOptional = exceptionHandlerOptional;
        }

        @Override
        public void uncaughtException(Thread thread, Throwable throwable) {
            if (SystemProperties.getBoolean("plugin.debugging", false)) {
                mHandler.uncaughtException(thread, throwable);
                Throwable finalThrowable = throwable;
                mExceptionHandlerOptional.ifPresent(
                        handler -> handler.uncaughtException(thread, finalThrowable));

                return;
            }
            // Search for and disable plugins that may have been involved in this crash.
@@ -436,7 +416,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
                // disable all the plugins, so we can be sure that SysUI is running as
                // best as possible.
                synchronized (this) {
                    for (PluginInstanceManager manager : mPluginMap.values()) {
                    for (PluginInstanceManager<?> manager : mPluginMap.values()) {
                        disabledAny |= manager.disableAll();
                    }
                }
@@ -446,7 +426,9 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
            }

            // Run the normal exception handler so we can crash and cleanup our state.
            mHandler.uncaughtException(thread, throwable);
            Throwable finalThrowable = throwable;
            mExceptionHandlerOptional.ifPresent(
                    handler -> handler.uncaughtException(thread, finalThrowable));
        }

        private boolean checkStack(Throwable throwable) {
@@ -454,7 +436,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
            boolean disabledAny = false;
            synchronized (this) {
                for (StackTraceElement element : throwable.getStackTrace()) {
                    for (PluginInstanceManager manager : mPluginMap.values()) {
                    for (PluginInstanceManager<?> manager : mPluginMap.values()) {
                        disabledAny |= manager.checkAndDisable(element.getClassName());
                    }
                }
+3 −13
Original line number Diff line number Diff line
@@ -68,21 +68,15 @@ import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarOverlayController;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
import com.android.systemui.plugins.PluginInitializerImpl;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.unfold.UnfoldTransitionFactory;
import com.android.unfold.UnfoldTransitionProgressProvider;
import com.android.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -98,6 +92,9 @@ import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.theme.ThemeOverlayApplier;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.settings.SecureSettings;
import com.android.unfold.UnfoldTransitionFactory;
import com.android.unfold.UnfoldTransitionProgressProvider;
import com.android.unfold.config.UnfoldTransitionConfig;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.pip.Pip;

@@ -195,13 +192,6 @@ public class DependencyProvider {
        return new MetricsLogger();
    }

    /** */
    @Provides
    @SysUISingleton
    public PluginManager providePluginManager(Context context) {
        return new PluginManagerImpl(context, new PluginInitializerImpl());
    }

    /** */
    @SysUISingleton
    @Provides
Loading