Merge "Skip dynamic load of known clock plugins" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
9387c640b0
@@ -48,6 +48,18 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
private val KEY_TIMESTAMP = "appliedTimestamp"
|
||||
private val KNOWN_PLUGINS =
|
||||
mapOf<String, List<ClockMetadata>>(
|
||||
"com.android.systemui.falcon.one" to listOf(ClockMetadata("ANALOG_CLOCK_BIGNUM")),
|
||||
"com.android.systemui.falcon.two" to listOf(ClockMetadata("DIGITAL_CLOCK_CALLIGRAPHY")),
|
||||
"com.android.systemui.falcon.three" to listOf(ClockMetadata("DIGITAL_CLOCK_FLEX")),
|
||||
"com.android.systemui.falcon.four" to listOf(ClockMetadata("DIGITAL_CLOCK_GROWTH")),
|
||||
"com.android.systemui.falcon.five" to listOf(ClockMetadata("DIGITAL_CLOCK_HANDWRITTEN")),
|
||||
"com.android.systemui.falcon.six" to listOf(ClockMetadata("DIGITAL_CLOCK_INFLATE")),
|
||||
"com.android.systemui.falcon.seven" to listOf(ClockMetadata("DIGITAL_CLOCK_METRO")),
|
||||
"com.android.systemui.falcon.eight" to listOf(ClockMetadata("DIGITAL_CLOCK_NUMBEROVERLAP")),
|
||||
"com.android.systemui.falcon.nine" to listOf(ClockMetadata("DIGITAL_CLOCK_WEATHER")),
|
||||
)
|
||||
|
||||
private fun <TKey, TVal> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut(
|
||||
key: TKey,
|
||||
@@ -127,8 +139,61 @@ open class ClockRegistry(
|
||||
|
||||
private val pluginListener =
|
||||
object : PluginListener<ClockProviderPlugin> {
|
||||
override fun onPluginAttached(manager: PluginLifecycleManager<ClockProviderPlugin>) {
|
||||
manager.loadPlugin()
|
||||
override fun onPluginAttached(
|
||||
manager: PluginLifecycleManager<ClockProviderPlugin>
|
||||
): Boolean {
|
||||
if (keepAllLoaded) {
|
||||
// Always load new plugins if requested
|
||||
return true
|
||||
}
|
||||
|
||||
val knownClocks = KNOWN_PLUGINS.get(manager.getPackage())
|
||||
if (knownClocks == null) {
|
||||
logBuffer.tryLog(
|
||||
TAG,
|
||||
LogLevel.WARNING,
|
||||
{ str1 = manager.getPackage() },
|
||||
{ "Loading unrecognized clock package: $str1" }
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
logBuffer.tryLog(
|
||||
TAG,
|
||||
LogLevel.INFO,
|
||||
{ str1 = manager.getPackage() },
|
||||
{ "Skipping initial load of known clock package package: $str1" }
|
||||
)
|
||||
|
||||
var isClockListChanged = false
|
||||
for (metadata in knownClocks) {
|
||||
val id = metadata.clockId
|
||||
val info =
|
||||
availableClocks.concurrentGetOrPut(id, ClockInfo(metadata, null, manager)) {
|
||||
isClockListChanged = true
|
||||
onConnected(id)
|
||||
}
|
||||
|
||||
if (manager != info.manager) {
|
||||
logBuffer.tryLog(
|
||||
TAG,
|
||||
LogLevel.ERROR,
|
||||
{ str1 = id },
|
||||
{ "Clock Id conflict on known attach: $str1 is double registered" }
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
info.provider = null
|
||||
}
|
||||
|
||||
if (isClockListChanged) {
|
||||
triggerOnAvailableClocksChanged()
|
||||
}
|
||||
verifyLoadedProviders()
|
||||
|
||||
// Load executed via verifyLoadedProviders
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onPluginLoaded(
|
||||
|
||||
@@ -190,7 +190,9 @@ enum class ClockTickRate(val value: Int) {
|
||||
data class ClockMetadata(
|
||||
val clockId: ClockId,
|
||||
val name: String,
|
||||
)
|
||||
) {
|
||||
constructor(clockId: ClockId) : this(clockId, clockId) {}
|
||||
}
|
||||
|
||||
/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
|
||||
data class ClockConfig(
|
||||
|
||||
@@ -16,12 +16,20 @@
|
||||
|
||||
package com.android.systemui.plugins;
|
||||
|
||||
import android.content.ComponentName;
|
||||
|
||||
/**
|
||||
* Provides the ability for consumers to control plugin lifecycle.
|
||||
*
|
||||
* @param <T> is the target plugin type
|
||||
*/
|
||||
public interface PluginLifecycleManager<T extends Plugin> {
|
||||
/** Returns the ComponentName of the target plugin. Maybe be called when not loaded. */
|
||||
ComponentName getComponentName();
|
||||
|
||||
/** Returns the package name of the target plugin. May be called when not loaded. */
|
||||
String getPackage();
|
||||
|
||||
/** Returns the currently loaded plugin instance (if plugin is loaded) */
|
||||
T getPlugin();
|
||||
|
||||
|
||||
@@ -60,13 +60,18 @@ public interface PluginListener<T extends Plugin> {
|
||||
|
||||
/**
|
||||
* Called when the plugin is first attached to the host application. {@link #onPluginLoaded}
|
||||
* will be automatically called as well when first attached. This may be called multiple times
|
||||
* if multiple plugins are allowed. It may also be called in the future if the plugin package
|
||||
* changes and needs to be reloaded. Each call to {@link #onPluginAttached} will provide a new
|
||||
* or different {@link PluginLifecycleManager}.
|
||||
* will be automatically called as well when first attached if true is returned. This may be
|
||||
* called multiple times if multiple plugins are allowed. It may also be called in the future
|
||||
* if the plugin package changes and needs to be reloaded. Each call to
|
||||
* {@link #onPluginAttached} will provide a new or different {@link PluginLifecycleManager}.
|
||||
*
|
||||
* @return returning true will immediately load the plugin and call onPluginLoaded with the
|
||||
* created object. false will skip loading, but the listener can load it at any time using the
|
||||
* provided PluginLifecycleManager. Loading plugins immediately is the default behavior.
|
||||
*/
|
||||
default void onPluginAttached(PluginLifecycleManager<T> manager) {
|
||||
default boolean onPluginAttached(PluginLifecycleManager<T> manager) {
|
||||
// Optional
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -79,17 +79,26 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
|
||||
|
||||
/** Alerts listener and plugin that the plugin has been created. */
|
||||
public void onCreate() {
|
||||
mListener.onPluginAttached(this);
|
||||
boolean loadPlugin = mListener.onPluginAttached(this);
|
||||
if (!loadPlugin) {
|
||||
if (mPlugin != null) {
|
||||
unloadPlugin();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPlugin == null) {
|
||||
loadPlugin();
|
||||
} else {
|
||||
if (!(mPlugin instanceof PluginFragment)) {
|
||||
// Only call onCreate for plugins that aren't fragments, as fragments
|
||||
// will get the onCreate as part of the fragment lifecycle.
|
||||
mPlugin.onCreate(mAppContext, mPluginContext);
|
||||
}
|
||||
mListener.onPluginLoaded(mPlugin, mPluginContext, this);
|
||||
return;
|
||||
}
|
||||
|
||||
mPluginFactory.checkVersion(mPlugin);
|
||||
if (!(mPlugin instanceof PluginFragment)) {
|
||||
// Only call onCreate for plugins that aren't fragments, as fragments
|
||||
// will get the onCreate as part of the fragment lifecycle.
|
||||
mPlugin.onCreate(mAppContext, mPluginContext);
|
||||
}
|
||||
mListener.onPluginLoaded(mPlugin, mPluginContext, this);
|
||||
}
|
||||
|
||||
/** Alerts listener and plugin that the plugin is being shutdown. */
|
||||
@@ -118,6 +127,7 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
|
||||
return;
|
||||
}
|
||||
|
||||
mPluginFactory.checkVersion(mPlugin);
|
||||
if (!(mPlugin instanceof PluginFragment)) {
|
||||
// Only call onCreate for plugins that aren't fragments, as fragments
|
||||
// will get the onCreate as part of the fragment lifecycle.
|
||||
@@ -205,12 +215,8 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
|
||||
PluginFactory<T> pluginFactory = new PluginFactory<T>(
|
||||
context, mInstanceFactory, appInfo, componentName, mVersionChecker, pluginClass,
|
||||
() -> getClassLoader(appInfo, mBaseClassLoader));
|
||||
// TODO: Only create the plugin before version check if we need it for
|
||||
// legacy version check.
|
||||
T instance = pluginFactory.createPlugin();
|
||||
pluginFactory.checkVersion(instance);
|
||||
return new PluginInstance<T>(
|
||||
context, listener, componentName, pluginFactory, instance);
|
||||
context, listener, componentName, pluginFactory, null);
|
||||
}
|
||||
|
||||
private boolean isPluginPackagePrivileged(String packageName) {
|
||||
@@ -332,7 +338,9 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
|
||||
ClassLoader loader = mClassLoaderFactory.get();
|
||||
Class<T> instanceClass = (Class<T>) Class.forName(
|
||||
mComponentName.getClassName(), true, loader);
|
||||
return (T) mInstanceFactory.create(instanceClass);
|
||||
T result = (T) mInstanceFactory.create(instanceClass);
|
||||
Log.v(TAG, "Created plugin: " + result);
|
||||
return result;
|
||||
} catch (ClassNotFoundException ex) {
|
||||
Log.e(TAG, "Failed to load plugin", ex);
|
||||
} catch (IllegalAccessException ex) {
|
||||
|
||||
@@ -297,6 +297,54 @@ class ClockRegistryTest : SysuiTestCase() {
|
||||
assertEquals(4, listChangeCallCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unknownPluginAttached_clockAndListUnchanged_loadRequested() {
|
||||
val mockPluginLifecycle = mock<PluginLifecycleManager<ClockProviderPlugin>>()
|
||||
whenever(mockPluginLifecycle.getPackage()).thenReturn("some.other.package")
|
||||
|
||||
var changeCallCount = 0
|
||||
var listChangeCallCount = 0
|
||||
registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener {
|
||||
override fun onCurrentClockChanged() { changeCallCount++ }
|
||||
override fun onAvailableClocksChanged() { listChangeCallCount++ }
|
||||
})
|
||||
|
||||
assertEquals(true, pluginListener.onPluginAttached(mockPluginLifecycle))
|
||||
scheduler.runCurrent()
|
||||
assertEquals(0, changeCallCount)
|
||||
assertEquals(0, listChangeCallCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun knownPluginAttached_clockAndListChanged_notLoaded() {
|
||||
val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
|
||||
whenever(mockPluginLifecycle1.getPackage()).thenReturn("com.android.systemui.falcon.one")
|
||||
val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
|
||||
whenever(mockPluginLifecycle2.getPackage()).thenReturn("com.android.systemui.falcon.two")
|
||||
|
||||
var changeCallCount = 0
|
||||
var listChangeCallCount = 0
|
||||
registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener {
|
||||
override fun onCurrentClockChanged() { changeCallCount++ }
|
||||
override fun onAvailableClocksChanged() { listChangeCallCount++ }
|
||||
})
|
||||
|
||||
registry.applySettings(ClockSettings("DIGITAL_CLOCK_CALLIGRAPHY", null))
|
||||
scheduler.runCurrent()
|
||||
assertEquals(1, changeCallCount)
|
||||
assertEquals(0, listChangeCallCount)
|
||||
|
||||
assertEquals(false, pluginListener.onPluginAttached(mockPluginLifecycle1))
|
||||
scheduler.runCurrent()
|
||||
assertEquals(1, changeCallCount)
|
||||
assertEquals(1, listChangeCallCount)
|
||||
|
||||
assertEquals(false, pluginListener.onPluginAttached(mockPluginLifecycle2))
|
||||
scheduler.runCurrent()
|
||||
assertEquals(1, changeCallCount)
|
||||
assertEquals(2, listChangeCallCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pluginAddRemove_concurrentModification() {
|
||||
val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
|
||||
|
||||
@@ -122,6 +122,7 @@ public class PluginInstanceTest extends SysuiTestCase {
|
||||
mPluginInstanceFactory.create(
|
||||
mContext, mAppInfo, wrongVersionTestPluginComponentName,
|
||||
TestPlugin.class, mPluginListener);
|
||||
mPluginInstance.onCreate();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -135,11 +136,12 @@ public class PluginInstanceTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void testOnDestroy() {
|
||||
mPluginInstance.onCreate();
|
||||
mPluginInstance.onDestroy();
|
||||
assertEquals(1, mPluginListener.mDetachedCount);
|
||||
assertEquals(1, mPluginListener.mUnloadCount);
|
||||
assertNull(mPluginInstance.getPlugin());
|
||||
assertInstances(0, -1); // Destroyed but never created
|
||||
assertInstances(0, 0); // Destroyed but never created
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -161,6 +163,16 @@ public class PluginInstanceTest extends SysuiTestCase {
|
||||
assertInstances(0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnAttach_SkipLoad() {
|
||||
mPluginListener.mAttachReturn = false;
|
||||
mPluginInstance.onCreate();
|
||||
assertEquals(1, mPluginListener.mAttachedCount);
|
||||
assertEquals(0, mPluginListener.mLoadCount);
|
||||
assertEquals(null, mPluginInstance.getPlugin());
|
||||
assertInstances(0, 0);
|
||||
}
|
||||
|
||||
// This target class doesn't matter, it just needs to have a Requires to hit the flow where
|
||||
// the mock version info is called.
|
||||
@ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
|
||||
@@ -220,15 +232,17 @@ public class PluginInstanceTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
public class FakeListener implements PluginListener<TestPlugin> {
|
||||
public boolean mAttachReturn = true;
|
||||
public int mAttachedCount = 0;
|
||||
public int mDetachedCount = 0;
|
||||
public int mLoadCount = 0;
|
||||
public int mUnloadCount = 0;
|
||||
|
||||
@Override
|
||||
public void onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
|
||||
public boolean onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
|
||||
mAttachedCount++;
|
||||
assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
|
||||
return mAttachReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user