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