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.SurfaceAnimatorProto.ANIMATION_ADAPTER;
20 import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_START_DELAYED;
21 import static com.android.server.wm.SurfaceAnimatorProto.LEASH;
22 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
24 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.util.Slog;
29 import android.util.proto.ProtoOutputStream;
30 import android.view.SurfaceControl;
31 import android.view.SurfaceControl.Transaction;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.io.PrintWriter;
36 
37 /**
38  * A class that can run animations on objects that have a set of child surfaces. We do this by
39  * reparenting all child surfaces of an object onto a new surface, called the "Leash". The Leash
40  * gets attached in the surface hierarchy where the the children were attached to. We then hand off
41  * the Leash to the component handling the animation, which is specified by the
42  * {@link AnimationAdapter}. When the animation is done animating, our callback to finish the
43  * animation will be invoked, at which we reparent the children back to the original parent.
44  */
45 class SurfaceAnimator {
46 
47     private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM;
48     private final WindowManagerService mService;
49     private AnimationAdapter mAnimation;
50 
51     @VisibleForTesting
52     SurfaceControl mLeash;
53     @VisibleForTesting
54     final Animatable mAnimatable;
55     private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
56     @VisibleForTesting
57     final Runnable mAnimationFinishedCallback;
58     private boolean mAnimationStartDelayed;
59 
60     /**
61      * @param animatable The object to animate.
62      * @param animationFinishedCallback Callback to invoke when an animation has finished running.
63      */
SurfaceAnimator(Animatable animatable, @Nullable Runnable animationFinishedCallback, WindowManagerService service)64     SurfaceAnimator(Animatable animatable, @Nullable Runnable animationFinishedCallback,
65             WindowManagerService service) {
66         mAnimatable = animatable;
67         mService = service;
68         mAnimationFinishedCallback = animationFinishedCallback;
69         mInnerAnimationFinishedCallback = getFinishedCallback(animationFinishedCallback);
70     }
71 
getFinishedCallback( @ullable Runnable animationFinishedCallback)72     private OnAnimationFinishedCallback getFinishedCallback(
73             @Nullable Runnable animationFinishedCallback) {
74         return anim -> {
75             synchronized (mService.mGlobalLock) {
76                 final SurfaceAnimator target = mService.mAnimationTransferMap.remove(anim);
77                 if (target != null) {
78                     target.mInnerAnimationFinishedCallback.onAnimationFinished(anim);
79                     return;
80                 }
81 
82                 if (anim != mAnimation) {
83                     return;
84                 }
85                 final Runnable resetAndInvokeFinish = () -> {
86                     // We need to check again if the animation has been replaced with a new
87                     // animation because the animatable may defer to finish.
88                     if (anim != mAnimation) {
89                         return;
90                     }
91                     reset(mAnimatable.getPendingTransaction(), true /* destroyLeash */);
92                     if (animationFinishedCallback != null) {
93                         animationFinishedCallback.run();
94                     }
95                 };
96                 if (!mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)) {
97                     resetAndInvokeFinish.run();
98                 }
99             }
100         };
101     }
102 
103     /**
104      * Starts an animation.
105      *
106      * @param anim The object that bridges the controller, {@link SurfaceAnimator}, with the
107      *             component responsible for running the animation. It runs the animation with
108      *             {@link AnimationAdapter#startAnimation} once the hierarchy with
109      *             the Leash has been set up.
110      * @param hidden Whether the container holding the child surfaces is currently visible or not.
111      *               This is important as it will start with the leash hidden or visible before
112      *               handing it to the component that is responsible to run the animation.
113      */
114     void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
115         cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
116         mAnimation = anim;
117         final SurfaceControl surface = mAnimatable.getSurfaceControl();
118         if (surface == null) {
119             Slog.w(TAG, "Unable to start animation, surface is null or no children.");
120             cancelAnimation();
121             return;
122         }
123         mLeash = createAnimationLeash(surface, t,
124                 mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), hidden);
125         mAnimatable.onAnimationLeashCreated(t, mLeash);
126         if (mAnimationStartDelayed) {
127             if (DEBUG_ANIM) Slog.i(TAG, "Animation start delayed");
128             return;
129         }
130         mAnimation.startAnimation(mLeash, t, mInnerAnimationFinishedCallback);
131     }
132 
133     /**
134      * Begins with delaying all animations to start. Any subsequent call to {@link #startAnimation}
135      * will not start the animation until {@link #endDelayingAnimationStart} is called. When an
136      * animation start is being delayed, the animator is considered animating already.
137      */
138     void startDelayingAnimationStart() {
139 
140         // We only allow delaying animation start we are not currently animating
141         if (!isAnimating()) {
142             mAnimationStartDelayed = true;
143         }
144     }
145 
146     /**
147      * See {@link #startDelayingAnimationStart}.
148      */
149     void endDelayingAnimationStart() {
150         final boolean delayed = mAnimationStartDelayed;
151         mAnimationStartDelayed = false;
152         if (delayed && mAnimation != null) {
153             mAnimation.startAnimation(mLeash, mAnimatable.getPendingTransaction(),
154                     mInnerAnimationFinishedCallback);
155             mAnimatable.commitPendingTransaction();
156         }
157     }
158 
159     /**
160      * @return Whether we are currently running an animation, or we have a pending animation that
161      *         is waiting to be started with {@link #endDelayingAnimationStart}
162      */
163     boolean isAnimating() {
164         return mAnimation != null;
165     }
166 
167     /**
168      * @return The current animation spec if we are running an animation, or {@code null} otherwise.
169      */
170     AnimationAdapter getAnimation() {
171         return mAnimation;
172     }
173 
174     /**
175      * Cancels any currently running animation.
176      */
177     void cancelAnimation() {
178         cancelAnimation(mAnimatable.getPendingTransaction(), false /* restarting */,
179                 true /* forwardCancel */);
180         mAnimatable.commitPendingTransaction();
181     }
182 
183     /**
184      * Sets the layer of the surface.
185      * <p>
186      * When the layer of the surface needs to be adjusted, we need to set it on the leash if the
187      * surface is reparented to the leash. This method takes care of that.
188      */
189     void setLayer(Transaction t, int layer) {
190         t.setLayer(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), layer);
191     }
192 
193     /**
194      * Sets the surface to be relatively layered.
195      *
196      * @see #setLayer
197      */
198     void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
199         t.setRelativeLayer(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), relativeTo, layer);
200     }
201 
202     /**
203      * Reparents the surface.
204      *
205      * @see #setLayer
206      */
207     void reparent(Transaction t, SurfaceControl newParent) {
208         t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent);
209     }
210 
211     /**
212      * @return True if the surface is attached to the leash; false otherwise.
213      */
214     boolean hasLeash() {
215         return mLeash != null;
216     }
217 
218     void transferAnimation(SurfaceAnimator from) {
219         if (from.mLeash == null) {
220             return;
221         }
222         final SurfaceControl surface = mAnimatable.getSurfaceControl();
223         final SurfaceControl parent = mAnimatable.getAnimationLeashParent();
224         if (surface == null || parent == null) {
225             Slog.w(TAG, "Unable to transfer animation, surface or parent is null");
226             cancelAnimation();
227             return;
228         }
229         endDelayingAnimationStart();
230         final Transaction t = mAnimatable.getPendingTransaction();
231         cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
232         mLeash = from.mLeash;
233         mAnimation = from.mAnimation;
234 
235         // Cancel source animation, but don't let animation runner cancel the animation.
236         from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
237         t.reparent(surface, mLeash);
238         t.reparent(mLeash, parent);
239         mAnimatable.onAnimationLeashCreated(t, mLeash);
240         mService.mAnimationTransferMap.put(mAnimation, this);
241     }
242 
243     boolean isAnimationStartDelayed() {
244         return mAnimationStartDelayed;
245     }
246 
247     /**
248      * Cancels the animation, and resets the leash.
249      *
250      * @param t The transaction to use for all cancelling surface operations.
251      * @param restarting Whether we are restarting the animation.
252      * @param forwardCancel Whether to forward the cancel signal to the adapter executing the
253      *                      animation. This will be set to false when just transferring an animation
254      *                      to another animator.
255      */
256     private void cancelAnimation(Transaction t, boolean restarting, boolean forwardCancel) {
257         if (DEBUG_ANIM) Slog.i(TAG, "Cancelling animation restarting=" + restarting);
258         final SurfaceControl leash = mLeash;
259         final AnimationAdapter animation = mAnimation;
260         reset(t, false);
261         if (animation != null) {
262             if (!mAnimationStartDelayed && forwardCancel) {
263                 animation.onAnimationCancelled(leash);
264             }
265             if (!restarting) {
266                 mAnimationFinishedCallback.run();
267             }
268         }
269 
270         if (forwardCancel && leash != null) {
271             t.remove(leash);
272             mService.scheduleAnimationLocked();
273         }
274 
275         if (!restarting) {
276             mAnimationStartDelayed = false;
277         }
278     }
279 
280     private void reset(Transaction t, boolean destroyLeash) {
281         final SurfaceControl surface = mAnimatable.getSurfaceControl();
282         final SurfaceControl parent = mAnimatable.getParentSurfaceControl();
283 
284         boolean scheduleAnim = false;
285 
286         // If the surface was destroyed or the leash is invalid, we don't care to reparent it back.
287         // Note that we also set this variable to true even if the parent isn't valid anymore, in
288         // order to ensure onAnimationLeashLost still gets called in this case.
289         final boolean reparent = mLeash != null && surface != null;
290         if (reparent) {
291             if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent: " + parent);
292             // We shouldn't really need these isValid checks but we do
293             // b/130364451
294             if (surface.isValid() && parent != null && parent.isValid()) {
295                 t.reparent(surface, parent);
296                 scheduleAnim = true;
297             }
298         }
299         mService.mAnimationTransferMap.remove(mAnimation);
300         if (mLeash != null && destroyLeash) {
301             t.remove(mLeash);
302             scheduleAnim = true;
303         }
304         mLeash = null;
305         mAnimation = null;
306 
307         if (reparent) {
308             // Make sure to inform the animatable after the surface was reparented (or reparent
309             // wasn't possible, but we still need to invoke the callback)
310             mAnimatable.onAnimationLeashLost(t);
311             scheduleAnim = true;
312         }
313 
314         if (scheduleAnim) {
315             mService.scheduleAnimationLocked();
316         }
317     }
318 
319     private SurfaceControl createAnimationLeash(SurfaceControl surface, Transaction t, int width,
320             int height, boolean hidden) {
321         if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash");
322         final SurfaceControl.Builder builder = mAnimatable.makeAnimationLeash()
323                 .setParent(mAnimatable.getAnimationLeashParent())
324                 .setName(surface + " - animation-leash");
325         final SurfaceControl leash = builder.build();
326         t.setWindowCrop(leash, width, height);
327         if (!hidden) {
328             t.show(leash);
329         }
330         t.reparent(surface, leash);
331         return leash;
332     }
333 
334     /**
335      * Write to a protocol buffer output stream. Protocol buffer message definition is at {@link
336      * com.android.server.wm.SurfaceAnimatorProto}.
337      *
338      * @param proto Stream to write the SurfaceAnimator object to.
339      * @param fieldId Field Id of the SurfaceAnimator as defined in the parent message.
340      * @hide
341      */
342     void writeToProto(ProtoOutputStream proto, long fieldId) {
343         final long token = proto.start(fieldId);
344         if (mAnimation != null) {
345             mAnimation.writeToProto(proto, ANIMATION_ADAPTER);
346         }
347         if (mLeash != null) {
348             mLeash.writeToProto(proto, LEASH);
349         }
350         proto.write(ANIMATION_START_DELAYED, mAnimationStartDelayed);
351         proto.end(token);
352     }
353 
354     void dump(PrintWriter pw, String prefix) {
355         pw.print(prefix); pw.print("mLeash="); pw.print(mLeash);
356         if (mAnimationStartDelayed) {
357             pw.print(" mAnimationStartDelayed="); pw.println(mAnimationStartDelayed);
358         } else {
359             pw.println();
360         }
361         pw.print(prefix); pw.println("Animation:");
362         if (mAnimation != null) {
363             mAnimation.dump(pw, prefix + "  ");
364         } else {
365             pw.print(prefix); pw.println("null");
366         }
367     }
368 
369     /**
370      * Callback to be passed into {@link AnimationAdapter#startAnimation} to be invoked by the
371      * component that is running the animation when the animation is finished.
372      */
373     interface OnAnimationFinishedCallback {
374         void onAnimationFinished(AnimationAdapter anim);
375     }
376 
377     /**
378      * Interface to be animated by {@link SurfaceAnimator}.
379      */
380     interface Animatable {
381 
382         /**
383          * @return The pending transaction that will be committed in the next frame.
384          */
385         @NonNull Transaction getPendingTransaction();
386 
387         /**
388          * Schedules a commit of the pending transaction.
389          */
390         void commitPendingTransaction();
391 
392         /**
393          * Called when the was created.
394          *
395          * @param t The transaction to use to apply any necessary changes.
396          * @param leash The leash that was created.
397          */
398         void onAnimationLeashCreated(Transaction t, SurfaceControl leash);
399 
400         /**
401          * Called when the leash is being destroyed, or when the leash is being transferred to
402          * another SurfaceAnimator.
403          *
404          * @param t The transaction to use to apply any necessary changes.
405          */
406         void onAnimationLeashLost(Transaction t);
407 
408         /**
409          * @return A new surface to be used for the animation leash, inserted at the correct
410          *         position in the hierarchy.
411          */
412         SurfaceControl.Builder makeAnimationLeash();
413 
414         /**
415          * @return The parent that should be used for the animation leash.
416          */
417         @Nullable SurfaceControl getAnimationLeashParent();
418 
419         /**
420          * @return The surface of the object to be animated.
421          *         This SurfaceControl must be valid if non-null.
422          */
423         @Nullable SurfaceControl getSurfaceControl();
424 
425         /**
426          * @return The parent of the surface object to be animated.
427          *         This SurfaceControl must be valid if non-null.
428          */
429         @Nullable SurfaceControl getParentSurfaceControl();
430 
431         /**
432          * @return The width of the surface to be animated.
433          */
434         int getSurfaceWidth();
435 
436         /**
437          * @return The height of the surface to be animated.
438          */
439         int getSurfaceHeight();
440 
441         /**
442          * Gets called when the animation is about to finish and gives the client the opportunity to
443          * defer finishing the animation, i.e. it keeps the leash around until the client calls
444          * {@link #cancelAnimation}.
445          *
446          * @param endDeferFinishCallback The callback to call when defer finishing should be ended.
447          * @return Whether the client would like to defer the animation finish.
448          */
449         default boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
450             return false;
451         }
452     }
453 }
454