• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.display.plugin;
18 
19 import android.annotation.Nullable;
20 import android.util.Slog;
21 
22 import com.android.internal.annotations.GuardedBy;
23 import com.android.internal.annotations.VisibleForTesting;
24 import com.android.tools.r8.keepanno.annotations.KeepForApi;
25 
26 import java.io.PrintWriter;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.LinkedHashMap;
31 import java.util.LinkedHashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 
36 /**
37  * Stores values pushed by Plugins and forwards them to corresponding listener.
38  */
39 public class PluginStorage {
40     private static final String TAG = "PluginStorage";
41 
42     // Special ID used to indicate that given value is to be applied globally, rather than to a
43     // specific display. If both GLOBAL and specific display values are present - specific display
44     // value is selected.
45     @VisibleForTesting
46     static final String GLOBAL_ID = "GLOBAL";
47 
48     private final Object mLock = new Object();
49     @GuardedBy("mLock")
50     private final Map<PluginType<?>, ValuesContainer<?>> mValues = new HashMap<>();
51     @GuardedBy("mLock")
52     private final Map<PluginType<?>, ListenersContainer<?>> mListeners = new HashMap<>();
53     @GuardedBy("mLock")
54     private final Map<String, PluginEventStorage> mPluginEventStorages = new HashMap<>();
55 
56     /**
57      * Updates value in storage and forwards it to corresponding listeners for all displays
58      * that does not have display specific value.
59      * Should be called by OEM Plugin implementation in order to communicate with Framework
60      */
61     @KeepForApi
updateGlobalValue(PluginType<T> type, @Nullable T value)62     public <T> void updateGlobalValue(PluginType<T> type, @Nullable T value) {
63         updateValue(type, GLOBAL_ID, value);
64     }
65 
66     private final Set<PluginType<?>> mEnabledTypes;
67 
PluginStorage(Set<PluginType<?>> enabledTypes)68     PluginStorage(Set<PluginType<?>> enabledTypes) {
69         mEnabledTypes = Collections.unmodifiableSet(enabledTypes);
70     }
71 
72     /**
73      * Updates value in storage and forwards it to corresponding listeners for specific display.
74      * Should be called by OEM Plugin implementation in order to communicate with Framework
75      * @param type - plugin type, that need to be updated
76      * @param uniqueDisplayId - uniqueDisplayId that this type/value should be applied to
77      * @param value - plugin value for particular type and display
78      */
79     @KeepForApi
updateValue(PluginType<T> type, String uniqueDisplayId, @Nullable T value)80     public <T> void updateValue(PluginType<T> type, String uniqueDisplayId, @Nullable T value) {
81         if (isTypeDisabled(type)) {
82             Slog.d(TAG, "updateValue ignored for disabled type=" + type.mName);
83             return;
84         }
85         Slog.d(TAG, "updateValue, type=" + type.mName + "; value=" + value
86                 + "; displayId=" + uniqueDisplayId);
87         Set<PluginManager.PluginChangeListener<T>> localListeners;
88         T valueToNotify;
89         synchronized (mLock) {
90             ValuesContainer<T> valuesByType = getValuesContainerLocked(type);
91             valuesByType.updateValueLocked(uniqueDisplayId, value);
92             // if value was set to null, we might need to notify with GLOBAL value instead
93             valueToNotify = valuesByType.getValueLocked(uniqueDisplayId);
94 
95             PluginEventStorage storage = mPluginEventStorages.computeIfAbsent(uniqueDisplayId,
96                     d -> new PluginEventStorage());
97             storage.onValueUpdated(type);
98 
99             localListeners =  getListenersForUpdateLocked(type, uniqueDisplayId);
100         }
101         Slog.d(TAG, "updateValue, notifying listeners=" + localListeners);
102         localListeners.forEach(l -> l.onChanged(valueToNotify));
103     }
104 
105     @GuardedBy("mLock")
getListenersForUpdateLocked( PluginType<T> type, String uniqueDisplayId)106     private <T> Set<PluginManager.PluginChangeListener<T>> getListenersForUpdateLocked(
107             PluginType<T> type, String uniqueDisplayId) {
108         ListenersContainer<T> listenersContainer = getListenersContainerLocked(type);
109         Set<PluginManager.PluginChangeListener<T>> localListeners = new LinkedHashSet<>();
110         // if GLOBAL value change we need to notify only listeners for displays that does not
111         // have display specific value
112         if (GLOBAL_ID.equals(uniqueDisplayId)) {
113             ValuesContainer<T> valuesContainer = getValuesContainerLocked(type);
114             Set<String> excludedDisplayIds = valuesContainer.getNonGlobalDisplaysLocked();
115             listenersContainer.mListeners.forEach((localDisplayId, listeners) -> {
116                 if (!excludedDisplayIds.contains(localDisplayId)) {
117                     localListeners.addAll(listeners);
118                 }
119             });
120         } else {
121             localListeners.addAll(
122                     listenersContainer.mListeners.getOrDefault(uniqueDisplayId, Set.of()));
123         }
124         return localListeners;
125     }
126 
127     /**
128      * Adds listener for PluginType. If storage already has value for this type, listener will
129      * be notified immediately.
130      */
addListener(PluginType<T> type, String uniqueDisplayId, PluginManager.PluginChangeListener<T> listener)131     <T> void addListener(PluginType<T> type, String uniqueDisplayId,
132             PluginManager.PluginChangeListener<T> listener) {
133         if (isTypeDisabled(type)) {
134             Slog.d(TAG, "addListener ignored for disabled type=" + type.mName);
135             return;
136         }
137         if (GLOBAL_ID.equals(uniqueDisplayId)) {
138             Slog.d(TAG, "addListener ignored for GLOBAL_ID, type=" + type.mName);
139             return;
140         }
141         T value = null;
142         synchronized (mLock) {
143             ListenersContainer<T> container = getListenersContainerLocked(type);
144             if (container.addListenerLocked(uniqueDisplayId, listener)) {
145                 ValuesContainer<T> valuesContainer = getValuesContainerLocked(type);
146                 value = valuesContainer.getValueLocked(uniqueDisplayId);
147             }
148         }
149         if (value != null) {
150             listener.onChanged(value);
151         }
152     }
153 
154     /**
155      * Removes listener
156      */
removeListener(PluginType<T> type, String uniqueDisplayId, PluginManager.PluginChangeListener<T> listener)157     <T> void removeListener(PluginType<T> type, String uniqueDisplayId,
158             PluginManager.PluginChangeListener<T> listener) {
159         if (isTypeDisabled(type)) {
160             Slog.d(TAG, "removeListener ignored for disabled type=" + type.mName);
161             return;
162         }
163         if (GLOBAL_ID.equals(uniqueDisplayId)) {
164             Slog.d(TAG, "removeListener ignored for GLOBAL_ID, type=" + type.mName);
165             return;
166         }
167         synchronized (mLock) {
168             ListenersContainer<T> container = getListenersContainerLocked(type);
169             container.removeListenerLocked(uniqueDisplayId, listener);
170         }
171     }
172 
173     /**
174      * Print the object's state and debug information into the given stream.
175      */
dump(PrintWriter pw)176     void dump(PrintWriter pw) {
177         Map<PluginType<?>, Map<String, Object>> localValues = new HashMap<>();
178         @SuppressWarnings("rawtypes")
179         Map<PluginType, Map<String, Set>> localListeners = new HashMap<>();
180         Map<String, List<PluginEventStorage.TimeFrame>> timeFrames = new HashMap<>();
181         synchronized (mLock) {
182             mPluginEventStorages.forEach((displayId, storage) -> {
183                 timeFrames.put(displayId, storage.getTimeFrames());
184             });
185             mValues.forEach((type, valueContainer) -> {
186                 localValues.put(type, new HashMap<>(valueContainer.mValues));
187             });
188             mListeners.forEach((type, container) -> {
189                 localListeners.put(type, new HashMap<>(container.mListeners));
190             });
191         }
192         pw.println("PluginStorage:");
193         pw.println("values=" + localValues);
194         pw.println("listeners=" + localListeners);
195         pw.println("PluginEventStorage:");
196         for (Map.Entry<String, List<PluginEventStorage.TimeFrame>> timeFrameEntry :
197                 timeFrames.entrySet()) {
198             pw.println("TimeFrames for displayId=" + timeFrameEntry.getKey());
199             for (PluginEventStorage.TimeFrame timeFrame : timeFrameEntry.getValue()) {
200                 timeFrame.dump(pw);
201             }
202         }
203     }
204 
isTypeDisabled(PluginType<?> type)205     private boolean isTypeDisabled(PluginType<?> type) {
206         return !mEnabledTypes.contains(type);
207     }
208 
209     @GuardedBy("mLock")
210     @SuppressWarnings("unchecked")
getListenersContainerLocked(PluginType<T> type)211     private <T> ListenersContainer<T> getListenersContainerLocked(PluginType<T> type) {
212         ListenersContainer<?> container = mListeners.get(type);
213         if (container == null) {
214             ListenersContainer<T> lc = new ListenersContainer<>();
215             mListeners.put(type, lc);
216             return lc;
217         } else {
218             return (ListenersContainer<T>) container;
219         }
220     }
221 
222     @GuardedBy("mLock")
223     @SuppressWarnings("unchecked")
getValuesContainerLocked(PluginType<T> type)224     private <T> ValuesContainer<T> getValuesContainerLocked(PluginType<T> type) {
225         ValuesContainer<?> container = mValues.get(type);
226         if (container == null) {
227             ValuesContainer<T> vc = new ValuesContainer<>();
228             mValues.put(type, vc);
229             return vc;
230         } else {
231             return (ValuesContainer<T>) container;
232         }
233     }
234 
235     private static final class ListenersContainer<T> {
236         private final Map<String, Set<PluginManager.PluginChangeListener<T>>> mListeners =
237                 new LinkedHashMap<>();
238 
addListenerLocked( String uniqueDisplayId, PluginManager.PluginChangeListener<T> listener)239         private boolean addListenerLocked(
240                 String uniqueDisplayId, PluginManager.PluginChangeListener<T> listener) {
241             Set<PluginManager.PluginChangeListener<T>> listenersForDisplay =
242                     mListeners.computeIfAbsent(uniqueDisplayId, k -> new LinkedHashSet<>());
243             return listenersForDisplay.add(listener);
244         }
245 
removeListenerLocked(String uniqueDisplayId, PluginManager.PluginChangeListener<T> listener)246         private void removeListenerLocked(String uniqueDisplayId,
247                 PluginManager.PluginChangeListener<T> listener) {
248             Set<PluginManager.PluginChangeListener<T>> listenersForDisplay = mListeners.get(
249                     uniqueDisplayId);
250             if (listenersForDisplay == null) {
251                 return;
252             }
253 
254             listenersForDisplay.remove(listener);
255 
256             if (listenersForDisplay.isEmpty()) {
257                 mListeners.remove(uniqueDisplayId);
258             }
259         }
260     }
261 
262     private static final class ValuesContainer<T> {
263         private final Map<String, T> mValues = new HashMap<>();
264 
updateValueLocked(String uniqueDisplayId, @Nullable T value)265         private void updateValueLocked(String uniqueDisplayId, @Nullable T value) {
266             if (value == null) {
267                 mValues.remove(uniqueDisplayId);
268             } else {
269                 mValues.put(uniqueDisplayId, value);
270             }
271         }
272 
getNonGlobalDisplaysLocked()273         private Set<String> getNonGlobalDisplaysLocked() {
274             Set<String> keys = new HashSet<>(mValues.keySet());
275             keys.remove(GLOBAL_ID);
276             return keys;
277         }
278 
getValueLocked(String displayId)279         private @Nullable T getValueLocked(String displayId) {
280             return mValues.getOrDefault(displayId, mValues.get(GLOBAL_ID));
281         }
282     }
283 }
284