• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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>&lt;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