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; 18 19 import static android.hardware.display.DisplayTopology.pxToDp; 20 21 import android.hardware.display.DisplayTopology; 22 import android.hardware.display.DisplayTopologyGraph; 23 import android.util.Pair; 24 import android.util.Slog; 25 import android.util.SparseArray; 26 import android.util.SparseIntArray; 27 import android.view.Display; 28 import android.view.DisplayInfo; 29 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.internal.annotations.VisibleForTesting; 32 33 import java.io.PrintWriter; 34 import java.util.HashMap; 35 import java.util.Map; 36 import java.util.concurrent.Executor; 37 import java.util.function.BooleanSupplier; 38 import java.util.function.Consumer; 39 40 /** 41 * Manages the relative placement (topology) of extended displays. Responsible for updating and 42 * persisting the topology. 43 */ 44 class DisplayTopologyCoordinator { 45 private static final String TAG = "DisplayTopologyCoordinator"; 46 getUniqueId(DisplayInfo info)47 private static String getUniqueId(DisplayInfo info) { 48 if (info.displayId == Display.DEFAULT_DISPLAY && info.type == Display.TYPE_INTERNAL) { 49 return "internal"; 50 } 51 return info.uniqueId; 52 } 53 54 // Persistent data store for display topologies. 55 private final DisplayTopologyStore mTopologyStore; 56 57 @GuardedBy("mSyncRoot") 58 private DisplayTopology mTopology; 59 60 // Map from logical display ID to logical display density. Should always be consistent with 61 // mTopology. 62 @GuardedBy("mSyncRoot") 63 private final SparseIntArray mDensities = new SparseIntArray(); 64 65 @GuardedBy("mSyncRoot") 66 private final Map<String, Integer> mUniqueIdToDisplayIdMapping = new HashMap<>(); 67 68 @GuardedBy("mSyncRoot") 69 private final SparseArray<String> mDisplayIdToUniqueIdMapping = new SparseArray<>(); 70 71 /** 72 * Check if extended displays are allowed. If not, a topology is not needed. 73 */ 74 private final BooleanSupplier mIsExtendedDisplayAllowed; 75 76 /** 77 * Callback used to send topology updates. 78 * Should be invoked from the corresponding executor. 79 * A copy of the topology should be sent that will not be modified by the system. 80 */ 81 private final Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> mOnTopologyChangedCallback; 82 private final Executor mTopologyChangeExecutor; 83 private final DisplayManagerService.SyncRoot mSyncRoot; 84 private final Runnable mTopologySavedCallback; 85 DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayAllowed, Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback, Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot, Runnable topologySavedCallback)86 DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayAllowed, 87 Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback, 88 Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot, 89 Runnable topologySavedCallback) { 90 this(new Injector(), isExtendedDisplayAllowed, onTopologyChangedCallback, 91 topologyChangeExecutor, syncRoot, topologySavedCallback); 92 } 93 94 @VisibleForTesting DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayAllowed, Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback, Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot, Runnable topologySavedCallback)95 DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayAllowed, 96 Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback, 97 Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot, 98 Runnable topologySavedCallback) { 99 mTopology = injector.getTopology(); 100 mIsExtendedDisplayAllowed = isExtendedDisplayAllowed; 101 mOnTopologyChangedCallback = onTopologyChangedCallback; 102 mTopologyChangeExecutor = topologyChangeExecutor; 103 mSyncRoot = syncRoot; 104 mTopologyStore = injector.createTopologyStore( 105 mDisplayIdToUniqueIdMapping, mUniqueIdToDisplayIdMapping); 106 mTopologySavedCallback = topologySavedCallback; 107 } 108 109 /** 110 * Add a display to the topology. 111 * @param info The display info 112 */ onDisplayAdded(DisplayInfo info)113 void onDisplayAdded(DisplayInfo info) { 114 if (!isDisplayAllowedInTopology(info, /* shouldLog= */ true)) { 115 return; 116 } 117 synchronized (mSyncRoot) { 118 addDisplayIdMappingLocked(info); 119 mDensities.put(info.displayId, info.logicalDensityDpi); 120 mTopology.addDisplay(info.displayId, getWidth(info), getHeight(info)); 121 Slog.i(TAG, "Display " + info.displayId + " added, new topology: " + mTopology); 122 restoreTopologyLocked(); 123 sendTopologyUpdateLocked(); 124 } 125 } 126 127 /** 128 * Update the topology with display changes. 129 * @param info The new display info 130 */ onDisplayChanged(DisplayInfo info)131 void onDisplayChanged(DisplayInfo info) { 132 if (!isDisplayAllowedInTopology(info, /* shouldLog= */ false)) { 133 return; 134 } 135 synchronized (mSyncRoot) { 136 if (mDensities.indexOfKey(info.displayId) >= 0) { 137 mDensities.put(info.displayId, info.logicalDensityDpi); 138 } 139 if (mTopology.updateDisplay(info.displayId, getWidth(info), getHeight(info))) { 140 sendTopologyUpdateLocked(); 141 } 142 } 143 } 144 145 /** 146 * Remove a display from the topology. 147 * @param displayId The logical display ID 148 */ onDisplayRemoved(int displayId)149 void onDisplayRemoved(int displayId) { 150 synchronized (mSyncRoot) { 151 mDensities.delete(displayId); 152 if (mTopology.removeDisplay(displayId)) { 153 Slog.i(TAG, "Display " + displayId + " removed, new topology: " + mTopology); 154 removeDisplayIdMappingLocked(displayId); 155 restoreTopologyLocked(); 156 sendTopologyUpdateLocked(); 157 } 158 } 159 } 160 161 /** 162 * Loads all topologies from the persistent topology store for the given userId. 163 * @param userId the user id, same as returned from 164 * {@link android.app.ActivityManagerInternal#getCurrentUserId()}. 165 * @param isUserSwitching whether the id of the user is currently switching. 166 */ reloadTopologies(int userId, boolean isUserSwitching)167 void reloadTopologies(int userId, boolean isUserSwitching) { 168 boolean isTopologySaved = false; 169 synchronized (mSyncRoot) { 170 mTopologyStore.reloadTopologies(userId); 171 boolean isTopologyRestored = restoreTopologyLocked(); 172 if (isTopologyRestored) { 173 sendTopologyUpdateLocked(); 174 } 175 if (isUserSwitching && !isTopologyRestored) { 176 // During user switch, if topology is not restored - last user topology is the 177 // good initial guess. Save this topology for consistent use in the future. 178 isTopologySaved = mTopologyStore.saveTopology(mTopology); 179 } 180 } 181 182 if (isTopologySaved) { 183 mTopologySavedCallback.run(); 184 } 185 } 186 187 /** 188 * @return A deep copy of the topology. 189 */ getTopology()190 DisplayTopology getTopology() { 191 synchronized (mSyncRoot) { 192 return mTopology.copy(); 193 } 194 } 195 setTopology(DisplayTopology topology)196 void setTopology(DisplayTopology topology) { 197 final boolean isTopologySaved; 198 synchronized (mSyncRoot) { 199 topology.normalize(); 200 mTopology = topology; 201 sendTopologyUpdateLocked(); 202 isTopologySaved = mTopologyStore.saveTopology(topology); 203 } 204 205 if (isTopologySaved) { 206 mTopologySavedCallback.run(); 207 } 208 } 209 210 /** 211 * Print the object's state and debug information into the given stream. 212 * @param pw The stream to dump information to. 213 */ dump(PrintWriter pw)214 void dump(PrintWriter pw) { 215 synchronized (mSyncRoot) { 216 mTopology.dump(pw); 217 } 218 } 219 220 @GuardedBy("mSyncRoot") removeDisplayIdMappingLocked(final int displayId)221 private void removeDisplayIdMappingLocked(final int displayId) { 222 final String uniqueId = mDisplayIdToUniqueIdMapping.get(displayId); 223 if (null == uniqueId) { 224 Slog.e(TAG, "Can't find uniqueId for displayId=" + displayId); 225 return; 226 } 227 mDisplayIdToUniqueIdMapping.remove(displayId); 228 mUniqueIdToDisplayIdMapping.remove(uniqueId); 229 } 230 231 @GuardedBy("mSyncRoot") addDisplayIdMappingLocked(DisplayInfo info)232 private void addDisplayIdMappingLocked(DisplayInfo info) { 233 final String uniqueId = getUniqueId(info); 234 mUniqueIdToDisplayIdMapping.put(uniqueId, info.displayId); 235 mDisplayIdToUniqueIdMapping.put(info.displayId, uniqueId); 236 } 237 238 /** 239 * @param info The display info 240 * @return The width of the display in dp 241 */ getWidth(DisplayInfo info)242 private float getWidth(DisplayInfo info) { 243 return pxToDp(info.logicalWidth, info.logicalDensityDpi); 244 } 245 246 /** 247 * @param info The display info 248 * @return The height of the display in dp 249 */ getHeight(DisplayInfo info)250 private float getHeight(DisplayInfo info) { 251 return pxToDp(info.logicalHeight, info.logicalDensityDpi); 252 } 253 isDisplayAllowedInTopology(DisplayInfo info, boolean shouldLog)254 private boolean isDisplayAllowedInTopology(DisplayInfo info, boolean shouldLog) { 255 if (info.type != Display.TYPE_INTERNAL && info.type != Display.TYPE_EXTERNAL 256 && info.type != Display.TYPE_OVERLAY) { 257 if (shouldLog) { 258 Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because " 259 + "type is not INTERNAL, EXTERNAL or OVERLAY"); 260 } 261 return false; 262 } 263 if (info.type == Display.TYPE_INTERNAL && info.displayId != Display.DEFAULT_DISPLAY) { 264 if (shouldLog) { 265 Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because " 266 + "it is a non-default internal display"); 267 } 268 return false; 269 } 270 if ((info.type == Display.TYPE_EXTERNAL || info.type == Display.TYPE_OVERLAY) 271 && !mIsExtendedDisplayAllowed.getAsBoolean()) { 272 if (shouldLog) { 273 Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because " 274 + "type is EXTERNAL or OVERLAY and !mIsExtendedDisplayAllowed"); 275 } 276 return false; 277 } 278 return true; 279 } 280 281 /** 282 * Restores {@link #mTopology} from {@link #mTopologyStore}, saves it in {@link #mTopology}. 283 * @return true if the topology was restored, false otherwise. 284 */ 285 @GuardedBy("mSyncRoot") restoreTopologyLocked()286 private boolean restoreTopologyLocked() { 287 var restoredTopology = mTopologyStore.restoreTopology(mTopology); 288 if (restoredTopology == null) { 289 return false; 290 } 291 mTopology = restoredTopology; 292 mTopology.normalize(); 293 return true; 294 } 295 296 @GuardedBy("mSyncRoot") sendTopologyUpdateLocked()297 private void sendTopologyUpdateLocked() { 298 DisplayTopology copy = mTopology.copy(); 299 SparseIntArray densities = mDensities.clone(); 300 mTopologyChangeExecutor.execute(() -> mOnTopologyChangedCallback.accept( 301 new Pair<>(copy, copy.getGraph(densities)))); 302 } 303 304 @VisibleForTesting 305 static class Injector { getTopology()306 DisplayTopology getTopology() { 307 return new DisplayTopology(); 308 } 309 createTopologyStore( SparseArray<String> displayIdToUniqueIdMapping, Map<String, Integer> uniqueIdToDisplayIdMapping)310 DisplayTopologyStore createTopologyStore( 311 SparseArray<String> displayIdToUniqueIdMapping, 312 Map<String, Integer> uniqueIdToDisplayIdMapping) { 313 return new DisplayTopologyXmlStore(new DisplayTopologyXmlStore.Injector() { 314 @Override 315 public SparseArray<String> getDisplayIdToUniqueIdMapping() { 316 return displayIdToUniqueIdMapping; 317 } 318 319 @Override 320 public Map<String, Integer> getUniqueIdToDisplayIdMapping() { 321 return uniqueIdToDisplayIdMapping; 322 } 323 }); 324 } 325 } 326 } 327