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