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