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