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
|
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(
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>>()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user