1 /*
2  * Copyright (C) 2006 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.NonNull;
20 import android.content.pm.ActivityInfo.Config;
21 import android.content.res.ColorStateList;
22 import android.content.res.Resources;
23 import android.content.res.Resources.Theme;
24 import android.graphics.Bitmap;
25 import android.graphics.Canvas;
26 import android.graphics.ColorFilter;
27 import android.graphics.Insets;
28 import android.graphics.Outline;
29 import android.graphics.PixelFormat;
30 import android.graphics.Rect;
31 import android.graphics.PorterDuff.Mode;
32 import android.os.SystemClock;
33 import android.util.DisplayMetrics;
34 import android.util.LayoutDirection;
35 import android.util.SparseArray;
36 import android.view.View;
37 
38 import java.util.Collection;
39 
40 /**
41  * A helper class that contains several {@link Drawable}s and selects which one to use.
42  *
43  * You can subclass it to create your own DrawableContainers or directly use one its child classes.
44  */
45 public class DrawableContainer extends Drawable implements Drawable.Callback {
46     private static final boolean DEBUG = false;
47     private static final String TAG = "DrawableContainer";
48 
49     /**
50      * To be proper, we should have a getter for dither (and alpha, etc.)
51      * so that proxy classes like this can save/restore their delegates'
52      * values, but we don't have getters. Since we do have setters
53      * (e.g. setDither), which this proxy forwards on, we have to have some
54      * default/initial setting.
55      *
56      * The initial setting for dither is now true, since it almost always seems
57      * to improve the quality at negligible cost.
58      */
59     private static final boolean DEFAULT_DITHER = true;
60     private DrawableContainerState mDrawableContainerState;
61     private Rect mHotspotBounds;
62     private Drawable mCurrDrawable;
63     private Drawable mLastDrawable;
64     private int mAlpha = 0xFF;
65 
66     /** Whether setAlpha() has been called at least once. */
67     private boolean mHasAlpha;
68 
69     private int mCurIndex = -1;
70     private int mLastIndex = -1;
71     private boolean mMutated;
72 
73     // Animations.
74     private Runnable mAnimationRunnable;
75     private long mEnterAnimationEnd;
76     private long mExitAnimationEnd;
77 
78     /** Callback that blocks invalidation. Used for drawable initialization. */
79     private BlockInvalidateCallback mBlockInvalidateCallback;
80 
81     // overrides from Drawable
82 
83     @Override
draw(Canvas canvas)84     public void draw(Canvas canvas) {
85         if (mCurrDrawable != null) {
86             mCurrDrawable.draw(canvas);
87         }
88         if (mLastDrawable != null) {
89             mLastDrawable.draw(canvas);
90         }
91     }
92 
93     @Override
getChangingConfigurations()94     public @Config int getChangingConfigurations() {
95         return super.getChangingConfigurations()
96                 | mDrawableContainerState.getChangingConfigurations();
97     }
98 
needsMirroring()99     private boolean needsMirroring() {
100         return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
101     }
102 
103     @Override
getPadding(Rect padding)104     public boolean getPadding(Rect padding) {
105         final Rect r = mDrawableContainerState.getConstantPadding();
106         boolean result;
107         if (r != null) {
108             padding.set(r);
109             result = (r.left | r.top | r.bottom | r.right) != 0;
110         } else {
111             if (mCurrDrawable != null) {
112                 result = mCurrDrawable.getPadding(padding);
113             } else {
114                 result = super.getPadding(padding);
115             }
116         }
117         if (needsMirroring()) {
118             final int left = padding.left;
119             final int right = padding.right;
120             padding.left = right;
121             padding.right = left;
122         }
123         return result;
124     }
125 
126     /**
127      * @hide
128      */
129     @Override
getOpticalInsets()130     public Insets getOpticalInsets() {
131         if (mCurrDrawable != null) {
132             return mCurrDrawable.getOpticalInsets();
133         }
134         return Insets.NONE;
135     }
136 
137     @Override
getOutline(@onNull Outline outline)138     public void getOutline(@NonNull Outline outline) {
139         if (mCurrDrawable != null) {
140             mCurrDrawable.getOutline(outline);
141         }
142     }
143 
144     @Override
setAlpha(int alpha)145     public void setAlpha(int alpha) {
146         if (!mHasAlpha || mAlpha != alpha) {
147             mHasAlpha = true;
148             mAlpha = alpha;
149             if (mCurrDrawable != null) {
150                 if (mEnterAnimationEnd == 0) {
151                     mCurrDrawable.setAlpha(alpha);
152                 } else {
153                     animate(false);
154                 }
155             }
156         }
157     }
158 
159     @Override
getAlpha()160     public int getAlpha() {
161         return mAlpha;
162     }
163 
164     @Override
setDither(boolean dither)165     public void setDither(boolean dither) {
166         if (mDrawableContainerState.mDither != dither) {
167             mDrawableContainerState.mDither = dither;
168             if (mCurrDrawable != null) {
169                 mCurrDrawable.setDither(mDrawableContainerState.mDither);
170             }
171         }
172     }
173 
174     @Override
setColorFilter(ColorFilter colorFilter)175     public void setColorFilter(ColorFilter colorFilter) {
176         mDrawableContainerState.mHasColorFilter = true;
177 
178         if (mDrawableContainerState.mColorFilter != colorFilter) {
179             mDrawableContainerState.mColorFilter = colorFilter;
180 
181             if (mCurrDrawable != null) {
182                 mCurrDrawable.setColorFilter(colorFilter);
183             }
184         }
185     }
186 
187     @Override
setTintList(ColorStateList tint)188     public void setTintList(ColorStateList tint) {
189         mDrawableContainerState.mHasTintList = true;
190 
191         if (mDrawableContainerState.mTintList != tint) {
192             mDrawableContainerState.mTintList = tint;
193 
194             if (mCurrDrawable != null) {
195                 mCurrDrawable.setTintList(tint);
196             }
197         }
198     }
199 
200     @Override
setTintMode(Mode tintMode)201     public void setTintMode(Mode tintMode) {
202         mDrawableContainerState.mHasTintMode = true;
203 
204         if (mDrawableContainerState.mTintMode != tintMode) {
205             mDrawableContainerState.mTintMode = tintMode;
206 
207             if (mCurrDrawable != null) {
208                 mCurrDrawable.setTintMode(tintMode);
209             }
210         }
211     }
212 
213     /**
214      * Change the global fade duration when a new drawable is entering
215      * the scene.
216      * @param ms The amount of time to fade in milliseconds.
217      */
setEnterFadeDuration(int ms)218     public void setEnterFadeDuration(int ms) {
219         mDrawableContainerState.mEnterFadeDuration = ms;
220     }
221 
222     /**
223      * Change the global fade duration when a new drawable is leaving
224      * the scene.
225      * @param ms The amount of time to fade in milliseconds.
226      */
setExitFadeDuration(int ms)227     public void setExitFadeDuration(int ms) {
228         mDrawableContainerState.mExitFadeDuration = ms;
229     }
230 
231     @Override
onBoundsChange(Rect bounds)232     protected void onBoundsChange(Rect bounds) {
233         if (mLastDrawable != null) {
234             mLastDrawable.setBounds(bounds);
235         }
236         if (mCurrDrawable != null) {
237             mCurrDrawable.setBounds(bounds);
238         }
239     }
240 
241     @Override
isStateful()242     public boolean isStateful() {
243         return mDrawableContainerState.isStateful();
244     }
245 
246     @Override
setAutoMirrored(boolean mirrored)247     public void setAutoMirrored(boolean mirrored) {
248         if (mDrawableContainerState.mAutoMirrored != mirrored) {
249             mDrawableContainerState.mAutoMirrored = mirrored;
250             if (mCurrDrawable != null) {
251                 mCurrDrawable.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
252             }
253         }
254     }
255 
256     @Override
isAutoMirrored()257     public boolean isAutoMirrored() {
258         return mDrawableContainerState.mAutoMirrored;
259     }
260 
261     @Override
jumpToCurrentState()262     public void jumpToCurrentState() {
263         boolean changed = false;
264         if (mLastDrawable != null) {
265             mLastDrawable.jumpToCurrentState();
266             mLastDrawable = null;
267             mLastIndex = -1;
268             changed = true;
269         }
270         if (mCurrDrawable != null) {
271             mCurrDrawable.jumpToCurrentState();
272             if (mHasAlpha) {
273                 mCurrDrawable.setAlpha(mAlpha);
274             }
275         }
276         if (mExitAnimationEnd != 0) {
277             mExitAnimationEnd = 0;
278             changed = true;
279         }
280         if (mEnterAnimationEnd != 0) {
281             mEnterAnimationEnd = 0;
282             changed = true;
283         }
284         if (changed) {
285             invalidateSelf();
286         }
287     }
288 
289     @Override
setHotspot(float x, float y)290     public void setHotspot(float x, float y) {
291         if (mCurrDrawable != null) {
292             mCurrDrawable.setHotspot(x, y);
293         }
294     }
295 
296     @Override
setHotspotBounds(int left, int top, int right, int bottom)297     public void setHotspotBounds(int left, int top, int right, int bottom) {
298         if (mHotspotBounds == null) {
299             mHotspotBounds = new Rect(left, top, right, bottom);
300         } else {
301             mHotspotBounds.set(left, top, right, bottom);
302         }
303 
304         if (mCurrDrawable != null) {
305             mCurrDrawable.setHotspotBounds(left, top, right, bottom);
306         }
307     }
308 
309     @Override
getHotspotBounds(Rect outRect)310     public void getHotspotBounds(Rect outRect) {
311         if (mHotspotBounds != null) {
312             outRect.set(mHotspotBounds);
313         } else {
314             super.getHotspotBounds(outRect);
315         }
316     }
317 
318     @Override
onStateChange(int[] state)319     protected boolean onStateChange(int[] state) {
320         if (mLastDrawable != null) {
321             return mLastDrawable.setState(state);
322         }
323         if (mCurrDrawable != null) {
324             return mCurrDrawable.setState(state);
325         }
326         return false;
327     }
328 
329     @Override
onLevelChange(int level)330     protected boolean onLevelChange(int level) {
331         if (mLastDrawable != null) {
332             return mLastDrawable.setLevel(level);
333         }
334         if (mCurrDrawable != null) {
335             return mCurrDrawable.setLevel(level);
336         }
337         return false;
338     }
339 
340     @Override
onLayoutDirectionChanged(@iew.ResolvedLayoutDir int layoutDirection)341     public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
342         // Let the container handle setting its own layout direction. Otherwise,
343         // we're accessing potentially unused states.
344         return mDrawableContainerState.setLayoutDirection(layoutDirection, getCurrentIndex());
345     }
346 
347     @Override
getIntrinsicWidth()348     public int getIntrinsicWidth() {
349         if (mDrawableContainerState.isConstantSize()) {
350             return mDrawableContainerState.getConstantWidth();
351         }
352         return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1;
353     }
354 
355     @Override
getIntrinsicHeight()356     public int getIntrinsicHeight() {
357         if (mDrawableContainerState.isConstantSize()) {
358             return mDrawableContainerState.getConstantHeight();
359         }
360         return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1;
361     }
362 
363     @Override
getMinimumWidth()364     public int getMinimumWidth() {
365         if (mDrawableContainerState.isConstantSize()) {
366             return mDrawableContainerState.getConstantMinimumWidth();
367         }
368         return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0;
369     }
370 
371     @Override
getMinimumHeight()372     public int getMinimumHeight() {
373         if (mDrawableContainerState.isConstantSize()) {
374             return mDrawableContainerState.getConstantMinimumHeight();
375         }
376         return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0;
377     }
378 
379     @Override
invalidateDrawable(@onNull Drawable who)380     public void invalidateDrawable(@NonNull Drawable who) {
381         if (who == mCurrDrawable && getCallback() != null) {
382             getCallback().invalidateDrawable(this);
383         }
384     }
385 
386     @Override
scheduleDrawable(@onNull Drawable who, @NonNull Runnable what, long when)387     public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
388         if (who == mCurrDrawable && getCallback() != null) {
389             getCallback().scheduleDrawable(this, what, when);
390         }
391     }
392 
393     @Override
unscheduleDrawable(@onNull Drawable who, @NonNull Runnable what)394     public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
395         if (who == mCurrDrawable && getCallback() != null) {
396             getCallback().unscheduleDrawable(this, what);
397         }
398     }
399 
400     @Override
setVisible(boolean visible, boolean restart)401     public boolean setVisible(boolean visible, boolean restart) {
402         boolean changed = super.setVisible(visible, restart);
403         if (mLastDrawable != null) {
404             mLastDrawable.setVisible(visible, restart);
405         }
406         if (mCurrDrawable != null) {
407             mCurrDrawable.setVisible(visible, restart);
408         }
409         return changed;
410     }
411 
412     @Override
getOpacity()413     public int getOpacity() {
414         return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT :
415                 mDrawableContainerState.getOpacity();
416     }
417 
418     /** @hide */
setCurrentIndex(int index)419     public void setCurrentIndex(int index) {
420         selectDrawable(index);
421     }
422 
423     /** @hide */
getCurrentIndex()424     public int getCurrentIndex() {
425         return mCurIndex;
426     }
427 
428     /**
429      * Sets the currently displayed drawable by index.
430      * <p>
431      * If an invalid index is specified, the current drawable will be set to
432      * {@code null} and the index will be set to {@code -1}.
433      *
434      * @param index the index of the drawable to display
435      * @return {@code true} if the drawable changed, {@code false} otherwise
436      */
selectDrawable(int index)437     public boolean selectDrawable(int index) {
438         if (index == mCurIndex) {
439             return false;
440         }
441 
442         final long now = SystemClock.uptimeMillis();
443 
444         if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + index
445                 + ": exit=" + mDrawableContainerState.mExitFadeDuration
446                 + " enter=" + mDrawableContainerState.mEnterFadeDuration);
447 
448         if (mDrawableContainerState.mExitFadeDuration > 0) {
449             if (mLastDrawable != null) {
450                 mLastDrawable.setVisible(false, false);
451             }
452             if (mCurrDrawable != null) {
453                 mLastDrawable = mCurrDrawable;
454                 mLastIndex = mCurIndex;
455                 mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
456             } else {
457                 mLastDrawable = null;
458                 mLastIndex = -1;
459                 mExitAnimationEnd = 0;
460             }
461         } else if (mCurrDrawable != null) {
462             mCurrDrawable.setVisible(false, false);
463         }
464 
465         if (index >= 0 && index < mDrawableContainerState.mNumChildren) {
466             final Drawable d = mDrawableContainerState.getChild(index);
467             mCurrDrawable = d;
468             mCurIndex = index;
469             if (d != null) {
470                 if (mDrawableContainerState.mEnterFadeDuration > 0) {
471                     mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
472                 }
473                 initializeDrawableForDisplay(d);
474             }
475         } else {
476             mCurrDrawable = null;
477             mCurIndex = -1;
478         }
479 
480         if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
481             if (mAnimationRunnable == null) {
482                 mAnimationRunnable = new Runnable() {
483                     @Override public void run() {
484                         animate(true);
485                         invalidateSelf();
486                     }
487                 };
488             } else {
489                 unscheduleSelf(mAnimationRunnable);
490             }
491             // Compute first frame and schedule next animation.
492             animate(true);
493         }
494 
495         invalidateSelf();
496 
497         return true;
498     }
499 
500     /**
501      * Initializes a drawable for display in this container.
502      *
503      * @param d The drawable to initialize.
504      */
initializeDrawableForDisplay(Drawable d)505     private void initializeDrawableForDisplay(Drawable d) {
506         if (mBlockInvalidateCallback == null) {
507             mBlockInvalidateCallback = new BlockInvalidateCallback();
508         }
509 
510         // Temporary fix for suspending callbacks during initialization. We
511         // don't want any of these setters causing an invalidate() since that
512         // may call back into DrawableContainer.
513         d.setCallback(mBlockInvalidateCallback.wrap(d.getCallback()));
514 
515         try {
516             if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) {
517                 d.setAlpha(mAlpha);
518             }
519 
520             if (mDrawableContainerState.mHasColorFilter) {
521                 // Color filter always overrides tint.
522                 d.setColorFilter(mDrawableContainerState.mColorFilter);
523             } else {
524                 if (mDrawableContainerState.mHasTintList) {
525                     d.setTintList(mDrawableContainerState.mTintList);
526                 }
527                 if (mDrawableContainerState.mHasTintMode) {
528                     d.setTintMode(mDrawableContainerState.mTintMode);
529                 }
530             }
531 
532             d.setVisible(isVisible(), true);
533             d.setDither(mDrawableContainerState.mDither);
534             d.setState(getState());
535             d.setLevel(getLevel());
536             d.setBounds(getBounds());
537             d.setLayoutDirection(getLayoutDirection());
538             d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
539 
540             final Rect hotspotBounds = mHotspotBounds;
541             if (hotspotBounds != null) {
542                 d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top,
543                         hotspotBounds.right, hotspotBounds.bottom);
544             }
545         } finally {
546             d.setCallback(mBlockInvalidateCallback.unwrap());
547         }
548     }
549 
animate(boolean schedule)550     void animate(boolean schedule) {
551         mHasAlpha = true;
552 
553         final long now = SystemClock.uptimeMillis();
554         boolean animating = false;
555         if (mCurrDrawable != null) {
556             if (mEnterAnimationEnd != 0) {
557                 if (mEnterAnimationEnd <= now) {
558                     mCurrDrawable.setAlpha(mAlpha);
559                     mEnterAnimationEnd = 0;
560                 } else {
561                     int animAlpha = (int)((mEnterAnimationEnd-now)*255)
562                             / mDrawableContainerState.mEnterFadeDuration;
563                     mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
564                     animating = true;
565                 }
566             }
567         } else {
568             mEnterAnimationEnd = 0;
569         }
570         if (mLastDrawable != null) {
571             if (mExitAnimationEnd != 0) {
572                 if (mExitAnimationEnd <= now) {
573                     mLastDrawable.setVisible(false, false);
574                     mLastDrawable = null;
575                     mLastIndex = -1;
576                     mExitAnimationEnd = 0;
577                 } else {
578                     int animAlpha = (int)((mExitAnimationEnd-now)*255)
579                             / mDrawableContainerState.mExitFadeDuration;
580                     mLastDrawable.setAlpha((animAlpha*mAlpha)/255);
581                     animating = true;
582                 }
583             }
584         } else {
585             mExitAnimationEnd = 0;
586         }
587 
588         if (schedule && animating) {
589             scheduleSelf(mAnimationRunnable, now + 1000 / 60);
590         }
591     }
592 
593     @Override
getCurrent()594     public Drawable getCurrent() {
595         return mCurrDrawable;
596     }
597 
598     /**
599      * Updates the source density based on the resources used to inflate
600      * density-dependent values. Implementing classes should call this method
601      * during inflation.
602      *
603      * @param res the resources used to inflate density-dependent values
604      */
updateDensity(Resources res)605     final void updateDensity(Resources res) {
606         mDrawableContainerState.updateDensity(res);
607     }
608 
609     @Override
applyTheme(Theme theme)610     public void applyTheme(Theme theme) {
611         mDrawableContainerState.applyTheme(theme);
612     }
613 
614     @Override
canApplyTheme()615     public boolean canApplyTheme() {
616         return mDrawableContainerState.canApplyTheme();
617     }
618 
619     @Override
getConstantState()620     public ConstantState getConstantState() {
621         if (mDrawableContainerState.canConstantState()) {
622             mDrawableContainerState.mChangingConfigurations = getChangingConfigurations();
623             return mDrawableContainerState;
624         }
625         return null;
626     }
627 
628     @Override
mutate()629     public Drawable mutate() {
630         if (!mMutated && super.mutate() == this) {
631             final DrawableContainerState clone = cloneConstantState();
632             clone.mutate();
633             setConstantState(clone);
634             mMutated = true;
635         }
636         return this;
637     }
638 
639     /**
640      * Returns a shallow copy of the container's constant state to be used as
641      * the base state for {@link #mutate()}.
642      *
643      * @return a shallow copy of the constant state
644      */
cloneConstantState()645     DrawableContainerState cloneConstantState() {
646         return mDrawableContainerState;
647     }
648 
649     /**
650      * @hide
651      */
clearMutated()652     public void clearMutated() {
653         super.clearMutated();
654         mDrawableContainerState.clearMutated();
655         mMutated = false;
656     }
657 
658     /**
659      * A ConstantState that can contain several {@link Drawable}s.
660      *
661      * This class was made public to enable testing, and its visibility may change in a future
662      * release.
663      */
664     public abstract static class DrawableContainerState extends ConstantState {
665         final DrawableContainer mOwner;
666 
667         Resources mSourceRes;
668         int mDensity = DisplayMetrics.DENSITY_DEFAULT;
669         @Config int mChangingConfigurations;
670         @Config int mChildrenChangingConfigurations;
671 
672         SparseArray<ConstantState> mDrawableFutures;
673         Drawable[] mDrawables;
674         int mNumChildren;
675 
676         boolean mVariablePadding = false;
677         boolean mCheckedPadding;
678         Rect mConstantPadding;
679 
680         boolean mConstantSize = false;
681         boolean mCheckedConstantSize;
682         int mConstantWidth;
683         int mConstantHeight;
684         int mConstantMinimumWidth;
685         int mConstantMinimumHeight;
686 
687         boolean mCheckedOpacity;
688         int mOpacity;
689 
690         boolean mCheckedStateful;
691         boolean mStateful;
692 
693         boolean mCheckedConstantState;
694         boolean mCanConstantState;
695 
696         boolean mDither = DEFAULT_DITHER;
697 
698         boolean mMutated;
699         int mLayoutDirection;
700 
701         int mEnterFadeDuration = 0;
702         int mExitFadeDuration = 0;
703 
704         boolean mAutoMirrored;
705 
706         ColorFilter mColorFilter;
707         boolean mHasColorFilter;
708 
709         ColorStateList mTintList;
710         Mode mTintMode;
711         boolean mHasTintList;
712         boolean mHasTintMode;
713 
DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res)714         DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
715                 Resources res) {
716             mOwner = owner;
717             mSourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null);
718             mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
719 
720             if (orig != null) {
721                 mChangingConfigurations = orig.mChangingConfigurations;
722                 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
723 
724                 mCheckedConstantState = true;
725                 mCanConstantState = true;
726 
727                 mVariablePadding = orig.mVariablePadding;
728                 mConstantSize = orig.mConstantSize;
729                 mDither = orig.mDither;
730                 mMutated = orig.mMutated;
731                 mLayoutDirection = orig.mLayoutDirection;
732                 mEnterFadeDuration = orig.mEnterFadeDuration;
733                 mExitFadeDuration = orig.mExitFadeDuration;
734                 mAutoMirrored = orig.mAutoMirrored;
735                 mColorFilter = orig.mColorFilter;
736                 mHasColorFilter = orig.mHasColorFilter;
737                 mTintList = orig.mTintList;
738                 mTintMode = orig.mTintMode;
739                 mHasTintList = orig.mHasTintList;
740                 mHasTintMode = orig.mHasTintMode;
741 
742                 if (orig.mDensity == mDensity) {
743                     if (orig.mCheckedPadding) {
744                         mConstantPadding = new Rect(orig.mConstantPadding);
745                         mCheckedPadding = true;
746                     }
747 
748                     if (orig.mCheckedConstantSize) {
749                         mConstantWidth = orig.mConstantWidth;
750                         mConstantHeight = orig.mConstantHeight;
751                         mConstantMinimumWidth = orig.mConstantMinimumWidth;
752                         mConstantMinimumHeight = orig.mConstantMinimumHeight;
753                         mCheckedConstantSize = true;
754                     }
755                 }
756 
757                 if (orig.mCheckedOpacity) {
758                     mOpacity = orig.mOpacity;
759                     mCheckedOpacity = true;
760                 }
761 
762                 if (orig.mCheckedStateful) {
763                     mStateful = orig.mStateful;
764                     mCheckedStateful = true;
765                 }
766 
767                 // Postpone cloning children and futures until we're absolutely
768                 // sure that we're done computing values for the original state.
769                 final Drawable[] origDr = orig.mDrawables;
770                 mDrawables = new Drawable[origDr.length];
771                 mNumChildren = orig.mNumChildren;
772 
773                 final SparseArray<ConstantState> origDf = orig.mDrawableFutures;
774                 if (origDf != null) {
775                     mDrawableFutures = origDf.clone();
776                 } else {
777                     mDrawableFutures = new SparseArray<>(mNumChildren);
778                 }
779 
780                 // Create futures for drawables with constant states. If a
781                 // drawable doesn't have a constant state, then we can't clone
782                 // it and we'll have to reference the original.
783                 final int N = mNumChildren;
784                 for (int i = 0; i < N; i++) {
785                     if (origDr[i] != null) {
786                         final ConstantState cs = origDr[i].getConstantState();
787                         if (cs != null) {
788                             mDrawableFutures.put(i, cs);
789                         } else {
790                             mDrawables[i] = origDr[i];
791                         }
792                     }
793                 }
794             } else {
795                 mDrawables = new Drawable[10];
796                 mNumChildren = 0;
797             }
798         }
799 
800         @Override
getChangingConfigurations()801         public @Config int getChangingConfigurations() {
802             return mChangingConfigurations | mChildrenChangingConfigurations;
803         }
804 
805         /**
806          * Adds the drawable to the end of the list of contained drawables.
807          *
808          * @param dr the drawable to add
809          * @return the position of the drawable within the container
810          */
addChild(Drawable dr)811         public final int addChild(Drawable dr) {
812             final int pos = mNumChildren;
813             if (pos >= mDrawables.length) {
814                 growArray(pos, pos+10);
815             }
816 
817             dr.mutate();
818             dr.setVisible(false, true);
819             dr.setCallback(mOwner);
820 
821             mDrawables[pos] = dr;
822             mNumChildren++;
823             mChildrenChangingConfigurations |= dr.getChangingConfigurations();
824             mCheckedStateful = false;
825             mCheckedOpacity = false;
826 
827             mConstantPadding = null;
828             mCheckedPadding = false;
829             mCheckedConstantSize = false;
830             mCheckedConstantState = false;
831 
832             return pos;
833         }
834 
getCapacity()835         final int getCapacity() {
836             return mDrawables.length;
837         }
838 
createAllFutures()839         private void createAllFutures() {
840             if (mDrawableFutures != null) {
841                 final int futureCount = mDrawableFutures.size();
842                 for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) {
843                     final int index = mDrawableFutures.keyAt(keyIndex);
844                     final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
845                     mDrawables[index] = prepareDrawable(cs.newDrawable(mSourceRes));
846                 }
847 
848                 mDrawableFutures = null;
849             }
850         }
851 
prepareDrawable(Drawable child)852         private Drawable prepareDrawable(Drawable child) {
853             child.setLayoutDirection(mLayoutDirection);
854             child.setCallback(mOwner);
855             child = child.mutate();
856             return child;
857         }
858 
getChildCount()859         public final int getChildCount() {
860             return mNumChildren;
861         }
862 
863         /*
864          * @deprecated Use {@link #getChild} instead.
865          */
getChildren()866         public final Drawable[] getChildren() {
867             // Create all futures for backwards compatibility.
868             createAllFutures();
869 
870             return mDrawables;
871         }
872 
getChild(int index)873         public final Drawable getChild(int index) {
874             final Drawable result = mDrawables[index];
875             if (result != null) {
876                 return result;
877             }
878 
879             // Prepare future drawable if necessary.
880             if (mDrawableFutures != null) {
881                 final int keyIndex = mDrawableFutures.indexOfKey(index);
882                 if (keyIndex >= 0) {
883                     final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
884                     final Drawable prepared = prepareDrawable(cs.newDrawable(mSourceRes));
885                     mDrawables[index] = prepared;
886                     mDrawableFutures.removeAt(keyIndex);
887                     if (mDrawableFutures.size() == 0) {
888                         mDrawableFutures = null;
889                     }
890                     return prepared;
891                 }
892             }
893 
894             return null;
895         }
896 
setLayoutDirection(int layoutDirection, int currentIndex)897         final boolean setLayoutDirection(int layoutDirection, int currentIndex) {
898             boolean changed = false;
899 
900             // No need to call createAllFutures, since future drawables will
901             // change layout direction when they are prepared.
902             final int N = mNumChildren;
903             final Drawable[] drawables = mDrawables;
904             for (int i = 0; i < N; i++) {
905                 if (drawables[i] != null) {
906                     final boolean childChanged = drawables[i].setLayoutDirection(layoutDirection);
907                     if (i == currentIndex) {
908                         changed = childChanged;
909                     }
910                 }
911             }
912 
913             mLayoutDirection = layoutDirection;
914 
915             return changed;
916         }
917 
918         /**
919          * Updates the source density based on the resources used to inflate
920          * density-dependent values.
921          *
922          * @param res the resources used to inflate density-dependent values
923          */
updateDensity(Resources res)924         final void updateDensity(Resources res) {
925             if (res != null) {
926                 mSourceRes = res;
927 
928                 // The density may have changed since the last update (if any). Any
929                 // dimension-type attributes will need their default values scaled.
930                 final int targetDensity = Drawable.resolveDensity(res, mDensity);
931                 final int sourceDensity = mDensity;
932                 mDensity = targetDensity;
933 
934                 if (sourceDensity != targetDensity) {
935                     mCheckedConstantSize = false;
936                     mCheckedPadding = false;
937                 }
938             }
939         }
940 
applyTheme(Theme theme)941         final void applyTheme(Theme theme) {
942             if (theme != null) {
943                 createAllFutures();
944 
945                 final int N = mNumChildren;
946                 final Drawable[] drawables = mDrawables;
947                 for (int i = 0; i < N; i++) {
948                     if (drawables[i] != null && drawables[i].canApplyTheme()) {
949                         drawables[i].applyTheme(theme);
950 
951                         // Update cached mask of child changing configurations.
952                         mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations();
953                     }
954                 }
955 
956                 updateDensity(theme.getResources());
957             }
958         }
959 
960         @Override
canApplyTheme()961         public boolean canApplyTheme() {
962             final int N = mNumChildren;
963             final Drawable[] drawables = mDrawables;
964             for (int i = 0; i < N; i++) {
965                 final Drawable d = drawables[i];
966                 if (d != null) {
967                     if (d.canApplyTheme()) {
968                         return true;
969                     }
970                 } else {
971                     final ConstantState future = mDrawableFutures.get(i);
972                     if (future != null && future.canApplyTheme()) {
973                         return true;
974                     }
975                 }
976             }
977 
978             return false;
979         }
980 
mutate()981         private void mutate() {
982             // No need to call createAllFutures, since future drawables will
983             // mutate when they are prepared.
984             final int N = mNumChildren;
985             final Drawable[] drawables = mDrawables;
986             for (int i = 0; i < N; i++) {
987                 if (drawables[i] != null) {
988                     drawables[i].mutate();
989                 }
990             }
991 
992             mMutated = true;
993         }
994 
clearMutated()995         final void clearMutated() {
996             final int N = mNumChildren;
997             final Drawable[] drawables = mDrawables;
998             for (int i = 0; i < N; i++) {
999                 if (drawables[i] != null) {
1000                     drawables[i].clearMutated();
1001                 }
1002             }
1003 
1004             mMutated = false;
1005         }
1006 
1007         /**
1008          * A boolean value indicating whether to use the maximum padding value
1009          * of all frames in the set (false), or to use the padding value of the
1010          * frame being shown (true). Default value is false.
1011          */
setVariablePadding(boolean variable)1012         public final void setVariablePadding(boolean variable) {
1013             mVariablePadding = variable;
1014         }
1015 
getConstantPadding()1016         public final Rect getConstantPadding() {
1017             if (mVariablePadding) {
1018                 return null;
1019             }
1020 
1021             if ((mConstantPadding != null) || mCheckedPadding) {
1022                 return mConstantPadding;
1023             }
1024 
1025             createAllFutures();
1026 
1027             Rect r = null;
1028             final Rect t = new Rect();
1029             final int N = mNumChildren;
1030             final Drawable[] drawables = mDrawables;
1031             for (int i = 0; i < N; i++) {
1032                 if (drawables[i].getPadding(t)) {
1033                     if (r == null) r = new Rect(0, 0, 0, 0);
1034                     if (t.left > r.left) r.left = t.left;
1035                     if (t.top > r.top) r.top = t.top;
1036                     if (t.right > r.right) r.right = t.right;
1037                     if (t.bottom > r.bottom) r.bottom = t.bottom;
1038                 }
1039             }
1040 
1041             mCheckedPadding = true;
1042             return (mConstantPadding = r);
1043         }
1044 
setConstantSize(boolean constant)1045         public final void setConstantSize(boolean constant) {
1046             mConstantSize = constant;
1047         }
1048 
isConstantSize()1049         public final boolean isConstantSize() {
1050             return mConstantSize;
1051         }
1052 
getConstantWidth()1053         public final int getConstantWidth() {
1054             if (!mCheckedConstantSize) {
1055                 computeConstantSize();
1056             }
1057 
1058             return mConstantWidth;
1059         }
1060 
getConstantHeight()1061         public final int getConstantHeight() {
1062             if (!mCheckedConstantSize) {
1063                 computeConstantSize();
1064             }
1065 
1066             return mConstantHeight;
1067         }
1068 
getConstantMinimumWidth()1069         public final int getConstantMinimumWidth() {
1070             if (!mCheckedConstantSize) {
1071                 computeConstantSize();
1072             }
1073 
1074             return mConstantMinimumWidth;
1075         }
1076 
getConstantMinimumHeight()1077         public final int getConstantMinimumHeight() {
1078             if (!mCheckedConstantSize) {
1079                 computeConstantSize();
1080             }
1081 
1082             return mConstantMinimumHeight;
1083         }
1084 
computeConstantSize()1085         protected void computeConstantSize() {
1086             mCheckedConstantSize = true;
1087 
1088             createAllFutures();
1089 
1090             final int N = mNumChildren;
1091             final Drawable[] drawables = mDrawables;
1092             mConstantWidth = mConstantHeight = -1;
1093             mConstantMinimumWidth = mConstantMinimumHeight = 0;
1094             for (int i = 0; i < N; i++) {
1095                 final Drawable dr = drawables[i];
1096                 int s = dr.getIntrinsicWidth();
1097                 if (s > mConstantWidth) mConstantWidth = s;
1098                 s = dr.getIntrinsicHeight();
1099                 if (s > mConstantHeight) mConstantHeight = s;
1100                 s = dr.getMinimumWidth();
1101                 if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
1102                 s = dr.getMinimumHeight();
1103                 if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
1104             }
1105         }
1106 
setEnterFadeDuration(int duration)1107         public final void setEnterFadeDuration(int duration) {
1108             mEnterFadeDuration = duration;
1109         }
1110 
getEnterFadeDuration()1111         public final int getEnterFadeDuration() {
1112             return mEnterFadeDuration;
1113         }
1114 
setExitFadeDuration(int duration)1115         public final void setExitFadeDuration(int duration) {
1116             mExitFadeDuration = duration;
1117         }
1118 
getExitFadeDuration()1119         public final int getExitFadeDuration() {
1120             return mExitFadeDuration;
1121         }
1122 
getOpacity()1123         public final int getOpacity() {
1124             if (mCheckedOpacity) {
1125                 return mOpacity;
1126             }
1127 
1128             createAllFutures();
1129 
1130             mCheckedOpacity = true;
1131 
1132             final int N = mNumChildren;
1133             final Drawable[] drawables = mDrawables;
1134             int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
1135             for (int i = 1; i < N; i++) {
1136                 op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
1137             }
1138 
1139             mOpacity = op;
1140             return op;
1141         }
1142 
isStateful()1143         public final boolean isStateful() {
1144             if (mCheckedStateful) {
1145                 return mStateful;
1146             }
1147 
1148             createAllFutures();
1149 
1150             mCheckedStateful = true;
1151 
1152             final int N = mNumChildren;
1153             final Drawable[] drawables = mDrawables;
1154             for (int i = 0; i < N; i++) {
1155                 if (drawables[i].isStateful()) {
1156                     mStateful = true;
1157                     return true;
1158                 }
1159             }
1160 
1161             mStateful = false;
1162             return false;
1163         }
1164 
growArray(int oldSize, int newSize)1165         public void growArray(int oldSize, int newSize) {
1166             Drawable[] newDrawables = new Drawable[newSize];
1167             System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
1168             mDrawables = newDrawables;
1169         }
1170 
canConstantState()1171         public synchronized boolean canConstantState() {
1172             if (mCheckedConstantState) {
1173                 return mCanConstantState;
1174             }
1175 
1176             createAllFutures();
1177 
1178             mCheckedConstantState = true;
1179 
1180             final int N = mNumChildren;
1181             final Drawable[] drawables = mDrawables;
1182             for (int i = 0; i < N; i++) {
1183                 if (drawables[i].getConstantState() == null) {
1184                     mCanConstantState = false;
1185                     return false;
1186                 }
1187             }
1188 
1189             mCanConstantState = true;
1190             return true;
1191         }
1192 
1193         /** @hide */
1194         @Override
addAtlasableBitmaps(Collection<Bitmap> atlasList)1195         public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
1196             final int N = mNumChildren;
1197             int pixelCount = 0;
1198             for (int i = 0; i < N; i++) {
1199                 final ConstantState state = getChild(i).getConstantState();
1200                 if (state != null) {
1201                     pixelCount += state.addAtlasableBitmaps(atlasList);
1202                 }
1203             }
1204             return pixelCount;
1205         }
1206     }
1207 
setConstantState(DrawableContainerState state)1208     protected void setConstantState(DrawableContainerState state) {
1209         mDrawableContainerState = state;
1210 
1211         // The locally cached drawables may have changed.
1212         if (mCurIndex >= 0) {
1213             mCurrDrawable = state.getChild(mCurIndex);
1214             if (mCurrDrawable != null) {
1215                 initializeDrawableForDisplay(mCurrDrawable);
1216             }
1217         }
1218 
1219         // Clear out the last drawable. We don't have enough information to
1220         // propagate local state from the past.
1221         mLastIndex = -1;
1222         mLastDrawable = null;
1223     }
1224 
1225     /**
1226      * Callback that blocks drawable invalidation.
1227      */
1228     private static class BlockInvalidateCallback implements Drawable.Callback {
1229         private Drawable.Callback mCallback;
1230 
wrap(Drawable.Callback callback)1231         public BlockInvalidateCallback wrap(Drawable.Callback callback) {
1232             mCallback = callback;
1233             return this;
1234         }
1235 
unwrap()1236         public Drawable.Callback unwrap() {
1237             final Drawable.Callback callback = mCallback;
1238             mCallback = null;
1239             return callback;
1240         }
1241 
1242         @Override
invalidateDrawable(@onNull Drawable who)1243         public void invalidateDrawable(@NonNull Drawable who) {
1244             // Ignore invalidation.
1245         }
1246 
1247         @Override
scheduleDrawable(@onNull Drawable who, @NonNull Runnable what, long when)1248         public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
1249             if (mCallback != null) {
1250                 mCallback.scheduleDrawable(who, what, when);
1251             }
1252         }
1253 
1254         @Override
unscheduleDrawable(@onNull Drawable who, @NonNull Runnable what)1255         public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
1256             if (mCallback != null) {
1257                 mCallback.unscheduleDrawable(who, what);
1258             }
1259         }
1260     }
1261 }
1262