• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 com.android.internal.R;
20 
21 import org.xmlpull.v1.XmlPullParser;
22 import org.xmlpull.v1.XmlPullParserException;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.content.pm.ActivityInfo.Config;
27 import android.content.res.ColorStateList;
28 import android.content.res.Resources;
29 import android.content.res.Resources.Theme;
30 import android.content.res.TypedArray;
31 import android.graphics.Canvas;
32 import android.graphics.ColorFilter;
33 import android.graphics.Insets;
34 import android.graphics.Outline;
35 import android.graphics.PixelFormat;
36 import android.graphics.PorterDuff;
37 import android.graphics.Rect;
38 import android.util.AttributeSet;
39 import android.util.DisplayMetrics;
40 import android.view.View;
41 
42 import java.io.IOException;
43 
44 /**
45  * Drawable container with only one child element.
46  */
47 public abstract class DrawableWrapper extends Drawable implements Drawable.Callback {
48     private DrawableWrapperState mState;
49     private Drawable mDrawable;
50     private boolean mMutated;
51 
DrawableWrapper(DrawableWrapperState state, Resources res)52     DrawableWrapper(DrawableWrapperState state, Resources res) {
53         mState = state;
54 
55         updateLocalState(res);
56     }
57 
58     /**
59      * Creates a new wrapper around the specified drawable.
60      *
61      * @param dr the drawable to wrap
62      */
DrawableWrapper(@ullable Drawable dr)63     public DrawableWrapper(@Nullable Drawable dr) {
64         mState = null;
65         mDrawable = dr;
66     }
67 
68     /**
69      * Initializes local dynamic properties from state. This should be called
70      * after significant state changes, e.g. from the One True Constructor and
71      * after inflating or applying a theme.
72      */
updateLocalState(Resources res)73     private void updateLocalState(Resources res) {
74         if (mState != null && mState.mDrawableState != null) {
75             final Drawable dr = mState.mDrawableState.newDrawable(res);
76             setDrawable(dr);
77         }
78     }
79 
80     /**
81      * Sets the wrapped drawable.
82      *
83      * @param dr the wrapped drawable
84      */
setDrawable(@ullable Drawable dr)85     public void setDrawable(@Nullable Drawable dr) {
86         if (mDrawable != null) {
87             mDrawable.setCallback(null);
88         }
89 
90         mDrawable = dr;
91 
92         if (dr != null) {
93             dr.setCallback(this);
94 
95             // Only call setters for data that's stored in the base Drawable.
96             dr.setVisible(isVisible(), true);
97             dr.setState(getState());
98             dr.setLevel(getLevel());
99             dr.setBounds(getBounds());
100             dr.setLayoutDirection(getLayoutDirection());
101 
102             if (mState != null) {
103                 mState.mDrawableState = dr.getConstantState();
104             }
105         }
106 
107         invalidateSelf();
108     }
109 
110     /**
111      * @return the wrapped drawable
112      */
113     @Nullable
getDrawable()114     public Drawable getDrawable() {
115         return mDrawable;
116     }
117 
118     @Override
inflate(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)119     public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
120             @NonNull AttributeSet attrs, @Nullable Theme theme)
121             throws XmlPullParserException, IOException {
122         super.inflate(r, parser, attrs, theme);
123 
124         final DrawableWrapperState state = mState;
125         if (state == null) {
126             return;
127         }
128 
129         // The density may have changed since the last update. This will
130         // apply scaling to any existing constant state properties.
131         final int densityDpi = r.getDisplayMetrics().densityDpi;
132         final int targetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
133         state.setDensity(targetDensity);
134         state.mSrcDensityOverride = mSrcDensityOverride;
135 
136         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.DrawableWrapper);
137         updateStateFromTypedArray(a);
138         a.recycle();
139 
140         inflateChildDrawable(r, parser, attrs, theme);
141     }
142 
143     @Override
applyTheme(@onNull Theme t)144     public void applyTheme(@NonNull Theme t) {
145         super.applyTheme(t);
146 
147         // If we load the drawable later as part of updating from the typed
148         // array, it will already be themed correctly. So, we can theme the
149         // local drawable first.
150         if (mDrawable != null && mDrawable.canApplyTheme()) {
151             mDrawable.applyTheme(t);
152         }
153 
154         final DrawableWrapperState state = mState;
155         if (state == null) {
156             return;
157         }
158 
159         final int densityDpi = t.getResources().getDisplayMetrics().densityDpi;
160         final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
161         state.setDensity(density);
162 
163         if (state.mThemeAttrs != null) {
164             final TypedArray a = t.resolveAttributes(
165                     state.mThemeAttrs, R.styleable.DrawableWrapper);
166             updateStateFromTypedArray(a);
167             a.recycle();
168         }
169     }
170 
171     /**
172      * Updates constant state properties from the provided typed array.
173      * <p>
174      * Implementing subclasses should call through to the super method first.
175      *
176      * @param a the typed array rom which properties should be read
177      */
updateStateFromTypedArray(@onNull TypedArray a)178     private void updateStateFromTypedArray(@NonNull TypedArray a) {
179         final DrawableWrapperState state = mState;
180         if (state == null) {
181             return;
182         }
183 
184         // Account for any configuration changes.
185         state.mChangingConfigurations |= a.getChangingConfigurations();
186 
187         // Extract the theme attributes, if any.
188         state.mThemeAttrs = a.extractThemeAttrs();
189 
190         if (a.hasValueOrEmpty(R.styleable.DrawableWrapper_drawable)) {
191             setDrawable(a.getDrawable(R.styleable.DrawableWrapper_drawable));
192         }
193     }
194 
195     @Override
canApplyTheme()196     public boolean canApplyTheme() {
197         return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
198     }
199 
200     @Override
invalidateDrawable(@onNull Drawable who)201     public void invalidateDrawable(@NonNull Drawable who) {
202         final Callback callback = getCallback();
203         if (callback != null) {
204             callback.invalidateDrawable(this);
205         }
206     }
207 
208     @Override
scheduleDrawable(@onNull Drawable who, @NonNull Runnable what, long when)209     public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
210         final Callback callback = getCallback();
211         if (callback != null) {
212             callback.scheduleDrawable(this, what, when);
213         }
214     }
215 
216     @Override
unscheduleDrawable(@onNull Drawable who, @NonNull Runnable what)217     public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
218         final Callback callback = getCallback();
219         if (callback != null) {
220             callback.unscheduleDrawable(this, what);
221         }
222     }
223 
224     @Override
draw(@onNull Canvas canvas)225     public void draw(@NonNull Canvas canvas) {
226         if (mDrawable != null) {
227             mDrawable.draw(canvas);
228         }
229     }
230 
231     @Override
getChangingConfigurations()232     public @Config int getChangingConfigurations() {
233         return super.getChangingConfigurations()
234                 | (mState != null ? mState.getChangingConfigurations() : 0)
235                 | mDrawable.getChangingConfigurations();
236     }
237 
238     @Override
getPadding(@onNull Rect padding)239     public boolean getPadding(@NonNull Rect padding) {
240         return mDrawable != null && mDrawable.getPadding(padding);
241     }
242 
243     /** @hide */
244     @Override
getOpticalInsets()245     public Insets getOpticalInsets() {
246         return mDrawable != null ? mDrawable.getOpticalInsets() : Insets.NONE;
247     }
248 
249     @Override
setHotspot(float x, float y)250     public void setHotspot(float x, float y) {
251         if (mDrawable != null) {
252             mDrawable.setHotspot(x, y);
253         }
254     }
255 
256     @Override
setHotspotBounds(int left, int top, int right, int bottom)257     public void setHotspotBounds(int left, int top, int right, int bottom) {
258         if (mDrawable != null) {
259             mDrawable.setHotspotBounds(left, top, right, bottom);
260         }
261     }
262 
263     @Override
getHotspotBounds(@onNull Rect outRect)264     public void getHotspotBounds(@NonNull Rect outRect) {
265         if (mDrawable != null) {
266             mDrawable.getHotspotBounds(outRect);
267         } else {
268             outRect.set(getBounds());
269         }
270     }
271 
272     @Override
setVisible(boolean visible, boolean restart)273     public boolean setVisible(boolean visible, boolean restart) {
274         final boolean superChanged = super.setVisible(visible, restart);
275         final boolean changed = mDrawable != null && mDrawable.setVisible(visible, restart);
276         return superChanged | changed;
277     }
278 
279     @Override
setAlpha(int alpha)280     public void setAlpha(int alpha) {
281         if (mDrawable != null) {
282             mDrawable.setAlpha(alpha);
283         }
284     }
285 
286     @Override
getAlpha()287     public int getAlpha() {
288         return mDrawable != null ? mDrawable.getAlpha() : 255;
289     }
290 
291     @Override
setColorFilter(@ullable ColorFilter colorFilter)292     public void setColorFilter(@Nullable ColorFilter colorFilter) {
293         if (mDrawable != null) {
294             mDrawable.setColorFilter(colorFilter);
295         }
296     }
297 
298     @Override
getColorFilter()299     public ColorFilter getColorFilter() {
300         final Drawable drawable = getDrawable();
301         if (drawable != null) {
302             return drawable.getColorFilter();
303         }
304         return super.getColorFilter();
305     }
306 
307     @Override
setTintList(@ullable ColorStateList tint)308     public void setTintList(@Nullable ColorStateList tint) {
309         if (mDrawable != null) {
310             mDrawable.setTintList(tint);
311         }
312     }
313 
314     @Override
setTintMode(@ullable PorterDuff.Mode tintMode)315     public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
316         if (mDrawable != null) {
317             mDrawable.setTintMode(tintMode);
318         }
319     }
320 
321     @Override
onLayoutDirectionChanged(@iew.ResolvedLayoutDir int layoutDirection)322     public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
323         return mDrawable != null && mDrawable.setLayoutDirection(layoutDirection);
324     }
325 
326     @Override
getOpacity()327     public int getOpacity() {
328         return mDrawable != null ? mDrawable.getOpacity() : PixelFormat.TRANSPARENT;
329     }
330 
331     @Override
isStateful()332     public boolean isStateful() {
333         return mDrawable != null && mDrawable.isStateful();
334     }
335 
336     /** @hide */
337     @Override
hasFocusStateSpecified()338     public boolean hasFocusStateSpecified() {
339         return mDrawable != null && mDrawable.hasFocusStateSpecified();
340     }
341 
342     @Override
onStateChange(int[] state)343     protected boolean onStateChange(int[] state) {
344         if (mDrawable != null && mDrawable.isStateful()) {
345             final boolean changed = mDrawable.setState(state);
346             if (changed) {
347                 onBoundsChange(getBounds());
348             }
349             return changed;
350         }
351         return false;
352     }
353 
354     @Override
onLevelChange(int level)355     protected boolean onLevelChange(int level) {
356         return mDrawable != null && mDrawable.setLevel(level);
357     }
358 
359     @Override
onBoundsChange(@onNull Rect bounds)360     protected void onBoundsChange(@NonNull Rect bounds) {
361         if (mDrawable != null) {
362             mDrawable.setBounds(bounds);
363         }
364     }
365 
366     @Override
getIntrinsicWidth()367     public int getIntrinsicWidth() {
368         return mDrawable != null ? mDrawable.getIntrinsicWidth() : -1;
369     }
370 
371     @Override
getIntrinsicHeight()372     public int getIntrinsicHeight() {
373         return mDrawable != null ? mDrawable.getIntrinsicHeight() : -1;
374     }
375 
376     @Override
getOutline(@onNull Outline outline)377     public void getOutline(@NonNull Outline outline) {
378         if (mDrawable != null) {
379             mDrawable.getOutline(outline);
380         } else {
381             super.getOutline(outline);
382         }
383     }
384 
385     @Override
386     @Nullable
getConstantState()387     public ConstantState getConstantState() {
388         if (mState != null && mState.canConstantState()) {
389             mState.mChangingConfigurations = getChangingConfigurations();
390             return mState;
391         }
392         return null;
393     }
394 
395     @Override
396     @NonNull
mutate()397     public Drawable mutate() {
398         if (!mMutated && super.mutate() == this) {
399             mState = mutateConstantState();
400             if (mDrawable != null) {
401                 mDrawable.mutate();
402             }
403             if (mState != null) {
404                 mState.mDrawableState = mDrawable != null ? mDrawable.getConstantState() : null;
405             }
406             mMutated = true;
407         }
408         return this;
409     }
410 
411     /**
412      * Mutates the constant state and returns the new state. Responsible for
413      * updating any local copy.
414      * <p>
415      * This method should never call the super implementation; it should always
416      * mutate and return its own constant state.
417      *
418      * @return the new state
419      */
mutateConstantState()420     DrawableWrapperState mutateConstantState() {
421         return mState;
422     }
423 
424     /**
425      * @hide Only used by the framework for pre-loading resources.
426      */
clearMutated()427     public void clearMutated() {
428         super.clearMutated();
429         if (mDrawable != null) {
430             mDrawable.clearMutated();
431         }
432         mMutated = false;
433     }
434 
435     /**
436      * Called during inflation to inflate the child element. The last valid
437      * child element will take precedence over any other child elements or
438      * explicit drawable attribute.
439      */
inflateChildDrawable(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)440     private void inflateChildDrawable(@NonNull Resources r, @NonNull XmlPullParser parser,
441             @NonNull AttributeSet attrs, @Nullable Theme theme)
442             throws XmlPullParserException, IOException {
443         // Seek to the first child element.
444         Drawable dr = null;
445         int type;
446         final int outerDepth = parser.getDepth();
447         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
448                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
449             if (type == XmlPullParser.START_TAG) {
450                 dr = Drawable.createFromXmlInnerForDensity(r, parser, attrs,
451                         mState.mSrcDensityOverride, theme);
452             }
453         }
454 
455         if (dr != null) {
456             setDrawable(dr);
457         }
458     }
459 
460     abstract static class DrawableWrapperState extends Drawable.ConstantState {
461         private int[] mThemeAttrs;
462 
463         @Config int mChangingConfigurations;
464         int mDensity = DisplayMetrics.DENSITY_DEFAULT;
465 
466         /**
467          * The density to use when looking up resources from
468          * {@link Resources#getDrawableForDensity(int, int, Theme)}.
469          * A value of 0 means there is no override and the system density will be used.
470          * @hide
471          */
472         int mSrcDensityOverride = 0;
473 
474         Drawable.ConstantState mDrawableState;
475 
DrawableWrapperState(@ullable DrawableWrapperState orig, @Nullable Resources res)476         DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) {
477             if (orig != null) {
478                 mThemeAttrs = orig.mThemeAttrs;
479                 mChangingConfigurations = orig.mChangingConfigurations;
480                 mDrawableState = orig.mDrawableState;
481                 mSrcDensityOverride = orig.mSrcDensityOverride;
482             }
483 
484             final int density;
485             if (res != null) {
486                 density = res.getDisplayMetrics().densityDpi;
487             } else if (orig != null) {
488                 density = orig.mDensity;
489             } else {
490                 density = 0;
491             }
492 
493             mDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
494         }
495 
496         /**
497          * Sets the constant state density.
498          * <p>
499          * If the density has been previously set, dispatches the change to
500          * subclasses so that density-dependent properties may be scaled as
501          * necessary.
502          *
503          * @param targetDensity the new constant state density
504          */
setDensity(int targetDensity)505         public final void setDensity(int targetDensity) {
506             if (mDensity != targetDensity) {
507                 final int sourceDensity = mDensity;
508                 mDensity = targetDensity;
509 
510                 onDensityChanged(sourceDensity, targetDensity);
511             }
512         }
513 
514         /**
515          * Called when the constant state density changes.
516          * <p>
517          * Subclasses with density-dependent constant state properties should
518          * override this method and scale their properties as necessary.
519          *
520          * @param sourceDensity the previous constant state density
521          * @param targetDensity the new constant state density
522          */
onDensityChanged(int sourceDensity, int targetDensity)523         void onDensityChanged(int sourceDensity, int targetDensity) {
524             // Stub method.
525         }
526 
527         @Override
canApplyTheme()528         public boolean canApplyTheme() {
529             return mThemeAttrs != null
530                     || (mDrawableState != null && mDrawableState.canApplyTheme())
531                     || super.canApplyTheme();
532         }
533 
534         @Override
newDrawable()535         public Drawable newDrawable() {
536             return newDrawable(null);
537         }
538 
539         @Override
newDrawable(@ullable Resources res)540         public abstract Drawable newDrawable(@Nullable Resources res);
541 
542         @Override
getChangingConfigurations()543         public @Config int getChangingConfigurations() {
544             return mChangingConfigurations
545                     | (mDrawableState != null ? mDrawableState.getChangingConfigurations() : 0);
546         }
547 
canConstantState()548         public boolean canConstantState() {
549             return mDrawableState != null;
550         }
551     }
552 }
553