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