diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 093ea80950b7e..d7dd3ec92a561 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5018,6 +5018,12 @@ i + + + + + + @@ -5385,6 +5391,7 @@ i + diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java index 4fc5edee94c68..971a3a295ca2d 100644 --- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -50,7 +50,7 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl * Creates a new animated rotating drawable with no wrapped drawable. */ public AnimatedRotateDrawable() { - this(new AnimatedRotateState(null), null); + this(new AnimatedRotateState(null, null), null); } @Override @@ -126,57 +126,21 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedRotateDrawable); - super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible); + + // Inflation will advance the XmlPullParser and AttributeSet. + super.inflate(r, parser, attrs, theme); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); updateLocalState(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { - // If we're not waiting on a theme, verify required attributes. - if (getDrawable() == null && (mState.mThemeAttrs == null - || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) { - throw new XmlPullParserException(a.getPositionDescription() - + ": tag requires a 'drawable' attribute or " - + "child tag defining a drawable"); - } - } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); - final AnimatedRotateState state = mState; - - if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) { - final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX); - state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION; - state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); - } - - if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) { - final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY); - state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION; - state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); - } - - setFramesCount(a.getInt( - R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount)); - setFramesDuration(a.getInt( - R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration)); - - final Drawable dr = a.getDrawable(R.styleable.AnimatedRotateDrawable_drawable); - if (dr != null) { - setDrawable(dr); - } - } - - @Override - public void applyTheme(@Nullable Theme t) { final AnimatedRotateState state = mState; if (state == null) { return; @@ -195,13 +159,49 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl } } - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); - updateLocalState(); } + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (getDrawable() == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + } + + private void updateStateFromTypedArray(@NonNull TypedArray a) { + final AnimatedRotateState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) { + final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX); + state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION; + state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); + } + + if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) { + final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY); + state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION; + state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); + } + + setFramesCount(a.getInt( + R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount)); + setFramesDuration(a.getInt( + R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration)); + } + public void setFramesCount(int framesCount) { mState.mFramesCount = framesCount; mIncrement = 360.0f / mState.mFramesCount; @@ -211,7 +211,15 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl mState.mFrameDuration = framesDuration; } + @Override + DrawableWrapperState mutateConstantState() { + mState = new AnimatedRotateState(mState, null); + return mState; + } + static final class AnimatedRotateState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + boolean mPivotXRel = false; float mPivotX = 0; boolean mPivotYRel = false; @@ -219,8 +227,8 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl int mFrameDuration = 150; int mFramesCount = 12; - public AnimatedRotateState(AnimatedRotateState orig) { - super(orig); + public AnimatedRotateState(AnimatedRotateState orig, Resources res) { + super(orig, res); if (orig != null) { mPivotXRel = orig.mPivotXRel; diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java index 31fccd0880c9b..cdd336dded0ac 100644 --- a/graphics/java/android/graphics/drawable/ClipDrawable.java +++ b/graphics/java/android/graphics/drawable/ClipDrawable.java @@ -21,6 +21,8 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; @@ -59,7 +61,7 @@ public class ClipDrawable extends DrawableWrapper { private ClipState mState; ClipDrawable() { - this(new ClipState(null), null); + this(new ClipState(null, null), null); } /** @@ -72,7 +74,7 @@ public class ClipDrawable extends DrawableWrapper { * {@link #VERTICAL} */ public ClipDrawable(Drawable drawable, int gravity, int orientation) { - this(new ClipState(null), null); + this(new ClipState(null, null), null); mState.mGravity = gravity; mState.mOrientation = orientation; @@ -81,45 +83,23 @@ public class ClipDrawable extends DrawableWrapper { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable); + + // Inflation will advance the XmlPullParser and AttributeSet. super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { - // If we're not waiting on a theme, verify required attributes. - if (getDrawable() == null && (mState.mThemeAttrs == null - || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) { - throw new XmlPullParserException(a.getPositionDescription() - + ": tag requires a 'drawable' attribute or " - + "child tag defining a drawable"); - } - } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); - final ClipState state = mState; - state.mOrientation = a.getInt( - R.styleable.ClipDrawable_clipOrientation, state.mOrientation); - state.mGravity = a.getInt( - R.styleable.ClipDrawable_gravity, state.mGravity); - - final Drawable dr = a.getDrawable(R.styleable.ClipDrawable_drawable); - if (dr != null) { - setDrawable(dr); - } - } - - @Override - public void applyTheme(Theme t) { final ClipState state = mState; if (state == null) { return; @@ -136,10 +116,34 @@ public class ClipDrawable extends DrawableWrapper { a.recycle(); } } + } - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (getDrawable() == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + } + + private void updateStateFromTypedArray(@NonNull TypedArray a) { + final ClipState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + state.mOrientation = a.getInt( + R.styleable.ClipDrawable_clipOrientation, state.mOrientation); + state.mGravity = a.getInt( + R.styleable.ClipDrawable_gravity, state.mGravity); } @Override @@ -200,12 +204,20 @@ public class ClipDrawable extends DrawableWrapper { } } + @Override + DrawableWrapperState mutateConstantState() { + mState = new ClipState(mState, null); + return mState; + } + static final class ClipState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + int mOrientation = HORIZONTAL; int mGravity = Gravity.LEFT; - ClipState(ClipState orig) { - super(orig); + ClipState(ClipState orig, Resources res) { + super(orig, res); if (orig != null) { mOrientation = orig.mOrientation; diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index b95c183642ca2..ff287776e1a38 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -55,6 +55,8 @@ import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Collection; +import com.android.internal.R; + /** * A Drawable is a general abstraction for "something that can be drawn." Most * often you will deal with Drawable as the type of resource retrieved for @@ -791,8 +793,10 @@ public abstract class Drawable { /** * Applies the specified theme to this Drawable and its children. + * + * @param t the theme to apply */ - public void applyTheme(@SuppressWarnings("unused") Theme t) { + public void applyTheme(@NonNull @SuppressWarnings("unused") Theme t) { } public boolean canApplyTheme() { @@ -1177,8 +1181,8 @@ public abstract class Drawable { * * @see #inflate(Resources, XmlPullParser, AttributeSet, Theme) */ - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) - throws XmlPullParserException, IOException { + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs) throws XmlPullParserException, IOException { inflate(r, parser, attrs, null); } @@ -1192,17 +1196,11 @@ public abstract class Drawable { * @throws XmlPullParserException * @throws IOException */ - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { - final TypedArray a; - if (theme != null) { - a = theme.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.Drawable, 0, 0); - } else { - a = r.obtainAttributes(attrs, com.android.internal.R.styleable.Drawable); - } - - inflateWithAttributes(r, parser, a, com.android.internal.R.styleable.Drawable_visible); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.Drawable); + mVisible = a.getBoolean(R.styleable.Drawable_visible, mVisible); a.recycle(); } @@ -1212,8 +1210,8 @@ public abstract class Drawable { * @throws XmlPullParserException * @throws IOException */ - void inflateWithAttributes(Resources r, XmlPullParser parser, TypedArray attrs, int visibleAttr) - throws XmlPullParserException, IOException { + void inflateWithAttributes(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull TypedArray attrs, int visibleAttr) throws XmlPullParserException, IOException { mVisible = attrs.getBoolean(visibleAttr, mVisible); } diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java index 9185e1a064662..c42787000c49c 100644 --- a/graphics/java/android/graphics/drawable/DrawableWrapper.java +++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java @@ -16,6 +16,8 @@ package android.graphics.drawable; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -23,6 +25,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.ColorStateList; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -33,6 +36,7 @@ import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.Rect; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.view.View; import java.io.IOException; @@ -112,7 +116,66 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb return mDrawable; } - void updateStateFromTypedArray(TypedArray a) { + @Override + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + super.inflate(r, parser, attrs, theme); + + final DrawableWrapperState state = mState; + if (state == null) { + return; + } + + // The density may have changed since the last update. This will + // apply scaling to any existing constant state properties. + final int densityDpi = r.getDisplayMetrics().densityDpi; + final int targetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + state.setDensity(targetDensity); + + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.DrawableWrapper); + updateStateFromTypedArray(a); + a.recycle(); + + inflateChildDrawable(r, parser, attrs, theme); + } + + @Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + + // If we load the drawable later as part of updating from the typed + // array, it will already be themed correctly. So, we can theme the + // local drawable first. + if (mDrawable != null && mDrawable.canApplyTheme()) { + mDrawable.applyTheme(t); + } + + final DrawableWrapperState state = mState; + if (state == null) { + return; + } + + final int densityDpi = t.getResources().getDisplayMetrics().densityDpi; + final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + state.setDensity(density); + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes( + state.mThemeAttrs, R.styleable.DrawableWrapper); + updateStateFromTypedArray(a); + a.recycle(); + } + } + + /** + * Updates constant state properties from the provided typed array. + *

+ * Implementing subclasses should call through to the super method first. + * + * @param a the typed array rom which properties should be read + */ + private void updateStateFromTypedArray(@NonNull TypedArray a) { final DrawableWrapperState state = mState; if (state == null) { return; @@ -124,20 +187,8 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb // Extract the theme attributes, if any. state.mThemeAttrs = a.extractThemeAttrs(); - // TODO: Consider using R.styleable.DrawableWrapper_drawable - } - - @Override - public void applyTheme(Resources.Theme t) { - super.applyTheme(t); - - final DrawableWrapperState state = mState; - if (state == null) { - return; - } - - if (mDrawable != null && mDrawable.canApplyTheme()) { - mDrawable.applyTheme(t); + if (a.hasValueOrEmpty(R.styleable.DrawableWrapper_drawable)) { + setDrawable(a.getDrawable(R.styleable.DrawableWrapper_drawable)); } } @@ -371,8 +422,9 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb * child element will take precedence over any other child elements or * explicit drawable attribute. */ - void inflateChildDrawable(Resources r, XmlPullParser parser, AttributeSet attrs, - Resources.Theme theme) throws XmlPullParserException, IOException { + private void inflateChildDrawable(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { // Seek to the first child element. Drawable dr = null; int type; @@ -390,17 +442,61 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb } abstract static class DrawableWrapperState extends Drawable.ConstantState { - int[] mThemeAttrs; + private int[] mThemeAttrs; + int mChangingConfigurations; + int mDensity = DisplayMetrics.DENSITY_DEFAULT; Drawable.ConstantState mDrawableState; - DrawableWrapperState(DrawableWrapperState orig) { + DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) { if (orig != null) { mThemeAttrs = orig.mThemeAttrs; mChangingConfigurations = orig.mChangingConfigurations; mDrawableState = orig.mDrawableState; } + + final int density; + if (res != null) { + density = res.getDisplayMetrics().densityDpi; + } else if (orig != null) { + density = orig.mDensity; + } else { + density = 0; + } + + mDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; + } + + /** + * Sets the constant state density. + *

+ * If the density has been previously set, dispatches the change to + * subclasses so that density-dependent properties may be scaled as + * necessary. + * + * @param targetDensity the new constant state density + */ + public final void setDensity(int targetDensity) { + if (mDensity != targetDensity) { + final int sourceDensity = mDensity; + mDensity = targetDensity; + + onDensityChanged(sourceDensity, targetDensity); + } + } + + /** + * Called when the constant state density changes. + *

+ * Subclasses with density-dependent constant state properties should + * override this method and scale their properties as necessary. + * + * @param sourceDensity the previous constant state density + * @param targetDensity the new constant state density + */ + void onDensityChanged(int sourceDensity, int targetDensity) { + // Stub method. } @Override @@ -425,7 +521,7 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb } @Override - public abstract Drawable newDrawable(Resources res); + public abstract Drawable newDrawable(@Nullable Resources res); @Override public int getChangingConfigurations() { diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index e1ebdbb65a266..927b9c9d9f6c8 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -22,14 +22,17 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.PixelFormat; import android.graphics.Rect; import android.util.AttributeSet; +import android.util.DisplayMetrics; import java.io.IOException; @@ -58,7 +61,7 @@ public class InsetDrawable extends DrawableWrapper { * No-arg constructor used by drawable inflation. */ InsetDrawable() { - this(new InsetState(null), null); + this(new InsetState(null, null), null); } /** @@ -67,7 +70,7 @@ public class InsetDrawable extends DrawableWrapper { * @param drawable The drawable to inset. * @param inset Inset in pixels around the drawable. */ - public InsetDrawable(Drawable drawable, int inset) { + public InsetDrawable(@Nullable Drawable drawable, int inset) { this(drawable, inset, inset, inset, inset); } @@ -80,9 +83,9 @@ public class InsetDrawable extends DrawableWrapper { * @param insetRight Right inset in pixels. * @param insetBottom Bottom inset in pixels. */ - public InsetDrawable(Drawable drawable, int insetLeft, int insetTop,int insetRight, - int insetBottom) { - this(new InsetState(null), null); + public InsetDrawable(@Nullable Drawable drawable, int insetLeft, int insetTop, + int insetRight, int insetBottom) { + this(new InsetState(null, null), null); mState.mInsetLeft = insetLeft; mState.mInsetTop = insetTop; @@ -93,69 +96,23 @@ public class InsetDrawable extends DrawableWrapper { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable); + + // Inflation will advance the XmlPullParser and AttributeSet. super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { - // If we're not waiting on a theme, verify required attributes. - if (getDrawable() == null && (mState.mThemeAttrs == null - || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { - throw new XmlPullParserException(a.getPositionDescription() - + ": tag requires a 'drawable' attribute or " - + "child tag defining a drawable"); - } - } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); - final InsetState state = mState; - final int N = a.getIndexCount(); - for (int i = 0; i < N; i++) { - final int attr = a.getIndex(i); - switch (attr) { - case R.styleable.InsetDrawable_drawable: - final Drawable dr = a.getDrawable(attr); - if (dr != null) { - setDrawable(dr); - } - break; - case R.styleable.InsetDrawable_inset: - final int inset = a.getDimensionPixelOffset(attr, Integer.MIN_VALUE); - if (inset != Integer.MIN_VALUE) { - state.mInsetLeft = inset; - state.mInsetTop = inset; - state.mInsetRight = inset; - state.mInsetBottom = inset; - } - break; - case R.styleable.InsetDrawable_insetLeft: - state.mInsetLeft = a.getDimensionPixelOffset(attr, state.mInsetLeft); - break; - case R.styleable.InsetDrawable_insetTop: - state.mInsetTop = a.getDimensionPixelOffset(attr, state.mInsetTop); - break; - case R.styleable.InsetDrawable_insetRight: - state.mInsetRight = a.getDimensionPixelOffset(attr, state.mInsetRight); - break; - case R.styleable.InsetDrawable_insetBottom: - state.mInsetBottom = a.getDimensionPixelOffset(attr, state.mInsetBottom); - break; - } - } - } - - @Override - public void applyTheme(Theme t) { final InsetState state = mState; if (state == null) { return; @@ -172,10 +129,47 @@ public class InsetDrawable extends DrawableWrapper { a.recycle(); } } + } - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (getDrawable() == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + } + + private void updateStateFromTypedArray(@NonNull TypedArray a) { + final InsetState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + // Inset attribute may be overridden by more specific attributes. + if (a.hasValue(R.styleable.InsetDrawable_inset)) { + final int inset = a.getDimensionPixelOffset(R.styleable.InsetDrawable_inset, 0); + state.mInsetLeft = inset; + state.mInsetTop = inset; + state.mInsetRight = inset; + state.mInsetBottom = inset; + } + + state.mInsetLeft = a.getDimensionPixelOffset( + R.styleable.InsetDrawable_insetLeft, state.mInsetLeft); + state.mInsetRight = a.getDimensionPixelOffset( + R.styleable.InsetDrawable_insetRight, state.mInsetRight); + state.mInsetTop = a.getDimensionPixelOffset( + R.styleable.InsetDrawable_insetTop, state.mInsetTop); + state.mInsetBottom = a.getDimensionPixelOffset( + R.styleable.InsetDrawable_insetBottom, state.mInsetBottom); } @Override @@ -243,30 +237,72 @@ public class InsetDrawable extends DrawableWrapper { @Override DrawableWrapperState mutateConstantState() { - mState = new InsetState(mState); + mState = new InsetState(mState, null); return mState; } static final class InsetState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + int mInsetLeft = 0; int mInsetTop = 0; int mInsetRight = 0; int mInsetBottom = 0; - InsetState(InsetState orig) { - super(orig); + InsetState(@Nullable InsetState orig, @Nullable Resources res) { + super(orig, res); if (orig != null) { mInsetLeft = orig.mInsetLeft; mInsetTop = orig.mInsetTop; mInsetRight = orig.mInsetRight; mInsetBottom = orig.mInsetBottom; + + if (orig.mDensity != mDensity) { + applyDensityScaling(orig.mDensity, mDensity); + } } } @Override - public Drawable newDrawable(Resources res) { - return new InsetDrawable(this, res); + void onDensityChanged(int sourceDensity, int targetDensity) { + super.onDensityChanged(sourceDensity, targetDensity); + + applyDensityScaling(sourceDensity, targetDensity); + } + + /** + * Called when the constant state density changes to scale + * density-dependent properties specific to insets. + * + * @param sourceDensity the previous constant state density + * @param targetDensity the new constant state density + */ + private void applyDensityScaling(int sourceDensity, int targetDensity) { + mInsetLeft = Bitmap.scaleFromDensity(mInsetLeft, sourceDensity, targetDensity); + mInsetTop = Bitmap.scaleFromDensity(mInsetTop, sourceDensity, targetDensity); + mInsetRight = Bitmap.scaleFromDensity(mInsetRight, sourceDensity, targetDensity); + mInsetBottom = Bitmap.scaleFromDensity(mInsetBottom, sourceDensity, targetDensity); + } + + @Override + public Drawable newDrawable(@Nullable Resources res) { + // If this drawable is being created for a different density, + // just create a new constant state and call it a day. + final InsetState state; + if (res != null) { + final int densityDpi = res.getDisplayMetrics().densityDpi; + final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + if (density != mDensity) { + state = new InsetState(this, res); + } else { + state = this; + } + } else { + state = this; + } + + return new InsetDrawable(state, res); } } @@ -274,7 +310,7 @@ public class InsetDrawable extends DrawableWrapper { * The one constructor to rule them all. This is called by all public * constructors to set the state and initialize local properties. */ - private InsetDrawable(InsetState state, Resources res) { + private InsetDrawable(@NonNull InsetState state, @Nullable Resources res) { super(state, res); mState = state; diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index 036a078eb00d5..1531ba2380fb8 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -21,6 +21,8 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Rect; import android.content.res.Resources; @@ -58,22 +60,46 @@ public class RotateDrawable extends DrawableWrapper { * Creates a new rotating drawable with no wrapped drawable. */ public RotateDrawable() { - this(new RotateState(null), null); + this(new RotateState(null, null), null); } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable); - super.inflateWithAttributes(r, parser, a, R.styleable.RotateDrawable_visible); + + // Inflation will advance the XmlPullParser and AttributeSet. + super.inflate(r, parser, attrs, theme); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + @Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + + final RotateState state = mState; + if (state == null) { + return; + } + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } + } + } + + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { // If we're not waiting on a theme, verify required attributes. if (getDrawable() == null && (mState.mThemeAttrs == null || mState.mThemeAttrs[R.styleable.RotateDrawable_drawable] == 0)) { @@ -83,11 +109,14 @@ public class RotateDrawable extends DrawableWrapper { } } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); - + private void updateStateFromTypedArray(@NonNull TypedArray a) { final RotateState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); // Extract the theme attributes, if any. state.mThemeAttrs = a.extractThemeAttrs(); @@ -109,35 +138,6 @@ public class RotateDrawable extends DrawableWrapper { state.mToDegrees = a.getFloat( R.styleable.RotateDrawable_toDegrees, state.mToDegrees); state.mCurrentDegrees = state.mFromDegrees; - - final Drawable dr = a.getDrawable(R.styleable.RotateDrawable_drawable); - if (dr != null) { - setDrawable(dr); - } - } - - @Override - public void applyTheme(Theme t) { - final RotateState state = mState; - if (state == null) { - return; - } - - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable); - try { - updateStateFromTypedArray(a); - verifyRequiredAttributes(a); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } finally { - a.recycle(); - } - } - - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); } @Override @@ -316,11 +316,13 @@ public class RotateDrawable extends DrawableWrapper { @Override DrawableWrapperState mutateConstantState() { - mState = new RotateState(mState); + mState = new RotateState(mState, null); return mState; } static final class RotateState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + boolean mPivotXRel = true; float mPivotX = 0.5f; boolean mPivotYRel = true; @@ -329,8 +331,8 @@ public class RotateDrawable extends DrawableWrapper { float mToDegrees = 360.0f; float mCurrentDegrees = 0.0f; - RotateState(RotateState orig) { - super(orig); + RotateState(RotateState orig, Resources res) { + super(orig, res); if (orig != null) { mPivotXRel = orig.mPivotXRel; diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java index f9206b7d3c26a..f87c19a40538f 100644 --- a/graphics/java/android/graphics/drawable/ScaleDrawable.java +++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java @@ -21,6 +21,8 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; @@ -67,7 +69,7 @@ public class ScaleDrawable extends DrawableWrapper { private ScaleState mState; ScaleDrawable() { - this(new ScaleState(null), null); + this(new ScaleState(null, null), null); } /** @@ -83,7 +85,7 @@ public class ScaleDrawable extends DrawableWrapper { * is at the maximum value, or -1 to not scale height */ public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) { - this(new ScaleState(null), null); + this(new ScaleState(null, null), null); mState.mGravity = gravity; mState.mScaleWidth = scaleWidth; @@ -93,20 +95,46 @@ public class ScaleDrawable extends DrawableWrapper { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable); + + // Inflation will advance the XmlPullParser and AttributeSet. super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); updateLocalState(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + @Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + + final ScaleState state = mState; + if (state == null) { + return; + } + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } + } + + updateLocalState(); + } + + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { // If we're not waiting on a theme, verify required attributes. if (getDrawable() == null && (mState.mThemeAttrs == null || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) { @@ -116,11 +144,18 @@ public class ScaleDrawable extends DrawableWrapper { } } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); - + private void updateStateFromTypedArray(@NonNull TypedArray a) { final ScaleState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + state.mScaleWidth = getPercent(a, R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth); state.mScaleHeight = getPercent(a, @@ -131,11 +166,6 @@ public class ScaleDrawable extends DrawableWrapper { R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin); state.mInitialLevel = a.getInt( R.styleable.ScaleDrawable_level, state.mInitialLevel); - - final Drawable dr = a.getDrawable(R.styleable.ScaleDrawable_drawable); - if (dr != null) { - setDrawable(dr); - } } private static float getPercent(TypedArray a, int index, float defaultValue) { @@ -156,33 +186,6 @@ public class ScaleDrawable extends DrawableWrapper { return defaultValue; } - @Override - public void applyTheme(Theme t) { - final ScaleState state = mState; - if (state == null) { - return; - } - - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes( - state.mThemeAttrs, R.styleable.ScaleDrawable); - try { - updateStateFromTypedArray(a); - verifyRequiredAttributes(a); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } finally { - a.recycle(); - } - } - - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); - - updateLocalState(); - } - @Override public void draw(Canvas canvas) { final Drawable d = getDrawable(); @@ -243,7 +246,7 @@ public class ScaleDrawable extends DrawableWrapper { @Override DrawableWrapperState mutateConstantState() { - mState = new ScaleState(mState); + mState = new ScaleState(mState, null); return mState; } @@ -251,14 +254,16 @@ public class ScaleDrawable extends DrawableWrapper { /** Constant used to disable scaling for a particular dimension. */ private static final float DO_NOT_SCALE = -1.0f; + private int[] mThemeAttrs; + float mScaleWidth = DO_NOT_SCALE; float mScaleHeight = DO_NOT_SCALE; int mGravity = Gravity.LEFT; boolean mUseIntrinsicSizeAsMin = false; int mInitialLevel = 0; - ScaleState(ScaleState orig) { - super(orig); + ScaleState(ScaleState orig, Resources res) { + super(orig, res); if (orig != null) { mScaleWidth = orig.mScaleWidth;