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