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