1 /* 2 * Copyright (C) 2018 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 android.graphics.drawable; 18 19 import android.annotation.IntRange; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.res.AssetFileDescriptor; 23 import android.content.res.Resources; 24 import android.content.res.Resources.Theme; 25 import android.content.res.TypedArray; 26 import android.graphics.Bitmap; 27 import android.graphics.Canvas; 28 import android.graphics.ColorFilter; 29 import android.graphics.ImageDecoder; 30 import android.graphics.PixelFormat; 31 import android.graphics.Rect; 32 import android.os.Handler; 33 import android.os.Looper; 34 import android.os.SystemClock; 35 import android.util.AttributeSet; 36 import android.util.DisplayMetrics; 37 import android.util.TypedValue; 38 import android.view.View; 39 40 import com.android.graphics.hwui.flags.Flags; 41 import com.android.internal.R; 42 43 import dalvik.annotation.optimization.FastNative; 44 45 import libcore.util.NativeAllocationRegistry; 46 47 import org.xmlpull.v1.XmlPullParser; 48 import org.xmlpull.v1.XmlPullParserException; 49 50 import java.io.IOException; 51 import java.io.InputStream; 52 import java.lang.ref.WeakReference; 53 import java.util.ArrayList; 54 55 /** 56 * {@link Drawable} for drawing animated images (like GIF). 57 * 58 * <p>The framework handles decoding subsequent frames in another thread and 59 * updating when necessary. The drawable will only animate while it is being 60 * displayed.</p> 61 * 62 * <p>Created by {@link ImageDecoder#decodeDrawable}. A user needs to call 63 * {@link #start} to start the animation.</p> 64 * 65 * <p>It can also be defined in XML using the <code><animated-image></code> 66 * element.</p> 67 * 68 * @attr ref android.R.styleable#AnimatedImageDrawable_src 69 * @attr ref android.R.styleable#AnimatedImageDrawable_autoStart 70 * @attr ref android.R.styleable#AnimatedImageDrawable_repeatCount 71 * @attr ref android.R.styleable#AnimatedImageDrawable_autoMirrored 72 */ 73 public class AnimatedImageDrawable extends Drawable implements Animatable2 { 74 private int mIntrinsicWidth; 75 private int mIntrinsicHeight; 76 77 private boolean mStarting; 78 79 private Handler mHandler; 80 81 private class State { State(long nativePtr, InputStream is, AssetFileDescriptor afd)82 State(long nativePtr, InputStream is, AssetFileDescriptor afd) { 83 mNativePtr = nativePtr; 84 mInputStream = is; 85 mAssetFd = afd; 86 } 87 88 final long mNativePtr; 89 90 // These just keep references so the native code can continue using them. 91 private final InputStream mInputStream; 92 private final AssetFileDescriptor mAssetFd; 93 94 int[] mThemeAttrs = null; 95 boolean mAutoMirrored = false; 96 int mRepeatCount = REPEAT_UNDEFINED; 97 } 98 99 private State mState; 100 101 private Runnable mRunnable; 102 103 private ColorFilter mColorFilter; 104 105 /** 106 * Pass this to {@link #setRepeatCount} to repeat infinitely. 107 * 108 * <p>{@link Animatable2.AnimationCallback#onAnimationEnd} will never be 109 * called unless there is an error.</p> 110 */ 111 public static final int REPEAT_INFINITE = -1; 112 113 /** @removed 114 * @deprecated Replaced with REPEAT_INFINITE to match other APIs. 115 */ 116 @java.lang.Deprecated 117 public static final int LOOP_INFINITE = REPEAT_INFINITE; 118 119 private static final int REPEAT_UNDEFINED = -2; 120 121 /** 122 * Specify the number of times to repeat the animation. 123 * 124 * <p>By default, the repeat count in the encoded data is respected. If set 125 * to {@link #REPEAT_INFINITE}, the animation will repeat as long as it is 126 * displayed. If the value is {@code 0}, the animation will play once.</p> 127 * 128 * <p>This call replaces the current repeat count. If the encoded data 129 * specified a repeat count of {@code 2} (meaning that 130 * {@link #getRepeatCount()} returns {@code 2}, the animation will play 131 * three times. Calling {@code setRepeatCount(1)} will result in playing only 132 * twice and {@link #getRepeatCount()} returning {@code 1}.</p> 133 * 134 * <p>If the animation is already playing, the iterations that have already 135 * occurred count towards the new count. If the animation has already 136 * repeated the appropriate number of times (or more), it will finish its 137 * current iteration and then stop.</p> 138 */ setRepeatCount(@ntRangefrom = REPEAT_INFINITE) int repeatCount)139 public void setRepeatCount(@IntRange(from = REPEAT_INFINITE) int repeatCount) { 140 if (repeatCount < REPEAT_INFINITE) { 141 throw new IllegalArgumentException("invalid value passed to setRepeatCount" 142 + repeatCount); 143 } 144 if (mState.mRepeatCount != repeatCount) { 145 mState.mRepeatCount = repeatCount; 146 if (mState.mNativePtr != 0) { 147 nSetRepeatCount(mState.mNativePtr, repeatCount); 148 } 149 } 150 } 151 152 /** @removed 153 * @deprecated Replaced with setRepeatCount to match other APIs. 154 */ 155 @java.lang.Deprecated setLoopCount(int loopCount)156 public void setLoopCount(int loopCount) { 157 setRepeatCount(loopCount); 158 } 159 160 /** 161 * Retrieve the number of times the animation will repeat. 162 * 163 * <p>By default, the repeat count in the encoded data is respected. If the 164 * value is {@link #REPEAT_INFINITE}, the animation will repeat as long as 165 * it is displayed. If the value is {@code 0}, it will play once.</p> 166 * 167 * <p>Calling {@link #setRepeatCount} will make future calls to this method 168 * return the value passed to {@link #setRepeatCount}.</p> 169 */ getRepeatCount()170 public int getRepeatCount() { 171 if (mState.mNativePtr == 0) { 172 throw new IllegalStateException("called getRepeatCount on empty AnimatedImageDrawable"); 173 } 174 if (mState.mRepeatCount == REPEAT_UNDEFINED) { 175 mState.mRepeatCount = nGetRepeatCount(mState.mNativePtr); 176 177 } 178 return mState.mRepeatCount; 179 } 180 181 /** @removed 182 * @deprecated Replaced with getRepeatCount to match other APIs. 183 */ 184 @java.lang.Deprecated getLoopCount(int loopCount)185 public int getLoopCount(int loopCount) { 186 return getRepeatCount(); 187 } 188 189 /** 190 * Create an empty AnimatedImageDrawable. 191 */ AnimatedImageDrawable()192 public AnimatedImageDrawable() { 193 mState = new State(0, null, null); 194 } 195 196 @Override inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)197 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 198 throws XmlPullParserException, IOException { 199 super.inflate(r, parser, attrs, theme); 200 201 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedImageDrawable); 202 updateStateFromTypedArray(a, mSrcDensityOverride); 203 } 204 updateStateFromTypedArray(TypedArray a, int srcDensityOverride)205 private void updateStateFromTypedArray(TypedArray a, int srcDensityOverride) 206 throws XmlPullParserException { 207 State oldState = mState; 208 final Resources r = a.getResources(); 209 final int srcResId = a.getResourceId(R.styleable.AnimatedImageDrawable_src, 0); 210 if (srcResId != 0) { 211 // Follow the density handling in BitmapDrawable. 212 final TypedValue value = new TypedValue(); 213 r.getValueForDensity(srcResId, srcDensityOverride, value, true); 214 if (srcDensityOverride > 0 && value.density > 0 215 && value.density != TypedValue.DENSITY_NONE) { 216 if (value.density == srcDensityOverride) { 217 value.density = r.getDisplayMetrics().densityDpi; 218 } else { 219 value.density = 220 (value.density * r.getDisplayMetrics().densityDpi) / srcDensityOverride; 221 } 222 } 223 224 int density = Bitmap.DENSITY_NONE; 225 if (value.density == TypedValue.DENSITY_DEFAULT) { 226 density = DisplayMetrics.DENSITY_DEFAULT; 227 } else if (value.density != TypedValue.DENSITY_NONE) { 228 density = value.density; 229 } 230 231 Drawable drawable = null; 232 try { 233 InputStream is = r.openRawResource(srcResId, value); 234 ImageDecoder.Source source = ImageDecoder.createSource(r, is, density); 235 drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> { 236 if (!info.isAnimated()) { 237 throw new IllegalArgumentException("image is not animated"); 238 } 239 }); 240 } catch (IOException e) { 241 throw new XmlPullParserException(a.getPositionDescription() + 242 ": <animated-image> requires a valid 'src' attribute", null, e); 243 } 244 245 if (!(drawable instanceof AnimatedImageDrawable)) { 246 throw new XmlPullParserException(a.getPositionDescription() + 247 ": <animated-image> did not decode animated"); 248 } 249 250 // This may have previously been set without a src if we were waiting for a 251 // theme. 252 final int repeatCount = mState.mRepeatCount; 253 // Transfer the state of other to this one. other will be discarded. 254 AnimatedImageDrawable other = (AnimatedImageDrawable) drawable; 255 mState = other.mState; 256 other.mState = null; 257 mIntrinsicWidth = other.mIntrinsicWidth; 258 mIntrinsicHeight = other.mIntrinsicHeight; 259 if (repeatCount != REPEAT_UNDEFINED) { 260 this.setRepeatCount(repeatCount); 261 } 262 } 263 264 mState.mThemeAttrs = a.extractThemeAttrs(); 265 if (mState.mNativePtr == 0 && (mState.mThemeAttrs == null 266 || mState.mThemeAttrs[R.styleable.AnimatedImageDrawable_src] == 0)) { 267 throw new XmlPullParserException(a.getPositionDescription() + 268 ": <animated-image> requires a valid 'src' attribute"); 269 } 270 271 mState.mAutoMirrored = a.getBoolean( 272 R.styleable.AnimatedImageDrawable_autoMirrored, oldState.mAutoMirrored); 273 274 int repeatCount = a.getInt( 275 R.styleable.AnimatedImageDrawable_repeatCount, REPEAT_UNDEFINED); 276 if (repeatCount != REPEAT_UNDEFINED) { 277 this.setRepeatCount(repeatCount); 278 } 279 280 boolean autoStart = a.getBoolean( 281 R.styleable.AnimatedImageDrawable_autoStart, false); 282 if (autoStart && mState.mNativePtr != 0) { 283 this.start(); 284 } 285 } 286 287 /** 288 * @hide 289 * This should only be called by ImageDecoder. 290 * 291 * decoder is only non-null if it has a PostProcess 292 */ AnimatedImageDrawable(long nativeImageDecoder, @Nullable ImageDecoder decoder, int width, int height, long colorSpaceHandle, boolean extended, int srcDensity, int dstDensity, Rect cropRect, InputStream inputStream, AssetFileDescriptor afd)293 public AnimatedImageDrawable(long nativeImageDecoder, 294 @Nullable ImageDecoder decoder, int width, int height, 295 long colorSpaceHandle, boolean extended, int srcDensity, int dstDensity, 296 Rect cropRect, InputStream inputStream, AssetFileDescriptor afd) 297 throws IOException { 298 width = Bitmap.scaleFromDensity(width, srcDensity, dstDensity); 299 height = Bitmap.scaleFromDensity(height, srcDensity, dstDensity); 300 301 if (cropRect == null) { 302 mIntrinsicWidth = width; 303 mIntrinsicHeight = height; 304 } else { 305 cropRect.set(Bitmap.scaleFromDensity(cropRect.left, srcDensity, dstDensity), 306 Bitmap.scaleFromDensity(cropRect.top, srcDensity, dstDensity), 307 Bitmap.scaleFromDensity(cropRect.right, srcDensity, dstDensity), 308 Bitmap.scaleFromDensity(cropRect.bottom, srcDensity, dstDensity)); 309 mIntrinsicWidth = cropRect.width(); 310 mIntrinsicHeight = cropRect.height(); 311 } 312 313 mState = new State(nCreate(nativeImageDecoder, decoder, width, height, colorSpaceHandle, 314 extended, cropRect), inputStream, afd); 315 316 final long nativeSize = nNativeByteSize(mState.mNativePtr); 317 NativeAllocationRegistry registry = NativeAllocationRegistry.createMalloced( 318 AnimatedImageDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize); 319 registry.registerNativeAllocation(mState, mState.mNativePtr); 320 } 321 322 @Override getIntrinsicWidth()323 public int getIntrinsicWidth() { 324 return mIntrinsicWidth; 325 } 326 327 @Override getIntrinsicHeight()328 public int getIntrinsicHeight() { 329 return mIntrinsicHeight; 330 } 331 332 // nDraw returns -1 if the animation has finished. 333 private static final int FINISHED = -1; 334 335 @Override draw(@onNull Canvas canvas)336 public void draw(@NonNull Canvas canvas) { 337 if (mState.mNativePtr == 0) { 338 throw new IllegalStateException("called draw on empty AnimatedImageDrawable"); 339 } 340 341 if (mStarting) { 342 mStarting = false; 343 344 postOnAnimationStart(); 345 } 346 347 long nextUpdate = nDraw(mState.mNativePtr, canvas.getNativeCanvasWrapper()); 348 // a value <= 0 indicates that the drawable is stopped or that renderThread 349 // will manage the animation 350 if (nextUpdate > 0) { 351 if (mRunnable == null) { 352 mRunnable = this::invalidateSelf; 353 } 354 scheduleSelf(mRunnable, nextUpdate + SystemClock.uptimeMillis()); 355 } else if (nextUpdate == FINISHED) { 356 // This means the animation was drawn in software mode and ended. 357 postOnAnimationEnd(); 358 } 359 } 360 361 @Override setAlpha(@ntRangefrom = 0, to = 255) int alpha)362 public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { 363 if (alpha < 0 || alpha > 255) { 364 throw new IllegalArgumentException("Alpha must be between 0 and" 365 + " 255! provided " + alpha); 366 } 367 368 if (mState.mNativePtr == 0) { 369 throw new IllegalStateException("called setAlpha on empty AnimatedImageDrawable"); 370 } 371 372 nSetAlpha(mState.mNativePtr, alpha); 373 invalidateSelf(); 374 } 375 376 @Override getAlpha()377 public int getAlpha() { 378 if (mState.mNativePtr == 0) { 379 throw new IllegalStateException("called getAlpha on empty AnimatedImageDrawable"); 380 } 381 return nGetAlpha(mState.mNativePtr); 382 } 383 384 @Override setColorFilter(@ullable ColorFilter colorFilter)385 public void setColorFilter(@Nullable ColorFilter colorFilter) { 386 if (mState.mNativePtr == 0) { 387 throw new IllegalStateException("called setColorFilter on empty AnimatedImageDrawable"); 388 } 389 390 if (colorFilter != mColorFilter) { 391 mColorFilter = colorFilter; 392 long nativeFilter = colorFilter == null ? 0 : colorFilter.getNativeInstance(); 393 nSetColorFilter(mState.mNativePtr, nativeFilter); 394 invalidateSelf(); 395 } 396 } 397 398 @Override 399 @Nullable getColorFilter()400 public ColorFilter getColorFilter() { 401 return mColorFilter; 402 } 403 404 @Override getOpacity()405 public @PixelFormat.Opacity int getOpacity() { 406 return PixelFormat.TRANSLUCENT; 407 } 408 409 @Override setAutoMirrored(boolean mirrored)410 public void setAutoMirrored(boolean mirrored) { 411 if (mState.mAutoMirrored != mirrored) { 412 mState.mAutoMirrored = mirrored; 413 if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL && mState.mNativePtr != 0) { 414 nSetMirrored(mState.mNativePtr, mirrored); 415 invalidateSelf(); 416 } 417 } 418 } 419 420 @Override onLayoutDirectionChanged(int layoutDirection)421 public boolean onLayoutDirectionChanged(int layoutDirection) { 422 if (!mState.mAutoMirrored || mState.mNativePtr == 0) { 423 return false; 424 } 425 426 final boolean mirror = layoutDirection == View.LAYOUT_DIRECTION_RTL; 427 nSetMirrored(mState.mNativePtr, mirror); 428 return true; 429 } 430 431 @Override isAutoMirrored()432 public final boolean isAutoMirrored() { 433 return mState.mAutoMirrored; 434 } 435 436 // Animatable overrides 437 /** 438 * Return whether the animation is currently running. 439 * 440 * <p>When this drawable is created, this will return {@code false}. A client 441 * needs to call {@link #start} to start the animation.</p> 442 */ 443 @Override isRunning()444 public boolean isRunning() { 445 if (mState.mNativePtr == 0) { 446 throw new IllegalStateException("called isRunning on empty AnimatedImageDrawable"); 447 } 448 return nIsRunning(mState.mNativePtr); 449 } 450 451 /** 452 * Start the animation. 453 * 454 * <p>Does nothing if the animation is already running. If the animation is stopped, 455 * this will reset it.</p> 456 * 457 * <p>When the drawable is drawn, starting the animation, 458 * {@link Animatable2.AnimationCallback#onAnimationStart} will be called.</p> 459 */ 460 @Override start()461 public void start() { 462 if (mState.mNativePtr == 0) { 463 throw new IllegalStateException("called start on empty AnimatedImageDrawable"); 464 } 465 466 if (nStart(mState.mNativePtr)) { 467 mStarting = true; 468 invalidateSelf(); 469 } 470 } 471 472 /** 473 * Stop the animation. 474 * 475 * <p>If the animation is stopped, it will continue to display the frame 476 * it was displaying when stopped.</p> 477 */ 478 @Override stop()479 public void stop() { 480 if (mState.mNativePtr == 0) { 481 throw new IllegalStateException("called stop on empty AnimatedImageDrawable"); 482 } 483 if (nStop(mState.mNativePtr)) { 484 postOnAnimationEnd(); 485 } 486 } 487 488 // Animatable2 overrides 489 private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null; 490 491 @Override registerAnimationCallback(@onNull AnimationCallback callback)492 public void registerAnimationCallback(@NonNull AnimationCallback callback) { 493 if (callback == null) { 494 return; 495 } 496 497 if (mAnimationCallbacks == null) { 498 mAnimationCallbacks = new ArrayList<Animatable2.AnimationCallback>(); 499 nSetOnAnimationEndListener(mState.mNativePtr, new WeakReference<>(this)); 500 } 501 502 if (!mAnimationCallbacks.contains(callback)) { 503 mAnimationCallbacks.add(callback); 504 } 505 } 506 507 @Override unregisterAnimationCallback(@onNull AnimationCallback callback)508 public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) { 509 if (callback == null || mAnimationCallbacks == null 510 || !mAnimationCallbacks.remove(callback)) { 511 return false; 512 } 513 514 if (mAnimationCallbacks.isEmpty()) { 515 clearAnimationCallbacks(); 516 } 517 518 return true; 519 } 520 521 @Override clearAnimationCallbacks()522 public void clearAnimationCallbacks() { 523 if (mAnimationCallbacks != null) { 524 mAnimationCallbacks = null; 525 nSetOnAnimationEndListener(mState.mNativePtr, null); 526 } 527 } 528 529 @Override setFilterBitmap(boolean filterBitmap)530 public void setFilterBitmap(boolean filterBitmap) { 531 if (!Flags.animatedImageDrawableFilterBitmap()) { 532 super.setFilterBitmap(filterBitmap); 533 return; 534 } 535 if (mState.mNativePtr == 0) { 536 throw new IllegalStateException( 537 "called setFilterBitmap on empty AnimatedImageDrawable" 538 ); 539 } 540 if (nSetFilterBitmap(mState.mNativePtr, filterBitmap)) { 541 invalidateSelf(); 542 } 543 } 544 545 @Override isFilterBitmap()546 public boolean isFilterBitmap() { 547 if (!Flags.animatedImageDrawableFilterBitmap()) { 548 return super.isFilterBitmap(); 549 } 550 if (mState.mNativePtr == 0) { 551 throw new IllegalStateException( 552 "called isFilterBitmap on empty AnimatedImageDrawable" 553 ); 554 } 555 return nGetFilterBitmap(mState.mNativePtr); 556 } 557 postOnAnimationStart()558 private void postOnAnimationStart() { 559 if (mAnimationCallbacks == null) { 560 return; 561 } 562 563 getHandler().post(() -> { 564 for (Animatable2.AnimationCallback callback : mAnimationCallbacks) { 565 callback.onAnimationStart(this); 566 } 567 }); 568 } 569 postOnAnimationEnd()570 private void postOnAnimationEnd() { 571 if (mAnimationCallbacks == null) { 572 return; 573 } 574 575 getHandler().post(() -> { 576 for (Animatable2.AnimationCallback callback : mAnimationCallbacks) { 577 callback.onAnimationEnd(this); 578 } 579 }); 580 } 581 getHandler()582 private Handler getHandler() { 583 if (mHandler == null) { 584 mHandler = new Handler(Looper.getMainLooper()); 585 } 586 return mHandler; 587 } 588 589 /** 590 * Called by JNI. 591 * 592 * The JNI code has already posted this to the thread that created the 593 * callback, so no need to post. 594 */ 595 @SuppressWarnings("unused") callOnAnimationEnd(WeakReference<AnimatedImageDrawable> weakDrawable)596 private static void callOnAnimationEnd(WeakReference<AnimatedImageDrawable> weakDrawable) { 597 AnimatedImageDrawable drawable = weakDrawable.get(); 598 if (drawable != null) { 599 drawable.onAnimationEnd(); 600 } 601 } 602 onAnimationEnd()603 private void onAnimationEnd() { 604 if (mAnimationCallbacks != null) { 605 for (Animatable2.AnimationCallback callback : mAnimationCallbacks) { 606 callback.onAnimationEnd(this); 607 } 608 } 609 } 610 611 @Override onBoundsChange(Rect bounds)612 protected void onBoundsChange(Rect bounds) { 613 if (mState.mNativePtr != 0) { 614 nSetBounds(mState.mNativePtr, bounds); 615 } 616 } 617 618 nCreate(long nativeImageDecoder, @Nullable ImageDecoder decoder, int width, int height, long colorSpaceHandle, boolean extended, Rect cropRect)619 private static native long nCreate(long nativeImageDecoder, 620 @Nullable ImageDecoder decoder, int width, int height, long colorSpaceHandle, 621 boolean extended, Rect cropRect) throws IOException; 622 @FastNative nGetNativeFinalizer()623 private static native long nGetNativeFinalizer(); nDraw(long nativePtr, long canvasNativePtr)624 private static native long nDraw(long nativePtr, long canvasNativePtr); 625 @FastNative nSetAlpha(long nativePtr, int alpha)626 private static native void nSetAlpha(long nativePtr, int alpha); 627 @FastNative nGetAlpha(long nativePtr)628 private static native int nGetAlpha(long nativePtr); 629 @FastNative nSetColorFilter(long nativePtr, long nativeFilter)630 private static native void nSetColorFilter(long nativePtr, long nativeFilter); 631 @FastNative nIsRunning(long nativePtr)632 private static native boolean nIsRunning(long nativePtr); 633 // Return whether the animation started. 634 @FastNative nStart(long nativePtr)635 private static native boolean nStart(long nativePtr); 636 @FastNative nStop(long nativePtr)637 private static native boolean nStop(long nativePtr); 638 @FastNative nGetRepeatCount(long nativePtr)639 private static native int nGetRepeatCount(long nativePtr); 640 @FastNative nSetRepeatCount(long nativePtr, int repeatCount)641 private static native void nSetRepeatCount(long nativePtr, int repeatCount); 642 // Pass the drawable down to native so it can call onAnimationEnd. nSetOnAnimationEndListener(long nativePtr, @Nullable WeakReference<AnimatedImageDrawable> drawable)643 private static native void nSetOnAnimationEndListener(long nativePtr, 644 @Nullable WeakReference<AnimatedImageDrawable> drawable); 645 @FastNative nNativeByteSize(long nativePtr)646 private static native long nNativeByteSize(long nativePtr); 647 @FastNative nSetMirrored(long nativePtr, boolean mirror)648 private static native void nSetMirrored(long nativePtr, boolean mirror); 649 @FastNative nSetBounds(long nativePtr, Rect rect)650 private static native void nSetBounds(long nativePtr, Rect rect); 651 @FastNative nSetFilterBitmap(long nativePtr, boolean filterBitmap)652 private static native boolean nSetFilterBitmap(long nativePtr, boolean filterBitmap); 653 @FastNative nGetFilterBitmap(long nativePtr)654 private static native boolean nGetFilterBitmap(long nativePtr); 655 } 656