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