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