1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.shared.plugins; 18 19 import android.app.LoadedApk; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.text.TextUtils; 25 import android.util.ArrayMap; 26 import android.util.Log; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.systemui.plugins.Plugin; 30 import com.android.systemui.plugins.PluginFragment; 31 import com.android.systemui.plugins.PluginListener; 32 33 import dalvik.system.PathClassLoader; 34 35 import java.io.File; 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.Map; 39 40 /** 41 * Contains a single instantiation of a Plugin. 42 * 43 * This class and its related Factory are in charge of actually instantiating a plugin and 44 * managing any state related to it. 45 * 46 * @param <T> The type of plugin that this contains. 47 */ 48 public class PluginInstance<T extends Plugin> { 49 private static final String TAG = "PluginInstance"; 50 private static final Map<String, ClassLoader> sClassLoaders = new ArrayMap<>(); 51 52 private final Context mPluginContext; 53 private final VersionInfo mVersionInfo; 54 private final ComponentName mComponentName; 55 private final T mPlugin; 56 57 /** */ PluginInstance(ComponentName componentName, T plugin, Context pluginContext, VersionInfo versionInfo)58 public PluginInstance(ComponentName componentName, T plugin, Context pluginContext, 59 VersionInfo versionInfo) { 60 mComponentName = componentName; 61 mPlugin = plugin; 62 mPluginContext = pluginContext; 63 mVersionInfo = versionInfo; 64 } 65 66 /** Alerts listener and plugin that the plugin has been created. */ onCreate(Context appContext, PluginListener<T> listener)67 public void onCreate(Context appContext, PluginListener<T> listener) { 68 if (!(mPlugin instanceof PluginFragment)) { 69 // Only call onCreate for plugins that aren't fragments, as fragments 70 // will get the onCreate as part of the fragment lifecycle. 71 mPlugin.onCreate(appContext, mPluginContext); 72 } 73 listener.onPluginConnected(mPlugin, mPluginContext); 74 } 75 76 /** Alerts listener and plugin that the plugin is being shutdown. */ onDestroy(PluginListener<T> listener)77 public void onDestroy(PluginListener<T> listener) { 78 listener.onPluginDisconnected(mPlugin); 79 if (!(mPlugin instanceof PluginFragment)) { 80 // Only call onDestroy for plugins that aren't fragments, as fragments 81 // will get the onDestroy as part of the fragment lifecycle. 82 mPlugin.onDestroy(); 83 } 84 } 85 86 /** 87 * Returns if the contained plugin matches the passed in class name. 88 * 89 * It does this by string comparison of the class names. 90 **/ containsPluginClass(Class pluginClass)91 public boolean containsPluginClass(Class pluginClass) { 92 return mPlugin.getClass().getName().equals(pluginClass.getName()); 93 } 94 getComponentName()95 public ComponentName getComponentName() { 96 return mComponentName; 97 } 98 getPackage()99 public String getPackage() { 100 return mComponentName.getPackageName(); 101 } 102 getVersionInfo()103 public VersionInfo getVersionInfo() { 104 return mVersionInfo; 105 } 106 107 @VisibleForTesting getPluginContext()108 Context getPluginContext() { 109 return mPluginContext; 110 } 111 112 /** Used to create new {@link PluginInstance}s. */ 113 public static class Factory { 114 private final ClassLoader mBaseClassLoader; 115 private final InstanceFactory<?> mInstanceFactory; 116 private final VersionChecker mVersionChecker; 117 private final boolean mIsDebug; 118 private final List<String> mPrivilegedPlugins; 119 120 /** Factory used to construct {@link PluginInstance}s. */ Factory(ClassLoader classLoader, InstanceFactory<?> instanceFactory, VersionChecker versionChecker, List<String> privilegedPlugins, boolean isDebug)121 public Factory(ClassLoader classLoader, InstanceFactory<?> instanceFactory, 122 VersionChecker versionChecker, 123 List<String> privilegedPlugins, 124 boolean isDebug) { 125 mPrivilegedPlugins = privilegedPlugins; 126 mBaseClassLoader = classLoader; 127 mInstanceFactory = instanceFactory; 128 mVersionChecker = versionChecker; 129 mIsDebug = isDebug; 130 } 131 132 /** Construct a new PluginInstance. */ create( Context context, ApplicationInfo appInfo, ComponentName componentName, Class<T> pluginClass)133 public <T extends Plugin> PluginInstance<T> create( 134 Context context, 135 ApplicationInfo appInfo, 136 ComponentName componentName, 137 Class<T> pluginClass) 138 throws PackageManager.NameNotFoundException, ClassNotFoundException, 139 InstantiationException, IllegalAccessException { 140 141 ClassLoader classLoader = getClassLoader(appInfo, mBaseClassLoader); 142 Context pluginContext = new PluginActionManager.PluginContextWrapper( 143 context.createApplicationContext(appInfo, 0), classLoader); 144 Class<T> instanceClass = (Class<T>) Class.forName( 145 componentName.getClassName(), true, classLoader); 146 // TODO: Only create the plugin before version check if we need it for 147 // legacy version check. 148 T instance = (T) mInstanceFactory.create(instanceClass); 149 VersionInfo version = mVersionChecker.checkVersion( 150 instanceClass, pluginClass, instance); 151 return new PluginInstance<T>(componentName, instance, pluginContext, version); 152 } 153 isPluginPackagePrivileged(String packageName)154 private boolean isPluginPackagePrivileged(String packageName) { 155 for (String componentNameOrPackage : mPrivilegedPlugins) { 156 ComponentName componentName = ComponentName.unflattenFromString( 157 componentNameOrPackage); 158 if (componentName != null) { 159 if (componentName.getPackageName().equals(packageName)) { 160 return true; 161 } 162 } else if (componentNameOrPackage.equals(packageName)) { 163 return true; 164 } 165 } 166 return false; 167 } 168 getParentClassLoader(ClassLoader baseClassLoader)169 private ClassLoader getParentClassLoader(ClassLoader baseClassLoader) { 170 return new PluginManagerImpl.ClassLoaderFilter( 171 baseClassLoader, "com.android.systemui.plugin"); 172 } 173 174 /** Returns class loader specific for the given plugin. */ getClassLoader(ApplicationInfo appInfo, ClassLoader baseClassLoader)175 private ClassLoader getClassLoader(ApplicationInfo appInfo, 176 ClassLoader baseClassLoader) { 177 if (!mIsDebug && !isPluginPackagePrivileged(appInfo.packageName)) { 178 Log.w(TAG, "Cannot get class loader for non-privileged plugin. Src:" 179 + appInfo.sourceDir + ", pkg: " + appInfo.packageName); 180 return null; 181 } 182 if (sClassLoaders.containsKey(appInfo.packageName)) { 183 return sClassLoaders.get(appInfo.packageName); 184 } 185 186 List<String> zipPaths = new ArrayList<>(); 187 List<String> libPaths = new ArrayList<>(); 188 LoadedApk.makePaths(null, true, appInfo, zipPaths, libPaths); 189 ClassLoader classLoader = new PathClassLoader( 190 TextUtils.join(File.pathSeparator, zipPaths), 191 TextUtils.join(File.pathSeparator, libPaths), 192 getParentClassLoader(baseClassLoader)); 193 sClassLoaders.put(appInfo.packageName, classLoader); 194 return classLoader; 195 } 196 } 197 198 /** Class that compares a plugin class against an implementation for version matching. */ 199 public static class VersionChecker { 200 /** Compares two plugin classes. */ checkVersion( Class<T> instanceClass, Class<T> pluginClass, Plugin plugin)201 public <T extends Plugin> VersionInfo checkVersion( 202 Class<T> instanceClass, Class<T> pluginClass, Plugin plugin) { 203 VersionInfo pluginVersion = new VersionInfo().addClass(pluginClass); 204 VersionInfo instanceVersion = new VersionInfo().addClass(instanceClass); 205 if (instanceVersion.hasVersionInfo()) { 206 pluginVersion.checkVersion(instanceVersion); 207 } else { 208 int fallbackVersion = plugin.getVersion(); 209 if (fallbackVersion != pluginVersion.getDefaultVersion()) { 210 throw new VersionInfo.InvalidVersionException("Invalid legacy version", false); 211 } 212 return null; 213 } 214 return instanceVersion; 215 } 216 } 217 218 /** 219 * Simple class to create a new instance. Useful for testing. 220 * 221 * @param <T> The type of plugin this create. 222 **/ 223 public static class InstanceFactory<T extends Plugin> { create(Class cls)224 T create(Class cls) throws IllegalAccessException, InstantiationException { 225 return (T) cls.newInstance(); 226 } 227 } 228 } 229