Loading services/core/java/com/android/server/display/plugin/PluginManager.java +6 −4 Original line number Diff line number Diff line Loading @@ -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); } /** Loading services/core/java/com/android/server/display/plugin/PluginStorage.java +158 −42 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading @@ -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); } } Loading @@ -92,42 +155,37 @@ 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) { 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; } else { Slog.d(TAG, "getValueForType: unexpected value type=" + value.getClass().getName() + ", expected=" + type.mType.getName()); return null; } } @GuardedBy("mLock") @SuppressWarnings("unchecked") private <T> ListenersContainer<T> getListenersContainerForTypeLocked(PluginType<T> type) { private <T> ListenersContainer<T> getListenersContainerLocked(PluginType<T> type) { ListenersContainer<?> container = mListeners.get(type); if (container == null) { ListenersContainer<T> lc = new ListenersContainer<>(); Loading @@ -138,7 +196,65 @@ public class PluginStorage { } } @GuardedBy("mLock") @SuppressWarnings("unchecked") private <T> ValuesContainer<T> getValuesContainerLocked(PluginType<T> type) { ValuesContainer<?> container = mValues.get(type); if (container == null) { ValuesContainer<T> vc = new ValuesContainer<>(); mValues.put(type, vc); return vc; } else { 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)); } } } services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt +5 −4 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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 { Loading services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt +62 −13 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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) } Loading @@ -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) } Loading @@ -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() } Loading @@ -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) } Loading @@ -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 Loading Loading
services/core/java/com/android/server/display/plugin/PluginManager.java +6 −4 Original line number Diff line number Diff line Loading @@ -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); } /** Loading
services/core/java/com/android/server/display/plugin/PluginStorage.java +158 −42 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading @@ -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); } } Loading @@ -92,42 +155,37 @@ 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) { 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; } else { Slog.d(TAG, "getValueForType: unexpected value type=" + value.getClass().getName() + ", expected=" + type.mType.getName()); return null; } } @GuardedBy("mLock") @SuppressWarnings("unchecked") private <T> ListenersContainer<T> getListenersContainerForTypeLocked(PluginType<T> type) { private <T> ListenersContainer<T> getListenersContainerLocked(PluginType<T> type) { ListenersContainer<?> container = mListeners.get(type); if (container == null) { ListenersContainer<T> lc = new ListenersContainer<>(); Loading @@ -138,7 +196,65 @@ public class PluginStorage { } } @GuardedBy("mLock") @SuppressWarnings("unchecked") private <T> ValuesContainer<T> getValuesContainerLocked(PluginType<T> type) { ValuesContainer<?> container = mValues.get(type); if (container == null) { ValuesContainer<T> vc = new ValuesContainer<>(); mValues.put(type, vc); return vc; } else { 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)); } } }
services/tests/displayservicetests/src/com/android/server/display/plugin/PluginManagerTest.kt +5 −4 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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 { Loading
services/tests/displayservicetests/src/com/android/server/display/plugin/PluginStorageTest.kt +62 −13 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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) } Loading @@ -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) } Loading @@ -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() } Loading @@ -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) } Loading @@ -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 Loading