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