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 android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
20 import static android.util.TimeUtils.NANOS_PER_MS;
21 import static android.view.Choreographer.CALLBACK_TRAVERSAL;
22 import static android.view.Choreographer.getSfInstance;
23 
24 import android.animation.AnimationHandler;
25 import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
26 import android.animation.Animator;
27 import android.animation.AnimatorListenerAdapter;
28 import android.animation.ValueAnimator;
29 import android.annotation.Nullable;
30 import android.graphics.BitmapShader;
31 import android.graphics.Canvas;
32 import android.graphics.Insets;
33 import android.graphics.Paint;
34 import android.graphics.PixelFormat;
35 import android.graphics.Rect;
36 import android.hardware.power.Boost;
37 import android.os.Handler;
38 import android.os.PowerManagerInternal;
39 import android.os.Trace;
40 import android.util.ArrayMap;
41 import android.util.Log;
42 import android.view.Choreographer;
43 import android.view.Surface;
44 import android.view.SurfaceControl;
45 import android.view.SurfaceControl.Transaction;
46 import android.view.animation.Animation;
47 import android.view.animation.Transformation;
48 import android.window.ScreenCapture;
49 
50 import com.android.internal.annotations.GuardedBy;
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
53 import com.android.server.AnimationThread;
54 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
55 
56 import java.util.ArrayList;
57 import java.util.concurrent.ExecutorService;
58 import java.util.concurrent.Executors;
59 import java.util.function.Supplier;
60 
61 /**
62  * Class to run animations without holding the window manager lock.
63  */
64 class SurfaceAnimationRunner {
65 
66     private static final String TAG = SurfaceAnimationRunner.class.getSimpleName();
67 
68     private final Object mLock = new Object();
69 
70     /**
71      * Lock for cancelling animations. Must be acquired on it's own, or after acquiring
72      * {@link #mLock}
73      */
74     private final Object mCancelLock = new Object();
75 
76     /**
77      * Lock for synchronizing {@link #mEdgeExtensions} to prevent race conditions when managing
78      * created edge extension surfaces.
79      */
80     private final Object mEdgeExtensionLock = new Object();
81 
82     @VisibleForTesting
83     Choreographer mChoreographer;
84 
85     private final Handler mAnimationThreadHandler = AnimationThread.getHandler();
86     private final Handler mSurfaceAnimationHandler = SurfaceAnimationThread.getHandler();
87     private final Runnable mApplyTransactionRunnable = this::applyTransaction;
88     private final AnimationHandler mAnimationHandler;
89     private final Transaction mFrameTransaction;
90     private final AnimatorFactory mAnimatorFactory;
91     private final PowerManagerInternal mPowerManagerInternal;
92     private boolean mApplyScheduled;
93 
94     // Executor to perform the edge extension.
95     // With two threads because in practice we will want to extend two surfaces in one animation,
96     // in which case we want to be able to parallelize those two extensions to cut down latency in
97     // starting the animation.
98     private final ExecutorService mEdgeExtensionExecutor = Executors.newFixedThreadPool(2);
99 
100     @GuardedBy("mLock")
101     @VisibleForTesting
102     final ArrayMap<SurfaceControl, RunningAnimation> mPendingAnimations = new ArrayMap<>();
103 
104     @GuardedBy("mLock")
105     @VisibleForTesting
106     final ArrayMap<SurfaceControl, RunningAnimation> mPreProcessingAnimations = new ArrayMap<>();
107 
108     @GuardedBy("mLock")
109     @VisibleForTesting
110     final ArrayMap<SurfaceControl, RunningAnimation> mRunningAnimations = new ArrayMap<>();
111 
112     @GuardedBy("mLock")
113     private boolean mAnimationStartDeferred;
114 
115     // Mapping animation leashes to a list of edge extension surfaces associated with them
116     @GuardedBy("mEdgeExtensionLock")
117     private final ArrayMap<SurfaceControl, ArrayList<SurfaceControl>> mEdgeExtensions =
118             new ArrayMap<>();
119 
120     /**
121      * There should only ever be one instance of this class. Usual spot for it is with
122      * {@link WindowManagerService}
123      */
SurfaceAnimationRunner(Supplier<Transaction> transactionFactory, PowerManagerInternal powerManagerInternal)124     SurfaceAnimationRunner(Supplier<Transaction> transactionFactory,
125             PowerManagerInternal powerManagerInternal) {
126         this(null /* callbackProvider */, null /* animatorFactory */,
127                 transactionFactory.get(), powerManagerInternal);
128     }
129 
130     @VisibleForTesting
SurfaceAnimationRunner(@ullable AnimationFrameCallbackProvider callbackProvider, AnimatorFactory animatorFactory, Transaction frameTransaction, PowerManagerInternal powerManagerInternal)131     SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider,
132             AnimatorFactory animatorFactory, Transaction frameTransaction,
133             PowerManagerInternal powerManagerInternal) {
134         mSurfaceAnimationHandler.runWithScissors(() -> mChoreographer = getSfInstance(),
135                 0 /* timeout */);
136         mFrameTransaction = frameTransaction;
137         mAnimationHandler = new AnimationHandler();
138         mAnimationHandler.setProvider(callbackProvider != null
139                 ? callbackProvider
140                 : new SfVsyncFrameCallbackProvider(mChoreographer));
141         mAnimatorFactory = animatorFactory != null
142                 ? animatorFactory
143                 : SfValueAnimator::new;
144         mPowerManagerInternal = powerManagerInternal;
145     }
146 
147     /**
148      * Defers starting of animations until {@link #continueStartingAnimations} is called. This
149      * method is NOT nestable.
150      *
151      * @see #continueStartingAnimations
152      */
deferStartingAnimations()153     void deferStartingAnimations() {
154         synchronized (mLock) {
155             mAnimationStartDeferred = true;
156         }
157     }
158 
159     /**
160      * Continues starting of animations.
161      *
162      * @see #deferStartingAnimations
163      */
continueStartingAnimations()164     void continueStartingAnimations() {
165         synchronized (mLock) {
166             mAnimationStartDeferred = false;
167             if (!mPendingAnimations.isEmpty() && mPreProcessingAnimations.isEmpty()) {
168                 mChoreographer.postFrameCallback(this::startAnimations);
169             }
170         }
171     }
172 
startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t, Runnable finishCallback)173     void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
174             Runnable finishCallback) {
175         synchronized (mLock) {
176             final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
177                     finishCallback);
178             boolean requiresEdgeExtension = requiresEdgeExtension(a);
179 
180             if (requiresEdgeExtension) {
181                 final ArrayList<SurfaceControl> extensionSurfaces = new ArrayList<>();
182                 synchronized (mEdgeExtensionLock) {
183                     mEdgeExtensions.put(animationLeash, extensionSurfaces);
184                 }
185 
186                 mPreProcessingAnimations.put(animationLeash, runningAnim);
187 
188                 // We must wait for t to be committed since otherwise the leash doesn't have the
189                 // windows we want to screenshot and extend as children.
190                 t.addTransactionCommittedListener(mEdgeExtensionExecutor, () -> {
191                     if (!animationLeash.isValid()) {
192                         Log.e(TAG, "Animation leash is not valid");
193                         synchronized (mEdgeExtensionLock) {
194                             mEdgeExtensions.remove(animationLeash);
195                         }
196                         synchronized (mLock) {
197                             mPreProcessingAnimations.remove(animationLeash);
198                         }
199                         return;
200                     }
201                     final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec();
202 
203                     final Transaction edgeExtensionCreationTransaction = new Transaction();
204                     edgeExtendWindow(animationLeash,
205                             animationSpec.getRootTaskBounds(), animationSpec.getAnimation(),
206                             edgeExtensionCreationTransaction);
207 
208                     synchronized (mLock) {
209                         // only run if animation is not yet canceled by this point
210                         if (mPreProcessingAnimations.get(animationLeash) == runningAnim) {
211                             // In the case the animation is cancelled, edge extensions are removed
212                             // onAnimationLeashLost which is called before onAnimationCancelled.
213                             // So we need to check if the edge extensions have already been removed
214                             // or not, and if so we don't want to apply the transaction.
215                             synchronized (mEdgeExtensionLock) {
216                                 if (!mEdgeExtensions.isEmpty()) {
217                                     edgeExtensionCreationTransaction.apply();
218                                 }
219                             }
220 
221                             mPreProcessingAnimations.remove(animationLeash);
222                             mPendingAnimations.put(animationLeash, runningAnim);
223                             if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
224                                 mChoreographer.postFrameCallback(this::startAnimations);
225                             }
226                         }
227                     }
228                 });
229             }
230 
231             if (!requiresEdgeExtension) {
232                 mPendingAnimations.put(animationLeash, runningAnim);
233                 if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
234                     mChoreographer.postFrameCallback(this::startAnimations);
235                 }
236             }
237 
238             // Some animations (e.g. move animations) require the initial transform to be
239             // applied immediately.
240             applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
241         }
242     }
243 
requiresEdgeExtension(AnimationSpec a)244     private boolean requiresEdgeExtension(AnimationSpec a) {
245         return a.asWindowAnimationSpec() != null && a.asWindowAnimationSpec().hasExtension();
246     }
247 
onAnimationCancelled(SurfaceControl leash)248     void onAnimationCancelled(SurfaceControl leash) {
249         synchronized (mLock) {
250             if (mPendingAnimations.containsKey(leash)) {
251                 mPendingAnimations.remove(leash);
252                 return;
253             }
254             if (mPreProcessingAnimations.containsKey(leash)) {
255                 mPreProcessingAnimations.remove(leash);
256                 return;
257             }
258             final RunningAnimation anim = mRunningAnimations.get(leash);
259             if (anim != null) {
260                 mRunningAnimations.remove(leash);
261                 synchronized (mCancelLock) {
262                     anim.mCancelled = true;
263                 }
264                 mSurfaceAnimationHandler.post(() -> {
265                     anim.mAnim.cancel();
266                     applyTransaction();
267                 });
268             }
269         }
270     }
271 
272     @GuardedBy("mLock")
startPendingAnimationsLocked()273     private void startPendingAnimationsLocked() {
274         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
275             startAnimationLocked(mPendingAnimations.valueAt(i));
276         }
277         mPendingAnimations.clear();
278     }
279 
280     @GuardedBy("mLock")
startAnimationLocked(RunningAnimation a)281     private void startAnimationLocked(RunningAnimation a) {
282         final ValueAnimator anim = mAnimatorFactory.makeAnimator();
283 
284         // Animation length is already expected to be scaled.
285         anim.overrideDurationScale(1.0f);
286         anim.setDuration(a.mAnimSpec.getDuration());
287         anim.addUpdateListener(animation -> {
288             synchronized (mCancelLock) {
289                 if (!a.mCancelled) {
290                     final long duration = anim.getDuration();
291                     long currentPlayTime = anim.getCurrentPlayTime();
292                     if (currentPlayTime > duration) {
293                         currentPlayTime = duration;
294                     }
295                     applyTransformation(a, mFrameTransaction, currentPlayTime);
296                 }
297             }
298 
299             // Transaction will be applied in the commit phase.
300             scheduleApplyTransaction();
301         });
302 
303         anim.addListener(new AnimatorListenerAdapter() {
304             @Override
305             public void onAnimationStart(Animator animation) {
306                 synchronized (mCancelLock) {
307                     if (!a.mCancelled) {
308                         // TODO: change this back to use show instead of alpha when b/138459974 is
309                         // fixed.
310                         mFrameTransaction.setAlpha(a.mLeash, 1);
311                     }
312                 }
313             }
314 
315             @Override
316             public void onAnimationEnd(Animator animation) {
317                 synchronized (mLock) {
318                     mRunningAnimations.remove(a.mLeash);
319                     synchronized (mCancelLock) {
320                         if (!a.mCancelled) {
321 
322                             // Post on other thread that we can push final state without jank.
323                             mAnimationThreadHandler.post(a.mFinishCallback);
324                         }
325                     }
326                 }
327             }
328         });
329         a.mAnim = anim;
330         mRunningAnimations.put(a.mLeash, a);
331 
332         anim.start();
333         if (a.mAnimSpec.canSkipFirstFrame()) {
334             // If we can skip the first frame, we start one frame later.
335             anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
336         }
337 
338         // Immediately start the animation by manually applying an animation frame. Otherwise, the
339         // start time would only be set in the next frame, leading to a delay.
340         anim.doAnimationFrame(mChoreographer.getFrameTime());
341     }
342 
applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime)343     private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
344         a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);
345     }
346 
startAnimations(long frameTimeNanos)347     private void startAnimations(long frameTimeNanos) {
348         synchronized (mLock) {
349             if (!mPreProcessingAnimations.isEmpty()) {
350                 // We only want to start running animations once all mPreProcessingAnimations have
351                 // been processed to ensure preprocessed animations start in sync.
352                 // NOTE: This means we might delay running animations that require preprocessing if
353                 // new animations that also require preprocessing are requested before the previous
354                 // ones have finished (see b/227449117).
355                 return;
356             }
357             startPendingAnimationsLocked();
358         }
359         mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
360     }
361 
scheduleApplyTransaction()362     private void scheduleApplyTransaction() {
363         if (!mApplyScheduled) {
364             mChoreographer.postCallback(CALLBACK_TRAVERSAL, mApplyTransactionRunnable,
365                     null /* token */);
366             mApplyScheduled = true;
367         }
368     }
369 
applyTransaction()370     private void applyTransaction() {
371         mFrameTransaction.setAnimationTransaction();
372         mFrameTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
373         mFrameTransaction.apply();
374         mApplyScheduled = false;
375     }
376 
edgeExtendWindow(SurfaceControl leash, Rect bounds, Animation a, Transaction transaction)377     private void edgeExtendWindow(SurfaceControl leash, Rect bounds, Animation a,
378             Transaction transaction) {
379         final Transformation transformationAtStart = new Transformation();
380         a.getTransformationAt(0, transformationAtStart);
381         final Transformation transformationAtEnd = new Transformation();
382         a.getTransformationAt(1, transformationAtEnd);
383 
384         // We want to create an extension surface that is the maximal size and the animation will
385         // take care of cropping any part that overflows.
386         final Insets maxExtensionInsets = Insets.min(
387                 transformationAtStart.getInsets(), transformationAtEnd.getInsets());
388 
389         final int targetSurfaceHeight = bounds.height();
390         final int targetSurfaceWidth = bounds.width();
391 
392         if (maxExtensionInsets.left < 0) {
393             final Rect edgeBounds = new Rect(bounds.left, bounds.top, bounds.left + 1,
394                     bounds.bottom);
395             final Rect extensionRect = new Rect(0, 0,
396                     -maxExtensionInsets.left, targetSurfaceHeight);
397             final int xPos = bounds.left + maxExtensionInsets.left;
398             final int yPos = bounds.top;
399             createExtensionSurface(leash, edgeBounds,
400                     extensionRect, xPos, yPos, "Left Edge Extension", transaction);
401         }
402 
403         if (maxExtensionInsets.top < 0) {
404             final Rect edgeBounds = new Rect(bounds.left, bounds.top, targetSurfaceWidth,
405                     bounds.top + 1);
406             final Rect extensionRect = new Rect(0, 0,
407                     targetSurfaceWidth, -maxExtensionInsets.top);
408             final int xPos = bounds.left;
409             final int yPos = bounds.top + maxExtensionInsets.top;
410             createExtensionSurface(leash, edgeBounds,
411                     extensionRect, xPos, yPos, "Top Edge Extension", transaction);
412         }
413 
414         if (maxExtensionInsets.right < 0) {
415             final Rect edgeBounds = new Rect(bounds.right - 1, bounds.top, bounds.right,
416                     bounds.bottom);
417             final Rect extensionRect = new Rect(0, 0,
418                     -maxExtensionInsets.right, targetSurfaceHeight);
419             final int xPos = bounds.right;
420             final int yPos = bounds.top;
421             createExtensionSurface(leash, edgeBounds,
422                     extensionRect, xPos, yPos, "Right Edge Extension", transaction);
423         }
424 
425         if (maxExtensionInsets.bottom < 0) {
426             final Rect edgeBounds = new Rect(bounds.left, bounds.bottom - 1,
427                     bounds.right, bounds.bottom);
428             final Rect extensionRect = new Rect(0, 0,
429                     targetSurfaceWidth, -maxExtensionInsets.bottom);
430             final int xPos = bounds.left;
431             final int yPos = bounds.bottom;
432             createExtensionSurface(leash, edgeBounds,
433                     extensionRect, xPos, yPos, "Bottom Edge Extension", transaction);
434         }
435     }
436 
createExtensionSurface(SurfaceControl leash, Rect edgeBounds, Rect extensionRect, int xPos, int yPos, String layerName, Transaction startTransaction)437     private void createExtensionSurface(SurfaceControl leash, Rect edgeBounds,
438             Rect extensionRect, int xPos, int yPos, String layerName,
439             Transaction startTransaction) {
440         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createExtensionSurface");
441         doCreateExtensionSurface(leash, edgeBounds, extensionRect, xPos, yPos, layerName,
442                 startTransaction);
443         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
444     }
445 
doCreateExtensionSurface(SurfaceControl leash, Rect edgeBounds, Rect extensionRect, int xPos, int yPos, String layerName, Transaction startTransaction)446     private void doCreateExtensionSurface(SurfaceControl leash, Rect edgeBounds,
447             Rect extensionRect, int xPos, int yPos, String layerName,
448             Transaction startTransaction) {
449         ScreenCapture.LayerCaptureArgs captureArgs =
450                 new ScreenCapture.LayerCaptureArgs.Builder(leash /* surfaceToExtend */)
451                         .setSourceCrop(edgeBounds)
452                         .setFrameScale(1)
453                         .setPixelFormat(PixelFormat.RGBA_8888)
454                         .setChildrenOnly(true)
455                         .setAllowProtected(true)
456                         .setCaptureSecureLayers(true)
457                         .build();
458         final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
459                 ScreenCapture.captureLayers(captureArgs);
460 
461         if (edgeBuffer == null) {
462             // The leash we are trying to screenshot may have been removed by this point, which is
463             // likely the reason for ending up with a null edgeBuffer, in which case we just want to
464             // return and do nothing.
465             Log.e(TAG, "Failed to create edge extension - edge buffer is null");
466             return;
467         }
468 
469         final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
470                 .setName(layerName)
471                 .setHidden(true)
472                 .setCallsite("DefaultTransitionHandler#startAnimation")
473                 .setOpaque(true)
474                 .setBufferSize(extensionRect.width(), extensionRect.height())
475                 .build();
476 
477         BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
478                 android.graphics.Shader.TileMode.CLAMP,
479                 android.graphics.Shader.TileMode.CLAMP);
480         final Paint paint = new Paint();
481         paint.setShader(shader);
482 
483         final Surface surface = new Surface(edgeExtensionLayer);
484         Canvas c = surface.lockHardwareCanvas();
485         c.drawRect(extensionRect, paint);
486         surface.unlockCanvasAndPost(c);
487         surface.release();
488 
489         synchronized (mEdgeExtensionLock) {
490             if (!mEdgeExtensions.containsKey(leash)) {
491                 // The animation leash has already been removed, so we don't want to attach the
492                 // edgeExtension layer and should immediately remove it instead.
493                 startTransaction.remove(edgeExtensionLayer);
494                 return;
495             }
496 
497             startTransaction.reparent(edgeExtensionLayer, leash);
498             startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
499             startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
500             startTransaction.setVisibility(edgeExtensionLayer, true);
501 
502             mEdgeExtensions.get(leash).add(edgeExtensionLayer);
503         }
504     }
505 
getScaleXForExtensionSurface(Rect edgeBounds, Rect extensionRect)506     private float getScaleXForExtensionSurface(Rect edgeBounds, Rect extensionRect) {
507         if (edgeBounds.width() == extensionRect.width()) {
508             // Top or bottom edge extension, no need to scale the X axis of the extension surface.
509             return 1;
510         }
511         if (edgeBounds.width() == 1) {
512             // Left or right edge extension, scale the surface to be the extensionRect's width.
513             return extensionRect.width();
514         }
515 
516         throw new RuntimeException("Unexpected edgeBounds and extensionRect widths");
517     }
518 
getScaleYForExtensionSurface(Rect edgeBounds, Rect extensionRect)519     private float getScaleYForExtensionSurface(Rect edgeBounds, Rect extensionRect) {
520         if (edgeBounds.height() == extensionRect.height()) {
521             // Left or right edge extension, no need to scale the Y axis of the extension surface.
522             return 1;
523         }
524         if (edgeBounds.height() == 1) {
525             // Top or bottom edge extension, scale the surface to be the extensionRect's height.
526             return extensionRect.height();
527         }
528 
529         throw new RuntimeException("Unexpected edgeBounds and extensionRect heights");
530     }
531 
532     private static final class RunningAnimation {
533         final AnimationSpec mAnimSpec;
534         final SurfaceControl mLeash;
535         final Runnable mFinishCallback;
536         ValueAnimator mAnim;
537 
538         @GuardedBy("mCancelLock")
539         private boolean mCancelled;
540 
RunningAnimation(AnimationSpec animSpec, SurfaceControl leash, Runnable finishCallback)541         RunningAnimation(AnimationSpec animSpec, SurfaceControl leash, Runnable finishCallback) {
542             mAnimSpec = animSpec;
543             mLeash = leash;
544             mFinishCallback = finishCallback;
545         }
546     }
547 
onAnimationLeashLost(SurfaceControl animationLeash, Transaction t)548     protected void onAnimationLeashLost(SurfaceControl animationLeash,
549             Transaction t) {
550         synchronized (mEdgeExtensionLock) {
551             if (!mEdgeExtensions.containsKey(animationLeash)) {
552                 return;
553             }
554 
555             final ArrayList<SurfaceControl> edgeExtensions = mEdgeExtensions.get(animationLeash);
556             for (int i = 0; i < edgeExtensions.size(); i++) {
557                 final SurfaceControl extension = edgeExtensions.get(i);
558                 t.remove(extension);
559             }
560             mEdgeExtensions.remove(animationLeash);
561         }
562     }
563 
564     @VisibleForTesting
565     interface AnimatorFactory {
makeAnimator()566         ValueAnimator makeAnimator();
567     }
568 
569     /**
570      * Value animator that uses sf-vsync signal to tick.
571      */
572     private class SfValueAnimator extends ValueAnimator {
573 
SfValueAnimator()574         SfValueAnimator() {
575             setFloatValues(0f, 1f);
576         }
577 
578         @Override
getAnimationHandler()579         public AnimationHandler getAnimationHandler() {
580             return mAnimationHandler;
581         }
582     }
583 }