1 package com.android.server.wm;
2 
3 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
4 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
5 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
6 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
7 import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
8 
9 import android.graphics.Rect;
10 import android.util.ArrayMap;
11 import android.util.Slog;
12 import android.util.TypedValue;
13 
14 import com.android.internal.annotations.VisibleForTesting;
15 import com.android.server.wm.DimLayer.DimLayerUser;
16 
17 import java.io.PrintWriter;
18 
19 /**
20  * Centralizes the control of dim layers used for
21  * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
22  * as well as other use cases (such as dimming above a dead window).
23  */
24 class DimLayerController {
25     private static final String TAG_LOCAL = "DimLayerController";
26     private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
27 
28     /** Amount of time in milliseconds to animate the dim surface from one value to another,
29      * when no window animation is driving it. */
30     private static final int DEFAULT_DIM_DURATION = 200;
31 
32     /**
33      * The default amount of dim applied over a dead window
34      */
35     private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
36 
37     // Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this
38     // instead of creating a new object per fullscreen task on a display.
39     private DimLayer mSharedFullScreenDimLayer;
40 
41     private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>();
42 
43     private DisplayContent mDisplayContent;
44 
45     private Rect mTmpBounds = new Rect();
46 
DimLayerController(DisplayContent displayContent)47     DimLayerController(DisplayContent displayContent) {
48         mDisplayContent = displayContent;
49     }
50 
51     /** Updates the dim layer bounds, recreating it if needed. */
updateDimLayer(DimLayer.DimLayerUser dimLayerUser)52     void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
53         final DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
54         final boolean previousFullscreen = state.dimLayer != null
55                 && state.dimLayer == mSharedFullScreenDimLayer;
56         DimLayer newDimLayer;
57         final int displayId = mDisplayContent.getDisplayId();
58         if (dimLayerUser.dimFullscreen()) {
59             if (previousFullscreen && mSharedFullScreenDimLayer != null) {
60                 // Update the bounds for fullscreen in case of rotation.
61                 mSharedFullScreenDimLayer.setBoundsForFullscreen();
62                 return;
63             }
64             // Use shared fullscreen dim layer
65             newDimLayer = mSharedFullScreenDimLayer;
66             if (newDimLayer == null) {
67                 if (state.dimLayer != null) {
68                     // Re-purpose the previous dim layer.
69                     newDimLayer = state.dimLayer;
70                 } else {
71                     // Create new full screen dim layer.
72                     newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
73                             getDimLayerTag(dimLayerUser));
74                 }
75                 dimLayerUser.getDimBounds(mTmpBounds);
76                 newDimLayer.setBounds(mTmpBounds);
77                 mSharedFullScreenDimLayer = newDimLayer;
78             } else if (state.dimLayer != null) {
79                 state.dimLayer.destroySurface();
80             }
81         } else {
82             newDimLayer = (state.dimLayer == null || previousFullscreen)
83                     ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
84                             getDimLayerTag(dimLayerUser))
85                     : state.dimLayer;
86             dimLayerUser.getDimBounds(mTmpBounds);
87             newDimLayer.setBounds(mTmpBounds);
88         }
89         state.dimLayer = newDimLayer;
90     }
91 
getDimLayerTag(DimLayerUser dimLayerUser)92     private static String getDimLayerTag(DimLayerUser dimLayerUser) {
93         return TAG_LOCAL + "/" + dimLayerUser.toShortString();
94     }
95 
getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser)96     private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) {
97         if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
98                 + dimLayerUser.toShortString());
99         DimLayerState state = mState.get(dimLayerUser);
100         if (state == null) {
101             state = new DimLayerState();
102             mState.put(dimLayerUser, state);
103         }
104         return state;
105     }
106 
setContinueDimming(DimLayer.DimLayerUser dimLayerUser)107     private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
108         DimLayerState state = mState.get(dimLayerUser);
109         if (state == null) {
110             if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: "
111                     + dimLayerUser.toShortString());
112             return;
113         }
114         state.continueDimming = true;
115     }
116 
isDimming()117     boolean isDimming() {
118         for (int i = mState.size() - 1; i >= 0; i--) {
119             DimLayerState state = mState.valueAt(i);
120             if (state.dimLayer != null && state.dimLayer.isDimming()) {
121                 return true;
122             }
123         }
124         return false;
125     }
126 
resetDimming()127     void resetDimming() {
128         for (int i = mState.size() - 1; i >= 0; i--) {
129             mState.valueAt(i).continueDimming = false;
130         }
131     }
132 
getContinueDimming(DimLayer.DimLayerUser dimLayerUser)133     private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
134         DimLayerState state = mState.get(dimLayerUser);
135         return state != null && state.continueDimming;
136     }
137 
startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator newWinAnimator, boolean aboveApp)138     void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
139             WindowStateAnimator newWinAnimator, boolean aboveApp) {
140         // Only set dim params on the highest dimmed layer.
141         // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
142         DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
143         state.dimAbove = aboveApp;
144         if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
145                 + " dimLayerUser=" + dimLayerUser.toShortString()
146                 + " newWinAnimator=" + newWinAnimator
147                 + " state.animator=" + state.animator);
148         if (newWinAnimator.getShown() && (state.animator == null
149                 || !state.animator.getShown()
150                 || state.animator.mAnimLayer <= newWinAnimator.mAnimLayer)) {
151             state.animator = newWinAnimator;
152             if (state.animator.mWin.mAppToken == null && !dimLayerUser.dimFullscreen()) {
153                 // Dim should cover the entire screen for system windows.
154                 mDisplayContent.getLogicalDisplayRect(mTmpBounds);
155             } else {
156                 dimLayerUser.getDimBounds(mTmpBounds);
157             }
158             state.dimLayer.setBounds(mTmpBounds);
159         }
160     }
161 
stopDimmingIfNeeded()162     void stopDimmingIfNeeded() {
163         if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded, mState.size()=" + mState.size());
164         for (int i = mState.size() - 1; i >= 0; i--) {
165             DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i);
166             stopDimmingIfNeeded(dimLayerUser);
167         }
168     }
169 
stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser)170     private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) {
171         // No need to check if state is null, we know the key has a value.
172         DimLayerState state = mState.get(dimLayerUser);
173         if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
174                 + " dimLayerUser=" + dimLayerUser.toShortString()
175                 + " state.continueDimming=" + state.continueDimming
176                 + " state.dimLayer.isDimming=" + state.dimLayer.isDimming());
177         if (state.animator != null && state.animator.mWin.mWillReplaceWindow) {
178             return;
179         }
180 
181         if (!state.continueDimming && state.dimLayer.isDimming()) {
182             state.animator = null;
183             dimLayerUser.getDimBounds(mTmpBounds);
184             state.dimLayer.setBounds(mTmpBounds);
185         }
186     }
187 
animateDimLayers()188     boolean animateDimLayers() {
189         int fullScreen = -1;
190         int fullScreenAndDimming = -1;
191         int topFullScreenUserLayer = 0;
192         boolean result = false;
193 
194         for (int i = mState.size() - 1; i >= 0; i--) {
195             final DimLayer.DimLayerUser user = mState.keyAt(i);
196             final DimLayerState state = mState.valueAt(i);
197 
198             if (!user.isAttachedToDisplay()) {
199                 // Leaked dim user that is no longer attached to the display. Go ahead and clean it
200                 // clean-up and log what happened.
201                 // TODO: This is a work around for b/34395537 as the dim user should have cleaned-up
202                 // it self when it was detached from the display. Need to investigate how the dim
203                 // user is leaking...
204                 //Slog.wtfStack(TAG_WM, "Leaked dim user=" + user.toShortString()
205                 //        + " state=" + state);
206                 Slog.w(TAG_WM, "Leaked dim user=" + user.toShortString() + " state=" + state);
207                 removeDimLayerUser(user);
208                 continue;
209             }
210 
211             // We have to check that we are actually the shared fullscreen layer
212             // for this path. If we began as non fullscreen and became fullscreen
213             // (e.g. Docked stack closing), then we may not be the shared layer
214             // and we have to make sure we always animate the layer.
215             if (user.dimFullscreen() && state.dimLayer == mSharedFullScreenDimLayer) {
216                 fullScreen = i;
217                 if (!state.continueDimming) {
218                     continue;
219                 }
220 
221                 // When choosing which user to assign the shared fullscreen layer to
222                 // we need to look at Z-order.
223                 if (topFullScreenUserLayer == 0 ||
224                         (state.animator != null && state.animator.mAnimLayer > topFullScreenUserLayer)) {
225                     fullScreenAndDimming = i;
226                     if (state.animator != null) {
227                         topFullScreenUserLayer = state.animator.mAnimLayer;
228                     }
229                 }
230             } else {
231                 // We always want to animate the non fullscreen windows, they don't share their
232                 // dim layers.
233                 result |= animateDimLayers(user);
234             }
235         }
236         // For the shared, full screen dim layer, we prefer the animation that is causing it to
237         // appear.
238         if (fullScreenAndDimming != -1) {
239             result |= animateDimLayers(mState.keyAt(fullScreenAndDimming));
240         } else if (fullScreen != -1) {
241             // If there is no animation for the full screen dim layer to appear, we can use any of
242             // the animators that will cause it to disappear.
243             result |= animateDimLayers(mState.keyAt(fullScreen));
244         }
245         return result;
246     }
247 
animateDimLayers(DimLayer.DimLayerUser dimLayerUser)248     private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
249         DimLayerState state = mState.get(dimLayerUser);
250         if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
251                 + " dimLayerUser=" + dimLayerUser.toShortString()
252                 + " state.animator=" + state.animator
253                 + " state.continueDimming=" + state.continueDimming);
254         final int dimLayer;
255         final float dimAmount;
256         if (state.animator == null) {
257             dimLayer = state.dimLayer.getLayer();
258             dimAmount = 0;
259         } else {
260             if (state.dimAbove) {
261                 dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
262                 dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
263             } else {
264                 dimLayer = state.animator.mAnimLayer - LAYER_OFFSET_DIM;
265                 dimAmount = state.animator.mWin.mAttrs.dimAmount;
266             }
267         }
268         final float targetAlpha = state.dimLayer.getTargetAlpha();
269         if (targetAlpha != dimAmount) {
270             if (state.animator == null) {
271                 state.dimLayer.hide(DEFAULT_DIM_DURATION);
272             } else {
273                 long duration = (state.animator.mAnimating && state.animator.mAnimation != null)
274                         ? state.animator.mAnimation.computeDurationHint()
275                         : DEFAULT_DIM_DURATION;
276                 if (targetAlpha > dimAmount) {
277                     duration = getDimLayerFadeDuration(duration);
278                 }
279                 state.dimLayer.show(dimLayer, dimAmount, duration);
280 
281                 // If we showed a dim layer, make sure to redo the layout because some things depend
282                 // on whether a dim layer is showing or not.
283                 if (targetAlpha == 0) {
284                     mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
285                     mDisplayContent.setLayoutNeeded();
286                 }
287             }
288         } else if (state.dimLayer.getLayer() != dimLayer) {
289             state.dimLayer.setLayer(dimLayer);
290         }
291         if (state.dimLayer.isAnimating()) {
292             if (!mDisplayContent.mService.okToDisplay()) {
293                 // Jump to the end of the animation.
294                 state.dimLayer.show();
295             } else {
296                 return state.dimLayer.stepAnimation();
297             }
298         }
299         return false;
300     }
301 
isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator)302     boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
303         DimLayerState state = mState.get(dimLayerUser);
304         return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
305     }
306 
getDimLayerFadeDuration(long duration)307     private long getDimLayerFadeDuration(long duration) {
308         TypedValue tv = new TypedValue();
309         mDisplayContent.mService.mContext.getResources().getValue(
310                 com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
311         if (tv.type == TypedValue.TYPE_FRACTION) {
312             duration = (long) tv.getFraction(duration, duration);
313         } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
314             duration = tv.data;
315         }
316         return duration;
317     }
318 
close()319     void close() {
320         for (int i = mState.size() - 1; i >= 0; i--) {
321             DimLayerState state = mState.valueAt(i);
322             state.dimLayer.destroySurface();
323         }
324         mState.clear();
325         mSharedFullScreenDimLayer = null;
326     }
327 
removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser)328     void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
329         DimLayerState state = mState.get(dimLayerUser);
330         if (state != null) {
331             // Destroy the surface, unless it's the shared fullscreen dim.
332             if (state.dimLayer != mSharedFullScreenDimLayer) {
333                 state.dimLayer.destroySurface();
334             }
335             mState.remove(dimLayerUser);
336         }
337         if (mState.isEmpty()) {
338             mSharedFullScreenDimLayer = null;
339         }
340     }
341 
342     @VisibleForTesting
hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser)343     boolean hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
344         return mState.containsKey(dimLayerUser);
345     }
346 
347     @VisibleForTesting
hasSharedFullScreenDimLayer()348     boolean hasSharedFullScreenDimLayer() {
349         return mSharedFullScreenDimLayer != null;
350     }
351 
applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator)352     void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
353         applyDim(dimLayerUser, animator, false /* aboveApp */);
354     }
355 
applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator)356     void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
357         applyDim(dimLayerUser, animator, true /* aboveApp */);
358     }
359 
applyDim( DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp)360     void applyDim(
361             DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
362         if (dimLayerUser == null) {
363             Slog.e(TAG, "Trying to apply dim layer for: " + this
364                     + ", but no dim layer user found.");
365             return;
366         }
367         if (!getContinueDimming(dimLayerUser)) {
368             setContinueDimming(dimLayerUser);
369             if (!isDimming(dimLayerUser, animator)) {
370                 if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
371                 startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
372             }
373         }
374     }
375 
376     private static class DimLayerState {
377         // The particular window requesting a dim layer. If null, hide dimLayer.
378         WindowStateAnimator animator;
379         // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
380         // end then stop any dimming.
381         boolean continueDimming;
382         DimLayer dimLayer;
383         boolean dimAbove;
384     }
385 
dump(String prefix, PrintWriter pw)386     void dump(String prefix, PrintWriter pw) {
387         pw.println(prefix + "DimLayerController");
388         final String doubleSpace = "  ";
389         final String prefixPlusDoubleSpace = prefix + doubleSpace;
390 
391         for (int i = 0, n = mState.size(); i < n; i++) {
392             pw.println(prefixPlusDoubleSpace + mState.keyAt(i).toShortString());
393             DimLayerState state = mState.valueAt(i);
394             pw.println(prefixPlusDoubleSpace + doubleSpace + "dimLayer="
395                     + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" : state.dimLayer)
396                     + ", animator=" + state.animator + ", continueDimming=" + state.continueDimming);
397             if (state.dimLayer != null) {
398                 state.dimLayer.printTo(prefixPlusDoubleSpace + doubleSpace, pw);
399             }
400         }
401     }
402 }
403