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