diff options
Diffstat (limited to 'graphics/java')
30 files changed, 2298 insertions, 1260 deletions
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 3090ffd..72f6118 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -48,13 +48,8 @@ public final class Bitmap implements Parcelable { /** * Backing buffer for the Bitmap. - * Made public for quick access from drawing methods -- do NOT modify - * from outside this class - * - * @hide */ - @SuppressWarnings("UnusedDeclaration") // native code only - public byte[] mBuffer; + private byte[] mBuffer; @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) // Keep to finalize native resources private final BitmapFinalizer mFinalizer; @@ -309,7 +304,7 @@ public final class Bitmap implements Parcelable { * there are no more references to this bitmap. */ public void recycle() { - if (!mRecycled) { + if (!mRecycled && mFinalizer.mNativeBitmap != 0) { if (nativeRecycle(mNativeBitmap)) { // return value indicates whether native pixel object was actually recycled. // false indicates that it is still in use at the native level and these @@ -1576,7 +1571,7 @@ public final class Bitmap implements Parcelable { } private static class BitmapFinalizer { - private final long mNativeBitmap; + private long mNativeBitmap; // Native memory allocated for the duration of the Bitmap, // if pixel data allocated into native memory, instead of java byte[] @@ -1602,6 +1597,7 @@ public final class Bitmap implements Parcelable { VMRuntime.getRuntime().registerNativeFree(mNativeAllocationByteCount); } nativeDestructor(mNativeBitmap); + mNativeBitmap = 0; } } } diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index f45c0cb..b0580d5 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -353,25 +353,51 @@ public class Canvas { @Retention(RetentionPolicy.SOURCE) public @interface Saveflags {} - /** restore the current matrix when restore() is called */ + /** + * Restore the current matrix when restore() is called. + */ public static final int MATRIX_SAVE_FLAG = 0x01; - /** restore the current clip when restore() is called */ + + /** + * Restore the current clip when restore() is called. + */ public static final int CLIP_SAVE_FLAG = 0x02; - /** the layer needs to per-pixel alpha */ + + /** + * The layer requires a per-pixel alpha channel. + */ public static final int HAS_ALPHA_LAYER_SAVE_FLAG = 0x04; - /** the layer needs to 8-bits per color component */ + + /** + * The layer requires full 8-bit precision for each color channel. + */ public static final int FULL_COLOR_LAYER_SAVE_FLAG = 0x08; - /** clip against the layer's bounds */ + + /** + * Clip drawing to the bounds of the offscreen layer, omit at your own peril. + * <p class="note"><strong>Note:</strong> it is strongly recommended to not + * omit this flag for any call to <code>saveLayer()</code> and + * <code>saveLayerAlpha()</code> variants. Not passing this flag generally + * triggers extremely poor performance with hardware accelerated rendering. + */ public static final int CLIP_TO_LAYER_SAVE_FLAG = 0x10; - /** restore everything when restore() is called */ + + /** + * Restore everything when restore() is called (standard save flags). + * <p class="note"><strong>Note:</strong> for performance reasons, it is + * strongly recommended to pass this - the complete set of flags - to any + * call to <code>saveLayer()</code> and <code>saveLayerAlpha()</code> + * variants. + */ public static final int ALL_SAVE_FLAG = 0x1F; /** - * Saves the current matrix and clip onto a private stack. Subsequent - * calls to translate,scale,rotate,skew,concat or clipRect,clipPath - * will all operate as usual, but when the balancing call to restore() - * is made, those calls will be forgotten, and the settings that existed - * before the save() will be reinstated. + * Saves the current matrix and clip onto a private stack. + * <p> + * Subsequent calls to translate,scale,rotate,skew,concat or clipRect, + * clipPath will all operate as usual, but when the balancing call to + * restore() is made, those calls will be forgotten, and the settings that + * existed before the save() will be reinstated. * * @return The value to pass to restoreToCount() to balance this save() */ @@ -381,10 +407,15 @@ public class Canvas { /** * Based on saveFlags, can save the current matrix and clip onto a private - * stack. Subsequent calls to translate,scale,rotate,skew,concat or - * clipRect,clipPath will all operate as usual, but when the balancing - * call to restore() is made, those calls will be forgotten, and the - * settings that existed before the save() will be reinstated. + * stack. + * <p class="note"><strong>Note:</strong> if possible, use the + * parameter-less save(). It is simpler and faster than individually + * disabling the saving of matrix or clip with this method. + * <p> + * Subsequent calls to translate,scale,rotate,skew,concat or clipRect, + * clipPath will all operate as usual, but when the balancing call to + * restore() is made, those calls will be forgotten, and the settings that + * existed before the save() will be reinstated. * * @param saveFlags flag bits that specify which parts of the Canvas state * to save/restore @@ -395,19 +426,33 @@ public class Canvas { } /** - * This behaves the same as save(), but in addition it allocates an - * offscreen bitmap. All drawing calls are directed there, and only when - * the balancing call to restore() is made is that offscreen transfered to - * the canvas (or the previous layer). Subsequent calls to translate, - * scale, rotate, skew, concat or clipRect, clipPath all operate on this - * copy. When the balancing call to restore() is made, this copy is - * deleted and the previous matrix/clip state is restored. + * This behaves the same as save(), but in addition it allocates and + * redirects drawing to an offscreen bitmap. + * <p class="note"><strong>Note:</strong> this method is very expensive, + * incurring more than double rendering cost for contained content. Avoid + * using this method, especially if the bounds provided are large, or if + * the {@link #CLIP_TO_LAYER_SAVE_FLAG} is omitted from the + * {@code saveFlags} parameter. It is recommended to use a + * {@link android.view.View#LAYER_TYPE_HARDWARE hardware layer} on a View + * to apply an xfermode, color filter, or alpha, as it will perform much + * better than this method. + * <p> + * All drawing calls are directed to a newly allocated offscreen bitmap. + * Only when the balancing call to restore() is made, is that offscreen + * buffer drawn back to the current target of the Canvas (either the + * screen, it's target Bitmap, or the previous layer). + * <p> + * Attributes of the Paint - {@link Paint#getAlpha() alpha}, + * {@link Paint#getXfermode() Xfermode}, and + * {@link Paint#getColorFilter() ColorFilter} are applied when the + * offscreen bitmap is drawn back when restore() is called. * * @param bounds May be null. The maximum size the offscreen bitmap * needs to be (in local coordinates) * @param paint This is copied, and is applied to the offscreen when * restore() is called. - * @param saveFlags see _SAVE_FLAG constants + * @param saveFlags see _SAVE_FLAG constants, generally {@link #ALL_SAVE_FLAG} is recommended + * for performance reasons. * @return value to pass to restoreToCount() to balance this save() */ public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint, @Saveflags int saveFlags) { @@ -442,19 +487,31 @@ public class Canvas { } /** - * This behaves the same as save(), but in addition it allocates an - * offscreen bitmap. All drawing calls are directed there, and only when - * the balancing call to restore() is made is that offscreen transfered to - * the canvas (or the previous layer). Subsequent calls to translate, - * scale, rotate, skew, concat or clipRect, clipPath all operate on this - * copy. When the balancing call to restore() is made, this copy is - * deleted and the previous matrix/clip state is restored. + * This behaves the same as save(), but in addition it allocates and + * redirects drawing to an offscreen bitmap. + * <p class="note"><strong>Note:</strong> this method is very expensive, + * incurring more than double rendering cost for contained content. Avoid + * using this method, especially if the bounds provided are large, or if + * the {@link #CLIP_TO_LAYER_SAVE_FLAG} is omitted from the + * {@code saveFlags} parameter. It is recommended to use a + * {@link android.view.View#LAYER_TYPE_HARDWARE hardware layer} on a View + * to apply an xfermode, color filter, or alpha, as it will perform much + * better than this method. + * <p> + * All drawing calls are directed to a newly allocated offscreen bitmap. + * Only when the balancing call to restore() is made, is that offscreen + * buffer drawn back to the current target of the Canvas (either the + * screen, it's target Bitmap, or the previous layer). + * <p> + * The {@code alpha} parameter is applied when the offscreen bitmap is + * drawn back when restore() is called. * * @param bounds The maximum size the offscreen bitmap needs to be * (in local coordinates) * @param alpha The alpha to apply to the offscreen when when it is drawn during restore() - * @param saveFlags see _SAVE_FLAG constants + * @param saveFlags see _SAVE_FLAG constants, generally {@link #ALL_SAVE_FLAG} is recommended + * for performance reasons. * @return value to pass to restoreToCount() to balance this call */ public int saveLayerAlpha(@Nullable RectF bounds, int alpha, @Saveflags int saveFlags) { @@ -1644,6 +1701,9 @@ public class Canvas { */ public void drawText(@NonNull CharSequence text, int start, int end, float x, float y, @NonNull Paint paint) { + if ((start | end | (end - start) | (text.length() - end)) < 0) { + throw new IndexOutOfBoundsException(); + } if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { native_drawText(mNativeCanvasWrapper, text.toString(), start, end, x, y, diff --git a/graphics/java/android/graphics/ColorMatrix.java b/graphics/java/android/graphics/ColorMatrix.java index 1242eb5..64f0c05 100644 --- a/graphics/java/android/graphics/ColorMatrix.java +++ b/graphics/java/android/graphics/ColorMatrix.java @@ -21,23 +21,43 @@ import android.util.FloatMath; import java.util.Arrays; /** - * 4x5 matrix for transforming the color+alpha components of a Bitmap. - * The matrix is stored in a single array, and its treated as follows: + * 4x5 matrix for transforming the color and alpha components of a Bitmap. + * The matrix can be passed as single array, and is treated as follows: + * * <pre> * [ a, b, c, d, e, * f, g, h, i, j, * k, l, m, n, o, - * p, q, r, s, t ] - * </pre> + * p, q, r, s, t ]</pre> + * + * <p> + * When applied to a color <code>[R, G, B, A]</code>, the resulting color + * is computed as: + * </p> + * + * <pre> + * R’ = a*R + b*G + c*B + d*A + e; + * G’ = f*R + g*G + h*B + i*A + j; + * B’ = k*R + l*G + m*B + n*A + o; + * A’ = p*R + q*G + r*B + s*A + t;</pre> + * + * <p> + * That resulting color <code>[R’, G’, B’, A’]</code> + * then has each channel clamped to the <code>0</code> to <code>255</code> + * range. + * </p> + * + * <p> + * The sample ColorMatrix below inverts incoming colors by scaling each + * channel by <code>-1</code>, and then shifting the result up by + * <code>255</code> to remain in the standard color space. + * </p> * - * When applied to a color <code>[r, g, b, a]</code>, the resulting color - * is computed as (after clamping): * <pre> - * R' = a*R + b*G + c*B + d*A + e; - * G' = f*R + g*G + h*B + i*A + j; - * B' = k*R + l*G + m*B + n*A + o; - * A' = p*R + q*G + r*B + s*A + t; - * </pre> + * [ -1, 0, 0, 0, 255, + * 0, -1, 0, 0, 255, + * 0, 0, -1, 0, 255, + * 0, 0, 0, 1, 0 ]</pre> */ @SuppressWarnings({ "MismatchedReadAndWriteOfArray", "PointlessArithmeticExpression" }) public class ColorMatrix { @@ -52,24 +72,24 @@ public class ColorMatrix { } /** - * Create a new colormatrix initialized with the specified array of values. + * Create a new colormatrix initialized with the specified array of values. */ public ColorMatrix(float[] src) { System.arraycopy(src, 0, mArray, 0, 20); } - + /** * Create a new colormatrix initialized with the specified colormatrix. */ public ColorMatrix(ColorMatrix src) { System.arraycopy(src.mArray, 0, mArray, 0, 20); } - + /** * Return the array of floats representing this colormatrix. */ public final float[] getArray() { return mArray; } - + /** * Set this colormatrix to identity: * <pre> @@ -84,7 +104,7 @@ public class ColorMatrix { Arrays.fill(a, 0); a[0] = a[6] = a[12] = a[18] = 1; } - + /** * Assign the src colormatrix into this matrix, copying all of its values. */ @@ -98,7 +118,7 @@ public class ColorMatrix { public void set(float[] src) { System.arraycopy(src, 0, mArray, 0, 20); } - + /** * Set this colormatrix to scale by the specified values. */ @@ -114,12 +134,14 @@ public class ColorMatrix { a[12] = bScale; a[18] = aScale; } - + /** * Set the rotation on a color axis by the specified values. + * <p> * <code>axis=0</code> correspond to a rotation around the RED color * <code>axis=1</code> correspond to a rotation around the GREEN color * <code>axis=2</code> correspond to a rotation around the BLUE color + * </p> */ public void setRotate(int axis, float degrees) { reset(); @@ -153,8 +175,10 @@ public class ColorMatrix { /** * Set this colormatrix to the concatenation of the two specified * colormatrices, such that the resulting colormatrix has the same effect - * as applying matB and then applying matA. It is legal for either matA or - * matB to be the same colormatrix as this. + * as applying matB and then applying matA. + * <p> + * It is legal for either matA or matB to be the same colormatrix as this. + * </p> */ public void setConcat(ColorMatrix matA, ColorMatrix matB) { float[] tmp; @@ -163,7 +187,7 @@ public class ColorMatrix { } else { tmp = mArray; } - + final float[] a = matA.mArray; final float[] b = matB.mArray; int index = 0; @@ -176,38 +200,43 @@ public class ColorMatrix { a[j + 2] * b[14] + a[j + 3] * b[19] + a[j + 4]; } - + if (tmp != mArray) { System.arraycopy(tmp, 0, mArray, 0, 20); } } /** - * Concat this colormatrix with the specified prematrix. This is logically - * the same as calling setConcat(this, prematrix); + * Concat this colormatrix with the specified prematrix. + * <p> + * This is logically the same as calling setConcat(this, prematrix); + * </p> */ public void preConcat(ColorMatrix prematrix) { setConcat(this, prematrix); } /** - * Concat this colormatrix with the specified postmatrix. This is logically - * the same as calling setConcat(postmatrix, this); + * Concat this colormatrix with the specified postmatrix. + * <p> + * This is logically the same as calling setConcat(postmatrix, this); + * </p> */ public void postConcat(ColorMatrix postmatrix) { setConcat(postmatrix, this); } /////////////////////////////////////////////////////////////////////////// - + /** - * Set the matrix to affect the saturation of colors. A value of 0 maps the - * color to gray-scale. 1 is identity. + * Set the matrix to affect the saturation of colors. + * + * @param sat A value of 0 maps the color to gray-scale. 1 is identity. */ public void setSaturation(float sat) { reset(); float[] m = mArray; - + final float invSat = 1 - sat; final float R = 0.213f * invSat; final float G = 0.715f * invSat; @@ -217,7 +246,7 @@ public class ColorMatrix { m[5] = R; m[6] = G + sat; m[7] = B; m[10] = R; m[11] = G; m[12] = B + sat; } - + /** * Set the matrix to convert RGB to YUV */ @@ -229,7 +258,7 @@ public class ColorMatrix { m[5] = -0.16874f; m[6] = -0.33126f; m[7] = 0.5f; m[10] = 0.5f; m[11] = -0.41869f; m[12] = -0.08131f; } - + /** * Set the matrix to convert from YUV to RGB */ @@ -242,4 +271,3 @@ public class ColorMatrix { m[10] = 1; m[11] = 1.772f; m[12] = 0; } } - diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java index 4bf0b71..f76184f 100644 --- a/graphics/java/android/graphics/Outline.java +++ b/graphics/java/android/graphics/Outline.java @@ -221,4 +221,15 @@ public final class Outline { mRect = null; mRadius = -1.0f; } + + /** + * Offsets the Outline by (dx,dy) + */ + public void offset(int dx, int dy) { + if (mRect != null) { + mRect.offset(dx, dy); + } else if (mPath != null) { + mPath.offset(dx, dy); + } + } } diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index c40a66d..0e9823d 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -678,7 +678,7 @@ public class Path { } /** - * Offset the path by (dx,dy), returning true on success + * Offset the path by (dx,dy) * * @param dx The amount in the X direction to offset the entire path * @param dy The amount in the Y direction to offset the entire path @@ -695,7 +695,7 @@ public class Path { } /** - * Offset the path by (dx,dy), returning true on success + * Offset the path by (dx,dy) * * @param dx The amount in the X direction to offset the entire path * @param dy The amount in the Y direction to offset the entire path diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java index 9fb3fb4..d2799e1 100644 --- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -16,6 +16,8 @@ package android.graphics.drawable; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.ColorFilter; @@ -41,6 +43,7 @@ import com.android.internal.R; */ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callback, Runnable, Animatable { + private static final String TAG = "AnimatedRotateDrawable"; private AnimatedRotateState mState; private boolean mMutated; @@ -186,6 +189,11 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac } @Override + public boolean canApplyTheme() { + return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); + } + + @Override public void invalidateDrawable(Drawable who) { final Callback callback = getCallback(); if (callback != null) { @@ -254,62 +262,112 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac } @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.AnimatedRotateDrawable); - super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible); + updateStateFromTypedArray(a); + a.recycle(); - TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX); - final boolean pivotXRel = tv.type == TypedValue.TYPE_FRACTION; - final float pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); + inflateChildElements(r, parser, attrs, theme); - tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY); - final boolean pivotYRel = tv.type == TypedValue.TYPE_FRACTION; - final float pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); + init(); + } - setFramesCount(a.getInt(R.styleable.AnimatedRotateDrawable_framesCount, 12)); - setFramesDuration(a.getInt(R.styleable.AnimatedRotateDrawable_frameDuration, 150)); + @Override + public void applyTheme(@Nullable Theme t) { + super.applyTheme(t); - final int res = a.getResourceId(R.styleable.AnimatedRotateDrawable_drawable, 0); - Drawable drawable = null; - if (res > 0) { - drawable = r.getDrawable(res, theme); + final AnimatedRotateState state = mState; + if (state == null) { + return; } - a.recycle(); + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes( + state.mThemeAttrs, R.styleable.AnimatedRotateDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } + } + + if (state.mDrawable != null && state.mDrawable.canApplyTheme()) { + state.mDrawable.applyTheme(t); + } + + init(); + } + private void updateStateFromTypedArray(TypedArray a) { + final AnimatedRotateState state = mState; + + // 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)); + + final Drawable dr = a.getDrawable(R.styleable.AnimatedRotateDrawable_drawable); + if (dr != null) { + state.mDrawable = dr; + dr.setCallback(this); + } + } + + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { + final AnimatedRotateState state = mState; + + Drawable dr = null; int outerDepth = parser.getDepth(); int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && - (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type != XmlPullParser.START_TAG) { continue; } - if ((drawable = Drawable.createFromXmlInner(r, parser, attrs, theme)) == null) { - Log.w("drawable", "Bad element under <animated-rotate>: " - + parser .getName()); + if ((dr = Drawable.createFromXmlInner(r, parser, attrs, theme)) == null) { + Log.w(TAG, "Bad element under <animated-rotate>: " + parser.getName()); } } - if (drawable == null) { - Log.w("drawable", "No drawable specified for <animated-rotate>"); + if (dr != null) { + state.mDrawable = dr; + dr.setCallback(this); } + } - final AnimatedRotateState rotateState = mState; - rotateState.mDrawable = drawable; - rotateState.mPivotXRel = pivotXRel; - rotateState.mPivotX = pivotX; - rotateState.mPivotYRel = pivotYRel; - rotateState.mPivotY = pivotY; - - init(); - - if (drawable != null) { - drawable.setCallback(this); + private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (mState.mDrawable == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <animated-rotate> tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); } } @@ -331,17 +389,27 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac return this; } + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mState.mDrawable.clearMutated(); + mMutated = false; + } + final static class AnimatedRotateState extends Drawable.ConstantState { Drawable mDrawable; + int[] mThemeAttrs; int mChangingConfigurations; - boolean mPivotXRel; - float mPivotX; - boolean mPivotYRel; - float mPivotY; - int mFrameDuration; - int mFramesCount; + boolean mPivotXRel = false; + float mPivotX = 0; + boolean mPivotYRel = false; + float mPivotY = 0; + int mFrameDuration = 150; + int mFramesCount = 12; private boolean mCanConstantState; private boolean mCheckedConstantState; @@ -358,6 +426,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); mDrawable.setBounds(orig.mDrawable.getBounds()); mDrawable.setLevel(orig.mDrawable.getLevel()); + mThemeAttrs = orig.mThemeAttrs; mPivotXRel = orig.mPivotXRel; mPivotX = orig.mPivotX; mPivotYRel = orig.mPivotYRel; @@ -379,6 +448,12 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme()) + || super.canApplyTheme(); + } + + @Override public int getChangingConfigurations() { return mChangingConfigurations; } diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java index cb09bbf..4af5946 100644 --- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java @@ -139,27 +139,21 @@ public class AnimatedStateListDrawable extends StateListDrawable { @Override protected boolean onStateChange(int[] stateSet) { - final int keyframeIndex = mState.indexOfKeyframe(stateSet); - if (keyframeIndex == getCurrentIndex()) { - // Propagate state change to current keyframe. - final Drawable current = getCurrent(); - if (current != null) { - return current.setState(stateSet); - } - return false; - } - - // Attempt to find a valid transition to the keyframe. - if (selectTransition(keyframeIndex)) { - return true; - } + // If we're not already at the target index, either attempt to find a + // valid transition to it or jump directly there. + final int targetIndex = mState.indexOfKeyframe(stateSet); + boolean changed = targetIndex != getCurrentIndex() + && (selectTransition(targetIndex) || selectDrawable(targetIndex)); - // No valid transition, attempt to jump directly to the keyframe. - if (selectDrawable(keyframeIndex)) { - return true; + // We need to propagate the state change to the current drawable, but + // we can't call StateListDrawable.onStateChange() without changing the + // current drawable. + final Drawable current = getCurrent(); + if (current != null) { + changed |= current.setState(stateSet); } - return super.onStateChange(stateSet); + return changed; } private boolean selectTransition(int toIndex) { @@ -205,6 +199,8 @@ public class AnimatedStateListDrawable extends StateListDrawable { return false; } + boolean hasReversibleFlag = state.transitionHasReversibleFlag(fromId, toId); + // This may fail if we're already on the transition, but that's okay! selectDrawable(transitionIndex); @@ -212,10 +208,14 @@ public class AnimatedStateListDrawable extends StateListDrawable { final Drawable d = getCurrent(); if (d instanceof AnimationDrawable) { final boolean reversed = state.isTransitionReversed(fromId, toId); - transition = new AnimationDrawableTransition((AnimationDrawable) d, reversed); + + transition = new AnimationDrawableTransition((AnimationDrawable) d, + reversed, hasReversibleFlag); } else if (d instanceof AnimatedVectorDrawable) { final boolean reversed = state.isTransitionReversed(fromId, toId); - transition = new AnimatedVectorDrawableTransition((AnimatedVectorDrawable) d, reversed); + + transition = new AnimatedVectorDrawableTransition((AnimatedVectorDrawable) d, + reversed, hasReversibleFlag); } else if (d instanceof Animatable) { transition = new AnimatableTransition((Animatable) d); } else { @@ -266,7 +266,12 @@ public class AnimatedStateListDrawable extends StateListDrawable { private static class AnimationDrawableTransition extends Transition { private final ObjectAnimator mAnim; - public AnimationDrawableTransition(AnimationDrawable ad, boolean reversed) { + // Even AnimationDrawable is always reversible technically, but + // we should obey the XML's android:reversible flag. + private final boolean mHasReversibleFlag; + + public AnimationDrawableTransition(AnimationDrawable ad, + boolean reversed, boolean hasReversibleFlag) { final int frameCount = ad.getNumberOfFrames(); final int fromFrame = reversed ? frameCount - 1 : 0; final int toFrame = reversed ? 0 : frameCount - 1; @@ -275,13 +280,13 @@ public class AnimatedStateListDrawable extends StateListDrawable { anim.setAutoCancel(true); anim.setDuration(interp.getTotalDuration()); anim.setInterpolator(interp); - + mHasReversibleFlag = hasReversibleFlag; mAnim = anim; } @Override public boolean canReverse() { - return true; + return mHasReversibleFlag; } @Override @@ -302,16 +307,28 @@ public class AnimatedStateListDrawable extends StateListDrawable { private static class AnimatedVectorDrawableTransition extends Transition { private final AnimatedVectorDrawable mAvd; + + // mReversed is indicating the current transition's direction. private final boolean mReversed; - public AnimatedVectorDrawableTransition(AnimatedVectorDrawable avd, boolean reversed) { + // mHasReversibleFlag is indicating whether the whole transition has + // reversible flag set to true. + // If mHasReversibleFlag is false, then mReversed is always false. + private final boolean mHasReversibleFlag; + + public AnimatedVectorDrawableTransition(AnimatedVectorDrawable avd, + boolean reversed, boolean hasReversibleFlag) { mAvd = avd; mReversed = reversed; + mHasReversibleFlag = hasReversibleFlag; } @Override public boolean canReverse() { - return mAvd.canReverse(); + // When the transition's XML says it is not reversible, then we obey + // it, even if the AVD itself is reversible. + // This will help the single direction transition. + return mAvd.canReverse() && mHasReversibleFlag; } @Override @@ -328,7 +345,8 @@ public class AnimatedStateListDrawable extends StateListDrawable { if (canReverse()) { mAvd.reverse(); } else { - Log.w(LOGTAG, "Reverse() is called on a drawable can't reverse"); + Log.w(LOGTAG, "Can't reverse, either the reversible is set to false," + + " or the AnimatedVectorDrawable can't reverse"); } } @@ -359,24 +377,62 @@ public class AnimatedStateListDrawable extends StateListDrawable { throws XmlPullParserException, IOException { final TypedArray a = obtainAttributes( r, theme, attrs, R.styleable.AnimatedStateListDrawable); - super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedStateListDrawable_visible); + updateStateFromTypedArray(a); + a.recycle(); + + inflateChildElements(r, parser, attrs, theme); - final StateListState stateListState = getStateListState(); - stateListState.setVariablePadding(a.getBoolean( - R.styleable.AnimatedStateListDrawable_variablePadding, false)); - stateListState.setConstantSize(a.getBoolean( - R.styleable.AnimatedStateListDrawable_constantSize, false)); - stateListState.setEnterFadeDuration(a.getInt( - R.styleable.AnimatedStateListDrawable_enterFadeDuration, 0)); - stateListState.setExitFadeDuration(a.getInt( - R.styleable.AnimatedStateListDrawable_exitFadeDuration, 0)); + init(); + } + + @Override + public void applyTheme(@Nullable Theme theme) { + super.applyTheme(theme); - setDither(a.getBoolean(R.styleable.AnimatedStateListDrawable_dither, true)); - setAutoMirrored(a.getBoolean(R.styleable.AnimatedStateListDrawable_autoMirrored, false)); + final AnimatedStateListState state = mState; + if (state == null || state.mAnimThemeAttrs == null) { + return; + } + final TypedArray a = theme.resolveAttributes( + state.mAnimThemeAttrs, R.styleable.AnimatedRotateDrawable); + updateStateFromTypedArray(a); a.recycle(); + init(); + } + + private void updateStateFromTypedArray(TypedArray a) { + final AnimatedStateListState state = mState; + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mAnimThemeAttrs = a.extractThemeAttrs(); + + state.setVariablePadding(a.getBoolean( + R.styleable.AnimatedStateListDrawable_variablePadding, state.mVariablePadding)); + state.setConstantSize(a.getBoolean( + R.styleable.AnimatedStateListDrawable_constantSize, state.mConstantSize)); + state.setEnterFadeDuration(a.getInt( + R.styleable.AnimatedStateListDrawable_enterFadeDuration, state.mEnterFadeDuration)); + state.setExitFadeDuration(a.getInt( + R.styleable.AnimatedStateListDrawable_exitFadeDuration, state.mExitFadeDuration)); + + setDither(a.getBoolean( + R.styleable.AnimatedStateListDrawable_dither, state.mDither)); + setAutoMirrored(a.getBoolean( + R.styleable.AnimatedStateListDrawable_autoMirrored, state.mAutoMirrored)); + } + + private void init() { + onStateChange(getState()); + } + + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { int type; final int innerDepth = parser.getDepth() + 1; @@ -398,50 +454,36 @@ public class AnimatedStateListDrawable extends StateListDrawable { parseTransition(r, parser, attrs, theme); } } - - onStateChange(getState()); } private int parseTransition(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { - int drawableRes = 0; - int fromId = 0; - int toId = 0; - boolean reversible = false; - - final int numAttrs = attrs.getAttributeCount(); - for (int i = 0; i < numAttrs; i++) { - final int stateResId = attrs.getAttributeNameResource(i); - switch (stateResId) { - case 0: - break; - case R.attr.fromId: - fromId = attrs.getAttributeResourceValue(i, 0); - break; - case R.attr.toId: - toId = attrs.getAttributeResourceValue(i, 0); - break; - case R.attr.drawable: - drawableRes = attrs.getAttributeResourceValue(i, 0); - break; - case R.attr.reversible: - reversible = attrs.getAttributeBooleanValue(i, false); - break; - } - } + // This allows state list drawable item elements to be themed at + // inflation time but does NOT make them work for Zygote preload. + final TypedArray a = obtainAttributes(r, theme, attrs, + R.styleable.AnimatedStateListDrawableTransition); + final int fromId = a.getResourceId( + R.styleable.AnimatedStateListDrawableTransition_fromId, 0); + final int toId = a.getResourceId( + R.styleable.AnimatedStateListDrawableTransition_toId, 0); + final boolean reversible = a.getBoolean( + R.styleable.AnimatedStateListDrawableTransition_reversible, false); + Drawable dr = a.getDrawable( + R.styleable.AnimatedStateListDrawableTransition_drawable); + a.recycle(); - final Drawable dr; - if (drawableRes != 0) { - dr = r.getDrawable(drawableRes, theme); - } else { + // Loading child elements modifies the state of the AttributeSet's + // underlying parser, so it needs to happen after obtaining + // attributes and extracting states. + if (dr == null) { int type; while ((type = parser.next()) == XmlPullParser.TEXT) { } if (type != XmlPullParser.START_TAG) { throw new XmlPullParserException( parser.getPositionDescription() - + ": <item> tag requires a 'drawable' attribute or " + + ": <transition> tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } dr = Drawable.createFromXmlInner(r, parser, attrs, theme); @@ -453,34 +495,20 @@ public class AnimatedStateListDrawable extends StateListDrawable { private int parseItem(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { - int drawableRes = 0; - int keyframeId = 0; - - int j = 0; - final int numAttrs = attrs.getAttributeCount(); - int[] states = new int[numAttrs]; - for (int i = 0; i < numAttrs; i++) { - final int stateResId = attrs.getAttributeNameResource(i); - switch (stateResId) { - case 0: - break; - case R.attr.id: - keyframeId = attrs.getAttributeResourceValue(i, 0); - break; - case R.attr.drawable: - drawableRes = attrs.getAttributeResourceValue(i, 0); - break; - default: - final boolean hasState = attrs.getAttributeBooleanValue(i, false); - states[j++] = hasState ? stateResId : -stateResId; - } - } - states = StateSet.trimStateSet(states, j); + // This allows state list drawable item elements to be themed at + // inflation time but does NOT make them work for Zygote preload. + final TypedArray a = obtainAttributes(r, theme, attrs, + R.styleable.AnimatedStateListDrawableItem); + final int keyframeId = a.getResourceId(R.styleable.AnimatedStateListDrawableItem_id, 0); + Drawable dr = a.getDrawable(R.styleable.AnimatedStateListDrawableItem_drawable); + a.recycle(); - final Drawable dr; - if (drawableRes != 0) { - dr = r.getDrawable(drawableRes, theme); - } else { + final int[] states = extractStateSet(attrs); + + // Loading child elements modifies the state of the AttributeSet's + // underlying parser, so it needs to happen after obtaining + // attributes and extracting states. + if (dr == null) { int type; while ((type = parser.next()) == XmlPullParser.TEXT) { } @@ -499,42 +527,71 @@ public class AnimatedStateListDrawable extends StateListDrawable { @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { - final AnimatedStateListState newState = new AnimatedStateListState(mState, this, null); - setConstantState(newState); + mState.mutate(); mMutated = true; } return this; } + @Override + AnimatedStateListState cloneConstantState() { + return new AnimatedStateListState(mState, this, null); + } + + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mMutated = false; + } + static class AnimatedStateListState extends StateListState { - private static final int REVERSE_SHIFT = 32; - private static final int REVERSE_MASK = 0x1; + // REVERSED_BIT is indicating the current transition's direction. + private static final long REVERSED_BIT = 0x100000000l; + + // REVERSIBLE_FLAG_BIT is indicating whether the whole transition has + // reversible flag set to true. + private static final long REVERSIBLE_FLAG_BIT = 0x200000000l; - final LongSparseLongArray mTransitions; - final SparseIntArray mStateIds; + int[] mAnimThemeAttrs; + + LongSparseLongArray mTransitions; + SparseIntArray mStateIds; AnimatedStateListState(@Nullable AnimatedStateListState orig, @NonNull AnimatedStateListDrawable owner, @Nullable Resources res) { super(orig, owner, res); if (orig != null) { - mTransitions = orig.mTransitions.clone(); - mStateIds = orig.mStateIds.clone(); + // Perform a shallow copy and rely on mutate() to deep-copy. + mAnimThemeAttrs = orig.mAnimThemeAttrs; + mTransitions = orig.mTransitions; + mStateIds = orig.mStateIds; } else { mTransitions = new LongSparseLongArray(); mStateIds = new SparseIntArray(); } } + private void mutate() { + mTransitions = mTransitions.clone(); + mStateIds = mStateIds.clone(); + } + int addTransition(int fromId, int toId, @NonNull Drawable anim, boolean reversible) { final int pos = super.addChild(anim); final long keyFromTo = generateTransitionKey(fromId, toId); - mTransitions.append(keyFromTo, pos); + long reversibleBit = 0; + if (reversible) { + reversibleBit = REVERSIBLE_FLAG_BIT; + } + mTransitions.append(keyFromTo, pos | reversibleBit); if (reversible) { final long keyToFrom = generateTransitionKey(toId, fromId); - mTransitions.append(keyToFrom, pos | (1L << REVERSE_SHIFT)); + mTransitions.append(keyToFrom, pos | REVERSED_BIT | reversibleBit); } return addChild(anim); @@ -566,7 +623,17 @@ public class AnimatedStateListDrawable extends StateListDrawable { boolean isTransitionReversed(int fromId, int toId) { final long keyFromTo = generateTransitionKey(fromId, toId); - return (mTransitions.get(keyFromTo, -1) >> REVERSE_SHIFT & REVERSE_MASK) == 1; + return (mTransitions.get(keyFromTo, -1) & REVERSED_BIT) != 0; + } + + boolean transitionHasReversibleFlag(int fromId, int toId) { + final long keyFromTo = generateTransitionKey(fromId, toId); + return (mTransitions.get(keyFromTo, -1) & REVERSIBLE_FLAG_BIT) != 0; + } + + @Override + public boolean canApplyTheme() { + return mAnimThemeAttrs != null || super.canApplyTheme(); } @Override @@ -584,15 +651,19 @@ public class AnimatedStateListDrawable extends StateListDrawable { } } - void setConstantState(@NonNull AnimatedStateListState state) { + @Override + protected void setConstantState(@NonNull DrawableContainerState state) { super.setConstantState(state); - mState = state; + if (state instanceof AnimatedStateListState) { + mState = (AnimatedStateListState) state; + } } private AnimatedStateListDrawable(@Nullable AnimatedStateListState state, @Nullable Resources res) { super(null); + // Every animated state list drawable has its own constant state. final AnimatedStateListState newState = new AnimatedStateListState(state, this, res); setConstantState(newState); onStateChange(getState()); diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index ad0b415..2a17a60 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -16,7 +16,6 @@ package android.graphics.drawable; import android.animation.Animator; import android.animation.AnimatorInflater; -import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -137,26 +136,32 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable { private boolean mMutated; public AnimatedVectorDrawable() { - mAnimatedVectorState = new AnimatedVectorDrawableState(null); + this(null, null); } - private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res, - Theme theme) { - mAnimatedVectorState = new AnimatedVectorDrawableState(state); - if (theme != null && canApplyTheme()) { - applyTheme(theme); - } + private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) { + mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res); } @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { - mAnimatedVectorState.mVectorDrawable.mutate(); + mAnimatedVectorState = new AnimatedVectorDrawableState( + mAnimatedVectorState, mCallback, null); mMutated = true; } return this; } + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mAnimatedVectorState.mVectorDrawable.clearMutated(); + mMutated = false; + } + @Override public ConstantState getConstantState() { mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations(); @@ -281,7 +286,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable { VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable( drawableRes, theme).mutate(); vectorDrawable.setAllowCaching(false); + vectorDrawable.setCallback(mCallback); pathErrorScale = vectorDrawable.getPixelSize(); + if (mAnimatedVectorState.mVectorDrawable != null) { + mAnimatedVectorState.mVectorDrawable.setCallback(null); + } mAnimatedVectorState.mVectorDrawable = vectorDrawable; } a.recycle(); @@ -308,9 +317,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable { @Override public boolean canApplyTheme() { - return super.canApplyTheme() || mAnimatedVectorState != null - && mAnimatedVectorState.mVectorDrawable != null - && mAnimatedVectorState.mVectorDrawable.canApplyTheme(); + return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme()) + || super.canApplyTheme(); } @Override @@ -329,14 +337,22 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable { ArrayList<Animator> mAnimators; ArrayMap<Animator, String> mTargetNameMap; - public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) { + public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy, + Callback owner, Resources res) { if (copy != null) { mChangingConfigurations = copy.mChangingConfigurations; if (copy.mVectorDrawable != null) { - mVectorDrawable = (VectorDrawable) copy.mVectorDrawable.getConstantState().newDrawable(); - mVectorDrawable.mutate(); - mVectorDrawable.setAllowCaching(false); + final ConstantState cs = copy.mVectorDrawable.getConstantState(); + if (res != null) { + mVectorDrawable = (VectorDrawable) cs.newDrawable(res); + } else { + mVectorDrawable = (VectorDrawable) cs.newDrawable(); + } + mVectorDrawable = (VectorDrawable) mVectorDrawable.mutate(); + mVectorDrawable.setCallback(owner); + mVectorDrawable.setLayoutDirection(copy.mVectorDrawable.getLayoutDirection()); mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds()); + mVectorDrawable.setAllowCaching(false); } if (copy.mAnimators != null) { final int numAnimators = copy.mAnimators.size(); @@ -358,18 +374,19 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable { } @Override - public Drawable newDrawable() { - return new AnimatedVectorDrawable(this, null, null); + public boolean canApplyTheme() { + return (mVectorDrawable != null && mVectorDrawable.canApplyTheme()) + || super.canApplyTheme(); } @Override - public Drawable newDrawable(Resources res) { - return new AnimatedVectorDrawable(this, res, null); + public Drawable newDrawable() { + return new AnimatedVectorDrawable(this, null); } @Override - public Drawable newDrawable(Resources res, Theme theme) { - return new AnimatedVectorDrawable(this, res, theme); + public Drawable newDrawable(Resources res) { + return new AnimatedVectorDrawable(this, res); } @Override @@ -419,13 +436,16 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable { @Override public void start() { + // If any one of the animator has not ended, do nothing. + if (isStarted()) { + return; + } + // Otherwise, kick off every animator. final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; final int size = animators.size(); for (int i = 0; i < size; i++) { final Animator animator = animators.get(i); - if (!animator.isStarted()) { - animator.start(); - } + animator.start(); } invalidateSelf(); } @@ -443,19 +463,22 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable { /** * Reverses ongoing animations or starts pending animations in reverse. * <p> - * NOTE: Only works of all animations are ValueAnimators. + * NOTE: Only works if all animations support reverse. Otherwise, this will + * do nothing. * @hide */ public void reverse() { + // Only reverse when all the animators can be reverse. Otherwise, partially + // reverse is confusing. + if (!canReverse()) { + Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); + return; + } final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; final int size = animators.size(); for (int i = 0; i < size; i++) { final Animator animator = animators.get(i); - if (animator.canReverse()) { - animator.reverse(); - } else { - Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); - } + animator.reverse(); } } @@ -473,4 +496,21 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable { } return true; } + + private final Callback mCallback = new Callback() { + @Override + public void invalidateDrawable(Drawable who) { + invalidateSelf(); + } + + @Override + public void scheduleDrawable(Drawable who, Runnable what, long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(Drawable who, Runnable what) { + unscheduleSelf(what); + } + }; } diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java index 9a9fd82..74ff1b0 100644 --- a/graphics/java/android/graphics/drawable/AnimationDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java @@ -16,11 +16,14 @@ package android.graphics.drawable; +import com.android.internal.R; + import java.io.IOException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.NonNull; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; @@ -79,7 +82,7 @@ import android.util.AttributeSet; * @attr ref android.R.styleable#AnimationDrawableItem_drawable */ public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable { - private final AnimationState mAnimationState; + private AnimationState mAnimationState; /** The current frame, may be -1 when not animating. */ private int mCurFrame = -1; @@ -271,27 +274,24 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimationDrawable); + super.inflateWithAttributes(r, parser, a, R.styleable.AnimationDrawable_visible); + updateStateFromTypedArray(a); + a.recycle(); - TypedArray a = obtainAttributes(r, theme, attrs, - com.android.internal.R.styleable.AnimationDrawable); - - super.inflateWithAttributes(r, parser, a, - com.android.internal.R.styleable.AnimationDrawable_visible); - - mAnimationState.setVariablePadding(a.getBoolean( - com.android.internal.R.styleable.AnimationDrawable_variablePadding, false)); - - mAnimationState.mOneShot = a.getBoolean( - com.android.internal.R.styleable.AnimationDrawable_oneshot, false); + inflateChildElements(r, parser, attrs, theme); - a.recycle(); + setFrame(0, true, false); + } + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { int type; final int innerDepth = parser.getDepth()+1; int depth; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && - ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { if (type != XmlPullParser.START_TAG) { continue; } @@ -300,31 +300,27 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An continue; } - a = obtainAttributes( - r, theme, attrs, com.android.internal.R.styleable.AnimationDrawableItem); - int duration = a.getInt( - com.android.internal.R.styleable.AnimationDrawableItem_duration, -1); + final TypedArray a = obtainAttributes(r, theme, attrs, + R.styleable.AnimationDrawableItem); + + final int duration = a.getInt(R.styleable.AnimationDrawableItem_duration, -1); if (duration < 0) { - throw new XmlPullParserException( - parser.getPositionDescription() + throw new XmlPullParserException(parser.getPositionDescription() + ": <item> tag requires a 'duration' attribute"); } - int drawableRes = a.getResourceId( - com.android.internal.R.styleable.AnimationDrawableItem_drawable, 0); + + Drawable dr = a.getDrawable(R.styleable.AnimationDrawableItem_drawable); a.recycle(); - Drawable dr; - if (drawableRes != 0) { - dr = r.getDrawable(drawableRes, theme); - } else { + if (dr == null) { while ((type=parser.next()) == XmlPullParser.TEXT) { // Empty } if (type != XmlPullParser.START_TAG) { - throw new XmlPullParserException(parser.getPositionDescription() + - ": <item> tag requires a 'drawable' attribute or child tag" + - " defining a drawable"); + throw new XmlPullParserException(parser.getPositionDescription() + + ": <item> tag requires a 'drawable' attribute or child tag" + + " defining a drawable"); } dr = Drawable.createFromXmlInner(r, parser, attrs, theme); } @@ -334,22 +330,41 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An dr.setCallback(this); } } + } - setFrame(0, true, false); + private void updateStateFromTypedArray(TypedArray a) { + mAnimationState.mVariablePadding = a.getBoolean( + R.styleable.AnimationDrawable_variablePadding, mAnimationState.mVariablePadding); + + mAnimationState.mOneShot = a.getBoolean( + R.styleable.AnimationDrawable_oneshot, mAnimationState.mOneShot); } @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { - mAnimationState.mDurations = mAnimationState.mDurations.clone(); + mAnimationState.mutate(); mMutated = true; } return this; } + @Override + AnimationState cloneConstantState() { + return new AnimationState(mAnimationState, this, null); + } + + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mMutated = false; + } + private final static class AnimationState extends DrawableContainerState { private int[] mDurations; - private boolean mOneShot; + private boolean mOneShot = false; AnimationState(AnimationState orig, AnimationDrawable owner, Resources res) { @@ -360,10 +375,14 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An mOneShot = orig.mOneShot; } else { mDurations = new int[getCapacity()]; - mOneShot = true; + mOneShot = false; } } + private void mutate() { + mDurations = mDurations.clone(); + } + @Override public Drawable newDrawable() { return new AnimationDrawable(this, null); @@ -390,9 +409,17 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An } } + @Override + protected void setConstantState(@NonNull DrawableContainerState state) { + super.setConstantState(state); + + if (state instanceof AnimationState) { + mAnimationState = (AnimationState) state; + } + } + private AnimationDrawable(AnimationState state, Resources res) { - AnimationState as = new AnimationState(state, this, res); - mAnimationState = as; + final AnimationState as = new AnimationState(state, this, res); setConstantState(as); if (state != null) { setFrame(0, true, false); diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index cf6be48..9be296a 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -48,6 +48,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.Collection; /** * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a @@ -132,7 +133,7 @@ public class BitmapDrawable extends Drawable { */ @Deprecated public BitmapDrawable(Bitmap bitmap) { - this(new BitmapState(bitmap), null, null); + this(new BitmapState(bitmap), null); } /** @@ -140,7 +141,7 @@ public class BitmapDrawable extends Drawable { * the display metrics of the resources. */ public BitmapDrawable(Resources res, Bitmap bitmap) { - this(new BitmapState(bitmap), res, null); + this(new BitmapState(bitmap), res); mBitmapState.mTargetDensity = mTargetDensity; } @@ -151,7 +152,7 @@ public class BitmapDrawable extends Drawable { */ @Deprecated public BitmapDrawable(String filepath) { - this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null); + this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); if (mBitmapState.mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); } @@ -162,7 +163,7 @@ public class BitmapDrawable extends Drawable { */ @SuppressWarnings("unused") public BitmapDrawable(Resources res, String filepath) { - this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null); + this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); mBitmapState.mTargetDensity = mTargetDensity; if (mBitmapState.mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); @@ -176,7 +177,7 @@ public class BitmapDrawable extends Drawable { */ @Deprecated public BitmapDrawable(java.io.InputStream is) { - this(new BitmapState(BitmapFactory.decodeStream(is)), null, null); + this(new BitmapState(BitmapFactory.decodeStream(is)), null); if (mBitmapState.mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); } @@ -187,7 +188,7 @@ public class BitmapDrawable extends Drawable { */ @SuppressWarnings("unused") public BitmapDrawable(Resources res, java.io.InputStream is) { - this(new BitmapState(BitmapFactory.decodeStream(is)), null, null); + this(new BitmapState(BitmapFactory.decodeStream(is)), null); mBitmapState.mTargetDensity = mTargetDensity; if (mBitmapState.mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); @@ -684,6 +685,14 @@ public class BitmapDrawable extends Drawable { return this; } + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mMutated = false; + } + @Override protected boolean onStateChange(int[] stateSet) { final BitmapState state = mBitmapState; @@ -905,23 +914,21 @@ public class BitmapDrawable extends Drawable { } @Override - public Bitmap getBitmap() { - return mBitmap; + public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { + if (isAtlasable(mBitmap) && atlasList.add(mBitmap)) { + return mBitmap.getWidth() * mBitmap.getHeight(); + } + return 0; } @Override public Drawable newDrawable() { - return new BitmapDrawable(this, null, null); + return new BitmapDrawable(this, null); } @Override public Drawable newDrawable(Resources res) { - return new BitmapDrawable(this, res, null); - } - - @Override - public Drawable newDrawable(Resources res, Theme theme) { - return new BitmapDrawable(this, res, theme); + return new BitmapDrawable(this, res); } @Override @@ -934,16 +941,10 @@ public class BitmapDrawable extends Drawable { * The one constructor to rule them all. This is called by all public * constructors to set the state and initialize local properties. */ - private BitmapDrawable(BitmapState state, Resources res, Theme theme) { - if (theme != null && state.canApplyTheme()) { - // If we need to apply a theme, implicitly mutate. - mBitmapState = new BitmapState(state); - applyTheme(theme); - } else { - mBitmapState = state; - } + private BitmapDrawable(BitmapState state, Resources res) { + mBitmapState = state; - initializeWithState(state, res); + initializeWithState(mBitmapState, res); } /** diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java index 40711cf..e5b2b76 100644 --- a/graphics/java/android/graphics/drawable/ClipDrawable.java +++ b/graphics/java/android/graphics/drawable/ClipDrawable.java @@ -16,6 +16,8 @@ package android.graphics.drawable; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -49,12 +51,14 @@ import java.io.IOException; * @attr ref android.R.styleable#ClipDrawable_drawable */ public class ClipDrawable extends Drawable implements Drawable.Callback { - private ClipState mClipState; + private ClipState mState; private final Rect mTmpRect = new Rect(); public static final int HORIZONTAL = 1; public static final int VERTICAL = 2; + private boolean mMutated; + ClipDrawable() { this(null, null); } @@ -65,9 +69,9 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { public ClipDrawable(Drawable drawable, int gravity, int orientation) { this(null, null); - mClipState.mDrawable = drawable; - mClipState.mGravity = gravity; - mClipState.mOrientation = orientation; + mState.mDrawable = drawable; + mState.mGravity = gravity; + mState.mOrientation = orientation; if (drawable != null) { drawable.setCallback(this); @@ -79,19 +83,22 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { throws XmlPullParserException, IOException { super.inflate(r, parser, attrs, theme); - int type; - - TypedArray a = obtainAttributes( - r, theme, attrs, com.android.internal.R.styleable.ClipDrawable); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable); - int orientation = a.getInt( - com.android.internal.R.styleable.ClipDrawable_clipOrientation, - HORIZONTAL); - int g = a.getInt(com.android.internal.R.styleable.ClipDrawable_gravity, Gravity.LEFT); - Drawable dr = a.getDrawable(com.android.internal.R.styleable.ClipDrawable_drawable); + // Reset mDrawable to preserve old multiple-inflate behavior. This is + // silly, but we have CTS tests that rely on it. + mState.mDrawable = null; + updateStateFromTypedArray(a); + inflateChildElements(r, parser, attrs, theme); + verifyRequiredAttributes(a); a.recycle(); + } + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { + Drawable dr = null; + int type; final int outerDepth = parser.getDepth(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { @@ -101,15 +108,68 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { dr = Drawable.createFromXmlInner(r, parser, attrs, theme); } - if (dr == null) { - throw new IllegalArgumentException("No drawable specified for <clip>"); + if (dr != null) { + mState.mDrawable = dr; + dr.setCallback(this); + } + } + + private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (mState.mDrawable == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <clip> tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + } + + private void updateStateFromTypedArray(TypedArray a) { + final ClipState state = mState; + + // 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); + + final Drawable dr = a.getDrawable(R.styleable.ClipDrawable_drawable); + if (dr != null) { + state.mDrawable = dr; + dr.setCallback(this); + } + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final ClipState state = mState; + if (state == null || state.mThemeAttrs == null) { + return; + } + + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ClipDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); } - mClipState.mDrawable = dr; - mClipState.mOrientation = orientation; - mClipState.mGravity = g; + if (state.mDrawable != null && state.mDrawable.canApplyTheme()) { + state.mDrawable.applyTheme(t); + } + } - dr.setCallback(this); + @Override + public boolean canApplyTheme() { + return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); } // overrides from Drawable.Callback @@ -143,78 +203,78 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { @Override public int getChangingConfigurations() { return super.getChangingConfigurations() - | mClipState.mChangingConfigurations - | mClipState.mDrawable.getChangingConfigurations(); + | mState.mChangingConfigurations + | mState.mDrawable.getChangingConfigurations(); } @Override public boolean getPadding(Rect padding) { // XXX need to adjust padding! - return mClipState.mDrawable.getPadding(padding); + return mState.mDrawable.getPadding(padding); } @Override public boolean setVisible(boolean visible, boolean restart) { - mClipState.mDrawable.setVisible(visible, restart); + mState.mDrawable.setVisible(visible, restart); return super.setVisible(visible, restart); } @Override public void setAlpha(int alpha) { - mClipState.mDrawable.setAlpha(alpha); + mState.mDrawable.setAlpha(alpha); } @Override public int getAlpha() { - return mClipState.mDrawable.getAlpha(); + return mState.mDrawable.getAlpha(); } @Override public void setColorFilter(ColorFilter cf) { - mClipState.mDrawable.setColorFilter(cf); + mState.mDrawable.setColorFilter(cf); } @Override public void setTintList(ColorStateList tint) { - mClipState.mDrawable.setTintList(tint); + mState.mDrawable.setTintList(tint); } @Override public void setTintMode(Mode tintMode) { - mClipState.mDrawable.setTintMode(tintMode); + mState.mDrawable.setTintMode(tintMode); } @Override public int getOpacity() { - return mClipState.mDrawable.getOpacity(); + return mState.mDrawable.getOpacity(); } @Override public boolean isStateful() { - return mClipState.mDrawable.isStateful(); + return mState.mDrawable.isStateful(); } @Override protected boolean onStateChange(int[] state) { - return mClipState.mDrawable.setState(state); + return mState.mDrawable.setState(state); } @Override protected boolean onLevelChange(int level) { - mClipState.mDrawable.setLevel(level); + mState.mDrawable.setLevel(level); invalidateSelf(); return true; } @Override protected void onBoundsChange(Rect bounds) { - mClipState.mDrawable.setBounds(bounds); + mState.mDrawable.setBounds(bounds); } @Override public void draw(Canvas canvas) { - if (mClipState.mDrawable.getLevel() == 0) { + if (mState.mDrawable.getLevel() == 0) { return; } @@ -222,41 +282,41 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { final Rect bounds = getBounds(); int level = getLevel(); int w = bounds.width(); - final int iw = 0; //mClipState.mDrawable.getIntrinsicWidth(); - if ((mClipState.mOrientation & HORIZONTAL) != 0) { + final int iw = 0; //mState.mDrawable.getIntrinsicWidth(); + if ((mState.mOrientation & HORIZONTAL) != 0) { w -= (w - iw) * (10000 - level) / 10000; } int h = bounds.height(); - final int ih = 0; //mClipState.mDrawable.getIntrinsicHeight(); - if ((mClipState.mOrientation & VERTICAL) != 0) { + final int ih = 0; //mState.mDrawable.getIntrinsicHeight(); + if ((mState.mOrientation & VERTICAL) != 0) { h -= (h - ih) * (10000 - level) / 10000; } final int layoutDirection = getLayoutDirection(); - Gravity.apply(mClipState.mGravity, w, h, bounds, r, layoutDirection); + Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection); if (w > 0 && h > 0) { canvas.save(); canvas.clipRect(r); - mClipState.mDrawable.draw(canvas); + mState.mDrawable.draw(canvas); canvas.restore(); } } @Override public int getIntrinsicWidth() { - return mClipState.mDrawable.getIntrinsicWidth(); + return mState.mDrawable.getIntrinsicWidth(); } @Override public int getIntrinsicHeight() { - return mClipState.mDrawable.getIntrinsicHeight(); + return mState.mDrawable.getIntrinsicHeight(); } @Override public ConstantState getConstantState() { - if (mClipState.canConstantState()) { - mClipState.mChangingConfigurations = getChangingConfigurations(); - return mClipState; + if (mState.canConstantState()) { + mState.mChangingConfigurations = getChangingConfigurations(); + return mState; } return null; } @@ -264,21 +324,44 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { /** @hide */ @Override public void setLayoutDirection(int layoutDirection) { - mClipState.mDrawable.setLayoutDirection(layoutDirection); + mState.mDrawable.setLayoutDirection(layoutDirection); super.setLayoutDirection(layoutDirection); } + @Override + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mState.mDrawable.mutate(); + mMutated = true; + } + return this; + } + + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mState.mDrawable.clearMutated(); + mMutated = false; + } + final static class ClipState extends ConstantState { - Drawable mDrawable; + int[] mThemeAttrs; int mChangingConfigurations; - int mOrientation; - int mGravity; + + Drawable mDrawable; + + int mOrientation = HORIZONTAL; + int mGravity = Gravity.LEFT; private boolean mCheckedConstantState; private boolean mCanConstantState; ClipState(ClipState orig, ClipDrawable owner, Resources res) { if (orig != null) { + mThemeAttrs = orig.mThemeAttrs; + mChangingConfigurations = orig.mChangingConfigurations; if (res != null) { mDrawable = orig.mDrawable.getConstantState().newDrawable(res); } else { @@ -295,6 +378,12 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme()) + || super.canApplyTheme(); + } + + @Override public Drawable newDrawable() { return new ClipDrawable(this, null); } @@ -320,7 +409,7 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { } private ClipDrawable(ClipState state, Resources res) { - mClipState = new ClipState(state, this, res); + mState = new ClipState(state, this, res); } } diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java index 1253c46..e3b50ea 100644 --- a/graphics/java/android/graphics/drawable/ColorDrawable.java +++ b/graphics/java/android/graphics/drawable/ColorDrawable.java @@ -88,6 +88,14 @@ public class ColorDrawable extends Drawable { return this; } + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mMutated = false; + } + @Override public void draw(Canvas canvas) { final ColorFilter colorFilter = mPaint.getColorFilter(); @@ -244,6 +252,11 @@ public class ColorDrawable extends Drawable { } @Override + public boolean canApplyTheme() { + return mColorState.canApplyTheme() || super.canApplyTheme(); + } + + @Override public void applyTheme(Theme t) { super.applyTheme(t); @@ -291,17 +304,12 @@ public class ColorDrawable extends Drawable { @Override public Drawable newDrawable() { - return new ColorDrawable(this, null, null); + return new ColorDrawable(this); } @Override public Drawable newDrawable(Resources res) { - return new ColorDrawable(this, res, null); - } - - @Override - public Drawable newDrawable(Resources res, Theme theme) { - return new ColorDrawable(this, res, theme); + return new ColorDrawable(this); } @Override @@ -310,14 +318,8 @@ public class ColorDrawable extends Drawable { } } - private ColorDrawable(ColorState state, Resources res, Theme theme) { - if (theme != null && state.canApplyTheme()) { - mColorState = new ColorState(state); - applyTheme(theme); - } else { - mColorState = state; - } - + private ColorDrawable(ColorState state) { + mColorState = state; mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); } } diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 9ae788c..0e38cc0 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -51,6 +51,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.Arrays; +import java.util.Collection; /** * A Drawable is a general abstraction for "something that can be drawn." Most @@ -917,6 +918,20 @@ public abstract class Drawable { } /** + * Clears the mutated state, allowing this drawable to be cached and + * mutated again. + * <p> + * This is hidden because only framework drawables can be cached, so + * custom drawables don't need to support constant state, mutate(), or + * clearMutated(). + * + * @hide + */ + public void clearMutated() { + // Default implementation is no-op. + } + + /** * Create a drawable from an inputstream */ public static Drawable createFromStream(InputStream is, String srcName) { @@ -1044,54 +1059,72 @@ public abstract class Drawable { final Drawable drawable; final String name = parser.getName(); - if (name.equals("selector")) { - drawable = new StateListDrawable(); - } else if (name.equals("animated-selector")) { - drawable = new AnimatedStateListDrawable(); - } else if (name.equals("level-list")) { - drawable = new LevelListDrawable(); - } else if (name.equals("layer-list")) { - drawable = new LayerDrawable(); - } else if (name.equals("transition")) { - drawable = new TransitionDrawable(); - } else if (name.equals("ripple")) { - drawable = new RippleDrawable(); - } else if (name.equals("color")) { - drawable = new ColorDrawable(); - } else if (name.equals("shape")) { - drawable = new GradientDrawable(); - } else if (name.equals("vector")) { - drawable = new VectorDrawable(); - } else if (name.equals("animated-vector")) { - drawable = new AnimatedVectorDrawable(); - } else if (name.equals("scale")) { - drawable = new ScaleDrawable(); - } else if (name.equals("clip")) { - drawable = new ClipDrawable(); - } else if (name.equals("rotate")) { - drawable = new RotateDrawable(); - } else if (name.equals("animated-rotate")) { - drawable = new AnimatedRotateDrawable(); - } else if (name.equals("animation-list")) { - drawable = new AnimationDrawable(); - } else if (name.equals("inset")) { - drawable = new InsetDrawable(); - } else if (name.equals("bitmap")) { - //noinspection deprecation - drawable = new BitmapDrawable(r); - if (r != null) { - ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics()); - } - } else if (name.equals("nine-patch")) { - drawable = new NinePatchDrawable(); - if (r != null) { - ((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics()); - } - } else { - throw new XmlPullParserException(parser.getPositionDescription() + - ": invalid drawable tag " + name); - } + switch (name) { + case "selector": + drawable = new StateListDrawable(); + break; + case "animated-selector": + drawable = new AnimatedStateListDrawable(); + break; + case "level-list": + drawable = new LevelListDrawable(); + break; + case "layer-list": + drawable = new LayerDrawable(); + break; + case "transition": + drawable = new TransitionDrawable(); + break; + case "ripple": + drawable = new RippleDrawable(); + break; + case "color": + drawable = new ColorDrawable(); + break; + case "shape": + drawable = new GradientDrawable(); + break; + case "vector": + drawable = new VectorDrawable(); + break; + case "animated-vector": + drawable = new AnimatedVectorDrawable(); + break; + case "scale": + drawable = new ScaleDrawable(); + break; + case "clip": + drawable = new ClipDrawable(); + break; + case "rotate": + drawable = new RotateDrawable(); + break; + case "animated-rotate": + drawable = new AnimatedRotateDrawable(); + break; + case "animation-list": + drawable = new AnimationDrawable(); + break; + case "inset": + drawable = new InsetDrawable(); + break; + case "bitmap": + drawable = new BitmapDrawable(r); + if (r != null) { + ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics()); + } + break; + case "nine-patch": + drawable = new NinePatchDrawable(); + if (r != null) { + ((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics()); + } + break; + default: + throw new XmlPullParserException(parser.getPositionDescription() + + ": invalid drawable tag " + name); + } drawable.inflate(r, parser, attrs, theme); return drawable; } @@ -1202,7 +1235,7 @@ public abstract class Drawable { * implemented for drawables that can have a theme applied. */ public Drawable newDrawable(Resources res, Theme theme) { - return newDrawable(); + return newDrawable(null); } /** @@ -1212,10 +1245,16 @@ public abstract class Drawable { public abstract int getChangingConfigurations(); /** + * @return Total pixel count * @hide */ - public Bitmap getBitmap() { - return null; + public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { + return 0; + } + + /** @hide */ + protected final boolean isAtlasable(Bitmap bitmap) { + return bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888; } /** diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index 4a719fe..39ef10c 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; @@ -31,6 +32,8 @@ import android.os.SystemClock; import android.util.LayoutDirection; import android.util.SparseArray; +import java.util.Collection; + /** * A helper class that contains several {@link Drawable}s and selects which one to use. * @@ -54,19 +57,20 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { private DrawableContainerState mDrawableContainerState; private Rect mHotspotBounds; private Drawable mCurrDrawable; + private Drawable mLastDrawable; private int mAlpha = 0xFF; /** Whether setAlpha() has been called at least once. */ private boolean mHasAlpha; private int mCurIndex = -1; + private int mLastIndex = -1; private boolean mMutated; // Animations. private Runnable mAnimationRunnable; private long mEnterAnimationEnd; private long mExitAnimationEnd; - private Drawable mLastDrawable; // overrides from Drawable @@ -255,6 +259,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { if (mLastDrawable != null) { mLastDrawable.jumpToCurrentState(); mLastDrawable = null; + mLastIndex = -1; changed = true; } if (mCurrDrawable != null) { @@ -426,9 +431,11 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } if (mCurrDrawable != null) { mLastDrawable = mCurrDrawable; + mLastIndex = mCurIndex; mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration; } else { mLastDrawable = null; + mLastIndex = -1; mExitAnimationEnd = 0; } } else if (mCurrDrawable != null) { @@ -440,36 +447,10 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mCurrDrawable = d; mCurIndex = idx; if (d != null) { - d.mutate(); if (mDrawableContainerState.mEnterFadeDuration > 0) { mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; - } else if (mHasAlpha) { - d.setAlpha(mAlpha); - } - if (mDrawableContainerState.mHasColorFilter) { - // Color filter always overrides tint. - d.setColorFilter(mDrawableContainerState.mColorFilter); - } else { - if (mDrawableContainerState.mHasTintList) { - d.setTintList(mDrawableContainerState.mTintList); - } - if (mDrawableContainerState.mHasTintMode) { - d.setTintMode(mDrawableContainerState.mTintMode); - } - } - d.setVisible(isVisible(), true); - d.setDither(mDrawableContainerState.mDither); - d.setState(getState()); - d.setLevel(getLevel()); - d.setBounds(getBounds()); - d.setLayoutDirection(getLayoutDirection()); - d.setAutoMirrored(mDrawableContainerState.mAutoMirrored); - - final Rect hotspotBounds = mHotspotBounds; - if (hotspotBounds != null) { - d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top, - hotspotBounds.right, hotspotBounds.bottom); } + initializeDrawableForDisplay(d); } } else { mCurrDrawable = null; @@ -496,6 +477,45 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return true; } + /** + * Initializes a drawable for display in this container. + * + * @param d The drawable to initialize. + */ + private void initializeDrawableForDisplay(Drawable d) { + d.mutate(); + + if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) { + d.setAlpha(mAlpha); + } + + if (mDrawableContainerState.mHasColorFilter) { + // Color filter always overrides tint. + d.setColorFilter(mDrawableContainerState.mColorFilter); + } else { + if (mDrawableContainerState.mHasTintList) { + d.setTintList(mDrawableContainerState.mTintList); + } + if (mDrawableContainerState.mHasTintMode) { + d.setTintMode(mDrawableContainerState.mTintMode); + } + } + + d.setVisible(isVisible(), true); + d.setDither(mDrawableContainerState.mDither); + d.setState(getState()); + d.setLevel(getLevel()); + d.setBounds(getBounds()); + d.setLayoutDirection(getLayoutDirection()); + d.setAutoMirrored(mDrawableContainerState.mAutoMirrored); + + final Rect hotspotBounds = mHotspotBounds; + if (hotspotBounds != null) { + d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top, + hotspotBounds.right, hotspotBounds.bottom); + } + } + void animate(boolean schedule) { mHasAlpha = true; @@ -522,6 +542,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { if (mExitAnimationEnd <= now) { mLastDrawable.setVisible(false, false); mLastDrawable = null; + mLastIndex = -1; mExitAnimationEnd = 0; } else { int animAlpha = (int)((mExitAnimationEnd-now)*255) @@ -536,7 +557,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } if (schedule && animating) { - scheduleSelf(mAnimationRunnable, now + 1000/60); + scheduleSelf(mAnimationRunnable, now + 1000 / 60); } } @@ -567,13 +588,34 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { - mDrawableContainerState.mutate(); + final DrawableContainerState clone = cloneConstantState(); + clone.mutate(); + setConstantState(clone); mMutated = true; } return this; } /** + * Returns a shallow copy of the container's constant state to be used as + * the base state for {@link #mutate()}. + * + * @return a shallow copy of the constant state + */ + DrawableContainerState cloneConstantState() { + return mDrawableContainerState; + } + + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mDrawableContainerState.clearMutated(); + mMutated = false; + } + + /** * A ConstantState that can contain several {@link Drawable}s. * * This class was made public to enable testing, and its visibility may change in a future @@ -583,8 +625,6 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { final DrawableContainer mOwner; final Resources mRes; - Theme mTheme; - SparseArray<ConstantStateFuture> mDrawableFutures; int mChangingConfigurations; @@ -593,11 +633,11 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { Drawable[] mDrawables; int mNumChildren; - boolean mVariablePadding; + boolean mVariablePadding = false; boolean mPaddingChecked; Rect mConstantPadding; - boolean mConstantSize; + boolean mConstantSize = false; boolean mComputedConstantSize; int mConstantWidth; int mConstantHeight; @@ -618,8 +658,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { boolean mMutated; int mLayoutDirection; - int mEnterFadeDuration; - int mExitFadeDuration; + int mEnterFadeDuration = 0; + int mExitFadeDuration = 0; boolean mAutoMirrored; @@ -687,10 +727,17 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mDrawableFutures = new SparseArray<ConstantStateFuture>(mNumChildren); } + // Create futures for drawables with constant states. If a + // drawable doesn't have a constant state, then we can't clone + // it and we'll have to reference the original. final int N = mNumChildren; for (int i = 0; i < N; i++) { if (origDr[i] != null) { - mDrawableFutures.put(i, new ConstantStateFuture(origDr[i])); + if (origDr[i].getConstantState() != null) { + mDrawableFutures.put(i, new ConstantStateFuture(origDr[i])); + } else { + mDrawables[i] = origDr[i]; + } } } } else { @@ -792,17 +839,17 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } final void applyTheme(Theme theme) { - // No need to call createAllFutures, since future drawables will - // apply the theme when they are prepared. - final int N = mNumChildren; - final Drawable[] drawables = mDrawables; - for (int i = 0; i < N; i++) { - if (drawables[i] != null) { - drawables[i].applyTheme(theme); + if (theme != null) { + createAllFutures(); + + final int N = mNumChildren; + final Drawable[] drawables = mDrawables; + for (int i = 0; i < N; i++) { + if (drawables[i] != null && drawables[i].canApplyTheme()) { + drawables[i].applyTheme(theme); + } } } - - mTheme = theme; } @Override @@ -826,7 +873,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return false; } - final void mutate() { + private void mutate() { // No need to call createAllFutures, since future drawables will // mutate when they are prepared. final int N = mNumChildren; @@ -840,6 +887,18 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mMutated = true; } + final void clearMutated() { + final int N = mNumChildren; + final Drawable[] drawables = mDrawables; + for (int i = 0; i < N; i++) { + if (drawables[i] != null) { + drawables[i].clearMutated(); + } + } + + mMutated = false; + } + /** * A boolean value indicating whether to use the maximum padding value * of all frames in the set (false), or to use the padding value of the @@ -1026,6 +1085,20 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return true; } + /** @hide */ + @Override + public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { + final int N = mNumChildren; + int pixelCount = 0; + for (int i = 0; i < N; i++) { + final ConstantState state = getChild(i).getConstantState(); + if (state != null) { + pixelCount += state.addAtlasableBitmaps(atlasList); + } + } + return pixelCount; + } + /** * Class capable of cloning a Drawable from another Drawable's * ConstantState. @@ -1047,10 +1120,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { final Drawable result; if (state.mRes == null) { result = mConstantState.newDrawable(); - } else if (state.mTheme == null) { - result = mConstantState.newDrawable(state.mRes); } else { - result = mConstantState.newDrawable(state.mRes, state.mTheme); + result = mConstantState.newDrawable(state.mRes); } result.setLayoutDirection(state.mLayoutDirection); result.setCallback(state.mOwner); @@ -1074,5 +1145,18 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { protected void setConstantState(DrawableContainerState state) { mDrawableContainerState = state; + + // The locally cached drawables may have changed. + if (mCurIndex >= 0) { + mCurrDrawable = state.getChild(mCurIndex); + if (mCurrDrawable != null) { + initializeDrawableForDisplay(mCurrDrawable); + } + } + + // Clear out the last drawable. We don't have enough information to + // propagate local state from the past. + mLastIndex = -1; + mLastDrawable = null; } } diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index cd6297b..cb42397 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -29,6 +29,8 @@ import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.RectF; @@ -134,6 +136,7 @@ public class GradientDrawable extends Drawable { private Rect mPadding; private Paint mStrokePaint; // optional, set by the caller private ColorFilter mColorFilter; // optional, set by the caller + private PorterDuffColorFilter mTintFilter; private int mAlpha = 0xFF; // modified by the caller private final Path mPath = new Path(); @@ -171,7 +174,7 @@ public class GradientDrawable extends Drawable { } public GradientDrawable() { - this(new GradientState(Orientation.TOP_BOTTOM, null), null); + this(new GradientState(Orientation.TOP_BOTTOM, null)); } /** @@ -179,7 +182,7 @@ public class GradientDrawable extends Drawable { * of colors for the gradient. */ public GradientDrawable(Orientation orientation, int[] colors) { - this(new GradientState(orientation, colors), null); + this(new GradientState(orientation, colors)); } @Override @@ -523,13 +526,15 @@ public class GradientDrawable extends Drawable { mStrokePaint.getStrokeWidth() > 0; final boolean haveFill = currFillAlpha > 0; final GradientState st = mGradientState; + final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mTintFilter; + /* we need a layer iff we're drawing both a fill and stroke, and the stroke is non-opaque, and our shapetype actually supports fill+stroke. Otherwise we can just draw the stroke (if any) on top of the fill (if any) without worrying about blending artifacts. */ - final boolean useLayer = haveStroke && haveFill && st.mShape != LINE && - currStrokeAlpha < 255 && (mAlpha < 255 || mColorFilter != null); + final boolean useLayer = haveStroke && haveFill && st.mShape != LINE && + currStrokeAlpha < 255 && (mAlpha < 255 || colorFilter != null); /* Drawing with a layer is slower than direct drawing, but it allows us to apply paint effects like alpha and colorfilter to @@ -544,7 +549,7 @@ public class GradientDrawable extends Drawable { } mLayerPaint.setDither(st.mDither); mLayerPaint.setAlpha(mAlpha); - mLayerPaint.setColorFilter(mColorFilter); + mLayerPaint.setColorFilter(colorFilter); float rad = mStrokePaint.getStrokeWidth(); canvas.saveLayer(mRect.left - rad, mRect.top - rad, @@ -561,14 +566,14 @@ public class GradientDrawable extends Drawable { */ mFillPaint.setAlpha(currFillAlpha); mFillPaint.setDither(st.mDither); - mFillPaint.setColorFilter(mColorFilter); - if (mColorFilter != null && st.mColorStateList == null) { + mFillPaint.setColorFilter(colorFilter); + if (colorFilter != null && st.mColorStateList == null) { mFillPaint.setColor(mAlpha << 24); } if (haveStroke) { mStrokePaint.setAlpha(currStrokeAlpha); mStrokePaint.setDither(st.mDither); - mStrokePaint.setColorFilter(mColorFilter); + mStrokePaint.setColorFilter(colorFilter); } } @@ -593,7 +598,7 @@ public class GradientDrawable extends Drawable { canvas.drawRoundRect(mRect, rad, rad, mStrokePaint); } } else { - if (mFillPaint.getColor() != 0 || mColorFilter != null || + if (mFillPaint.getColor() != 0 || colorFilter != null || mFillPaint.getShader() != null) { canvas.drawRect(mRect, mFillPaint); } @@ -768,6 +773,11 @@ public class GradientDrawable extends Drawable { } } + if (s.mTint != null && s.mTintMode != null) { + mTintFilter = updateTintFilter(mTintFilter, s.mTint, s.mTintMode); + invalidateSelf = true; + } + if (invalidateSelf) { invalidateSelf(); return true; @@ -781,7 +791,8 @@ public class GradientDrawable extends Drawable { final GradientState s = mGradientState; return super.isStateful() || (s.mColorStateList != null && s.mColorStateList.isStateful()) - || (s.mStrokeColorStateList != null && s.mStrokeColorStateList.isStateful()); + || (s.mStrokeColorStateList != null && s.mStrokeColorStateList.isStateful()) + || (s.mTint != null && s.mTint.isStateful()); } @Override @@ -824,6 +835,20 @@ public class GradientDrawable extends Drawable { } @Override + public void setTintList(ColorStateList tint) { + mGradientState.mTint = tint; + mTintFilter = updateTintFilter(mTintFilter, tint, mGradientState.mTintMode); + invalidateSelf(); + } + + @Override + public void setTintMode(PorterDuff.Mode tintMode) { + mGradientState.mTintMode = tintMode; + mTintFilter = updateTintFilter(mTintFilter, mGradientState.mTint, tintMode); + invalidateSelf(); + } + + @Override public int getOpacity() { return (mAlpha == 255 && mGradientState.mOpaqueOverBounds && isOpaqueForState()) ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; @@ -918,7 +943,11 @@ public class GradientDrawable extends Drawable { float radius = st.mGradientRadius; if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) { - radius *= Math.min(st.mWidth, st.mHeight); + // Fall back to parent width or height if intrinsic + // size is not specified. + final float width = st.mWidth >= 0 ? st.mWidth : r.width(); + final float height = st.mHeight >= 0 ? st.mHeight : r.height(); + radius *= Math.min(width, height); } else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) { radius *= Math.min(r.width(), r.height()); } @@ -929,9 +958,9 @@ public class GradientDrawable extends Drawable { mGradientRadius = radius; - if (radius == 0) { - // We can't have a shader with zero radius, so let's - // have a very, very small radius. + if (radius <= 0) { + // We can't have a shader with non-positive radius, so + // let's have a very, very small radius. radius = 0.001f; } @@ -997,13 +1026,16 @@ public class GradientDrawable extends Drawable { super.applyTheme(t); final GradientState state = mGradientState; - if (state == null || state.mThemeAttrs == null) { + if (state == null) { return; } - final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.GradientDrawable); - updateStateFromTypedArray(a); - a.recycle(); + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes( + state.mThemeAttrs, R.styleable.GradientDrawable); + updateStateFromTypedArray(a); + a.recycle(); + } applyThemeChildElements(t); @@ -1045,15 +1077,23 @@ public class GradientDrawable extends Drawable { state.mUseLevelForShape = a.getBoolean( R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape); } + + final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1); + if (tintMode != -1) { + state.mTintMode = Drawable.parseTintMode(tintMode, PorterDuff.Mode.SRC_IN); + } + + final ColorStateList tint = a.getColorStateList(R.styleable.GradientDrawable_tint); + if (tint != null) { + state.mTint = tint; + } + + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); } @Override public boolean canApplyTheme() { - final GradientState st = mGradientState; - return st != null && (st.mThemeAttrs != null || st.mAttrSize != null - || st.mAttrGradient != null || st.mAttrSolid != null - || st.mAttrStroke != null || st.mAttrCorners != null - || st.mAttrPadding != null); + return (mGradientState != null && mGradientState.canApplyTheme()) || super.canApplyTheme(); } private void applyThemeChildElements(Theme t) { @@ -1359,9 +1399,12 @@ public class GradientDrawable extends Drawable { } else { radiusType = RADIUS_TYPE_FRACTION; } - } else { + } else if (tv.type == TypedValue.TYPE_DIMENSION) { radius = tv.getDimension(r.getDisplayMetrics()); radiusType = RADIUS_TYPE_PIXELS; + } else { + radius = tv.getFloat(); + radiusType = RADIUS_TYPE_PIXELS; } st.mGradientRadius = radius; @@ -1479,6 +1522,14 @@ public class GradientDrawable extends Drawable { return this; } + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mMutated = false; + } + final static class GradientState extends ConstantState { public int mChangingConfigurations; public int mShape = RECTANGLE; @@ -1505,14 +1556,18 @@ public class GradientDrawable extends Drawable { public int mThickness = -1; public boolean mDither = false; - private float mCenterX = 0.5f; - private float mCenterY = 0.5f; - private float mGradientRadius = 0.5f; - private int mGradientRadiusType = RADIUS_TYPE_PIXELS; - private boolean mUseLevel; - private boolean mUseLevelForShape; - private boolean mOpaqueOverBounds; - private boolean mOpaqueOverShape; + float mCenterX = 0.5f; + float mCenterY = 0.5f; + float mGradientRadius = 0.5f; + int mGradientRadiusType = RADIUS_TYPE_PIXELS; + boolean mUseLevel = false; + boolean mUseLevelForShape = true; + + boolean mOpaqueOverBounds; + boolean mOpaqueOverShape; + + ColorStateList mTint = null; + PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; int[] mThemeAttrs; int[] mAttrSize; @@ -1566,6 +1621,8 @@ public class GradientDrawable extends Drawable { mUseLevelForShape = state.mUseLevelForShape; mOpaqueOverBounds = state.mOpaqueOverBounds; mOpaqueOverShape = state.mOpaqueOverShape; + mTint = state.mTint; + mTintMode = state.mTintMode; mThemeAttrs = state.mThemeAttrs; mAttrSize = state.mAttrSize; mAttrGradient = state.mAttrGradient; @@ -1577,22 +1634,19 @@ public class GradientDrawable extends Drawable { @Override public boolean canApplyTheme() { - return mThemeAttrs != null; + return mThemeAttrs != null || mAttrSize != null || mAttrGradient != null + || mAttrSolid != null || mAttrStroke != null || mAttrCorners != null + || mAttrPadding != null || super.canApplyTheme(); } @Override public Drawable newDrawable() { - return new GradientDrawable(this, null); + return new GradientDrawable(this); } @Override public Drawable newDrawable(Resources res) { - return new GradientDrawable(this, null); - } - - @Override - public Drawable newDrawable(Resources res, Theme theme) { - return new GradientDrawable(this, theme); + return new GradientDrawable(this); } @Override @@ -1696,18 +1750,11 @@ public class GradientDrawable extends Drawable { * The resulting drawable is guaranteed to have a new constant state. * * @param state Constant state from which the drawable inherits - * @param theme Theme to apply to the drawable */ - private GradientDrawable(GradientState state, Theme theme) { - if (theme != null && state.canApplyTheme()) { - // If we need to apply a theme, implicitly mutate. - mGradientState = new GradientState(state); - applyTheme(theme); - } else { - mGradientState = state; - } + private GradientDrawable(GradientState state) { + mGradientState = state; - initializeWithState(state); + initializeWithState(mGradientState); mGradientIsDirty = true; mMutated = false; @@ -1748,5 +1795,7 @@ public class GradientDrawable extends Drawable { mStrokePaint.setPathEffect(e); } } + + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); } } diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index 961d160..8b70a08 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -19,6 +19,7 @@ package android.graphics.drawable; import com.android.internal.R; import android.annotation.NonNull; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -26,15 +27,19 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.Outline; +import android.graphics.PixelFormat; import android.graphics.PorterDuff.Mode; +import android.graphics.drawable.Drawable.ConstantState; import android.graphics.Rect; import android.util.AttributeSet; import java.io.IOException; +import java.util.Collection; /** * A Drawable that insets another Drawable by a specified distance. @@ -55,7 +60,8 @@ import java.io.IOException; public class InsetDrawable extends Drawable implements Drawable.Callback { private final Rect mTmpRect = new Rect(); - private InsetState mInsetState; + private final InsetState mState; + private boolean mMutated; /*package*/ InsetDrawable() { @@ -70,11 +76,11 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { int insetRight, int insetBottom) { this(null, null); - mInsetState.mDrawable = drawable; - mInsetState.mInsetLeft = insetLeft; - mInsetState.mInsetTop = insetTop; - mInsetState.mInsetRight = insetRight; - mInsetState.mInsetBottom = insetBottom; + mState.mDrawable = drawable; + mState.mInsetLeft = insetLeft; + mState.mInsetTop = insetTop; + mState.mInsetRight = insetRight; + mState.mInsetBottom = insetBottom; if (drawable != null) { drawable.setCallback(this); @@ -87,11 +93,20 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable); super.inflateWithAttributes(r, parser, a, R.styleable.InsetDrawable_visible); - mInsetState.mDrawable = null; + // Reset mDrawable to preserve old multiple-inflate behavior. This is + // silly, but we have CTS tests that rely on it. + mState.mDrawable = null; + updateStateFromTypedArray(a); + inflateChildElements(r, parser, attrs, theme); + verifyRequiredAttributes(a); + a.recycle(); + } + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { // Load inner XML elements. - if (mInsetState.mDrawable == null) { + if (mState.mDrawable == null) { int type; while ((type=parser.next()) == XmlPullParser.TEXT) { } @@ -102,26 +117,23 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { + "child tag defining a drawable"); } final Drawable dr = Drawable.createFromXmlInner(r, parser, attrs, theme); - mInsetState.mDrawable = dr; + mState.mDrawable = dr; dr.setCallback(this); } - - verifyRequiredAttributes(a); - a.recycle(); } private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { // If we're not waiting on a theme, verify required attributes. - if (mInsetState.mDrawable == null && (mInsetState.mThemeAttrs == null - || mInsetState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { - throw new XmlPullParserException(a.getPositionDescription() + - ": <inset> tag requires a 'drawable' attribute or " + if (mState.mDrawable == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <inset> tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } } private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { - final InsetState state = mInsetState; + final InsetState state = mState; // Account for any configuration changes. state.mChangingConfigurations |= a.getChangingConfigurations(); @@ -169,25 +181,31 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { public void applyTheme(Theme t) { super.applyTheme(t); - final InsetState state = mInsetState; - if (state == null || state.mThemeAttrs == null) { + final InsetState state = mState; + if (state == null) { return; } - final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable); - try { - updateStateFromTypedArray(a); - verifyRequiredAttributes(a); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } finally { - a.recycle(); + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } + } + + if (state.mDrawable != null && state.mDrawable.canApplyTheme()) { + state.mDrawable.applyTheme(t); } } @Override public boolean canApplyTheme() { - return mInsetState != null && mInsetState.mThemeAttrs != null; + return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); } @Override @@ -216,112 +234,118 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { @Override public void draw(Canvas canvas) { - mInsetState.mDrawable.draw(canvas); + mState.mDrawable.draw(canvas); } @Override public int getChangingConfigurations() { return super.getChangingConfigurations() - | mInsetState.mChangingConfigurations - | mInsetState.mDrawable.getChangingConfigurations(); + | mState.mChangingConfigurations + | mState.mDrawable.getChangingConfigurations(); } @Override public boolean getPadding(Rect padding) { - boolean pad = mInsetState.mDrawable.getPadding(padding); + boolean pad = mState.mDrawable.getPadding(padding); - padding.left += mInsetState.mInsetLeft; - padding.right += mInsetState.mInsetRight; - padding.top += mInsetState.mInsetTop; - padding.bottom += mInsetState.mInsetBottom; + padding.left += mState.mInsetLeft; + padding.right += mState.mInsetRight; + padding.top += mState.mInsetTop; + padding.bottom += mState.mInsetBottom; - return pad || (mInsetState.mInsetLeft | mInsetState.mInsetRight | - mInsetState.mInsetTop | mInsetState.mInsetBottom) != 0; + return pad || (mState.mInsetLeft | mState.mInsetRight | + mState.mInsetTop | mState.mInsetBottom) != 0; } /** @hide */ @Override public Insets getOpticalInsets() { final Insets contentInsets = super.getOpticalInsets(); - return Insets.of(contentInsets.left + mInsetState.mInsetLeft, - contentInsets.top + mInsetState.mInsetTop, - contentInsets.right + mInsetState.mInsetRight, - contentInsets.bottom + mInsetState.mInsetBottom); + return Insets.of(contentInsets.left + mState.mInsetLeft, + contentInsets.top + mState.mInsetTop, + contentInsets.right + mState.mInsetRight, + contentInsets.bottom + mState.mInsetBottom); } @Override public void setHotspot(float x, float y) { - mInsetState.mDrawable.setHotspot(x, y); + mState.mDrawable.setHotspot(x, y); } @Override public void setHotspotBounds(int left, int top, int right, int bottom) { - mInsetState.mDrawable.setHotspotBounds(left, top, right, bottom); + mState.mDrawable.setHotspotBounds(left, top, right, bottom); } /** @hide */ @Override public void getHotspotBounds(Rect outRect) { - mInsetState.mDrawable.getHotspotBounds(outRect); + mState.mDrawable.getHotspotBounds(outRect); } @Override public boolean setVisible(boolean visible, boolean restart) { - mInsetState.mDrawable.setVisible(visible, restart); + mState.mDrawable.setVisible(visible, restart); return super.setVisible(visible, restart); } @Override public void setAlpha(int alpha) { - mInsetState.mDrawable.setAlpha(alpha); + mState.mDrawable.setAlpha(alpha); } @Override public int getAlpha() { - return mInsetState.mDrawable.getAlpha(); + return mState.mDrawable.getAlpha(); } @Override public void setColorFilter(ColorFilter cf) { - mInsetState.mDrawable.setColorFilter(cf); + mState.mDrawable.setColorFilter(cf); } @Override public void setTintList(ColorStateList tint) { - mInsetState.mDrawable.setTintList(tint); + mState.mDrawable.setTintList(tint); } @Override public void setTintMode(Mode tintMode) { - mInsetState.mDrawable.setTintMode(tintMode); + mState.mDrawable.setTintMode(tintMode); } /** {@hide} */ @Override public void setLayoutDirection(int layoutDirection) { - mInsetState.mDrawable.setLayoutDirection(layoutDirection); + mState.mDrawable.setLayoutDirection(layoutDirection); } @Override public int getOpacity() { - return mInsetState.mDrawable.getOpacity(); + final InsetState state = mState; + final int opacity = state.mDrawable.getOpacity(); + if (opacity == PixelFormat.OPAQUE && (state.mInsetLeft > 0 || state.mInsetTop > 0 + || state.mInsetRight > 0 || state.mInsetBottom > 0)) { + return PixelFormat.TRANSLUCENT; + } + return opacity; } @Override public boolean isStateful() { - return mInsetState.mDrawable.isStateful(); + return mState.mDrawable.isStateful(); } @Override protected boolean onStateChange(int[] state) { - boolean changed = mInsetState.mDrawable.setState(state); + boolean changed = mState.mDrawable.setState(state); onBoundsChange(getBounds()); return changed; } @Override protected boolean onLevelChange(int level) { - return mInsetState.mDrawable.setLevel(level); + return mState.mDrawable.setLevel(level); } @Override @@ -329,34 +353,36 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { final Rect r = mTmpRect; r.set(bounds); - r.left += mInsetState.mInsetLeft; - r.top += mInsetState.mInsetTop; - r.right -= mInsetState.mInsetRight; - r.bottom -= mInsetState.mInsetBottom; + r.left += mState.mInsetLeft; + r.top += mState.mInsetTop; + r.right -= mState.mInsetRight; + r.bottom -= mState.mInsetBottom; - mInsetState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom); + mState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom); } @Override public int getIntrinsicWidth() { - return mInsetState.mDrawable.getIntrinsicWidth(); + return mState.mDrawable.getIntrinsicWidth() + + mState.mInsetLeft + mState.mInsetRight; } @Override public int getIntrinsicHeight() { - return mInsetState.mDrawable.getIntrinsicHeight(); + return mState.mDrawable.getIntrinsicHeight() + + mState.mInsetTop + mState.mInsetBottom; } @Override public void getOutline(@NonNull Outline outline) { - mInsetState.mDrawable.getOutline(outline); + mState.mDrawable.getOutline(outline); } @Override public ConstantState getConstantState() { - if (mInsetState.canConstantState()) { - mInsetState.mChangingConfigurations = getChangingConfigurations(); - return mInsetState; + if (mState.canConstantState()) { + mState.mChangingConfigurations = getChangingConfigurations(); + return mState; } return null; } @@ -364,17 +390,26 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { - mInsetState.mDrawable.mutate(); + mState.mDrawable.mutate(); mMutated = true; } return this; } /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mState.mDrawable.clearMutated(); + mMutated = false; + } + + /** * Returns the drawable wrapped by this InsetDrawable. May be null. */ public Drawable getDrawable() { - return mInsetState.mDrawable; + return mState.mDrawable; } final static class InsetState extends ConstantState { @@ -383,13 +418,13 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { Drawable mDrawable; - int mInsetLeft; - int mInsetTop; - int mInsetRight; - int mInsetBottom; + int mInsetLeft = 0; + int mInsetTop = 0; + int mInsetRight = 0; + int mInsetBottom = 0; - boolean mCheckedConstantState; - boolean mCanConstantState; + private boolean mCheckedConstantState; + private boolean mCanConstantState; InsetState(InsetState orig, InsetDrawable owner, Resources res) { if (orig != null) { @@ -413,6 +448,12 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme()) + || super.canApplyTheme(); + } + + @Override public Drawable newDrawable() { return new InsetDrawable(this, null); } @@ -435,10 +476,19 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { return mCanConstantState; } + + @Override + public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { + final ConstantState state = mDrawable.getConstantState(); + if (state != null) { + return state.addAtlasableBitmaps(atlasList); + } + return 0; + } } private InsetDrawable(InsetState state, Resources res) { - mInsetState = new InsetState(state, this, res); + mState = new InsetState(state, this, res); } } diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 001ed88..4aa5f59 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -21,6 +21,7 @@ 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; import android.graphics.ColorFilter; import android.graphics.Outline; @@ -36,6 +37,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.Collection; /** * A Drawable that manages an array of other Drawables. These are drawn in array @@ -100,10 +102,10 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { * @param state The constant drawable state. */ LayerDrawable(Drawable[] layers, LayerState state) { - this(state, null, null); - int length = layers.length; - ChildDrawable[] r = new ChildDrawable[length]; + this(state, null); + final int length = layers.length; + final ChildDrawable[] r = new ChildDrawable[length]; for (int i = 0; i < length; i++) { r[i] = new ChildDrawable(); r[i].mDrawable = layers[i]; @@ -117,18 +119,14 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } LayerDrawable() { - this((LayerState) null, null, null); + this((LayerState) null, null); } - LayerDrawable(LayerState state, Resources res, Theme theme) { - final LayerState as = createConstantState(state, res); - mLayerState = as; - if (as.mNum > 0) { + LayerDrawable(LayerState state, Resources res) { + mLayerState = createConstantState(state, res); + if (mLayerState.mNum > 0) { ensurePadding(); } - if (theme != null && canApplyTheme()) { - applyTheme(theme); - } } LayerState createConstantState(LayerState state, Resources res) { @@ -256,8 +254,8 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { a.recycle(); } - final ChildDrawable[] array = mLayerState.mChildren; - final int N = mLayerState.mNum; + final ChildDrawable[] array = state.mChildren; + final int N = state.mNum; for (int i = 0; i < N; i++) { final ChildDrawable layer = array[i]; if (layer.mThemeAttrs != null) { @@ -279,25 +277,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { @Override public boolean canApplyTheme() { - final LayerState state = mLayerState; - if (state == null) { - return false; - } - - if (state.mThemeAttrs != null) { - return true; - } - - final ChildDrawable[] array = state.mChildren; - final int N = state.mNum; - for (int i = 0; i < N; i++) { - final ChildDrawable layer = array[i]; - if (layer.mThemeAttrs != null || layer.mDrawable.canApplyTheme()) { - return true; - } - } - - return false; + return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme(); } /** @@ -940,6 +920,19 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { return this; } + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + final ChildDrawable[] array = mLayerState.mChildren; + final int N = mLayerState.mNum; + for (int i = 0; i < N; i++) { + array[i].mDrawable.clearMutated(); + } + mMutated = false; + } + /** @hide */ @Override public void setLayoutDirection(int layoutDirection) { @@ -1029,22 +1022,30 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { @Override public boolean canApplyTheme() { - return mThemeAttrs != null; + if (mThemeAttrs != null || super.canApplyTheme()) { + return true; + } + + final ChildDrawable[] array = mChildren; + final int N = mNum; + for (int i = 0; i < N; i++) { + final ChildDrawable layer = array[i]; + if (layer.mThemeAttrs != null || layer.mDrawable.canApplyTheme()) { + return true; + } + } + + return false; } @Override public Drawable newDrawable() { - return new LayerDrawable(this, null, null); + return new LayerDrawable(this, null); } @Override public Drawable newDrawable(Resources res) { - return new LayerDrawable(this, res, null); - } - - @Override - public Drawable newDrawable(Resources res, Theme theme) { - return new LayerDrawable(this, res, theme); + return new LayerDrawable(this, res); } @Override @@ -1106,6 +1107,20 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { mHaveOpacity = false; mHaveIsStateful = false; } + + @Override + public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { + final ChildDrawable[] array = mChildren; + final int N = mNum; + int pixelCount = 0; + for (int i = 0; i < N; i++) { + final ConstantState state = array[i].mDrawable.getConstantState(); + if (state != null) { + pixelCount += state.addAtlasableBitmaps(atlasList); + } + } + return pixelCount; + } } } diff --git a/graphics/java/android/graphics/drawable/LevelListDrawable.java b/graphics/java/android/graphics/drawable/LevelListDrawable.java index bc1c61d..b01c643 100644 --- a/graphics/java/android/graphics/drawable/LevelListDrawable.java +++ b/graphics/java/android/graphics/drawable/LevelListDrawable.java @@ -21,6 +21,7 @@ import java.io.IOException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.NonNull; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; @@ -57,7 +58,7 @@ import android.util.AttributeSet; * @attr ref android.R.styleable#LevelListDrawableItem_drawable */ public class LevelListDrawable extends DrawableContainer { - private final LevelListState mLevelListState; + private LevelListState mLevelListState; private boolean mMutated; public LevelListDrawable() { @@ -146,13 +147,25 @@ public class LevelListDrawable extends DrawableContainer { @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { - mLevelListState.mLows = mLevelListState.mLows.clone(); - mLevelListState.mHighs = mLevelListState.mHighs.clone(); + mLevelListState.mutate(); mMutated = true; } return this; } + @Override + LevelListState cloneConstantState() { + return new LevelListState(mLevelListState, this, null); + } + + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mMutated = false; + } + private final static class LevelListState extends DrawableContainerState { private int[] mLows; private int[] mHighs; @@ -161,6 +174,7 @@ public class LevelListDrawable extends DrawableContainer { super(orig, owner, res); if (orig != null) { + // Perform a shallow copy and rely on mutate() to deep-copy. mLows = orig.mLows; mHighs = orig.mHighs; } else { @@ -169,6 +183,11 @@ public class LevelListDrawable extends DrawableContainer { } } + private void mutate() { + mLows = mLows.clone(); + mHighs = mHighs.clone(); + } + public void addLevel(int low, int high, Drawable drawable) { int pos = addChild(drawable); mLows[pos] = low; @@ -209,9 +228,17 @@ public class LevelListDrawable extends DrawableContainer { } } + @Override + protected void setConstantState(@NonNull DrawableContainerState state) { + super.setConstantState(state); + + if (state instanceof LevelListState) { + mLevelListState = (LevelListState) state; + } + } + private LevelListDrawable(LevelListState state, Resources res) { - LevelListState as = new LevelListState(state, this, res); - mLevelListState = as; + final LevelListState as = new LevelListState(state, this, res); setConstantState(as); onLevelChange(getLevel()); } diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index 6c62ccf..b87ae92 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -48,6 +48,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; /** * @@ -90,7 +91,7 @@ public class NinePatchDrawable extends Drawable { */ @Deprecated public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) { - this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null, null); + this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null); } /** @@ -99,7 +100,7 @@ public class NinePatchDrawable extends Drawable { */ public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, String srcName) { - this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res, null); + this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res); mNinePatchState.mTargetDensity = mTargetDensity; } @@ -112,7 +113,7 @@ public class NinePatchDrawable extends Drawable { public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, Rect opticalInsets, String srcName) { this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, opticalInsets), - res, null); + res); mNinePatchState.mTargetDensity = mTargetDensity; } @@ -123,7 +124,7 @@ public class NinePatchDrawable extends Drawable { */ @Deprecated public NinePatchDrawable(NinePatch patch) { - this(new NinePatchState(patch, new Rect()), null, null); + this(new NinePatchState(patch, new Rect()), null); } /** @@ -131,7 +132,7 @@ public class NinePatchDrawable extends Drawable { * based on the display metrics of the resources. */ public NinePatchDrawable(Resources res, NinePatch patch) { - this(new NinePatchState(patch, new Rect()), res, null); + this(new NinePatchState(patch, new Rect()), res); mNinePatchState.mTargetDensity = mTargetDensity; } @@ -289,7 +290,7 @@ public class NinePatchDrawable extends Drawable { if (bounds.isEmpty()) return; if (mNinePatchState != null) { - NinePatch.InsetStruct insets = mNinePatchState.getBitmap().getNinePatchInsets(); + NinePatch.InsetStruct insets = mNinePatchState.mNinePatch.getBitmap().getNinePatchInsets(); if (insets != null) { final Rect outlineInsets = insets.outlineRect; outline.setRoundRect(bounds.left + outlineInsets.left, @@ -563,6 +564,14 @@ public class NinePatchDrawable extends Drawable { return this; } + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mMutated = false; + } + @Override protected boolean onStateChange(int[] stateSet) { final NinePatchState state = mNinePatchState; @@ -640,23 +649,22 @@ public class NinePatchDrawable extends Drawable { } @Override - public Bitmap getBitmap() { - return mNinePatch.getBitmap(); + public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { + final Bitmap bitmap = mNinePatch.getBitmap(); + if (isAtlasable(bitmap) && atlasList.add(bitmap)) { + return bitmap.getWidth() * bitmap.getHeight(); + } + return 0; } @Override public Drawable newDrawable() { - return new NinePatchDrawable(this, null, null); + return new NinePatchDrawable(this, null); } @Override public Drawable newDrawable(Resources res) { - return new NinePatchDrawable(this, res, null); - } - - @Override - public Drawable newDrawable(Resources res, Theme theme) { - return new NinePatchDrawable(this, res, theme); + return new NinePatchDrawable(this, res); } @Override @@ -669,16 +677,10 @@ public class NinePatchDrawable extends Drawable { * The one constructor to rule them all. This is called by all public * constructors to set the state and initialize local properties. */ - private NinePatchDrawable(NinePatchState state, Resources res, Theme theme) { - if (theme != null && state.canApplyTheme()) { - // If we need to apply a theme, implicitly mutate. - mNinePatchState = new NinePatchState(state); - applyTheme(theme); - } else { - mNinePatchState = state; - } + private NinePatchDrawable(NinePatchState state, Resources res) { + mNinePatchState = state; - initializeWithState(state, res); + initializeWithState(mNinePatchState, res); } /** diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java index 864e119..bb1d3cb 100644 --- a/graphics/java/android/graphics/drawable/Ripple.java +++ b/graphics/java/android/graphics/drawable/Ripple.java @@ -22,9 +22,7 @@ import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.graphics.Canvas; import android.graphics.CanvasProperty; -import android.graphics.Color; import android.graphics.Paint; -import android.graphics.Paint.Style; import android.graphics.Rect; import android.util.MathUtils; import android.view.HardwareCanvas; @@ -50,17 +48,12 @@ class Ripple { // Hardware animators. private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<RenderNodeAnimator>(); - private final ArrayList<RenderNodeAnimator> mPendingAnimations = - new ArrayList<RenderNodeAnimator>(); private final RippleDrawable mOwner; /** Bounds used for computing max radius. */ private final Rect mBounds; - /** Full-opacity color for drawing this ripple. */ - private int mColorOpaque; - /** Maximum ripple radius. */ private float mOuterRadius; @@ -109,6 +102,10 @@ class Ripple { /** Whether we were canceled externally and should avoid self-removal. */ private boolean mCanceled; + private boolean mHasPendingHardwareExit; + private int mPendingRadiusDuration; + private int mPendingOpacityDuration; + /** * Creates a new ripple. */ @@ -120,9 +117,7 @@ class Ripple { mStartingY = startingY; } - public void setup(int maxRadius, int color, float density) { - mColorOpaque = color | 0xFF000000; - + public void setup(int maxRadius, float density) { if (maxRadius != RippleDrawable.RADIUS_AUTO) { mHasMaxRadius = true; mOuterRadius = maxRadius; @@ -224,8 +219,8 @@ class Ripple { mCanUseHardware = canUseHardware; final boolean hasContent; - if (canUseHardware && mHardwareAnimating) { - hasContent = drawHardware((HardwareCanvas) c); + if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) { + hasContent = drawHardware((HardwareCanvas) c, p); } else { hasContent = drawSoftware(c, p); } @@ -233,24 +228,10 @@ class Ripple { return hasContent; } - private boolean drawHardware(HardwareCanvas c) { - // If we have any pending hardware animations, cancel any running - // animations and start those now. - final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations; - final int N = pendingAnimations.size(); - if (N > 0) { + private boolean drawHardware(HardwareCanvas c, Paint p) { + if (mHasPendingHardwareExit) { cancelHardwareAnimations(false); - - // We canceled old animations, but we're about to run new ones. - mHardwareAnimating = true; - - for (int i = 0; i < N; i++) { - pendingAnimations.get(i).setTarget(c); - pendingAnimations.get(i).start(); - } - - mRunningAnimations.addAll(pendingAnimations); - pendingAnimations.clear(); + startPendingHardwareExit(c, p); } c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint); @@ -261,8 +242,8 @@ class Ripple { private boolean drawSoftware(Canvas c, Paint p) { boolean hasContent = false; - p.setColor(mColorOpaque); - final int alpha = (int) (255 * mOpacity + 0.5f); + final int paintAlpha = p.getAlpha(); + final int alpha = (int) (paintAlpha * mOpacity + 0.5f); final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); if (alpha > 0 && radius > 0) { final float x = MathUtils.lerp( @@ -270,8 +251,8 @@ class Ripple { final float y = MathUtils.lerp( mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY); p.setAlpha(alpha); - p.setStyle(Style.FILL); c.drawCircle(x, y, radius, p); + p.setAlpha(paintAlpha); hasContent = true; } @@ -342,8 +323,6 @@ class Ripple { * Starts the exit animation. */ public void exit() { - cancel(); - final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); final float remaining; if (mAnimRadius != null && mAnimRadius.isRunning()) { @@ -352,19 +331,33 @@ class Ripple { remaining = mOuterRadius; } + cancel(); + final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5); final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f); if (mCanUseHardware) { - exitHardware(radiusDuration, opacityDuration); + createPendingHardwareExit(radiusDuration, opacityDuration); } else { exitSoftware(radiusDuration, opacityDuration); } } - private void exitHardware(int radiusDuration, int opacityDuration) { - mPendingAnimations.clear(); + private void createPendingHardwareExit(int radiusDuration, int opacityDuration) { + mHasPendingHardwareExit = true; + mPendingRadiusDuration = radiusDuration; + mPendingOpacityDuration = opacityDuration; + + // The animation will start on the next draw(). + invalidateSelf(); + } + + private void startPendingHardwareExit(HardwareCanvas c, Paint p) { + mHasPendingHardwareExit = false; + + final int radiusDuration = mPendingRadiusDuration; + final int opacityDuration = mPendingOpacityDuration; final float startX = MathUtils.lerp( mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX); @@ -372,11 +365,8 @@ class Ripple { mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY); final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); - final Paint paint = getTempPaint(); - paint.setAntiAlias(true); - paint.setColor(mColorOpaque); - paint.setAlpha((int) (255 * mOpacity + 0.5f)); - paint.setStyle(Style.FILL); + final Paint paint = getTempPaint(p); + paint.setAlpha((int) (paint.getAlpha() * mOpacity + 0.5f)); mPropPaint = CanvasProperty.createPaint(paint); mPropRadius = CanvasProperty.createFloat(startRadius); mPropX = CanvasProperty.createFloat(startX); @@ -385,25 +375,33 @@ class Ripple { final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius); radiusAnim.setDuration(radiusDuration); radiusAnim.setInterpolator(DECEL_INTERPOLATOR); + radiusAnim.setTarget(c); + radiusAnim.start(); final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX); xAnim.setDuration(radiusDuration); xAnim.setInterpolator(DECEL_INTERPOLATOR); + xAnim.setTarget(c); + xAnim.start(); final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY); yAnim.setDuration(radiusDuration); yAnim.setInterpolator(DECEL_INTERPOLATOR); + yAnim.setTarget(c); + yAnim.start(); final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint, RenderNodeAnimator.PAINT_ALPHA, 0); opacityAnim.setDuration(opacityDuration); opacityAnim.setInterpolator(LINEAR_INTERPOLATOR); opacityAnim.addListener(mAnimationListener); + opacityAnim.setTarget(c); + opacityAnim.start(); - mPendingAnimations.add(radiusAnim); - mPendingAnimations.add(opacityAnim); - mPendingAnimations.add(xAnim); - mPendingAnimations.add(yAnim); + mRunningAnimations.add(radiusAnim); + mRunningAnimations.add(opacityAnim); + mRunningAnimations.add(xAnim); + mRunningAnimations.add(yAnim); mHardwareAnimating = true; @@ -412,8 +410,6 @@ class Ripple { mTweenX = 1; mTweenY = 1; mTweenRadius = 1; - - invalidateSelf(); } /** @@ -449,10 +445,11 @@ class Ripple { } } - private Paint getTempPaint() { + private Paint getTempPaint(Paint original) { if (mTempPaint == null) { mTempPaint = new Paint(); } + mTempPaint.set(original); return mTempPaint; } @@ -496,7 +493,7 @@ class Ripple { public void cancel() { mCanceled = true; cancelSoftwareAnimations(); - cancelHardwareAnimations(true); + cancelHardwareAnimations(false); mCanceled = false; } @@ -525,16 +522,28 @@ class Ripple { /** * Cancels any running hardware animations. */ - private void cancelHardwareAnimations(boolean cancelPending) { + private void cancelHardwareAnimations(boolean jumpToEnd) { final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations; final int N = runningAnimations.size(); for (int i = 0; i < N; i++) { - runningAnimations.get(i).cancel(); + if (jumpToEnd) { + runningAnimations.get(i).end(); + } else { + runningAnimations.get(i).cancel(); + } } runningAnimations.clear(); - if (cancelPending && !mPendingAnimations.isEmpty()) { - mPendingAnimations.clear(); + if (mHasPendingHardwareExit) { + // If we had a pending hardware exit, jump to the end state. + mHasPendingHardwareExit = false; + + if (jumpToEnd) { + mOpacity = 0; + mTweenX = 1; + mTweenY = 1; + mTweenRadius = 1; + } } mHardwareAnimating = false; diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java index faa89bf..fae4902 100644 --- a/graphics/java/android/graphics/drawable/RippleBackground.java +++ b/graphics/java/android/graphics/drawable/RippleBackground.java @@ -24,7 +24,6 @@ import android.graphics.Canvas; import android.graphics.CanvasProperty; import android.graphics.Color; import android.graphics.Paint; -import android.graphics.Paint.Style; import android.graphics.Rect; import android.util.MathUtils; import android.view.HardwareCanvas; @@ -43,26 +42,23 @@ class RippleBackground { private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED; private static final float WAVE_OUTER_OPACITY_EXIT_VELOCITY_MAX = 4.5f * GLOBAL_SPEED; private static final float WAVE_OUTER_OPACITY_EXIT_VELOCITY_MIN = 1.5f * GLOBAL_SPEED; - private static final float WAVE_OUTER_OPACITY_ENTER_VELOCITY = 10.0f * GLOBAL_SPEED; private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f; private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f; + private static final int ENTER_DURATION = 667; + private static final int ENTER_DURATION_FAST = 100; + // Hardware animators. private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<RenderNodeAnimator>(); - private final ArrayList<RenderNodeAnimator> mPendingAnimations = - new ArrayList<RenderNodeAnimator>(); private final RippleDrawable mOwner; /** Bounds used for computing max radius. */ private final Rect mBounds; - /** Full-opacity color for drawing this ripple. */ - private int mColorOpaque; - - /** Maximum alpha value for drawing this ripple. */ - private int mColorAlpha; + /** ARGB color for drawing this ripple. */ + private int mColor; /** Maximum ripple radius. */ private float mOuterRadius; @@ -96,6 +92,11 @@ class RippleBackground { /** Whether we have an explicit maximum radius. */ private boolean mHasMaxRadius; + private boolean mHasPendingHardwareExit; + private int mPendingOpacityDuration; + private int mPendingInflectionDuration; + private int mPendingInflectionOpacity; + /** * Creates a new ripple. */ @@ -104,10 +105,7 @@ class RippleBackground { mBounds = bounds; } - public void setup(int maxRadius, int color, float density) { - mColorOpaque = color | 0xFF000000; - mColorAlpha = Color.alpha(color) / 2; - + public void setup(int maxRadius, float density) { if (maxRadius != RippleDrawable.RADIUS_AUTO) { mHasMaxRadius = true; mOuterRadius = maxRadius; @@ -122,10 +120,6 @@ class RippleBackground { mDensity = density; } - public boolean isHardwareAnimating() { - return mHardwareAnimating; - } - public void onHotspotBoundsChanged() { if (!mHasMaxRadius) { final float halfWidth = mBounds.width() / 2.0f; @@ -149,6 +143,8 @@ class RippleBackground { * Draws the ripple centered at (0,0) using the specified paint. */ public boolean draw(Canvas c, Paint p) { + mColor = p.getColor(); + final boolean canUseHardware = c.isHardwareAccelerated(); if (mCanUseHardware != canUseHardware && mCanUseHardware) { // We've switched from hardware to non-hardware mode. Panic. @@ -157,8 +153,8 @@ class RippleBackground { mCanUseHardware = canUseHardware; final boolean hasContent; - if (canUseHardware && mHardwareAnimating) { - hasContent = drawHardware((HardwareCanvas) c); + if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) { + hasContent = drawHardware((HardwareCanvas) c, p); } else { hasContent = drawSoftware(c, p); } @@ -167,28 +163,13 @@ class RippleBackground { } public boolean shouldDraw() { - final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f); - return mCanUseHardware && mHardwareAnimating || outerAlpha > 0 && mOuterRadius > 0; + return (mCanUseHardware && mHardwareAnimating) || (mOuterOpacity > 0 && mOuterRadius > 0); } - private boolean drawHardware(HardwareCanvas c) { - // If we have any pending hardware animations, cancel any running - // animations and start those now. - final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations; - final int N = pendingAnimations.size(); - if (N > 0) { + private boolean drawHardware(HardwareCanvas c, Paint p) { + if (mHasPendingHardwareExit) { cancelHardwareAnimations(false); - - // We canceled old animations, but we're about to run new ones. - mHardwareAnimating = true; - - for (int i = 0; i < N; i++) { - pendingAnimations.get(i).setTarget(c); - pendingAnimations.get(i).start(); - } - - mRunningAnimations.addAll(pendingAnimations); - pendingAnimations.clear(); + startPendingHardwareExit(c, p); } c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint); @@ -199,12 +180,13 @@ class RippleBackground { private boolean drawSoftware(Canvas c, Paint p) { boolean hasContent = false; - p.setColor(mColorOpaque); - final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f); - if (outerAlpha > 0 && mOuterRadius > 0) { - p.setAlpha(outerAlpha); - p.setStyle(Style.FILL); - c.drawCircle(mOuterX, mOuterY, mOuterRadius, p); + final int paintAlpha = p.getAlpha(); + final int alpha = (int) (paintAlpha * mOuterOpacity + 0.5f); + final float radius = mOuterRadius; + if (alpha > 0 && radius > 0) { + p.setAlpha(alpha); + c.drawCircle(mOuterX, mOuterY, radius, p); + p.setAlpha(paintAlpha); hasContent = true; } @@ -224,21 +206,20 @@ class RippleBackground { /** * Starts the enter animation. */ - public void enter() { + public void enter(boolean fast) { cancel(); - final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_ENTER_VELOCITY); - final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1); - outer.setAutoCancel(true); - outer.setDuration(outerDuration); - outer.setInterpolator(LINEAR_INTERPOLATOR); + final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1); + opacity.setAutoCancel(true); + opacity.setDuration(fast ? ENTER_DURATION_FAST : ENTER_DURATION); + opacity.setInterpolator(LINEAR_INTERPOLATOR); - mAnimOuterOpacity = outer; + mAnimOuterOpacity = opacity; // Enter animations always run on the UI thread, since it's unlikely // that anything interesting is happening until the user lifts their // finger. - outer.start(); + opacity.start(); } /** @@ -261,24 +242,36 @@ class RippleBackground { // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000 final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity) / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f)); - final int inflectionOpacity = (int) (mColorAlpha * (mOuterOpacity + final int inflectionOpacity = (int) (Color.alpha(mColor) * (mOuterOpacity + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f); if (mCanUseHardware) { - exitHardware(opacityDuration, inflectionDuration, inflectionOpacity); + createPendingHardwareExit(opacityDuration, inflectionDuration, inflectionOpacity); } else { exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity); } } - private void exitHardware(int opacityDuration, int inflectionDuration, int inflectionOpacity) { - mPendingAnimations.clear(); + private void createPendingHardwareExit( + int opacityDuration, int inflectionDuration, int inflectionOpacity) { + mHasPendingHardwareExit = true; + mPendingOpacityDuration = opacityDuration; + mPendingInflectionDuration = inflectionDuration; + mPendingInflectionOpacity = inflectionOpacity; + + // The animation will start on the next draw(). + invalidateSelf(); + } + + private void startPendingHardwareExit(HardwareCanvas c, Paint p) { + mHasPendingHardwareExit = false; + + final int opacityDuration = mPendingOpacityDuration; + final int inflectionDuration = mPendingInflectionDuration; + final int inflectionOpacity = mPendingInflectionOpacity; - final Paint outerPaint = getTempPaint(); - outerPaint.setAntiAlias(true); - outerPaint.setColor(mColorOpaque); - outerPaint.setAlpha((int) (mColorAlpha * mOuterOpacity + 0.5f)); - outerPaint.setStyle(Style.FILL); + final Paint outerPaint = getTempPaint(p); + outerPaint.setAlpha((int) (outerPaint.getAlpha() * mOuterOpacity + 0.5f)); mPropOuterPaint = CanvasProperty.createPaint(outerPaint); mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius); mPropOuterX = CanvasProperty.createFloat(mOuterX); @@ -302,8 +295,10 @@ class RippleBackground { outerFadeOutAnim.setStartDelay(inflectionDuration); outerFadeOutAnim.setStartValue(inflectionOpacity); outerFadeOutAnim.addListener(mAnimationListener); + outerFadeOutAnim.setTarget(c); + outerFadeOutAnim.start(); - mPendingAnimations.add(outerFadeOutAnim); + mRunningAnimations.add(outerFadeOutAnim); } else { outerOpacityAnim.addListener(mAnimationListener); } @@ -315,14 +310,15 @@ class RippleBackground { outerOpacityAnim.addListener(mAnimationListener); } - mPendingAnimations.add(outerOpacityAnim); + outerOpacityAnim.setTarget(c); + outerOpacityAnim.start(); + + mRunningAnimations.add(outerOpacityAnim); mHardwareAnimating = true; // Set up the software values to match the hardware end values. mOuterOpacity = 0; - - invalidateSelf(); } /** @@ -341,10 +337,11 @@ class RippleBackground { } } - private Paint getTempPaint() { + private Paint getTempPaint(Paint original) { if (mTempPaint == null) { mTempPaint = new Paint(); } + mTempPaint.set(original); return mTempPaint; } @@ -402,7 +399,7 @@ class RippleBackground { */ public void cancel() { cancelSoftwareAnimations(); - cancelHardwareAnimations(true); + cancelHardwareAnimations(false); } private void cancelSoftwareAnimations() { @@ -415,16 +412,25 @@ class RippleBackground { /** * Cancels any running hardware animations. */ - private void cancelHardwareAnimations(boolean cancelPending) { + private void cancelHardwareAnimations(boolean jumpToEnd) { final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations; final int N = runningAnimations.size(); for (int i = 0; i < N; i++) { - runningAnimations.get(i).cancel(); + if (jumpToEnd) { + runningAnimations.get(i).end(); + } else { + runningAnimations.get(i).cancel(); + } } runningAnimations.clear(); - if (cancelPending && !mPendingAnimations.isEmpty()) { - mPendingAnimations.clear(); + if (mHasPendingHardwareExit) { + // If we had a pending hardware exit, jump to the end state. + mHasPendingHardwareExit = false; + + if (jumpToEnd) { + mOuterOpacity = 0; + } } mHardwareAnimating = false; diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index c7aa98e..1263447 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -16,29 +16,33 @@ package android.graphics.drawable; +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.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; +import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffXfermode; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; +import android.graphics.Shader; import android.util.AttributeSet; import android.util.DisplayMetrics; -import com.android.internal.R; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import java.io.IOException; import java.util.Arrays; @@ -56,7 +60,7 @@ import java.util.Arrays; * <ripple android:color="#ffff0000"> * <item android:id="@android:id/mask" * android:drawable="@android:color/white" /> - * <ripple /></code> + * </ripple></code> * </pre> * <p> * If a mask layer is set, the ripple effect will be masked against that layer @@ -65,15 +69,15 @@ import java.util.Arrays; * If no mask layer is set, the ripple effect is masked against the composite * of the child layers. * <pre> - * <code><!-- A blue ripple drawn atop a black rectangle. --/> + * <code><!-- A green ripple drawn atop a black rectangle. --/> * <ripple android:color="#ff00ff00"> * <item android:drawable="@android:color/black" /> - * <ripple /> + * </ripple> * - * <!-- A red ripple drawn atop a drawable resource. --/> - * <ripple android:color="#ff00ff00"> + * <!-- A blue ripple drawn atop a drawable resource. --/> + * <ripple android:color="#ff0000ff"> * <item android:drawable="@drawable/my_drawable" /> - * <ripple /></code> + * </ripple></code> * </pre> * <p> * If no child layers or mask is specified and the ripple is set as a View @@ -81,16 +85,17 @@ import java.util.Arrays; * background within the View's hierarchy. In this case, the drawing region * may extend outside of the Drawable bounds. * <pre> - * <code><!-- An unbounded green ripple. --/> - * <ripple android:color="#ff0000ff" /></code> + * <code><!-- An unbounded red ripple. --/> + * <ripple android:color="#ffff0000" /></code> * </pre> * * @attr ref android.R.styleable#RippleDrawable_color */ public class RippleDrawable extends LayerDrawable { - private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN); - private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP); - private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER); + private static final int MASK_UNKNOWN = -1; + private static final int MASK_NONE = 0; + private static final int MASK_CONTENT = 1; + private static final int MASK_EXPLICIT = 2; /** * Constant for automatically determining the maximum ripple radius. @@ -123,6 +128,13 @@ public class RippleDrawable extends LayerDrawable { /** The current background. May be actively animating or pending entry. */ private RippleBackground mBackground; + private Bitmap mMaskBuffer; + private BitmapShader mMaskShader; + private Canvas mMaskCanvas; + private Matrix mMaskMatrix; + private PorterDuffColorFilter mMaskColorFilter; + private boolean mHasValidMask; + /** Whether we expect to draw a background when visible. */ private boolean mBackgroundActive; @@ -147,9 +159,6 @@ public class RippleDrawable extends LayerDrawable { /** Paint used to control appearance of ripples. */ private Paint mRipplePaint; - /** Paint used to control reveal layer masking. */ - private Paint mMaskingPaint; - /** Target density of the display into which ripples are drawn. */ private float mDensity = 1.0f; @@ -157,17 +166,10 @@ public class RippleDrawable extends LayerDrawable { private boolean mOverrideBounds; /** - * Whether the next draw MUST draw something to canvas. Used to work around - * a bug in hardware invalidation following a render thread-accelerated - * animation. - */ - private boolean mNeedsDraw; - - /** * Constructor used for drawable inflation. */ RippleDrawable() { - this(new RippleState(null, null, null), null, null); + this(new RippleState(null, null, null), null); } /** @@ -180,7 +182,7 @@ public class RippleDrawable extends LayerDrawable { */ public RippleDrawable(@NonNull ColorStateList color, @Nullable Drawable content, @Nullable Drawable mask) { - this(new RippleState(null, null, null), null, null); + this(new RippleState(null, null, null), null); if (color == null) { throw new IllegalArgumentException("RippleDrawable requires a non-null color"); @@ -203,21 +205,15 @@ public class RippleDrawable extends LayerDrawable { public void jumpToCurrentState() { super.jumpToCurrentState(); - boolean needsDraw = false; - if (mRipple != null) { - needsDraw |= mRipple.isHardwareAnimating(); mRipple.jump(); } if (mBackground != null) { - needsDraw |= mBackground.isHardwareAnimating(); mBackground.jump(); } - needsDraw |= cancelExitingRipples(); - - mNeedsDraw = needsDraw; + cancelExitingRipples(); invalidateSelf(); } @@ -280,7 +276,7 @@ public class RippleDrawable extends LayerDrawable { } setRippleActive(enabled && pressed); - setBackgroundActive(focused || (enabled && pressed)); + setBackgroundActive(focused || (enabled && pressed), focused); return changed; } @@ -296,11 +292,11 @@ public class RippleDrawable extends LayerDrawable { } } - private void setBackgroundActive(boolean active) { + private void setBackgroundActive(boolean active, boolean focused) { if (mBackgroundActive != active) { mBackgroundActive = active; if (active) { - tryBackgroundEnter(); + tryBackgroundEnter(focused); } else { tryBackgroundExit(); } @@ -333,8 +329,11 @@ public class RippleDrawable extends LayerDrawable { } if (mBackgroundActive) { - tryBackgroundEnter(); + tryBackgroundEnter(false); } + + // Skip animations, just show the correct final states. + jumpToCurrentState(); } return changed; @@ -470,7 +469,7 @@ public class RippleDrawable extends LayerDrawable { @Override public boolean canApplyTheme() { - return super.canApplyTheme() || mState != null && mState.mTouchThemeAttrs != null; + return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); } @Override @@ -489,14 +488,13 @@ public class RippleDrawable extends LayerDrawable { /** * Creates an active hotspot at the specified location. */ - private void tryBackgroundEnter() { + private void tryBackgroundEnter(boolean focused) { if (mBackground == null) { mBackground = new RippleBackground(this, mHotspotBounds); } - final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT); - mBackground.setup(mState.mMaxRadius, color, mDensity); - mBackground.enter(); + mBackground.setup(mState.mMaxRadius, mDensity); + mBackground.enter(focused); } private void tryBackgroundExit() { @@ -531,8 +529,7 @@ public class RippleDrawable extends LayerDrawable { mRipple = new Ripple(this, mHotspotBounds, x, y); } - final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT); - mRipple.setup(mState.mMaxRadius, color, mDensity); + mRipple.setup(mState.mMaxRadius, mDensity); mRipple.enter(); } @@ -556,23 +553,19 @@ public class RippleDrawable extends LayerDrawable { * background. Nothing will be drawn after this method is called. */ private void clearHotspots() { - boolean needsDraw = false; - if (mRipple != null) { - needsDraw |= mRipple.isHardwareAnimating(); mRipple.cancel(); mRipple = null; + mRippleActive = false; } if (mBackground != null) { - needsDraw |= mBackground.isHardwareAnimating(); mBackground.cancel(); mBackground = null; + mBackgroundActive = false; } - needsDraw |= cancelExitingRipples(); - - mNeedsDraw = needsDraw; + cancelExitingRipples(); invalidateSelf(); } @@ -628,57 +621,121 @@ public class RippleDrawable extends LayerDrawable { } } + /** + * Optimized for drawing ripples with a mask layer and optional content. + */ @Override public void draw(@NonNull Canvas canvas) { - final boolean hasMask = mMask != null; - final boolean drawNonMaskContent = mLayerState.mNum > (hasMask ? 1 : 0); - final boolean drawMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE; + // Clip to the dirty bounds, which will be the drawable bounds if we + // have a mask or content and the ripple bounds if we're projecting. final Rect bounds = getDirtyBounds(); final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.clipRect(bounds); - // If we have content, draw it into a layer first. - final int contentLayer; - if (drawNonMaskContent) { - contentLayer = drawContentLayer(canvas, bounds, SRC_OVER); - } else { - contentLayer = -1; + drawContent(canvas); + drawBackgroundAndRipples(canvas); + + canvas.restoreToCount(saveCount); + } + + @Override + public void invalidateSelf() { + super.invalidateSelf(); + + // Force the mask to update on the next draw(). + mHasValidMask = false; + } + + /** + * @return whether we need to use a mask + */ + private void updateMaskShaderIfNeeded() { + if (mHasValidMask) { + return; } - // Next, try to draw the ripples (into a layer if necessary). If we need - // to mask against the underlying content, set the xfermode to SRC_ATOP. - final PorterDuffXfermode xfermode = (hasMask || !drawNonMaskContent) ? SRC_OVER : SRC_ATOP; + final int maskType = getMaskType(); + if (maskType == MASK_UNKNOWN) { + return; + } + + mHasValidMask = true; - // If we have a background and a non-opaque mask, draw the masking layer. - final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode, drawMask); - if (backgroundLayer >= 0) { - if (drawMask) { - drawMaskingLayer(canvas, bounds, DST_IN); + final Rect bounds = getBounds(); + if (maskType == MASK_NONE || bounds.isEmpty()) { + if (mMaskBuffer != null) { + mMaskBuffer.recycle(); + mMaskBuffer = null; + mMaskShader = null; + mMaskCanvas = null; } - canvas.restoreToCount(backgroundLayer); + mMaskMatrix = null; + mMaskColorFilter = null; + return; } - // If we have ripples and a non-opaque mask, draw the masking layer. - final int rippleLayer = drawRippleLayer(canvas, bounds, xfermode); - if (rippleLayer >= 0) { - if (drawMask) { - drawMaskingLayer(canvas, bounds, DST_IN); + // Ensure we have a correctly-sized buffer. + if (mMaskBuffer == null + || mMaskBuffer.getWidth() != bounds.width() + || mMaskBuffer.getHeight() != bounds.height()) { + if (mMaskBuffer != null) { + mMaskBuffer.recycle(); } - canvas.restoreToCount(rippleLayer); + + mMaskBuffer = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8); + mMaskShader = new BitmapShader(mMaskBuffer, + Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + mMaskCanvas = new Canvas(mMaskBuffer); + } else { + mMaskBuffer.eraseColor(Color.TRANSPARENT); } - // If we failed to draw anything and we just canceled animations, at - // least draw a color so that hardware invalidation works correctly. - if (contentLayer < 0 && backgroundLayer < 0 && rippleLayer < 0 && mNeedsDraw) { - canvas.drawColor(Color.TRANSPARENT); + if (mMaskMatrix == null) { + mMaskMatrix = new Matrix(); + } else { + mMaskMatrix.reset(); + } - // Request another draw so we can avoid adding a transparent layer - // during the next display list refresh. - invalidateSelf(); + if (mMaskColorFilter == null) { + mMaskColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_IN); } - mNeedsDraw = false; - canvas.restoreToCount(saveCount); + // Draw the appropriate mask. + if (maskType == MASK_EXPLICIT) { + drawMask(mMaskCanvas); + } else if (maskType == MASK_CONTENT) { + drawContent(mMaskCanvas); + } + } + + private int getMaskType() { + if (mRipple == null && mExitingRipplesCount <= 0 + && (mBackground == null || !mBackground.shouldDraw())) { + // We might need a mask later. + return MASK_UNKNOWN; + } + + if (mMask != null) { + if (mMask.getOpacity() == PixelFormat.OPAQUE) { + // Clipping handles opaque explicit masks. + return MASK_NONE; + } else { + return MASK_EXPLICIT; + } + } + + // Check for non-opaque, non-mask content. + final ChildDrawable[] array = mLayerState.mChildren; + final int count = mLayerState.mNum; + for (int i = 0; i < count; i++) { + if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) { + return MASK_CONTENT; + } + } + + // Clipping handles opaque content. + return MASK_NONE; } /** @@ -711,141 +768,92 @@ public class RippleDrawable extends LayerDrawable { return -1; } - private int drawContentLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { + private void drawContent(Canvas canvas) { + // Draw everything except the mask. final ChildDrawable[] array = mLayerState.mChildren; final int count = mLayerState.mNum; - - // We don't need a layer if we don't expect to draw any ripples, we have - // an explicit mask, or if the non-mask content is all opaque. - boolean needsLayer = false; - if ((mExitingRipplesCount > 0 || mBackground != null) && mMask == null) { - for (int i = 0; i < count; i++) { - if (array[i].mId != R.id.mask - && array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) { - needsLayer = true; - break; - } - } - } - - final Paint maskingPaint = getMaskingPaint(mode); - final int restoreToCount = needsLayer ? canvas.saveLayer(bounds.left, bounds.top, - bounds.right, bounds.bottom, maskingPaint) : -1; - - // Draw everything except the mask. for (int i = 0; i < count; i++) { if (array[i].mId != R.id.mask) { array[i].mDrawable.draw(canvas); } } - - return restoreToCount; } - private int drawBackgroundLayer( - Canvas canvas, Rect bounds, PorterDuffXfermode mode, boolean drawMask) { - int saveCount = -1; + private void drawBackgroundAndRipples(Canvas canvas) { + final Ripple active = mRipple; + final RippleBackground background = mBackground; + final int count = mExitingRipplesCount; + if (active == null && count <= 0 && (background == null || !background.shouldDraw())) { + // Move along, nothing to draw here. + return; + } - if (mBackground != null && mBackground.shouldDraw()) { - // TODO: We can avoid saveLayer here if we push the xfermode into - // the background's render thread animator at exit() time. - if (drawMask || mode != SRC_OVER) { - saveCount = canvas.saveLayer(bounds.left, bounds.top, bounds.right, - bounds.bottom, getMaskingPaint(mode)); - } + final float x = mHotspotBounds.exactCenterX(); + final float y = mHotspotBounds.exactCenterY(); + canvas.translate(x, y); - final float x = mHotspotBounds.exactCenterX(); - final float y = mHotspotBounds.exactCenterY(); - canvas.translate(x, y); - mBackground.draw(canvas, getRipplePaint()); - canvas.translate(-x, -y); + updateMaskShaderIfNeeded(); + + // Position the shader to account for canvas translation. + if (mMaskShader != null) { + mMaskMatrix.setTranslate(-x, -y); + mMaskShader.setLocalMatrix(mMaskMatrix); } - return saveCount; - } + // Grab the color for the current state and cut the alpha channel in + // half so that the ripple and background together yield full alpha. + final int color = mState.mColor.getColorForState(getState(), Color.BLACK); + final int halfAlpha = (Color.alpha(color) / 2) << 24; + final Paint p = getRipplePaint(); - private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { - boolean drewRipples = false; - int restoreToCount = -1; - int restoreTranslate = -1; + if (mMaskColorFilter != null) { + // The ripple timing depends on the paint's alpha value, so we need + // to push just the alpha channel into the paint and let the filter + // handle the full-alpha color. + final int fullAlphaColor = color | (0xFF << 24); + mMaskColorFilter.setColor(fullAlphaColor); - // Draw ripples and update the animating ripples array. - final int count = mExitingRipplesCount; - final Ripple[] ripples = mExitingRipples; - for (int i = 0; i <= count; i++) { - final Ripple ripple; - if (i < count) { - ripple = ripples[i]; - } else if (mRipple != null) { - ripple = mRipple; - } else { - continue; - } - - // If we're masking the ripple layer, make sure we have a layer - // first. This will merge SRC_OVER (directly) onto the canvas. - if (restoreToCount < 0) { - final Paint maskingPaint = getMaskingPaint(mode); - final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT); - final int alpha = Color.alpha(color); - maskingPaint.setAlpha(alpha / 2); - - // TODO: We can avoid saveLayer here if we're only drawing one - // ripple and we don't have content or a translucent mask. - restoreToCount = canvas.saveLayer(bounds.left, bounds.top, - bounds.right, bounds.bottom, maskingPaint); - - // Translate the canvas to the current hotspot bounds. - restoreTranslate = canvas.save(); - canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY()); - } + p.setColor(halfAlpha); + p.setColorFilter(mMaskColorFilter); + p.setShader(mMaskShader); + } else { + final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha; + p.setColor(halfAlphaColor); + p.setColorFilter(null); + p.setShader(null); + } - drewRipples |= ripple.draw(canvas, getRipplePaint()); + if (background != null && background.shouldDraw()) { + background.draw(canvas, p); } - // Always restore the translation. - if (restoreTranslate >= 0) { - canvas.restoreToCount(restoreTranslate); + if (count > 0) { + final Ripple[] ripples = mExitingRipples; + for (int i = 0; i < count; i++) { + ripples[i].draw(canvas, p); + } } - // If we created a layer with no content, merge it immediately. - if (restoreToCount >= 0 && !drewRipples) { - canvas.restoreToCount(restoreToCount); - restoreToCount = -1; + if (active != null) { + active.draw(canvas, p); } - return restoreToCount; + canvas.translate(-x, -y); } - private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { - final int restoreToCount = canvas.saveLayer(bounds.left, bounds.top, - bounds.right, bounds.bottom, getMaskingPaint(mode)); - - // Ensure that DST_IN blends using the entire layer. - canvas.drawColor(Color.TRANSPARENT); - + private void drawMask(Canvas canvas) { mMask.draw(canvas); - - return restoreToCount; } private Paint getRipplePaint() { if (mRipplePaint == null) { mRipplePaint = new Paint(); mRipplePaint.setAntiAlias(true); + mRipplePaint.setStyle(Paint.Style.FILL); } return mRipplePaint; } - private Paint getMaskingPaint(PorterDuffXfermode xfermode) { - if (mMaskingPaint == null) { - mMaskingPaint = new Paint(); - } - mMaskingPaint.setXfermode(xfermode); - mMaskingPaint.setAlpha(0xFF); - return mMaskingPaint; - } - @Override public Rect getDirtyBounds() { if (isProjected()) { @@ -893,6 +901,10 @@ public class RippleDrawable extends LayerDrawable { // LayerDrawable creates a new state using createConstantState, so // this should always be a safe cast. mState = (RippleState) mLayerState; + + // The locally cached drawable may have changed. + mMask = findDrawableByLayerId(R.id.mask); + return this; } @@ -924,17 +936,12 @@ public class RippleDrawable extends LayerDrawable { @Override public Drawable newDrawable() { - return new RippleDrawable(this, null, null); + return new RippleDrawable(this, null); } @Override public Drawable newDrawable(Resources res) { - return new RippleDrawable(this, res, null); - } - - @Override - public Drawable newDrawable(Resources res, Theme theme) { - return new RippleDrawable(this, res, theme); + return new RippleDrawable(this, res); } } @@ -968,37 +975,18 @@ public class RippleDrawable extends LayerDrawable { return mState.mMaxRadius; } - private RippleDrawable(RippleState state, Resources res, Theme theme) { - boolean needsTheme = false; + private RippleDrawable(RippleState state, Resources res) { + mState = new RippleState(state, this, res); + mLayerState = mState; - final RippleState ns; - if (theme != null && state != null && state.canApplyTheme()) { - ns = new RippleState(state, this, res); - needsTheme = true; - } else if (state == null) { - ns = new RippleState(null, this, res); - } else { - // We always need a new state since child drawables contain local - // state but live within the parent's constant state. - // TODO: Move child drawables into local state. - ns = new RippleState(state, this, res); + if (mState.mNum > 0) { + ensurePadding(); } if (res != null) { mDensity = res.getDisplayMetrics().density; } - mState = ns; - mLayerState = ns; - - if (ns.mNum > 0) { - ensurePadding(); - } - - if (needsTheme) { - applyTheme(theme); - } - initializeFromState(); } diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index 55c9637..e1991fe 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -16,6 +16,8 @@ package android.graphics.drawable; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -29,7 +31,6 @@ import android.content.res.TypedArray; import android.content.res.Resources.Theme; import android.util.TypedValue; import android.util.AttributeSet; -import android.util.Log; import java.io.IOException; @@ -215,7 +216,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { * @attr ref android.R.styleable#RotateDrawable_pivotX */ public void setPivotX(float pivotX) { - if (mState.mPivotX == pivotX) { + if (mState.mPivotX != pivotX) { mState.mPivotX = pivotX; invalidateSelf(); } @@ -241,7 +242,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { * @see #isPivotXRelative() */ public void setPivotXRelative(boolean relative) { - if (mState.mPivotXRel == relative) { + if (mState.mPivotXRel != relative) { mState.mPivotXRel = relative; invalidateSelf(); } @@ -269,7 +270,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { * @attr ref android.R.styleable#RotateDrawable_pivotY */ public void setPivotY(float pivotY) { - if (mState.mPivotY == pivotY) { + if (mState.mPivotY != pivotY) { mState.mPivotY = pivotY; invalidateSelf(); } @@ -295,7 +296,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { * @see #isPivotYRelative() */ public void setPivotYRelative(boolean relative) { - if (mState.mPivotYRel == relative) { + if (mState.mPivotYRel != relative) { mState.mPivotYRel = relative; invalidateSelf(); } @@ -312,6 +313,11 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { } @Override + public boolean canApplyTheme() { + return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); + } + + @Override public void invalidateDrawable(Drawable who) { final Callback callback = getCallback(); if (callback != null) { @@ -399,79 +405,104 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - final TypedArray a = obtainAttributes(r, theme, attrs, - com.android.internal.R.styleable.RotateDrawable); - - super.inflateWithAttributes(r, parser, a, - com.android.internal.R.styleable.RotateDrawable_visible); - - TypedValue tv = a.peekValue(com.android.internal.R.styleable.RotateDrawable_pivotX); - final boolean pivotXRel; - final float pivotX; - if (tv == null) { - pivotXRel = true; - pivotX = 0.5f; - } else { - pivotXRel = tv.type == TypedValue.TYPE_FRACTION; - pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); - } + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable); + super.inflateWithAttributes(r, parser, a, R.styleable.RotateDrawable_visible); + + // Reset mDrawable to preserve old multiple-inflate behavior. This is + // silly, but we have CTS tests that rely on it. + mState.mDrawable = null; + + updateStateFromTypedArray(a); + inflateChildElements(r, parser, attrs, theme); + verifyRequiredAttributes(a); + a.recycle(); + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); - tv = a.peekValue(com.android.internal.R.styleable.RotateDrawable_pivotY); - final boolean pivotYRel; - final float pivotY; - if (tv == null) { - pivotYRel = true; - pivotY = 0.5f; - } else { - pivotYRel = tv.type == TypedValue.TYPE_FRACTION; - pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); + final RotateState state = mState; + if (state == null) { + return; } - final float fromDegrees = a.getFloat( - com.android.internal.R.styleable.RotateDrawable_fromDegrees, 0.0f); - final float toDegrees = a.getFloat( - com.android.internal.R.styleable.RotateDrawable_toDegrees, 360.0f); + 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(); + } + } - final int res = a.getResourceId( - com.android.internal.R.styleable.RotateDrawable_drawable, 0); - Drawable drawable = null; - if (res > 0) { - drawable = r.getDrawable(res, theme); + if (state.mDrawable != null && state.mDrawable.canApplyTheme()) { + state.mDrawable.applyTheme(t); } - a.recycle(); + } - final int outerDepth = parser.getDepth(); + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { + Drawable dr = null; int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && - (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - + final int outerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type != XmlPullParser.START_TAG) { continue; } + dr = Drawable.createFromXmlInner(r, parser, attrs, theme); + } - if ((drawable = Drawable.createFromXmlInner(r, parser, attrs, theme)) == null) { - Log.w("drawable", "Bad element under <rotate>: " - + parser .getName()); - } + if (dr != null) { + mState.mDrawable = dr; + dr.setCallback(this); + } + } + + private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (mState.mDrawable == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <rotate> tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); } + } + + private void updateStateFromTypedArray(TypedArray a) { + final RotateState state = mState; - if (drawable == null) { - Log.w("drawable", "No drawable specified for <rotate>"); + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + if (a.hasValue(R.styleable.RotateDrawable_pivotX)) { + final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotX); + state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION; + state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); } - final RotateState st = mState; - st.mDrawable = drawable; - st.mPivotXRel = pivotXRel; - st.mPivotX = pivotX; - st.mPivotYRel = pivotYRel; - st.mPivotY = pivotY; - st.mFromDegrees = fromDegrees; - st.mCurrentDegrees = fromDegrees; - st.mToDegrees = toDegrees; - - if (drawable != null) { - drawable.setCallback(this); + if (a.hasValue(R.styleable.RotateDrawable_pivotY)) { + final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotY); + state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION; + state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); + } + + state.mFromDegrees = a.getFloat(R.styleable.RotateDrawable_fromDegrees, state.mFromDegrees); + 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) { + state.mDrawable = dr; + dr.setCallback(this); } } @@ -485,30 +516,42 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { } /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mState.mDrawable.clearMutated(); + mMutated = false; + } + + /** * Represents the state of a rotation for a given drawable. The same * rotate drawable can be invoked with different states to drive several * rotations at the same time. */ final static class RotateState extends Drawable.ConstantState { - Drawable mDrawable; - + int[] mThemeAttrs; int mChangingConfigurations; - boolean mPivotXRel; - float mPivotX; - boolean mPivotYRel; - float mPivotY; + Drawable mDrawable; - float mFromDegrees; - float mToDegrees; + boolean mPivotXRel = true; + float mPivotX = 0.5f; + boolean mPivotYRel = true; + float mPivotY = 0.5f; - float mCurrentDegrees; + float mFromDegrees = 0.0f; + float mToDegrees = 360.0f; + + float mCurrentDegrees = 0.0f; - private boolean mCanConstantState; private boolean mCheckedConstantState; + private boolean mCanConstantState; - public RotateState(RotateState orig, RotateDrawable owner, Resources res) { + RotateState(RotateState orig, RotateDrawable owner, Resources res) { if (orig != null) { + mThemeAttrs = orig.mThemeAttrs; + mChangingConfigurations = orig.mChangingConfigurations; if (res != null) { mDrawable = orig.mDrawable.getConstantState().newDrawable(res); } else { @@ -522,13 +565,20 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { mPivotX = orig.mPivotX; mPivotYRel = orig.mPivotYRel; mPivotY = orig.mPivotY; - mFromDegrees = mCurrentDegrees = orig.mFromDegrees; + mFromDegrees = orig.mFromDegrees; mToDegrees = orig.mToDegrees; - mCanConstantState = mCheckedConstantState = true; + mCurrentDegrees = orig.mCurrentDegrees; + mCheckedConstantState = mCanConstantState = true; } } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme()) + || super.canApplyTheme(); + } + + @Override public Drawable newDrawable() { return new RotateDrawable(this, null); } diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java index b990249..da722f3 100644 --- a/graphics/java/android/graphics/drawable/ScaleDrawable.java +++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java @@ -16,6 +16,8 @@ package android.graphics.drawable; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -29,6 +31,7 @@ import android.view.Gravity; import android.util.AttributeSet; import java.io.IOException; +import java.util.Collection; /** * A Drawable that changes the size of another Drawable based on its current @@ -47,7 +50,7 @@ import java.io.IOException; * @attr ref android.R.styleable#ScaleDrawable_drawable */ public class ScaleDrawable extends Drawable implements Drawable.Callback { - private ScaleState mScaleState; + private ScaleState mState; private boolean mMutated; private final Rect mTmpRect = new Rect(); @@ -58,10 +61,10 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) { this(null, null); - mScaleState.mDrawable = drawable; - mScaleState.mGravity = gravity; - mScaleState.mScaleWidth = scaleWidth; - mScaleState.mScaleHeight = scaleHeight; + mState.mDrawable = drawable; + mState.mGravity = gravity; + mState.mScaleWidth = scaleWidth; + mState.mScaleHeight = scaleHeight; if (drawable != null) { drawable.setCallback(this); @@ -72,18 +75,18 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { * Returns the drawable scaled by this ScaleDrawable. */ public Drawable getDrawable() { - return mScaleState.mDrawable; + return mState.mDrawable; } - private static float getPercent(TypedArray a, int name) { - String s = a.getString(name); + private static float getPercent(TypedArray a, int name, float defaultValue) { + final String s = a.getString(name); if (s != null) { if (s.endsWith("%")) { String f = s.substring(0, s.length() - 1); return Float.parseFloat(f) / 100.0f; } } - return -1; + return defaultValue; } @Override @@ -91,20 +94,48 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { throws XmlPullParserException, IOException { super.inflate(r, parser, attrs, theme); - int type; - - TypedArray a = obtainAttributes( - r, theme, attrs, com.android.internal.R.styleable.ScaleDrawable); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable); - float sw = getPercent(a, com.android.internal.R.styleable.ScaleDrawable_scaleWidth); - float sh = getPercent(a, com.android.internal.R.styleable.ScaleDrawable_scaleHeight); - int g = a.getInt(com.android.internal.R.styleable.ScaleDrawable_scaleGravity, Gravity.LEFT); - boolean min = a.getBoolean( - com.android.internal.R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, false); - Drawable dr = a.getDrawable(com.android.internal.R.styleable.ScaleDrawable_drawable); + // Reset mDrawable to preserve old multiple-inflate behavior. This is + // silly, but we have CTS tests that rely on it. + mState.mDrawable = null; + updateStateFromTypedArray(a); + inflateChildElements(r, parser, attrs, theme); + verifyRequiredAttributes(a); a.recycle(); + } + + @Override + public void applyTheme(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(); + } + } + if (state.mDrawable != null && state.mDrawable.canApplyTheme()) { + state.mDrawable.applyTheme(t); + } + } + + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { + Drawable dr = null; + int type; final int outerDepth = parser.getDepth(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { @@ -114,20 +145,51 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { dr = Drawable.createFromXmlInner(r, parser, attrs, theme); } - if (dr == null) { - throw new IllegalArgumentException("No drawable specified for <scale>"); + if (dr != null) { + mState.mDrawable = dr; + dr.setCallback(this); } + } + + private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (mState.mDrawable == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <scale> tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + } + + private void updateStateFromTypedArray(TypedArray a) { + final ScaleState state = mState; - mScaleState.mDrawable = dr; - mScaleState.mScaleWidth = sw; - mScaleState.mScaleHeight = sh; - mScaleState.mGravity = g; - mScaleState.mUseIntrinsicSizeAsMin = min; + // 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, R.styleable.ScaleDrawable_scaleHeight, state.mScaleHeight); + state.mGravity = a.getInt(R.styleable.ScaleDrawable_scaleGravity, state.mGravity); + state.mUseIntrinsicSizeAsMin = a.getBoolean( + R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin); + + final Drawable dr = a.getDrawable(R.styleable.ScaleDrawable_drawable); if (dr != null) { + state.mDrawable = dr; dr.setCallback(this); } } + @Override + public boolean canApplyTheme() { + return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); + } + // overrides from Drawable.Callback public void invalidateDrawable(Drawable who) { @@ -152,74 +214,74 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { @Override public void draw(Canvas canvas) { - if (mScaleState.mDrawable.getLevel() != 0) - mScaleState.mDrawable.draw(canvas); + if (mState.mDrawable.getLevel() != 0) + mState.mDrawable.draw(canvas); } @Override public int getChangingConfigurations() { return super.getChangingConfigurations() - | mScaleState.mChangingConfigurations - | mScaleState.mDrawable.getChangingConfigurations(); + | mState.mChangingConfigurations + | mState.mDrawable.getChangingConfigurations(); } @Override public boolean getPadding(Rect padding) { // XXX need to adjust padding! - return mScaleState.mDrawable.getPadding(padding); + return mState.mDrawable.getPadding(padding); } @Override public boolean setVisible(boolean visible, boolean restart) { - mScaleState.mDrawable.setVisible(visible, restart); + mState.mDrawable.setVisible(visible, restart); return super.setVisible(visible, restart); } @Override public void setAlpha(int alpha) { - mScaleState.mDrawable.setAlpha(alpha); + mState.mDrawable.setAlpha(alpha); } @Override public int getAlpha() { - return mScaleState.mDrawable.getAlpha(); + return mState.mDrawable.getAlpha(); } @Override public void setColorFilter(ColorFilter cf) { - mScaleState.mDrawable.setColorFilter(cf); + mState.mDrawable.setColorFilter(cf); } @Override public void setTintList(ColorStateList tint) { - mScaleState.mDrawable.setTintList(tint); + mState.mDrawable.setTintList(tint); } @Override public void setTintMode(Mode tintMode) { - mScaleState.mDrawable.setTintMode(tintMode); + mState.mDrawable.setTintMode(tintMode); } @Override public int getOpacity() { - return mScaleState.mDrawable.getOpacity(); + return mState.mDrawable.getOpacity(); } @Override public boolean isStateful() { - return mScaleState.mDrawable.isStateful(); + return mState.mDrawable.isStateful(); } @Override protected boolean onStateChange(int[] state) { - boolean changed = mScaleState.mDrawable.setState(state); + boolean changed = mState.mDrawable.setState(state); onBoundsChange(getBounds()); return changed; } @Override protected boolean onLevelChange(int level) { - mScaleState.mDrawable.setLevel(level); + mState.mDrawable.setLevel(level); onBoundsChange(getBounds()); invalidateSelf(); return true; @@ -228,41 +290,41 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { @Override protected void onBoundsChange(Rect bounds) { final Rect r = mTmpRect; - final boolean min = mScaleState.mUseIntrinsicSizeAsMin; + final boolean min = mState.mUseIntrinsicSizeAsMin; int level = getLevel(); int w = bounds.width(); - if (mScaleState.mScaleWidth > 0) { - final int iw = min ? mScaleState.mDrawable.getIntrinsicWidth() : 0; - w -= (int) ((w - iw) * (10000 - level) * mScaleState.mScaleWidth / 10000); + if (mState.mScaleWidth > 0) { + final int iw = min ? mState.mDrawable.getIntrinsicWidth() : 0; + w -= (int) ((w - iw) * (10000 - level) * mState.mScaleWidth / 10000); } int h = bounds.height(); - if (mScaleState.mScaleHeight > 0) { - final int ih = min ? mScaleState.mDrawable.getIntrinsicHeight() : 0; - h -= (int) ((h - ih) * (10000 - level) * mScaleState.mScaleHeight / 10000); + if (mState.mScaleHeight > 0) { + final int ih = min ? mState.mDrawable.getIntrinsicHeight() : 0; + h -= (int) ((h - ih) * (10000 - level) * mState.mScaleHeight / 10000); } final int layoutDirection = getLayoutDirection(); - Gravity.apply(mScaleState.mGravity, w, h, bounds, r, layoutDirection); + Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection); if (w > 0 && h > 0) { - mScaleState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom); + mState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom); } } @Override public int getIntrinsicWidth() { - return mScaleState.mDrawable.getIntrinsicWidth(); + return mState.mDrawable.getIntrinsicWidth(); } @Override public int getIntrinsicHeight() { - return mScaleState.mDrawable.getIntrinsicHeight(); + return mState.mDrawable.getIntrinsicHeight(); } @Override public ConstantState getConstantState() { - if (mScaleState.canConstantState()) { - mScaleState.mChangingConfigurations = getChangingConfigurations(); - return mScaleState; + if (mState.canConstantState()) { + mState.mChangingConfigurations = getChangingConfigurations(); + return mState; } return null; } @@ -270,25 +332,42 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { - mScaleState.mDrawable.mutate(); + mState.mDrawable.mutate(); mMutated = true; } return this; } + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mState.mDrawable.clearMutated(); + mMutated = false; + } + final static class ScaleState extends ConstantState { - Drawable mDrawable; + /** Constant used to disable scaling for a particular dimension. */ + private static final float DO_NOT_SCALE = -1.0f; + + int[] mThemeAttrs; int mChangingConfigurations; - float mScaleWidth; - float mScaleHeight; - int mGravity; - boolean mUseIntrinsicSizeAsMin; + + Drawable mDrawable; + + float mScaleWidth = DO_NOT_SCALE; + float mScaleHeight = DO_NOT_SCALE; + int mGravity = Gravity.LEFT; + boolean mUseIntrinsicSizeAsMin = false; private boolean mCheckedConstantState; private boolean mCanConstantState; ScaleState(ScaleState orig, ScaleDrawable owner, Resources res) { if (orig != null) { + mThemeAttrs = orig.mThemeAttrs; + mChangingConfigurations = orig.mChangingConfigurations; if (res != null) { mDrawable = orig.mDrawable.getConstantState().newDrawable(res); } else { @@ -307,6 +386,12 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme()) + || super.canApplyTheme(); + } + + @Override public Drawable newDrawable() { return new ScaleDrawable(this, null); } @@ -329,10 +414,19 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { return mCanConstantState; } + + @Override + public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { + final ConstantState state = mDrawable.getConstantState(); + if (state != null) { + return state.addAtlasableBitmaps(atlasList); + } + return 0; + } } private ScaleDrawable(ScaleState state, Resources res) { - mScaleState = new ScaleState(state, this, res); + mState = new ScaleState(state, this, res); } } diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java index bd69d76..72edf03 100644 --- a/graphics/java/android/graphics/drawable/ShapeDrawable.java +++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java @@ -53,9 +53,9 @@ import java.io.IOException; * For more information about how to use ShapeDrawable, read the <a * href="{@docRoot}guide/topics/graphics/2d-graphics.html#shape-drawable"> * Canvas and Drawables</a> document. For more information about defining a - * ShapeDrawable in XML, read the <a href="{@docRoot} - * guide/topics/resources/drawable-resource.html#Shape">Drawable Resources</a> - * document. + * ShapeDrawable in XML, read the + * <a href="{@docRoot}guide/topics/resources/drawable-resource.html#Shape"> + * Drawable Resources</a> document. * </p> * </div> * @@ -76,7 +76,7 @@ public class ShapeDrawable extends Drawable { * ShapeDrawable constructor. */ public ShapeDrawable() { - this(new ShapeState(null), null, null); + this(new ShapeState(null), null); } /** @@ -85,7 +85,7 @@ public class ShapeDrawable extends Drawable { * @param s the Shape that this ShapeDrawable should be */ public ShapeDrawable(Shape s) { - this(new ShapeState(null), null, null); + this(new ShapeState(null), null); mShapeState.mShape = s; } @@ -508,6 +508,14 @@ public class ShapeDrawable extends Drawable { } /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mMutated = false; + } + + /** * Defines the intrinsic properties of this ShapeDrawable's Shape. */ final static class ShapeState extends ConstantState { @@ -547,17 +555,12 @@ public class ShapeDrawable extends Drawable { @Override public Drawable newDrawable() { - return new ShapeDrawable(this, null, null); + return new ShapeDrawable(this, null); } @Override public Drawable newDrawable(Resources res) { - return new ShapeDrawable(this, res, null); - } - - @Override - public Drawable newDrawable(Resources res, Theme theme) { - return new ShapeDrawable(this, res, theme); + return new ShapeDrawable(this, res); } @Override @@ -570,13 +573,8 @@ public class ShapeDrawable extends Drawable { * The one constructor to rule them all. This is called by all public * constructors to set the state and initialize local properties. */ - private ShapeDrawable(ShapeState state, Resources res, Theme theme) { - if (theme != null && state.canApplyTheme()) { - mShapeState = new ShapeState(state); - applyTheme(theme); - } else { - mShapeState = state; - } + private ShapeDrawable(ShapeState state, Resources res) { + mShapeState = state; initializeWithState(state, res); } diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java index 2eb8a80..59d0ba6 100644 --- a/graphics/java/android/graphics/drawable/StateListDrawable.java +++ b/graphics/java/android/graphics/drawable/StateListDrawable.java @@ -24,6 +24,8 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.Arrays; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; @@ -117,26 +119,48 @@ public class StateListDrawable extends DrawableContainer { @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable); + super.inflateWithAttributes(r, parser, a, R.styleable.StateListDrawable_visible); + updateStateFromTypedArray(a); + a.recycle(); - super.inflateWithAttributes(r, parser, a, - R.styleable.StateListDrawable_visible); - - mStateListState.setVariablePadding(a.getBoolean( - R.styleable.StateListDrawable_variablePadding, false)); - mStateListState.setConstantSize(a.getBoolean( - R.styleable.StateListDrawable_constantSize, false)); - mStateListState.setEnterFadeDuration(a.getInt( - R.styleable.StateListDrawable_enterFadeDuration, 0)); - mStateListState.setExitFadeDuration(a.getInt( - R.styleable.StateListDrawable_exitFadeDuration, 0)); + inflateChildElements(r, parser, attrs, theme); - setDither(a.getBoolean(R.styleable.StateListDrawable_dither, DEFAULT_DITHER)); - setAutoMirrored(a.getBoolean(R.styleable.StateListDrawable_autoMirrored, false)); + onStateChange(getState()); + } - a.recycle(); + /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final StateListState state = mStateListState; + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + state.mVariablePadding = a.getBoolean( + R.styleable.StateListDrawable_variablePadding, state.mVariablePadding); + state.mConstantSize = a.getBoolean( + R.styleable.StateListDrawable_constantSize, state.mConstantSize); + state.mEnterFadeDuration = a.getInt( + R.styleable.StateListDrawable_enterFadeDuration, state.mEnterFadeDuration); + state.mExitFadeDuration = a.getInt( + R.styleable.StateListDrawable_exitFadeDuration, state.mExitFadeDuration); + state.mDither = a.getBoolean( + R.styleable.StateListDrawable_dither, state.mDither); + state.mAutoMirrored = a.getBoolean( + R.styleable.StateListDrawable_autoMirrored, state.mAutoMirrored); + } + /** + * Inflates child elements from XML. + */ + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { + final StateListState state = mStateListState; final int innerDepth = parser.getDepth() + 1; int type; int depth; @@ -151,29 +175,19 @@ public class StateListDrawable extends DrawableContainer { continue; } - int drawableRes = 0; - - int i; - int j = 0; - final int numAttrs = attrs.getAttributeCount(); - int[] states = new int[numAttrs]; - for (i = 0; i < numAttrs; i++) { - final int stateResId = attrs.getAttributeNameResource(i); - if (stateResId == 0) break; - if (stateResId == R.attr.drawable) { - drawableRes = attrs.getAttributeResourceValue(i, 0); - } else { - states[j++] = attrs.getAttributeBooleanValue(i, false) - ? stateResId - : -stateResId; - } - } - states = StateSet.trimStateSet(states, j); + // This allows state list drawable item elements to be themed at + // inflation time but does NOT make them work for Zygote preload. + final TypedArray a = obtainAttributes(r, theme, attrs, + R.styleable.StateListDrawableItem); + Drawable dr = a.getDrawable(R.styleable.StateListDrawableItem_drawable); + a.recycle(); - final Drawable dr; - if (drawableRes != 0) { - dr = r.getDrawable(drawableRes, theme); - } else { + final int[] states = extractStateSet(attrs); + + // Loading child elements modifies the state of the AttributeSet's + // underlying parser, so it needs to happen after obtaining + // attributes and extracting states. + if (dr == null) { while ((type = parser.next()) == XmlPullParser.TEXT) { } if (type != XmlPullParser.START_TAG) { @@ -185,10 +199,37 @@ public class StateListDrawable extends DrawableContainer { dr = Drawable.createFromXmlInner(r, parser, attrs, theme); } - mStateListState.addStateSet(states, dr); + state.addStateSet(states, dr); } + } - onStateChange(getState()); + /** + * Extracts state_ attributes from an attribute set. + * + * @param attrs The attribute set. + * @return An array of state_ attributes. + */ + int[] extractStateSet(AttributeSet attrs) { + int j = 0; + final int numAttrs = attrs.getAttributeCount(); + int[] states = new int[numAttrs]; + for (int i = 0; i < numAttrs; i++) { + final int stateResId = attrs.getAttributeNameResource(i); + switch (stateResId) { + case 0: + break; + case R.attr.drawable: + case R.attr.id: + // Ignore attributes from StateListDrawableItem and + // AnimatedStateListDrawableItem. + continue; + default: + states[j++] = attrs.getAttributeBooleanValue(i, false) + ? stateResId : -stateResId; + } + } + states = StateSet.trimStateSet(states, j); + return states; } StateListState getStateListState() { @@ -249,20 +290,25 @@ public class StateListDrawable extends DrawableContainer { @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { - final int[][] sets = mStateListState.mStateSets; - final int count = sets.length; - mStateListState.mStateSets = new int[count][]; - for (int i = 0; i < count; i++) { - final int[] set = sets[i]; - if (set != null) { - mStateListState.mStateSets[i] = set.clone(); - } - } + mStateListState.mutate(); mMutated = true; } return this; } + @Override + StateListState cloneConstantState() { + return new StateListState(mStateListState, this, null); + } + + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mMutated = false; + } + /** @hide */ @Override public void setLayoutDirection(int layoutDirection) { @@ -274,18 +320,31 @@ public class StateListDrawable extends DrawableContainer { } static class StateListState extends DrawableContainerState { + int[] mThemeAttrs; int[][] mStateSets; StateListState(StateListState orig, StateListDrawable owner, Resources res) { super(orig, owner, res); if (orig != null) { - mStateSets = Arrays.copyOf(orig.mStateSets, orig.mStateSets.length); + // Perform a shallow copy and rely on mutate() to deep-copy. + mThemeAttrs = orig.mThemeAttrs; + mStateSets = orig.mStateSets; } else { + mThemeAttrs = null; mStateSets = new int[getCapacity()][]; } } + private void mutate() { + mThemeAttrs = mThemeAttrs != null ? mThemeAttrs.clone() : null; + + final int[][] stateSets = new int[mStateSets.length][]; + for (int i = mStateSets.length - 1; i >= 0; i--) { + stateSets[i] = mStateSets[i] != null ? mStateSets[i].clone() : null; + } + } + int addStateSet(int[] stateSet, Drawable drawable) { final int pos = addChild(drawable); mStateSets[pos] = stateSet; @@ -314,6 +373,11 @@ public class StateListDrawable extends DrawableContainer { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null || super.canApplyTheme(); + } + + @Override public void growArray(int oldSize, int newSize) { super.growArray(oldSize, newSize); final int[][] newStateSets = new int[newSize][]; @@ -322,13 +386,23 @@ public class StateListDrawable extends DrawableContainer { } } - void setConstantState(StateListState state) { + @Override + public void applyTheme(Theme theme) { + super.applyTheme(theme); + + onStateChange(getState()); + } + + protected void setConstantState(@NonNull DrawableContainerState state) { super.setConstantState(state); - mStateListState = state; + if (state instanceof StateListState) { + mStateListState = (StateListState) state; + } } private StateListDrawable(StateListState state, Resources res) { + // Every state list drawable has its own constant state. final StateListState newState = new StateListState(state, this, res); setConstantState(newState); onStateChange(getState()); @@ -338,7 +412,7 @@ public class StateListDrawable extends DrawableContainer { * This constructor exists so subclasses can avoid calling the default * constructor and setting up a StateListDrawable-specific constant state. */ - StateListDrawable(StateListState state) { + StateListDrawable(@Nullable StateListState state) { if (state != null) { setConstantState(state); } diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java index 4380ca4..e5c235e 100644 --- a/graphics/java/android/graphics/drawable/TransitionDrawable.java +++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java @@ -17,7 +17,6 @@ package android.graphics.drawable; import android.content.res.Resources; -import android.content.res.Resources.Theme; import android.graphics.Canvas; import android.os.SystemClock; @@ -86,11 +85,11 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba * @see #TransitionDrawable(Drawable[]) */ TransitionDrawable() { - this(new TransitionState(null, null, null), null, null); + this(new TransitionState(null, null, null), (Resources) null); } - private TransitionDrawable(TransitionState state, Resources res, Theme theme) { - super(state, res, theme); + private TransitionDrawable(TransitionState state, Resources res) { + super(state, res); } private TransitionDrawable(TransitionState state, Drawable[] layers) { @@ -245,24 +244,18 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba } static class TransitionState extends LayerState { - TransitionState(TransitionState orig, TransitionDrawable owner, - Resources res) { + TransitionState(TransitionState orig, TransitionDrawable owner, Resources res) { super(orig, owner, res); } @Override public Drawable newDrawable() { - return new TransitionDrawable(this, null, null); + return new TransitionDrawable(this, (Resources) null); } @Override public Drawable newDrawable(Resources res) { - return new TransitionDrawable(this, res, null); - } - - @Override - public Drawable newDrawable(Resources res, Theme theme) { - return new TransitionDrawable(this, res, theme); + return new TransitionDrawable(this, res); } @Override diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index db0c94f..8b0f635 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -14,6 +14,7 @@ package android.graphics.drawable; +import android.annotation.NonNull; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; @@ -212,15 +213,8 @@ public class VectorDrawable extends Drawable { mVectorState = new VectorDrawableState(); } - private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) { - if (theme != null && state.canApplyTheme()) { - // If we need to apply a theme, implicitly mutate. - mVectorState = new VectorDrawableState(state); - applyTheme(theme); - } else { - mVectorState = state; - } - + private VectorDrawable(@NonNull VectorDrawableState state) { + mVectorState = state; mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); } @@ -233,6 +227,14 @@ public class VectorDrawable extends Drawable { return this; } + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + mMutated = false; + } + Object getTargetByName(String name) { return mVectorState.mVPathRenderer.mVGTargetsMap.get(name); } @@ -359,7 +361,7 @@ public class VectorDrawable extends Drawable { @Override public boolean canApplyTheme() { - return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme(); + return (mVectorState != null && mVectorState.canApplyTheme()) || super.canApplyTheme(); } @Override @@ -748,8 +750,8 @@ public class VectorDrawable extends Drawable { @Override public boolean canApplyTheme() { - return super.canApplyTheme() || mThemeAttrs != null - || (mVPathRenderer != null && mVPathRenderer.canApplyTheme()); + return mThemeAttrs != null || (mVPathRenderer != null && mVPathRenderer.canApplyTheme()) + || super.canApplyTheme(); } public VectorDrawableState() { @@ -758,17 +760,12 @@ public class VectorDrawable extends Drawable { @Override public Drawable newDrawable() { - return new VectorDrawable(this, null, null); + return new VectorDrawable(this); } @Override public Drawable newDrawable(Resources res) { - return new VectorDrawable(this, res, null); - } - - @Override - public Drawable newDrawable(Resources res, Theme theme) { - return new VectorDrawable(this, res, theme); + return new VectorDrawable(this); } @Override diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java index 9837139..2b70b6a 100644 --- a/graphics/java/android/graphics/pdf/PdfEditor.java +++ b/graphics/java/android/graphics/pdf/PdfEditor.java @@ -17,6 +17,10 @@ package android.graphics.pdf; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.Rect; import android.os.ParcelFileDescriptor; import android.system.ErrnoException; import android.system.OsConstants; @@ -55,6 +59,10 @@ public final class PdfEditor { * * @param input Seekable file descriptor to read from. * + * @throws java.io.IOException If an error occurs while reading the file. + * @throws java.lang.SecurityException If the file requires a password or + * the security scheme is not supported. + * * @see #close() */ public PdfEditor(@NonNull ParcelFileDescriptor input) throws IOException { @@ -98,6 +106,109 @@ public final class PdfEditor { } /** + * Sets a transformation and clip for a given page. The transformation matrix if + * non-null must be affine as per {@link android.graphics.Matrix#isAffine()}. If + * the clip is null, then no clipping is performed. + * + * @param pageIndex The page whose transform to set. + * @param transform The transformation to apply. + * @param clip The clip to apply. + */ + public void setTransformAndClip(int pageIndex, @Nullable Matrix transform, + @Nullable Rect clip) { + throwIfClosed(); + throwIfPageNotInDocument(pageIndex); + throwIfNotNullAndNotAfine(transform); + if (transform == null) { + transform = Matrix.IDENTITY_MATRIX; + } + if (clip == null) { + Point size = new Point(); + getPageSize(pageIndex, size); + nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance, + 0, 0, size.x, size.y); + } else { + nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance, + clip.left, clip.top, clip.right, clip.bottom); + } + } + + /** + * Gets the size of a given page in mils (1/72"). + * + * @param pageIndex The page index. + * @param outSize The size output. + */ + public void getPageSize(int pageIndex, @NonNull Point outSize) { + throwIfClosed(); + throwIfOutSizeNull(outSize); + throwIfPageNotInDocument(pageIndex); + nativeGetPageSize(mNativeDocument, pageIndex, outSize); + } + + /** + * Gets the media box of a given page in mils (1/72"). + * + * @param pageIndex The page index. + * @param outMediaBox The media box output. + */ + public boolean getPageMediaBox(int pageIndex, @NonNull Rect outMediaBox) { + throwIfClosed(); + throwIfOutMediaBoxNull(outMediaBox); + throwIfPageNotInDocument(pageIndex); + return nativeGetPageMediaBox(mNativeDocument, pageIndex, outMediaBox); + } + + /** + * Sets the media box of a given page in mils (1/72"). + * + * @param pageIndex The page index. + * @param mediaBox The media box. + */ + public void setPageMediaBox(int pageIndex, @NonNull Rect mediaBox) { + throwIfClosed(); + throwIfMediaBoxNull(mediaBox); + throwIfPageNotInDocument(pageIndex); + nativeSetPageMediaBox(mNativeDocument, pageIndex, mediaBox); + } + + /** + * Gets the crop box of a given page in mils (1/72"). + * + * @param pageIndex The page index. + * @param outCropBox The crop box output. + */ + public boolean getPageCropBox(int pageIndex, @NonNull Rect outCropBox) { + throwIfClosed(); + throwIfOutCropBoxNull(outCropBox); + throwIfPageNotInDocument(pageIndex); + return nativeGetPageCropBox(mNativeDocument, pageIndex, outCropBox); + } + + /** + * Sets the crop box of a given page in mils (1/72"). + * + * @param pageIndex The page index. + * @param cropBox The crop box. + */ + public void setPageCropBox(int pageIndex, @NonNull Rect cropBox) { + throwIfClosed(); + throwIfCropBoxNull(cropBox); + throwIfPageNotInDocument(pageIndex); + nativeSetPageCropBox(mNativeDocument, pageIndex, cropBox); + } + + /** + * Gets whether the document prefers to be scaled for printing. + * + * @return Whether to scale the document. + */ + public boolean shouldScaleForPrinting() { + throwIfClosed(); + return nativeScaleForPrinting(mNativeDocument); + } + + /** * Writes the PDF file to the provided destination. * <p> * <strong>Note:</strong> This method takes ownership of the passed in file @@ -154,9 +265,57 @@ public final class PdfEditor { } } + private void throwIfNotNullAndNotAfine(Matrix matrix) { + if (matrix != null && !matrix.isAffine()) { + throw new IllegalStateException("Matrix must be afine"); + } + } + + private void throwIfOutSizeNull(Point outSize) { + if (outSize == null) { + throw new NullPointerException("outSize cannot be null"); + } + } + + private void throwIfOutMediaBoxNull(Rect outMediaBox) { + if (outMediaBox == null) { + throw new NullPointerException("outMediaBox cannot be null"); + } + } + + private void throwIfMediaBoxNull(Rect mediaBox) { + if (mediaBox == null) { + throw new NullPointerException("mediaBox cannot be null"); + } + } + + private void throwIfOutCropBoxNull(Rect outCropBox) { + if (outCropBox == null) { + throw new NullPointerException("outCropBox cannot be null"); + } + } + + private void throwIfCropBoxNull(Rect cropBox) { + if (cropBox == null) { + throw new NullPointerException("cropBox cannot be null"); + } + } + private static native long nativeOpen(int fd, long size); private static native void nativeClose(long documentPtr); private static native int nativeGetPageCount(long documentPtr); private static native int nativeRemovePage(long documentPtr, int pageIndex); private static native void nativeWrite(long documentPtr, int fd); + private static native void nativeSetTransformAndClip(long documentPtr, int pageIndex, + long transformPtr, int clipLeft, int clipTop, int clipRight, int clipBottom); + private static native void nativeGetPageSize(long documentPtr, int pageIndex, Point outSize); + private static native boolean nativeGetPageMediaBox(long documentPtr, int pageIndex, + Rect outMediaBox); + private static native void nativeSetPageMediaBox(long documentPtr, int pageIndex, + Rect mediaBox); + private static native boolean nativeGetPageCropBox(long documentPtr, int pageIndex, + Rect outMediaBox); + private static native void nativeSetPageCropBox(long documentPtr, int pageIndex, + Rect mediaBox); + private static native boolean nativeScaleForPrinting(long documentPtr); } diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java index 359c294..79934da 100644 --- a/graphics/java/android/graphics/pdf/PdfRenderer.java +++ b/graphics/java/android/graphics/pdf/PdfRenderer.java @@ -131,6 +131,10 @@ public final class PdfRenderer implements AutoCloseable { * </p> * * @param input Seekable file descriptor to read from. + * + * @throws java.io.IOException If an error occurs while reading the file. + * @throws java.lang.SecurityException If the file requires a password or + * the security scheme is not supported. */ public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException { if (input == null) { |
