• 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;
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