Loading packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java +16 −41 Original line number Diff line number Diff line Loading @@ -14,12 +14,10 @@ package com.android.systemui.plugins; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; Loading @@ -33,12 +31,10 @@ import android.view.LayoutInflater; import com.android.internal.annotations.VisibleForTesting; import dalvik.system.PathClassLoader; import java.util.ArrayList; import java.util.List; public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver { public class PluginInstanceManager<T extends Plugin> { private static final boolean DEBUG = false; Loading @@ -57,20 +53,21 @@ public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver { final PluginHandler mPluginHandler; private final boolean isDebuggable; private final PackageManager mPm; private final ClassLoaderFactory mClassLoaderFactory; private final PluginManager mManager; PluginInstanceManager(Context context, String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, int version) { boolean allowMultiple, Looper looper, int version, PluginManager manager) { this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version, Build.IS_DEBUGGABLE, new ClassLoaderFactory()); manager, Build.IS_DEBUGGABLE); } @VisibleForTesting PluginInstanceManager(Context context, PackageManager pm, String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, int version, boolean debuggable, ClassLoaderFactory classLoaderFactory) { PluginManager manager, boolean debuggable) { mMainHandler = new MainHandler(Looper.getMainLooper()); mPluginHandler = new PluginHandler(looper); mManager = manager; mContext = context; mPm = pm; mAction = action; Loading @@ -78,44 +75,29 @@ public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver { mAllowMultiple = allowMultiple; mVersion = version; isDebuggable = debuggable; mClassLoaderFactory = classLoaderFactory; } public void startListening() { public void loadAll() { if (DEBUG) Log.d(TAG, "startListening"); mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL); IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); mContext.registerReceiver(this, filter); filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiver(this, filter); } public void stopListening() { public void destroy() { if (DEBUG) Log.d(TAG, "stopListening"); ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins); for (PluginInfo plugin : plugins) { mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED, plugin.mPlugin).sendToTarget(); } mContext.unregisterReceiver(this); } @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "onReceive " + intent); if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL); } else { Uri data = intent.getData(); String pkgName = data.getEncodedSchemeSpecificPart(); mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkgName).sendToTarget(); if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { mPluginHandler.obtainMessage(PluginHandler.QUERY_PKG, pkgName).sendToTarget(); } public void onPackageRemoved(String pkg) { mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkg).sendToTarget(); } public void onPackageChange(String pkg) { mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkg).sendToTarget(); mPluginHandler.obtainMessage(PluginHandler.QUERY_PKG, pkg).sendToTarget(); } public boolean checkAndDisable(String className) { Loading Loading @@ -179,12 +161,6 @@ public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver { } } static class ClassLoaderFactory { public ClassLoader createClassLoader(String path, ClassLoader base) { return new PathClassLoader(path, base); } } private class PluginHandler extends Handler { private static final int QUERY_ALL = 1; private static final int QUERY_PKG = 2; Loading Loading @@ -279,8 +255,7 @@ public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver { return null; } // Create our own ClassLoader so we can use our own code as the parent. ClassLoader classLoader = mClassLoaderFactory.createClassLoader(info.sourceDir, getClass().getClassLoader()); ClassLoader classLoader = mManager.getClassLoader(info.sourceDir, info.packageName); Context pluginContext = new PluginContextWrapper( mContext.createApplicationContext(info, 0), classLoader); Class<?> pluginClass = Class.forName(cls, true, classLoader); Loading packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java +101 −8 Original line number Diff line number Diff line Loading @@ -14,7 +14,11 @@ package com.android.systemui.plugins; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Build; import android.os.HandlerThread; import android.os.Looper; Loading @@ -22,26 +26,31 @@ import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; import dalvik.system.PathClassLoader; import java.lang.Thread.UncaughtExceptionHandler; import java.util.Map; /** * @see Plugin */ public class PluginManager { public class PluginManager extends BroadcastReceiver { private static PluginManager sInstance; private final HandlerThread mBackgroundThread; private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap = new ArrayMap<>(); private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>(); private final Context mContext; private final PluginInstanceManagerFactory mFactory; private final boolean isDebuggable; private final PluginPrefs mPluginPrefs; private ClassLoaderFilter mParentClassLoader; private PluginManager(Context context) { this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler()); this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler()); } @VisibleForTesting Loading Loading @@ -72,9 +81,12 @@ public class PluginManager { } mPluginPrefs.addAction(action); PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener, allowMultiple, mBackgroundThread.getLooper(), version); p.startListening(); allowMultiple, mBackgroundThread.getLooper(), version, this); p.loadAll(); mPluginMap.put(listener, p); if (mPluginMap.size() == 1) { startListening(); } } public void removePluginListener(PluginListener<?> listener) { Loading @@ -83,7 +95,68 @@ public class PluginManager { return; } if (!mPluginMap.containsKey(listener)) return; mPluginMap.remove(listener).stopListening(); mPluginMap.remove(listener).destroy(); if (mPluginMap.size() == 0) { stopListening(); } } private void startListening() { IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); mContext.registerReceiver(this, filter); filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiver(this, filter); } private void stopListening() { mContext.unregisterReceiver(this); } @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { for (PluginInstanceManager manager : mPluginMap.values()) { manager.loadAll(); } } else { Uri data = intent.getData(); String pkg = data.getEncodedSchemeSpecificPart(); clearClassLoader(pkg); if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { for (PluginInstanceManager manager : mPluginMap.values()) { manager.onPackageChange(pkg); } } else { for (PluginInstanceManager manager : mPluginMap.values()) { manager.onPackageRemoved(pkg); } } } } public ClassLoader getClassLoader(String sourceDir, String pkg) { if (mClassLoaders.containsKey(pkg)) { return mClassLoaders.get(pkg); } ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader()); mClassLoaders.put(pkg, classLoader); return classLoader; } private void clearClassLoader(String pkg) { mClassLoaders.remove(pkg); } ClassLoader getParentClassLoader() { if (mParentClassLoader == null) { // Lazily load this so it doesn't have any effect on devices without plugins. mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(), "com.android.systemui.plugin"); } return mParentClassLoader; } public static PluginManager getInstance(Context context) { Loading @@ -97,9 +170,29 @@ public class PluginManager { public static class PluginInstanceManagerFactory { public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context, String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, int version) { int version, PluginManager manager) { return new PluginInstanceManager(context, action, listener, allowMultiple, looper, version); version, manager); } } // This allows plugins to include any libraries or copied code they want by only including // classes from the plugin library. private static class ClassLoaderFilter extends ClassLoader { private final String mPackage; private final ClassLoader mBase; public ClassLoaderFilter(ClassLoader base, String pkg) { super(ClassLoader.getSystemClassLoader()); mBase = base; mPackage = pkg; } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (!name.startsWith(mPackage)) super.loadClass(name, resolve); return mBase.loadClass(name); } } Loading packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java +12 −22 Original line number Diff line number Diff line Loading @@ -33,13 +33,11 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.HandlerThread; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.PluginInstanceManager.ClassLoaderFactory; import org.junit.After; import org.junit.Before; Loading @@ -64,6 +62,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { private PackageManager mMockPm; private PluginListener mMockListener; private PluginInstanceManager mPluginInstanceManager; private PluginManager mMockManager; @Before public void setup() throws Exception { Loading @@ -72,9 +71,11 @@ public class PluginInstanceManagerTest extends SysuiTestCase { mContextWrapper = new MyContextWrapper(getContext()); mMockPm = mock(PackageManager.class); mMockListener = mock(PluginListener.class); mMockManager = mock(PluginManager.class); when(mMockManager.getClassLoader(Mockito.any(), Mockito.any())) .thenReturn(getClass().getClassLoader()); mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", mMockListener, true, mHandlerThread.getLooper(), 1, true, new TestClassLoaderFactory()); mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, true); sMockPlugin = mock(Plugin.class); when(sMockPlugin.getVersion()).thenReturn(1); } Loading @@ -89,7 +90,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { public void testNoPlugins() { when(mMockPm.queryIntentServices(Mockito.any(), Mockito.anyInt())).thenReturn( Collections.emptyList()); mPluginInstanceManager.startListening(); mPluginInstanceManager.loadAll(); waitForIdleSync(mPluginInstanceManager.mPluginHandler); waitForIdleSync(mPluginInstanceManager.mMainHandler); Loading @@ -112,7 +113,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { public void testPluginDestroy() { createPlugin(); // Get into valid created state. mPluginInstanceManager.stopListening(); mPluginInstanceManager.destroy(); waitForIdleSync(mPluginInstanceManager.mPluginHandler); waitForIdleSync(mPluginInstanceManager.mMainHandler); Loading @@ -127,7 +128,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { setupFakePmQuery(); when(sMockPlugin.getVersion()).thenReturn(2); mPluginInstanceManager.startListening(); mPluginInstanceManager.loadAll(); waitForIdleSync(mPluginInstanceManager.mPluginHandler); waitForIdleSync(mPluginInstanceManager.mMainHandler); Loading @@ -141,10 +142,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { public void testReloadOnChange() { createPlugin(); // Get into valid created state. // Send a package changed broadcast. Intent i = new Intent(Intent.ACTION_PACKAGE_CHANGED, Uri.fromParts("package", "com.android.systemui", null)); mPluginInstanceManager.onReceive(mContextWrapper, i); mPluginInstanceManager.onPackageChange("com.android.systemui"); waitForIdleSync(mPluginInstanceManager.mPluginHandler); waitForIdleSync(mPluginInstanceManager.mMainHandler); Loading @@ -164,11 +162,10 @@ public class PluginInstanceManagerTest extends SysuiTestCase { public void testNonDebuggable() { // Create a version that thinks the build is not debuggable. mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", mMockListener, true, mHandlerThread.getLooper(), 1, false, new TestClassLoaderFactory()); mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, false); setupFakePmQuery(); mPluginInstanceManager.startListening(); mPluginInstanceManager.loadAll(); waitForIdleSync(mPluginInstanceManager.mPluginHandler); waitForIdleSync(mPluginInstanceManager.mMainHandler);; Loading Loading @@ -236,19 +233,12 @@ public class PluginInstanceManagerTest extends SysuiTestCase { private void createPlugin() { setupFakePmQuery(); mPluginInstanceManager.startListening(); mPluginInstanceManager.loadAll(); waitForIdleSync(mPluginInstanceManager.mPluginHandler); waitForIdleSync(mPluginInstanceManager.mMainHandler); } private static class TestClassLoaderFactory extends ClassLoaderFactory { @Override public ClassLoader createClassLoader(String path, ClassLoader base) { return base; } } // Real context with no registering/unregistering of receivers. private static class MyContextWrapper extends ContextWrapper { public MyContextWrapper(Context base) { Loading packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java +4 −4 Original line number Diff line number Diff line Loading @@ -51,7 +51,7 @@ public class PluginManagerTest extends SysuiTestCase { mMockFactory = mock(PluginInstanceManagerFactory.class); mMockPluginInstance = mock(PluginInstanceManager.class); when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.anyInt())) Mockito.anyBoolean(), Mockito.any(), Mockito.anyInt(), Mockito.any())) .thenReturn(mMockPluginInstance); mPluginManager = new PluginManager(getContext(), mMockFactory, true, mMockExceptionHandler); resetExceptionHandler(); Loading @@ -62,7 +62,7 @@ public class PluginManagerTest extends SysuiTestCase { public void testAddListener() { mPluginManager.addPluginListener("myAction", mMockListener, 1); verify(mMockPluginInstance).startListening(); verify(mMockPluginInstance).loadAll(); } @Test Loading @@ -70,7 +70,7 @@ public class PluginManagerTest extends SysuiTestCase { mPluginManager.addPluginListener("myAction", mMockListener, 1); mPluginManager.removePluginListener(mMockListener); verify(mMockPluginInstance).stopListening(); verify(mMockPluginInstance).destroy(); } @Test Loading @@ -80,7 +80,7 @@ public class PluginManagerTest extends SysuiTestCase { resetExceptionHandler(); mPluginManager.addPluginListener("myAction", mMockListener, 1); verify(mMockPluginInstance, Mockito.never()).startListening(); verify(mMockPluginInstance, Mockito.never()).loadAll(); } @Test Loading Loading
packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java +16 −41 Original line number Diff line number Diff line Loading @@ -14,12 +14,10 @@ package com.android.systemui.plugins; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; Loading @@ -33,12 +31,10 @@ import android.view.LayoutInflater; import com.android.internal.annotations.VisibleForTesting; import dalvik.system.PathClassLoader; import java.util.ArrayList; import java.util.List; public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver { public class PluginInstanceManager<T extends Plugin> { private static final boolean DEBUG = false; Loading @@ -57,20 +53,21 @@ public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver { final PluginHandler mPluginHandler; private final boolean isDebuggable; private final PackageManager mPm; private final ClassLoaderFactory mClassLoaderFactory; private final PluginManager mManager; PluginInstanceManager(Context context, String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, int version) { boolean allowMultiple, Looper looper, int version, PluginManager manager) { this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version, Build.IS_DEBUGGABLE, new ClassLoaderFactory()); manager, Build.IS_DEBUGGABLE); } @VisibleForTesting PluginInstanceManager(Context context, PackageManager pm, String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, int version, boolean debuggable, ClassLoaderFactory classLoaderFactory) { PluginManager manager, boolean debuggable) { mMainHandler = new MainHandler(Looper.getMainLooper()); mPluginHandler = new PluginHandler(looper); mManager = manager; mContext = context; mPm = pm; mAction = action; Loading @@ -78,44 +75,29 @@ public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver { mAllowMultiple = allowMultiple; mVersion = version; isDebuggable = debuggable; mClassLoaderFactory = classLoaderFactory; } public void startListening() { public void loadAll() { if (DEBUG) Log.d(TAG, "startListening"); mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL); IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); mContext.registerReceiver(this, filter); filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiver(this, filter); } public void stopListening() { public void destroy() { if (DEBUG) Log.d(TAG, "stopListening"); ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins); for (PluginInfo plugin : plugins) { mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED, plugin.mPlugin).sendToTarget(); } mContext.unregisterReceiver(this); } @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "onReceive " + intent); if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL); } else { Uri data = intent.getData(); String pkgName = data.getEncodedSchemeSpecificPart(); mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkgName).sendToTarget(); if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { mPluginHandler.obtainMessage(PluginHandler.QUERY_PKG, pkgName).sendToTarget(); } public void onPackageRemoved(String pkg) { mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkg).sendToTarget(); } public void onPackageChange(String pkg) { mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkg).sendToTarget(); mPluginHandler.obtainMessage(PluginHandler.QUERY_PKG, pkg).sendToTarget(); } public boolean checkAndDisable(String className) { Loading Loading @@ -179,12 +161,6 @@ public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver { } } static class ClassLoaderFactory { public ClassLoader createClassLoader(String path, ClassLoader base) { return new PathClassLoader(path, base); } } private class PluginHandler extends Handler { private static final int QUERY_ALL = 1; private static final int QUERY_PKG = 2; Loading Loading @@ -279,8 +255,7 @@ public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver { return null; } // Create our own ClassLoader so we can use our own code as the parent. ClassLoader classLoader = mClassLoaderFactory.createClassLoader(info.sourceDir, getClass().getClassLoader()); ClassLoader classLoader = mManager.getClassLoader(info.sourceDir, info.packageName); Context pluginContext = new PluginContextWrapper( mContext.createApplicationContext(info, 0), classLoader); Class<?> pluginClass = Class.forName(cls, true, classLoader); Loading
packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java +101 −8 Original line number Diff line number Diff line Loading @@ -14,7 +14,11 @@ package com.android.systemui.plugins; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Build; import android.os.HandlerThread; import android.os.Looper; Loading @@ -22,26 +26,31 @@ import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; import dalvik.system.PathClassLoader; import java.lang.Thread.UncaughtExceptionHandler; import java.util.Map; /** * @see Plugin */ public class PluginManager { public class PluginManager extends BroadcastReceiver { private static PluginManager sInstance; private final HandlerThread mBackgroundThread; private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap = new ArrayMap<>(); private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>(); private final Context mContext; private final PluginInstanceManagerFactory mFactory; private final boolean isDebuggable; private final PluginPrefs mPluginPrefs; private ClassLoaderFilter mParentClassLoader; private PluginManager(Context context) { this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler()); this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler()); } @VisibleForTesting Loading Loading @@ -72,9 +81,12 @@ public class PluginManager { } mPluginPrefs.addAction(action); PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener, allowMultiple, mBackgroundThread.getLooper(), version); p.startListening(); allowMultiple, mBackgroundThread.getLooper(), version, this); p.loadAll(); mPluginMap.put(listener, p); if (mPluginMap.size() == 1) { startListening(); } } public void removePluginListener(PluginListener<?> listener) { Loading @@ -83,7 +95,68 @@ public class PluginManager { return; } if (!mPluginMap.containsKey(listener)) return; mPluginMap.remove(listener).stopListening(); mPluginMap.remove(listener).destroy(); if (mPluginMap.size() == 0) { stopListening(); } } private void startListening() { IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); mContext.registerReceiver(this, filter); filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiver(this, filter); } private void stopListening() { mContext.unregisterReceiver(this); } @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { for (PluginInstanceManager manager : mPluginMap.values()) { manager.loadAll(); } } else { Uri data = intent.getData(); String pkg = data.getEncodedSchemeSpecificPart(); clearClassLoader(pkg); if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { for (PluginInstanceManager manager : mPluginMap.values()) { manager.onPackageChange(pkg); } } else { for (PluginInstanceManager manager : mPluginMap.values()) { manager.onPackageRemoved(pkg); } } } } public ClassLoader getClassLoader(String sourceDir, String pkg) { if (mClassLoaders.containsKey(pkg)) { return mClassLoaders.get(pkg); } ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader()); mClassLoaders.put(pkg, classLoader); return classLoader; } private void clearClassLoader(String pkg) { mClassLoaders.remove(pkg); } ClassLoader getParentClassLoader() { if (mParentClassLoader == null) { // Lazily load this so it doesn't have any effect on devices without plugins. mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(), "com.android.systemui.plugin"); } return mParentClassLoader; } public static PluginManager getInstance(Context context) { Loading @@ -97,9 +170,29 @@ public class PluginManager { public static class PluginInstanceManagerFactory { public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context, String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, int version) { int version, PluginManager manager) { return new PluginInstanceManager(context, action, listener, allowMultiple, looper, version); version, manager); } } // This allows plugins to include any libraries or copied code they want by only including // classes from the plugin library. private static class ClassLoaderFilter extends ClassLoader { private final String mPackage; private final ClassLoader mBase; public ClassLoaderFilter(ClassLoader base, String pkg) { super(ClassLoader.getSystemClassLoader()); mBase = base; mPackage = pkg; } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (!name.startsWith(mPackage)) super.loadClass(name, resolve); return mBase.loadClass(name); } } Loading
packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java +12 −22 Original line number Diff line number Diff line Loading @@ -33,13 +33,11 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.HandlerThread; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.PluginInstanceManager.ClassLoaderFactory; import org.junit.After; import org.junit.Before; Loading @@ -64,6 +62,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { private PackageManager mMockPm; private PluginListener mMockListener; private PluginInstanceManager mPluginInstanceManager; private PluginManager mMockManager; @Before public void setup() throws Exception { Loading @@ -72,9 +71,11 @@ public class PluginInstanceManagerTest extends SysuiTestCase { mContextWrapper = new MyContextWrapper(getContext()); mMockPm = mock(PackageManager.class); mMockListener = mock(PluginListener.class); mMockManager = mock(PluginManager.class); when(mMockManager.getClassLoader(Mockito.any(), Mockito.any())) .thenReturn(getClass().getClassLoader()); mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", mMockListener, true, mHandlerThread.getLooper(), 1, true, new TestClassLoaderFactory()); mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, true); sMockPlugin = mock(Plugin.class); when(sMockPlugin.getVersion()).thenReturn(1); } Loading @@ -89,7 +90,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { public void testNoPlugins() { when(mMockPm.queryIntentServices(Mockito.any(), Mockito.anyInt())).thenReturn( Collections.emptyList()); mPluginInstanceManager.startListening(); mPluginInstanceManager.loadAll(); waitForIdleSync(mPluginInstanceManager.mPluginHandler); waitForIdleSync(mPluginInstanceManager.mMainHandler); Loading @@ -112,7 +113,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { public void testPluginDestroy() { createPlugin(); // Get into valid created state. mPluginInstanceManager.stopListening(); mPluginInstanceManager.destroy(); waitForIdleSync(mPluginInstanceManager.mPluginHandler); waitForIdleSync(mPluginInstanceManager.mMainHandler); Loading @@ -127,7 +128,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { setupFakePmQuery(); when(sMockPlugin.getVersion()).thenReturn(2); mPluginInstanceManager.startListening(); mPluginInstanceManager.loadAll(); waitForIdleSync(mPluginInstanceManager.mPluginHandler); waitForIdleSync(mPluginInstanceManager.mMainHandler); Loading @@ -141,10 +142,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { public void testReloadOnChange() { createPlugin(); // Get into valid created state. // Send a package changed broadcast. Intent i = new Intent(Intent.ACTION_PACKAGE_CHANGED, Uri.fromParts("package", "com.android.systemui", null)); mPluginInstanceManager.onReceive(mContextWrapper, i); mPluginInstanceManager.onPackageChange("com.android.systemui"); waitForIdleSync(mPluginInstanceManager.mPluginHandler); waitForIdleSync(mPluginInstanceManager.mMainHandler); Loading @@ -164,11 +162,10 @@ public class PluginInstanceManagerTest extends SysuiTestCase { public void testNonDebuggable() { // Create a version that thinks the build is not debuggable. mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", mMockListener, true, mHandlerThread.getLooper(), 1, false, new TestClassLoaderFactory()); mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, false); setupFakePmQuery(); mPluginInstanceManager.startListening(); mPluginInstanceManager.loadAll(); waitForIdleSync(mPluginInstanceManager.mPluginHandler); waitForIdleSync(mPluginInstanceManager.mMainHandler);; Loading Loading @@ -236,19 +233,12 @@ public class PluginInstanceManagerTest extends SysuiTestCase { private void createPlugin() { setupFakePmQuery(); mPluginInstanceManager.startListening(); mPluginInstanceManager.loadAll(); waitForIdleSync(mPluginInstanceManager.mPluginHandler); waitForIdleSync(mPluginInstanceManager.mMainHandler); } private static class TestClassLoaderFactory extends ClassLoaderFactory { @Override public ClassLoader createClassLoader(String path, ClassLoader base) { return base; } } // Real context with no registering/unregistering of receivers. private static class MyContextWrapper extends ContextWrapper { public MyContextWrapper(Context base) { Loading
packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java +4 −4 Original line number Diff line number Diff line Loading @@ -51,7 +51,7 @@ public class PluginManagerTest extends SysuiTestCase { mMockFactory = mock(PluginInstanceManagerFactory.class); mMockPluginInstance = mock(PluginInstanceManager.class); when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.anyInt())) Mockito.anyBoolean(), Mockito.any(), Mockito.anyInt(), Mockito.any())) .thenReturn(mMockPluginInstance); mPluginManager = new PluginManager(getContext(), mMockFactory, true, mMockExceptionHandler); resetExceptionHandler(); Loading @@ -62,7 +62,7 @@ public class PluginManagerTest extends SysuiTestCase { public void testAddListener() { mPluginManager.addPluginListener("myAction", mMockListener, 1); verify(mMockPluginInstance).startListening(); verify(mMockPluginInstance).loadAll(); } @Test Loading @@ -70,7 +70,7 @@ public class PluginManagerTest extends SysuiTestCase { mPluginManager.addPluginListener("myAction", mMockListener, 1); mPluginManager.removePluginListener(mMockListener); verify(mMockPluginInstance).stopListening(); verify(mMockPluginInstance).destroy(); } @Test Loading @@ -80,7 +80,7 @@ public class PluginManagerTest extends SysuiTestCase { resetExceptionHandler(); mPluginManager.addPluginListener("myAction", mMockListener, 1); verify(mMockPluginInstance, Mockito.never()).startListening(); verify(mMockPluginInstance, Mockito.never()).loadAll(); } @Test Loading