1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.shared.plugins; 16 17 import android.app.NotificationManager; 18 import android.content.BroadcastReceiver; 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.net.Uri; 24 import android.os.Build; 25 import android.os.SystemProperties; 26 import android.util.ArrayMap; 27 import android.util.ArraySet; 28 import android.util.Log; 29 import android.widget.Toast; 30 31 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; 32 import com.android.systemui.plugins.Plugin; 33 import com.android.systemui.plugins.PluginListener; 34 import com.android.systemui.plugins.PluginManager; 35 import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager; 36 37 import java.io.FileDescriptor; 38 import java.io.PrintWriter; 39 import java.lang.Thread.UncaughtExceptionHandler; 40 import java.util.List; 41 import java.util.Map; 42 43 /** 44 * @see Plugin 45 */ 46 public class PluginManagerImpl extends BroadcastReceiver implements PluginManager { 47 48 private static final String TAG = PluginManagerImpl.class.getSimpleName(); 49 static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN"; 50 51 private final ArrayMap<PluginListener<?>, PluginActionManager<?>> mPluginMap 52 = new ArrayMap<>(); 53 private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>(); 54 private final ArraySet<String> mPrivilegedPlugins = new ArraySet<>(); 55 private final Context mContext; 56 private final PluginActionManager.Factory mActionManagerFactory; 57 private final boolean mIsDebuggable; 58 private final PluginPrefs mPluginPrefs; 59 private final PluginEnabler mPluginEnabler; 60 private boolean mListening; 61 PluginManagerImpl(Context context, PluginActionManager.Factory actionManagerFactory, boolean debuggable, UncaughtExceptionPreHandlerManager preHandlerManager, PluginEnabler pluginEnabler, PluginPrefs pluginPrefs, List<String> privilegedPlugins)62 public PluginManagerImpl(Context context, 63 PluginActionManager.Factory actionManagerFactory, 64 boolean debuggable, 65 UncaughtExceptionPreHandlerManager preHandlerManager, 66 PluginEnabler pluginEnabler, 67 PluginPrefs pluginPrefs, 68 List<String> privilegedPlugins) { 69 mContext = context; 70 mActionManagerFactory = actionManagerFactory; 71 mIsDebuggable = debuggable; 72 mPrivilegedPlugins.addAll(privilegedPlugins); 73 mPluginPrefs = pluginPrefs; 74 mPluginEnabler = pluginEnabler; 75 76 preHandlerManager.registerHandler(new PluginExceptionHandler()); 77 } 78 isDebuggable()79 public boolean isDebuggable() { 80 return mIsDebuggable; 81 } 82 getPrivilegedPlugins()83 public String[] getPrivilegedPlugins() { 84 return mPrivilegedPlugins.toArray(new String[0]); 85 } 86 87 /** */ addPluginListener(PluginListener<T> listener, Class<T> cls)88 public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls) { 89 addPluginListener(listener, cls, false); 90 } 91 92 /** */ addPluginListener(PluginListener<T> listener, Class<T> cls, boolean allowMultiple)93 public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls, 94 boolean allowMultiple) { 95 addPluginListener(PluginManager.Helper.getAction(cls), listener, cls, allowMultiple); 96 } 97 addPluginListener(String action, PluginListener<T> listener, Class<T> cls)98 public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, 99 Class<T> cls) { 100 addPluginListener(action, listener, cls, false); 101 } 102 addPluginListener(String action, PluginListener<T> listener, Class<T> cls, boolean allowMultiple)103 public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, 104 Class<T> cls, boolean allowMultiple) { 105 mPluginPrefs.addAction(action); 106 PluginActionManager<T> p = mActionManagerFactory.create(action, listener, cls, 107 allowMultiple, isDebuggable()); 108 p.loadAll(); 109 synchronized (this) { 110 mPluginMap.put(listener, p); 111 } 112 startListening(); 113 } 114 removePluginListener(PluginListener<?> listener)115 public void removePluginListener(PluginListener<?> listener) { 116 synchronized (this) { 117 if (!mPluginMap.containsKey(listener)) { 118 return; 119 } 120 mPluginMap.remove(listener).destroy(); 121 if (mPluginMap.size() == 0) { 122 stopListening(); 123 } 124 } 125 } 126 startListening()127 private void startListening() { 128 if (mListening) return; 129 mListening = true; 130 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 131 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 132 filter.addAction(Intent.ACTION_PACKAGE_REPLACED); 133 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 134 filter.addDataScheme("package"); 135 mContext.registerReceiver(this, filter); 136 filter.addAction(PLUGIN_CHANGED); 137 filter.addAction(DISABLE_PLUGIN); 138 filter.addDataScheme("package"); 139 mContext.registerReceiver(this, filter, PluginActionManager.PLUGIN_PERMISSION, null, 140 Context.RECEIVER_EXPORTED_UNAUDITED); 141 filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); 142 mContext.registerReceiver(this, filter); 143 } 144 stopListening()145 private void stopListening() { 146 if (!mListening) return; 147 mListening = false; 148 mContext.unregisterReceiver(this); 149 } 150 151 @Override onReceive(Context context, Intent intent)152 public void onReceive(Context context, Intent intent) { 153 if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { 154 synchronized (this) { 155 for (PluginActionManager<?> manager : mPluginMap.values()) { 156 manager.loadAll(); 157 } 158 } 159 } else if (DISABLE_PLUGIN.equals(intent.getAction())) { 160 Uri uri = intent.getData(); 161 ComponentName component = ComponentName.unflattenFromString( 162 uri.toString().substring(10)); 163 if (isPluginPrivileged(component)) { 164 // Don't disable privileged plugins as they are a part of the OS. 165 return; 166 } 167 mPluginEnabler.setDisabled(component, PluginEnabler.DISABLED_INVALID_VERSION); 168 mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(), 169 SystemMessage.NOTE_PLUGIN); 170 } else { 171 Uri data = intent.getData(); 172 String pkg = data.getEncodedSchemeSpecificPart(); 173 ComponentName componentName = ComponentName.unflattenFromString(pkg); 174 if (clearClassLoader(pkg)) { 175 if (Build.IS_ENG) { 176 Toast.makeText(mContext, "Reloading " + pkg, Toast.LENGTH_LONG).show(); 177 } else { 178 Log.v(TAG, "Reloading " + pkg); 179 } 180 } 181 if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction()) 182 && componentName != null) { 183 @PluginEnabler.DisableReason int disableReason = 184 mPluginEnabler.getDisableReason(componentName); 185 if (disableReason == PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH 186 || disableReason == PluginEnabler.DISABLED_FROM_SYSTEM_CRASH 187 || disableReason == PluginEnabler.DISABLED_INVALID_VERSION) { 188 Log.i(TAG, "Re-enabling previously disabled plugin that has been " 189 + "updated: " + componentName.flattenToShortString()); 190 mPluginEnabler.setEnabled(componentName); 191 } 192 } 193 synchronized (this) { 194 if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction()) 195 || Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) 196 || Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) { 197 for (PluginActionManager<?> actionManager : mPluginMap.values()) { 198 actionManager.reloadPackage(pkg); 199 } 200 } else { 201 for (PluginActionManager<?> manager : mPluginMap.values()) { 202 manager.onPackageRemoved(pkg); 203 } 204 } 205 } 206 } 207 } 208 clearClassLoader(String pkg)209 private boolean clearClassLoader(String pkg) { 210 return mClassLoaders.remove(pkg) != null; 211 } 212 dependsOn(Plugin p, Class<T> cls)213 public <T> boolean dependsOn(Plugin p, Class<T> cls) { 214 synchronized (this) { 215 for (int i = 0; i < mPluginMap.size(); i++) { 216 if (mPluginMap.valueAt(i).dependsOn(p, cls)) { 217 return true; 218 } 219 } 220 } 221 return false; 222 } 223 dump(FileDescriptor fd, PrintWriter pw, String[] args)224 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 225 synchronized (this) { 226 pw.println(String.format(" plugin map (%d):", mPluginMap.size())); 227 for (PluginListener<?> listener : mPluginMap.keySet()) { 228 pw.println(String.format(" %s -> %s", 229 listener, mPluginMap.get(listener))); 230 } 231 } 232 } 233 isPluginPrivileged(ComponentName pluginName)234 private boolean isPluginPrivileged(ComponentName pluginName) { 235 for (String componentNameOrPackage : mPrivilegedPlugins) { 236 ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage); 237 if (componentName != null) { 238 if (componentName.equals(pluginName)) { 239 return true; 240 } 241 } else if (componentNameOrPackage.equals(pluginName.getPackageName())) { 242 return true; 243 } 244 } 245 return false; 246 } 247 248 // This allows plugins to include any libraries or copied code they want by only including 249 // classes from the plugin library. 250 static class ClassLoaderFilter extends ClassLoader { 251 private final String[] mPackages; 252 private final ClassLoader mBase; 253 ClassLoaderFilter(ClassLoader base, String... pkgs)254 ClassLoaderFilter(ClassLoader base, String... pkgs) { 255 super(ClassLoader.getSystemClassLoader()); 256 mBase = base; 257 mPackages = pkgs; 258 } 259 260 @Override loadClass(String name, boolean resolve)261 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 262 for (String pkg : mPackages) { 263 if (name.startsWith(pkg)) { 264 return mBase.loadClass(name); 265 } 266 } 267 return super.loadClass(name, resolve); 268 } 269 } 270 271 private class PluginExceptionHandler implements UncaughtExceptionHandler { 272 PluginExceptionHandler()273 private PluginExceptionHandler() {} 274 275 @Override uncaughtException(Thread thread, Throwable throwable)276 public void uncaughtException(Thread thread, Throwable throwable) { 277 if (SystemProperties.getBoolean("plugin.debugging", false)) { 278 return; 279 } 280 // Search for and disable plugins that may have been involved in this crash. 281 boolean disabledAny = checkStack(throwable); 282 if (!disabledAny) { 283 // We couldn't find any plugins involved in this crash, just to be safe 284 // disable all the plugins, so we can be sure that SysUI is running as 285 // best as possible. 286 synchronized (this) { 287 for (PluginActionManager<?> manager : mPluginMap.values()) { 288 disabledAny |= manager.disableAll(); 289 } 290 } 291 } 292 if (disabledAny) { 293 throwable = new CrashWhilePluginActiveException(throwable); 294 } 295 } 296 checkStack(Throwable throwable)297 private boolean checkStack(Throwable throwable) { 298 if (throwable == null) return false; 299 boolean disabledAny = false; 300 synchronized (this) { 301 for (StackTraceElement element : throwable.getStackTrace()) { 302 for (PluginActionManager<?> manager : mPluginMap.values()) { 303 disabledAny |= manager.checkAndDisable(element.getClassName()); 304 } 305 } 306 } 307 return disabledAny | checkStack(throwable.getCause()); 308 } 309 } 310 311 public static class CrashWhilePluginActiveException extends RuntimeException { CrashWhilePluginActiveException(Throwable throwable)312 public CrashWhilePluginActiveException(Throwable throwable) { 313 super(throwable); 314 } 315 } 316 } 317