1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wm;
18
19import android.graphics.PixelFormat;
20import android.graphics.Rect;
21import android.os.SystemClock;
22import android.util.Slog;
23import android.view.DisplayInfo;
24import android.view.SurfaceControl;
25
26import java.io.PrintWriter;
27
28public class DimLayer {
29    private static final String TAG = "DimLayer";
30    private static final boolean DEBUG = false;
31
32    /** Reference to the owner of this object. */
33    final DisplayContent mDisplayContent;
34
35    /** Actual surface that dims */
36    SurfaceControl mDimSurface;
37
38    /** Last value passed to mDimSurface.setAlpha() */
39    float mAlpha = 0;
40
41    /** Last value passed to mDimSurface.setLayer() */
42    int mLayer = -1;
43
44    /** Next values to pass to mDimSurface.setPosition() and mDimSurface.setSize() */
45    Rect mBounds = new Rect();
46
47    /** Last values passed to mDimSurface.setPosition() and mDimSurface.setSize() */
48    Rect mLastBounds = new Rect();
49
50    /** True after mDimSurface.show() has been called, false after mDimSurface.hide(). */
51    private boolean mShowing = false;
52
53    /** Value of mAlpha when beginning transition to mTargetAlpha */
54    float mStartAlpha = 0;
55
56    /** Final value of mAlpha following transition */
57    float mTargetAlpha = 0;
58
59    /** Time in units of SystemClock.uptimeMillis() at which the current transition started */
60    long mStartTime;
61
62    /** Time in milliseconds to take to transition from mStartAlpha to mTargetAlpha */
63    long mDuration;
64
65    /** Owning stack */
66    final TaskStack mStack;
67
68    DimLayer(WindowManagerService service, TaskStack stack, DisplayContent displayContent) {
69        mStack = stack;
70        mDisplayContent = displayContent;
71        final int displayId = mDisplayContent.getDisplayId();
72        if (DEBUG) Slog.v(TAG, "Ctor: displayId=" + displayId);
73        SurfaceControl.openTransaction();
74        try {
75            if (WindowManagerService.DEBUG_SURFACE_TRACE) {
76                mDimSurface = new WindowStateAnimator.SurfaceTrace(service.mFxSession,
77                    "DimSurface",
78                    16, 16, PixelFormat.OPAQUE,
79                    SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
80            } else {
81                mDimSurface = new SurfaceControl(service.mFxSession, TAG,
82                    16, 16, PixelFormat.OPAQUE,
83                    SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
84            }
85            if (WindowManagerService.SHOW_TRANSACTIONS ||
86                    WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(TAG,
87                            "  DIM " + mDimSurface + ": CREATE");
88            mDimSurface.setLayerStack(displayId);
89        } catch (Exception e) {
90            Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e);
91        } finally {
92            SurfaceControl.closeTransaction();
93        }
94    }
95
96    /** Return true if dim layer is showing */
97    boolean isDimming() {
98        return mTargetAlpha != 0;
99    }
100
101    /** Return true if in a transition period */
102    boolean isAnimating() {
103        return mTargetAlpha != mAlpha;
104    }
105
106    float getTargetAlpha() {
107        return mTargetAlpha;
108    }
109
110    void setLayer(int layer) {
111        if (mLayer != layer) {
112            mLayer = layer;
113            mDimSurface.setLayer(layer);
114        }
115    }
116
117    int getLayer() {
118        return mLayer;
119    }
120
121    private void setAlpha(float alpha) {
122        if (mAlpha != alpha) {
123            if (DEBUG) Slog.v(TAG, "setAlpha alpha=" + alpha);
124            try {
125                mDimSurface.setAlpha(alpha);
126                if (alpha == 0 && mShowing) {
127                    if (DEBUG) Slog.v(TAG, "setAlpha hiding");
128                    mDimSurface.hide();
129                    mShowing = false;
130                } else if (alpha > 0 && !mShowing) {
131                    if (DEBUG) Slog.v(TAG, "setAlpha showing");
132                    mDimSurface.show();
133                    mShowing = true;
134                }
135            } catch (RuntimeException e) {
136                Slog.w(TAG, "Failure setting alpha immediately", e);
137            }
138            mAlpha = alpha;
139        }
140    }
141
142    /**
143     * @param layer The new layer value.
144     * @param inTransaction Whether the call is made within a surface transaction.
145     */
146    void adjustSurface(int layer, boolean inTransaction) {
147        final int dw, dh;
148        final float xPos, yPos;
149        if (!mStack.isFullscreen()) {
150            dw = mBounds.width();
151            dh = mBounds.height();
152            xPos = mBounds.left;
153            yPos = mBounds.top;
154        } else {
155            // Set surface size to screen size.
156            final DisplayInfo info = mDisplayContent.getDisplayInfo();
157            // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose
158            // a corner.
159            dw = (int) (info.logicalWidth * 1.5);
160            dh = (int) (info.logicalHeight * 1.5);
161            // back off position so 1/4 of Surface is before and 1/4 is after.
162            xPos = -1 * dw / 6;
163            yPos = -1 * dh / 6;
164        }
165
166        try {
167            if (!inTransaction) {
168                SurfaceControl.openTransaction();
169            }
170            mDimSurface.setPosition(xPos, yPos);
171            mDimSurface.setSize(dw, dh);
172            mDimSurface.setLayer(layer);
173        } catch (RuntimeException e) {
174            Slog.w(TAG, "Failure setting size or layer", e);
175        } finally {
176            if (!inTransaction) {
177                SurfaceControl.closeTransaction();
178            }
179        }
180        mLastBounds.set(mBounds);
181        mLayer = layer;
182    }
183
184    // Assumes that surface transactions are currently closed.
185    void setBounds(Rect bounds) {
186        mBounds.set(bounds);
187        if (isDimming() && !mLastBounds.equals(bounds)) {
188            adjustSurface(mLayer, false);
189        }
190    }
191
192    /**
193     * @param duration The time to test.
194     * @return True if the duration would lead to an earlier end to the current animation.
195     */
196    private boolean durationEndsEarlier(long duration) {
197        return SystemClock.uptimeMillis() + duration < mStartTime + mDuration;
198    }
199
200    /** Jump to the end of the animation.
201     * NOTE: Must be called with Surface transaction open. */
202    void show() {
203        if (isAnimating()) {
204            if (DEBUG) Slog.v(TAG, "show: immediate");
205            show(mLayer, mTargetAlpha, 0);
206        }
207    }
208
209    /**
210     * Begin an animation to a new dim value.
211     * NOTE: Must be called with Surface transaction open.
212     *
213     * @param layer The layer to set the surface to.
214     * @param alpha The dim value to end at.
215     * @param duration How long to take to get there in milliseconds.
216     */
217    void show(int layer, float alpha, long duration) {
218        if (DEBUG) Slog.v(TAG, "show: layer=" + layer + " alpha=" + alpha
219                + " duration=" + duration);
220        if (mDimSurface == null) {
221            Slog.e(TAG, "show: no Surface");
222            // Make sure isAnimating() returns false.
223            mTargetAlpha = mAlpha = 0;
224            return;
225        }
226
227        if (!mLastBounds.equals(mBounds) || mLayer != layer) {
228            adjustSurface(layer, true);
229        }
230
231        long curTime = SystemClock.uptimeMillis();
232        final boolean animating = isAnimating();
233        if ((animating && (mTargetAlpha != alpha || durationEndsEarlier(duration)))
234                || (!animating && mAlpha != alpha)) {
235            if (duration <= 0) {
236                // No animation required, just set values.
237                setAlpha(alpha);
238            } else {
239                // Start or continue animation with new parameters.
240                mStartAlpha = mAlpha;
241                mStartTime = curTime;
242                mDuration = duration;
243            }
244        }
245        if (DEBUG) Slog.v(TAG, "show: mStartAlpha=" + mStartAlpha + " mStartTime=" + mStartTime);
246        mTargetAlpha = alpha;
247    }
248
249    /** Immediate hide.
250     * NOTE: Must be called with Surface transaction open. */
251    void hide() {
252        if (mShowing) {
253            if (DEBUG) Slog.v(TAG, "hide: immediate");
254            hide(0);
255        }
256    }
257
258    /**
259     * Gradually fade to transparent.
260     * NOTE: Must be called with Surface transaction open.
261     *
262     * @param duration Time to fade in milliseconds.
263     */
264    void hide(long duration) {
265        if (mShowing && (mTargetAlpha != 0 || durationEndsEarlier(duration))) {
266            if (DEBUG) Slog.v(TAG, "hide: duration=" + duration);
267            show(mLayer, 0, duration);
268        }
269    }
270
271    /**
272     * Advance the dimming per the last #show(int, float, long) call.
273     * NOTE: Must be called with Surface transaction open.
274     *
275     * @return True if animation is still required after this step.
276     */
277    boolean stepAnimation() {
278        if (mDimSurface == null) {
279            Slog.e(TAG, "stepAnimation: null Surface");
280            // Ensure that isAnimating() returns false;
281            mTargetAlpha = mAlpha = 0;
282            return false;
283        }
284
285        if (isAnimating()) {
286            final long curTime = SystemClock.uptimeMillis();
287            final float alphaDelta = mTargetAlpha - mStartAlpha;
288            float alpha = mStartAlpha + alphaDelta * (curTime - mStartTime) / mDuration;
289            if (alphaDelta > 0 && alpha > mTargetAlpha ||
290                    alphaDelta < 0 && alpha < mTargetAlpha) {
291                // Don't exceed limits.
292                alpha = mTargetAlpha;
293            }
294            if (DEBUG) Slog.v(TAG, "stepAnimation: curTime=" + curTime + " alpha=" + alpha);
295            setAlpha(alpha);
296        }
297
298        return isAnimating();
299    }
300
301    /** Cleanup */
302    void destroySurface() {
303        if (DEBUG) Slog.v(TAG, "destroySurface.");
304        if (mDimSurface != null) {
305            mDimSurface.destroy();
306            mDimSurface = null;
307        }
308    }
309
310    public void printTo(String prefix, PrintWriter pw) {
311        pw.print(prefix); pw.print("mDimSurface="); pw.print(mDimSurface);
312                pw.print(" mLayer="); pw.print(mLayer);
313                pw.print(" mAlpha="); pw.println(mAlpha);
314        pw.print(prefix); pw.print("mLastBounds="); pw.print(mLastBounds.toShortString());
315                pw.print(" mBounds="); pw.println(mBounds.toShortString());
316        pw.print(prefix); pw.print("Last animation: ");
317                pw.print(" mDuration="); pw.print(mDuration);
318                pw.print(" mStartTime="); pw.print(mStartTime);
319                pw.print(" curTime="); pw.println(SystemClock.uptimeMillis());
320        pw.print(prefix); pw.print(" mStartAlpha="); pw.print(mStartAlpha);
321                pw.print(" mTargetAlpha="); pw.println(mTargetAlpha);
322    }
323}
324