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