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