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