1 /*
2  * Copyright (C) 2023 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.wm;
18 
19 import static android.view.WindowManager.TRANSIT_CHANGE;
20 
21 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS;
22 import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
23 import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS;
24 import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.graphics.Rect;
29 import android.os.Message;
30 import android.os.Trace;
31 import android.util.Slog;
32 import android.view.DisplayInfo;
33 import android.window.DisplayAreaInfo;
34 import android.window.TransitionRequestInfo;
35 import android.window.WindowContainerTransaction;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.internal.display.BrightnessSynchronizer;
39 import com.android.internal.protolog.common.ProtoLog;
40 import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater;
41 import com.android.window.flags.Flags;
42 
43 import java.util.Arrays;
44 import java.util.Objects;
45 
46 /**
47  * A DisplayUpdater that could defer and queue display updates coming from DisplayManager to
48  * WindowManager. It allows to defer pending display updates if WindowManager is currently not
49  * ready to apply them.
50  * For example, this might happen if there is a Shell transition running and physical display
51  * changed. We can't immediately apply the display updates because we want to start a separate
52  * display change transition. In this case, we will queue all display updates until the current
53  * transition's collection finishes and then apply them afterwards.
54  */
55 public class DeferredDisplayUpdater implements DisplayUpdater {
56 
57     /**
58      * List of fields that could be deferred before applying to DisplayContent.
59      * This should be kept in sync with {@link DeferredDisplayUpdater#calculateDisplayInfoDiff}
60      */
61     @VisibleForTesting
62     static final DisplayInfoFieldsUpdater DEFERRABLE_FIELDS = (out, override) -> {
63         // Treat unique id and address change as WM-specific display change as we re-query display
64         // settings and parameters based on it which could cause window changes
65         out.uniqueId = override.uniqueId;
66         out.address = override.address;
67 
68         // Also apply WM-override fields, since they might produce differences in window hierarchy
69         WM_OVERRIDE_FIELDS.setFields(out, override);
70     };
71 
72     private static final String TAG = "DeferredDisplayUpdater";
73 
74     private static final String TRACE_TAG_WAIT_FOR_TRANSITION =
75             "Screen unblock: wait for transition";
76     private static final int WAIT_FOR_TRANSITION_TIMEOUT = 1000;
77 
78     private final DisplayContent mDisplayContent;
79 
80     @NonNull
81     private final DisplayInfo mNonOverrideDisplayInfo = new DisplayInfo();
82 
83     /**
84      * The last known display parameters from DisplayManager, some WM-specific fields in this object
85      * might not be applied to the DisplayContent yet
86      */
87     @Nullable
88     private DisplayInfo mLastDisplayInfo;
89 
90     /**
91      * The last DisplayInfo that was applied to DisplayContent, only WM-specific parameters must be
92      * used from this object. This object is used to store old values of DisplayInfo while these
93      * fields are pending to be applied to DisplayContent.
94      */
95     @Nullable
96     private DisplayInfo mLastWmDisplayInfo;
97 
98     @NonNull
99     private final DisplayInfo mOutputDisplayInfo = new DisplayInfo();
100 
101     /** Whether {@link #mScreenUnblocker} should wait for transition to be ready. */
102     private boolean mShouldWaitForTransitionWhenScreenOn;
103 
104     /** The message to notify PhoneWindowManager#finishWindowsDrawn. */
105     @Nullable
106     private Message mScreenUnblocker;
107 
108     private final Runnable mScreenUnblockTimeoutRunnable = () -> {
109         Slog.e(TAG, "Timeout waiting for the display switch transition to start");
110         continueScreenUnblocking();
111     };
112 
DeferredDisplayUpdater(@onNull DisplayContent displayContent)113     public DeferredDisplayUpdater(@NonNull DisplayContent displayContent) {
114         mDisplayContent = displayContent;
115         mNonOverrideDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
116     }
117 
118     /**
119      * Reads the latest display parameters from the display manager and returns them in a callback.
120      * If there are pending display updates, it will wait for them to finish first and only then it
121      * will call the callback with the latest display parameters.
122      *
123      * @param finishCallback is called when all pending display updates are finished
124      */
125     @Override
updateDisplayInfo(@onNull Runnable finishCallback)126     public void updateDisplayInfo(@NonNull Runnable finishCallback) {
127         // Get the latest display parameters from the DisplayManager
128         final DisplayInfo displayInfo = getCurrentDisplayInfo();
129 
130         final int displayInfoDiff = calculateDisplayInfoDiff(mLastDisplayInfo, displayInfo);
131         final boolean physicalDisplayUpdated = isPhysicalDisplayUpdated(mLastDisplayInfo,
132                 displayInfo);
133 
134         mLastDisplayInfo = displayInfo;
135 
136         // Apply whole display info immediately as is if either:
137         // * it is the first display update
138         // * the display doesn't have visible content
139         // * shell transitions are disabled or temporary unavailable
140         if (displayInfoDiff == DIFF_EVERYTHING
141                 || !mDisplayContent.getLastHasContent()
142                 || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
143             ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
144                     "DeferredDisplayUpdater: applying DisplayInfo immediately");
145 
146             mLastWmDisplayInfo = displayInfo;
147             applyLatestDisplayInfo();
148             finishCallback.run();
149             return;
150         }
151 
152         // If there are non WM-specific display info changes, apply only these fields immediately
153         if ((displayInfoDiff & DIFF_NOT_WM_DEFERRABLE) > 0) {
154             ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
155                     "DeferredDisplayUpdater: partially applying DisplayInfo immediately");
156             applyLatestDisplayInfo();
157         }
158 
159         // If there are WM-specific display info changes, apply them through a Shell transition
160         if ((displayInfoDiff & DIFF_WM_DEFERRABLE) > 0) {
161             ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
162                     "DeferredDisplayUpdater: deferring DisplayInfo update");
163 
164             requestDisplayChangeTransition(physicalDisplayUpdated, () -> {
165                 // Apply deferrable fields to DisplayContent only when the transition
166                 // starts collecting, non-deferrable fields are ignored in mLastWmDisplayInfo
167                 mLastWmDisplayInfo = displayInfo;
168                 applyLatestDisplayInfo();
169                 finishCallback.run();
170             });
171         } else {
172             // There are no WM-specific updates, so we can immediately notify that all display
173             // info changes are applied
174             finishCallback.run();
175         }
176     }
177 
178     /**
179      * Requests a display change Shell transition
180      *
181      * @param physicalDisplayUpdated if true also starts remote display change
182      * @param onStartCollect         called when the Shell transition starts collecting
183      */
requestDisplayChangeTransition(boolean physicalDisplayUpdated, @NonNull Runnable onStartCollect)184     private void requestDisplayChangeTransition(boolean physicalDisplayUpdated,
185             @NonNull Runnable onStartCollect) {
186 
187         final Transition transition = new Transition(TRANSIT_CHANGE, /* flags= */ 0,
188                 mDisplayContent.mTransitionController,
189                 mDisplayContent.mTransitionController.mSyncEngine);
190 
191         mDisplayContent.mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
192 
193         mDisplayContent.mTransitionController.startCollectOrQueue(transition, deferred -> {
194             final Rect startBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
195                     mDisplayContent.mInitialDisplayHeight);
196             final int fromRotation = mDisplayContent.getRotation();
197             if (Flags.blastSyncNotificationShadeOnDisplaySwitch() && physicalDisplayUpdated) {
198                 final WindowState notificationShade =
199                         mDisplayContent.getDisplayPolicy().getNotificationShade();
200                 if (notificationShade != null && notificationShade.isVisible()
201                         && mDisplayContent.mAtmService.mKeyguardController.isKeyguardOrAodShowing(
202                                 mDisplayContent.mDisplayId)) {
203                     Slog.i(TAG, notificationShade + " uses blast for display switch");
204                     notificationShade.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
205                 }
206             }
207 
208             mDisplayContent.mAtmService.deferWindowLayout();
209             try {
210                 onStartCollect.run();
211 
212                 ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
213                         "DeferredDisplayUpdater: applied DisplayInfo after deferring");
214 
215                 if (physicalDisplayUpdated) {
216                     onDisplayUpdated(transition, fromRotation, startBounds);
217                 } else {
218                     final TransitionRequestInfo.DisplayChange displayChange =
219                             getCurrentDisplayChange(fromRotation, startBounds);
220                     mDisplayContent.mTransitionController.requestStartTransition(transition,
221                             /* startTask= */ null, /* remoteTransition= */ null, displayChange);
222                 }
223             } finally {
224                 // Run surface placement after requestStartTransition, so shell side can receive
225                 // the transition request before handling task info changes.
226                 mDisplayContent.mAtmService.continueWindowLayout();
227             }
228         });
229     }
230 
231     /**
232      * Applies current DisplayInfo to DisplayContent, DisplayContent is merged from two parts:
233      * - non-deferrable fields are set from the most recent values received from DisplayManager
234      * (uses {@link mLastDisplayInfo} field)
235      * - deferrable fields are set from the latest values that we could apply to WM
236      * (uses {@link mLastWmDisplayInfo} field)
237      */
applyLatestDisplayInfo()238     private void applyLatestDisplayInfo() {
239         copyDisplayInfoFields(mOutputDisplayInfo, /* base= */ mLastDisplayInfo,
240                 /* override= */ mLastWmDisplayInfo, /* fields= */ DEFERRABLE_FIELDS);
241         mDisplayContent.onDisplayInfoUpdated(mOutputDisplayInfo);
242     }
243 
244     @NonNull
getCurrentDisplayInfo()245     private DisplayInfo getCurrentDisplayInfo() {
246         mDisplayContent.mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo(
247                 mDisplayContent.mDisplayId, mNonOverrideDisplayInfo);
248         return new DisplayInfo(mNonOverrideDisplayInfo);
249     }
250 
251     @NonNull
getCurrentDisplayChange(int fromRotation, @NonNull Rect startBounds)252     private TransitionRequestInfo.DisplayChange getCurrentDisplayChange(int fromRotation,
253             @NonNull Rect startBounds) {
254         final Rect endBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
255                 mDisplayContent.mInitialDisplayHeight);
256         final int toRotation = mDisplayContent.getRotation();
257 
258         final TransitionRequestInfo.DisplayChange displayChange =
259                 new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId());
260         displayChange.setStartAbsBounds(startBounds);
261         displayChange.setEndAbsBounds(endBounds);
262         displayChange.setStartRotation(fromRotation);
263         displayChange.setEndRotation(toRotation);
264         return displayChange;
265     }
266 
267     /**
268      * Called when physical display is updated, this could happen e.g. on foldable
269      * devices when the physical underlying display is replaced. This method should be called
270      * when the new display info is already applied to the WM hierarchy.
271      *
272      * @param fromRotation rotation before the display change
273      * @param startBounds  display bounds before the display change
274      */
onDisplayUpdated(@onNull Transition transition, int fromRotation, @NonNull Rect startBounds)275     private void onDisplayUpdated(@NonNull Transition transition, int fromRotation,
276             @NonNull Rect startBounds) {
277         final int toRotation = mDisplayContent.getRotation();
278 
279         final TransitionRequestInfo.DisplayChange displayChange =
280                 getCurrentDisplayChange(fromRotation, startBounds);
281         displayChange.setPhysicalDisplayChanged(true);
282 
283         transition.addTransactionCompletedListener(this::continueScreenUnblocking);
284         mDisplayContent.mTransitionController.requestStartTransition(transition,
285                 /* startTask= */ null, /* remoteTransition= */ null, displayChange);
286 
287         final DisplayAreaInfo newDisplayAreaInfo = mDisplayContent.getDisplayAreaInfo();
288 
289         final boolean startedRemoteChange = mDisplayContent.mRemoteDisplayChangeController
290                 .performRemoteDisplayChange(fromRotation, toRotation, newDisplayAreaInfo,
291                         transaction -> finishDisplayUpdate(transaction, transition));
292 
293         if (!startedRemoteChange) {
294             finishDisplayUpdate(/* wct= */ null, transition);
295         }
296     }
297 
finishDisplayUpdate(@ullable WindowContainerTransaction wct, @NonNull Transition transition)298     private void finishDisplayUpdate(@Nullable WindowContainerTransaction wct,
299             @NonNull Transition transition) {
300         if (wct != null) {
301             mDisplayContent.mAtmService.mWindowOrganizerController.applyTransaction(
302                     wct);
303         }
304         transition.setAllReady();
305     }
306 
isPhysicalDisplayUpdated(@ullable DisplayInfo first, @Nullable DisplayInfo second)307     private boolean isPhysicalDisplayUpdated(@Nullable DisplayInfo first,
308             @Nullable DisplayInfo second) {
309         if (first == null || second == null) return true;
310         return !Objects.equals(first.uniqueId, second.uniqueId);
311     }
312 
313     @Override
onDisplayContentDisplayPropertiesPostChanged(int previousRotation, int newRotation, DisplayAreaInfo newDisplayAreaInfo)314     public void onDisplayContentDisplayPropertiesPostChanged(int previousRotation, int newRotation,
315             DisplayAreaInfo newDisplayAreaInfo) {
316         // Unblock immediately in case there is no transition. This is unlikely to happen.
317         if (mScreenUnblocker != null && !mDisplayContent.mTransitionController.inTransition()) {
318             mScreenUnblocker.sendToTarget();
319             mScreenUnblocker = null;
320         }
321     }
322 
323     @Override
onDisplaySwitching(boolean switching)324     public void onDisplaySwitching(boolean switching) {
325         mShouldWaitForTransitionWhenScreenOn = switching;
326     }
327 
328     @Override
waitForTransition(@onNull Message screenUnblocker)329     public boolean waitForTransition(@NonNull Message screenUnblocker) {
330         if (!Flags.waitForTransitionOnDisplaySwitch()) return false;
331         if (!mShouldWaitForTransitionWhenScreenOn) {
332             return false;
333         }
334         mScreenUnblocker = screenUnblocker;
335         if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
336             Trace.beginAsyncSection(TRACE_TAG_WAIT_FOR_TRANSITION, screenUnblocker.hashCode());
337         }
338 
339         mDisplayContent.mWmService.mH.removeCallbacks(mScreenUnblockTimeoutRunnable);
340         mDisplayContent.mWmService.mH.postDelayed(mScreenUnblockTimeoutRunnable,
341                 WAIT_FOR_TRANSITION_TIMEOUT);
342         return true;
343     }
344 
345     /**
346      * Continues the screen unblocking flow, could be called either on a binder thread as
347      * a result of surface transaction completed listener or from {@link WindowManagerService#mH}
348      * handler in case of timeout
349      */
continueScreenUnblocking()350     private void continueScreenUnblocking() {
351         synchronized (mDisplayContent.mWmService.mGlobalLock) {
352             mShouldWaitForTransitionWhenScreenOn = false;
353             mDisplayContent.mWmService.mH.removeCallbacks(mScreenUnblockTimeoutRunnable);
354             if (mScreenUnblocker == null) {
355                 return;
356             }
357             mScreenUnblocker.sendToTarget();
358             if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
359                 Trace.endAsyncSection(TRACE_TAG_WAIT_FOR_TRANSITION, mScreenUnblocker.hashCode());
360             }
361             mScreenUnblocker = null;
362         }
363     }
364 
365     /**
366      * Diff result: fields are the same
367      */
368     static final int DIFF_NONE = 0;
369 
370     /**
371      * Diff result: fields that could be deferred in WM are different
372      */
373     static final int DIFF_WM_DEFERRABLE = 1 << 0;
374 
375     /**
376      * Diff result: fields that could not be deferred in WM are different
377      */
378     static final int DIFF_NOT_WM_DEFERRABLE = 1 << 1;
379 
380     /**
381      * Diff result: everything is different
382      */
383     static final int DIFF_EVERYTHING = 0XFFFFFFFF;
384 
385     @VisibleForTesting
calculateDisplayInfoDiff(@ullable DisplayInfo first, @Nullable DisplayInfo second)386     static int calculateDisplayInfoDiff(@Nullable DisplayInfo first, @Nullable DisplayInfo second) {
387         int diff = DIFF_NONE;
388 
389         if (Objects.equals(first, second)) return diff;
390         if (first == null || second == null) return DIFF_EVERYTHING;
391 
392         if (first.layerStack != second.layerStack
393                 || first.flags != second.flags
394                 || first.type != second.type
395                 || first.displayId != second.displayId
396                 || first.displayGroupId != second.displayGroupId
397                 || !Objects.equals(first.deviceProductInfo, second.deviceProductInfo)
398                 || first.modeId != second.modeId
399                 || first.renderFrameRate != second.renderFrameRate
400                 || first.defaultModeId != second.defaultModeId
401                 || first.userPreferredModeId != second.userPreferredModeId
402                 || !Arrays.equals(first.supportedModes, second.supportedModes)
403                 || !Arrays.equals(first.appsSupportedModes, second.appsSupportedModes)
404                 || first.colorMode != second.colorMode
405                 || !Arrays.equals(first.supportedColorModes, second.supportedColorModes)
406                 || !Objects.equals(first.hdrCapabilities, second.hdrCapabilities)
407                 || !Arrays.equals(first.userDisabledHdrTypes, second.userDisabledHdrTypes)
408                 || first.minimalPostProcessingSupported != second.minimalPostProcessingSupported
409                 || first.appVsyncOffsetNanos != second.appVsyncOffsetNanos
410                 || first.presentationDeadlineNanos != second.presentationDeadlineNanos
411                 || first.state != second.state
412                 || first.committedState != second.committedState
413                 || first.ownerUid != second.ownerUid
414                 || !Objects.equals(first.ownerPackageName, second.ownerPackageName)
415                 || first.removeMode != second.removeMode
416                 || first.getRefreshRate() != second.getRefreshRate()
417                 || first.brightnessMinimum != second.brightnessMinimum
418                 || first.brightnessMaximum != second.brightnessMaximum
419                 || first.brightnessDefault != second.brightnessDefault
420                 || first.installOrientation != second.installOrientation
421                 || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate)
422                 || !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio)
423                 || !first.thermalRefreshRateThrottling.contentEquals(
424                 second.thermalRefreshRateThrottling)
425                 || !Objects.equals(first.thermalBrightnessThrottlingDataId,
426                 second.thermalBrightnessThrottlingDataId)) {
427             diff |= DIFF_NOT_WM_DEFERRABLE;
428         }
429 
430         if (first.appWidth != second.appWidth
431                 || first.appHeight != second.appHeight
432                 || first.smallestNominalAppWidth != second.smallestNominalAppWidth
433                 || first.smallestNominalAppHeight != second.smallestNominalAppHeight
434                 || first.largestNominalAppWidth != second.largestNominalAppWidth
435                 || first.largestNominalAppHeight != second.largestNominalAppHeight
436                 || first.logicalWidth != second.logicalWidth
437                 || first.logicalHeight != second.logicalHeight
438                 || first.physicalXDpi != second.physicalXDpi
439                 || first.physicalYDpi != second.physicalYDpi
440                 || first.rotation != second.rotation
441                 || !Objects.equals(first.displayCutout, second.displayCutout)
442                 || first.logicalDensityDpi != second.logicalDensityDpi
443                 || !Objects.equals(first.roundedCorners, second.roundedCorners)
444                 || !Objects.equals(first.displayShape, second.displayShape)
445                 || !Objects.equals(first.uniqueId, second.uniqueId)
446                 || !Objects.equals(first.address, second.address)
447         ) {
448             diff |= DIFF_WM_DEFERRABLE;
449         }
450 
451         return diff;
452     }
453 }
454