Merge "Daggerize Plugin code." into sc-v2-dev

This commit is contained in:
Dave Mankoff
2021-08-13 21:12:40 +00:00
committed by Android (Google) Code Review
16 changed files with 274 additions and 201 deletions

View File

@@ -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[] getPrivilegedPlugins(Context context);
String[] getWhitelistedPlugins(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();
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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());
}
}

View File

@@ -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

View File

@@ -23,6 +23,7 @@ import android.util.DisplayMetrics;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.systemui.dagger.qualifiers.TestHarness;
import com.android.systemui.plugins.PluginsModule;
import com.android.systemui.util.concurrency.GlobalConcurrencyModule;
import javax.inject.Singleton;
@@ -47,7 +48,9 @@ import dagger.Provides;
*/
@Module(includes = {
FrameworkServicesModule.class,
GlobalConcurrencyModule.class})
GlobalConcurrencyModule.class,
PluginsModule.class,
})
public class GlobalModule {
/** */

View File

@@ -18,8 +18,6 @@ package com.android.systemui.dagger;
import android.content.Context;
import com.android.systemui.util.concurrency.ThreadFactory;
import javax.inject.Singleton;
import dagger.BindsInstance;
@@ -55,9 +53,4 @@ public interface GlobalRootComponent {
* Builder for a SysUIComponent.
*/
SysUIComponent.Builder getSysUIComponent();
/**
* Build a {@link ThreadFactory}.
*/
ThreadFactory createThreadFactory();
}

View File

@@ -17,25 +17,27 @@ package com.android.systemui.plugins;
import android.util.ArrayMap;
import com.android.systemui.Dependency;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.PluginDependency.DependencyProvider;
import com.android.systemui.shared.plugins.PluginManager;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Lazy;
/**
*/
@SysUISingleton
@Singleton
public class PluginDependencyProvider extends DependencyProvider {
private final ArrayMap<Class<?>, Object> mDependencies = new ArrayMap<>();
private final PluginManager mManager;
private final Lazy<PluginManager> mManagerLazy;
/**
*/
@Inject
public PluginDependencyProvider(PluginManager manager) {
mManager = manager;
public PluginDependencyProvider(Lazy<PluginManager> managerLazy) {
mManagerLazy = managerLazy;
PluginDependency.sProvider = this;
}
@@ -51,7 +53,7 @@ public class PluginDependencyProvider extends DependencyProvider {
@Override
<T> T get(Plugin p, Class<T> cls) {
if (!mManager.dependsOn(p, cls)) {
if (!mManagerLazy.get().dependsOn(p, cls)) {
throw new IllegalArgumentException(p.getClass() + " does not depend on " + cls);
}
synchronized (mDependencies) {

View File

@@ -22,16 +22,22 @@ import android.content.pm.PackageManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.shared.plugins.PluginEnabler;
import javax.inject.Inject;
import javax.inject.Singleton;
/** */
@Singleton
public class PluginEnablerImpl implements PluginEnabler {
private static final String CRASH_DISABLED_PLUGINS_PREF_FILE = "auto_disabled_plugins_prefs";
private PackageManager mPm;
private final PackageManager mPm;
private final SharedPreferences mAutoDisabledPrefs;
public PluginEnablerImpl(Context context) {
this(context, context.getPackageManager());
}
@Inject
@VisibleForTesting public PluginEnablerImpl(Context context, PackageManager pm) {
mAutoDisabledPrefs = context.getSharedPreferences(
CRASH_DISABLED_PLUGINS_PREF_FILE, Context.MODE_PRIVATE);

View File

@@ -15,16 +15,17 @@
package com.android.systemui.plugins;
import android.content.Context;
import android.os.Build;
import android.os.Looper;
import android.util.Log;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.shared.plugins.PluginEnabler;
import com.android.systemui.shared.plugins.PluginInitializer;
import com.android.systemui.shared.plugins.PluginManagerImpl;
import javax.inject.Inject;
import javax.inject.Singleton;
/** */
@Singleton
public class PluginInitializerImpl implements PluginInitializer {
/**
@@ -33,44 +34,24 @@ public class PluginInitializerImpl implements PluginInitializer {
private static final boolean WTFS_SHOULD_CRASH = false;
private boolean mWtfsSet;
@Override
public Looper getBgLooper() {
return Dependency.get(Dependency.BG_LOOPER);
@Inject
public PluginInitializerImpl(PluginDependencyProvider dependencyProvider) {
dependencyProvider.allowPluginDependency(ActivityStarter.class);
}
@Override
public void onPluginManagerInit() {
// Plugin dependencies that don't have another good home can go here, but
// dependencies that have better places to init can happen elsewhere.
Dependency.get(PluginDependencyProvider.class)
.allowPluginDependency(ActivityStarter.class);
}
@Override
public String[] getWhitelistedPlugins(Context context) {
public String[] getPrivilegedPlugins(Context context) {
return context.getResources().getStringArray(R.array.config_pluginWhitelist);
}
public PluginEnabler getPluginEnabler(Context context) {
return new PluginEnablerImpl(context);
}
@Override
public void handleWtfs() {
if (WTFS_SHOULD_CRASH && !mWtfsSet) {
mWtfsSet = true;
Log.setWtfHandler(new Log.TerribleFailureHandler() {
@Override
public void onTerribleFailure(String tag, Log.TerribleFailure what,
boolean system) {
throw new PluginManagerImpl.CrashWhilePluginActiveException(what);
}
Log.setWtfHandler((tag, what, system) -> {
throw new PluginManagerImpl.CrashWhilePluginActiveException(what);
});
}
}
@Override
public boolean isDebuggable() {
return Build.IS_DEBUGGABLE;
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2021 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.plugins;
import static com.android.systemui.util.concurrency.GlobalConcurrencyModule.PRE_HANDLER;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Looper;
import com.android.systemui.shared.plugins.PluginEnabler;
import com.android.systemui.shared.plugins.PluginInitializer;
import com.android.systemui.shared.plugins.PluginInstanceManager;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.plugins.PluginPrefs;
import com.android.systemui.util.concurrency.ThreadFactory;
import java.util.Optional;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
/**
* Dagger Module for code related to plugins.
*
* Covers code both in com.android.systemui.plugins and code in
* com.android.systemui.shared.plugins.
*/
@Module
public abstract class PluginsModule {
public static final String PLUGIN_THREAD = "plugin_thread";
public static final String PLUGIN_DEBUG = "plugin_debug";
public static final String PLUGIN_PRIVILEGED = "plugin_privileged";
@Provides
@Named(PLUGIN_DEBUG)
static boolean providesPluginDebug() {
return Build.IS_DEBUGGABLE;
}
@Binds
abstract PluginEnabler bindsPluginEnablerImpl(PluginEnablerImpl impl);
@Binds
abstract PluginInitializer bindsPluginInitializerImpl(PluginInitializerImpl impl);
@Provides
@Singleton
static PluginInstanceManager.Factory providePluginInstanceManagerFactory(Context context,
PackageManager packageManager, @Named(PLUGIN_THREAD) Looper pluginLooper,
PluginInitializer initializer) {
return new PluginInstanceManager.Factory(
context, packageManager, pluginLooper, initializer);
}
@Provides
@Singleton
@Named(PLUGIN_THREAD)
static Looper providesPluginLooper(ThreadFactory threadFactory) {
return threadFactory.buildLooperOnNewThread("plugin");
}
@Provides
static PluginManager providesPluginManager(
Context context,
PluginInstanceManager.Factory instanceManagerFactory,
@Named(PLUGIN_DEBUG) boolean debug,
@Named(PRE_HANDLER)
Optional<Thread.UncaughtExceptionHandler> uncaughtExceptionHandlerOptional,
PluginEnabler pluginEnabler,
PluginPrefs pluginPrefs,
@Named(PLUGIN_PRIVILEGED) String[] privilegedPlugins) {
return new PluginManagerImpl(context, instanceManagerFactory, debug,
uncaughtExceptionHandlerOptional, pluginEnabler, pluginPrefs,
privilegedPlugins);
}
@Provides
static PluginPrefs providesPluginPrefs(Context context) {
return new PluginPrefs(context);
}
@Provides
@Named(PLUGIN_PRIVILEGED)
static String[] providesPrivilegedPlugins(PluginInitializer initializer, Context context) {
return initializer.getPrivilegedPlugins(context);
}
}

View File

@@ -106,8 +106,8 @@ public class PluginFragment extends PreferenceFragment {
PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES);
apps.forEach(app -> {
if (!plugins.containsKey(app.packageName)) return;
if (ArrayUtils.contains(manager.getWhitelistedPlugins(), app.packageName)) {
// Don't manage whitelisted plugins, they are part of the OS.
if (ArrayUtils.contains(manager.getPrivilegedPlugins(), app.packageName)) {
// Don't manage privileged plugins, they are part of the OS.
return;
}
SwitchPreference pref = new PluginPreference(prefContext, app, mPluginEnabler);

View File

@@ -22,8 +22,10 @@ import android.os.Looper;
import com.android.systemui.dagger.qualifiers.Main;
import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Binds;
@@ -35,6 +37,7 @@ import dagger.Provides;
*/
@Module
public abstract class GlobalConcurrencyModule {
public static final String PRE_HANDLER = "pre_handler";
/**
* Binds {@link ThreadFactoryImpl} to {@link ThreadFactory}.
@@ -73,4 +76,11 @@ public abstract class GlobalConcurrencyModule {
@Binds
@Singleton
public abstract Execution provideExecution(ExecutionImpl execution);
/** */
@Provides
@Named(PRE_HANDLER)
public static Optional<Thread.UncaughtExceptionHandler> providesUncaughtExceptionHandler() {
return Optional.ofNullable(Thread.getUncaughtExceptionPreHandler());
}
}

View File

@@ -82,6 +82,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
private PluginEnabler mMockEnabler;
ComponentName mTestPluginComponentName =
new ComponentName(WHITELISTED_PACKAGE, TestPlugin.class.getName());
private PluginInitializer mInitializer;
@Before
public void setup() throws Exception {
@@ -95,9 +96,10 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
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, mHandlerThread.getLooper(), mMockVersionInfo,
mMockManager, true, new String[0]);
mMockManager, true, new String[0], mInitializer);
sMockPlugin = mock(Plugin.class);
when(sMockPlugin.getVersion()).thenReturn(1);
}
@@ -194,7 +196,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
// Create a version that thinks the build is not debuggable.
mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
mMockManager, false, new String[0]);
mMockManager, false, new String[0], mInitializer);
setupFakePmQuery();
mPluginInstanceManager.loadAll();
@@ -211,7 +213,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
// Create a version that thinks the build is not debuggable.
mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
mMockManager, false, new String[] {WHITELISTED_PACKAGE});
mMockManager, false, new String[] {WHITELISTED_PACKAGE}, mInitializer);
setupFakePmQuery();
mPluginInstanceManager.loadAll();
@@ -255,7 +257,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
public void testDisableWhitelisted() throws Exception {
mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
mMockManager, false, new String[] {WHITELISTED_PACKAGE});
mMockManager, false, new String[] {WHITELISTED_PACKAGE}, mInitializer);
createPlugin(); // Get into valid created state.
mPluginInstanceManager.disableAll();

View File

@@ -30,19 +30,14 @@ import android.content.pm.PackageManager;
import android.net.Uri;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginEnablerImpl;
import com.android.systemui.plugins.PluginInitializerImpl;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.annotations.ProvidesInterface;
import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo;
import com.android.systemui.shared.plugins.PluginManagerImpl.PluginInstanceManagerFactory;
import org.junit.Before;
import org.junit.Test;
@@ -51,6 +46,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -59,11 +55,13 @@ public class PluginManagerTest extends SysuiTestCase {
private static final String WHITELISTED_PACKAGE = "com.android.systemui";
private PluginInstanceManagerFactory mMockFactory;
private PluginInstanceManager.Factory mMockFactory;
private PluginInstanceManager mMockPluginInstance;
private PluginManagerImpl mPluginManager;
private PluginListener mMockListener;
private PluginListener<?> mMockListener;
private PackageManager mMockPackageManager;
private PluginEnabler mPluginEnabler;
private PluginPrefs mPluginPrefs;
private UncaughtExceptionHandler mRealExceptionHandler;
private UncaughtExceptionHandler mMockExceptionHandler;
@@ -71,30 +69,23 @@ public class PluginManagerTest extends SysuiTestCase {
@Before
public void setup() throws Exception {
mDependency.injectTestDependency(Dependency.BG_LOOPER,
TestableLooper.get(this).getLooper());
mRealExceptionHandler = Thread.getUncaughtExceptionPreHandler();
mMockExceptionHandler = mock(UncaughtExceptionHandler.class);
mMockFactory = mock(PluginInstanceManagerFactory.class);
mMockFactory = mock(PluginInstanceManager.Factory.class);
mMockPluginInstance = mock(PluginInstanceManager.class);
when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any()))
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()))
.thenReturn(mMockPluginInstance);
mMockPackageManager = mock(PackageManager.class);
mPluginManager = new PluginManagerImpl(
getContext(), mMockFactory, true,
mMockExceptionHandler, new PluginInitializerImpl() {
@Override
public String[] getWhitelistedPlugins(Context context) {
return new String[0];
}
Optional.of(mMockExceptionHandler), mPluginEnabler,
mPluginPrefs, new String[0]);
@Override
public PluginEnabler getPluginEnabler(Context context) {
return new PluginEnablerImpl(context, mMockPackageManager);
}
});
resetExceptionHandler();
mMockListener = mock(PluginListener.class);
}
@@ -127,13 +118,10 @@ public class PluginManagerTest extends SysuiTestCase {
@Test
@RunWithLooper(setAsMainLooper = true)
public void testNonDebuggable_noWhitelist() {
mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, false,
mMockExceptionHandler, new PluginInitializerImpl() {
@Override
public String[] getWhitelistedPlugins(Context context) {
return new String[0];
}
});
mPluginManager = new PluginManagerImpl(
getContext(), mMockFactory, false,
Optional.of(mMockExceptionHandler), mPluginEnabler,
mPluginPrefs, new String[0]);
resetExceptionHandler();
String sourceDir = "myPlugin";
@@ -148,13 +136,10 @@ public class PluginManagerTest extends SysuiTestCase {
@Test
@RunWithLooper(setAsMainLooper = true)
public void testNonDebuggable_whitelistedPkg() {
mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, false,
mMockExceptionHandler, new PluginInitializerImpl() {
@Override
public String[] getWhitelistedPlugins(Context context) {
return new String[] {WHITELISTED_PACKAGE};
}
});
mPluginManager = new PluginManagerImpl(
getContext(), mMockFactory, false,
Optional.of(mMockExceptionHandler), mPluginEnabler,
mPluginPrefs, new String[] {WHITELISTED_PACKAGE});
resetExceptionHandler();
String sourceDir = "myPlugin";
@@ -211,9 +196,7 @@ public class PluginManagerTest extends SysuiTestCase {
intent.setData(Uri.parse("package://" + testComponent.flattenToString()));
mPluginManager.onReceive(mContext, intent);
verify(nm).cancel(eq(testComponent.getClassName()), eq(SystemMessage.NOTE_PLUGIN));
verify(mMockPackageManager).setComponentEnabledSetting(eq(testComponent),
eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
eq(PackageManager.DONT_KILL_APP));
verify(mPluginEnabler).setDisabled(testComponent, PluginEnabler.DISABLED_INVALID_VERSION);
}
private void resetExceptionHandler() {

View File

@@ -22,7 +22,7 @@ import com.android.systemui.shared.plugins.PluginManager;
public class FakePluginManager implements PluginManager {
private final BaseLeakChecker<PluginListener> mLeakChecker;
private final BaseLeakChecker<PluginListener<?>> mLeakChecker;
public FakePluginManager(LeakCheck test) {
mLeakChecker = new BaseLeakChecker<>(test, "Plugin");
@@ -30,7 +30,7 @@ public class FakePluginManager implements PluginManager {
@Override
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
Class cls, boolean allowMultiple) {
Class<?> cls, boolean allowMultiple) {
mLeakChecker.addCallback(listener);
}
@@ -62,7 +62,7 @@ public class FakePluginManager implements PluginManager {
}
@Override
public String[] getWhitelistedPlugins() {
public String[] getPrivilegedPlugins() {
return new String[0];
}