Merge "Allow whitelisted plugins on user builds"
This commit is contained in:
committed by
Android (Google) Code Review
commit
a48d662edd
@@ -508,4 +508,7 @@
|
||||
|
||||
<!-- Allow dragging the PIP to a location to close it -->
|
||||
<bool name="config_pipEnableDismissDragToEdge">true</bool>
|
||||
|
||||
<!-- SystemUI Plugins that can be loaded on user builds. -->
|
||||
<string-array name="config_pluginWhitelist" translatable="false" />
|
||||
</resources>
|
||||
|
||||
@@ -33,6 +33,7 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
@@ -41,7 +42,9 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.systemui.plugins.VersionInfo.InvalidVersionException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import com.android.systemui.R;
|
||||
|
||||
public class PluginInstanceManager<T extends Plugin> {
|
||||
|
||||
@@ -63,17 +66,19 @@ public class PluginInstanceManager<T extends Plugin> {
|
||||
private final boolean isDebuggable;
|
||||
private final PackageManager mPm;
|
||||
private final PluginManagerImpl mManager;
|
||||
private final ArraySet<String> mWhitelistedPlugins = new ArraySet<>();
|
||||
|
||||
PluginInstanceManager(Context context, String action, PluginListener<T> listener,
|
||||
boolean allowMultiple, Looper looper, VersionInfo version, PluginManagerImpl manager) {
|
||||
this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version,
|
||||
manager, Build.IS_DEBUGGABLE);
|
||||
manager, Build.IS_DEBUGGABLE,
|
||||
context.getResources().getStringArray(R.array.config_pluginWhitelist));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
PluginInstanceManager(Context context, PackageManager pm, String action,
|
||||
PluginListener<T> listener, boolean allowMultiple, Looper looper, VersionInfo version,
|
||||
PluginManagerImpl manager, boolean debuggable) {
|
||||
PluginManagerImpl manager, boolean debuggable, String[] pluginWhitelist) {
|
||||
mMainHandler = new MainHandler(Looper.getMainLooper());
|
||||
mPluginHandler = new PluginHandler(looper);
|
||||
mManager = manager;
|
||||
@@ -83,6 +88,7 @@ public class PluginInstanceManager<T extends Plugin> {
|
||||
mListener = listener;
|
||||
mAllowMultiple = allowMultiple;
|
||||
mVersion = version;
|
||||
mWhitelistedPlugins.addAll(Arrays.asList(pluginWhitelist));
|
||||
isDebuggable = debuggable;
|
||||
}
|
||||
|
||||
@@ -294,9 +300,9 @@ public class PluginInstanceManager<T extends Plugin> {
|
||||
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 (!isDebuggable) {
|
||||
if (!isDebuggable && !mWhitelistedPlugins.contains(component.getPackageName())) {
|
||||
// Never ever ever allow these on production builds, they are only for prototyping.
|
||||
Log.d(TAG, "Somehow hit second debuggable check");
|
||||
Log.w(TAG, "Plugin cannot be loaded on production build: " + component);
|
||||
return null;
|
||||
}
|
||||
String pkg = component.getPackageName();
|
||||
|
||||
@@ -37,13 +37,12 @@ import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.util.Log.TerribleFailure;
|
||||
import android.util.Log.TerribleFailureHandler;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper;
|
||||
import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
|
||||
import com.android.systemui.plugins.annotations.ProvidesInterface;
|
||||
@@ -53,13 +52,14 @@ import dalvik.system.PathClassLoader;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @see Plugin
|
||||
*/
|
||||
public class PluginManagerImpl extends BroadcastReceiver implements PluginManager {
|
||||
|
||||
private static final String TAG = PluginManagerImpl.class.getSimpleName();
|
||||
static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN";
|
||||
|
||||
private static PluginManager sInstance;
|
||||
@@ -68,6 +68,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
|
||||
= new ArrayMap<>();
|
||||
private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>();
|
||||
private final ArraySet<String> mOneShotPackages = new ArraySet<>();
|
||||
private final ArraySet<String> mWhitelistedPlugins = new ArraySet<>();
|
||||
private final Context mContext;
|
||||
private final PluginInstanceManagerFactory mFactory;
|
||||
private final boolean isDebuggable;
|
||||
@@ -79,30 +80,30 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
|
||||
private boolean mWtfsSet;
|
||||
|
||||
public PluginManagerImpl(Context context) {
|
||||
this(context, new PluginInstanceManagerFactory(),
|
||||
Build.IS_DEBUGGABLE, Thread.getUncaughtExceptionPreHandler());
|
||||
this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE,
|
||||
context.getResources().getStringArray(R.array.config_pluginWhitelist),
|
||||
Thread.getUncaughtExceptionPreHandler());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
PluginManagerImpl(Context context, PluginInstanceManagerFactory factory, boolean debuggable,
|
||||
UncaughtExceptionHandler defaultHandler) {
|
||||
String[] whitelistedPlugins, UncaughtExceptionHandler defaultHandler) {
|
||||
mContext = context;
|
||||
mFactory = factory;
|
||||
mLooper = Dependency.get(Dependency.BG_LOOPER);
|
||||
isDebuggable = debuggable;
|
||||
mWhitelistedPlugins.addAll(Arrays.asList(whitelistedPlugins));
|
||||
mPluginPrefs = new PluginPrefs(mContext);
|
||||
|
||||
PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
|
||||
defaultHandler);
|
||||
Thread.setUncaughtExceptionPreHandler(uncaughtExceptionHandler);
|
||||
if (isDebuggable) {
|
||||
new Handler(mLooper).post(() -> {
|
||||
// Plugin dependencies that don't have another good home can go here, but
|
||||
// dependencies that have better places to init can happen elsewhere.
|
||||
Dependency.get(PluginDependencyProvider.class)
|
||||
.allowPluginDependency(ActivityStarter.class);
|
||||
});
|
||||
}
|
||||
new Handler(mLooper).post(() -> {
|
||||
// Plugin dependencies that don't have another good home can go here, but
|
||||
// dependencies that have better places to init can happen elsewhere.
|
||||
Dependency.get(PluginDependencyProvider.class)
|
||||
.allowPluginDependency(ActivityStarter.class);
|
||||
});
|
||||
}
|
||||
|
||||
public <T extends Plugin> T getOneShotPlugin(Class<T> cls) {
|
||||
@@ -117,10 +118,6 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
|
||||
}
|
||||
|
||||
public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) {
|
||||
if (!isDebuggable) {
|
||||
// Never ever ever allow these on production builds, they are only for prototyping.
|
||||
return null;
|
||||
}
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
throw new RuntimeException("Must be called from UI thread");
|
||||
}
|
||||
@@ -153,10 +150,6 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
|
||||
|
||||
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
|
||||
Class cls, boolean allowMultiple) {
|
||||
if (!isDebuggable) {
|
||||
// Never ever ever allow these on production builds, they are only for prototyping.
|
||||
return;
|
||||
}
|
||||
mPluginPrefs.addAction(action);
|
||||
PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
|
||||
allowMultiple, mLooper, cls, this);
|
||||
@@ -166,10 +159,6 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
|
||||
}
|
||||
|
||||
public void removePluginListener(PluginListener<?> listener) {
|
||||
if (!isDebuggable) {
|
||||
// Never ever ever allow these on production builds, they are only for prototyping.
|
||||
return;
|
||||
}
|
||||
if (!mPluginMap.containsKey(listener)) return;
|
||||
mPluginMap.remove(listener).destroy();
|
||||
if (mPluginMap.size() == 0) {
|
||||
@@ -261,6 +250,11 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
|
||||
}
|
||||
|
||||
public ClassLoader getClassLoader(String sourceDir, String pkg) {
|
||||
if (!isDebuggable && !mWhitelistedPlugins.contains(pkg)) {
|
||||
Log.w(TAG, "Cannot get class loader for non-whitelisted plugin. Src:" + sourceDir +
|
||||
", pkg: " + pkg);
|
||||
return null;
|
||||
}
|
||||
if (mClassLoaders.containsKey(pkg)) {
|
||||
return mClassLoaders.get(pkg);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ import java.util.List;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PluginInstanceManagerTest extends SysuiTestCase {
|
||||
|
||||
private static final String WHITELISTED_PACKAGE = "com.android.systemui";
|
||||
// Static since the plugin needs to be generated by the PluginInstanceManager using newInstance.
|
||||
private static Plugin sMockPlugin;
|
||||
|
||||
@@ -88,7 +89,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
|
||||
mMockVersionInfo = mock(VersionInfo.class);
|
||||
mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
|
||||
mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
|
||||
mMockManager, true);
|
||||
mMockManager, true, new String[0]);
|
||||
sMockPlugin = mock(Plugin.class);
|
||||
when(sMockPlugin.getVersion()).thenReturn(1);
|
||||
}
|
||||
@@ -186,7 +187,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
|
||||
// Create a version that thinks the build is not debuggable.
|
||||
mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
|
||||
mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
|
||||
mMockManager, false);
|
||||
mMockManager, false, new String[0]);
|
||||
setupFakePmQuery();
|
||||
|
||||
mPluginInstanceManager.loadAll();
|
||||
@@ -198,6 +199,25 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
|
||||
verify(mMockListener, Mockito.never()).onPluginConnected(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonDebuggable_whitelist() throws Exception {
|
||||
// Create a version that thinks the build is not debuggable.
|
||||
mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
|
||||
mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
|
||||
mMockManager, false, new String[] {WHITELISTED_PACKAGE});
|
||||
setupFakePmQuery();
|
||||
|
||||
mPluginInstanceManager.loadAll();
|
||||
|
||||
waitForIdleSync(mPluginInstanceManager.mPluginHandler);
|
||||
waitForIdleSync(mPluginInstanceManager.mMainHandler);
|
||||
|
||||
// Verify startup lifecycle
|
||||
verify(sMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(),
|
||||
ArgumentCaptor.forClass(Context.class).capture());
|
||||
verify(mMockListener).onPluginConnected(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAndDisable() throws Exception {
|
||||
createPlugin(); // Get into valid created state.
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
*/
|
||||
package com.android.systemui.plugins;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -26,8 +27,6 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.support.test.annotation.UiThreadTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
@@ -36,11 +35,10 @@ import android.testing.TestableLooper.RunWithLooper;
|
||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.plugins.annotations.ProvidesInterface;
|
||||
import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
|
||||
import com.android.systemui.plugins.PluginManagerImpl.PluginInstanceManagerFactory;
|
||||
import com.android.systemui.plugins.annotations.ProvidesInterface;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -54,6 +52,8 @@ import java.lang.Thread.UncaughtExceptionHandler;
|
||||
@RunWithLooper
|
||||
public class PluginManagerTest extends SysuiTestCase {
|
||||
|
||||
private static final String WHITELISTED_PACKAGE = "com.android.systemui";
|
||||
|
||||
private PluginInstanceManagerFactory mMockFactory;
|
||||
private PluginInstanceManager mMockPluginInstance;
|
||||
private PluginManagerImpl mPluginManager;
|
||||
@@ -74,7 +74,7 @@ public class PluginManagerTest extends SysuiTestCase {
|
||||
when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(),
|
||||
Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any()))
|
||||
.thenReturn(mMockPluginInstance);
|
||||
mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, true,
|
||||
mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, true, new String[0],
|
||||
mMockExceptionHandler);
|
||||
resetExceptionHandler();
|
||||
mMockListener = mock(PluginListener.class);
|
||||
@@ -87,7 +87,7 @@ public class PluginManagerTest extends SysuiTestCase {
|
||||
when(mMockPluginInstance.getPlugin()).thenReturn(new PluginInfo(null, null, mockPlugin,
|
||||
null, null));
|
||||
Plugin result = mPluginManager.getOneShotPlugin("myAction", TestPlugin.class);
|
||||
assertTrue(result == mockPlugin);
|
||||
assertSame(mockPlugin, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -106,16 +106,27 @@ public class PluginManagerTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonDebuggable() {
|
||||
@RunWithLooper(setAsMainLooper = true)
|
||||
public void testNonDebuggable_noWhitelist() {
|
||||
mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, false,
|
||||
mMockExceptionHandler);
|
||||
new String[0], mMockExceptionHandler);
|
||||
resetExceptionHandler();
|
||||
|
||||
mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
|
||||
verify(mMockPluginInstance, Mockito.never()).loadAll();
|
||||
|
||||
assertNull(mPluginManager.getOneShotPlugin("myPlugin", TestPlugin.class));
|
||||
verify(mMockPluginInstance, Mockito.never()).getPlugin();
|
||||
assertNull(mPluginManager.getClassLoader("myPlugin", WHITELISTED_PACKAGE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@RunWithLooper(setAsMainLooper = true)
|
||||
public void testNonDebuggable_whitelistedPkg() {
|
||||
mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, false,
|
||||
new String[] {WHITELISTED_PACKAGE}, mMockExceptionHandler);
|
||||
resetExceptionHandler();
|
||||
|
||||
mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
|
||||
assertNotNull(mPluginManager.getClassLoader("myPlugin", WHITELISTED_PACKAGE));
|
||||
assertNull(mPluginManager.getClassLoader("myPlugin", "com.android.invalidpackage"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user