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