Merge "Skip dynamic load of known clock plugins" into udc-dev

This commit is contained in:
Hawkwood Glazier
2023-06-06 22:21:07 +00:00
committed by Android (Google) Code Review
7 changed files with 174 additions and 24 deletions

View File

@@ -48,6 +48,18 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
private val KEY_TIMESTAMP = "appliedTimestamp" 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( private fun <TKey, TVal> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut(
key: TKey, key: TKey,
@@ -127,8 +139,61 @@ open class ClockRegistry(
private val pluginListener = private val pluginListener =
object : PluginListener<ClockProviderPlugin> { object : PluginListener<ClockProviderPlugin> {
override fun onPluginAttached(manager: PluginLifecycleManager<ClockProviderPlugin>) { override fun onPluginAttached(
manager.loadPlugin() 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( override fun onPluginLoaded(

View File

@@ -190,7 +190,9 @@ enum class ClockTickRate(val value: Int) {
data class ClockMetadata( data class ClockMetadata(
val clockId: ClockId, val clockId: ClockId,
val name: String, val name: String,
) ) {
constructor(clockId: ClockId) : this(clockId, clockId) {}
}
/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */ /** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
data class ClockConfig( data class ClockConfig(

View File

@@ -16,12 +16,20 @@
package com.android.systemui.plugins; package com.android.systemui.plugins;
import android.content.ComponentName;
/** /**
* Provides the ability for consumers to control plugin lifecycle. * Provides the ability for consumers to control plugin lifecycle.
* *
* @param <T> is the target plugin type * @param <T> is the target plugin type
*/ */
public interface PluginLifecycleManager<T extends Plugin> { 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) */ /** Returns the currently loaded plugin instance (if plugin is loaded) */
T getPlugin(); T getPlugin();

View File

@@ -60,13 +60,18 @@ public interface PluginListener<T extends Plugin> {
/** /**
* Called when the plugin is first attached to the host application. {@link #onPluginLoaded} * 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 * will be automatically called as well when first attached if true is returned. This may be
* if multiple plugins are allowed. It may also be called in the future if the plugin package * called multiple times if multiple plugins are allowed. It may also be called in the future
* changes and needs to be reloaded. Each call to {@link #onPluginAttached} will provide a new * if the plugin package changes and needs to be reloaded. Each call to
* or different {@link PluginLifecycleManager}. * {@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 // Optional
return true;
} }
/** /**

View File

@@ -79,17 +79,26 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
/** Alerts listener and plugin that the plugin has been created. */ /** Alerts listener and plugin that the plugin has been created. */
public void onCreate() { public void onCreate() {
mListener.onPluginAttached(this); boolean loadPlugin = mListener.onPluginAttached(this);
if (!loadPlugin) {
if (mPlugin != null) {
unloadPlugin();
}
return;
}
if (mPlugin == null) { if (mPlugin == null) {
loadPlugin(); loadPlugin();
} else { return;
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);
} }
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. */ /** Alerts listener and plugin that the plugin is being shutdown. */
@@ -118,6 +127,7 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
return; return;
} }
mPluginFactory.checkVersion(mPlugin);
if (!(mPlugin instanceof PluginFragment)) { if (!(mPlugin instanceof PluginFragment)) {
// Only call onCreate for plugins that aren't fragments, as fragments // Only call onCreate for plugins that aren't fragments, as fragments
// will get the onCreate as part of the fragment lifecycle. // 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>( PluginFactory<T> pluginFactory = new PluginFactory<T>(
context, mInstanceFactory, appInfo, componentName, mVersionChecker, pluginClass, context, mInstanceFactory, appInfo, componentName, mVersionChecker, pluginClass,
() -> getClassLoader(appInfo, mBaseClassLoader)); () -> 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>( return new PluginInstance<T>(
context, listener, componentName, pluginFactory, instance); context, listener, componentName, pluginFactory, null);
} }
private boolean isPluginPackagePrivileged(String packageName) { private boolean isPluginPackagePrivileged(String packageName) {
@@ -332,7 +338,9 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
ClassLoader loader = mClassLoaderFactory.get(); ClassLoader loader = mClassLoaderFactory.get();
Class<T> instanceClass = (Class<T>) Class.forName( Class<T> instanceClass = (Class<T>) Class.forName(
mComponentName.getClassName(), true, loader); 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) { } catch (ClassNotFoundException ex) {
Log.e(TAG, "Failed to load plugin", ex); Log.e(TAG, "Failed to load plugin", ex);
} catch (IllegalAccessException ex) { } catch (IllegalAccessException ex) {

View File

@@ -297,6 +297,54 @@ class ClockRegistryTest : SysuiTestCase() {
assertEquals(4, listChangeCallCount) 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 @Test
fun pluginAddRemove_concurrentModification() { fun pluginAddRemove_concurrentModification() {
val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()

View File

@@ -122,6 +122,7 @@ public class PluginInstanceTest extends SysuiTestCase {
mPluginInstanceFactory.create( mPluginInstanceFactory.create(
mContext, mAppInfo, wrongVersionTestPluginComponentName, mContext, mAppInfo, wrongVersionTestPluginComponentName,
TestPlugin.class, mPluginListener); TestPlugin.class, mPluginListener);
mPluginInstance.onCreate();
} }
@Test @Test
@@ -135,11 +136,12 @@ public class PluginInstanceTest extends SysuiTestCase {
@Test @Test
public void testOnDestroy() { public void testOnDestroy() {
mPluginInstance.onCreate();
mPluginInstance.onDestroy(); mPluginInstance.onDestroy();
assertEquals(1, mPluginListener.mDetachedCount); assertEquals(1, mPluginListener.mDetachedCount);
assertEquals(1, mPluginListener.mUnloadCount); assertEquals(1, mPluginListener.mUnloadCount);
assertNull(mPluginInstance.getPlugin()); assertNull(mPluginInstance.getPlugin());
assertInstances(0, -1); // Destroyed but never created assertInstances(0, 0); // Destroyed but never created
} }
@Test @Test
@@ -161,6 +163,16 @@ public class PluginInstanceTest extends SysuiTestCase {
assertInstances(0, 0); 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 // This target class doesn't matter, it just needs to have a Requires to hit the flow where
// the mock version info is called. // the mock version info is called.
@ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION) @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
@@ -220,15 +232,17 @@ public class PluginInstanceTest extends SysuiTestCase {
} }
public class FakeListener implements PluginListener<TestPlugin> { public class FakeListener implements PluginListener<TestPlugin> {
public boolean mAttachReturn = true;
public int mAttachedCount = 0; public int mAttachedCount = 0;
public int mDetachedCount = 0; public int mDetachedCount = 0;
public int mLoadCount = 0; public int mLoadCount = 0;
public int mUnloadCount = 0; public int mUnloadCount = 0;
@Override @Override
public void onPluginAttached(PluginLifecycleManager<TestPlugin> manager) { public boolean onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
mAttachedCount++; mAttachedCount++;
assertEquals(PluginInstanceTest.this.mPluginInstance, manager); assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
return mAttachReturn;
} }
@Override @Override