DimLayerController.java revision 7393c7b3ec4230f2dc2caf4c6a8c25dfcf6a5b91
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            } else {
153                dimLayerUser.getDimBounds(mTmpBounds);
154            }
155            state.dimLayer.setBounds(mTmpBounds);
156        }
157    }
158
159    void stopDimmingIfNeeded() {
160        if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded, mState.size()=" + mState.size());
161        for (int i = mState.size() - 1; i >= 0; i--) {
162            DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i);
163            stopDimmingIfNeeded(dimLayerUser);
164        }
165    }
166
167    private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) {
168        // No need to check if state is null, we know the key has a value.
169        DimLayerState state = mState.get(dimLayerUser);
170        if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
171                + " dimLayerUser=" + dimLayerUser.toShortString()
172                + " state.continueDimming=" + state.continueDimming
173                + " state.dimLayer.isDimming=" + state.dimLayer.isDimming());
174        if (!state.continueDimming && state.dimLayer.isDimming()) {
175            state.animator = null;
176            dimLayerUser.getDimBounds(mTmpBounds);
177            state.dimLayer.setBounds(mTmpBounds);
178        }
179    }
180
181    boolean animateDimLayers() {
182        int fullScreen = -1;
183        int fullScreenAndDimming = -1;
184        boolean result = false;
185
186        for (int i = mState.size() - 1; i >= 0; i--) {
187            DimLayer.DimLayerUser user = mState.keyAt(i);
188            DimLayerState state = mState.valueAt(i);
189            // We have to check that we are acutally the shared fullscreen layer
190            // for this path. If we began as non fullscreen and became fullscreen
191            // (e.g. Docked stack closing), then we may not be the shared layer
192            // and we have to make sure we always animate the layer.
193            if (user.isFullscreen() && state.dimLayer == mSharedFullScreenDimLayer) {
194                fullScreen = i;
195                if (mState.valueAt(i).continueDimming) {
196                    fullScreenAndDimming = i;
197                }
198            } else {
199                // We always want to animate the non fullscreen windows, they don't share their
200                // dim layers.
201                result |= animateDimLayers(user);
202            }
203        }
204        // For the shared, full screen dim layer, we prefer the animation that is causing it to
205        // appear.
206        if (fullScreenAndDimming != -1) {
207            result |= animateDimLayers(mState.keyAt(fullScreenAndDimming));
208        } else if (fullScreen != -1) {
209            // If there is no animation for the full screen dim layer to appear, we can use any of
210            // the animators that will cause it to disappear.
211            result |= animateDimLayers(mState.keyAt(fullScreen));
212        }
213        return result;
214    }
215
216    private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
217        DimLayerState state = mState.get(dimLayerUser);
218        if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
219                + " dimLayerUser=" + dimLayerUser.toShortString()
220                + " state.animator=" + state.animator
221                + " state.continueDimming=" + state.continueDimming);
222        final int dimLayer;
223        final float dimAmount;
224        if (state.animator == null) {
225            dimLayer = state.dimLayer.getLayer();
226            dimAmount = 0;
227        } else {
228            if (state.dimAbove) {
229                dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
230                dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
231            } else {
232                dimLayer = state.animator.mAnimLayer - LAYER_OFFSET_DIM;
233                dimAmount = state.animator.mWin.mAttrs.dimAmount;
234            }
235        }
236        final float targetAlpha = state.dimLayer.getTargetAlpha();
237        if (targetAlpha != dimAmount) {
238            if (state.animator == null) {
239                state.dimLayer.hide(DEFAULT_DIM_DURATION);
240            } else {
241                long duration = (state.animator.mAnimating && state.animator.mAnimation != null)
242                        ? state.animator.mAnimation.computeDurationHint()
243                        : DEFAULT_DIM_DURATION;
244                if (targetAlpha > dimAmount) {
245                    duration = getDimLayerFadeDuration(duration);
246                }
247                state.dimLayer.show(dimLayer, dimAmount, duration);
248            }
249        } else if (state.dimLayer.getLayer() != dimLayer) {
250            state.dimLayer.setLayer(dimLayer);
251        }
252        if (state.dimLayer.isAnimating()) {
253            if (!mDisplayContent.mService.okToDisplay()) {
254                // Jump to the end of the animation.
255                state.dimLayer.show();
256            } else {
257                return state.dimLayer.stepAnimation();
258            }
259        }
260        return false;
261    }
262
263    boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
264        DimLayerState state = mState.get(dimLayerUser);
265        return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
266    }
267
268    private long getDimLayerFadeDuration(long duration) {
269        TypedValue tv = new TypedValue();
270        mDisplayContent.mService.mContext.getResources().getValue(
271                com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
272        if (tv.type == TypedValue.TYPE_FRACTION) {
273            duration = (long) tv.getFraction(duration, duration);
274        } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
275            duration = tv.data;
276        }
277        return duration;
278    }
279
280    void close() {
281        for (int i = mState.size() - 1; i >= 0; i--) {
282            DimLayerState state = mState.valueAt(i);
283            state.dimLayer.destroySurface();
284        }
285        mState.clear();
286        mSharedFullScreenDimLayer = null;
287    }
288
289    void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
290        DimLayerState state = mState.get(dimLayerUser);
291        if (state != null) {
292            // Destroy the surface, unless it's the shared fullscreen dim.
293            if (state.dimLayer != mSharedFullScreenDimLayer) {
294                state.dimLayer.destroySurface();
295            }
296            mState.remove(dimLayerUser);
297        }
298    }
299
300    void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
301        applyDim(dimLayerUser, animator, false /* aboveApp */);
302    }
303
304    void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
305        applyDim(dimLayerUser, animator, true /* aboveApp */);
306    }
307
308    private void applyDim(
309            DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
310        if (dimLayerUser == null) {
311            Slog.e(TAG, "Trying to apply dim layer for: " + this
312                    + ", but no dim layer user found.");
313            return;
314        }
315        if (!getContinueDimming(dimLayerUser)) {
316            setContinueDimming(dimLayerUser);
317            if (!isDimming(dimLayerUser, animator)) {
318                if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
319                startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
320            }
321        }
322    }
323
324    private static class DimLayerState {
325        // The particular window requesting a dim layer. If null, hide dimLayer.
326        WindowStateAnimator animator;
327        // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
328        // end then stop any dimming.
329        boolean continueDimming;
330        DimLayer dimLayer;
331        boolean dimAbove;
332    }
333
334    void dump(String prefix, PrintWriter pw) {
335        pw.println(prefix + "DimLayerController");
336        for (int i = 0, n = mState.size(); i < n; i++) {
337            pw.println(prefix + "  " + mState.keyAt(i).toShortString());
338            pw.print(prefix + "    ");
339            DimLayerState state = mState.valueAt(i);
340            pw.print("dimLayer=" + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" :
341                    state.dimLayer));
342            pw.print(", animator=" + state.animator);
343            pw.println(", continueDimming=" + state.continueDimming + "}");
344
345        }
346    }
347}
348