summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Oleg Petšjonkin <petsjonkin@google.com> 2025-01-27 03:33:21 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-01-27 03:33:21 -0800
commit413149d2a46ab5a7a4337c80cd3c51ac3b4766b8 (patch)
tree97c494789968107aa85beb4d6b08a8a25a14a575
parent754681b79a5274b9a6647e16b20710cf47af6b09 (diff)
parent02a6fc45d3d441a3e29f56e5902163615f5b92b7 (diff)
Merge "Adding multi-display support to Plugins framework" into main
-rw-r--r--services/core/java/com/android/server/display/plugin/PluginManager.java10
-rw-r--r--services/core/java/com/android/server/display/plugin/PluginStorage.java200
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt9
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt75
4 files changed, 231 insertions, 63 deletions
diff --git a/services/core/java/com/android/server/display/plugin/PluginManager.java b/services/core/java/com/android/server/display/plugin/PluginManager.java
index d4099975cafa..cb0a4574361a 100644
--- a/services/core/java/com/android/server/display/plugin/PluginManager.java
+++ b/services/core/java/com/android/server/display/plugin/PluginManager.java
@@ -74,15 +74,17 @@ public class PluginManager {
/**
* Adds change listener for particular plugin type
*/
- public <T> void subscribe(PluginType<T> type, PluginChangeListener<T> listener) {
- mPluginStorage.addListener(type, listener);
+ public <T> void subscribe(PluginType<T> type, String uniqueDisplayId,
+ PluginChangeListener<T> listener) {
+ mPluginStorage.addListener(type, uniqueDisplayId, listener);
}
/**
* Removes change listener
*/
- public <T> void unsubscribe(PluginType<T> type, PluginChangeListener<T> listener) {
- mPluginStorage.removeListener(type, listener);
+ public <T> void unsubscribe(PluginType<T> type, String uniqueDisplayId,
+ PluginChangeListener<T> listener) {
+ mPluginStorage.removeListener(type, uniqueDisplayId, listener);
}
/**
diff --git a/services/core/java/com/android/server/display/plugin/PluginStorage.java b/services/core/java/com/android/server/display/plugin/PluginStorage.java
index dd3415fb614d..5102c2709329 100644
--- a/services/core/java/com/android/server/display/plugin/PluginStorage.java
+++ b/services/core/java/com/android/server/display/plugin/PluginStorage.java
@@ -20,10 +20,13 @@ import android.annotation.Nullable;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
import java.io.PrintWriter;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -35,42 +38,97 @@ import java.util.Set;
public class PluginStorage {
private static final String TAG = "PluginStorage";
+ // Special ID used to indicate that given value is to be applied globally, rather than to a
+ // specific display. If both GLOBAL and specific display values are present - specific display
+ // value is selected.
+ @VisibleForTesting
+ static final String GLOBAL_ID = "GLOBAL";
+
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final Map<PluginType<?>, Object> mValues = new HashMap<>();
+ private final Map<PluginType<?>, ValuesContainer<?>> mValues = new HashMap<>();
@GuardedBy("mLock")
private final Map<PluginType<?>, ListenersContainer<?>> mListeners = new HashMap<>();
@GuardedBy("mLock")
- private final PluginEventStorage mPluginEventStorage = new PluginEventStorage();
+ private final Map<String, PluginEventStorage> mPluginEventStorages = new HashMap<>();
+
+ /**
+ * Updates value in storage and forwards it to corresponding listeners for all displays
+ * that does not have display specific value.
+ * Should be called by OEM Plugin implementation in order to communicate with Framework
+ */
+ @KeepForApi
+ public <T> void updateGlobalValue(PluginType<T> type, @Nullable T value) {
+ updateValue(type, GLOBAL_ID, value);
+ }
/**
- * Updates value in storage and forwards it to corresponding listeners.
- * Should be called by OEM Plugin implementation in order to provide communicate with Framework
+ * Updates value in storage and forwards it to corresponding listeners for specific display.
+ * Should be called by OEM Plugin implementation in order to communicate with Framework
+ * @param type - plugin type, that need to be updated
+ * @param uniqueDisplayId - uniqueDisplayId that this type/value should be applied to
+ * @param value - plugin value for particular type and display
*/
@KeepForApi
- public <T> void updateValue(PluginType<T> type, @Nullable T value) {
- Slog.d(TAG, "updateValue, type=" + type.mName + "; value=" + value);
+ public <T> void updateValue(PluginType<T> type, String uniqueDisplayId, @Nullable T value) {
+ Slog.d(TAG, "updateValue, type=" + type.mName + "; value=" + value
+ + "; displayId=" + uniqueDisplayId);
Set<PluginManager.PluginChangeListener<T>> localListeners;
+ T valueToNotify;
synchronized (mLock) {
- mValues.put(type, value);
- mPluginEventStorage.onValueUpdated(type);
- ListenersContainer<T> container = getListenersContainerForTypeLocked(type);
- localListeners = new LinkedHashSet<>(container.mListeners);
+ ValuesContainer<T> valuesByType = getValuesContainerLocked(type);
+ valuesByType.updateValueLocked(uniqueDisplayId, value);
+ // if value was set to null, we might need to notify with GLOBAL value instead
+ valueToNotify = valuesByType.getValueLocked(uniqueDisplayId);
+
+ PluginEventStorage storage = mPluginEventStorages.computeIfAbsent(uniqueDisplayId,
+ d -> new PluginEventStorage());
+ storage.onValueUpdated(type);
+
+ localListeners = getListenersForUpdateLocked(type, uniqueDisplayId);
}
Slog.d(TAG, "updateValue, notifying listeners=" + localListeners);
- localListeners.forEach(l -> l.onChanged(value));
+ localListeners.forEach(l -> l.onChanged(valueToNotify));
+ }
+
+ @GuardedBy("mLock")
+ private <T> Set<PluginManager.PluginChangeListener<T>> getListenersForUpdateLocked(
+ PluginType<T> type, String uniqueDisplayId) {
+ ListenersContainer<T> listenersContainer = getListenersContainerLocked(type);
+ Set<PluginManager.PluginChangeListener<T>> localListeners = new LinkedHashSet<>();
+ // if GLOBAL value change we need to notify only listeners for displays that does not
+ // have display specific value
+ if (GLOBAL_ID.equals(uniqueDisplayId)) {
+ ValuesContainer<T> valuesContainer = getValuesContainerLocked(type);
+ Set<String> excludedDisplayIds = valuesContainer.getNonGlobalDisplaysLocked();
+ listenersContainer.mListeners.forEach((localDisplayId, listeners) -> {
+ if (!excludedDisplayIds.contains(localDisplayId)) {
+ localListeners.addAll(listeners);
+ }
+ });
+ } else {
+ localListeners.addAll(
+ listenersContainer.mListeners.getOrDefault(uniqueDisplayId, Set.of()));
+ }
+ return localListeners;
}
/**
* Adds listener for PluginType. If storage already has value for this type, listener will
* be notified immediately.
*/
- <T> void addListener(PluginType<T> type, PluginManager.PluginChangeListener<T> listener) {
+ <T> void addListener(PluginType<T> type, String uniqueDisplayId,
+ PluginManager.PluginChangeListener<T> listener) {
+ if (GLOBAL_ID.equals(uniqueDisplayId)) {
+ Slog.d(TAG, "addListener ignored for GLOBAL_ID, type=" + type.mName);
+ return;
+ }
T value = null;
synchronized (mLock) {
- ListenersContainer<T> container = getListenersContainerForTypeLocked(type);
- if (container.mListeners.add(listener)) {
- value = getValueForTypeLocked(type);
+ ListenersContainer<T> container = getListenersContainerLocked(type);
+ if (container.addListenerLocked(uniqueDisplayId, listener)) {
+ ValuesContainer<T> valuesContainer = getValuesContainerLocked(type);
+ value = valuesContainer.getValueLocked(uniqueDisplayId);
}
}
if (value != null) {
@@ -81,10 +139,15 @@ public class PluginStorage {
/**
* Removes listener
*/
- <T> void removeListener(PluginType<T> type, PluginManager.PluginChangeListener<T> listener) {
+ <T> void removeListener(PluginType<T> type, String uniqueDisplayId,
+ PluginManager.PluginChangeListener<T> listener) {
+ if (GLOBAL_ID.equals(uniqueDisplayId)) {
+ Slog.d(TAG, "removeListener ignored for GLOBAL_ID, type=" + type.mName);
+ return;
+ }
synchronized (mLock) {
- ListenersContainer<T> container = getListenersContainerForTypeLocked(type);
- container.mListeners.remove(listener);
+ ListenersContainer<T> container = getListenersContainerLocked(type);
+ container.removeListenerLocked(uniqueDisplayId, listener);
}
}
@@ -92,53 +155,106 @@ public class PluginStorage {
* Print the object's state and debug information into the given stream.
*/
void dump(PrintWriter pw) {
- Map<PluginType<?>, Object> localValues;
+ Map<PluginType<?>, Map<String, Object>> localValues = new HashMap<>();
@SuppressWarnings("rawtypes")
- Map<PluginType, Set> localListeners = new HashMap<>();
- List<PluginEventStorage.TimeFrame> timeFrames;
+ Map<PluginType, Map<String, Set>> localListeners = new HashMap<>();
+ Map<String, List<PluginEventStorage.TimeFrame>> timeFrames = new HashMap<>();
synchronized (mLock) {
- timeFrames = mPluginEventStorage.getTimeFrames();
- localValues = new HashMap<>(mValues);
- mListeners.forEach((type, container) -> localListeners.put(type, container.mListeners));
+ mPluginEventStorages.forEach((displayId, storage) -> {
+ timeFrames.put(displayId, storage.getTimeFrames());
+ });
+ mValues.forEach((type, valueContainer) -> {
+ localValues.put(type, new HashMap<>(valueContainer.mValues));
+ });
+ mListeners.forEach((type, container) -> {
+ localListeners.put(type, new HashMap<>(container.mListeners));
+ });
}
pw.println("PluginStorage:");
pw.println("values=" + localValues);
pw.println("listeners=" + localListeners);
pw.println("PluginEventStorage:");
- for (PluginEventStorage.TimeFrame timeFrame: timeFrames) {
- timeFrame.dump(pw);
+ for (Map.Entry<String, List<PluginEventStorage.TimeFrame>> timeFrameEntry :
+ timeFrames.entrySet()) {
+ pw.println("TimeFrames for displayId=" + timeFrameEntry.getKey());
+ for (PluginEventStorage.TimeFrame timeFrame : timeFrameEntry.getValue()) {
+ timeFrame.dump(pw);
+ }
}
}
@GuardedBy("mLock")
@SuppressWarnings("unchecked")
- private <T> T getValueForTypeLocked(PluginType<T> type) {
- Object value = mValues.get(type);
- if (value == null) {
- return null;
- } else if (type.mType == value.getClass()) {
- return (T) value;
+ private <T> ListenersContainer<T> getListenersContainerLocked(PluginType<T> type) {
+ ListenersContainer<?> container = mListeners.get(type);
+ if (container == null) {
+ ListenersContainer<T> lc = new ListenersContainer<>();
+ mListeners.put(type, lc);
+ return lc;
} else {
- Slog.d(TAG, "getValueForType: unexpected value type=" + value.getClass().getName()
- + ", expected=" + type.mType.getName());
- return null;
+ return (ListenersContainer<T>) container;
}
}
@GuardedBy("mLock")
@SuppressWarnings("unchecked")
- private <T> ListenersContainer<T> getListenersContainerForTypeLocked(PluginType<T> type) {
- ListenersContainer<?> container = mListeners.get(type);
+ private <T> ValuesContainer<T> getValuesContainerLocked(PluginType<T> type) {
+ ValuesContainer<?> container = mValues.get(type);
if (container == null) {
- ListenersContainer<T> lc = new ListenersContainer<>();
- mListeners.put(type, lc);
- return lc;
+ ValuesContainer<T> vc = new ValuesContainer<>();
+ mValues.put(type, vc);
+ return vc;
} else {
- return (ListenersContainer<T>) container;
+ return (ValuesContainer<T>) container;
}
}
private static final class ListenersContainer<T> {
- private final Set<PluginManager.PluginChangeListener<T>> mListeners = new LinkedHashSet<>();
+ private final Map<String, Set<PluginManager.PluginChangeListener<T>>> mListeners =
+ new LinkedHashMap<>();
+
+ private boolean addListenerLocked(
+ String uniqueDisplayId, PluginManager.PluginChangeListener<T> listener) {
+ Set<PluginManager.PluginChangeListener<T>> listenersForDisplay =
+ mListeners.computeIfAbsent(uniqueDisplayId, k -> new LinkedHashSet<>());
+ return listenersForDisplay.add(listener);
+ }
+
+ private void removeListenerLocked(String uniqueDisplayId,
+ PluginManager.PluginChangeListener<T> listener) {
+ Set<PluginManager.PluginChangeListener<T>> listenersForDisplay = mListeners.get(
+ uniqueDisplayId);
+ if (listenersForDisplay == null) {
+ return;
+ }
+
+ listenersForDisplay.remove(listener);
+
+ if (listenersForDisplay.isEmpty()) {
+ mListeners.remove(uniqueDisplayId);
+ }
+ }
+ }
+
+ private static final class ValuesContainer<T> {
+ private final Map<String, T> mValues = new HashMap<>();
+
+ private void updateValueLocked(String uniqueDisplayId, @Nullable T value) {
+ if (value == null) {
+ mValues.remove(uniqueDisplayId);
+ } else {
+ mValues.put(uniqueDisplayId, value);
+ }
+ }
+
+ private Set<String> getNonGlobalDisplaysLocked() {
+ Set<String> keys = new HashSet<>(mValues.keySet());
+ keys.remove(GLOBAL_ID);
+ return keys;
+ }
+
+ private @Nullable T getValueLocked(String displayId) {
+ return mValues.getOrDefault(displayId, mValues.get(GLOBAL_ID));
+ }
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt
index 01061f16c279..d9224eaf66ca 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt
@@ -29,6 +29,7 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
private val TEST_PLUGIN_TYPE = PluginType(Int::class.java, "test_type")
+private val DISPLAY_ID = "display_id"
@SmallTest
class PluginManagerTest {
@@ -62,18 +63,18 @@ class PluginManagerTest {
fun testSubscribe() {
val pluginManager = createPluginManager()
- pluginManager.subscribe(TEST_PLUGIN_TYPE, mockListener)
+ pluginManager.subscribe(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener)
- verify(testInjector.mockStorage).addListener(TEST_PLUGIN_TYPE, mockListener)
+ verify(testInjector.mockStorage).addListener(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener)
}
@Test
fun testUnsubscribe() {
val pluginManager = createPluginManager()
- pluginManager.unsubscribe(TEST_PLUGIN_TYPE, mockListener)
+ pluginManager.unsubscribe(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener)
- verify(testInjector.mockStorage).removeListener(TEST_PLUGIN_TYPE, mockListener)
+ verify(testInjector.mockStorage).removeListener(TEST_PLUGIN_TYPE, DISPLAY_ID, mockListener)
}
private fun createPluginManager(enabled: Boolean = true): PluginManager {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt
index 218e34134e93..8eb3e9fbf9b0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt
@@ -23,6 +23,8 @@ import org.junit.Test
private val TEST_PLUGIN_TYPE1 = PluginType(String::class.java, "test_type1")
private val TEST_PLUGIN_TYPE2 = PluginType(String::class.java, "test_type2")
+private val DISPLAY_ID_1 = "display_1"
+private val DISPLAY_ID_2 = "display_2"
@SmallTest
class PluginStorageTest {
@@ -33,9 +35,9 @@ class PluginStorageTest {
fun testUpdateValue() {
val type1Value = "value1"
val testChangeListener = TestPluginChangeListener<String>()
- storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener)
- storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
assertThat(testChangeListener.receivedValue).isEqualTo(type1Value)
}
@@ -44,9 +46,9 @@ class PluginStorageTest {
fun testAddListener() {
val type1Value = "value1"
val testChangeListener = TestPluginChangeListener<String>()
- storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
- storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener)
assertThat(testChangeListener.receivedValue).isEqualTo(type1Value)
}
@@ -55,10 +57,10 @@ class PluginStorageTest {
fun testRemoveListener() {
val type1Value = "value1"
val testChangeListener = TestPluginChangeListener<String>()
- storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener)
- storage.removeListener(TEST_PLUGIN_TYPE1, testChangeListener)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener)
+ storage.removeListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener)
- storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
assertThat(testChangeListener.receivedValue).isNull()
}
@@ -68,10 +70,10 @@ class PluginStorageTest {
val type1Value = "value1"
val type2Value = "value2"
val testChangeListener = TestPluginChangeListener<String>()
- storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
- storage.updateValue(TEST_PLUGIN_TYPE2, type2Value)
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
+ storage.updateValue(TEST_PLUGIN_TYPE2, DISPLAY_ID_1, type2Value)
- storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener)
assertThat(testChangeListener.receivedValue).isEqualTo(type1Value)
}
@@ -81,15 +83,62 @@ class PluginStorageTest {
val type1Value = "value1"
val testChangeListener1 = TestPluginChangeListener<String>()
val testChangeListener2 = TestPluginChangeListener<String>()
- storage.addListener(TEST_PLUGIN_TYPE1, testChangeListener1)
- storage.addListener(TEST_PLUGIN_TYPE2, testChangeListener2)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1)
+ storage.addListener(TEST_PLUGIN_TYPE2, DISPLAY_ID_1, testChangeListener2)
- storage.updateValue(TEST_PLUGIN_TYPE1, type1Value)
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value)
assertThat(testChangeListener2.receivedValue).isNull()
}
+ @Test
+ fun testUpdateGlobal_noDisplaySpecificValue() {
+ val type1Value = "value1"
+ val testChangeListener1 = TestPluginChangeListener<String>()
+ val testChangeListener2 = TestPluginChangeListener<String>()
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2)
+
+ storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1Value)
+
+ assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value)
+ assertThat(testChangeListener2.receivedValue).isEqualTo(type1Value)
+ }
+
+ @Test
+ fun testUpdateGlobal_withDisplaySpecificValue() {
+ val type1Value = "value1"
+ val type1GlobalValue = "value1Global"
+ val testChangeListener1 = TestPluginChangeListener<String>()
+ val testChangeListener2 = TestPluginChangeListener<String>()
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2)
+
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
+ storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1GlobalValue)
+
+ assertThat(testChangeListener1.receivedValue).isEqualTo(type1Value)
+ assertThat(testChangeListener2.receivedValue).isEqualTo(type1GlobalValue)
+ }
+
+ @Test
+ fun testUpdateGlobal_withDisplaySpecificValueRemoved() {
+ val type1Value = "value1"
+ val type1GlobalValue = "value1Global"
+ val testChangeListener1 = TestPluginChangeListener<String>()
+ val testChangeListener2 = TestPluginChangeListener<String>()
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, testChangeListener1)
+ storage.addListener(TEST_PLUGIN_TYPE1, DISPLAY_ID_2, testChangeListener2)
+
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, type1Value)
+ storage.updateGlobalValue(TEST_PLUGIN_TYPE1, type1GlobalValue)
+ storage.updateValue(TEST_PLUGIN_TYPE1, DISPLAY_ID_1, null)
+
+ assertThat(testChangeListener1.receivedValue).isEqualTo(type1GlobalValue)
+ assertThat(testChangeListener2.receivedValue).isEqualTo(type1GlobalValue)
+ }
+
private class TestPluginChangeListener<T> : PluginChangeListener<T> {
var receivedValue: T? = null