• 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.compat.annotation.UnsupportedAppUsage;
21 import android.content.pm.ActivityInfo.Config;
22 import android.content.res.ColorStateList;
23 import android.content.res.Resources;
24 import android.content.res.Resources.Theme;
25 import android.graphics.BlendMode;
26 import android.graphics.Canvas;
27 import android.graphics.ColorFilter;
28 import android.graphics.Insets;
29 import android.graphics.Outline;
30 import android.graphics.PixelFormat;
31 import android.graphics.Rect;
32 import android.os.Build;
33 import android.os.SystemClock;
34 import android.util.DisplayMetrics;
35 import android.util.LayoutDirection;
36 import android.util.SparseArray;
37 import android.view.View;
38 
39 /**
40  * A helper class that contains several {@link Drawable}s and selects which one to use.
41  *
42  * You can subclass it to create your own DrawableContainers or directly use one its child classes.
43  */
44 public class DrawableContainer extends Drawable implements Drawable.Callback {
45     private static final boolean DEBUG = false;
46     private static final String TAG = "DrawableContainer";
47 
48     /**
49      * To be proper, we should have a getter for dither (and alpha, etc.)
50      * so that proxy classes like this can save/restore their delegates'
51      * values, but we don't have getters. Since we do have setters
52      * (e.g. setDither), which this proxy forwards on, we have to have some
53      * default/initial setting.
54      *
55      * The initial setting for dither is now true, since it almost always seems
56      * to improve the quality at negligible cost.
57      */
58     private static final boolean DEFAULT_DITHER = true;
59     @UnsupportedAppUsage
60     private DrawableContainerState mDrawableContainerState;
61     private Rect mHotspotBounds;
62     private Drawable mCurrDrawable;
63     @UnsupportedAppUsage
64     private Drawable mLastDrawable;
65     private int mAlpha = 0xFF;
66 
67     /** Whether setAlpha() has been called at least once. */
68     private boolean mHasAlpha;
69 
70     private int mCurIndex = -1;
71     private int mLastIndex = -1;
72     private boolean mMutated;
73 
74     // Animations.
75     private Runnable mAnimationRunnable;
76     private long mEnterAnimationEnd;
77     private long mExitAnimationEnd;
78 
79     /** Callback that blocks invalidation. Used for drawable initialization. */
80     private BlockInvalidateCallback mBlockInvalidateCallback;
81 
82     // overrides from Drawable
83 
84     @Override
draw(Canvas canvas)85     public void draw(Canvas canvas) {
86         if (mCurrDrawable != null) {
87             mCurrDrawable.draw(canvas);
88         }
89         if (mLastDrawable != null) {
90             mLastDrawable.draw(canvas);
91         }
92     }
93 
94     @Override
getChangingConfigurations()95     public @Config int getChangingConfigurations() {
96         return super.getChangingConfigurations()
97                 | mDrawableContainerState.getChangingConfigurations();
98     }
99 
needsMirroring()100     private boolean needsMirroring() {
101         return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
102     }
103 
104     @Override
getPadding(Rect padding)105     public boolean getPadding(Rect padding) {
106         final Rect r = mDrawableContainerState.getConstantPadding();
107         boolean result;
108         if (r != null) {
109             padding.set(r);
110             result = (r.left | r.top | r.bottom | r.right) != 0;
111         } else {
112             if (mCurrDrawable != null) {
113                 result = mCurrDrawable.getPadding(padding);
114             } else {
115                 result = super.getPadding(padding);
116             }
117         }
118         if (needsMirroring()) {
119             final int left = padding.left;
120             final int right = padding.right;
121             padding.left = right;
122             padding.right = left;
123         }
124         return result;
125     }
126 
127     @Override
getOpticalInsets()128     public Insets getOpticalInsets() {
129         if (mCurrDrawable != null) {
130             return mCurrDrawable.getOpticalInsets();
131         }
132         return Insets.NONE;
133     }
134 
135     @Override
getOutline(@onNull Outline outline)136     public void getOutline(@NonNull Outline outline) {
137         if (mCurrDrawable != null) {
138             mCurrDrawable.getOutline(outline);
139         }
140     }
141 
142     @Override
setAlpha(int alpha)143     public void setAlpha(int alpha) {
144         if (!mHasAlpha || mAlpha != alpha) {
145             mHasAlpha = true;
146             mAlpha = alpha;
147             if (mCurrDrawable != null) {
148                 if (mEnterAnimationEnd == 0) {
149                     mCurrDrawable.setAlpha(alpha);
150                 } else {
151                     animate(false);
152                 }
153             }
154         }
155     }
156 
157     @Override
getAlpha()158     public int getAlpha() {
159         return mAlpha;
160     }
161 
162     @Override
setDither(boolean dither)163     public void setDither(boolean dither) {
164         if (mDrawableContainerState.mDither != dither) {
165             mDrawableContainerState.mDither = dither;
166             if (mCurrDrawable != null) {
167                 mCurrDrawable.setDither(mDrawableContainerState.mDither);
168             }
169         }
170     }
171 
172     @Override
setColorFilter(ColorFilter colorFilter)173     public void setColorFilter(ColorFilter colorFilter) {
174         mDrawableContainerState.mHasColorFilter = true;
175 
176         if (mDrawableContainerState.mColorFilter != colorFilter) {
177             mDrawableContainerState.mColorFilter = colorFilter;
178 
179             if (mCurrDrawable != null) {
180                 mCurrDrawable.setColorFilter(colorFilter);
181             }
182         }
183     }
184 
185     @Override
setTintList(ColorStateList tint)186     public void setTintList(ColorStateList tint) {
187         mDrawableContainerState.mHasTintList = true;
188 
189         if (mDrawableContainerState.mTintList != tint) {
190             mDrawableContainerState.mTintList = tint;
191 
192             if (mCurrDrawable != null) {
193                 mCurrDrawable.setTintList(tint);
194             }
195         }
196     }
197 
198     @Override
setTintBlendMode(@onNull BlendMode blendMode)199     public void setTintBlendMode(@NonNull BlendMode blendMode) {
200         mDrawableContainerState.mHasTintMode = true;
201 
202         if (mDrawableContainerState.mBlendMode != blendMode) {
203             mDrawableContainerState.mBlendMode = blendMode;
204 
205             if (mCurrDrawable != null) {
206                 mCurrDrawable.setTintBlendMode(blendMode);
207             }
208         }
209     }
210 
211     /**
212      * Change the global fade duration when a new drawable is entering
213      * the scene.
214      *
215      * @param ms The amount of time to fade in milliseconds.
216      */
setEnterFadeDuration(int ms)217     public void setEnterFadeDuration(int ms) {
218         mDrawableContainerState.mEnterFadeDuration = ms;
219     }
220 
221     /**
222      * Change the global fade duration when a new drawable is leaving
223      * the scene.
224      *
225      * @param ms The amount of time to fade in milliseconds.
226      */
setExitFadeDuration(int ms)227     public void setExitFadeDuration(int ms) {
228         mDrawableContainerState.mExitFadeDuration = ms;
229     }
230 
231     @Override
onBoundsChange(Rect bounds)232     protected void onBoundsChange(Rect bounds) {
233         if (mLastDrawable != null) {
234             mLastDrawable.setBounds(bounds);
235         }
236         if (mCurrDrawable != null) {
237             mCurrDrawable.setBounds(bounds);
238         }
239     }
240 
241     @Override
isStateful()242     public boolean isStateful() {
243         return mDrawableContainerState.isStateful();
244     }
245 
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.setTintBlendMode(mDrawableContainerState.mBlendMode);
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         @UnsupportedAppUsage
693         Drawable[] mDrawables;
694         int mNumChildren;
695 
696         boolean mVariablePadding = false;
697         boolean mCheckedPadding;
698         @UnsupportedAppUsage
699         Rect mConstantPadding;
700 
701         boolean mConstantSize = false;
702         boolean mCheckedConstantSize;
703         int mConstantWidth;
704         int mConstantHeight;
705         int mConstantMinimumWidth;
706         int mConstantMinimumHeight;
707 
708         boolean mCheckedOpacity;
709         int mOpacity;
710 
711         boolean mCheckedStateful;
712         boolean mStateful;
713 
714         boolean mCheckedConstantState;
715         boolean mCanConstantState;
716 
717         boolean mDither = DEFAULT_DITHER;
718 
719         boolean mMutated;
720         int mLayoutDirection;
721 
722         int mEnterFadeDuration = 0;
723         int mExitFadeDuration = 0;
724 
725         boolean mAutoMirrored;
726 
727         ColorFilter mColorFilter;
728         @UnsupportedAppUsage
729         boolean mHasColorFilter;
730 
731         ColorStateList mTintList;
732         BlendMode mBlendMode;
733         boolean mHasTintList;
734         boolean mHasTintMode;
735 
736         /**
737          * @hide
738          */
739         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res)740         protected DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
741                 Resources res) {
742             mOwner = owner;
743             mSourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null);
744             mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
745 
746             if (orig != null) {
747                 mChangingConfigurations = orig.mChangingConfigurations;
748                 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
749 
750                 mCheckedConstantState = true;
751                 mCanConstantState = true;
752 
753                 mVariablePadding = orig.mVariablePadding;
754                 mConstantSize = orig.mConstantSize;
755                 mDither = orig.mDither;
756                 mMutated = orig.mMutated;
757                 mLayoutDirection = orig.mLayoutDirection;
758                 mEnterFadeDuration = orig.mEnterFadeDuration;
759                 mExitFadeDuration = orig.mExitFadeDuration;
760                 mAutoMirrored = orig.mAutoMirrored;
761                 mColorFilter = orig.mColorFilter;
762                 mHasColorFilter = orig.mHasColorFilter;
763                 mTintList = orig.mTintList;
764                 mBlendMode = orig.mBlendMode;
765                 mHasTintList = orig.mHasTintList;
766                 mHasTintMode = orig.mHasTintMode;
767 
768                 if (orig.mDensity == mDensity) {
769                     if (orig.mCheckedPadding) {
770                         mConstantPadding = new Rect(orig.mConstantPadding);
771                         mCheckedPadding = true;
772                     }
773 
774                     if (orig.mCheckedConstantSize) {
775                         mConstantWidth = orig.mConstantWidth;
776                         mConstantHeight = orig.mConstantHeight;
777                         mConstantMinimumWidth = orig.mConstantMinimumWidth;
778                         mConstantMinimumHeight = orig.mConstantMinimumHeight;
779                         mCheckedConstantSize = true;
780                     }
781                 }
782 
783                 if (orig.mCheckedOpacity) {
784                     mOpacity = orig.mOpacity;
785                     mCheckedOpacity = true;
786                 }
787 
788                 if (orig.mCheckedStateful) {
789                     mStateful = orig.mStateful;
790                     mCheckedStateful = true;
791                 }
792 
793                 // Postpone cloning children and futures until we're absolutely
794                 // sure that we're done computing values for the original state.
795                 final Drawable[] origDr = orig.mDrawables;
796                 mDrawables = new Drawable[origDr.length];
797                 mNumChildren = orig.mNumChildren;
798 
799                 final SparseArray<ConstantState> origDf = orig.mDrawableFutures;
800                 if (origDf != null) {
801                     mDrawableFutures = origDf.clone();
802                 } else {
803                     mDrawableFutures = new SparseArray<>(mNumChildren);
804                 }
805 
806                 // Create futures for drawables with constant states. If a
807                 // drawable doesn't have a constant state, then we can't clone
808                 // it and we'll have to reference the original.
809                 final int N = mNumChildren;
810                 for (int i = 0; i < N; i++) {
811                     if (origDr[i] != null) {
812                         final ConstantState cs = origDr[i].getConstantState();
813                         if (cs != null) {
814                             mDrawableFutures.put(i, cs);
815                         } else {
816                             mDrawables[i] = origDr[i];
817                         }
818                     }
819                 }
820             } else {
821                 mDrawables = new Drawable[10];
822                 mNumChildren = 0;
823             }
824         }
825 
826         @Override
getChangingConfigurations()827         public @Config int getChangingConfigurations() {
828             return mChangingConfigurations | mChildrenChangingConfigurations;
829         }
830 
831         /**
832          * Adds the drawable to the end of the list of contained drawables.
833          *
834          * @param dr the drawable to add
835          * @return the position of the drawable within the container
836          */
addChild(Drawable dr)837         public final int addChild(Drawable dr) {
838             final int pos = mNumChildren;
839             if (pos >= mDrawables.length) {
840                 growArray(pos, pos+10);
841             }
842 
843             dr.mutate();
844             dr.setVisible(false, true);
845             dr.setCallback(mOwner);
846 
847             mDrawables[pos] = dr;
848             mNumChildren++;
849             mChildrenChangingConfigurations |= dr.getChangingConfigurations();
850 
851             invalidateCache();
852 
853             mConstantPadding = null;
854             mCheckedPadding = false;
855             mCheckedConstantSize = false;
856             mCheckedConstantState = false;
857 
858             return pos;
859         }
860 
861         /**
862          * Invalidates the cached opacity and statefulness.
863          */
invalidateCache()864         void invalidateCache() {
865             mCheckedOpacity = false;
866             mCheckedStateful = false;
867         }
868 
getCapacity()869         final int getCapacity() {
870             return mDrawables.length;
871         }
872 
createAllFutures()873         private void createAllFutures() {
874             if (mDrawableFutures != null) {
875                 final int futureCount = mDrawableFutures.size();
876                 for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) {
877                     final int index = mDrawableFutures.keyAt(keyIndex);
878                     final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
879                     mDrawables[index] = prepareDrawable(cs.newDrawable(mSourceRes));
880                 }
881 
882                 mDrawableFutures = null;
883             }
884         }
885 
prepareDrawable(Drawable child)886         private Drawable prepareDrawable(Drawable child) {
887             child.setLayoutDirection(mLayoutDirection);
888             child = child.mutate();
889             child.setCallback(mOwner);
890             return child;
891         }
892 
getChildCount()893         public final int getChildCount() {
894             return mNumChildren;
895         }
896 
897         /*
898          * @deprecated Use {@link #getChild} instead.
899          */
getChildren()900         public final Drawable[] getChildren() {
901             // Create all futures for backwards compatibility.
902             createAllFutures();
903 
904             return mDrawables;
905         }
906 
getChild(int index)907         public final Drawable getChild(int index) {
908             final Drawable result = mDrawables[index];
909             if (result != null) {
910                 return result;
911             }
912 
913             // Prepare future drawable if necessary.
914             if (mDrawableFutures != null) {
915                 final int keyIndex = mDrawableFutures.indexOfKey(index);
916                 if (keyIndex >= 0) {
917                     final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
918                     final Drawable prepared = prepareDrawable(cs.newDrawable(mSourceRes));
919                     mDrawables[index] = prepared;
920                     mDrawableFutures.removeAt(keyIndex);
921                     if (mDrawableFutures.size() == 0) {
922                         mDrawableFutures = null;
923                     }
924                     return prepared;
925                 }
926             }
927 
928             return null;
929         }
930 
setLayoutDirection(int layoutDirection, int currentIndex)931         final boolean setLayoutDirection(int layoutDirection, int currentIndex) {
932             boolean changed = false;
933 
934             // No need to call createAllFutures, since future drawables will
935             // change layout direction when they are prepared.
936             final int N = mNumChildren;
937             final Drawable[] drawables = mDrawables;
938             for (int i = 0; i < N; i++) {
939                 if (drawables[i] != null) {
940                     final boolean childChanged = drawables[i].setLayoutDirection(layoutDirection);
941                     if (i == currentIndex) {
942                         changed = childChanged;
943                     }
944                 }
945             }
946 
947             mLayoutDirection = layoutDirection;
948 
949             return changed;
950         }
951 
952         /**
953          * Updates the source density based on the resources used to inflate
954          * density-dependent values.
955          *
956          * @param res the resources used to inflate density-dependent values
957          */
updateDensity(Resources res)958         final void updateDensity(Resources res) {
959             if (res != null) {
960                 mSourceRes = res;
961 
962                 // The density may have changed since the last update (if any). Any
963                 // dimension-type attributes will need their default values scaled.
964                 final int targetDensity = Drawable.resolveDensity(res, mDensity);
965                 final int sourceDensity = mDensity;
966                 mDensity = targetDensity;
967 
968                 if (sourceDensity != targetDensity) {
969                     mCheckedConstantSize = false;
970                     mCheckedPadding = false;
971                 }
972             }
973         }
974 
applyTheme(Theme theme)975         final void applyTheme(Theme theme) {
976             if (theme != null) {
977                 createAllFutures();
978 
979                 final int N = mNumChildren;
980                 final Drawable[] drawables = mDrawables;
981                 for (int i = 0; i < N; i++) {
982                     if (drawables[i] != null && drawables[i].canApplyTheme()) {
983                         drawables[i].applyTheme(theme);
984 
985                         // Update cached mask of child changing configurations.
986                         mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations();
987                     }
988                 }
989 
990                 updateDensity(theme.getResources());
991             }
992         }
993 
994         @Override
canApplyTheme()995         public boolean canApplyTheme() {
996             final int N = mNumChildren;
997             final Drawable[] drawables = mDrawables;
998             for (int i = 0; i < N; i++) {
999                 final Drawable d = drawables[i];
1000                 if (d != null) {
1001                     if (d.canApplyTheme()) {
1002                         return true;
1003                     }
1004                 } else {
1005                     final ConstantState future = mDrawableFutures.get(i);
1006                     if (future != null && future.canApplyTheme()) {
1007                         return true;
1008                     }
1009                 }
1010             }
1011 
1012             return false;
1013         }
1014 
mutate()1015         private void mutate() {
1016             // No need to call createAllFutures, since future drawables will
1017             // mutate when they are prepared.
1018             final int N = mNumChildren;
1019             final Drawable[] drawables = mDrawables;
1020             for (int i = 0; i < N; i++) {
1021                 if (drawables[i] != null) {
1022                     drawables[i].mutate();
1023                 }
1024             }
1025 
1026             mMutated = true;
1027         }
1028 
clearMutated()1029         final void clearMutated() {
1030             final int N = mNumChildren;
1031             final Drawable[] drawables = mDrawables;
1032             for (int i = 0; i < N; i++) {
1033                 if (drawables[i] != null) {
1034                     drawables[i].clearMutated();
1035                 }
1036             }
1037 
1038             mMutated = false;
1039         }
1040 
1041         /**
1042          * A boolean value indicating whether to use the maximum padding value
1043          * of all frames in the set (false), or to use the padding value of the
1044          * frame being shown (true). Default value is false.
1045          */
setVariablePadding(boolean variable)1046         public final void setVariablePadding(boolean variable) {
1047             mVariablePadding = variable;
1048         }
1049 
getConstantPadding()1050         public final Rect getConstantPadding() {
1051             if (mVariablePadding) {
1052                 return null;
1053             }
1054 
1055             if ((mConstantPadding != null) || mCheckedPadding) {
1056                 return mConstantPadding;
1057             }
1058 
1059             createAllFutures();
1060 
1061             Rect r = null;
1062             final Rect t = new Rect();
1063             final int N = mNumChildren;
1064             final Drawable[] drawables = mDrawables;
1065             for (int i = 0; i < N; i++) {
1066                 if (drawables[i].getPadding(t)) {
1067                     if (r == null) r = new Rect(0, 0, 0, 0);
1068                     if (t.left > r.left) r.left = t.left;
1069                     if (t.top > r.top) r.top = t.top;
1070                     if (t.right > r.right) r.right = t.right;
1071                     if (t.bottom > r.bottom) r.bottom = t.bottom;
1072                 }
1073             }
1074 
1075             mCheckedPadding = true;
1076             return (mConstantPadding = r);
1077         }
1078 
setConstantSize(boolean constant)1079         public final void setConstantSize(boolean constant) {
1080             mConstantSize = constant;
1081         }
1082 
isConstantSize()1083         public final boolean isConstantSize() {
1084             return mConstantSize;
1085         }
1086 
getConstantWidth()1087         public final int getConstantWidth() {
1088             if (!mCheckedConstantSize) {
1089                 computeConstantSize();
1090             }
1091 
1092             return mConstantWidth;
1093         }
1094 
getConstantHeight()1095         public final int getConstantHeight() {
1096             if (!mCheckedConstantSize) {
1097                 computeConstantSize();
1098             }
1099 
1100             return mConstantHeight;
1101         }
1102 
getConstantMinimumWidth()1103         public final int getConstantMinimumWidth() {
1104             if (!mCheckedConstantSize) {
1105                 computeConstantSize();
1106             }
1107 
1108             return mConstantMinimumWidth;
1109         }
1110 
getConstantMinimumHeight()1111         public final int getConstantMinimumHeight() {
1112             if (!mCheckedConstantSize) {
1113                 computeConstantSize();
1114             }
1115 
1116             return mConstantMinimumHeight;
1117         }
1118 
computeConstantSize()1119         protected void computeConstantSize() {
1120             mCheckedConstantSize = true;
1121 
1122             createAllFutures();
1123 
1124             final int N = mNumChildren;
1125             final Drawable[] drawables = mDrawables;
1126             mConstantWidth = mConstantHeight = -1;
1127             mConstantMinimumWidth = mConstantMinimumHeight = 0;
1128             for (int i = 0; i < N; i++) {
1129                 final Drawable dr = drawables[i];
1130                 int s = dr.getIntrinsicWidth();
1131                 if (s > mConstantWidth) mConstantWidth = s;
1132                 s = dr.getIntrinsicHeight();
1133                 if (s > mConstantHeight) mConstantHeight = s;
1134                 s = dr.getMinimumWidth();
1135                 if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
1136                 s = dr.getMinimumHeight();
1137                 if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
1138             }
1139         }
1140 
setEnterFadeDuration(int duration)1141         public final void setEnterFadeDuration(int duration) {
1142             mEnterFadeDuration = duration;
1143         }
1144 
getEnterFadeDuration()1145         public final int getEnterFadeDuration() {
1146             return mEnterFadeDuration;
1147         }
1148 
setExitFadeDuration(int duration)1149         public final void setExitFadeDuration(int duration) {
1150             mExitFadeDuration = duration;
1151         }
1152 
getExitFadeDuration()1153         public final int getExitFadeDuration() {
1154             return mExitFadeDuration;
1155         }
1156 
getOpacity()1157         public final int getOpacity() {
1158             if (mCheckedOpacity) {
1159                 return mOpacity;
1160             }
1161 
1162             createAllFutures();
1163 
1164             final int N = mNumChildren;
1165             final Drawable[] drawables = mDrawables;
1166             int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
1167             for (int i = 1; i < N; i++) {
1168                 op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
1169             }
1170 
1171             mOpacity = op;
1172             mCheckedOpacity = true;
1173             return op;
1174         }
1175 
isStateful()1176         public final boolean isStateful() {
1177             if (mCheckedStateful) {
1178                 return mStateful;
1179             }
1180 
1181             createAllFutures();
1182 
1183             final int N = mNumChildren;
1184             final Drawable[] drawables = mDrawables;
1185             boolean isStateful = false;
1186             for (int i = 0; i < N; i++) {
1187                 if (drawables[i].isStateful()) {
1188                     isStateful = true;
1189                     break;
1190                 }
1191             }
1192 
1193             mStateful = isStateful;
1194             mCheckedStateful = true;
1195             return isStateful;
1196         }
1197 
growArray(int oldSize, int newSize)1198         public void growArray(int oldSize, int newSize) {
1199             Drawable[] newDrawables = new Drawable[newSize];
1200             System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
1201             mDrawables = newDrawables;
1202         }
1203 
canConstantState()1204         public synchronized boolean canConstantState() {
1205             if (mCheckedConstantState) {
1206                 return mCanConstantState;
1207             }
1208 
1209             createAllFutures();
1210 
1211             mCheckedConstantState = true;
1212 
1213             final int N = mNumChildren;
1214             final Drawable[] drawables = mDrawables;
1215             for (int i = 0; i < N; i++) {
1216                 if (drawables[i].getConstantState() == null) {
1217                     mCanConstantState = false;
1218                     return false;
1219                 }
1220             }
1221 
1222             mCanConstantState = true;
1223             return true;
1224         }
1225 
1226     }
1227 
setConstantState(DrawableContainerState state)1228     protected void setConstantState(DrawableContainerState state) {
1229         mDrawableContainerState = state;
1230 
1231         // The locally cached drawables may have changed.
1232         if (mCurIndex >= 0) {
1233             mCurrDrawable = state.getChild(mCurIndex);
1234             if (mCurrDrawable != null) {
1235                 initializeDrawableForDisplay(mCurrDrawable);
1236             }
1237         }
1238 
1239         // Clear out the last drawable. We don't have enough information to
1240         // propagate local state from the past.
1241         mLastIndex = -1;
1242         mLastDrawable = null;
1243     }
1244 
1245     /**
1246      * Callback that blocks drawable invalidation.
1247      */
1248     private static class BlockInvalidateCallback implements Drawable.Callback {
1249         private Drawable.Callback mCallback;
1250 
wrap(Drawable.Callback callback)1251         public BlockInvalidateCallback wrap(Drawable.Callback callback) {
1252             mCallback = callback;
1253             return this;
1254         }
1255 
unwrap()1256         public Drawable.Callback unwrap() {
1257             final Drawable.Callback callback = mCallback;
1258             mCallback = null;
1259             return callback;
1260         }
1261 
1262         @Override
invalidateDrawable(@onNull Drawable who)1263         public void invalidateDrawable(@NonNull Drawable who) {
1264             // Ignore invalidation.
1265         }
1266 
1267         @Override
scheduleDrawable(@onNull Drawable who, @NonNull Runnable what, long when)1268         public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
1269             if (mCallback != null) {
1270                 mCallback.scheduleDrawable(who, what, when);
1271             }
1272         }
1273 
1274         @Override
unscheduleDrawable(@onNull Drawable who, @NonNull Runnable what)1275         public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
1276             if (mCallback != null) {
1277                 mCallback.unscheduleDrawable(who, what);
1278             }
1279         }
1280     }
1281 }
1282