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