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

Commit 6fe47701 authored by Dave Mankoff's avatar Dave Mankoff Committed by Android (Google) Code Review
Browse files

Merge changes from topics "b194781951-flags-5", "b194781951-flags-6",...

Merge changes from topics "b194781951-flags-5", "b194781951-flags-6", "b194781951-flags-7", "b194781951-flags-8" into sc-v2-dev

* changes:
  5/N Remove PluginInitializer entirely.
  4/N Remove #handleWtfs from PluginInitializer.
  3/N Remove #getPrivilegedPlugins from PluginInitializer.
  2/N Rename PluginInstanceManager to PluginActionManager.
parents 2d113fb2 bfb3a7a8
Loading
Loading
Loading
Loading
+432 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.shared.plugins;

import android.app.LoadedApk;
import android.app.Notification;
import android.app.Notification.Action;
import android.app.NotificationManager;
@@ -29,8 +30,6 @@ 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;
@@ -38,19 +37,22 @@ import android.view.LayoutInflater;
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.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.List;
import java.util.Map;
import java.util.concurrent.Executor;

public class PluginInstanceManager<T extends Plugin> {
/**
 * Coordinates all the available plugins for a given action.
 *
 * The available plugins are queried from the {@link PackageManager} via an an {@link Intent}
 * action.
 *
 * @param <T> The type of plugin that this contains.
 */
public class PluginActionManager<T extends Plugin> {

    private static final boolean DEBUG = false;

@@ -61,30 +63,34 @@ public class PluginInstanceManager<T extends Plugin> {
    private final PluginListener<T> mListener;
    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 PluginInstance.Factory mPluginInstanceFactory;
    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 ArrayList<PluginInstance<T>> mPluginInstances = new ArrayList<>();
    private final boolean mIsDebuggable;
    private final PackageManager mPm;
    private final PluginInitializer mInitializer;
    private final Class<T> mPluginClass;
    private final Executor mMainExecutor;
    private final Executor mBgExecutor;

    private PluginManagerImpl.ClassLoaderFilter mParentClassLoader;

    private PluginInstanceManager(Context context, PackageManager pm, String action,
            PluginListener<T> listener, boolean allowMultiple, Executor mainExecutor,
            Executor bgExecutor, VersionInfo version, boolean debuggable,
            PluginInitializer initializer, NotificationManager notificationManager,
            PluginEnabler pluginEnabler, List<String> privilegedPlugins,
            InstanceFactory<T> instanceFactory) {
        mInitializer = initializer;
    private PluginActionManager(
            Context context,
            PackageManager pm,
            String action,
            PluginListener<T> listener,
            Class<T> pluginClass,
            boolean allowMultiple,
            Executor mainExecutor,
            Executor bgExecutor,
            boolean debuggable,
            NotificationManager notificationManager,
            PluginEnabler pluginEnabler,
            List<String> privilegedPlugins,
            PluginInstance.Factory pluginInstanceFactory) {
        mPluginClass = pluginClass;
        mMainExecutor = mainExecutor;
        mBgExecutor = bgExecutor;
        mContext = context;
@@ -92,49 +98,56 @@ public class PluginInstanceManager<T extends Plugin> {
        mAction = action;
        mListener = listener;
        mAllowMultiple = allowMultiple;
        mVersion = version;
        mNotificationManager = notificationManager;
        mPluginEnabler = pluginEnabler;
        mInstanceFactory = instanceFactory;
        mPluginInstanceFactory = pluginInstanceFactory;
        mPrivilegedPlugins.addAll(privilegedPlugins);
        mIsDebuggable = debuggable;
    }

    /** Load all plugins matching this instance's action. */
    public void loadAll() {
        if (DEBUG) Log.d(TAG, "startListening");
        mBgExecutor.execute(this::queryAll);
    }

    /** Unload all plugins managed by this instance. */
    public void destroy() {
        if (DEBUG) Log.d(TAG, "stopListening");
        ArrayList<PluginInfo<T>> plugins = new ArrayList<>(mPlugins);
        for (PluginInfo<T> pluginInfo : plugins) {
            mMainExecutor.execute(() -> onPluginDisconnected(pluginInfo.mPlugin));
        ArrayList<PluginInstance<T>> plugins = new ArrayList<>(mPluginInstances);
        for (PluginInstance<T> plugInstance : plugins) {
            mMainExecutor.execute(() -> onPluginDisconnected(plugInstance));
        }
    }

    /** Unload all matching plugins managed by this instance. */
    public void onPackageRemoved(String pkg) {
        mBgExecutor.execute(() -> removePkg(pkg));
    }

    public void onPackageChange(String pkg) {
        mBgExecutor.execute(() -> removePkg(pkg));
        mBgExecutor.execute(() -> queryPkg(pkg));
    /** Unload and then reload all matching plugins managed by this instance. */
    public void reloadPackage(String pkg) {
        mBgExecutor.execute(() -> {
            removePkg(pkg);
            queryPkg(pkg);
        });
    }

    /** Disable a specific plugin managed by this instance. */
    public boolean checkAndDisable(String className) {
        boolean disableAny = false;
        ArrayList<PluginInfo<T>> plugins = new ArrayList<>(mPlugins);
        for (PluginInfo<T> info : plugins) {
            if (className.startsWith(info.mPackage)) {
        ArrayList<PluginInstance<T>> plugins = new ArrayList<>(mPluginInstances);
        for (PluginInstance<T> info : plugins) {
            if (className.startsWith(info.getPackage())) {
                disableAny |= disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
            }
        }
        return disableAny;
    }

    /** Disable all plugins managed by this instance. */
    public boolean disableAll() {
        ArrayList<PluginInfo<T>> plugins = new ArrayList<>(mPlugins);
        ArrayList<PluginInstance<T>> plugins = new ArrayList<>(mPluginInstances);
        boolean disabledAny = false;
        for (int i = 0; i < plugins.size(); i++) {
            disabledAny |= disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
@@ -142,21 +155,7 @@ public class PluginInstanceManager<T extends Plugin> {
        return disabledAny;
    }

    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) {
    boolean isPluginPrivileged(ComponentName pluginName) {
        for (String componentNameOrPackage : mPrivilegedPlugins) {
            ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage);
            if (componentName == null) {
@@ -172,16 +171,17 @@ public class PluginInstanceManager<T extends Plugin> {
        return false;
    }

    private boolean disable(PluginInfo<T> info, @PluginEnabler.DisableReason int reason) {
    private boolean disable(
            PluginInstance<T> pluginInstance, @PluginEnabler.DisableReason int reason) {
        // Live by the sword, die by the sword.
        // Misbehaving plugins get disabled and won't come back until uninstall/reinstall.

        ComponentName pluginComponent = new ComponentName(info.mPackage, info.mClass);
        ComponentName pluginComponent = pluginInstance.getComponentName();
        // 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 (isPluginPrivileged(pluginComponent)) {
            // Don't disable whitelisted plugins as they are a part of the OS.
            // Don't disable privileged plugins as they are a part of the OS.
            return false;
        }
        Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString());
@@ -191,10 +191,10 @@ public class PluginInstanceManager<T extends Plugin> {
    }

    <C> boolean dependsOn(Plugin p, Class<C> cls) {
        ArrayList<PluginInfo<T>> plugins = new ArrayList<>(mPlugins);
        for (PluginInfo<T> info : plugins) {
            if (info.mPlugin.getClass().getName().equals(p.getClass().getName())) {
                return info.mVersion != null && info.mVersion.hasClass(cls);
        ArrayList<PluginInstance<T>> instances = new ArrayList<>(mPluginInstances);
        for (PluginInstance<T> instance : instances) {
            if (instance.containsPluginClass(p.getClass())) {
                return instance.getVersionInfo() != null && instance.getVersionInfo().hasClass(cls);
            }
        }
        return false;
@@ -206,51 +206,40 @@ public class PluginInstanceManager<T extends Plugin> {
                getClass().getSimpleName(), hashCode(), mAction);
    }

    private void onPluginConnected(PluginInfo<T> pluginInfo) {
    private void onPluginConnected(PluginInstance<T> pluginInstance) {
        if (DEBUG) Log.d(TAG, "onPluginConnected");
        PluginPrefs.setHasPlugins(mContext);
        mInitializer.handleWtfs();
        if (!(pluginInfo.mPlugin instanceof PluginFragment)) {
            // Only call onCreate for plugins that aren't fragments, as fragments
            // will get the onCreate as part of the fragment lifecycle.
            pluginInfo.mPlugin.onCreate(mContext, pluginInfo.mPluginContext);
        }
        mListener.onPluginConnected(pluginInfo.mPlugin, pluginInfo.mPluginContext);
        pluginInstance.onCreate(mContext, mListener);
    }

    private void onPluginDisconnected(T plugin) {
    private void onPluginDisconnected(PluginInstance<T> pluginInstance) {
        if (DEBUG) Log.d(TAG, "onPluginDisconnected");
        mListener.onPluginDisconnected(plugin);
        if (!(plugin instanceof PluginFragment)) {
            // Only call onDestroy for plugins that aren't fragments, as fragments
            // will get the onDestroy as part of the fragment lifecycle.
            plugin.onDestroy();
        }
        pluginInstance.onDestroy(mListener);
    }

    private void queryAll() {
        if (DEBUG) Log.d(TAG, "queryAll " + mAction);
        for (int i = mPlugins.size() - 1; i >= 0; i--) {
            PluginInfo<T> pluginInfo = mPlugins.get(i);
            mMainExecutor.execute(() -> onPluginDisconnected(pluginInfo.mPlugin));
        for (int i = mPluginInstances.size() - 1; i >= 0; i--) {
            PluginInstance<T> pluginInstance = mPluginInstances.get(i);
            mMainExecutor.execute(() -> onPluginDisconnected(pluginInstance));
        }
        mPlugins.clear();
        mPluginInstances.clear();
        handleQueryPlugins(null);
    }

    private void removePkg(String pkg) {
        for (int i = mPlugins.size() - 1; i >= 0; i--) {
            final PluginInfo<T> pluginInfo = mPlugins.get(i);
            if (pluginInfo.mPackage.equals(pkg)) {
                mMainExecutor.execute(() -> onPluginDisconnected(pluginInfo.mPlugin));
                mPlugins.remove(i);
        for (int i = mPluginInstances.size() - 1; i >= 0; i--) {
            final PluginInstance<T> pluginInstance = mPluginInstances.get(i);
            if (pluginInstance.getPackage().equals(pkg)) {
                mMainExecutor.execute(() -> onPluginDisconnected(pluginInstance));
                mPluginInstances.remove(i);
            }
        }
    }

    private void queryPkg(String pkg) {
        if (DEBUG) Log.d(TAG, "queryPkg " + pkg + " " + mAction);
        if (mAllowMultiple || (mPlugins.size() == 0)) {
        if (mAllowMultiple || (mPluginInstances.size() == 0)) {
            handleQueryPlugins(pkg);
        } else {
            if (DEBUG) Log.d(TAG, "Too many of " + mAction);
@@ -281,16 +270,16 @@ public class PluginInstanceManager<T extends Plugin> {
        for (ResolveInfo info : result) {
            ComponentName name = new ComponentName(info.serviceInfo.packageName,
                    info.serviceInfo.name);
            PluginInfo<T> pluginInfo = handleLoadPlugin(name);
            if (pluginInfo == null) continue;

            PluginInstance<T> pluginInstance = loadPluginComponent(name);
            if (pluginInstance != null) {
                // add plugin before sending PLUGIN_CONNECTED message
            mPlugins.add(pluginInfo);
            mMainExecutor.execute(() -> onPluginConnected(pluginInfo));
                mPluginInstances.add(pluginInstance);
                mMainExecutor.execute(() -> onPluginConnected(pluginInstance));
            }
        }
    }

    protected PluginInfo<T> handleLoadPlugin(ComponentName component) {
    private PluginInstance<T> loadPluginComponent(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)) {
@@ -299,32 +288,43 @@ public class PluginInstanceManager<T extends Plugin> {
            return null;
        }
        if (!mPluginEnabler.isEnabled(component)) {
            if (DEBUG) Log.d(TAG, "Plugin is not enabled, aborting load: " + component);
            if (DEBUG) {
                Log.d(TAG, "Plugin is not enabled, aborting load: " + component);
            }
            return null;
        }
        String pkg = component.getPackageName();
        String cls = component.getClassName();
        String packageName = component.getPackageName();
        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)
            if (mPm.checkPermission(PLUGIN_PERMISSION, packageName)
                    != PackageManager.PERMISSION_GRANTED) {
                Log.d(TAG, "Plugin doesn't have permission: " + pkg);
                Log.d(TAG, "Plugin doesn't have permission: " + packageName);
                return null;
            }
            // 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);

            ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0);
            // TODO: Only create the plugin before version check if we need it for
            // legacy version check.
            T plugin = mInstanceFactory.create(pluginClass);
            if (DEBUG) {
                Log.d(TAG, "createPlugin");
            }
            try {
                VersionInfo version = checkVersion(pluginClass, plugin, mVersion);
                if (DEBUG) Log.d(TAG, "createPlugin");
                return new PluginInfo<>(pkg, cls, plugin, pluginContext, version);
                return mPluginInstanceFactory.create(
                        mContext, appInfo, component,
                        mPluginClass);
            } catch (InvalidVersionException e) {
                reportInvalidVersion(component, component.getClassName(), e);
            }
        } catch (Throwable e) {
            Log.w(TAG, "Couldn't load plugin: " + packageName, e);
            return null;
        }

        return null;
    }

    private void reportInvalidVersion(
            ComponentName component, String className, InvalidVersionException e) {
        final int icon = Resources.getSystem().getIdentifier(
                "stat_sys_warning", "drawable", "android");
        final int color = Resources.getSystem().getIdentifier(
@@ -337,10 +337,11 @@ public class PluginInstanceManager<T extends Plugin> {
                .setShowWhen(false)
                .setVisibility(Notification.VISIBILITY_PUBLIC)
                .setColor(mContext.getColor(color));
                String label = cls;
        String label = className;
        try {
            label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString();
        } catch (NameNotFoundException e2) {
            // no-op
        }
        if (!e.isTooNew()) {
            // Localization not required as this will never ever appear in a user build.
@@ -360,108 +361,48 @@ public class PluginInstanceManager<T extends Plugin> {
        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);
        }

        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;
        Log.w(TAG, "Plugin has invalid interface version " + e.getActualVersion()
                + ", expected " + e.getExpectedVersion());
    }

    /**
     * Construct a {@link PluginInstanceManager}
     * Construct a {@link PluginActionManager}
     */
    public static class Factory {
        private final Context mContext;
        private final PackageManager mPackageManager;
        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;
        private final PluginInstance.Factory mPluginInstanceFactory;

        public Factory(Context context, PackageManager packageManager,
                Executor mainExecutor, Executor bgExecutor, PluginInitializer initializer,
                Executor mainExecutor, Executor bgExecutor,
                NotificationManager notificationManager, PluginEnabler pluginEnabler,
                List<String> privilegedPlugins) {
                List<String> privilegedPlugins, PluginInstance.Factory pluginInstanceFactory) {
            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;
            mPluginInstanceFactory = pluginInstanceFactory;
        }

        <T extends Plugin> PluginInstanceManager<T> create(
                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);
        <T extends Plugin> PluginActionManager<T> create(
                String action, PluginListener<T> listener, Class<T> pluginClass,
                boolean allowMultiple, boolean debuggable) {
            return new PluginActionManager<>(mContext, mPackageManager, action, listener,
                    pluginClass, allowMultiple, mMainExecutor, mBgExecutor,
                    debuggable, mNotificationManager, mPluginEnabler,
                    mPrivilegedPlugins, mPluginInstanceFactory);
        }
    }

    /** */
    public static class PluginContextWrapper extends ContextWrapper {
        private final ClassLoader mClassLoader;
        private LayoutInflater mInflater;
@@ -488,26 +429,4 @@ public class PluginInstanceManager<T extends Plugin> {
        }
    }

    static class PluginInfo<T extends Plugin> {
        private final Context mPluginContext;
        private final VersionInfo mVersion;
        private final String mClass;
        T mPlugin;
        String mPackage;

        public PluginInfo(String pkg, String cls, T plugin, Context pluginContext,
                VersionInfo info) {
            mPlugin = plugin;
            mClass = cls;
            mPackage = pkg;
            mPluginContext = pluginContext;
            mVersion = info;
        }
    }

    static class InstanceFactory<T extends Plugin> {
        T create(Class cls) throws IllegalAccessException, InstantiationException {
            return (T) cls.newInstance();
        }
    }
}
+0 −34
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.android.systemui.shared.plugins;

import android.content.Context;

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

    /**
     * Return a list of plugins that don't get disabled when an exception occurs.
     */
    String[] getPrivilegedPlugins(Context context);


    /**
     * Called from {@link PluginInstanceManager}.
     */
    void handleWtfs();
}
+228 −0

File added.

Preview size limit exceeded, changes collapsed.

+6 −4

File changed.

Preview size limit exceeded, changes collapsed.

+22 −18

File changed.

Preview size limit exceeded, changes collapsed.

Loading