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