1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package android.graphics.drawable; 16 17 import android.animation.Animator; 18 import android.animation.Animator.AnimatorListener; 19 import android.animation.AnimatorInflater; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.animation.PropertyValuesHolder; 24 import android.animation.TimeInterpolator; 25 import android.animation.ValueAnimator; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.app.ActivityThread; 29 import android.app.Application; 30 import android.compat.annotation.UnsupportedAppUsage; 31 import android.content.pm.ActivityInfo.Config; 32 import android.content.res.ColorStateList; 33 import android.content.res.Resources; 34 import android.content.res.Resources.Theme; 35 import android.content.res.TypedArray; 36 import android.graphics.BlendMode; 37 import android.graphics.Canvas; 38 import android.graphics.ColorFilter; 39 import android.graphics.Insets; 40 import android.graphics.Outline; 41 import android.graphics.PixelFormat; 42 import android.graphics.RecordingCanvas; 43 import android.graphics.Rect; 44 import android.graphics.RenderNode; 45 import android.os.Build; 46 import android.os.Handler; 47 import android.util.ArrayMap; 48 import android.util.AttributeSet; 49 import android.util.IntArray; 50 import android.util.Log; 51 import android.util.LongArray; 52 import android.util.PathParser; 53 import android.util.Property; 54 import android.util.TimeUtils; 55 import android.view.Choreographer; 56 import android.view.NativeVectorDrawableAnimator; 57 import android.view.RenderNodeAnimatorSetHelper; 58 import android.view.View; 59 60 import com.android.internal.R; 61 import com.android.internal.util.VirtualRefBasePtr; 62 63 import dalvik.annotation.optimization.FastNative; 64 65 import org.xmlpull.v1.XmlPullParser; 66 import org.xmlpull.v1.XmlPullParserException; 67 68 import java.io.IOException; 69 import java.lang.ref.WeakReference; 70 import java.util.ArrayList; 71 72 73 /** 74 * This class animates properties of a {@link android.graphics.drawable.VectorDrawable} with 75 * animations defined using {@link android.animation.ObjectAnimator} or 76 * {@link android.animation.AnimatorSet}. 77 * <p> 78 * Starting from API 25, AnimatedVectorDrawable runs on RenderThread (as opposed to on UI thread for 79 * earlier APIs). This means animations in AnimatedVectorDrawable can remain smooth even when there 80 * is heavy workload on the UI thread. Note: If the UI thread is unresponsive, RenderThread may 81 * continue animating until the UI thread is capable of pushing another frame. Therefore, it is not 82 * possible to precisely coordinate a RenderThread-enabled AnimatedVectorDrawable with UI thread 83 * animations. Additionally, 84 * {@link android.graphics.drawable.Animatable2.AnimationCallback#onAnimationEnd(Drawable)} will be 85 * called the frame after the AnimatedVectorDrawable finishes on the RenderThread. 86 * </p> 87 * <p> 88 * AnimatedVectorDrawable can be defined in either <a href="#ThreeXML">three separate XML files</a>, 89 * or <a href="#OneXML">one XML</a>. 90 * </p> 91 * <a name="ThreeXML"></a> 92 * <h3>Define an AnimatedVectorDrawable in three separate XML files</h3> 93 * <ul> 94 * <a name="VDExample"></a> 95 * <li><h4>XML for the VectorDrawable containing properties to be animated</h4> 96 * <p> 97 * Animations can be performed on the animatable attributes in 98 * {@link android.graphics.drawable.VectorDrawable}. These attributes will be animated by 99 * {@link android.animation.ObjectAnimator}. The ObjectAnimator's target can be the root element, 100 * a group element or a path element. The targeted elements need to be named uniquely within 101 * the same VectorDrawable. Elements without animation do not need to be named. 102 * </p> 103 * <p> 104 * Here are all the animatable attributes in {@link android.graphics.drawable.VectorDrawable}: 105 * <table border="2" align="center" cellpadding="5"> 106 * <thead> 107 * <tr> 108 * <th>Element Name</th> 109 * <th>Animatable attribute name</th> 110 * </tr> 111 * </thead> 112 * <tr> 113 * <td><vector></td> 114 * <td>alpha</td> 115 * </tr> 116 * <tr> 117 * <td rowspan="7"><group></td> 118 * <td>rotation</td> 119 * </tr> 120 * <tr> 121 * <td>pivotX</td> 122 * </tr> 123 * <tr> 124 * <td>pivotY</td> 125 * </tr> 126 * <tr> 127 * <td>scaleX</td> 128 * </tr> 129 * <tr> 130 * <td>scaleY</td> 131 * </tr> 132 * <tr> 133 * <td>translateX</td> 134 * </tr> 135 * <tr> 136 * <td>translateY</td> 137 * </tr> 138 * <tr> 139 * <td rowspan="9"><path></td> 140 * <td>pathData</td> 141 * </tr> 142 * <tr> 143 * <td>fillColor</td> 144 * </tr> 145 * <tr> 146 * <td>strokeColor</td> 147 * </tr> 148 * <tr> 149 * <td>strokeWidth</td> 150 * </tr> 151 * <tr> 152 * <td>strokeAlpha</td> 153 * </tr> 154 * <tr> 155 * <td>fillAlpha</td> 156 * </tr> 157 * <tr> 158 * <td>trimPathStart</td> 159 * </tr> 160 * <tr> 161 * <td>trimPathEnd</td> 162 * </tr> 163 * <tr> 164 * <td>trimPathOffset</td> 165 * </tr> 166 * <tr> 167 * <td><clip-path></td> 168 * <td>pathData</td> 169 * </tr> 170 * </table> 171 * </p> 172 * Below is an example of a VectorDrawable defined in vectordrawable.xml. This VectorDrawable is 173 * referred to by its file name (not including file suffix) in the 174 * <a href="#AVDExample">AnimatedVectorDrawable XML example</a>. 175 * <pre> 176 * <vector xmlns:android="http://schemas.android.com/apk/res/android" 177 * android:height="64dp" 178 * android:width="64dp" 179 * android:viewportHeight="600" 180 * android:viewportWidth="600" > 181 * <group 182 * android:name="rotationGroup" 183 * android:pivotX="300.0" 184 * android:pivotY="300.0" 185 * android:rotation="45.0" > 186 * <path 187 * android:name="v" 188 * android:fillColor="#000000" 189 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 190 * </group> 191 * </vector> 192 * </pre></li> 193 * 194 * <a name="AVDExample"></a> 195 * <li><h4>XML for AnimatedVectorDrawable</h4> 196 * <p> 197 * An AnimatedVectorDrawable element has a VectorDrawable attribute, and one or more target 198 * element(s). The target element can specify its target by android:name attribute, and link the 199 * target with the proper ObjectAnimator or AnimatorSet by android:animation attribute. 200 * </p> 201 * The following code sample defines an AnimatedVectorDrawable. Note that the names refer to the 202 * groups and paths in the <a href="#VDExample">VectorDrawable XML above</a>. 203 * <pre> 204 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" 205 * android:drawable="@drawable/vectordrawable" > 206 * <target 207 * android:name="rotationGroup" 208 * android:animation="@animator/rotation" /> 209 * <target 210 * android:name="v" 211 * android:animation="@animator/path_morph" /> 212 * </animated-vector> 213 * </pre> 214 * </li> 215 * 216 * <li><h4>XML for Animations defined using ObjectAnimator or AnimatorSet</h4> 217 * <p> 218 * From the previous <a href="#AVDExample">example of AnimatedVectorDrawable</a>, two animations 219 * were used: rotation.xml and path_morph.xml. 220 * </p> 221 * rotation.xml rotates the target group from 0 degree to 360 degrees over 6000ms: 222 * <pre> 223 * <objectAnimator 224 * android:duration="6000" 225 * android:propertyName="rotation" 226 * android:valueFrom="0" 227 * android:valueTo="360" /> 228 * </pre> 229 * 230 * path_morph.xml morphs the path from one shape into the other. Note that the paths must be 231 * compatible for morphing. Specifically, the paths must have the same commands, in the same order, 232 * and must have the same number of parameters for each command. It is recommended to store path 233 * strings as string resources for reuse. 234 * <pre> 235 * <set xmlns:android="http://schemas.android.com/apk/res/android"> 236 * <objectAnimator 237 * android:duration="3000" 238 * android:propertyName="pathData" 239 * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z" 240 * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z" 241 * android:valueType="pathType"/> 242 * </set> 243 * </pre> 244 * </ul> 245 * <a name="OneXML"></a> 246 * <h3>Define an AnimatedVectorDrawable all in one XML file</h3> 247 * <p> 248 * Since the AAPT tool supports a new format that bundles several related XML files together, we can 249 * merge the XML files from the previous examples into one XML file: 250 * </p> 251 * <pre> 252 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" 253 * xmlns:aapt="http://schemas.android.com/aapt" > 254 * <aapt:attr name="android:drawable"> 255 * <vector 256 * android:height="64dp" 257 * android:width="64dp" 258 * android:viewportHeight="600" 259 * android:viewportWidth="600" > 260 * <group 261 * android:name="rotationGroup" 262 * android:pivotX="300.0" 263 * android:pivotY="300.0" 264 * android:rotation="45.0" > 265 * <path 266 * android:name="v" 267 * android:fillColor="#000000" 268 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 269 * </group> 270 * </vector> 271 * </aapt:attr> 272 * 273 * <target android:name="rotationGroup"> * 274 * <aapt:attr name="android:animation"> 275 * <objectAnimator 276 * android:duration="6000" 277 * android:propertyName="rotation" 278 * android:valueFrom="0" 279 * android:valueTo="360" /> 280 * </aapt:attr> 281 * </target> 282 * 283 * <target android:name="v" > 284 * <aapt:attr name="android:animation"> 285 * <set> 286 * <objectAnimator 287 * android:duration="3000" 288 * android:propertyName="pathData" 289 * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z" 290 * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z" 291 * android:valueType="pathType"/> 292 * </set> 293 * </aapt:attr> 294 * </target> 295 * </animated-vector> 296 * </pre> 297 * 298 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable 299 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name 300 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation 301 */ 302 public class AnimatedVectorDrawable extends Drawable implements Animatable2 { 303 private static final String LOGTAG = "AnimatedVectorDrawable"; 304 305 private static final String ANIMATED_VECTOR = "animated-vector"; 306 private static final String TARGET = "target"; 307 308 private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; 309 310 /** Local, mutable animator set. */ 311 @UnsupportedAppUsage 312 private VectorDrawableAnimator mAnimatorSet; 313 314 /** 315 * The resources against which this drawable was created. Used to attempt 316 * to inflate animators if applyTheme() doesn't get called. 317 */ 318 private Resources mRes; 319 320 @UnsupportedAppUsage 321 private AnimatedVectorDrawableState mAnimatedVectorState; 322 323 /** The animator set that is parsed from the xml. */ 324 private AnimatorSet mAnimatorSetFromXml = null; 325 326 private boolean mMutated; 327 328 /** Use a internal AnimatorListener to support callbacks during animation events. */ 329 private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null; 330 private AnimatorListener mAnimatorListener = null; 331 AnimatedVectorDrawable()332 public AnimatedVectorDrawable() { 333 this(null, null); 334 } 335 AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res)336 private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) { 337 mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res); 338 mAnimatorSet = new VectorDrawableAnimatorRT(this); 339 mRes = res; 340 } 341 342 @Override mutate()343 public Drawable mutate() { 344 if (!mMutated && super.mutate() == this) { 345 mAnimatedVectorState = new AnimatedVectorDrawableState( 346 mAnimatedVectorState, mCallback, mRes); 347 mMutated = true; 348 } 349 return this; 350 } 351 352 /** 353 * @hide 354 */ clearMutated()355 public void clearMutated() { 356 super.clearMutated(); 357 if (mAnimatedVectorState.mVectorDrawable != null) { 358 mAnimatedVectorState.mVectorDrawable.clearMutated(); 359 } 360 mMutated = false; 361 } 362 363 /** 364 * In order to avoid breaking old apps, we only throw exception on invalid VectorDrawable 365 * animations for apps targeting N and later. For older apps, we ignore (i.e. quietly skip) 366 * these animations. 367 * 368 * @return whether invalid animations for vector drawable should be ignored. 369 */ shouldIgnoreInvalidAnimation()370 private static boolean shouldIgnoreInvalidAnimation() { 371 Application app = ActivityThread.currentApplication(); 372 if (app == null || app.getApplicationInfo() == null) { 373 return true; 374 } 375 if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 376 return true; 377 } 378 return false; 379 } 380 381 @Override getConstantState()382 public ConstantState getConstantState() { 383 mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations(); 384 return mAnimatedVectorState; 385 } 386 387 @Override getChangingConfigurations()388 public @Config int getChangingConfigurations() { 389 return super.getChangingConfigurations() | mAnimatedVectorState.getChangingConfigurations(); 390 } 391 392 /** 393 * Draws the AnimatedVectorDrawable into the given canvas. 394 * <p> 395 * <strong>Note:</strong> Calling this method with a software canvas when the 396 * AnimatedVectorDrawable is being animated on RenderThread (for API 25 and later) may yield 397 * outdated result, as the UI thread is not guaranteed to be in sync with RenderThread on 398 * VectorDrawable's property changes during RenderThread animations. 399 * </p> 400 * 401 * @param canvas The canvas to draw into 402 */ 403 @Override draw(Canvas canvas)404 public void draw(Canvas canvas) { 405 if (!canvas.isHardwareAccelerated() && mAnimatorSet instanceof VectorDrawableAnimatorRT) { 406 // If we have SW canvas and the RT animation is waiting to start, We need to fallback 407 // to UI thread animation for AVD. 408 if (!mAnimatorSet.isRunning() && 409 ((VectorDrawableAnimatorRT) mAnimatorSet).mPendingAnimationActions.size() > 0) { 410 fallbackOntoUI(); 411 } 412 } 413 mAnimatorSet.onDraw(canvas); 414 mAnimatedVectorState.mVectorDrawable.draw(canvas); 415 } 416 417 @Override onBoundsChange(Rect bounds)418 protected void onBoundsChange(Rect bounds) { 419 mAnimatedVectorState.mVectorDrawable.setBounds(bounds); 420 } 421 422 @Override onStateChange(int[] state)423 protected boolean onStateChange(int[] state) { 424 return mAnimatedVectorState.mVectorDrawable.setState(state); 425 } 426 427 @Override onLevelChange(int level)428 protected boolean onLevelChange(int level) { 429 return mAnimatedVectorState.mVectorDrawable.setLevel(level); 430 } 431 432 @Override onLayoutDirectionChanged(@iew.ResolvedLayoutDir int layoutDirection)433 public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) { 434 return mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection); 435 } 436 437 /** 438 * For API 25 and later, AnimatedVectorDrawable runs on RenderThread. Therefore, when the 439 * root alpha is being animated, this getter does not guarantee to return an up-to-date alpha 440 * value. 441 * 442 * @return the containing vector drawable's root alpha value. 443 */ 444 @Override getAlpha()445 public int getAlpha() { 446 return mAnimatedVectorState.mVectorDrawable.getAlpha(); 447 } 448 449 @Override setAlpha(int alpha)450 public void setAlpha(int alpha) { 451 mAnimatedVectorState.mVectorDrawable.setAlpha(alpha); 452 } 453 454 @Override setColorFilter(ColorFilter colorFilter)455 public void setColorFilter(ColorFilter colorFilter) { 456 mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter); 457 } 458 459 @Override getColorFilter()460 public ColorFilter getColorFilter() { 461 return mAnimatedVectorState.mVectorDrawable.getColorFilter(); 462 } 463 464 @Override setTintList(ColorStateList tint)465 public void setTintList(ColorStateList tint) { 466 mAnimatedVectorState.mVectorDrawable.setTintList(tint); 467 } 468 469 @Override setHotspot(float x, float y)470 public void setHotspot(float x, float y) { 471 mAnimatedVectorState.mVectorDrawable.setHotspot(x, y); 472 } 473 474 @Override setHotspotBounds(int left, int top, int right, int bottom)475 public void setHotspotBounds(int left, int top, int right, int bottom) { 476 mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom); 477 } 478 479 @Override setTintBlendMode(@onNull BlendMode blendMode)480 public void setTintBlendMode(@NonNull BlendMode blendMode) { 481 mAnimatedVectorState.mVectorDrawable.setTintBlendMode(blendMode); 482 } 483 484 @Override setVisible(boolean visible, boolean restart)485 public boolean setVisible(boolean visible, boolean restart) { 486 if (mAnimatorSet.isInfinite() && mAnimatorSet.isStarted()) { 487 if (visible) { 488 // Resume the infinite animation when the drawable becomes visible again. 489 mAnimatorSet.resume(); 490 } else { 491 // Pause the infinite animation once the drawable is no longer visible. 492 mAnimatorSet.pause(); 493 } 494 } 495 mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart); 496 return super.setVisible(visible, restart); 497 } 498 499 @Override isStateful()500 public boolean isStateful() { 501 return mAnimatedVectorState.mVectorDrawable.isStateful(); 502 } 503 504 @Override getOpacity()505 public int getOpacity() { 506 return PixelFormat.TRANSLUCENT; 507 } 508 509 @Override getIntrinsicWidth()510 public int getIntrinsicWidth() { 511 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); 512 } 513 514 @Override getIntrinsicHeight()515 public int getIntrinsicHeight() { 516 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); 517 } 518 519 @Override getOutline(@onNull Outline outline)520 public void getOutline(@NonNull Outline outline) { 521 mAnimatedVectorState.mVectorDrawable.getOutline(outline); 522 } 523 524 @Override getOpticalInsets()525 public Insets getOpticalInsets() { 526 return mAnimatedVectorState.mVectorDrawable.getOpticalInsets(); 527 } 528 529 @Override inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)530 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) 531 throws XmlPullParserException, IOException { 532 final AnimatedVectorDrawableState state = mAnimatedVectorState; 533 534 int eventType = parser.getEventType(); 535 float pathErrorScale = 1; 536 final int innerDepth = parser.getDepth() + 1; 537 538 // Parse everything until the end of the animated-vector element. 539 while (eventType != XmlPullParser.END_DOCUMENT 540 && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) { 541 if (eventType == XmlPullParser.START_TAG) { 542 final String tagName = parser.getName(); 543 if (ANIMATED_VECTOR.equals(tagName)) { 544 final TypedArray a = obtainAttributes(res, theme, attrs, 545 R.styleable.AnimatedVectorDrawable); 546 int drawableRes = a.getResourceId( 547 R.styleable.AnimatedVectorDrawable_drawable, 0); 548 if (drawableRes != 0) { 549 VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable( 550 drawableRes, theme).mutate(); 551 vectorDrawable.setAllowCaching(false); 552 vectorDrawable.setCallback(mCallback); 553 pathErrorScale = vectorDrawable.getPixelSize(); 554 if (state.mVectorDrawable != null) { 555 state.mVectorDrawable.setCallback(null); 556 } 557 state.mVectorDrawable = vectorDrawable; 558 } 559 a.recycle(); 560 } else if (TARGET.equals(tagName)) { 561 final TypedArray a = obtainAttributes(res, theme, attrs, 562 R.styleable.AnimatedVectorDrawableTarget); 563 final String target = a.getString( 564 R.styleable.AnimatedVectorDrawableTarget_name); 565 final int animResId = a.getResourceId( 566 R.styleable.AnimatedVectorDrawableTarget_animation, 0); 567 if (animResId != 0) { 568 if (theme != null) { 569 // The animator here could be ObjectAnimator or AnimatorSet. 570 final Animator animator = AnimatorInflater.loadAnimator( 571 res, theme, animResId, pathErrorScale); 572 updateAnimatorProperty(animator, target, state.mVectorDrawable, 573 state.mShouldIgnoreInvalidAnim); 574 state.addTargetAnimator(target, animator); 575 } else { 576 // The animation may be theme-dependent. As a 577 // workaround until Animator has full support for 578 // applyTheme(), postpone loading the animator 579 // until we have a theme in applyTheme(). 580 state.addPendingAnimator(animResId, pathErrorScale, target); 581 582 } 583 } 584 a.recycle(); 585 } 586 } 587 588 eventType = parser.next(); 589 } 590 591 // If we don't have any pending animations, we don't need to hold a 592 // reference to the resources. 593 mRes = state.mPendingAnims == null ? null : res; 594 } 595 updateAnimatorProperty(Animator animator, String targetName, VectorDrawable vectorDrawable, boolean ignoreInvalidAnim)596 private static void updateAnimatorProperty(Animator animator, String targetName, 597 VectorDrawable vectorDrawable, boolean ignoreInvalidAnim) { 598 if (animator instanceof ObjectAnimator) { 599 // Change the property of the Animator from using reflection based on the property 600 // name to a Property object that wraps the setter and getter for modifying that 601 // specific property for a given object. By replacing the reflection with a direct call, 602 // we can largely reduce the time it takes for a animator to modify a VD property. 603 PropertyValuesHolder[] holders = ((ObjectAnimator) animator).getValues(); 604 for (int i = 0; i < holders.length; i++) { 605 PropertyValuesHolder pvh = holders[i]; 606 String propertyName = pvh.getPropertyName(); 607 Object targetNameObj = vectorDrawable.getTargetByName(targetName); 608 Property property = null; 609 if (targetNameObj instanceof VectorDrawable.VObject) { 610 property = ((VectorDrawable.VObject) targetNameObj).getProperty(propertyName); 611 } else if (targetNameObj instanceof VectorDrawable.VectorDrawableState) { 612 property = ((VectorDrawable.VectorDrawableState) targetNameObj) 613 .getProperty(propertyName); 614 } 615 if (property != null) { 616 if (containsSameValueType(pvh, property)) { 617 pvh.setProperty(property); 618 } else if (!ignoreInvalidAnim) { 619 throw new RuntimeException("Wrong valueType for Property: " + propertyName 620 + ". Expected type: " + property.getType().toString() + ". Actual " 621 + "type defined in resources: " + pvh.getValueType().toString()); 622 623 } 624 } 625 } 626 } else if (animator instanceof AnimatorSet) { 627 for (Animator anim : ((AnimatorSet) animator).getChildAnimations()) { 628 updateAnimatorProperty(anim, targetName, vectorDrawable, ignoreInvalidAnim); 629 } 630 } 631 } 632 containsSameValueType(PropertyValuesHolder holder, Property property)633 private static boolean containsSameValueType(PropertyValuesHolder holder, Property property) { 634 Class type1 = holder.getValueType(); 635 Class type2 = property.getType(); 636 if (type1 == float.class || type1 == Float.class) { 637 return type2 == float.class || type2 == Float.class; 638 } else if (type1 == int.class || type1 == Integer.class) { 639 return type2 == int.class || type2 == Integer.class; 640 } else { 641 return type1 == type2; 642 } 643 } 644 645 /** 646 * Force to animate on UI thread. 647 * @hide 648 */ 649 @UnsupportedAppUsage forceAnimationOnUI()650 public void forceAnimationOnUI() { 651 if (mAnimatorSet instanceof VectorDrawableAnimatorRT) { 652 VectorDrawableAnimatorRT animator = (VectorDrawableAnimatorRT) mAnimatorSet; 653 if (animator.isRunning()) { 654 throw new UnsupportedOperationException("Cannot force Animated Vector Drawable to" + 655 " run on UI thread when the animation has started on RenderThread."); 656 } 657 fallbackOntoUI(); 658 } 659 } 660 fallbackOntoUI()661 private void fallbackOntoUI() { 662 if (mAnimatorSet instanceof VectorDrawableAnimatorRT) { 663 VectorDrawableAnimatorRT oldAnim = (VectorDrawableAnimatorRT) mAnimatorSet; 664 mAnimatorSet = new VectorDrawableAnimatorUI(this); 665 if (mAnimatorSetFromXml != null) { 666 mAnimatorSet.init(mAnimatorSetFromXml); 667 } 668 // Transfer the listener from RT animator to UI animator 669 if (oldAnim.mListener != null) { 670 mAnimatorSet.setListener(oldAnim.mListener); 671 } 672 oldAnim.transferPendingActions(mAnimatorSet); 673 } 674 } 675 676 @Override canApplyTheme()677 public boolean canApplyTheme() { 678 return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme()) 679 || super.canApplyTheme(); 680 } 681 682 @Override applyTheme(Theme t)683 public void applyTheme(Theme t) { 684 super.applyTheme(t); 685 686 final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable; 687 if (vectorDrawable != null && vectorDrawable.canApplyTheme()) { 688 vectorDrawable.applyTheme(t); 689 } 690 691 if (t != null) { 692 mAnimatedVectorState.inflatePendingAnimators(t.getResources(), t); 693 } 694 695 // If we don't have any pending animations, we don't need to hold a 696 // reference to the resources. 697 if (mAnimatedVectorState.mPendingAnims == null) { 698 mRes = null; 699 } 700 } 701 702 private static class AnimatedVectorDrawableState extends ConstantState { 703 @Config int mChangingConfigurations; 704 VectorDrawable mVectorDrawable; 705 706 private final boolean mShouldIgnoreInvalidAnim; 707 708 /** Animators that require a theme before inflation. */ 709 ArrayList<PendingAnimator> mPendingAnims; 710 711 /** Fully inflated animators awaiting cloning into an AnimatorSet. */ 712 ArrayList<Animator> mAnimators; 713 714 /** Map of animators to their target object names */ 715 ArrayMap<Animator, String> mTargetNameMap; 716 AnimatedVectorDrawableState(AnimatedVectorDrawableState copy, Callback owner, Resources res)717 public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy, 718 Callback owner, Resources res) { 719 mShouldIgnoreInvalidAnim = shouldIgnoreInvalidAnimation(); 720 if (copy != null) { 721 mChangingConfigurations = copy.mChangingConfigurations; 722 723 if (copy.mVectorDrawable != null) { 724 final ConstantState cs = copy.mVectorDrawable.getConstantState(); 725 if (res != null) { 726 mVectorDrawable = (VectorDrawable) cs.newDrawable(res); 727 } else { 728 mVectorDrawable = (VectorDrawable) cs.newDrawable(); 729 } 730 mVectorDrawable = (VectorDrawable) mVectorDrawable.mutate(); 731 mVectorDrawable.setCallback(owner); 732 mVectorDrawable.setLayoutDirection(copy.mVectorDrawable.getLayoutDirection()); 733 mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds()); 734 mVectorDrawable.setAllowCaching(false); 735 } 736 737 if (copy.mAnimators != null) { 738 mAnimators = new ArrayList<>(copy.mAnimators); 739 } 740 741 if (copy.mTargetNameMap != null) { 742 mTargetNameMap = new ArrayMap<>(copy.mTargetNameMap); 743 } 744 745 if (copy.mPendingAnims != null) { 746 mPendingAnims = new ArrayList<>(copy.mPendingAnims); 747 } 748 } else { 749 mVectorDrawable = new VectorDrawable(); 750 } 751 } 752 753 @Override canApplyTheme()754 public boolean canApplyTheme() { 755 return (mVectorDrawable != null && mVectorDrawable.canApplyTheme()) 756 || mPendingAnims != null || super.canApplyTheme(); 757 } 758 759 @Override newDrawable()760 public Drawable newDrawable() { 761 return new AnimatedVectorDrawable(this, null); 762 } 763 764 @Override newDrawable(Resources res)765 public Drawable newDrawable(Resources res) { 766 return new AnimatedVectorDrawable(this, res); 767 } 768 769 @Override getChangingConfigurations()770 public @Config int getChangingConfigurations() { 771 return mChangingConfigurations; 772 } 773 addPendingAnimator(int resId, float pathErrorScale, String target)774 public void addPendingAnimator(int resId, float pathErrorScale, String target) { 775 if (mPendingAnims == null) { 776 mPendingAnims = new ArrayList<>(1); 777 } 778 mPendingAnims.add(new PendingAnimator(resId, pathErrorScale, target)); 779 } 780 addTargetAnimator(String targetName, Animator animator)781 public void addTargetAnimator(String targetName, Animator animator) { 782 if (mAnimators == null) { 783 mAnimators = new ArrayList<>(1); 784 mTargetNameMap = new ArrayMap<>(1); 785 } 786 mAnimators.add(animator); 787 mTargetNameMap.put(animator, targetName); 788 789 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 790 Log.v(LOGTAG, "add animator for target " + targetName + " " + animator); 791 } 792 } 793 794 /** 795 * Prepares a local set of mutable animators based on the constant 796 * state. 797 * <p> 798 * If there are any pending uninflated animators, attempts to inflate 799 * them immediately against the provided resources object. 800 * 801 * @param animatorSet the animator set to which the animators should 802 * be added 803 * @param res the resources against which to inflate any pending 804 * animators, or {@code null} if not available 805 */ prepareLocalAnimators(@onNull AnimatorSet animatorSet, @Nullable Resources res)806 public void prepareLocalAnimators(@NonNull AnimatorSet animatorSet, 807 @Nullable Resources res) { 808 // Check for uninflated animators. We can remove this after we add 809 // support for Animator.applyTheme(). See comments in inflate(). 810 if (mPendingAnims != null) { 811 // Attempt to load animators without applying a theme. 812 if (res != null) { 813 inflatePendingAnimators(res, null); 814 } else { 815 Log.e(LOGTAG, "Failed to load animators. Either the AnimatedVectorDrawable" 816 + " must be created using a Resources object or applyTheme() must be" 817 + " called with a non-null Theme object."); 818 } 819 820 mPendingAnims = null; 821 } 822 823 // Perform a deep copy of the constant state's animators. 824 final int count = mAnimators == null ? 0 : mAnimators.size(); 825 if (count > 0) { 826 final Animator firstAnim = prepareLocalAnimator(0); 827 final AnimatorSet.Builder builder = animatorSet.play(firstAnim); 828 for (int i = 1; i < count; ++i) { 829 final Animator nextAnim = prepareLocalAnimator(i); 830 builder.with(nextAnim); 831 } 832 } 833 } 834 835 /** 836 * Prepares a local animator for the given index within the constant 837 * state's list of animators. 838 * 839 * @param index the index of the animator within the constant state 840 */ prepareLocalAnimator(int index)841 private Animator prepareLocalAnimator(int index) { 842 final Animator animator = mAnimators.get(index); 843 final Animator localAnimator = animator.clone(); 844 final String targetName = mTargetNameMap.get(animator); 845 final Object target = mVectorDrawable.getTargetByName(targetName); 846 if (!mShouldIgnoreInvalidAnim) { 847 if (target == null) { 848 throw new IllegalStateException("Target with the name \"" + targetName 849 + "\" cannot be found in the VectorDrawable to be animated."); 850 } else if (!(target instanceof VectorDrawable.VectorDrawableState) 851 && !(target instanceof VectorDrawable.VObject)) { 852 throw new UnsupportedOperationException("Target should be either VGroup, VPath," 853 + " or ConstantState, " + target.getClass() + " is not supported"); 854 } 855 } 856 localAnimator.setTarget(target); 857 return localAnimator; 858 } 859 860 /** 861 * Inflates pending animators, if any, against a theme. Clears the list of 862 * pending animators. 863 * 864 * @param t the theme against which to inflate the animators 865 */ inflatePendingAnimators(@onNull Resources res, @Nullable Theme t)866 public void inflatePendingAnimators(@NonNull Resources res, @Nullable Theme t) { 867 final ArrayList<PendingAnimator> pendingAnims = mPendingAnims; 868 if (pendingAnims != null) { 869 mPendingAnims = null; 870 871 for (int i = 0, count = pendingAnims.size(); i < count; i++) { 872 final PendingAnimator pendingAnimator = pendingAnims.get(i); 873 final Animator animator = pendingAnimator.newInstance(res, t); 874 updateAnimatorProperty(animator, pendingAnimator.target, mVectorDrawable, 875 mShouldIgnoreInvalidAnim); 876 addTargetAnimator(pendingAnimator.target, animator); 877 } 878 } 879 } 880 881 /** 882 * Basically a constant state for Animators until we actually implement 883 * constant states for Animators. 884 */ 885 private static class PendingAnimator { 886 public final int animResId; 887 public final float pathErrorScale; 888 public final String target; 889 PendingAnimator(int animResId, float pathErrorScale, String target)890 public PendingAnimator(int animResId, float pathErrorScale, String target) { 891 this.animResId = animResId; 892 this.pathErrorScale = pathErrorScale; 893 this.target = target; 894 } 895 newInstance(Resources res, Theme theme)896 public Animator newInstance(Resources res, Theme theme) { 897 return AnimatorInflater.loadAnimator(res, theme, animResId, pathErrorScale); 898 } 899 } 900 } 901 902 @Override isRunning()903 public boolean isRunning() { 904 return mAnimatorSet.isRunning(); 905 } 906 907 /** 908 * Resets the AnimatedVectorDrawable to the start state as specified in the animators. 909 */ reset()910 public void reset() { 911 ensureAnimatorSet(); 912 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 913 Log.w(LOGTAG, "calling reset on AVD: " + 914 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState) 915 getConstantState()).mVectorDrawable.getConstantState()).mRootName 916 + ", at: " + this); 917 } 918 mAnimatorSet.reset(); 919 } 920 921 @Override start()922 public void start() { 923 ensureAnimatorSet(); 924 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 925 Log.w(LOGTAG, "calling start on AVD: " + 926 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState) 927 getConstantState()).mVectorDrawable.getConstantState()).mRootName 928 + ", at: " + this); 929 } 930 mAnimatorSet.start(); 931 } 932 933 @NonNull ensureAnimatorSet()934 private void ensureAnimatorSet() { 935 if (mAnimatorSetFromXml == null) { 936 // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly 937 // with a list of LocalAnimators. 938 mAnimatorSetFromXml = new AnimatorSet(); 939 mAnimatedVectorState.prepareLocalAnimators(mAnimatorSetFromXml, mRes); 940 mAnimatorSet.init(mAnimatorSetFromXml); 941 mRes = null; 942 } 943 } 944 945 @Override stop()946 public void stop() { 947 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 948 Log.w(LOGTAG, "calling stop on AVD: " + 949 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState) 950 getConstantState()).mVectorDrawable.getConstantState()) 951 .mRootName + ", at: " + this); 952 } 953 mAnimatorSet.end(); 954 } 955 956 /** 957 * Reverses ongoing animations or starts pending animations in reverse. 958 * <p> 959 * NOTE: Only works if all animations support reverse. Otherwise, this will 960 * do nothing. 961 * @hide 962 */ reverse()963 public void reverse() { 964 ensureAnimatorSet(); 965 966 // Only reverse when all the animators can be reversed. 967 if (!canReverse()) { 968 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); 969 return; 970 } 971 972 mAnimatorSet.reverse(); 973 } 974 975 /** 976 * @hide 977 */ canReverse()978 public boolean canReverse() { 979 return mAnimatorSet.canReverse(); 980 } 981 982 private final Callback mCallback = new Callback() { 983 @Override 984 public void invalidateDrawable(@NonNull Drawable who) { 985 invalidateSelf(); 986 } 987 988 @Override 989 public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { 990 scheduleSelf(what, when); 991 } 992 993 @Override 994 public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { 995 unscheduleSelf(what); 996 } 997 }; 998 999 @Override registerAnimationCallback(@onNull AnimationCallback callback)1000 public void registerAnimationCallback(@NonNull AnimationCallback callback) { 1001 if (callback == null) { 1002 return; 1003 } 1004 1005 // Add listener accordingly. 1006 if (mAnimationCallbacks == null) { 1007 mAnimationCallbacks = new ArrayList<>(); 1008 } 1009 1010 mAnimationCallbacks.add(callback); 1011 1012 if (mAnimatorListener == null) { 1013 // Create a animator listener and trigger the callback events when listener is 1014 // triggered. 1015 mAnimatorListener = new AnimatorListenerAdapter() { 1016 @Override 1017 public void onAnimationStart(Animator animation) { 1018 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks); 1019 int size = tmpCallbacks.size(); 1020 for (int i = 0; i < size; i ++) { 1021 tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawable.this); 1022 } 1023 } 1024 1025 @Override 1026 public void onAnimationEnd(Animator animation) { 1027 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks); 1028 int size = tmpCallbacks.size(); 1029 for (int i = 0; i < size; i ++) { 1030 tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawable.this); 1031 } 1032 } 1033 }; 1034 } 1035 mAnimatorSet.setListener(mAnimatorListener); 1036 } 1037 1038 // A helper function to clean up the animator listener in the mAnimatorSet. removeAnimatorSetListener()1039 private void removeAnimatorSetListener() { 1040 if (mAnimatorListener != null) { 1041 mAnimatorSet.removeListener(mAnimatorListener); 1042 mAnimatorListener = null; 1043 } 1044 } 1045 1046 @Override unregisterAnimationCallback(@onNull AnimationCallback callback)1047 public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) { 1048 if (mAnimationCallbacks == null || callback == null) { 1049 // Nothing to be removed. 1050 return false; 1051 } 1052 boolean removed = mAnimationCallbacks.remove(callback); 1053 1054 // When the last call back unregistered, remove the listener accordingly. 1055 if (mAnimationCallbacks.size() == 0) { 1056 removeAnimatorSetListener(); 1057 } 1058 return removed; 1059 } 1060 1061 @Override clearAnimationCallbacks()1062 public void clearAnimationCallbacks() { 1063 removeAnimatorSetListener(); 1064 if (mAnimationCallbacks == null) { 1065 return; 1066 } 1067 1068 mAnimationCallbacks.clear(); 1069 } 1070 1071 private interface VectorDrawableAnimator { init(@onNull AnimatorSet set)1072 void init(@NonNull AnimatorSet set); start()1073 void start(); end()1074 void end(); reset()1075 void reset(); reverse()1076 void reverse(); canReverse()1077 boolean canReverse(); setListener(AnimatorListener listener)1078 void setListener(AnimatorListener listener); removeListener(AnimatorListener listener)1079 void removeListener(AnimatorListener listener); onDraw(Canvas canvas)1080 void onDraw(Canvas canvas); isStarted()1081 boolean isStarted(); isRunning()1082 boolean isRunning(); isInfinite()1083 boolean isInfinite(); pause()1084 void pause(); resume()1085 void resume(); 1086 } 1087 1088 private static class VectorDrawableAnimatorUI implements VectorDrawableAnimator { 1089 // mSet is only initialized in init(). So we need to check whether it is null before any 1090 // operation. 1091 private AnimatorSet mSet = null; 1092 private final Drawable mDrawable; 1093 // Caching the listener in the case when listener operation is called before the mSet is 1094 // setup by init(). 1095 private ArrayList<AnimatorListener> mListenerArray = null; 1096 private boolean mIsInfinite = false; 1097 VectorDrawableAnimatorUI(@onNull AnimatedVectorDrawable drawable)1098 VectorDrawableAnimatorUI(@NonNull AnimatedVectorDrawable drawable) { 1099 mDrawable = drawable; 1100 } 1101 1102 @Override init(@onNull AnimatorSet set)1103 public void init(@NonNull AnimatorSet set) { 1104 if (mSet != null) { 1105 // Already initialized 1106 throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " + 1107 "re-initialized"); 1108 } 1109 // Keep a deep copy of the set, such that set can be still be constantly representing 1110 // the static content from XML file. 1111 mSet = set.clone(); 1112 mIsInfinite = mSet.getTotalDuration() == Animator.DURATION_INFINITE; 1113 1114 // If there are listeners added before calling init(), now they should be setup. 1115 if (mListenerArray != null && !mListenerArray.isEmpty()) { 1116 for (int i = 0; i < mListenerArray.size(); i++) { 1117 mSet.addListener(mListenerArray.get(i)); 1118 } 1119 mListenerArray.clear(); 1120 mListenerArray = null; 1121 } 1122 } 1123 1124 // Although start(), reset() and reverse() should call init() already, it is better to 1125 // protect these functions from NPE in any situation. 1126 @Override start()1127 public void start() { 1128 if (mSet == null || mSet.isStarted()) { 1129 return; 1130 } 1131 mSet.start(); 1132 invalidateOwningView(); 1133 } 1134 1135 @Override end()1136 public void end() { 1137 if (mSet == null) { 1138 return; 1139 } 1140 mSet.end(); 1141 } 1142 1143 @Override reset()1144 public void reset() { 1145 if (mSet == null) { 1146 return; 1147 } 1148 start(); 1149 mSet.cancel(); 1150 } 1151 1152 @Override reverse()1153 public void reverse() { 1154 if (mSet == null) { 1155 return; 1156 } 1157 mSet.reverse(); 1158 invalidateOwningView(); 1159 } 1160 1161 @Override canReverse()1162 public boolean canReverse() { 1163 return mSet != null && mSet.canReverse(); 1164 } 1165 1166 @Override setListener(AnimatorListener listener)1167 public void setListener(AnimatorListener listener) { 1168 if (mSet == null) { 1169 if (mListenerArray == null) { 1170 mListenerArray = new ArrayList<AnimatorListener>(); 1171 } 1172 mListenerArray.add(listener); 1173 } else { 1174 mSet.addListener(listener); 1175 } 1176 } 1177 1178 @Override removeListener(AnimatorListener listener)1179 public void removeListener(AnimatorListener listener) { 1180 if (mSet == null) { 1181 if (mListenerArray == null) { 1182 return; 1183 } 1184 mListenerArray.remove(listener); 1185 } else { 1186 mSet.removeListener(listener); 1187 } 1188 } 1189 1190 @Override onDraw(Canvas canvas)1191 public void onDraw(Canvas canvas) { 1192 if (mSet != null && mSet.isStarted()) { 1193 invalidateOwningView(); 1194 } 1195 } 1196 1197 @Override isStarted()1198 public boolean isStarted() { 1199 return mSet != null && mSet.isStarted(); 1200 } 1201 1202 @Override isRunning()1203 public boolean isRunning() { 1204 return mSet != null && mSet.isRunning(); 1205 } 1206 1207 @Override isInfinite()1208 public boolean isInfinite() { 1209 return mIsInfinite; 1210 } 1211 1212 @Override pause()1213 public void pause() { 1214 if (mSet == null) { 1215 return; 1216 } 1217 mSet.pause(); 1218 } 1219 1220 @Override resume()1221 public void resume() { 1222 if (mSet == null) { 1223 return; 1224 } 1225 mSet.resume(); 1226 } 1227 invalidateOwningView()1228 private void invalidateOwningView() { 1229 mDrawable.invalidateSelf(); 1230 } 1231 } 1232 1233 /** 1234 * @hide 1235 */ 1236 public static class VectorDrawableAnimatorRT implements VectorDrawableAnimator, 1237 NativeVectorDrawableAnimator { 1238 private static final int START_ANIMATION = 1; 1239 private static final int REVERSE_ANIMATION = 2; 1240 private static final int RESET_ANIMATION = 3; 1241 private static final int END_ANIMATION = 4; 1242 1243 // If the duration of an animation is more than 300 frames, we cap the sample size to 300. 1244 private static final int MAX_SAMPLE_POINTS = 300; 1245 private Handler mHandler; 1246 private AnimatorListener mListener = null; 1247 private final LongArray mStartDelays = new LongArray(); 1248 private PropertyValuesHolder.PropertyValues mTmpValues = 1249 new PropertyValuesHolder.PropertyValues(); 1250 private long mSetPtr = 0; 1251 private boolean mContainsSequentialAnimators = false; 1252 private boolean mStarted = false; 1253 private boolean mInitialized = false; 1254 private boolean mIsReversible = false; 1255 private boolean mIsInfinite = false; 1256 private final VirtualRefBasePtr mSetRefBasePtr; 1257 private WeakReference<RenderNode> mLastSeenTarget = null; 1258 private int mLastListenerId = 0; 1259 private final IntArray mPendingAnimationActions = new IntArray(); 1260 private final AnimatedVectorDrawable mDrawable; 1261 VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable)1262 VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) { 1263 mDrawable = drawable; 1264 mSetPtr = nCreateAnimatorSet(); 1265 // Increment ref count on native AnimatorSet, so it doesn't get released before Java 1266 // side is done using it. 1267 mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr); 1268 } 1269 1270 @Override init(@onNull AnimatorSet set)1271 public void init(@NonNull AnimatorSet set) { 1272 if (mInitialized) { 1273 // Already initialized 1274 throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " + 1275 "re-initialized"); 1276 } 1277 parseAnimatorSet(set, 0); 1278 long vectorDrawableTreePtr = mDrawable.mAnimatedVectorState.mVectorDrawable 1279 .getNativeTree(); 1280 nSetVectorDrawableTarget(mSetPtr, vectorDrawableTreePtr); 1281 mInitialized = true; 1282 mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE; 1283 1284 // Check reversible. 1285 mIsReversible = true; 1286 if (mContainsSequentialAnimators) { 1287 mIsReversible = false; 1288 } else { 1289 // Check if there's any start delay set on child 1290 for (int i = 0; i < mStartDelays.size(); i++) { 1291 if (mStartDelays.get(i) > 0) { 1292 mIsReversible = false; 1293 return; 1294 } 1295 } 1296 } 1297 } 1298 parseAnimatorSet(AnimatorSet set, long startTime)1299 private void parseAnimatorSet(AnimatorSet set, long startTime) { 1300 ArrayList<Animator> animators = set.getChildAnimations(); 1301 1302 boolean playTogether = set.shouldPlayTogether(); 1303 // Convert AnimatorSet to VectorDrawableAnimatorRT 1304 for (int i = 0; i < animators.size(); i++) { 1305 Animator animator = animators.get(i); 1306 // Here we only support ObjectAnimator 1307 if (animator instanceof AnimatorSet) { 1308 parseAnimatorSet((AnimatorSet) animator, startTime); 1309 } else if (animator instanceof ObjectAnimator) { 1310 createRTAnimator((ObjectAnimator) animator, startTime); 1311 } // ignore ValueAnimators and others because they don't directly modify VD 1312 // therefore will be useless to AVD. 1313 1314 if (!playTogether) { 1315 // Assume not play together means play sequentially 1316 startTime += animator.getTotalDuration(); 1317 mContainsSequentialAnimators = true; 1318 } 1319 } 1320 } 1321 1322 // TODO: This method reads animation data from already parsed Animators. We need to move 1323 // this step further up the chain in the parser to avoid the detour. createRTAnimator(ObjectAnimator animator, long startTime)1324 private void createRTAnimator(ObjectAnimator animator, long startTime) { 1325 PropertyValuesHolder[] values = animator.getValues(); 1326 Object target = animator.getTarget(); 1327 if (target instanceof VectorDrawable.VGroup) { 1328 createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target, 1329 startTime); 1330 } else if (target instanceof VectorDrawable.VPath) { 1331 for (int i = 0; i < values.length; i++) { 1332 values[i].getPropertyValues(mTmpValues); 1333 if (mTmpValues.endValue instanceof PathParser.PathData && 1334 mTmpValues.propertyName.equals("pathData")) { 1335 createRTAnimatorForPath(animator, (VectorDrawable.VPath) target, 1336 startTime); 1337 } else if (target instanceof VectorDrawable.VFullPath) { 1338 createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target, 1339 startTime); 1340 } else if (!mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1341 throw new IllegalArgumentException("ClipPath only supports PathData " + 1342 "property"); 1343 } 1344 } 1345 } else if (target instanceof VectorDrawable.VectorDrawableState) { 1346 createRTAnimatorForRootGroup(values, animator, 1347 (VectorDrawable.VectorDrawableState) target, startTime); 1348 } 1349 } 1350 createRTAnimatorForGroup(PropertyValuesHolder[] values, ObjectAnimator animator, VectorDrawable.VGroup target, long startTime)1351 private void createRTAnimatorForGroup(PropertyValuesHolder[] values, 1352 ObjectAnimator animator, VectorDrawable.VGroup target, 1353 long startTime) { 1354 1355 long nativePtr = target.getNativePtr(); 1356 int propertyId; 1357 for (int i = 0; i < values.length; i++) { 1358 // TODO: We need to support the rare case in AVD where no start value is provided 1359 values[i].getPropertyValues(mTmpValues); 1360 propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName); 1361 if (mTmpValues.type != Float.class && mTmpValues.type != float.class) { 1362 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1363 Log.e(LOGTAG, "Unsupported type: " + 1364 mTmpValues.type + ". Only float value is supported for Groups."); 1365 } 1366 continue; 1367 } 1368 if (propertyId < 0) { 1369 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1370 Log.e(LOGTAG, "Unsupported property: " + 1371 mTmpValues.propertyName + " for Vector Drawable Group"); 1372 } 1373 continue; 1374 } 1375 long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId, 1376 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); 1377 if (mTmpValues.dataSource != null) { 1378 float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource, 1379 animator.getDuration()); 1380 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 1381 } 1382 createNativeChildAnimator(propertyPtr, startTime, animator); 1383 } 1384 } createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target, long startTime)1385 private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target, 1386 long startTime) { 1387 1388 long nativePtr = target.getNativePtr(); 1389 long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue) 1390 .getNativePtr(); 1391 long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue) 1392 .getNativePtr(); 1393 long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr, 1394 endPathDataPtr); 1395 createNativeChildAnimator(propertyPtr, startTime, animator); 1396 } 1397 createRTAnimatorForFullPath(ObjectAnimator animator, VectorDrawable.VFullPath target, long startTime)1398 private void createRTAnimatorForFullPath(ObjectAnimator animator, 1399 VectorDrawable.VFullPath target, long startTime) { 1400 1401 int propertyId = target.getPropertyIndex(mTmpValues.propertyName); 1402 long propertyPtr; 1403 long nativePtr = target.getNativePtr(); 1404 if (mTmpValues.type == Float.class || mTmpValues.type == float.class) { 1405 if (propertyId < 0) { 1406 if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1407 return; 1408 } else { 1409 throw new IllegalArgumentException("Property: " + mTmpValues.propertyName 1410 + " is not supported for FullPath"); 1411 } 1412 } 1413 propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId, 1414 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); 1415 if (mTmpValues.dataSource != null) { 1416 // Pass keyframe data to native, if any. 1417 float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource, 1418 animator.getDuration()); 1419 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 1420 } 1421 1422 } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) { 1423 propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId, 1424 (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue); 1425 if (mTmpValues.dataSource != null) { 1426 // Pass keyframe data to native, if any. 1427 int[] dataPoints = createIntDataPoints(mTmpValues.dataSource, 1428 animator.getDuration()); 1429 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 1430 } 1431 } else { 1432 if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1433 return; 1434 } else { 1435 throw new UnsupportedOperationException("Unsupported type: " + 1436 mTmpValues.type + ". Only float, int or PathData value is " + 1437 "supported for Paths."); 1438 } 1439 } 1440 createNativeChildAnimator(propertyPtr, startTime, animator); 1441 } 1442 createRTAnimatorForRootGroup(PropertyValuesHolder[] values, ObjectAnimator animator, VectorDrawable.VectorDrawableState target, long startTime)1443 private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values, 1444 ObjectAnimator animator, VectorDrawable.VectorDrawableState target, 1445 long startTime) { 1446 long nativePtr = target.getNativeRenderer(); 1447 if (!animator.getPropertyName().equals("alpha")) { 1448 if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1449 return; 1450 } else { 1451 throw new UnsupportedOperationException("Only alpha is supported for root " 1452 + "group"); 1453 } 1454 } 1455 Float startValue = null; 1456 Float endValue = null; 1457 for (int i = 0; i < values.length; i++) { 1458 values[i].getPropertyValues(mTmpValues); 1459 if (mTmpValues.propertyName.equals("alpha")) { 1460 startValue = (Float) mTmpValues.startValue; 1461 endValue = (Float) mTmpValues.endValue; 1462 break; 1463 } 1464 } 1465 if (startValue == null && endValue == null) { 1466 if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1467 return; 1468 } else { 1469 throw new UnsupportedOperationException("No alpha values are specified"); 1470 } 1471 } 1472 long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue); 1473 if (mTmpValues.dataSource != null) { 1474 // Pass keyframe data to native, if any. 1475 float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource, 1476 animator.getDuration()); 1477 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 1478 } 1479 createNativeChildAnimator(propertyPtr, startTime, animator); 1480 } 1481 1482 /** 1483 * Calculate the amount of frames an animation will run based on duration. 1484 */ getFrameCount(long duration)1485 private static int getFrameCount(long duration) { 1486 long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos(); 1487 int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS); 1488 int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs); 1489 // We need 2 frames of data minimum. 1490 numAnimFrames = Math.max(2, numAnimFrames); 1491 if (numAnimFrames > MAX_SAMPLE_POINTS) { 1492 Log.w("AnimatedVectorDrawable", "Duration for the animation is too long :" + 1493 duration + ", the animation will subsample the keyframe or path data."); 1494 numAnimFrames = MAX_SAMPLE_POINTS; 1495 } 1496 return numAnimFrames; 1497 } 1498 1499 // These are the data points that define the value of the animating properties. 1500 // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1] 1501 // a point on the path corresponds to the values of translateX and translateY. 1502 // TODO: (Optimization) We should pass the path down in native and chop it into segments 1503 // in native. createFloatDataPoints( PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration)1504 private static float[] createFloatDataPoints( 1505 PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { 1506 int numAnimFrames = getFrameCount(duration); 1507 float values[] = new float[numAnimFrames]; 1508 float lastFrame = numAnimFrames - 1; 1509 for (int i = 0; i < numAnimFrames; i++) { 1510 float fraction = i / lastFrame; 1511 values[i] = (Float) dataSource.getValueAtFraction(fraction); 1512 } 1513 return values; 1514 } 1515 createIntDataPoints( PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration)1516 private static int[] createIntDataPoints( 1517 PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { 1518 int numAnimFrames = getFrameCount(duration); 1519 int values[] = new int[numAnimFrames]; 1520 float lastFrame = numAnimFrames - 1; 1521 for (int i = 0; i < numAnimFrames; i++) { 1522 float fraction = i / lastFrame; 1523 values[i] = (Integer) dataSource.getValueAtFraction(fraction); 1524 } 1525 return values; 1526 } 1527 createNativeChildAnimator(long propertyPtr, long extraDelay, ObjectAnimator animator)1528 private void createNativeChildAnimator(long propertyPtr, long extraDelay, 1529 ObjectAnimator animator) { 1530 long duration = animator.getDuration(); 1531 int repeatCount = animator.getRepeatCount(); 1532 long startDelay = extraDelay + animator.getStartDelay(); 1533 TimeInterpolator interpolator = animator.getInterpolator(); 1534 long nativeInterpolator = 1535 RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration); 1536 1537 startDelay *= ValueAnimator.getDurationScale(); 1538 duration *= ValueAnimator.getDurationScale(); 1539 1540 mStartDelays.add(startDelay); 1541 nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration, 1542 repeatCount, animator.getRepeatMode()); 1543 } 1544 1545 /** 1546 * Holds a weak reference to the target that was last seen (through the RecordingCanvas 1547 * in the last draw call), so that when animator set needs to start, we can add the animator 1548 * to the last seen RenderNode target and start right away. 1549 */ recordLastSeenTarget(RecordingCanvas canvas)1550 protected void recordLastSeenTarget(RecordingCanvas canvas) { 1551 final RenderNode node = RenderNodeAnimatorSetHelper.getTarget(canvas); 1552 mLastSeenTarget = new WeakReference<RenderNode>(node); 1553 // Add the animator to the list of animators on every draw 1554 if (mInitialized || mPendingAnimationActions.size() > 0) { 1555 if (useTarget(node)) { 1556 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1557 Log.d(LOGTAG, "Target is set in the next frame"); 1558 } 1559 for (int i = 0; i < mPendingAnimationActions.size(); i++) { 1560 handlePendingAction(mPendingAnimationActions.get(i)); 1561 } 1562 mPendingAnimationActions.clear(); 1563 } 1564 } 1565 } 1566 handlePendingAction(int pendingAnimationAction)1567 private void handlePendingAction(int pendingAnimationAction) { 1568 if (pendingAnimationAction == START_ANIMATION) { 1569 startAnimation(); 1570 } else if (pendingAnimationAction == REVERSE_ANIMATION) { 1571 reverseAnimation(); 1572 } else if (pendingAnimationAction == RESET_ANIMATION) { 1573 resetAnimation(); 1574 } else if (pendingAnimationAction == END_ANIMATION) { 1575 endAnimation(); 1576 } else { 1577 throw new UnsupportedOperationException("Animation action " + 1578 pendingAnimationAction + "is not supported"); 1579 } 1580 } 1581 useLastSeenTarget()1582 private boolean useLastSeenTarget() { 1583 if (mLastSeenTarget != null) { 1584 final RenderNode target = mLastSeenTarget.get(); 1585 return useTarget(target); 1586 } 1587 return false; 1588 } 1589 useTarget(RenderNode target)1590 private boolean useTarget(RenderNode target) { 1591 if (target != null && target.isAttached()) { 1592 target.registerVectorDrawableAnimator(this); 1593 return true; 1594 } 1595 return false; 1596 } 1597 invalidateOwningView()1598 private void invalidateOwningView() { 1599 mDrawable.invalidateSelf(); 1600 } 1601 addPendingAction(int pendingAnimationAction)1602 private void addPendingAction(int pendingAnimationAction) { 1603 invalidateOwningView(); 1604 mPendingAnimationActions.add(pendingAnimationAction); 1605 } 1606 1607 @Override start()1608 public void start() { 1609 if (!mInitialized) { 1610 return; 1611 } 1612 1613 if (useLastSeenTarget()) { 1614 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1615 Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java"); 1616 } 1617 startAnimation(); 1618 } else { 1619 addPendingAction(START_ANIMATION); 1620 } 1621 } 1622 1623 @Override end()1624 public void end() { 1625 if (!mInitialized) { 1626 return; 1627 } 1628 1629 if (useLastSeenTarget()) { 1630 endAnimation(); 1631 } else { 1632 addPendingAction(END_ANIMATION); 1633 } 1634 } 1635 1636 @Override reset()1637 public void reset() { 1638 if (!mInitialized) { 1639 return; 1640 } 1641 1642 if (useLastSeenTarget()) { 1643 resetAnimation(); 1644 } else { 1645 addPendingAction(RESET_ANIMATION); 1646 } 1647 } 1648 1649 // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential 1650 // animators or when the animator set has a start delay 1651 @Override reverse()1652 public void reverse() { 1653 if (!mIsReversible || !mInitialized) { 1654 return; 1655 } 1656 if (useLastSeenTarget()) { 1657 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1658 Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java"); 1659 } 1660 reverseAnimation(); 1661 } else { 1662 addPendingAction(REVERSE_ANIMATION); 1663 } 1664 } 1665 1666 // This should only be called after animator has been added to the RenderNode target. startAnimation()1667 private void startAnimation() { 1668 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1669 Log.w(LOGTAG, "starting animation on VD: " + 1670 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState) 1671 mDrawable.getConstantState()).mVectorDrawable.getConstantState()) 1672 .mRootName); 1673 } 1674 mStarted = true; 1675 if (mHandler == null) { 1676 mHandler = new Handler(); 1677 } 1678 nStart(mSetPtr, this, ++mLastListenerId); 1679 invalidateOwningView(); 1680 if (mListener != null) { 1681 mListener.onAnimationStart(null); 1682 } 1683 } 1684 1685 // This should only be called after animator has been added to the RenderNode target. endAnimation()1686 private void endAnimation() { 1687 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1688 Log.w(LOGTAG, "ending animation on VD: " + 1689 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState) 1690 mDrawable.getConstantState()).mVectorDrawable.getConstantState()) 1691 .mRootName); 1692 } 1693 nEnd(mSetPtr); 1694 invalidateOwningView(); 1695 } 1696 1697 // This should only be called after animator has been added to the RenderNode target. resetAnimation()1698 private void resetAnimation() { 1699 nReset(mSetPtr); 1700 invalidateOwningView(); 1701 } 1702 1703 // This should only be called after animator has been added to the RenderNode target. reverseAnimation()1704 private void reverseAnimation() { 1705 mStarted = true; 1706 nReverse(mSetPtr, this, ++mLastListenerId); 1707 invalidateOwningView(); 1708 if (mListener != null) { 1709 mListener.onAnimationStart(null); 1710 } 1711 } 1712 1713 @Override getAnimatorNativePtr()1714 public long getAnimatorNativePtr() { 1715 return mSetPtr; 1716 } 1717 1718 @Override canReverse()1719 public boolean canReverse() { 1720 return mIsReversible; 1721 } 1722 1723 @Override isStarted()1724 public boolean isStarted() { 1725 return mStarted; 1726 } 1727 1728 @Override isRunning()1729 public boolean isRunning() { 1730 if (!mInitialized) { 1731 return false; 1732 } 1733 return mStarted; 1734 } 1735 1736 @Override setListener(AnimatorListener listener)1737 public void setListener(AnimatorListener listener) { 1738 mListener = listener; 1739 } 1740 1741 @Override removeListener(AnimatorListener listener)1742 public void removeListener(AnimatorListener listener) { 1743 mListener = null; 1744 } 1745 1746 @Override onDraw(Canvas canvas)1747 public void onDraw(Canvas canvas) { 1748 if (canvas.isHardwareAccelerated()) { 1749 recordLastSeenTarget((RecordingCanvas) canvas); 1750 } 1751 } 1752 1753 @Override isInfinite()1754 public boolean isInfinite() { 1755 return mIsInfinite; 1756 } 1757 1758 @Override pause()1759 public void pause() { 1760 // TODO: Implement pause for Animator On RT. 1761 } 1762 1763 @Override resume()1764 public void resume() { 1765 // TODO: Implement resume for Animator On RT. 1766 } 1767 onAnimationEnd(int listenerId)1768 private void onAnimationEnd(int listenerId) { 1769 if (listenerId != mLastListenerId) { 1770 return; 1771 } 1772 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1773 Log.d(LOGTAG, "on finished called from native"); 1774 } 1775 mStarted = false; 1776 // Invalidate in the end of the animation to make sure the data in 1777 // RT thread is synced back to UI thread. 1778 invalidateOwningView(); 1779 if (mListener != null) { 1780 mListener.onAnimationEnd(null); 1781 } 1782 } 1783 1784 // onFinished: should be called from native 1785 @UnsupportedAppUsage callOnFinished(VectorDrawableAnimatorRT set, int id)1786 private static void callOnFinished(VectorDrawableAnimatorRT set, int id) { 1787 set.mHandler.post(() -> set.onAnimationEnd(id)); 1788 } 1789 transferPendingActions(VectorDrawableAnimator animatorSet)1790 private void transferPendingActions(VectorDrawableAnimator animatorSet) { 1791 for (int i = 0; i < mPendingAnimationActions.size(); i++) { 1792 int pendingAction = mPendingAnimationActions.get(i); 1793 if (pendingAction == START_ANIMATION) { 1794 animatorSet.start(); 1795 } else if (pendingAction == END_ANIMATION) { 1796 animatorSet.end(); 1797 } else if (pendingAction == REVERSE_ANIMATION) { 1798 animatorSet.reverse(); 1799 } else if (pendingAction == RESET_ANIMATION) { 1800 animatorSet.reset(); 1801 } else { 1802 throw new UnsupportedOperationException("Animation action " + 1803 pendingAction + "is not supported"); 1804 } 1805 } 1806 mPendingAnimationActions.clear(); 1807 } 1808 } 1809 nCreateAnimatorSet()1810 private static native long nCreateAnimatorSet(); nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr)1811 private static native void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr); nAddAnimator(long setPtr, long propertyValuesHolder, long nativeInterpolator, long startDelay, long duration, int repeatCount, int repeatMode)1812 private static native void nAddAnimator(long setPtr, long propertyValuesHolder, 1813 long nativeInterpolator, long startDelay, long duration, int repeatCount, 1814 int repeatMode); nSetPropertyHolderData(long nativePtr, float[] data, int length)1815 private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length); nSetPropertyHolderData(long nativePtr, int[] data, int length)1816 private static native void nSetPropertyHolderData(long nativePtr, int[] data, int length); nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id)1817 private static native void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id); nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id)1818 private static native void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id); 1819 1820 // ------------- @FastNative ------------------- 1821 1822 @FastNative nCreateGroupPropertyHolder(long nativePtr, int propertyId, float startValue, float endValue)1823 private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId, 1824 float startValue, float endValue); 1825 @FastNative nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr, long endValuePtr)1826 private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr, 1827 long endValuePtr); 1828 @FastNative nCreatePathColorPropertyHolder(long nativePtr, int propertyId, int startValue, int endValue)1829 private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId, 1830 int startValue, int endValue); 1831 @FastNative nCreatePathPropertyHolder(long nativePtr, int propertyId, float startValue, float endValue)1832 private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId, 1833 float startValue, float endValue); 1834 @FastNative nCreateRootAlphaPropertyHolder(long nativePtr, float startValue, float endValue)1835 private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue, 1836 float endValue); 1837 @FastNative nEnd(long animatorSetPtr)1838 private static native void nEnd(long animatorSetPtr); 1839 @FastNative nReset(long animatorSetPtr)1840 private static native void nReset(long animatorSetPtr); 1841 } 1842