1 /*
2  * Copyright (C) 2017 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 
17 package com.android.server.wm;
18 
19 import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
20 import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
21 import static com.android.server.wm.AlphaAnimationSpecProto.TO;
22 import static com.android.server.wm.AnimationSpecProto.ALPHA;
23 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
24 
25 import android.graphics.Rect;
26 import android.util.Log;
27 import android.util.proto.ProtoOutputStream;
28 import android.view.Surface;
29 import android.view.SurfaceControl;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.server.wm.SurfaceAnimator.AnimationType;
33 
34 import java.io.PrintWriter;
35 
36 /**
37  * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
38  * black layers of varying opacity at various Z-levels which create the effect of a Dim.
39  */
40 class Dimmer {
41     private static final String TAG = "WindowManager";
42     // This is in milliseconds.
43     private static final int DEFAULT_DIM_ANIM_DURATION = 200;
44 
45     private class DimAnimatable implements SurfaceAnimator.Animatable {
46         private SurfaceControl mDimLayer;
47 
DimAnimatable(SurfaceControl dimLayer)48         private DimAnimatable(SurfaceControl dimLayer) {
49             mDimLayer = dimLayer;
50         }
51 
52         @Override
getPendingTransaction()53         public SurfaceControl.Transaction getPendingTransaction() {
54             return mHost.getSyncTransaction();
55         }
56 
57         @Override
commitPendingTransaction()58         public void commitPendingTransaction() {
59             mHost.commitPendingTransaction();
60         }
61 
62         @Override
onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash)63         public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
64         }
65 
66         @Override
onAnimationLeashLost(SurfaceControl.Transaction t)67         public void onAnimationLeashLost(SurfaceControl.Transaction t) {
68         }
69 
70         @Override
makeAnimationLeash()71         public SurfaceControl.Builder makeAnimationLeash() {
72             return mHost.makeAnimationLeash();
73         }
74 
75         @Override
getAnimationLeashParent()76         public SurfaceControl getAnimationLeashParent() {
77             return mHost.getSurfaceControl();
78         }
79 
80         @Override
getSurfaceControl()81         public SurfaceControl getSurfaceControl() {
82             return mDimLayer;
83         }
84 
85         @Override
getParentSurfaceControl()86         public SurfaceControl getParentSurfaceControl() {
87             return mHost.getSurfaceControl();
88         }
89 
90         @Override
getSurfaceWidth()91         public int getSurfaceWidth() {
92             // This will determine the size of the leash created. This should be the size of the
93             // host and not the dim layer since the dim layer may get bigger during animation. If
94             // that occurs, the leash size cannot change so we need to ensure the leash is big
95             // enough that the dim layer can grow.
96             // This works because the mHost will be a Task which has the display bounds.
97             return mHost.getSurfaceWidth();
98         }
99 
100         @Override
getSurfaceHeight()101         public int getSurfaceHeight() {
102             // See getSurfaceWidth() above for explanation.
103             return mHost.getSurfaceHeight();
104         }
105 
removeSurface()106         void removeSurface() {
107             if (mDimLayer != null && mDimLayer.isValid()) {
108                 getPendingTransaction().remove(mDimLayer);
109             }
110             mDimLayer = null;
111         }
112     }
113 
114     @VisibleForTesting
115     class DimState {
116         /**
117          * The layer where property changes should be invoked on.
118          */
119         SurfaceControl mDimLayer;
120         boolean mDimming;
121         boolean isVisible;
122         SurfaceAnimator mSurfaceAnimator;
123 
124         /**
125          * Determines whether the dim layer should animate before destroying.
126          */
127         boolean mAnimateExit = true;
128 
129         /**
130          * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
131          * details on Dim lifecycle.
132          */
133         boolean mDontReset;
134 
DimState(SurfaceControl dimLayer)135         DimState(SurfaceControl dimLayer) {
136             mDimLayer = dimLayer;
137             mDimming = true;
138             final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
139             mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
140                 if (!mDimming) {
141                     dimAnimatable.removeSurface();
142                 }
143             }, mHost.mWmService);
144         }
145     }
146 
147     /**
148      * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
149      * host, some controller of it, or one of the hosts children.
150      */
151     private WindowContainer mHost;
152     private WindowContainer mLastRequestedDimContainer;
153     @VisibleForTesting
154     DimState mDimState;
155 
156     private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
157 
158     @VisibleForTesting
159     interface SurfaceAnimatorStarter {
startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type)160         void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
161                 AnimationAdapter anim, boolean hidden, @AnimationType int type);
162     }
163 
Dimmer(WindowContainer host)164     Dimmer(WindowContainer host) {
165         this(host, SurfaceAnimator::startAnimation);
166     }
167 
Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter)168     Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
169         mHost = host;
170         mSurfaceAnimatorStarter = surfaceAnimatorStarter;
171     }
172 
makeDimLayer()173     private SurfaceControl makeDimLayer() {
174         return mHost.makeChildSurface(null)
175                 .setParent(mHost.getSurfaceControl())
176                 .setColorLayer()
177                 .setName("Dim Layer for - " + mHost.getName())
178                 .setCallsite("Dimmer.makeDimLayer")
179                 .build();
180     }
181 
182     /**
183      * Retrieve the DimState, creating one if it doesn't exist.
184      */
getDimState(WindowContainer container)185     private DimState getDimState(WindowContainer container) {
186         if (mDimState == null) {
187             try {
188                 final SurfaceControl ctl = makeDimLayer();
189                 mDimState = new DimState(ctl);
190                 /**
191                  * See documentation on {@link #dimAbove} to understand lifecycle management of
192                  * Dim's via state resetting for Dim's with containers.
193                  */
194                 if (container == null) {
195                     mDimState.mDontReset = true;
196                 }
197             } catch (Surface.OutOfResourcesException e) {
198                 Log.w(TAG, "OutOfResourcesException creating dim surface");
199             }
200         }
201 
202         mLastRequestedDimContainer = container;
203         return mDimState;
204     }
205 
dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer, float alpha)206     private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer,
207             float alpha) {
208         final DimState d = getDimState(container);
209 
210         if (d == null) {
211             return;
212         }
213 
214         if (container != null) {
215             // The dim method is called from WindowState.prepareSurfaces(), which is always called
216             // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
217             // relative to the highest Z layer with a dim.
218             t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
219         } else {
220             t.setLayer(d.mDimLayer, Integer.MAX_VALUE);
221         }
222         t.setAlpha(d.mDimLayer, alpha);
223 
224         d.mDimming = true;
225     }
226 
227     /**
228      * Finish a dim started by dimAbove in the case there was no call to dimAbove.
229      *
230      * @param t A Transaction in which to finish the dim.
231      */
stopDim(SurfaceControl.Transaction t)232     void stopDim(SurfaceControl.Transaction t) {
233         if (mDimState != null) {
234             t.hide(mDimState.mDimLayer);
235             mDimState.isVisible = false;
236             mDimState.mDontReset = false;
237         }
238     }
239 
240     /**
241      * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
242      * remove this effect. If the Dim can be assosciated with a particular child of the host
243      * consider using the other variant of dimAbove which ties the Dim lifetime to the child
244      * lifetime more explicitly.
245      *
246      * @param t     A transaction in which to apply the Dim.
247      * @param alpha The alpha at which to Dim.
248      */
dimAbove(SurfaceControl.Transaction t, float alpha)249     void dimAbove(SurfaceControl.Transaction t, float alpha) {
250         dim(t, null, 1, alpha);
251     }
252 
253     /**
254      * Place a dim above the given container, which should be a child of the host container.
255      * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
256      * and the child should call dimAbove again to request the Dim to continue.
257      *
258      * @param t         A transaction in which to apply the Dim.
259      * @param container The container which to dim above. Should be a child of our host.
260      * @param alpha     The alpha at which to Dim.
261      */
dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha)262     void dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
263         dim(t, container, 1, alpha);
264     }
265 
266     /**
267      * Like {@link #dimAbove} but places the dim below the given container.
268      *
269      * @param t         A transaction in which to apply the Dim.
270      * @param container The container which to dim below. Should be a child of our host.
271      * @param alpha     The alpha at which to Dim.
272      */
273 
dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha)274     void dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
275         dim(t, container, -1, alpha);
276     }
277 
278     /**
279      * Mark all dims as pending completion on the next call to {@link #updateDims}
280      *
281      * This is intended for us by the host container, to be called at the beginning of
282      * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
283      * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
284      * a chance to request dims to continue.
285      */
resetDimStates()286     void resetDimStates() {
287         if (mDimState != null && !mDimState.mDontReset) {
288             mDimState.mDimming = false;
289         }
290     }
291 
dontAnimateExit()292     void dontAnimateExit() {
293         if (mDimState != null) {
294             mDimState.mAnimateExit = false;
295         }
296     }
297 
298     /**
299      * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
300      * described in {@link #resetDimStates}.
301      *
302      * @param t      A transaction in which to update the dims.
303      * @param bounds The bounds at which to dim.
304      * @return true if any Dims were updated.
305      */
updateDims(SurfaceControl.Transaction t, Rect bounds)306     boolean updateDims(SurfaceControl.Transaction t, Rect bounds) {
307         if (mDimState == null) {
308             return false;
309         }
310 
311         if (!mDimState.mDimming) {
312             if (!mDimState.mAnimateExit) {
313                 if (mDimState.mDimLayer.isValid()) {
314                     t.remove(mDimState.mDimLayer);
315                 }
316             } else {
317                 startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
318             }
319             mDimState = null;
320             return false;
321         } else {
322             // TODO: Once we use geometry from hierarchy this falls away.
323             t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
324             t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
325             if (!mDimState.isVisible) {
326                 mDimState.isVisible = true;
327                 t.show(mDimState.mDimLayer);
328                 startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
329             }
330             return true;
331         }
332     }
333 
startDimEnter(WindowContainer container, SurfaceAnimator animator, SurfaceControl.Transaction t)334     private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
335             SurfaceControl.Transaction t) {
336         startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
337     }
338 
startDimExit(WindowContainer container, SurfaceAnimator animator, SurfaceControl.Transaction t)339     private void startDimExit(WindowContainer container, SurfaceAnimator animator,
340             SurfaceControl.Transaction t) {
341         startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
342     }
343 
startAnim(WindowContainer container, SurfaceAnimator animator, SurfaceControl.Transaction t, float startAlpha, float endAlpha)344     private void startAnim(WindowContainer container, SurfaceAnimator animator,
345             SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
346         mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
347                 new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
348                 mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
349                 ANIMATION_TYPE_DIMMER);
350     }
351 
getDimDuration(WindowContainer container)352     private long getDimDuration(WindowContainer container) {
353         // If there's no container, then there isn't an animation occurring while dimming. Set the
354         // duration to 0 so it immediately dims to the set alpha.
355         if (container == null) {
356             return 0;
357         }
358 
359         // Otherwise use the same duration as the animation on the WindowContainer
360         AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
361         return animationAdapter == null ? DEFAULT_DIM_ANIM_DURATION
362                 : animationAdapter.getDurationHint();
363     }
364 
365     private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
366         private final long mDuration;
367         private final float mFromAlpha;
368         private final float mToAlpha;
369 
AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration)370         AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
371             mFromAlpha = fromAlpha;
372             mToAlpha = toAlpha;
373             mDuration = duration;
374         }
375 
376         @Override
getDuration()377         public long getDuration() {
378             return mDuration;
379         }
380 
381         @Override
apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime)382         public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
383             final float fraction = getFraction(currentPlayTime);
384             final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
385             t.setAlpha(sc, alpha);
386         }
387 
388         @Override
dump(PrintWriter pw, String prefix)389         public void dump(PrintWriter pw, String prefix) {
390             pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
391             pw.print(" to="); pw.print(mToAlpha);
392             pw.print(" duration="); pw.println(mDuration);
393         }
394 
395         @Override
dumpDebugInner(ProtoOutputStream proto)396         public void dumpDebugInner(ProtoOutputStream proto) {
397             final long token = proto.start(ALPHA);
398             proto.write(FROM, mFromAlpha);
399             proto.write(TO, mToAlpha);
400             proto.write(DURATION_MS, mDuration);
401             proto.end(token);
402         }
403     }
404 }
405