1package com.android.server.wm;
2
3import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
4import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
5import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
6import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
7import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
8
9import android.graphics.Rect;
10import android.util.ArrayMap;
11import android.util.Slog;
12import android.util.TypedValue;
13
14import com.android.internal.annotations.VisibleForTesting;
15import com.android.server.wm.DimLayer.DimLayerUser;
16
17import 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 */
24class 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
47    DimLayerController(DisplayContent displayContent) {
48        mDisplayContent = displayContent;
49    }
50
51    /** Updates the dim layer bounds, recreating it if needed. */
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
92    private static String getDimLayerTag(DimLayerUser dimLayerUser) {
93        return TAG_LOCAL + "/" + dimLayerUser.toShortString();
94    }
95
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
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
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
127    void resetDimming() {
128        for (int i = mState.size() - 1; i >= 0; i--) {
129            mState.valueAt(i).continueDimming = false;
130        }
131    }
132
133    private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
134        DimLayerState state = mState.get(dimLayerUser);
135        return state != null && state.continueDimming;
136    }
137
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
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
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
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
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
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
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
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
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
343    boolean hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
344        return mState.containsKey(dimLayerUser);
345    }
346
347    @VisibleForTesting
348    boolean hasSharedFullScreenDimLayer() {
349        return mSharedFullScreenDimLayer != null;
350    }
351
352    void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
353        applyDim(dimLayerUser, animator, false /* aboveApp */);
354    }
355
356    void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
357        applyDim(dimLayerUser, animator, true /* aboveApp */);
358    }
359
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
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