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