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