Merge changes from topics "b194781951-flags", "revert-15798157-revert-15560568-b194781951-flags-8-ZUNMWRYIZP-RBPLTLZZWW" into sc-v2-dev am: 886addc29d am: 16706806b8
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15804493 Change-Id: I892092d6300caec0bd76aee61d9e62ee17faeef9
This commit is contained in:
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.shared.plugins;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.Notification.Action;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.systemui.plugins.Plugin;
|
||||
import com.android.systemui.plugins.PluginListener;
|
||||
import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Coordinates all the available plugins for a given action.
|
||||
*
|
||||
* The available plugins are queried from the {@link PackageManager} via an an {@link Intent}
|
||||
* action.
|
||||
*
|
||||
* @param <T> The type of plugin that this contains.
|
||||
*/
|
||||
public class PluginActionManager<T extends Plugin> {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String TAG = "PluginInstanceManager";
|
||||
public static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
|
||||
|
||||
private final Context mContext;
|
||||
private final PluginListener<T> mListener;
|
||||
private final String mAction;
|
||||
private final boolean mAllowMultiple;
|
||||
private final NotificationManager mNotificationManager;
|
||||
private final PluginEnabler mPluginEnabler;
|
||||
private final PluginInstance.Factory mPluginInstanceFactory;
|
||||
private final ArraySet<String> mPrivilegedPlugins = new ArraySet<>();
|
||||
|
||||
@VisibleForTesting
|
||||
private final ArrayList<PluginInstance<T>> mPluginInstances = new ArrayList<>();
|
||||
private final boolean mIsDebuggable;
|
||||
private final PackageManager mPm;
|
||||
private final Class<T> mPluginClass;
|
||||
private final Executor mMainExecutor;
|
||||
private final Executor mBgExecutor;
|
||||
|
||||
private PluginActionManager(
|
||||
Context context,
|
||||
PackageManager pm,
|
||||
String action,
|
||||
PluginListener<T> listener,
|
||||
Class<T> pluginClass,
|
||||
boolean allowMultiple,
|
||||
Executor mainExecutor,
|
||||
Executor bgExecutor,
|
||||
boolean debuggable,
|
||||
NotificationManager notificationManager,
|
||||
PluginEnabler pluginEnabler,
|
||||
List<String> privilegedPlugins,
|
||||
PluginInstance.Factory pluginInstanceFactory) {
|
||||
mPluginClass = pluginClass;
|
||||
mMainExecutor = mainExecutor;
|
||||
mBgExecutor = bgExecutor;
|
||||
mContext = context;
|
||||
mPm = pm;
|
||||
mAction = action;
|
||||
mListener = listener;
|
||||
mAllowMultiple = allowMultiple;
|
||||
mNotificationManager = notificationManager;
|
||||
mPluginEnabler = pluginEnabler;
|
||||
mPluginInstanceFactory = pluginInstanceFactory;
|
||||
mPrivilegedPlugins.addAll(privilegedPlugins);
|
||||
mIsDebuggable = debuggable;
|
||||
}
|
||||
|
||||
/** Load all plugins matching this instance's action. */
|
||||
public void loadAll() {
|
||||
if (DEBUG) Log.d(TAG, "startListening");
|
||||
mBgExecutor.execute(this::queryAll);
|
||||
}
|
||||
|
||||
/** Unload all plugins managed by this instance. */
|
||||
public void destroy() {
|
||||
if (DEBUG) Log.d(TAG, "stopListening");
|
||||
ArrayList<PluginInstance<T>> plugins = new ArrayList<>(mPluginInstances);
|
||||
for (PluginInstance<T> plugInstance : plugins) {
|
||||
mMainExecutor.execute(() -> onPluginDisconnected(plugInstance));
|
||||
}
|
||||
}
|
||||
|
||||
/** Unload all matching plugins managed by this instance. */
|
||||
public void onPackageRemoved(String pkg) {
|
||||
mBgExecutor.execute(() -> removePkg(pkg));
|
||||
}
|
||||
|
||||
/** Unload and then reload all matching plugins managed by this instance. */
|
||||
public void reloadPackage(String pkg) {
|
||||
mBgExecutor.execute(() -> {
|
||||
removePkg(pkg);
|
||||
queryPkg(pkg);
|
||||
});
|
||||
}
|
||||
|
||||
/** Disable a specific plugin managed by this instance. */
|
||||
public boolean checkAndDisable(String className) {
|
||||
boolean disableAny = false;
|
||||
ArrayList<PluginInstance<T>> plugins = new ArrayList<>(mPluginInstances);
|
||||
for (PluginInstance<T> info : plugins) {
|
||||
if (className.startsWith(info.getPackage())) {
|
||||
disableAny |= disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
|
||||
}
|
||||
}
|
||||
return disableAny;
|
||||
}
|
||||
|
||||
/** Disable all plugins managed by this instance. */
|
||||
public boolean disableAll() {
|
||||
ArrayList<PluginInstance<T>> plugins = new ArrayList<>(mPluginInstances);
|
||||
boolean disabledAny = false;
|
||||
for (int i = 0; i < plugins.size(); i++) {
|
||||
disabledAny |= disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
|
||||
}
|
||||
return disabledAny;
|
||||
}
|
||||
|
||||
boolean isPluginPrivileged(ComponentName pluginName) {
|
||||
for (String componentNameOrPackage : mPrivilegedPlugins) {
|
||||
ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage);
|
||||
if (componentName == null) {
|
||||
if (componentNameOrPackage.equals(pluginName.getPackageName())) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (componentName.equals(pluginName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean disable(
|
||||
PluginInstance<T> pluginInstance, @PluginEnabler.DisableReason int reason) {
|
||||
// Live by the sword, die by the sword.
|
||||
// Misbehaving plugins get disabled and won't come back until uninstall/reinstall.
|
||||
|
||||
ComponentName pluginComponent = pluginInstance.getComponentName();
|
||||
// If a plugin is detected in the stack of a crash then this will be called for that
|
||||
// plugin, if the plugin causing a crash cannot be identified, they are all disabled
|
||||
// assuming one of them must be bad.
|
||||
if (isPluginPrivileged(pluginComponent)) {
|
||||
// Don't disable privileged plugins as they are a part of the OS.
|
||||
return false;
|
||||
}
|
||||
Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString());
|
||||
mPluginEnabler.setDisabled(pluginComponent, reason);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
<C> boolean dependsOn(Plugin p, Class<C> cls) {
|
||||
ArrayList<PluginInstance<T>> instances = new ArrayList<>(mPluginInstances);
|
||||
for (PluginInstance<T> instance : instances) {
|
||||
if (instance.containsPluginClass(p.getClass())) {
|
||||
return instance.getVersionInfo() != null && instance.getVersionInfo().hasClass(cls);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s@%s (action=%s)",
|
||||
getClass().getSimpleName(), hashCode(), mAction);
|
||||
}
|
||||
|
||||
private void onPluginConnected(PluginInstance<T> pluginInstance) {
|
||||
if (DEBUG) Log.d(TAG, "onPluginConnected");
|
||||
PluginPrefs.setHasPlugins(mContext);
|
||||
pluginInstance.onCreate(mContext, mListener);
|
||||
}
|
||||
|
||||
private void onPluginDisconnected(PluginInstance<T> pluginInstance) {
|
||||
if (DEBUG) Log.d(TAG, "onPluginDisconnected");
|
||||
pluginInstance.onDestroy(mListener);
|
||||
}
|
||||
|
||||
private void queryAll() {
|
||||
if (DEBUG) Log.d(TAG, "queryAll " + mAction);
|
||||
for (int i = mPluginInstances.size() - 1; i >= 0; i--) {
|
||||
PluginInstance<T> pluginInstance = mPluginInstances.get(i);
|
||||
mMainExecutor.execute(() -> onPluginDisconnected(pluginInstance));
|
||||
}
|
||||
mPluginInstances.clear();
|
||||
handleQueryPlugins(null);
|
||||
}
|
||||
|
||||
private void removePkg(String pkg) {
|
||||
for (int i = mPluginInstances.size() - 1; i >= 0; i--) {
|
||||
final PluginInstance<T> pluginInstance = mPluginInstances.get(i);
|
||||
if (pluginInstance.getPackage().equals(pkg)) {
|
||||
mMainExecutor.execute(() -> onPluginDisconnected(pluginInstance));
|
||||
mPluginInstances.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void queryPkg(String pkg) {
|
||||
if (DEBUG) Log.d(TAG, "queryPkg " + pkg + " " + mAction);
|
||||
if (mAllowMultiple || (mPluginInstances.size() == 0)) {
|
||||
handleQueryPlugins(pkg);
|
||||
} else {
|
||||
if (DEBUG) Log.d(TAG, "Too many of " + mAction);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleQueryPlugins(String pkgName) {
|
||||
// This isn't actually a service and shouldn't ever be started, but is
|
||||
// a convenient PM based way to manage our plugins.
|
||||
Intent intent = new Intent(mAction);
|
||||
if (pkgName != null) {
|
||||
intent.setPackage(pkgName);
|
||||
}
|
||||
List<ResolveInfo> result = mPm.queryIntentServices(intent, 0);
|
||||
if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins");
|
||||
if (result.size() > 1 && !mAllowMultiple) {
|
||||
// TODO: Show warning.
|
||||
Log.w(TAG, "Multiple plugins found for " + mAction);
|
||||
if (DEBUG) {
|
||||
for (ResolveInfo info : result) {
|
||||
ComponentName name = new ComponentName(info.serviceInfo.packageName,
|
||||
info.serviceInfo.name);
|
||||
Log.w(TAG, " " + name);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (ResolveInfo info : result) {
|
||||
ComponentName name = new ComponentName(info.serviceInfo.packageName,
|
||||
info.serviceInfo.name);
|
||||
PluginInstance<T> pluginInstance = loadPluginComponent(name);
|
||||
if (pluginInstance != null) {
|
||||
// add plugin before sending PLUGIN_CONNECTED message
|
||||
mPluginInstances.add(pluginInstance);
|
||||
mMainExecutor.execute(() -> onPluginConnected(pluginInstance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PluginInstance<T> loadPluginComponent(ComponentName component) {
|
||||
// This was already checked, but do it again here to make extra extra sure, we don't
|
||||
// use these on production builds.
|
||||
if (!mIsDebuggable && !isPluginPrivileged(component)) {
|
||||
// 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 packageName = component.getPackageName();
|
||||
try {
|
||||
// TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on
|
||||
if (mPm.checkPermission(PLUGIN_PERMISSION, packageName)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Plugin doesn't have permission: " + packageName);
|
||||
return null;
|
||||
}
|
||||
|
||||
ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0);
|
||||
// TODO: Only create the plugin before version check if we need it for
|
||||
// legacy version check.
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "createPlugin");
|
||||
}
|
||||
try {
|
||||
return mPluginInstanceFactory.create(
|
||||
mContext, appInfo, component,
|
||||
mPluginClass);
|
||||
} catch (InvalidVersionException e) {
|
||||
reportInvalidVersion(component, component.getClassName(), e);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Log.w(TAG, "Couldn't load plugin: " + packageName, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void reportInvalidVersion(
|
||||
ComponentName component, String className, InvalidVersionException e) {
|
||||
final int icon = Resources.getSystem().getIdentifier(
|
||||
"stat_sys_warning", "drawable", "android");
|
||||
final int color = Resources.getSystem().getIdentifier(
|
||||
"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 = className;
|
||||
try {
|
||||
label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString();
|
||||
} catch (NameNotFoundException e2) {
|
||||
// no-op
|
||||
}
|
||||
if (!e.isTooNew()) {
|
||||
// Localization not required as this will never ever appear in a user build.
|
||||
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 " + e.getActualVersion()
|
||||
+ ", expected " + e.getExpectedVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link PluginActionManager}
|
||||
*/
|
||||
public static class Factory {
|
||||
private final Context mContext;
|
||||
private final PackageManager mPackageManager;
|
||||
private final Executor mMainExecutor;
|
||||
private final Executor mBgExecutor;
|
||||
private final NotificationManager mNotificationManager;
|
||||
private final PluginEnabler mPluginEnabler;
|
||||
private final List<String> mPrivilegedPlugins;
|
||||
private final PluginInstance.Factory mPluginInstanceFactory;
|
||||
|
||||
public Factory(Context context, PackageManager packageManager,
|
||||
Executor mainExecutor, Executor bgExecutor,
|
||||
NotificationManager notificationManager, PluginEnabler pluginEnabler,
|
||||
List<String> privilegedPlugins, PluginInstance.Factory pluginInstanceFactory) {
|
||||
mContext = context;
|
||||
mPackageManager = packageManager;
|
||||
mMainExecutor = mainExecutor;
|
||||
mBgExecutor = bgExecutor;
|
||||
mNotificationManager = notificationManager;
|
||||
mPluginEnabler = pluginEnabler;
|
||||
mPrivilegedPlugins = privilegedPlugins;
|
||||
mPluginInstanceFactory = pluginInstanceFactory;
|
||||
}
|
||||
|
||||
<T extends Plugin> PluginActionManager<T> create(
|
||||
String action, PluginListener<T> listener, Class<T> pluginClass,
|
||||
boolean allowMultiple, boolean debuggable) {
|
||||
return new PluginActionManager<>(mContext, mPackageManager, action, listener,
|
||||
pluginClass, allowMultiple, mMainExecutor, mBgExecutor,
|
||||
debuggable, mNotificationManager, mPluginEnabler,
|
||||
mPrivilegedPlugins, mPluginInstanceFactory);
|
||||
}
|
||||
}
|
||||
|
||||
/** */
|
||||
public static class PluginContextWrapper extends ContextWrapper {
|
||||
private final ClassLoader mClassLoader;
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
public PluginContextWrapper(Context base, ClassLoader classLoader) {
|
||||
super(base);
|
||||
mClassLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getClassLoader() {
|
||||
return mClassLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSystemService(String name) {
|
||||
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
|
||||
if (mInflater == null) {
|
||||
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
|
||||
}
|
||||
return mInflater;
|
||||
}
|
||||
return getBaseContext().getSystemService(name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.shared.plugins;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Provides necessary components for initializing {@link PluginManagerImpl}.
|
||||
*/
|
||||
public interface PluginInitializer {
|
||||
|
||||
/**
|
||||
* Return a list of plugins that don't get disabled when an exception occurs.
|
||||
*/
|
||||
String[] getPrivilegedPlugins(Context context);
|
||||
|
||||
|
||||
/**
|
||||
* Called from {@link PluginInstanceManager}.
|
||||
*/
|
||||
void handleWtfs();
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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.shared.plugins;
|
||||
|
||||
import android.app.LoadedApk;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.plugins.Plugin;
|
||||
import com.android.systemui.plugins.PluginFragment;
|
||||
import com.android.systemui.plugins.PluginListener;
|
||||
|
||||
import dalvik.system.PathClassLoader;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Contains a single instantiation of a Plugin.
|
||||
*
|
||||
* This class and its related Factory are in charge of actually instantiating a plugin and
|
||||
* managing any state related to it.
|
||||
*
|
||||
* @param <T> The type of plugin that this contains.
|
||||
*/
|
||||
public class PluginInstance<T extends Plugin> {
|
||||
private static final String TAG = "PluginInstance";
|
||||
private static final Map<String, ClassLoader> sClassLoaders = new ArrayMap<>();
|
||||
|
||||
private final Context mPluginContext;
|
||||
private final VersionInfo mVersionInfo;
|
||||
private final ComponentName mComponentName;
|
||||
private final T mPlugin;
|
||||
|
||||
/** */
|
||||
public PluginInstance(ComponentName componentName, T plugin, Context pluginContext,
|
||||
VersionInfo versionInfo) {
|
||||
mComponentName = componentName;
|
||||
mPlugin = plugin;
|
||||
mPluginContext = pluginContext;
|
||||
mVersionInfo = versionInfo;
|
||||
}
|
||||
|
||||
/** Alerts listener and plugin that the plugin has been created. */
|
||||
public void onCreate(Context appContext, PluginListener<T> listener) {
|
||||
if (!(mPlugin instanceof PluginFragment)) {
|
||||
// Only call onCreate for plugins that aren't fragments, as fragments
|
||||
// will get the onCreate as part of the fragment lifecycle.
|
||||
mPlugin.onCreate(appContext, mPluginContext);
|
||||
}
|
||||
listener.onPluginConnected(mPlugin, mPluginContext);
|
||||
}
|
||||
|
||||
/** Alerts listener and plugin that the plugin is being shutdown. */
|
||||
public void onDestroy(PluginListener<T> listener) {
|
||||
listener.onPluginDisconnected(mPlugin);
|
||||
if (!(mPlugin instanceof PluginFragment)) {
|
||||
// Only call onDestroy for plugins that aren't fragments, as fragments
|
||||
// will get the onDestroy as part of the fragment lifecycle.
|
||||
mPlugin.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the contained plugin matches the passed in class name.
|
||||
*
|
||||
* It does this by string comparison of the class names.
|
||||
**/
|
||||
public boolean containsPluginClass(Class pluginClass) {
|
||||
return mPlugin.getClass().getName().equals(pluginClass.getName());
|
||||
}
|
||||
|
||||
public ComponentName getComponentName() {
|
||||
return mComponentName;
|
||||
}
|
||||
|
||||
public String getPackage() {
|
||||
return mComponentName.getPackageName();
|
||||
}
|
||||
|
||||
public VersionInfo getVersionInfo() {
|
||||
return mVersionInfo;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Context getPluginContext() {
|
||||
return mPluginContext;
|
||||
}
|
||||
|
||||
/** Used to create new {@link PluginInstance}s. */
|
||||
public static class Factory {
|
||||
private final ClassLoader mBaseClassLoader;
|
||||
private final InstanceFactory<?> mInstanceFactory;
|
||||
private final VersionChecker mVersionChecker;
|
||||
private final boolean mIsDebug;
|
||||
private final List<String> mPrivilegedPlugins;
|
||||
|
||||
/** Factory used to construct {@link PluginInstance}s. */
|
||||
public Factory(ClassLoader classLoader, InstanceFactory<?> instanceFactory,
|
||||
VersionChecker versionChecker,
|
||||
List<String> privilegedPlugins,
|
||||
boolean isDebug) {
|
||||
mPrivilegedPlugins = privilegedPlugins;
|
||||
mBaseClassLoader = classLoader;
|
||||
mInstanceFactory = instanceFactory;
|
||||
mVersionChecker = versionChecker;
|
||||
mIsDebug = isDebug;
|
||||
}
|
||||
|
||||
/** Construct a new PluginInstance. */
|
||||
public <T extends Plugin> PluginInstance<T> create(
|
||||
Context context,
|
||||
ApplicationInfo appInfo,
|
||||
ComponentName componentName,
|
||||
Class<T> pluginClass)
|
||||
throws PackageManager.NameNotFoundException, ClassNotFoundException,
|
||||
InstantiationException, IllegalAccessException {
|
||||
|
||||
ClassLoader classLoader = getClassLoader(appInfo, mBaseClassLoader);
|
||||
Context pluginContext = new PluginActionManager.PluginContextWrapper(
|
||||
context.createApplicationContext(appInfo, 0), classLoader);
|
||||
Class<T> instanceClass = (Class<T>) Class.forName(
|
||||
componentName.getClassName(), true, classLoader);
|
||||
// TODO: Only create the plugin before version check if we need it for
|
||||
// legacy version check.
|
||||
T instance = (T) mInstanceFactory.create(instanceClass);
|
||||
VersionInfo version = mVersionChecker.checkVersion(
|
||||
instanceClass, pluginClass, instance);
|
||||
return new PluginInstance<T>(componentName, instance, pluginContext, version);
|
||||
}
|
||||
|
||||
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 ClassLoader getParentClassLoader(ClassLoader baseClassLoader) {
|
||||
return new PluginManagerImpl.ClassLoaderFilter(
|
||||
baseClassLoader, "com.android.systemui.plugin");
|
||||
}
|
||||
|
||||
/** Returns class loader specific for the given plugin. */
|
||||
private ClassLoader getClassLoader(ApplicationInfo appInfo,
|
||||
ClassLoader baseClassLoader) {
|
||||
if (!mIsDebug && !isPluginPackagePrivileged(appInfo.packageName)) {
|
||||
Log.w(TAG, "Cannot get class loader for non-privileged plugin. Src:"
|
||||
+ appInfo.sourceDir + ", pkg: " + appInfo.packageName);
|
||||
return null;
|
||||
}
|
||||
if (sClassLoaders.containsKey(appInfo.packageName)) {
|
||||
return sClassLoaders.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(baseClassLoader));
|
||||
sClassLoaders.put(appInfo.packageName, classLoader);
|
||||
return classLoader;
|
||||
}
|
||||
}
|
||||
|
||||
/** Class that compares a plugin class against an implementation for version matching. */
|
||||
public static class VersionChecker {
|
||||
/** Compares two plugin classes. */
|
||||
public <T extends Plugin> VersionInfo checkVersion(
|
||||
Class<T> instanceClass, Class<T> pluginClass, Plugin plugin) {
|
||||
VersionInfo pluginVersion = new VersionInfo().addClass(pluginClass);
|
||||
VersionInfo instanceVersion = new VersionInfo().addClass(instanceClass);
|
||||
if (instanceVersion.hasVersionInfo()) {
|
||||
pluginVersion.checkVersion(instanceVersion);
|
||||
} else {
|
||||
int fallbackVersion = plugin.getVersion();
|
||||
if (fallbackVersion != pluginVersion.getDefaultVersion()) {
|
||||
throw new VersionInfo.InvalidVersionException("Invalid legacy version", false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return instanceVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple class to create a new instance. Useful for testing.
|
||||
*
|
||||
* @param <T> The type of plugin this create.
|
||||
**/
|
||||
public static class InstanceFactory<T extends Plugin> {
|
||||
T create(Class cls) throws IllegalAccessException, InstantiationException {
|
||||
return (T) cls.newInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,513 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.shared.plugins;
|
||||
|
||||
import android.app.LoadedApk;
|
||||
import android.app.Notification;
|
||||
import android.app.Notification.Action;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.systemui.plugins.Plugin;
|
||||
import com.android.systemui.plugins.PluginFragment;
|
||||
import com.android.systemui.plugins.PluginListener;
|
||||
import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
|
||||
|
||||
import dalvik.system.PathClassLoader;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class PluginInstanceManager<T extends Plugin> {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String TAG = "PluginInstanceManager";
|
||||
public static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
|
||||
|
||||
private final Context mContext;
|
||||
private final PluginListener<T> mListener;
|
||||
private final String mAction;
|
||||
private final boolean mAllowMultiple;
|
||||
private final VersionInfo mVersion;
|
||||
private final NotificationManager mNotificationManager;
|
||||
private final PluginEnabler mPluginEnabler;
|
||||
private final InstanceFactory<T> mInstanceFactory;
|
||||
private final ArraySet<String> mPrivilegedPlugins = new ArraySet<>();
|
||||
private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>();
|
||||
|
||||
@VisibleForTesting
|
||||
private final ArrayList<PluginInfo<T>> mPlugins = new ArrayList<>();
|
||||
private final boolean mIsDebuggable;
|
||||
private final PackageManager mPm;
|
||||
private final PluginInitializer mInitializer;
|
||||
private final Executor mMainExecutor;
|
||||
private final Executor mBgExecutor;
|
||||
|
||||
private PluginManagerImpl.ClassLoaderFilter mParentClassLoader;
|
||||
|
||||
private PluginInstanceManager(Context context, PackageManager pm, String action,
|
||||
PluginListener<T> listener, boolean allowMultiple, Executor mainExecutor,
|
||||
Executor bgExecutor, VersionInfo version, boolean debuggable,
|
||||
PluginInitializer initializer, NotificationManager notificationManager,
|
||||
PluginEnabler pluginEnabler, List<String> privilegedPlugins,
|
||||
InstanceFactory<T> instanceFactory) {
|
||||
mInitializer = initializer;
|
||||
mMainExecutor = mainExecutor;
|
||||
mBgExecutor = bgExecutor;
|
||||
mContext = context;
|
||||
mPm = pm;
|
||||
mAction = action;
|
||||
mListener = listener;
|
||||
mAllowMultiple = allowMultiple;
|
||||
mVersion = version;
|
||||
mNotificationManager = notificationManager;
|
||||
mPluginEnabler = pluginEnabler;
|
||||
mInstanceFactory = instanceFactory;
|
||||
mPrivilegedPlugins.addAll(privilegedPlugins);
|
||||
mIsDebuggable = debuggable;
|
||||
}
|
||||
|
||||
public void loadAll() {
|
||||
if (DEBUG) Log.d(TAG, "startListening");
|
||||
mBgExecutor.execute(this::queryAll);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (DEBUG) Log.d(TAG, "stopListening");
|
||||
ArrayList<PluginInfo<T>> plugins = new ArrayList<>(mPlugins);
|
||||
for (PluginInfo<T> pluginInfo : plugins) {
|
||||
mMainExecutor.execute(() -> onPluginDisconnected(pluginInfo.mPlugin));
|
||||
}
|
||||
}
|
||||
|
||||
public void onPackageRemoved(String pkg) {
|
||||
mBgExecutor.execute(() -> removePkg(pkg));
|
||||
}
|
||||
|
||||
public void onPackageChange(String pkg) {
|
||||
mBgExecutor.execute(() -> removePkg(pkg));
|
||||
mBgExecutor.execute(() -> queryPkg(pkg));
|
||||
}
|
||||
|
||||
public boolean checkAndDisable(String className) {
|
||||
boolean disableAny = false;
|
||||
ArrayList<PluginInfo<T>> plugins = new ArrayList<>(mPlugins);
|
||||
for (PluginInfo<T> info : plugins) {
|
||||
if (className.startsWith(info.mPackage)) {
|
||||
disableAny |= disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
|
||||
}
|
||||
}
|
||||
return disableAny;
|
||||
}
|
||||
|
||||
public boolean disableAll() {
|
||||
ArrayList<PluginInfo<T>> plugins = new ArrayList<>(mPlugins);
|
||||
boolean disabledAny = false;
|
||||
for (int i = 0; i < plugins.size(); i++) {
|
||||
disabledAny |= disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
|
||||
}
|
||||
return disabledAny;
|
||||
}
|
||||
|
||||
private boolean isPluginPackagePrivileged(String packageName) {
|
||||
for (String componentNameOrPackage : mPrivilegedPlugins) {
|
||||
ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage);
|
||||
if (componentName != null) {
|
||||
if (componentName.getPackageName().equals(packageName)) {
|
||||
return true;
|
||||
}
|
||||
} else if (componentNameOrPackage.equals(packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isPluginPrivileged(ComponentName pluginName) {
|
||||
for (String componentNameOrPackage : mPrivilegedPlugins) {
|
||||
ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage);
|
||||
if (componentName == null) {
|
||||
if (componentNameOrPackage.equals(pluginName.getPackageName())) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (componentName.equals(pluginName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean disable(PluginInfo<T> info, @PluginEnabler.DisableReason int reason) {
|
||||
// Live by the sword, die by the sword.
|
||||
// Misbehaving plugins get disabled and won't come back until uninstall/reinstall.
|
||||
|
||||
ComponentName pluginComponent = new ComponentName(info.mPackage, info.mClass);
|
||||
// If a plugin is detected in the stack of a crash then this will be called for that
|
||||
// plugin, if the plugin causing a crash cannot be identified, they are all disabled
|
||||
// assuming one of them must be bad.
|
||||
if (isPluginPrivileged(pluginComponent)) {
|
||||
// Don't disable whitelisted plugins as they are a part of the OS.
|
||||
return false;
|
||||
}
|
||||
Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString());
|
||||
mPluginEnabler.setDisabled(pluginComponent, reason);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
<C> boolean dependsOn(Plugin p, Class<C> cls) {
|
||||
ArrayList<PluginInfo<T>> plugins = new ArrayList<>(mPlugins);
|
||||
for (PluginInfo<T> info : plugins) {
|
||||
if (info.mPlugin.getClass().getName().equals(p.getClass().getName())) {
|
||||
return info.mVersion != null && info.mVersion.hasClass(cls);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s@%s (action=%s)",
|
||||
getClass().getSimpleName(), hashCode(), mAction);
|
||||
}
|
||||
|
||||
private void onPluginConnected(PluginInfo<T> pluginInfo) {
|
||||
if (DEBUG) Log.d(TAG, "onPluginConnected");
|
||||
PluginPrefs.setHasPlugins(mContext);
|
||||
mInitializer.handleWtfs();
|
||||
if (!(pluginInfo.mPlugin instanceof PluginFragment)) {
|
||||
// Only call onCreate for plugins that aren't fragments, as fragments
|
||||
// will get the onCreate as part of the fragment lifecycle.
|
||||
pluginInfo.mPlugin.onCreate(mContext, pluginInfo.mPluginContext);
|
||||
}
|
||||
mListener.onPluginConnected(pluginInfo.mPlugin, pluginInfo.mPluginContext);
|
||||
}
|
||||
|
||||
private void onPluginDisconnected(T plugin) {
|
||||
if (DEBUG) Log.d(TAG, "onPluginDisconnected");
|
||||
mListener.onPluginDisconnected(plugin);
|
||||
if (!(plugin instanceof PluginFragment)) {
|
||||
// Only call onDestroy for plugins that aren't fragments, as fragments
|
||||
// will get the onDestroy as part of the fragment lifecycle.
|
||||
plugin.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
private void queryAll() {
|
||||
if (DEBUG) Log.d(TAG, "queryAll " + mAction);
|
||||
for (int i = mPlugins.size() - 1; i >= 0; i--) {
|
||||
PluginInfo<T> pluginInfo = mPlugins.get(i);
|
||||
mMainExecutor.execute(() -> onPluginDisconnected(pluginInfo.mPlugin));
|
||||
}
|
||||
mPlugins.clear();
|
||||
handleQueryPlugins(null);
|
||||
}
|
||||
|
||||
private void removePkg(String pkg) {
|
||||
for (int i = mPlugins.size() - 1; i >= 0; i--) {
|
||||
final PluginInfo<T> pluginInfo = mPlugins.get(i);
|
||||
if (pluginInfo.mPackage.equals(pkg)) {
|
||||
mMainExecutor.execute(() -> onPluginDisconnected(pluginInfo.mPlugin));
|
||||
mPlugins.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void queryPkg(String pkg) {
|
||||
if (DEBUG) Log.d(TAG, "queryPkg " + pkg + " " + mAction);
|
||||
if (mAllowMultiple || (mPlugins.size() == 0)) {
|
||||
handleQueryPlugins(pkg);
|
||||
} else {
|
||||
if (DEBUG) Log.d(TAG, "Too many of " + mAction);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleQueryPlugins(String pkgName) {
|
||||
// This isn't actually a service and shouldn't ever be started, but is
|
||||
// a convenient PM based way to manage our plugins.
|
||||
Intent intent = new Intent(mAction);
|
||||
if (pkgName != null) {
|
||||
intent.setPackage(pkgName);
|
||||
}
|
||||
List<ResolveInfo> result = mPm.queryIntentServices(intent, 0);
|
||||
if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins");
|
||||
if (result.size() > 1 && !mAllowMultiple) {
|
||||
// TODO: Show warning.
|
||||
Log.w(TAG, "Multiple plugins found for " + mAction);
|
||||
if (DEBUG) {
|
||||
for (ResolveInfo info : result) {
|
||||
ComponentName name = new ComponentName(info.serviceInfo.packageName,
|
||||
info.serviceInfo.name);
|
||||
Log.w(TAG, " " + name);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (ResolveInfo info : result) {
|
||||
ComponentName name = new ComponentName(info.serviceInfo.packageName,
|
||||
info.serviceInfo.name);
|
||||
PluginInfo<T> pluginInfo = handleLoadPlugin(name);
|
||||
if (pluginInfo == null) continue;
|
||||
|
||||
// add plugin before sending PLUGIN_CONNECTED message
|
||||
mPlugins.add(pluginInfo);
|
||||
mMainExecutor.execute(() -> onPluginConnected(pluginInfo));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// Create our own ClassLoader so we can use our own code as the parent.
|
||||
ClassLoader classLoader = getClassLoader(info);
|
||||
Context pluginContext = new PluginContextWrapper(
|
||||
mContext.createApplicationContext(info, 0), classLoader);
|
||||
Class<?> pluginClass = Class.forName(cls, true, classLoader);
|
||||
// TODO: Only create the plugin before version check if we need it for
|
||||
// legacy version check.
|
||||
T plugin = mInstanceFactory.create(pluginClass);
|
||||
try {
|
||||
VersionInfo version = checkVersion(pluginClass, plugin, mVersion);
|
||||
if (DEBUG) Log.d(TAG, "createPlugin");
|
||||
return new PluginInfo<>(pkg, cls, plugin, pluginContext, version);
|
||||
} catch (InvalidVersionException e) {
|
||||
final int icon = Resources.getSystem().getIdentifier(
|
||||
"stat_sys_warning", "drawable", "android");
|
||||
final int color = Resources.getSystem().getIdentifier(
|
||||
"system_notification_accent_color", "color", "android");
|
||||
final Notification.Builder nb = new Notification.Builder(mContext,
|
||||
PluginManager.NOTIFICATION_CHANNEL_ID)
|
||||
.setStyle(new Notification.BigTextStyle())
|
||||
.setSmallIcon(icon)
|
||||
.setWhen(0)
|
||||
.setShowWhen(false)
|
||||
.setVisibility(Notification.VISIBILITY_PUBLIC)
|
||||
.setColor(mContext.getColor(color));
|
||||
String label = cls;
|
||||
try {
|
||||
label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString();
|
||||
} catch (NameNotFoundException e2) {
|
||||
}
|
||||
if (!e.isTooNew()) {
|
||||
// Localization not required as this will never ever appear in a user build.
|
||||
nb.setContentTitle("Plugin \"" + label + "\" is too old")
|
||||
.setContentText("Contact plugin developer to get an updated"
|
||||
+ " version.\n" + e.getMessage());
|
||||
} else {
|
||||
// Localization not required as this will never ever appear in a user build.
|
||||
nb.setContentTitle("Plugin \"" + label + "\" is too new")
|
||||
.setContentText("Check to see if an OTA is available.\n"
|
||||
+ e.getMessage());
|
||||
}
|
||||
Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData(
|
||||
Uri.parse("package://" + component.flattenToString()));
|
||||
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i,
|
||||
PendingIntent.FLAG_IMMUTABLE);
|
||||
nb.addAction(new Action.Builder(null, "Disable plugin", pi).build());
|
||||
mNotificationManager.notify(SystemMessage.NOTE_PLUGIN, nb.build());
|
||||
// TODO: Warn user.
|
||||
Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion()
|
||||
+ ", expected " + mVersion);
|
||||
return null;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Log.w(TAG, "Couldn't load plugin: " + pkg, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private VersionInfo checkVersion(Class<?> pluginClass, T plugin, VersionInfo version)
|
||||
throws InvalidVersionException {
|
||||
VersionInfo pv = new VersionInfo().addClass(pluginClass);
|
||||
if (pv.hasVersionInfo()) {
|
||||
version.checkVersion(pv);
|
||||
} else {
|
||||
int fallbackVersion = plugin.getVersion();
|
||||
if (fallbackVersion != version.getDefaultVersion()) {
|
||||
throw new InvalidVersionException("Invalid legacy version", false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return pv;
|
||||
}
|
||||
|
||||
/** Returns class loader specific for the given plugin. */
|
||||
public ClassLoader getClassLoader(ApplicationInfo appInfo) {
|
||||
if (!mIsDebuggable && !isPluginPackagePrivileged(appInfo.packageName)) {
|
||||
Log.w(TAG, "Cannot get class loader for non-privileged plugin. Src:"
|
||||
+ appInfo.sourceDir + ", pkg: " + appInfo.packageName);
|
||||
return null;
|
||||
}
|
||||
if (mClassLoaders.containsKey(appInfo.packageName)) {
|
||||
return mClassLoaders.get(appInfo.packageName);
|
||||
}
|
||||
|
||||
List<String> zipPaths = new ArrayList<>();
|
||||
List<String> libPaths = new ArrayList<>();
|
||||
LoadedApk.makePaths(null, true, appInfo, zipPaths, libPaths);
|
||||
ClassLoader classLoader = new PathClassLoader(
|
||||
TextUtils.join(File.pathSeparator, zipPaths),
|
||||
TextUtils.join(File.pathSeparator, libPaths),
|
||||
getParentClassLoader());
|
||||
mClassLoaders.put(appInfo.packageName, classLoader);
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
private ClassLoader getParentClassLoader() {
|
||||
if (mParentClassLoader == null) {
|
||||
// Lazily load this so it doesn't have any effect on devices without plugins.
|
||||
mParentClassLoader = new PluginManagerImpl.ClassLoaderFilter(
|
||||
getClass().getClassLoader(), "com.android.systemui.plugin");
|
||||
}
|
||||
return mParentClassLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link PluginInstanceManager}
|
||||
*/
|
||||
public static class Factory {
|
||||
private final Context mContext;
|
||||
private final PackageManager mPackageManager;
|
||||
private final Executor mMainExecutor;
|
||||
private final Executor mBgExecutor;
|
||||
private final PluginInitializer mInitializer;
|
||||
private final NotificationManager mNotificationManager;
|
||||
private final PluginEnabler mPluginEnabler;
|
||||
private final List<String> mPrivilegedPlugins;
|
||||
private InstanceFactory<?> mInstanceFactory;
|
||||
|
||||
public Factory(Context context, PackageManager packageManager,
|
||||
Executor mainExecutor, Executor bgExecutor, PluginInitializer initializer,
|
||||
NotificationManager notificationManager, PluginEnabler pluginEnabler,
|
||||
List<String> privilegedPlugins) {
|
||||
mContext = context;
|
||||
mPackageManager = packageManager;
|
||||
mMainExecutor = mainExecutor;
|
||||
mBgExecutor = bgExecutor;
|
||||
mInitializer = initializer;
|
||||
mNotificationManager = notificationManager;
|
||||
mPluginEnabler = pluginEnabler;
|
||||
mPrivilegedPlugins = privilegedPlugins;
|
||||
|
||||
mInstanceFactory = new InstanceFactory<>();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
<T extends Plugin> Factory setInstanceFactory(InstanceFactory<T> instanceFactory) {
|
||||
mInstanceFactory = instanceFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
<T extends Plugin> PluginInstanceManager<T> create(
|
||||
String action, PluginListener<T> listener, boolean allowMultiple,
|
||||
VersionInfo version, boolean debuggable) {
|
||||
return new PluginInstanceManager<T>(mContext, mPackageManager, action, listener,
|
||||
allowMultiple, mMainExecutor, mBgExecutor, version, debuggable,
|
||||
mInitializer, mNotificationManager, mPluginEnabler,
|
||||
mPrivilegedPlugins, (InstanceFactory<T>) mInstanceFactory);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PluginContextWrapper extends ContextWrapper {
|
||||
private final ClassLoader mClassLoader;
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
public PluginContextWrapper(Context base, ClassLoader classLoader) {
|
||||
super(base);
|
||||
mClassLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getClassLoader() {
|
||||
return mClassLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSystemService(String name) {
|
||||
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
|
||||
if (mInflater == null) {
|
||||
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
|
||||
}
|
||||
return mInflater;
|
||||
}
|
||||
return getBaseContext().getSystemService(name);
|
||||
}
|
||||
}
|
||||
|
||||
static class PluginInfo<T extends Plugin> {
|
||||
private final Context mPluginContext;
|
||||
private final VersionInfo mVersion;
|
||||
private final String mClass;
|
||||
T mPlugin;
|
||||
String mPackage;
|
||||
|
||||
public PluginInfo(String pkg, String cls, T plugin, Context pluginContext,
|
||||
VersionInfo info) {
|
||||
mPlugin = plugin;
|
||||
mClass = cls;
|
||||
mPackage = pkg;
|
||||
mPluginContext = pluginContext;
|
||||
mVersion = info;
|
||||
}
|
||||
}
|
||||
|
||||
static class InstanceFactory<T extends Plugin> {
|
||||
T create(Class cls) throws IllegalAccessException, InstantiationException {
|
||||
return (T) cls.newInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,13 +30,15 @@ public interface PluginManager {
|
||||
/** Returns plugins that don't get disabled when an exceptoin occurs. */
|
||||
String[] getPrivilegedPlugins();
|
||||
|
||||
<T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls);
|
||||
<T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
|
||||
/** */
|
||||
<T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls);
|
||||
/** */
|
||||
<T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls,
|
||||
boolean allowMultiple);
|
||||
<T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
|
||||
Class<?> cls);
|
||||
Class<T> cls);
|
||||
<T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
|
||||
Class<?> cls, boolean allowMultiple);
|
||||
Class<T> cls, boolean allowMultiple);
|
||||
|
||||
void removePluginListener(PluginListener<?> listener);
|
||||
|
||||
|
||||
@@ -47,26 +47,26 @@ 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<?>, PluginActionManager<?>> mPluginMap
|
||||
= new ArrayMap<>();
|
||||
private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>();
|
||||
private final ArraySet<String> mPrivilegedPlugins = new ArraySet<>();
|
||||
private final Context mContext;
|
||||
private final PluginInstanceManager.Factory mInstanceManagerFactory;
|
||||
private final PluginActionManager.Factory mActionManagerFactory;
|
||||
private final boolean mIsDebuggable;
|
||||
private final PluginPrefs mPluginPrefs;
|
||||
private final PluginEnabler mPluginEnabler;
|
||||
private boolean mListening;
|
||||
|
||||
public PluginManagerImpl(Context context,
|
||||
PluginInstanceManager.Factory instanceManagerFactory,
|
||||
PluginActionManager.Factory actionManagerFactory,
|
||||
boolean debuggable,
|
||||
Optional<UncaughtExceptionHandler> defaultHandlerOptional,
|
||||
PluginEnabler pluginEnabler,
|
||||
PluginPrefs pluginPrefs,
|
||||
List<String> privilegedPlugins) {
|
||||
mContext = context;
|
||||
mInstanceManagerFactory = instanceManagerFactory;
|
||||
mActionManagerFactory = actionManagerFactory;
|
||||
mIsDebuggable = debuggable;
|
||||
mPrivilegedPlugins.addAll(privilegedPlugins);
|
||||
mPluginPrefs = pluginPrefs;
|
||||
@@ -85,25 +85,27 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
|
||||
return mPrivilegedPlugins.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) {
|
||||
/** */
|
||||
public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls) {
|
||||
addPluginListener(listener, cls, false);
|
||||
}
|
||||
|
||||
public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
|
||||
/** */
|
||||
public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls,
|
||||
boolean allowMultiple) {
|
||||
addPluginListener(PluginManager.Helper.getAction(cls), listener, cls, allowMultiple);
|
||||
}
|
||||
|
||||
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
|
||||
Class<?> cls) {
|
||||
Class<T> cls) {
|
||||
addPluginListener(action, listener, cls, false);
|
||||
}
|
||||
|
||||
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
|
||||
Class<?> cls, boolean allowMultiple) {
|
||||
Class<T> cls, boolean allowMultiple) {
|
||||
mPluginPrefs.addAction(action);
|
||||
PluginInstanceManager<T> p = mInstanceManagerFactory.create(action, listener, allowMultiple,
|
||||
new VersionInfo().addClass(cls), isDebuggable());
|
||||
PluginActionManager<T> p = mActionManagerFactory.create(action, listener, cls,
|
||||
allowMultiple, isDebuggable());
|
||||
p.loadAll();
|
||||
synchronized (this) {
|
||||
mPluginMap.put(listener, p);
|
||||
@@ -135,7 +137,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
|
||||
filter.addAction(PLUGIN_CHANGED);
|
||||
filter.addAction(DISABLE_PLUGIN);
|
||||
filter.addDataScheme("package");
|
||||
mContext.registerReceiver(this, filter, PluginInstanceManager.PLUGIN_PERMISSION, null);
|
||||
mContext.registerReceiver(this, filter, PluginActionManager.PLUGIN_PERMISSION, null);
|
||||
filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
|
||||
mContext.registerReceiver(this, filter);
|
||||
}
|
||||
@@ -150,7 +152,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 (PluginActionManager<?> manager : mPluginMap.values()) {
|
||||
manager.loadAll();
|
||||
}
|
||||
}
|
||||
@@ -189,12 +191,14 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
|
||||
}
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
|
||||
for (PluginInstanceManager<?> manager : mPluginMap.values()) {
|
||||
manager.onPackageChange(pkg);
|
||||
if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
|
||||
|| Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())
|
||||
|| Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
|
||||
for (PluginActionManager<?> actionManager : mPluginMap.values()) {
|
||||
actionManager.reloadPackage(pkg);
|
||||
}
|
||||
} else {
|
||||
for (PluginInstanceManager<?> manager : mPluginMap.values()) {
|
||||
for (PluginActionManager<?> manager : mPluginMap.values()) {
|
||||
manager.onPackageRemoved(pkg);
|
||||
}
|
||||
}
|
||||
@@ -284,7 +288,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 (PluginActionManager<?> manager : mPluginMap.values()) {
|
||||
disabledAny |= manager.disableAll();
|
||||
}
|
||||
}
|
||||
@@ -304,7 +308,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
|
||||
boolean disabledAny = false;
|
||||
synchronized (this) {
|
||||
for (StackTraceElement element : throwable.getStackTrace()) {
|
||||
for (PluginInstanceManager<?> manager : mPluginMap.values()) {
|
||||
for (PluginActionManager<?> manager : mPluginMap.values()) {
|
||||
disabledAny |= manager.checkAndDisable(element.getClassName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,8 @@ public class VersionInfo {
|
||||
|
||||
public static class InvalidVersionException extends RuntimeException {
|
||||
private final boolean mTooNew;
|
||||
private int mExpected;
|
||||
private int mActual;
|
||||
|
||||
public InvalidVersionException(String str, boolean tooNew) {
|
||||
super(str);
|
||||
@@ -128,11 +130,21 @@ public class VersionInfo {
|
||||
public InvalidVersionException(Class<?> cls, boolean tooNew, int expected, int actual) {
|
||||
super(cls.getSimpleName() + " expected version " + expected + " but had " + actual);
|
||||
mTooNew = tooNew;
|
||||
mExpected = expected;
|
||||
mActual = actual;
|
||||
}
|
||||
|
||||
public boolean isTooNew() {
|
||||
return mTooNew;
|
||||
}
|
||||
|
||||
public int getExpectedVersion() {
|
||||
return mExpected;
|
||||
}
|
||||
|
||||
public int getActualVersion() {
|
||||
return mActual;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Version {
|
||||
|
||||
@@ -53,7 +53,7 @@ public class PluginInflateContainer extends AutoReinflateContainer
|
||||
|
||||
private static final String TAG = "PluginInflateContainer";
|
||||
|
||||
private Class<?> mClass;
|
||||
private Class<ViewProvider> mClass;
|
||||
private View mPluginView;
|
||||
|
||||
public PluginInflateContainer(Context context, @Nullable AttributeSet attrs) {
|
||||
@@ -61,7 +61,7 @@ public class PluginInflateContainer extends AutoReinflateContainer
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PluginInflateContainer);
|
||||
String viewType = a.getString(R.styleable.PluginInflateContainer_viewType);
|
||||
try {
|
||||
mClass = Class.forName(viewType);
|
||||
mClass = (Class<ViewProvider>) Class.forName(viewType);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Problem getting class info " + viewType, e);
|
||||
mClass = null;
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.DarkIconDispatcher;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.plugins.GlobalActions;
|
||||
import com.android.systemui.plugins.PluginDependencyProvider;
|
||||
import com.android.systemui.plugins.VolumeDialogController;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
|
||||
@@ -32,6 +33,7 @@ import com.android.systemui.volume.VolumeDialogControllerImpl;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
/**
|
||||
* Module for binding Plugin implementations.
|
||||
@@ -39,36 +41,40 @@ import dagger.Module;
|
||||
* TODO(b/166258224): Many of these should be moved closer to their implementations.
|
||||
*/
|
||||
@Module
|
||||
public interface PluginModule {
|
||||
public abstract class PluginModule {
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
static ActivityStarter provideActivityStarter(ActivityStarterDelegate delegate,
|
||||
PluginDependencyProvider dependencyProvider) {
|
||||
dependencyProvider.allowPluginDependency(ActivityStarter.class, delegate);
|
||||
return delegate;
|
||||
}
|
||||
|
||||
/** */
|
||||
@Binds
|
||||
ActivityStarter provideActivityStarter(ActivityStarterDelegate delegate);
|
||||
abstract DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherImpl controllerImpl);
|
||||
|
||||
/** */
|
||||
@Binds
|
||||
DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherImpl controllerImpl);
|
||||
abstract FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl);
|
||||
|
||||
/** */
|
||||
@Binds
|
||||
FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl);
|
||||
abstract GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl);
|
||||
|
||||
/** */
|
||||
@Binds
|
||||
GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl);
|
||||
|
||||
/** */
|
||||
@Binds
|
||||
GlobalActions.GlobalActionsManager provideGlobalActionsManager(
|
||||
abstract GlobalActions.GlobalActionsManager provideGlobalActionsManager(
|
||||
GlobalActionsComponent controllerImpl);
|
||||
|
||||
/** */
|
||||
@Binds
|
||||
StatusBarStateController provideStatusBarStateController(
|
||||
abstract StatusBarStateController provideStatusBarStateController(
|
||||
StatusBarStateControllerImpl controllerImpl);
|
||||
|
||||
/** */
|
||||
@Binds
|
||||
VolumeDialogController provideVolumeDialogController(VolumeDialogControllerImpl controllerImpl);
|
||||
|
||||
abstract VolumeDialogController provideVolumeDialogController(
|
||||
VolumeDialogControllerImpl controllerImpl);
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.plugins;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.systemui.R;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* True if WTFs should lead to crashes
|
||||
*/
|
||||
private static final boolean WTFS_SHOULD_CRASH = false;
|
||||
private boolean mWtfsSet;
|
||||
|
||||
@Inject
|
||||
public PluginInitializerImpl(PluginDependencyProvider dependencyProvider) {
|
||||
dependencyProvider.allowPluginDependency(ActivityStarter.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getPrivilegedPlugins(Context context) {
|
||||
return context.getResources().getStringArray(R.array.config_pluginWhitelist);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleWtfs() {
|
||||
if (WTFS_SHOULD_CRASH && !mWtfsSet) {
|
||||
mWtfsSet = true;
|
||||
Log.setWtfHandler((tag, what, system) -> {
|
||||
throw new PluginManagerImpl.CrashWhilePluginActiveException(what);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,12 @@ import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.dagger.PluginModule;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.shared.plugins.PluginActionManager;
|
||||
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.PluginInstance;
|
||||
import com.android.systemui.shared.plugins.PluginManager;
|
||||
import com.android.systemui.shared.plugins.PluginManagerImpl;
|
||||
import com.android.systemui.shared.plugins.PluginPrefs;
|
||||
@@ -66,19 +68,30 @@ public abstract class PluginsModule {
|
||||
@Binds
|
||||
abstract PluginEnabler bindsPluginEnablerImpl(PluginEnablerImpl impl);
|
||||
|
||||
@Binds
|
||||
abstract PluginInitializer bindsPluginInitializerImpl(PluginInitializerImpl impl);
|
||||
@Provides
|
||||
@Singleton
|
||||
static PluginInstance.Factory providesPluginInstanceFactory(
|
||||
@Named(PLUGIN_PRIVILEGED) List<String> privilegedPlugins,
|
||||
@Named(PLUGIN_DEBUG) boolean isDebug) {
|
||||
return new PluginInstance.Factory(
|
||||
PluginModule.class.getClassLoader(),
|
||||
new PluginInstance.InstanceFactory<>(),
|
||||
new PluginInstance.VersionChecker(),
|
||||
privilegedPlugins,
|
||||
isDebug);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static PluginInstanceManager.Factory providePluginInstanceManagerFactory(Context context,
|
||||
static PluginActionManager.Factory providePluginInstanceManagerFactory(Context context,
|
||||
PackageManager packageManager, @Main Executor mainExecutor,
|
||||
@Named(PLUGIN_THREAD) Executor pluginExecutor, PluginInitializer initializer,
|
||||
@Named(PLUGIN_THREAD) Executor pluginExecutor,
|
||||
NotificationManager notificationManager, PluginEnabler pluginEnabler,
|
||||
@Named(PLUGIN_PRIVILEGED) List<String> privilegedPlugins) {
|
||||
return new PluginInstanceManager.Factory(
|
||||
context, packageManager, mainExecutor, pluginExecutor, initializer,
|
||||
notificationManager, pluginEnabler, privilegedPlugins);
|
||||
@Named(PLUGIN_PRIVILEGED) List<String> privilegedPlugins,
|
||||
PluginInstance.Factory pluginInstanceFactory) {
|
||||
return new PluginActionManager.Factory(
|
||||
context, packageManager, mainExecutor, pluginExecutor,
|
||||
notificationManager, pluginEnabler, privilegedPlugins, pluginInstanceFactory);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -91,7 +104,7 @@ public abstract class PluginsModule {
|
||||
@Provides
|
||||
static PluginManager providesPluginManager(
|
||||
Context context,
|
||||
PluginInstanceManager.Factory instanceManagerFactory,
|
||||
PluginActionManager.Factory instanceManagerFactory,
|
||||
@Named(PLUGIN_DEBUG) boolean debug,
|
||||
@Named(PRE_HANDLER)
|
||||
Optional<Thread.UncaughtExceptionHandler> uncaughtExceptionHandlerOptional,
|
||||
@@ -110,7 +123,7 @@ public abstract class PluginsModule {
|
||||
|
||||
@Provides
|
||||
@Named(PLUGIN_PRIVILEGED)
|
||||
static List<String> providesPrivilegedPlugins(PluginInitializer initializer, Context context) {
|
||||
return Arrays.asList(initializer.getPrivilegedPlugins(context));
|
||||
static List<String> providesPrivilegedPlugins(Context context) {
|
||||
return Arrays.asList(context.getResources().getStringArray(R.array.config_pluginWhitelist));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ import com.android.internal.util.ArrayUtils;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.plugins.PluginEnablerImpl;
|
||||
import com.android.systemui.shared.plugins.PluginActionManager;
|
||||
import com.android.systemui.shared.plugins.PluginEnabler;
|
||||
import com.android.systemui.shared.plugins.PluginInstanceManager;
|
||||
import com.android.systemui.shared.plugins.PluginManager;
|
||||
import com.android.systemui.shared.plugins.PluginPrefs;
|
||||
|
||||
@@ -102,7 +102,7 @@ public class PluginFragment extends PreferenceFragment {
|
||||
}
|
||||
|
||||
List<PackageInfo> apps = pm.getPackagesHoldingPermissions(new String[]{
|
||||
PluginInstanceManager.PLUGIN_PERMISSION},
|
||||
PluginActionManager.PLUGIN_PERMISSION},
|
||||
PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES);
|
||||
apps.forEach(app -> {
|
||||
if (!plugins.containsKey(app.packageName)) return;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.shared.plugins;
|
||||
@@ -19,8 +21,6 @@ import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -41,13 +41,11 @@ import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.SysuiTestableContext;
|
||||
import com.android.systemui.plugins.Plugin;
|
||||
import com.android.systemui.plugins.PluginListener;
|
||||
import com.android.systemui.plugins.annotations.Requires;
|
||||
import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
|
||||
import com.android.systemui.util.concurrency.FakeExecutor;
|
||||
import com.android.systemui.util.time.FakeSystemClock;
|
||||
|
||||
@@ -64,30 +62,34 @@ import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PluginInstanceManagerTest extends SysuiTestCase {
|
||||
public class PluginActionManagerTest extends SysuiTestCase {
|
||||
|
||||
private static final String PRIVILEGED_PACKAGE = "com.android.systemui.shared.plugins";
|
||||
private TestPlugin mMockPlugin;
|
||||
|
||||
private PackageManager mMockPm;
|
||||
private PluginListener<Plugin> mMockListener;
|
||||
private PluginInstanceManager<Plugin> mPluginInstanceManager;
|
||||
private PluginListener<TestPlugin> mMockListener;
|
||||
private PluginActionManager<TestPlugin> mPluginActionManager;
|
||||
private VersionInfo mMockVersionInfo;
|
||||
private PluginEnabler mMockEnabler;
|
||||
ComponentName mTestPluginComponentName =
|
||||
new ComponentName(PRIVILEGED_PACKAGE, TestPlugin.class.getName());
|
||||
private PluginInitializer mInitializer;
|
||||
private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
NotificationManager mNotificationManager;
|
||||
private PluginInstanceManager.Factory mInstanceManagerFactory;
|
||||
private final PluginInstanceManager.InstanceFactory<Plugin> mPluginInstanceFactory =
|
||||
new PluginInstanceManager.InstanceFactory<Plugin>() {
|
||||
private PluginInstance<TestPlugin> mPluginInstance;
|
||||
private PluginInstance.Factory mPluginInstanceFactory = new PluginInstance.Factory(
|
||||
this.getClass().getClassLoader(),
|
||||
new PluginInstance.InstanceFactory<>(), new PluginInstance.VersionChecker(),
|
||||
Collections.emptyList(), false) {
|
||||
@Override
|
||||
Plugin create(Class cls) {
|
||||
return mMockPlugin;
|
||||
public <T extends Plugin> PluginInstance<T> create(Context context, ApplicationInfo appInfo,
|
||||
ComponentName componentName, Class<T> pluginClass) {
|
||||
return (PluginInstance<T>) mPluginInstance;
|
||||
}
|
||||
};
|
||||
|
||||
private PluginActionManager.Factory mActionManagerFactory;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
mContext = new MyContextWrapper(mContext);
|
||||
@@ -95,16 +97,17 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
|
||||
mMockListener = mock(PluginListener.class);
|
||||
mMockEnabler = mock(PluginEnabler.class);
|
||||
mMockVersionInfo = mock(VersionInfo.class);
|
||||
mInitializer = mock(PluginInitializer.class);
|
||||
mNotificationManager = mock(NotificationManager.class);
|
||||
mMockPlugin = mock(TestPlugin.class);
|
||||
mInstanceManagerFactory = new PluginInstanceManager.Factory(getContext(), mMockPm,
|
||||
mFakeExecutor, mFakeExecutor, mInitializer, mNotificationManager, mMockEnabler,
|
||||
new ArrayList<>())
|
||||
.setInstanceFactory(mPluginInstanceFactory);
|
||||
mPluginInstance = mock(PluginInstance.class);
|
||||
when(mPluginInstance.getComponentName()).thenReturn(mTestPluginComponentName);
|
||||
when(mPluginInstance.getPackage()).thenReturn(mTestPluginComponentName.getPackageName());
|
||||
mActionManagerFactory = new PluginActionManager.Factory(getContext(), mMockPm,
|
||||
mFakeExecutor, mFakeExecutor, mNotificationManager, mMockEnabler,
|
||||
new ArrayList<>(), mPluginInstanceFactory);
|
||||
|
||||
mPluginInstanceManager = mInstanceManagerFactory.create("myAction", mMockListener,
|
||||
true, mMockVersionInfo, true);
|
||||
mPluginActionManager = mActionManagerFactory.create("myAction", mMockListener,
|
||||
TestPlugin.class, true, true);
|
||||
when(mMockPlugin.getVersion()).thenReturn(1);
|
||||
}
|
||||
|
||||
@@ -112,7 +115,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
|
||||
public void testNoPlugins() {
|
||||
when(mMockPm.queryIntentServices(any(), anyInt())).thenReturn(
|
||||
Collections.emptyList());
|
||||
mPluginInstanceManager.loadAll();
|
||||
mPluginActionManager.loadAll();
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
@@ -121,68 +124,47 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void testPluginCreate() throws Exception {
|
||||
//Debug.waitForDebugger();
|
||||
createPlugin();
|
||||
|
||||
// Verify startup lifecycle
|
||||
verify(mMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(),
|
||||
ArgumentCaptor.forClass(Context.class).capture());
|
||||
verify(mMockListener).onPluginConnected(any(), any());
|
||||
verify(mPluginInstance).onCreate(mContext, mMockListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPluginDestroy() throws Exception {
|
||||
createPlugin(); // Get into valid created state.
|
||||
|
||||
mPluginInstanceManager.destroy();
|
||||
mPluginActionManager.destroy();
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
|
||||
// Verify shutdown lifecycle
|
||||
verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture());
|
||||
verify(mMockPlugin).onDestroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncorrectVersion() throws Exception {
|
||||
setupFakePmQuery();
|
||||
doThrow(new InvalidVersionException("", false)).when(mMockVersionInfo).checkVersion(any());
|
||||
|
||||
mPluginInstanceManager.loadAll();
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
// Plugin shouldn't be connected because it is the wrong version.
|
||||
verify(mMockListener, never()).onPluginConnected(any(), any());
|
||||
verify(mNotificationManager).notify(eq(SystemMessage.NOTE_PLUGIN), any());
|
||||
verify(mPluginInstance).onDestroy(mMockListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReloadOnChange() throws Exception {
|
||||
createPlugin(); // Get into valid created state.
|
||||
|
||||
mPluginInstanceManager.onPackageChange(PRIVILEGED_PACKAGE);
|
||||
mPluginActionManager.reloadPackage(PRIVILEGED_PACKAGE);
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
// Verify the old one was destroyed.
|
||||
verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture());
|
||||
verify(mMockPlugin).onDestroy();
|
||||
// Also verify we got a second onCreate.
|
||||
verify(mMockPlugin, Mockito.times(2)).onCreate(
|
||||
ArgumentCaptor.forClass(Context.class).capture(),
|
||||
ArgumentCaptor.forClass(Context.class).capture());
|
||||
verify(mMockListener, Mockito.times(2)).onPluginConnected(any(), any());
|
||||
verify(mPluginInstance).onDestroy(mMockListener);
|
||||
verify(mPluginInstance, Mockito.times(2))
|
||||
.onCreate(mContext, mMockListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonDebuggable() throws Exception {
|
||||
// Create a version that thinks the build is not debuggable.
|
||||
mPluginInstanceManager = mInstanceManagerFactory.create("myAction", mMockListener,
|
||||
true, mMockVersionInfo, false);
|
||||
mPluginActionManager = mActionManagerFactory.create("myAction", mMockListener,
|
||||
TestPlugin.class, true, false);
|
||||
setupFakePmQuery();
|
||||
|
||||
mPluginInstanceManager.loadAll();
|
||||
mPluginActionManager.loadAll();
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
@@ -193,22 +175,20 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
|
||||
@Test
|
||||
public void testNonDebuggable_privileged() throws Exception {
|
||||
// Create a version that thinks the build is not debuggable.
|
||||
PluginInstanceManager.Factory factory = new PluginInstanceManager.Factory(getContext(),
|
||||
mMockPm, mFakeExecutor, mFakeExecutor, mInitializer, mNotificationManager,
|
||||
mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE));
|
||||
factory.setInstanceFactory(mPluginInstanceFactory);
|
||||
mPluginInstanceManager = factory.create("myAction", mMockListener,
|
||||
true, mMockVersionInfo, false);
|
||||
PluginActionManager.Factory factory = new PluginActionManager.Factory(getContext(),
|
||||
mMockPm, mFakeExecutor, mFakeExecutor, mNotificationManager,
|
||||
mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE),
|
||||
mPluginInstanceFactory);
|
||||
mPluginActionManager = factory.create("myAction", mMockListener,
|
||||
TestPlugin.class, true, false);
|
||||
setupFakePmQuery();
|
||||
|
||||
mPluginInstanceManager.loadAll();
|
||||
mPluginActionManager.loadAll();
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
// Verify startup lifecycle
|
||||
verify(mMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(),
|
||||
ArgumentCaptor.forClass(Context.class).capture());
|
||||
verify(mMockListener).onPluginConnected(any(), any());
|
||||
verify(mPluginInstance).onCreate(mContext, mMockListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -216,12 +196,12 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
|
||||
createPlugin(); // Get into valid created state.
|
||||
|
||||
// Start with an unrelated class.
|
||||
boolean result = mPluginInstanceManager.checkAndDisable(Activity.class.getName());
|
||||
boolean result = mPluginActionManager.checkAndDisable(Activity.class.getName());
|
||||
assertFalse(result);
|
||||
verify(mMockEnabler, never()).setDisabled(any(ComponentName.class), anyInt());
|
||||
|
||||
// Now hand it a real class and make sure it disables the plugin.
|
||||
result = mPluginInstanceManager.checkAndDisable(TestPlugin.class.getName());
|
||||
result = mPluginActionManager.checkAndDisable(TestPlugin.class.getName());
|
||||
assertTrue(result);
|
||||
verify(mMockEnabler).setDisabled(
|
||||
mTestPluginComponentName, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
|
||||
@@ -231,24 +211,24 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
|
||||
public void testDisableAll() throws Exception {
|
||||
createPlugin(); // Get into valid created state.
|
||||
|
||||
mPluginInstanceManager.disableAll();
|
||||
mPluginActionManager.disableAll();
|
||||
|
||||
verify(mMockEnabler).setDisabled(
|
||||
mTestPluginComponentName, PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisableWhitelisted() throws Exception {
|
||||
PluginInstanceManager.Factory factory = new PluginInstanceManager.Factory(getContext(),
|
||||
mMockPm, mFakeExecutor, mFakeExecutor, mInitializer, mNotificationManager,
|
||||
mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE));
|
||||
factory.setInstanceFactory(mPluginInstanceFactory);
|
||||
mPluginInstanceManager = factory.create("myAction", mMockListener,
|
||||
true, mMockVersionInfo, false);
|
||||
public void testDisablePrivileged() throws Exception {
|
||||
PluginActionManager.Factory factory = new PluginActionManager.Factory(getContext(),
|
||||
mMockPm, mFakeExecutor, mFakeExecutor, mNotificationManager,
|
||||
mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE),
|
||||
mPluginInstanceFactory);
|
||||
mPluginActionManager = factory.create("myAction", mMockListener,
|
||||
TestPlugin.class, true, false);
|
||||
|
||||
createPlugin(); // Get into valid created state.
|
||||
|
||||
mPluginInstanceManager.disableAll();
|
||||
mPluginActionManager.disableAll();
|
||||
|
||||
verify(mMockPm, never()).setComponentEnabledSetting(
|
||||
ArgumentCaptor.forClass(ComponentName.class).capture(),
|
||||
@@ -282,14 +262,14 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
|
||||
private void createPlugin() throws Exception {
|
||||
setupFakePmQuery();
|
||||
|
||||
mPluginInstanceManager.loadAll();
|
||||
mPluginActionManager.loadAll();
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
}
|
||||
|
||||
// Real context with no registering/unregistering of receivers.
|
||||
private static class MyContextWrapper extends SysuiTestableContext {
|
||||
public MyContextWrapper(Context base) {
|
||||
MyContextWrapper(Context base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.shared.plugins;
|
||||
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.plugins.Plugin;
|
||||
import com.android.systemui.plugins.PluginListener;
|
||||
import com.android.systemui.plugins.annotations.ProvidesInterface;
|
||||
import com.android.systemui.plugins.annotations.Requires;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PluginInstanceTest extends SysuiTestCase {
|
||||
|
||||
private static final String PRIVILEGED_PACKAGE = "com.android.systemui.plugins";
|
||||
|
||||
@Mock
|
||||
private TestPluginImpl mMockPlugin;
|
||||
@Mock
|
||||
private PluginListener<TestPlugin> mMockListener;
|
||||
@Mock
|
||||
private VersionInfo mVersionInfo;
|
||||
ComponentName mTestPluginComponentName =
|
||||
new ComponentName(PRIVILEGED_PACKAGE, TestPluginImpl.class.getName());
|
||||
private PluginInstance<TestPlugin> mPluginInstance;
|
||||
private PluginInstance.Factory mPluginInstanceFactory;
|
||||
|
||||
private ApplicationInfo mAppInfo;
|
||||
private Context mPluginContext;
|
||||
@Mock
|
||||
private PluginInstance.VersionChecker mVersionChecker;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mAppInfo = mContext.getApplicationInfo();
|
||||
mAppInfo.packageName = mTestPluginComponentName.getPackageName();
|
||||
when(mVersionChecker.checkVersion(any(), any(), any())).thenReturn(mVersionInfo);
|
||||
|
||||
mPluginInstanceFactory = new PluginInstance.Factory(
|
||||
this.getClass().getClassLoader(),
|
||||
new PluginInstance.InstanceFactory<TestPlugin>() {
|
||||
@Override
|
||||
TestPlugin create(Class cls) {
|
||||
return mMockPlugin;
|
||||
}
|
||||
},
|
||||
mVersionChecker,
|
||||
Collections.singletonList(PRIVILEGED_PACKAGE),
|
||||
false);
|
||||
|
||||
mPluginInstance = mPluginInstanceFactory.create(
|
||||
mContext, mAppInfo, mTestPluginComponentName, TestPlugin.class);
|
||||
mPluginContext = mPluginInstance.getPluginContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectVersion() {
|
||||
assertNotNull(mPluginInstance);
|
||||
}
|
||||
|
||||
@Test(expected = VersionInfo.InvalidVersionException.class)
|
||||
public void testIncorrectVersion() throws Exception {
|
||||
|
||||
ComponentName wrongVersionTestPluginComponentName =
|
||||
new ComponentName(PRIVILEGED_PACKAGE, TestPlugin.class.getName());
|
||||
|
||||
when(mVersionChecker.checkVersion(any(), any(), any())).thenThrow(
|
||||
new VersionInfo.InvalidVersionException("test", true));
|
||||
|
||||
mPluginInstanceFactory.create(
|
||||
mContext, mAppInfo, wrongVersionTestPluginComponentName, TestPlugin.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnCreate() {
|
||||
mPluginInstance.onCreate(mContext, mMockListener);
|
||||
verify(mMockPlugin).onCreate(mContext, mPluginContext);
|
||||
verify(mMockListener).onPluginConnected(mMockPlugin, mPluginContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnDestroy() {
|
||||
mPluginInstance.onDestroy(mMockListener);
|
||||
verify(mMockListener).onPluginDisconnected(mMockPlugin);
|
||||
verify(mMockPlugin).onDestroy();
|
||||
}
|
||||
|
||||
// This target class doesn't matter, it just needs to have a Requires to hit the flow where
|
||||
// the mock version info is called.
|
||||
@ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
|
||||
public interface TestPlugin extends Plugin {
|
||||
int VERSION = 1;
|
||||
String ACTION = "testAction";
|
||||
}
|
||||
|
||||
@Requires(target = TestPlugin.class, version = TestPlugin.VERSION)
|
||||
public static class TestPluginImpl implements TestPlugin {
|
||||
@Override
|
||||
public void onCreate(Context sysuiContext, Context pluginContext) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
*/
|
||||
package com.android.systemui.shared.plugins;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -54,10 +55,10 @@ public class PluginManagerTest extends SysuiTestCase {
|
||||
|
||||
private static final String PRIVILEGED_PACKAGE = "com.android.systemui";
|
||||
|
||||
private PluginInstanceManager.Factory mMockFactory;
|
||||
private PluginInstanceManager<Plugin> mMockPluginInstance;
|
||||
private PluginActionManager.Factory mMockFactory;
|
||||
private PluginActionManager<TestPlugin> mMockPluginInstance;
|
||||
private PluginManagerImpl mPluginManager;
|
||||
private PluginListener<?> mMockListener;
|
||||
private PluginListener<TestPlugin> mMockListener;
|
||||
private PackageManager mMockPackageManager;
|
||||
private PluginEnabler mPluginEnabler;
|
||||
private PluginPrefs mPluginPrefs;
|
||||
@@ -70,11 +71,11 @@ public class PluginManagerTest extends SysuiTestCase {
|
||||
public void setup() throws Exception {
|
||||
mRealExceptionHandler = Thread.getUncaughtExceptionPreHandler();
|
||||
mMockExceptionHandler = mock(UncaughtExceptionHandler.class);
|
||||
mMockFactory = mock(PluginInstanceManager.Factory.class);
|
||||
mMockPluginInstance = mock(PluginInstanceManager.class);
|
||||
mMockFactory = mock(PluginActionManager.Factory.class);
|
||||
mMockPluginInstance = mock(PluginActionManager.class);
|
||||
mPluginEnabler = mock(PluginEnabler.class);
|
||||
mPluginPrefs = mock(PluginPrefs.class);
|
||||
when(mMockFactory.create(any(), any(), Mockito.anyBoolean(), any(), Mockito.anyBoolean()))
|
||||
when(mMockFactory.create(any(), any(), eq(TestPlugin.class), anyBoolean(), anyBoolean()))
|
||||
.thenReturn(mMockPluginInstance);
|
||||
|
||||
mMockPackageManager = mock(PackageManager.class);
|
||||
@@ -116,8 +117,8 @@ public class PluginManagerTest extends SysuiTestCase {
|
||||
applicationInfo.sourceDir = sourceDir;
|
||||
applicationInfo.packageName = PRIVILEGED_PACKAGE;
|
||||
mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
|
||||
verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(false),
|
||||
any(VersionInfo.class), eq(false));
|
||||
verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(TestPlugin.class),
|
||||
eq(false), eq(false));
|
||||
verify(mMockPluginInstance).loadAll();
|
||||
}
|
||||
|
||||
@@ -138,8 +139,8 @@ public class PluginManagerTest extends SysuiTestCase {
|
||||
invalidApplicationInfo.sourceDir = sourceDir;
|
||||
invalidApplicationInfo.packageName = "com.android.invalidpackage";
|
||||
mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
|
||||
verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(false),
|
||||
any(VersionInfo.class), eq(false));
|
||||
verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(TestPlugin.class),
|
||||
eq(false), eq(false));
|
||||
verify(mMockPluginInstance).loadAll();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,24 +30,24 @@ public class FakePluginManager implements PluginManager {
|
||||
|
||||
@Override
|
||||
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
|
||||
Class<?> cls, boolean allowMultiple) {
|
||||
Class<T> cls, boolean allowMultiple) {
|
||||
mLeakChecker.addCallback(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) {
|
||||
public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls) {
|
||||
mLeakChecker.addCallback(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
|
||||
public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls,
|
||||
boolean allowMultiple) {
|
||||
mLeakChecker.addCallback(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
|
||||
Class<?> cls) {
|
||||
Class<T> cls) {
|
||||
mLeakChecker.addCallback(listener);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user