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