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