1d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette/*
245c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette * Copyright (C) 2014 The Android Open Source Project
3d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette *
4d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * Licensed under the Apache License, Version 2.0 (the "License");
5d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * you may not use this file except in compliance with the License.
6d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * You may obtain a copy of the License at
7d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette *
8d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette *      http://www.apache.org/licenses/LICENSE-2.0
9d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette *
10d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * Unless required by applicable law or agreed to in writing, software
11d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * distributed under the License is distributed on an "AS IS" BASIS,
12d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * See the License for the specific language governing permissions and
14d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * limitations under the License.
15d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette */
16d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
17d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverettepackage android.graphics.drawable;
18d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
19cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viveretteimport com.android.internal.R;
20cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette
21cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viveretteimport org.xmlpull.v1.XmlPullParser;
22cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viveretteimport org.xmlpull.v1.XmlPullParserException;
23cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette
247275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viveretteimport android.annotation.NonNull;
257275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viveretteimport android.annotation.Nullable;
26ac85f90466dd60d2af8ffc3942d503a0de606726Alan Viveretteimport android.content.pm.ActivityInfo.Config;
27d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.content.res.ColorStateList;
28d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.content.res.Resources;
2952b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viveretteimport android.content.res.Resources.Theme;
30d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.content.res.TypedArray;
316dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viveretteimport android.graphics.Bitmap;
326dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viveretteimport android.graphics.BitmapShader;
33d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.graphics.Canvas;
34d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.graphics.Color;
356dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viveretteimport android.graphics.Matrix;
367c0517272ba2d97084739a14fea78641b265eb5dAlan Viveretteimport android.graphics.Outline;
37d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.graphics.Paint;
38d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.graphics.PixelFormat;
396dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viveretteimport android.graphics.PorterDuff;
406dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viveretteimport android.graphics.PorterDuffColorFilter;
41d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.graphics.Rect;
426dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viveretteimport android.graphics.Shader;
43d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.util.AttributeSet;
44d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
45d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport java.io.IOException;
4640e38d43675b9baa4383058e5afd5291291abc81Alan Viveretteimport java.util.Arrays;
47d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
48d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette/**
49ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * Drawable that shows a ripple effect in response to state changes. The
50ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * anchoring position of the ripple for a given state may be specified by
51c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette * calling {@link #setHotspot(float, float)} with the corresponding state
52ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * attribute identifier.
53ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * <p>
54ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * A touch feedback drawable may contain multiple child layers, including a
55d21be499753b964433faefbe0fd770c62a4c2e12Alan Viverette * special mask layer that is not drawn to the screen. A single layer may be
56d21be499753b964433faefbe0fd770c62a4c2e12Alan Viverette * set as the mask from XML by specifying its {@code android:id} value as
57d21be499753b964433faefbe0fd770c62a4c2e12Alan Viverette * {@link android.R.id#mask}. At run time, a single layer may be set as the
58d21be499753b964433faefbe0fd770c62a4c2e12Alan Viverette * mask using {@code setId(..., android.R.id.mask)} or an existing mask layer
59d21be499753b964433faefbe0fd770c62a4c2e12Alan Viverette * may be replaced using {@code setDrawableByLayerId(android.R.id.mask, ...)}.
60a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <pre>
61a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <code>&lt!-- A red ripple masked against an opaque rectangle. --/>
62a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * &ltripple android:color="#ffff0000">
63a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette *   &ltitem android:id="@android:id/mask"
647275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viverette *         android:drawable="@android:color/white" />
656dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette * &lt/ripple></code>
66a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * </pre>
67ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * <p>
68ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * If a mask layer is set, the ripple effect will be masked against that layer
69a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * before it is drawn over the composite of the remaining child layers.
70ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * <p>
71a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * If no mask layer is set, the ripple effect is masked against the composite
72a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * of the child layers.
73a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <pre>
746dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette * <code>&lt!-- A green ripple drawn atop a black rectangle. --/>
75a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * &ltripple android:color="#ff00ff00">
767275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viverette *   &ltitem android:drawable="@android:color/black" />
776dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette * &lt/ripple>
78a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette *
796dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette * &lt!-- A blue ripple drawn atop a drawable resource. --/>
806dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette * &ltripple android:color="#ff0000ff">
81a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette *   &ltitem android:drawable="@drawable/my_drawable" />
826dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette * &lt/ripple></code>
83a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * </pre>
84ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * <p>
85ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * If no child layers or mask is specified and the ripple is set as a View
86a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * background, the ripple will be drawn atop the first available parent
87a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * background within the View's hierarchy. In this case, the drawing region
88a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * may extend outside of the Drawable bounds.
89a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <pre>
906dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette * <code>&lt!-- An unbounded red ripple. --/>
916dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette * &ltripple android:color="#ffff0000" /></code>
92a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * </pre>
93ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette *
94a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * @attr ref android.R.styleable#RippleDrawable_color
95d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette */
96c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverettepublic class RippleDrawable extends LayerDrawable {
9788d44d4adaa68db02e4eef68606591028a92add0Alan Viverette    /**
9888d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     * Radius value that specifies the ripple radius should be computed based
9988d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     * on the size of the ripple's container.
10088d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     */
10188d44d4adaa68db02e4eef68606591028a92add0Alan Viverette    public static final int RADIUS_AUTO = -1;
10288d44d4adaa68db02e4eef68606591028a92add0Alan Viverette
1036dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private static final int MASK_UNKNOWN = -1;
1046dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private static final int MASK_NONE = 0;
1056dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private static final int MASK_CONTENT = 1;
1066dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private static final int MASK_EXPLICIT = 2;
107dccbe8b02a34a3c78028a31ee158d4d2818c72baAlan Viverette
10847bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette    /** The maximum number of ripples supported. */
10947bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette    private static final int MAX_RIPPLES = 10;
11047bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette
111d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    private final Rect mTempRect = new Rect();
112d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
11361956606818918194a38e045a8e35e7108480e5eAlan Viverette    /** Current ripple effect bounds, used to constrain ripple effects. */
11461956606818918194a38e045a8e35e7108480e5eAlan Viverette    private final Rect mHotspotBounds = new Rect();
11561956606818918194a38e045a8e35e7108480e5eAlan Viverette
116d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    /** Current drawing bounds, used to compute dirty region. */
117d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    private final Rect mDrawingBounds = new Rect();
118d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
119d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    /** Current dirty bounds, union of current and previous drawing bounds. */
120d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    private final Rect mDirtyBounds = new Rect();
121d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
1225004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette    /** Mirrors mLayerState with some extra information. */
1235004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette    private RippleState mState;
124d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
125b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette    /** The masking layer, e.g. the layer with id R.id.mask. */
126b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette    private Drawable mMask;
127b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette
1286ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    /** The current background. May be actively animating or pending entry. */
1296ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    private RippleBackground mBackground;
1306ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
1316dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private Bitmap mMaskBuffer;
1326dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private BitmapShader mMaskShader;
1336dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private Canvas mMaskCanvas;
1346dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private Matrix mMaskMatrix;
1356dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private PorterDuffColorFilter mMaskColorFilter;
1366dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private boolean mHasValidMask;
1376dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
1386ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    /** Whether we expect to draw a background when visible. */
1396ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    private boolean mBackgroundActive;
1406ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
1416ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    /** The current ripple. May be actively animating or pending entry. */
142f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette    private RippleForeground mRipple;
1436ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
1446ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    /** Whether we expect to draw a ripple when visible. */
1456ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    private boolean mRippleActive;
1466ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
1476ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    // Hotspot coordinates that are awaiting activation.
1486ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    private float mPendingX;
1496ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    private float mPendingY;
1506ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    private boolean mHasPending;
151d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
152ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette    /**
153ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette     * Lazily-created array of actively animating ripples. Inactive ripples are
154ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette     * pruned during draw(). The locations of these will not change.
155ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette     */
156f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette    private RippleForeground[] mExitingRipples;
157fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette    private int mExitingRipplesCount = 0;
158d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
159d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    /** Paint used to control appearance of ripples. */
160d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    private Paint mRipplePaint;
161d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
162d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    /** Target density of the display into which ripples are drawn. */
163bd5294bcb857a48ad22ddd54b13208ec2903c3f6Alan Viverette    private int mDensity;
164d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
16561956606818918194a38e045a8e35e7108480e5eAlan Viverette    /** Whether bounds are being overridden. */
16661956606818918194a38e045a8e35e7108480e5eAlan Viverette    private boolean mOverrideBounds;
16761956606818918194a38e045a8e35e7108480e5eAlan Viverette
1686ae2d7cefafd7fbf5e7c8d7d3c1869e66056b7f8Alan Viverette    /**
169b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi     * If set, force all ripple animations to not run on RenderThread, even if it would be
170b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi     * available.
171b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi     */
172b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi    private boolean mForceSoftware;
173b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi
174b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi    /**
1756ae2d7cefafd7fbf5e7c8d7d3c1869e66056b7f8Alan Viverette     * Constructor used for drawable inflation.
1766ae2d7cefafd7fbf5e7c8d7d3c1869e66056b7f8Alan Viverette     */
177c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    RippleDrawable() {
17817cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8bAlan Viverette        this(new RippleState(null, null, null), null);
1794d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
1804d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
1814d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    /**
1827275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viverette     * Creates a new ripple drawable with the specified ripple color and
1837275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viverette     * optional content and mask drawables.
1844d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     *
1857275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viverette     * @param color The ripple color
1864d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @param content The content drawable, may be {@code null}
1874d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @param mask The mask drawable, may be {@code null}
1884d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     */
1897275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viverette    public RippleDrawable(@NonNull ColorStateList color, @Nullable Drawable content,
1907275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viverette            @Nullable Drawable mask) {
19117cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8bAlan Viverette        this(new RippleState(null, null, null), null);
1924d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
1937275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viverette        if (color == null) {
1947275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viverette            throw new IllegalArgumentException("RippleDrawable requires a non-null color");
1957275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viverette        }
1967275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viverette
1974d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (content != null) {
1984d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            addLayer(content, null, 0, 0, 0, 0, 0);
1994d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
2004d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
2014d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (mask != null) {
202935b1fa24d05533a95ee47425ab9bedb31641012Alan Viverette            addLayer(mask, null, android.R.id.mask, 0, 0, 0, 0);
2034d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
2044d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
2057275abde37f0ad2df50e78de2fee1c0cfeb9cd92Alan Viverette        setColor(color);
2064d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        ensurePadding();
20706ff2af68aa1041eeb26778e994e0fe196bf8b1eAlan Viverette        refreshPadding();
20845c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette        updateLocalState();
209c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    }
210c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette
211c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    @Override
212d78a44576c6bac5541e04c1f38599d43c9943653Alan Viverette    public void jumpToCurrentState() {
213d78a44576c6bac5541e04c1f38599d43c9943653Alan Viverette        super.jumpToCurrentState();
214d78a44576c6bac5541e04c1f38599d43c9943653Alan Viverette
215d78a44576c6bac5541e04c1f38599d43c9943653Alan Viverette        if (mRipple != null) {
216f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette            mRipple.end();
217d78a44576c6bac5541e04c1f38599d43c9943653Alan Viverette        }
218d78a44576c6bac5541e04c1f38599d43c9943653Alan Viverette
219d78a44576c6bac5541e04c1f38599d43c9943653Alan Viverette        if (mBackground != null) {
220f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette            mBackground.end();
221d78a44576c6bac5541e04c1f38599d43c9943653Alan Viverette        }
222d78a44576c6bac5541e04c1f38599d43c9943653Alan Viverette
223cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        cancelExitingRipples();
224fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette    }
225fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette
226fc8882f1340055e00f6c21449983e92413045275Alan Viverette    private void cancelExitingRipples() {
227fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette        final int count = mExitingRipplesCount;
228f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        final RippleForeground[] ripples = mExitingRipples;
229d78a44576c6bac5541e04c1f38599d43c9943653Alan Viverette        for (int i = 0; i < count; i++) {
230f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette            ripples[i].end();
23140e38d43675b9baa4383058e5afd5291291abc81Alan Viverette        }
232fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette
23340e38d43675b9baa4383058e5afd5291291abc81Alan Viverette        if (ripples != null) {
23440e38d43675b9baa4383058e5afd5291291abc81Alan Viverette            Arrays.fill(ripples, 0, count, null);
235d78a44576c6bac5541e04c1f38599d43c9943653Alan Viverette        }
236fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette        mExitingRipplesCount = 0;
2372ae56403542582bd39c0e522bf29844d59300f37Alan Viverette
238fc8882f1340055e00f6c21449983e92413045275Alan Viverette        // Always draw an additional "clean" frame after canceling animations.
23915ce834e52806378d6ab2b90f573bae14cb3fd4bAlan Viverette        invalidateSelf(false);
240d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
241d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
242d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
243d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    public int getOpacity() {
24447bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        // Worst-case scenario.
24547bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        return PixelFormat.TRANSLUCENT;
246ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette    }
247ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
248ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette    @Override
24912b97f5d2b15194ed6673c9838b13c8312157709Alan Viverette    protected boolean onStateChange(int[] stateSet) {
250a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette        final boolean changed = super.onStateChange(stateSet);
251ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
2521b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        boolean enabled = false;
2536ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        boolean pressed = false;
2546ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        boolean focused = false;
255e4f976dc3b7dd2548deb409b6fd421c6c47f6b42George Mount        boolean hovered = false;
2566ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
257a8a8ff000b2902eb4e187e62be39fd9535c6c839Alan Viverette        for (int state : stateSet) {
258a8a8ff000b2902eb4e187e62be39fd9535c6c839Alan Viverette            if (state == R.attr.state_enabled) {
2591b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette                enabled = true;
260f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette            } else if (state == R.attr.state_focused) {
2616ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette                focused = true;
262f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette            } else if (state == R.attr.state_pressed) {
2636ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette                pressed = true;
264e4f976dc3b7dd2548deb409b6fd421c6c47f6b42George Mount            } else if (state == R.attr.state_hovered) {
265e4f976dc3b7dd2548deb409b6fd421c6c47f6b42George Mount                hovered = true;
2664d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            }
267d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        }
2686ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
2696ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        setRippleActive(enabled && pressed);
270e4f976dc3b7dd2548deb409b6fd421c6c47f6b42George Mount        setBackgroundActive(hovered || focused || (enabled && pressed), focused || hovered);
271d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
272a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette        return changed;
273d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
274d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
2756ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    private void setRippleActive(boolean active) {
2766ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        if (mRippleActive != active) {
2776ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            mRippleActive = active;
2786ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            if (active) {
279fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette                tryRippleEnter();
2806ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            } else {
281fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette                tryRippleExit();
2826ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            }
2836ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        }
2846ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    }
2854d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
286f92f26fef215897bd302c1c06adbe5d853881b3fAlan Viverette    private void setBackgroundActive(boolean active, boolean focused) {
2876ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        if (mBackgroundActive != active) {
2886ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            mBackgroundActive = active;
2894d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            if (active) {
290f92f26fef215897bd302c1c06adbe5d853881b3fAlan Viverette                tryBackgroundEnter(focused);
2914d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            } else {
292fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette                tryBackgroundExit();
2934d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            }
2944d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
2954d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
2964d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
297307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette    @Override
298c3f35b01b5a21e110ca4eedf09c8c6164ab85dfbAlan Viverette    protected void onBoundsChange(Rect bounds) {
299c3f35b01b5a21e110ca4eedf09c8c6164ab85dfbAlan Viverette        super.onBoundsChange(bounds);
300d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
30161956606818918194a38e045a8e35e7108480e5eAlan Viverette        if (!mOverrideBounds) {
30261956606818918194a38e045a8e35e7108480e5eAlan Viverette            mHotspotBounds.set(bounds);
303dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette            onHotspotBoundsChanged();
30461956606818918194a38e045a8e35e7108480e5eAlan Viverette        }
305c3f35b01b5a21e110ca4eedf09c8c6164ab85dfbAlan Viverette
3065bc0144302bb378e5f007baa4bf6620ab9031879Alan Viverette        if (mBackground != null) {
3075bc0144302bb378e5f007baa4bf6620ab9031879Alan Viverette            mBackground.onBoundsChange();
3085bc0144302bb378e5f007baa4bf6620ab9031879Alan Viverette        }
3095bc0144302bb378e5f007baa4bf6620ab9031879Alan Viverette
3105bc0144302bb378e5f007baa4bf6620ab9031879Alan Viverette        if (mRipple != null) {
3115bc0144302bb378e5f007baa4bf6620ab9031879Alan Viverette            mRipple.onBoundsChange();
3125bc0144302bb378e5f007baa4bf6620ab9031879Alan Viverette        }
3135bc0144302bb378e5f007baa4bf6620ab9031879Alan Viverette
314ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        invalidateSelf();
315c3f35b01b5a21e110ca4eedf09c8c6164ab85dfbAlan Viverette    }
316c3f35b01b5a21e110ca4eedf09c8c6164ab85dfbAlan Viverette
317c3f35b01b5a21e110ca4eedf09c8c6164ab85dfbAlan Viverette    @Override
318307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette    public boolean setVisible(boolean visible, boolean restart) {
3196ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        final boolean changed = super.setVisible(visible, restart);
3206ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
321307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette        if (!visible) {
32253d1cfe2d073bff7c7771d5f7dd9108062ddc706Alan Viverette            clearHotspots();
3236ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        } else if (changed) {
3246ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            // If we just became visible, ensure the background and ripple
3256ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            // visibilities are consistent with their internal states.
3266ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            if (mRippleActive) {
327fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette                tryRippleEnter();
3286ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            }
3296ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
3306ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            if (mBackgroundActive) {
331f92f26fef215897bd302c1c06adbe5d853881b3fAlan Viverette                tryBackgroundEnter(false);
3326ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            }
333f92f26fef215897bd302c1c06adbe5d853881b3fAlan Viverette
334f92f26fef215897bd302c1c06adbe5d853881b3fAlan Viverette            // Skip animations, just show the correct final states.
335f92f26fef215897bd302c1c06adbe5d853881b3fAlan Viverette            jumpToCurrentState();
336307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette        }
337307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette
3386ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        return changed;
339307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette    }
340307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette
341d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    /**
342d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette     * @hide
343d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette     */
344d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
345d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    public boolean isProjected() {
34699ca2a8470a48906aaba2d76c856037933496352Alan Viverette        // If the layer is bounded, then we don't need to project.
34799ca2a8470a48906aaba2d76c856037933496352Alan Viverette        if (isBounded()) {
34899ca2a8470a48906aaba2d76c856037933496352Alan Viverette            return false;
34999ca2a8470a48906aaba2d76c856037933496352Alan Viverette        }
35099ca2a8470a48906aaba2d76c856037933496352Alan Viverette
35199ca2a8470a48906aaba2d76c856037933496352Alan Viverette        // Otherwise, if the maximum radius is contained entirely within the
35299ca2a8470a48906aaba2d76c856037933496352Alan Viverette        // bounds then we don't need to project. This is sort of a hack to
35399ca2a8470a48906aaba2d76c856037933496352Alan Viverette        // prevent check box ripples from being projected across the edges of
35499ca2a8470a48906aaba2d76c856037933496352Alan Viverette        // scroll views. It does not impact rendering performance, and it can
35599ca2a8470a48906aaba2d76c856037933496352Alan Viverette        // be removed once we have better handling of projection in scrollable
35699ca2a8470a48906aaba2d76c856037933496352Alan Viverette        // views.
357388cd3b69c51a449b8bbb8086dbc429f24783ad1Alan Viverette        final int radius = mState.mMaxRadius;
35899ca2a8470a48906aaba2d76c856037933496352Alan Viverette        final Rect drawableBounds = getBounds();
35999ca2a8470a48906aaba2d76c856037933496352Alan Viverette        final Rect hotspotBounds = mHotspotBounds;
36099ca2a8470a48906aaba2d76c856037933496352Alan Viverette        if (radius != RADIUS_AUTO
36199ca2a8470a48906aaba2d76c856037933496352Alan Viverette                && radius <= hotspotBounds.width() / 2
36299ca2a8470a48906aaba2d76c856037933496352Alan Viverette                && radius <= hotspotBounds.height() / 2
36399ca2a8470a48906aaba2d76c856037933496352Alan Viverette                && (drawableBounds.equals(hotspotBounds)
36499ca2a8470a48906aaba2d76c856037933496352Alan Viverette                        || drawableBounds.contains(hotspotBounds))) {
365388cd3b69c51a449b8bbb8086dbc429f24783ad1Alan Viverette            return false;
366388cd3b69c51a449b8bbb8086dbc429f24783ad1Alan Viverette        }
367388cd3b69c51a449b8bbb8086dbc429f24783ad1Alan Viverette
36899ca2a8470a48906aaba2d76c856037933496352Alan Viverette        return true;
369388cd3b69c51a449b8bbb8086dbc429f24783ad1Alan Viverette    }
370388cd3b69c51a449b8bbb8086dbc429f24783ad1Alan Viverette
371388cd3b69c51a449b8bbb8086dbc429f24783ad1Alan Viverette    private boolean isBounded() {
372388cd3b69c51a449b8bbb8086dbc429f24783ad1Alan Viverette        return getNumberOfLayers() > 0;
373d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
374d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
375d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
376d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    public boolean isStateful() {
377ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        return true;
378d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
379d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
38045c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette    /**
38145c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette     * Sets the ripple color.
38245c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette     *
38345c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette     * @param color Ripple color as a color state list.
38488d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     *
38588d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     * @attr ref android.R.styleable#RippleDrawable_color
38645c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette     */
387a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette    public void setColor(ColorStateList color) {
388a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        mState.mColor = color;
38915ce834e52806378d6ab2b90f573bae14cb3fd4bAlan Viverette        invalidateSelf(false);
3902f8ba8f7fad2b608102a9282219aaea2223e94f5Alan Viverette    }
3912f8ba8f7fad2b608102a9282219aaea2223e94f5Alan Viverette
39288d44d4adaa68db02e4eef68606591028a92add0Alan Viverette    /**
39388d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     * Sets the radius in pixels of the fully expanded ripple.
39488d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     *
39588d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     * @param radius ripple radius in pixels, or {@link #RADIUS_AUTO} to
39688d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     *               compute the radius based on the container size
39788d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     * @attr ref android.R.styleable#RippleDrawable_radius
39888d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     */
39988d44d4adaa68db02e4eef68606591028a92add0Alan Viverette    public void setRadius(int radius) {
40088d44d4adaa68db02e4eef68606591028a92add0Alan Viverette        mState.mMaxRadius = radius;
40115ce834e52806378d6ab2b90f573bae14cb3fd4bAlan Viverette        invalidateSelf(false);
40288d44d4adaa68db02e4eef68606591028a92add0Alan Viverette    }
40388d44d4adaa68db02e4eef68606591028a92add0Alan Viverette
40488d44d4adaa68db02e4eef68606591028a92add0Alan Viverette    /**
40588d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     * @return the radius in pixels of the fully expanded ripple if an explicit
40688d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     *         radius has been set, or {@link #RADIUS_AUTO} if the radius is
40788d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     *         computed based on the container size
40888d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     * @attr ref android.R.styleable#RippleDrawable_radius
40988d44d4adaa68db02e4eef68606591028a92add0Alan Viverette     */
41088d44d4adaa68db02e4eef68606591028a92add0Alan Viverette    public int getRadius() {
41188d44d4adaa68db02e4eef68606591028a92add0Alan Viverette        return mState.mMaxRadius;
41288d44d4adaa68db02e4eef68606591028a92add0Alan Viverette    }
41388d44d4adaa68db02e4eef68606591028a92add0Alan Viverette
414d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
4150cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
4160cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette            @NonNull AttributeSet attrs, @Nullable Theme theme)
417d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette            throws XmlPullParserException, IOException {
418e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RippleDrawable);
419cb29189c29a553a8005044f32de37a610f2857dbAlan Viverette
420e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette        // Force padding default to STACK before inflating.
421e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette        setPaddingMode(PADDING_MODE_STACK);
422e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette
4230cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette        // Inflation will advance the XmlPullParser and AttributeSet.
42447bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        super.inflate(r, parser, attrs, theme);
425ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
4260cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette        updateStateFromTypedArray(a);
4270cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette        verifyRequiredAttributes(a);
4280cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette        a.recycle();
42945c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette
43045c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette        updateLocalState();
431d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette    }
432d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
433d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette    @Override
434d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette    public boolean setDrawableByLayerId(int id, Drawable drawable) {
435d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        if (super.setDrawableByLayerId(id, drawable)) {
436d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            if (id == R.id.mask) {
437b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette                mMask = drawable;
438690a16c35031b4474f429de1967d6738f9d00322Alan Viverette                mHasValidMask = false;
439d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            }
440d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
441d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            return true;
442d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        }
443d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
444d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        return false;
445ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette    }
446ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
447ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette    /**
448e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     * Specifies how layer padding should affect the bounds of subsequent
449e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     * layers. The default and recommended value for RippleDrawable is
450e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     * {@link #PADDING_MODE_STACK}.
451e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *
452e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     * @param mode padding mode, one of:
453e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *            <ul>
454e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *            <li>{@link #PADDING_MODE_NEST} to nest each layer inside the
455e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *            padding of the previous layer
456e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *            <li>{@link #PADDING_MODE_STACK} to stack each layer directly
457e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *            atop the previous layer
458e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *            </ul>
459e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     * @see #getPaddingMode()
460e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     */
461e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette    @Override
462e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette    public void setPaddingMode(int mode) {
463e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette        super.setPaddingMode(mode);
464e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette    }
465e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette
466e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette    /**
46752b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette     * Initializes the constant state from the values in the typed array.
46852b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette     */
4690cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette    private void updateStateFromTypedArray(@NonNull TypedArray a) throws XmlPullParserException {
470c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette        final RippleState state = mState;
47152b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
4728e5e11b99fac942122ee2d6cdd30af51564861aeAlan Viverette        // Account for any configuration changes.
4738e5e11b99fac942122ee2d6cdd30af51564861aeAlan Viverette        state.mChangingConfigurations |= a.getChangingConfigurations();
4748e5e11b99fac942122ee2d6cdd30af51564861aeAlan Viverette
47552b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        // Extract the theme attributes, if any.
476ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        state.mTouchThemeAttrs = a.extractThemeAttrs();
47752b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
478a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        final ColorStateList color = a.getColorStateList(R.styleable.RippleDrawable_color);
479a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        if (color != null) {
480a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette            mState.mColor = color;
48152b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        }
48252b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
48388d44d4adaa68db02e4eef68606591028a92add0Alan Viverette        mState.mMaxRadius = a.getDimensionPixelSize(
48488d44d4adaa68db02e4eef68606591028a92add0Alan Viverette                R.styleable.RippleDrawable_radius, mState.mMaxRadius);
48534a14f967ab6c88829c9a36ce6e909c47b3ee398Alan Viverette    }
48634a14f967ab6c88829c9a36ce6e909c47b3ee398Alan Viverette
4870cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
48834a14f967ab6c88829c9a36ce6e909c47b3ee398Alan Viverette        if (mState.mColor == null && (mState.mTouchThemeAttrs == null
48934a14f967ab6c88829c9a36ce6e909c47b3ee398Alan Viverette                || mState.mTouchThemeAttrs[R.styleable.RippleDrawable_color] == 0)) {
4904d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            throw new XmlPullParserException(a.getPositionDescription() +
491a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette                    ": <ripple> requires a valid color attribute");
4924d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
49352b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    }
49452b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
49552b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    @Override
4960cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette    public void applyTheme(@NonNull Theme t) {
49752b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        super.applyTheme(t);
49852b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
499c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette        final RippleState state = mState;
50045c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette        if (state == null) {
501ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette            return;
50252b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        }
50352b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
50445c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette        if (state.mTouchThemeAttrs != null) {
50545c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette            final TypedArray a = t.resolveAttributes(state.mTouchThemeAttrs,
50645c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette                    R.styleable.RippleDrawable);
50745c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette            try {
50845c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette                updateStateFromTypedArray(a);
5090cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette                verifyRequiredAttributes(a);
51045c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette            } catch (XmlPullParserException e) {
511c078c605ab904b0e4a5d793cbeffd78c340f2816Alan Viverette                rethrowAsRuntimeException(e);
51245c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette            } finally {
51345c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette                a.recycle();
51445c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette            }
51545c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette        }
51645c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette
51745c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette        if (state.mColor != null && state.mColor.canApplyTheme()) {
518e0f95f39c5a669a48ee3ebb8dc45bf2d7ee940f1Alan Viverette            state.mColor = state.mColor.obtainForTheme(t);
5194d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
520b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette
52145c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette        updateLocalState();
52252b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    }
52352b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
52452b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    @Override
52552b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    public boolean canApplyTheme() {
526d21fd9d1ccd2b525f9c004a6cd9ba19a645701abAlan Viverette        return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
52752b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    }
52852b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
529d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
530c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    public void setHotspot(float x, float y) {
5316ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        if (mRipple == null || mBackground == null) {
5326ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            mPendingX = x;
5336ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            mPendingY = y;
5346ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            mHasPending = true;
5356ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        }
5366ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
5376ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        if (mRipple != null) {
5386ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            mRipple.move(x, y);
5396ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        }
5406ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    }
5416ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
5426ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    /**
5436ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette     * Creates an active hotspot at the specified location.
5446ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette     */
545f92f26fef215897bd302c1c06adbe5d853881b3fAlan Viverette    private void tryBackgroundEnter(boolean focused) {
5466ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        if (mBackground == null) {
5470671f05fa94a234652c1cf3c6e0c2e123566f76fAlan Viverette            final boolean isBounded = isBounded();
5480671f05fa94a234652c1cf3c6e0c2e123566f76fAlan Viverette            mBackground = new RippleBackground(this, mHotspotBounds, isBounded, mForceSoftware);
5496ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        }
5506ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
551bd5294bcb857a48ad22ddd54b13208ec2903c3f6Alan Viverette        mBackground.setup(mState.mMaxRadius, mDensity);
552f92f26fef215897bd302c1c06adbe5d853881b3fAlan Viverette        mBackground.enter(focused);
5536ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette    }
5546ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
555fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette    private void tryBackgroundExit() {
5566ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        if (mBackground != null) {
5576ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            // Don't null out the background, we need it to draw!
5586ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            mBackground.exit();
559ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        }
560ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette    }
561ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette
562ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette    /**
563fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette     * Attempts to start an enter animation for the active hotspot. Fails if
564fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette     * there are too many animating ripples.
565ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette     */
566fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette    private void tryRippleEnter() {
567fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette        if (mExitingRipplesCount >= MAX_RIPPLES) {
5684d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            // This should never happen unless the user is tapping like a maniac
5694d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            // or there is a bug that's preventing ripples from being removed.
5704d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            return;
5714d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
5724d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
5736ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        if (mRipple == null) {
5746ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            final float x;
5756ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            final float y;
5766ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            if (mHasPending) {
5776ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette                mHasPending = false;
5786ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette                x = mPendingX;
5796ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette                y = mPendingY;
5806ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            } else {
5816ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette                x = mHotspotBounds.exactCenterX();
5826ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette                y = mHotspotBounds.exactCenterY();
5836ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            }
5846a67db41388165aca63d0d5de2830cc096ed930bAlan Viverette
585388cd3b69c51a449b8bbb8086dbc429f24783ad1Alan Viverette            final boolean isBounded = isBounded();
586b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi            mRipple = new RippleForeground(this, mHotspotBounds, x, y, isBounded, mForceSoftware);
587ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        }
588ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette
589cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        mRipple.setup(mState.mMaxRadius, mDensity);
590f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        mRipple.enter(false);
591ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette    }
592ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette
593fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette    /**
594fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette     * Attempts to start an exit animation for the active hotspot. Fails if
595fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette     * there is no active hotspot.
596fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette     */
597fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette    private void tryRippleExit() {
5986ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        if (mRipple != null) {
599fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette            if (mExitingRipples == null) {
600f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette                mExitingRipples = new RippleForeground[MAX_RIPPLES];
601fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette            }
602fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette            mExitingRipples[mExitingRipplesCount++] = mRipple;
6036ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            mRipple.exit();
6046ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            mRipple = null;
605d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        }
606d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
607d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
608fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette    /**
609fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette     * Cancels and removes the active ripple, all exiting ripples, and the
610fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette     * background. Nothing will be drawn after this method is called.
611fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette     */
612c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    private void clearHotspots() {
613b6417b8b9492d88ccfbb723decaece1bb9ff0f73Alan Viverette        if (mRipple != null) {
614f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette            mRipple.end();
615b6417b8b9492d88ccfbb723decaece1bb9ff0f73Alan Viverette            mRipple = null;
616b942b6f15c51c2ff48c59d8f620ee6156d00f67eAlan Viverette            mRippleActive = false;
617b6417b8b9492d88ccfbb723decaece1bb9ff0f73Alan Viverette        }
618b6417b8b9492d88ccfbb723decaece1bb9ff0f73Alan Viverette
619b6417b8b9492d88ccfbb723decaece1bb9ff0f73Alan Viverette        if (mBackground != null) {
620f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette            mBackground.end();
621b6417b8b9492d88ccfbb723decaece1bb9ff0f73Alan Viverette            mBackground = null;
622b942b6f15c51c2ff48c59d8f620ee6156d00f67eAlan Viverette            mBackgroundActive = false;
623d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        }
624d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
625cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        cancelExitingRipples();
626d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
627d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
62861956606818918194a38e045a8e35e7108480e5eAlan Viverette    @Override
62961956606818918194a38e045a8e35e7108480e5eAlan Viverette    public void setHotspotBounds(int left, int top, int right, int bottom) {
63061956606818918194a38e045a8e35e7108480e5eAlan Viverette        mOverrideBounds = true;
63161956606818918194a38e045a8e35e7108480e5eAlan Viverette        mHotspotBounds.set(left, top, right, bottom);
632dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette
633dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette        onHotspotBoundsChanged();
634dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette    }
635dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette
6367068c39526459c18a020e29c1ebfa6aed54e2d0fAlan Viverette    @Override
6377068c39526459c18a020e29c1ebfa6aed54e2d0fAlan Viverette    public void getHotspotBounds(Rect outRect) {
6387068c39526459c18a020e29c1ebfa6aed54e2d0fAlan Viverette        outRect.set(mHotspotBounds);
6397068c39526459c18a020e29c1ebfa6aed54e2d0fAlan Viverette    }
6407068c39526459c18a020e29c1ebfa6aed54e2d0fAlan Viverette
641dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette    /**
642dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette     * Notifies all the animating ripples that the hotspot bounds have changed.
643dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette     */
644dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette    private void onHotspotBoundsChanged() {
645fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette        final int count = mExitingRipplesCount;
646f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        final RippleForeground[] ripples = mExitingRipples;
647dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette        for (int i = 0; i < count; i++) {
648dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette            ripples[i].onHotspotBoundsChanged();
649dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette        }
6506ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
651a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette        if (mRipple != null) {
652a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette            mRipple.onHotspotBoundsChanged();
653a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette        }
654a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette
6556ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        if (mBackground != null) {
6566ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            mBackground.onHotspotBoundsChanged();
6576ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        }
65861956606818918194a38e045a8e35e7108480e5eAlan Viverette    }
65961956606818918194a38e045a8e35e7108480e5eAlan Viverette
6607c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette    /**
6617c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette     * Populates <code>outline</code> with the first available layer outline,
66277b5cad3efedd20f2b7cc14d87ccce1b0261960aChris Craik     * excluding the mask layer.
6637c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette     *
6647c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette     * @param outline Outline in which to place the first available layer outline
6657c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette     */
6667c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette    @Override
66731ba192dd201df2cad96a8c503f730130ab0d80fChris Craik    public void getOutline(@NonNull Outline outline) {
6687c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette        final LayerState state = mLayerState;
6697c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette        final ChildDrawable[] children = state.mChildren;
6707c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette        final int N = state.mNum;
6717c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette        for (int i = 0; i < N; i++) {
67231ba192dd201df2cad96a8c503f730130ab0d80fChris Craik            if (children[i].mId != R.id.mask) {
67331ba192dd201df2cad96a8c503f730130ab0d80fChris Craik                children[i].mDrawable.getOutline(outline);
67431ba192dd201df2cad96a8c503f730130ab0d80fChris Craik                if (!outline.isEmpty()) return;
6757c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette            }
6767c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette        }
6777c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette    }
6787c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette
679cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette    /**
680cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette     * Optimized for drawing ripples with a mask layer and optional content.
681cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette     */
682d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
6837c0517272ba2d97084739a14fea78641b265eb5dAlan Viverette    public void draw(@NonNull Canvas canvas) {
684f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        pruneRipples();
685f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette
686cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        // Clip to the dirty bounds, which will be the drawable bounds if we
687cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        // have a mask or content and the ripple bounds if we're projecting.
688be0dd99bac781d1dd73cb67f33bcd931e3693af5Alan Viverette        final Rect bounds = getDirtyBounds();
689a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette        final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
690a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette        canvas.clipRect(bounds);
6914d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
6926dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        drawContent(canvas);
6936dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        drawBackgroundAndRipples(canvas);
6946dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
6956dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        canvas.restoreToCount(saveCount);
6966dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    }
6976dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
6986dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    @Override
6996dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    public void invalidateSelf() {
70015ce834e52806378d6ab2b90f573bae14cb3fd4bAlan Viverette        invalidateSelf(true);
70115ce834e52806378d6ab2b90f573bae14cb3fd4bAlan Viverette    }
70215ce834e52806378d6ab2b90f573bae14cb3fd4bAlan Viverette
70315ce834e52806378d6ab2b90f573bae14cb3fd4bAlan Viverette    void invalidateSelf(boolean invalidateMask) {
7046dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        super.invalidateSelf();
7056dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
70615ce834e52806378d6ab2b90f573bae14cb3fd4bAlan Viverette        if (invalidateMask) {
70715ce834e52806378d6ab2b90f573bae14cb3fd4bAlan Viverette            // Force the mask to update on the next draw().
70815ce834e52806378d6ab2b90f573bae14cb3fd4bAlan Viverette            mHasValidMask = false;
70915ce834e52806378d6ab2b90f573bae14cb3fd4bAlan Viverette        }
71015ce834e52806378d6ab2b90f573bae14cb3fd4bAlan Viverette
7116dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    }
7126dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
713f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette    private void pruneRipples() {
714f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        int remaining = 0;
715f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette
716f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        // Move remaining entries into pruned spaces.
717f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        final RippleForeground[] ripples = mExitingRipples;
718f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        final int count = mExitingRipplesCount;
719f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        for (int i = 0; i < count; i++) {
720f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette            if (!ripples[i].hasFinishedExit()) {
721f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette                ripples[remaining++] = ripples[i];
722f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette            }
723f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        }
724f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette
725f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        // Null out the remaining entries.
726f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        for (int i = remaining; i < count; i++) {
727f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette            ripples[i] = null;
728f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        }
729f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette
730f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        mExitingRipplesCount = remaining;
731f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette    }
732f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette
7336dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    /**
7346dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette     * @return whether we need to use a mask
7356dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette     */
7366dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private void updateMaskShaderIfNeeded() {
7376dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        if (mHasValidMask) {
7386dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            return;
7396dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        }
7406dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
7416dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        final int maskType = getMaskType();
7426dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        if (maskType == MASK_UNKNOWN) {
7436dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            return;
7446dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        }
7456dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
7466dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        mHasValidMask = true;
7476dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
748b74155cf01f959fc9b7909de5a22806ad519f7c9Alan Viverette        final Rect bounds = getBounds();
749b74155cf01f959fc9b7909de5a22806ad519f7c9Alan Viverette        if (maskType == MASK_NONE || bounds.isEmpty()) {
7506dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            if (mMaskBuffer != null) {
7516dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                mMaskBuffer.recycle();
7526dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                mMaskBuffer = null;
7536dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                mMaskShader = null;
7546dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                mMaskCanvas = null;
7556ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            }
7566dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            mMaskMatrix = null;
7576dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            mMaskColorFilter = null;
7586dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            return;
7596ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette        }
7604d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
7616dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        // Ensure we have a correctly-sized buffer.
7626dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        if (mMaskBuffer == null
7636dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                || mMaskBuffer.getWidth() != bounds.width()
7646dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                || mMaskBuffer.getHeight() != bounds.height()) {
7656dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            if (mMaskBuffer != null) {
7666dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                mMaskBuffer.recycle();
7676dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            }
7686dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
7696dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            mMaskBuffer = Bitmap.createBitmap(
7706dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                    bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8);
7716dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            mMaskShader = new BitmapShader(mMaskBuffer,
7726dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
7736dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            mMaskCanvas = new Canvas(mMaskBuffer);
7746dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        } else {
7756dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            mMaskBuffer.eraseColor(Color.TRANSPARENT);
7766dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        }
7776dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
7786dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        if (mMaskMatrix == null) {
7796dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            mMaskMatrix = new Matrix();
7806dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        } else {
7816dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            mMaskMatrix.reset();
7826dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        }
7836dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
7846dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        if (mMaskColorFilter == null) {
7856dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            mMaskColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_IN);
7866dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        }
7876dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
788bac9769146af07753728e5c6b3a30eae3076aed0Alan Viverette        // Draw the appropriate mask anchored to (0,0).
789bac9769146af07753728e5c6b3a30eae3076aed0Alan Viverette        final int left = bounds.left;
790bac9769146af07753728e5c6b3a30eae3076aed0Alan Viverette        final int top = bounds.top;
791bac9769146af07753728e5c6b3a30eae3076aed0Alan Viverette        mMaskCanvas.translate(-left, -top);
7926dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        if (maskType == MASK_EXPLICIT) {
7936dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            drawMask(mMaskCanvas);
7946dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        } else if (maskType == MASK_CONTENT) {
7956dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            drawContent(mMaskCanvas);
7966dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        }
797bac9769146af07753728e5c6b3a30eae3076aed0Alan Viverette        mMaskCanvas.translate(left, top);
7986dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    }
7996dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
8006dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private int getMaskType() {
8016dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        if (mRipple == null && mExitingRipplesCount <= 0
802f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette                && (mBackground == null || !mBackground.isVisible())) {
8036dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            // We might need a mask later.
8046dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            return MASK_UNKNOWN;
8056dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        }
8066dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
8076dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        if (mMask != null) {
8086dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            if (mMask.getOpacity() == PixelFormat.OPAQUE) {
8096dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                // Clipping handles opaque explicit masks.
8106dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                return MASK_NONE;
8116dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            } else {
8126dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                return MASK_EXPLICIT;
8136dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            }
8146dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        }
8156dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
8166dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        // Check for non-opaque, non-mask content.
8176dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        final ChildDrawable[] array = mLayerState.mChildren;
8186dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        final int count = mLayerState.mNum;
8196dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        for (int i = 0; i < count; i++) {
8206dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
8216dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette                return MASK_CONTENT;
8226dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            }
8236dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        }
8246dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
8256dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        // Clipping handles opaque content.
8266dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        return MASK_NONE;
8274d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
8284d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
8296dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private void drawContent(Canvas canvas) {
8306dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        // Draw everything except the mask.
831a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        final ChildDrawable[] array = mLayerState.mChildren;
832a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        final int count = mLayerState.mNum;
8334d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        for (int i = 0; i < count; i++) {
834d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            if (array[i].mId != R.id.mask) {
835d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette                array[i].mDrawable.draw(canvas);
836d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            }
837d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        }
838d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette    }
839d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
8406dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private void drawBackgroundAndRipples(Canvas canvas) {
841f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        final RippleForeground active = mRipple;
8426dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        final RippleBackground background = mBackground;
8436dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        final int count = mExitingRipplesCount;
844f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        if (active == null && count <= 0 && (background == null || !background.isVisible())) {
8456dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            // Move along, nothing to draw here.
8466dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            return;
847cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        }
848323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette
849cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        final float x = mHotspotBounds.exactCenterX();
850cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        final float y = mHotspotBounds.exactCenterY();
851cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        canvas.translate(x, y);
8526ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
8536dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        updateMaskShaderIfNeeded();
8546dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
8556dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        // Position the shader to account for canvas translation.
8566dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        if (mMaskShader != null) {
857f0d69b9e33e32b60bf953fc775b7843825e99036Alan Viverette            final Rect bounds = getBounds();
858f0d69b9e33e32b60bf953fc775b7843825e99036Alan Viverette            mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
8596dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            mMaskShader.setLocalMatrix(mMaskMatrix);
8606dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        }
8616ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
862cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        // Grab the color for the current state and cut the alpha channel in
863cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        // half so that the ripple and background together yield full alpha.
864cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
8656dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        final int halfAlpha = (Color.alpha(color) / 2) << 24;
8666dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        final Paint p = getRipplePaint();
8676dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
8686dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        if (mMaskColorFilter != null) {
8696dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            // The ripple timing depends on the paint's alpha value, so we need
8706dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            // to push just the alpha channel into the paint and let the filter
8716dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            // handle the full-alpha color.
8726dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            final int fullAlphaColor = color | (0xFF << 24);
8736dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            mMaskColorFilter.setColor(fullAlphaColor);
8746dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette
8756dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            p.setColor(halfAlpha);
8766dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            p.setColorFilter(mMaskColorFilter);
8776dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            p.setShader(mMaskShader);
8786dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        } else {
8796dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
8806dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            p.setColor(halfAlphaColor);
8816dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            p.setColorFilter(null);
8826dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette            p.setShader(null);
8836dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette        }
8846ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
885f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette        if (background != null && background.isVisible()) {
886cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette            background.draw(canvas, p);
887cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        }
888d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
889cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        if (count > 0) {
890f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette            final RippleForeground[] ripples = mExitingRipples;
891cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette            for (int i = 0; i < count; i++) {
892cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette                ripples[i].draw(canvas, p);
893d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            }
894ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette        }
895ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
896cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        if (active != null) {
897cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette            active.draw(canvas, p);
898ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        }
899ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette
900cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette        canvas.translate(-x, -y);
901ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette    }
902ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
9036dfa60f33ca6018959ebff1efde82db7d2aed1e3Alan Viverette    private void drawMask(Canvas canvas) {
9041b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        mMask.draw(canvas);
9054d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
9064d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
907a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette    private Paint getRipplePaint() {
908a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette        if (mRipplePaint == null) {
909a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette            mRipplePaint = new Paint();
910a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette            mRipplePaint.setAntiAlias(true);
911cc3c573334a9cd2124a8a0ccf2f37884e36f83faAlan Viverette            mRipplePaint.setStyle(Paint.Style.FILL);
912a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette        }
913a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette        return mRipplePaint;
914a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette    }
915a3f0c2b21a73a82a919abe247c4046d114f3712cAlan Viverette
916d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
917d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    public Rect getDirtyBounds() {
918388cd3b69c51a449b8bbb8086dbc429f24783ad1Alan Viverette        if (!isBounded()) {
919e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            final Rect drawingBounds = mDrawingBounds;
920e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            final Rect dirtyBounds = mDirtyBounds;
921e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            dirtyBounds.set(drawingBounds);
922e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            drawingBounds.setEmpty();
923e3c433aa457138425e514494e4d06590076a1d07Alan Viverette
924e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            final int cX = (int) mHotspotBounds.exactCenterX();
925e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            final int cY = (int) mHotspotBounds.exactCenterY();
926e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            final Rect rippleBounds = mTempRect;
927fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette
928f872ee0057ed247aa93589347f1b53afc99517f8Alan Viverette            final RippleForeground[] activeRipples = mExitingRipples;
929fdbb98e56d4668c7bfa8de59c3c438c0cb69a535Alan Viverette            final int N = mExitingRipplesCount;
930e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            for (int i = 0; i < N; i++) {
931e3c433aa457138425e514494e4d06590076a1d07Alan Viverette                activeRipples[i].getBounds(rippleBounds);
932e3c433aa457138425e514494e4d06590076a1d07Alan Viverette                rippleBounds.offset(cX, cY);
933e3c433aa457138425e514494e4d06590076a1d07Alan Viverette                drawingBounds.union(rippleBounds);
934e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            }
935d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
9366ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            final RippleBackground background = mBackground;
9376ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            if (background != null) {
9386ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette                background.getBounds(rippleBounds);
9396ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette                rippleBounds.offset(cX, cY);
9406ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette                drawingBounds.union(rippleBounds);
9416ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette            }
9426ce6d70f9c78f0197f1369246bf55a5f6b8d7ba4Alan Viverette
943e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            dirtyBounds.union(drawingBounds);
944e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            dirtyBounds.union(super.getDirtyBounds());
945e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            return dirtyBounds;
946e3c433aa457138425e514494e4d06590076a1d07Alan Viverette        } else {
947e3c433aa457138425e514494e4d06590076a1d07Alan Viverette            return getBounds();
948e3c433aa457138425e514494e4d06590076a1d07Alan Viverette        }
949d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
950d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
951b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi    /**
952b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi     * Sets whether to disable RenderThread animations for this ripple.
953b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi     *
954b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi     * @param forceSoftware true if RenderThread animations should be disabled, false otherwise
955b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi     * @hide
956b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi     */
957b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi    public void setForceSoftware(boolean forceSoftware) {
958b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi        mForceSoftware = forceSoftware;
959b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi    }
960b7303a36baf8d0ac3efdeeee3310ef5974ba9ceaJorim Jaggi
9617f610fed107b158c144dca1b20a44ee91eb8c934Alan Viverette    @Override
9627f610fed107b158c144dca1b20a44ee91eb8c934Alan Viverette    public ConstantState getConstantState() {
9637f610fed107b158c144dca1b20a44ee91eb8c934Alan Viverette        return mState;
9647f610fed107b158c144dca1b20a44ee91eb8c934Alan Viverette    }
9657f610fed107b158c144dca1b20a44ee91eb8c934Alan Viverette
9665004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette    @Override
9675004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette    public Drawable mutate() {
9685004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette        super.mutate();
9695004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette
9705004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette        // LayerDrawable creates a new state using createConstantState, so
9715004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette        // this should always be a safe cast.
9725004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette        mState = (RippleState) mLayerState;
973ebc9f2e773c3ebb4d4af3025fc6770844cc8fdefAlan Viverette
974ebc9f2e773c3ebb4d4af3025fc6770844cc8fdefAlan Viverette        // The locally cached drawable may have changed.
975ebc9f2e773c3ebb4d4af3025fc6770844cc8fdefAlan Viverette        mMask = findDrawableByLayerId(R.id.mask);
976ebc9f2e773c3ebb4d4af3025fc6770844cc8fdefAlan Viverette
9775004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette        return this;
9785004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette    }
9795004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette
9805004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette    @Override
9815004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette    RippleState createConstantState(LayerState state, Resources res) {
9825004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette        return new RippleState(state, this, res);
9835004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette    }
9845004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette
985c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    static class RippleState extends LayerState {
98647bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        int[] mTouchThemeAttrs;
98734a14f967ab6c88829c9a36ce6e909c47b3ee398Alan Viverette        ColorStateList mColor = ColorStateList.valueOf(Color.MAGENTA);
9884d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        int mMaxRadius = RADIUS_AUTO;
989d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
9905004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette        public RippleState(LayerState orig, RippleDrawable owner, Resources res) {
99147bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette            super(orig, owner, res);
992ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
9935004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette            if (orig != null && orig instanceof RippleState) {
9945004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette                final RippleState origs = (RippleState) orig;
9955004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette                mTouchThemeAttrs = origs.mTouchThemeAttrs;
9965004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette                mColor = origs.mColor;
9975004032ebc2aee97c5884b7f91cc33d2f98ae8b5Alan Viverette                mMaxRadius = origs.mMaxRadius;
9980cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette
9990cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette                if (origs.mDensity != mDensity) {
10000cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette                    applyDensityScaling(orig.mDensity, mDensity);
10010cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette                }
10020cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette            }
10030cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette        }
10040cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette
10050cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette        @Override
10060cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette        protected void onDensityChanged(int sourceDensity, int targetDensity) {
10070cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette            super.onDensityChanged(sourceDensity, targetDensity);
10080cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette
10090cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette            applyDensityScaling(sourceDensity, targetDensity);
10100cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette        }
10110cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette
10120cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette        private void applyDensityScaling(int sourceDensity, int targetDensity) {
10130cacad7023ad30ad886ef85751e9e39b10f846f9Alan Viverette            if (mMaxRadius != RADIUS_AUTO) {
1014ce52037e0ae0c380f5b834fb3dad105bfaf5e374Alan Viverette                mMaxRadius = Drawable.scaleFromDensity(
1015ce52037e0ae0c380f5b834fb3dad105bfaf5e374Alan Viverette                        mMaxRadius, sourceDensity, targetDensity, true);
1016d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette            }
1017d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        }
1018d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
1019d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        @Override
102052b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        public boolean canApplyTheme() {
102145c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette            return mTouchThemeAttrs != null
102245c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette                    || (mColor != null && mColor.canApplyTheme())
102345c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette                    || super.canApplyTheme();
102452b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        }
102552b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
102652b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        @Override
1027d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        public Drawable newDrawable() {
102817cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8bAlan Viverette            return new RippleDrawable(this, null);
1029d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        }
1030d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
1031d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        @Override
1032d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        public Drawable newDrawable(Resources res) {
103317cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8bAlan Viverette            return new RippleDrawable(this, res);
1034d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        }
1035e0f95f39c5a669a48ee3ebb8dc45bf2d7ee940f1Alan Viverette
1036e0f95f39c5a669a48ee3ebb8dc45bf2d7ee940f1Alan Viverette        @Override
1037ac85f90466dd60d2af8ffc3942d503a0de606726Alan Viverette        public @Config int getChangingConfigurations() {
1038e0f95f39c5a669a48ee3ebb8dc45bf2d7ee940f1Alan Viverette            return super.getChangingConfigurations()
1039e0f95f39c5a669a48ee3ebb8dc45bf2d7ee940f1Alan Viverette                    | (mColor != null ? mColor.getChangingConfigurations() : 0);
1040e0f95f39c5a669a48ee3ebb8dc45bf2d7ee940f1Alan Viverette        }
10414d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
1042d30688f0a0a75743df247831688efcbe2fa62cc4Alan Viverette
104317cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8bAlan Viverette    private RippleDrawable(RippleState state, Resources res) {
104417cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8bAlan Viverette        mState = new RippleState(state, this, res);
104517cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8bAlan Viverette        mLayerState = mState;
1046ce52037e0ae0c380f5b834fb3dad105bfaf5e374Alan Viverette        mDensity = Drawable.resolveDensity(res, mState.mDensity);
104747bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette
104817cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8bAlan Viverette        if (mState.mNum > 0) {
104917cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8bAlan Viverette            ensurePadding();
105006ff2af68aa1041eeb26778e994e0fe196bf8b1eAlan Viverette            refreshPadding();
105147bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        }
105247bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette
105345c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette        updateLocalState();
1054b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette    }
1055b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette
105645c4bbbbce6bbad50a033efcba7948a23f1f117aAlan Viverette    private void updateLocalState() {
1057b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette        // Initialize from constant state.
1058b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette        mMask = findDrawableByLayerId(R.id.mask);
105952b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    }
1060d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette}
1061