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 final 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
onAnimationLeashDestroyed(SurfaceControl.Transaction t)65         public void onAnimationLeashDestroyed(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     }
104 
105     @VisibleForTesting
106     class DimState {
107         /**
108          * The layer where property changes should be invoked on.
109          */
110         SurfaceControl mDimLayer;
111         boolean mDimming;
112         boolean isVisible;
113         SurfaceAnimator mSurfaceAnimator;
114 
115         /**
116          * Determines whether the dim layer should animate before destroying.
117          */
118         boolean mAnimateExit = true;
119 
120         /**
121          * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
122          * details on Dim lifecycle.
123          */
124         boolean mDontReset;
125 
DimState(SurfaceControl dimLayer)126         DimState(SurfaceControl dimLayer) {
127             mDimLayer = dimLayer;
128             mDimming = true;
129             mSurfaceAnimator = new SurfaceAnimator(new DimAnimatable(dimLayer), () -> {
130                 if (!mDimming) {
131                     mDimLayer.destroy();
132                 }
133             }, mHost.mService);
134         }
135     }
136 
137     /**
138      * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
139      * host, some controller of it, or one of the hosts children.
140      */
141     private WindowContainer mHost;
142     private WindowContainer mLastRequestedDimContainer;
143     @VisibleForTesting
144     DimState mDimState;
145 
146     private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
147 
148     @VisibleForTesting
149     interface SurfaceAnimatorStarter {
startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, AnimationAdapter anim, boolean hidden)150         void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
151                 AnimationAdapter anim, boolean hidden);
152     }
153 
Dimmer(WindowContainer host)154     Dimmer(WindowContainer host) {
155         this(host, SurfaceAnimator::startAnimation);
156     }
157 
Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter)158     Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
159         mHost = host;
160         mSurfaceAnimatorStarter = surfaceAnimatorStarter;
161     }
162 
makeDimLayer()163     private SurfaceControl makeDimLayer() {
164         return mHost.makeChildSurface(null)
165                 .setParent(mHost.getSurfaceControl())
166                 .setColorLayer(true)
167                 .setName("Dim Layer for - " + mHost.getName())
168                 .build();
169     }
170 
171     /**
172      * Retrieve the DimState, creating one if it doesn't exist.
173      */
getDimState(WindowContainer container)174     private DimState getDimState(WindowContainer container) {
175         if (mDimState == null) {
176             try {
177                 final SurfaceControl ctl = makeDimLayer();
178                 mDimState = new DimState(ctl);
179                 /**
180                  * See documentation on {@link #dimAbove} to understand lifecycle management of
181                  * Dim's via state resetting for Dim's with containers.
182                  */
183                 if (container == null) {
184                     mDimState.mDontReset = true;
185                 }
186             } catch (Surface.OutOfResourcesException e) {
187                 Log.w(TAG, "OutOfResourcesException creating dim surface");
188             }
189         }
190 
191         mLastRequestedDimContainer = container;
192         return mDimState;
193     }
194 
dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer, float alpha)195     private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer,
196             float alpha) {
197         final DimState d = getDimState(container);
198 
199         if (d == null) {
200             return;
201         }
202 
203         if (container != null) {
204             // The dim method is called from WindowState.prepareSurfaces(), which is always called
205             // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
206             // relative to the highest Z layer with a dim.
207             t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
208         } else {
209             t.setLayer(d.mDimLayer, Integer.MAX_VALUE);
210         }
211         t.setAlpha(d.mDimLayer, alpha);
212 
213         d.mDimming = true;
214     }
215 
216     /**
217      * Finish a dim started by dimAbove in the case there was no call to dimAbove.
218      *
219      * @param t A Transaction in which to finish the dim.
220      */
stopDim(SurfaceControl.Transaction t)221     void stopDim(SurfaceControl.Transaction t) {
222         if (mDimState != null) {
223             t.hide(mDimState.mDimLayer);
224             mDimState.isVisible = false;
225             mDimState.mDontReset = false;
226         }
227     }
228 
229     /**
230      * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
231      * remove this effect. If the Dim can be assosciated with a particular child of the host
232      * consider using the other variant of dimAbove which ties the Dim lifetime to the child
233      * lifetime more explicitly.
234      *
235      * @param t     A transaction in which to apply the Dim.
236      * @param alpha The alpha at which to Dim.
237      */
dimAbove(SurfaceControl.Transaction t, float alpha)238     void dimAbove(SurfaceControl.Transaction t, float alpha) {
239         dim(t, null, 1, alpha);
240     }
241 
242     /**
243      * Place a dim above the given container, which should be a child of the host container.
244      * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
245      * and the child should call dimAbove again to request the Dim to continue.
246      *
247      * @param t         A transaction in which to apply the Dim.
248      * @param container The container which to dim above. Should be a child of our host.
249      * @param alpha     The alpha at which to Dim.
250      */
dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha)251     void dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
252         dim(t, container, 1, alpha);
253     }
254 
255     /**
256      * Like {@link #dimAbove} but places the dim below the given container.
257      *
258      * @param t         A transaction in which to apply the Dim.
259      * @param container The container which to dim below. Should be a child of our host.
260      * @param alpha     The alpha at which to Dim.
261      */
262 
dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha)263     void dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
264         dim(t, container, -1, alpha);
265     }
266 
267     /**
268      * Mark all dims as pending completion on the next call to {@link #updateDims}
269      *
270      * This is intended for us by the host container, to be called at the beginning of
271      * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
272      * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
273      * a chance to request dims to continue.
274      */
resetDimStates()275     void resetDimStates() {
276         if (mDimState != null && !mDimState.mDontReset) {
277             mDimState.mDimming = false;
278         }
279     }
280 
dontAnimateExit()281     void dontAnimateExit() {
282         if (mDimState != null) {
283             mDimState.mAnimateExit = false;
284         }
285     }
286 
287     /**
288      * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
289      * described in {@link #resetDimStates}.
290      *
291      * @param t      A transaction in which to update the dims.
292      * @param bounds The bounds at which to dim.
293      * @return true if any Dims were updated.
294      */
updateDims(SurfaceControl.Transaction t, Rect bounds)295     boolean updateDims(SurfaceControl.Transaction t, Rect bounds) {
296         if (mDimState == null) {
297             return false;
298         }
299 
300         if (!mDimState.mDimming) {
301             if (!mDimState.mAnimateExit) {
302                 t.destroy(mDimState.mDimLayer);
303             } else {
304                 startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
305             }
306             mDimState = null;
307             return false;
308         } else {
309             // TODO: Once we use geometry from hierarchy this falls away.
310             t.setSize(mDimState.mDimLayer, bounds.width(), bounds.height());
311             t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
312             if (!mDimState.isVisible) {
313                 mDimState.isVisible = true;
314                 t.show(mDimState.mDimLayer);
315                 startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
316             }
317             return true;
318         }
319     }
320 
startDimEnter(WindowContainer container, SurfaceAnimator animator, SurfaceControl.Transaction t)321     private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
322             SurfaceControl.Transaction t) {
323         startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
324     }
325 
startDimExit(WindowContainer container, SurfaceAnimator animator, SurfaceControl.Transaction t)326     private void startDimExit(WindowContainer container, SurfaceAnimator animator,
327             SurfaceControl.Transaction t) {
328         startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
329     }
330 
startAnim(WindowContainer container, SurfaceAnimator animator, SurfaceControl.Transaction t, float startAlpha, float endAlpha)331     private void startAnim(WindowContainer container, SurfaceAnimator animator,
332             SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
333         mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
334                 new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
335                 mHost.mService.mSurfaceAnimationRunner), false /* hidden */);
336     }
337 
getDimDuration(WindowContainer container)338     private long getDimDuration(WindowContainer container) {
339         // If there's no container, then there isn't an animation occurring while dimming. Set the
340         // duration to 0 so it immediately dims to the set alpha.
341         if (container == null) {
342             return 0;
343         }
344 
345         // Otherwise use the same duration as the animation on the WindowContainer
346         AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
347         return animationAdapter == null ? DEFAULT_DIM_ANIM_DURATION
348                 : animationAdapter.getDurationHint();
349     }
350 
351     private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
352         private final long mDuration;
353         private final float mFromAlpha;
354         private final float mToAlpha;
355 
AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration)356         AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
357             mFromAlpha = fromAlpha;
358             mToAlpha = toAlpha;
359             mDuration = duration;
360         }
361 
362         @Override
getDuration()363         public long getDuration() {
364             return mDuration;
365         }
366 
367         @Override
apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime)368         public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
369             float alpha = ((float) currentPlayTime / getDuration()) * (mToAlpha - mFromAlpha)
370                     + mFromAlpha;
371             t.setAlpha(sc, alpha);
372         }
373 
374         @Override
dump(PrintWriter pw, String prefix)375         public void dump(PrintWriter pw, String prefix) {
376             pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
377             pw.print(" to="); pw.print(mToAlpha);
378             pw.print(" duration="); pw.println(mDuration);
379         }
380 
381         @Override
writeToProtoInner(ProtoOutputStream proto)382         public void writeToProtoInner(ProtoOutputStream proto) {
383             final long token = proto.start(ALPHA);
384             proto.write(FROM, mFromAlpha);
385             proto.write(TO, mToAlpha);
386             proto.write(DURATION_MS, mDuration);
387             proto.end(token);
388         }
389     }
390 }
391