From 4a81674b45b7250c4e2a80330371f7aa1c066d05 Mon Sep 17 00:00:00 2001 From: Jiaquan He Date: Thu, 2 Feb 2017 14:26:45 -0800 Subject: [PATCH] Detect unhandled keyboard focused state. A View uses a Drawable as its background, which changes its appearance according to the View's state. This commit adds an algorithm to detect undefined state_focused in state specs for those Drawables. Test: cts-tradefed run singleCommand cts --skip-device-info --skip-preconditions --abi armeabi-v7a -m CtsGraphicsTestCases -t android.graphics.drawable.cts.DefaultFocusHighlightTest Bug: 35096940 Change-Id: I5e9f07141679881fe88f9040aa116ea2f9d537c9 --- core/java/android/content/res/ColorStateList.java | 11 +++++- core/java/android/util/StateSet.java | 23 +++++++++++++ .../graphics/drawable/AdaptiveIconDrawable.java | 17 +++++++++ .../android/graphics/drawable/BitmapDrawable.java | 6 ++++ .../android/graphics/drawable/ColorDrawable.java | 6 ++++ .../java/android/graphics/drawable/Drawable.java | 20 +++++++++++ .../graphics/drawable/DrawableContainer.java | 12 +++++++ .../android/graphics/drawable/DrawableWrapper.java | 6 ++++ .../graphics/drawable/GradientDrawable.java | 9 +++++ .../android/graphics/drawable/LayerDrawable.java | 18 ++++++++++ .../graphics/drawable/NinePatchDrawable.java | 6 ++++ .../android/graphics/drawable/RippleDrawable.java | 6 ++++ .../android/graphics/drawable/ShapeDrawable.java | 6 ++++ .../graphics/drawable/StateListDrawable.java | 10 ++++++ .../android/graphics/drawable/VectorDrawable.java | 40 ++++++++++++++++++++++ 15 files changed, 195 insertions(+), 1 deletion(-) diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index a46db06ebdc0..faf238164a09 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -91,7 +91,7 @@ import java.util.Arrays; * file. An item with no state spec is considered to match any set of states and is generally * useful as a final item to be used as a default. *

- * If an item with no state spec if placed before other items, those items + * If an item with no state spec is placed before other items, those items * will be ignored. * * @@ -521,6 +521,15 @@ public class ColorStateList extends ComplexColor implements Parcelable { } /** + * Return whether the state spec list has at least one item explicitly specifying + * {@link android.R.attr#state_focused}. + * @hide + */ + public boolean hasFocusStateSpecified() { + return StateSet.containsAttribute(mStateSpecs, R.attr.state_focused); + } + + /** * Indicates whether this color state list is opaque, which means that every * color returned from {@link #getColorForState(int[], int)} has an alpha * value of 255. diff --git a/core/java/android/util/StateSet.java b/core/java/android/util/StateSet.java index 051de8a190e1..4bbc0f863603 100644 --- a/core/java/android/util/StateSet.java +++ b/core/java/android/util/StateSet.java @@ -228,6 +228,29 @@ public class StateSet { return true; } + /** + * Check whether a list of state specs has an attribute specified. + * @param stateSpecs a list of state specs we're checking. + * @param attr an attribute we're looking for. + * @return {@code true} if the attribute is contained in the state specs. + * @hide + */ + public static boolean containsAttribute(int[][] stateSpecs, int attr) { + if (stateSpecs != null) { + for (int[] spec : stateSpecs) { + if (spec == null) { + break; + } + for (int specAttr : spec) { + if (specAttr == attr || -specAttr == attr) { + return true; + } + } + } + } + return false; + } + public static int[] trimStateSet(int[] states, int newSize) { if (states.length == newSize) { return states; diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java index 643c0dab0635..6dfe03d83ac0 100644 --- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java +++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java @@ -728,6 +728,12 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback return mLayerState.isStateful(); } + /** @hide */ + @Override + public boolean hasFocusStateSpecified() { + return mLayerState.hasFocusStateSpecified(); + } + @Override protected boolean onStateChange(int[] state) { boolean changed = false; @@ -1035,6 +1041,17 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback return isStateful; } + public final boolean hasFocusStateSpecified() { + final ChildDrawable[] array = mChildren; + for (int i = 0; i < N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null && dr.hasFocusStateSpecified()) { + return true; + } + } + return false; + } + public final boolean canConstantState() { final ChildDrawable[] array = mChildren; for (int i = 0; i < N_CHILDREN; i++) { diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index 6deeb0d9af9a..5004667c1089 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -737,6 +737,12 @@ public class BitmapDrawable extends Drawable { || super.isStateful(); } + /** @hide */ + @Override + public boolean hasFocusStateSpecified() { + return mBitmapState.mTint != null && mBitmapState.mTint.hasFocusStateSpecified(); + } + @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java index 7524cac47491..559e3d3cc98e 100644 --- a/graphics/java/android/graphics/drawable/ColorDrawable.java +++ b/graphics/java/android/graphics/drawable/ColorDrawable.java @@ -207,6 +207,12 @@ public class ColorDrawable extends Drawable { return mColorState.mTint != null && mColorState.mTint.isStateful(); } + /** @hide */ + @Override + public boolean hasFocusStateSpecified() { + return mColorState.mTint != null && mColorState.mTint.hasFocusStateSpecified(); + } + @Override public int getOpacity() { if (mTintFilter != null || mPaint.getColorFilter() != null) { diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 850f40ec3eb6..44fb1c75684e 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -26,6 +26,7 @@ import android.annotation.ColorInt; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -713,6 +714,25 @@ public abstract class Drawable { } /** + * Indicates whether this drawable has at least one state spec explicitly + * specifying {@link android.R.attr#state_focused}. + * + *

Note: A View uses a {@link Drawable} instance as its background and it + * changes its appearance based on a state. On keyboard devices, it should + * specify its {@link android.R.attr#state_focused} to make sure the user + * knows which view is holding the focus.

+ * + * @return {@code true} if {@link android.R.attr#state_focused} is specified + * for this drawable. + * + * @hide + */ + @TestApi + public boolean hasFocusStateSpecified() { + return false; + } + + /** * Specify a set of states for the drawable. These are use-case specific, * so see the relevant documentation. As an example, the background for * widgets like Button understand the following states: diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index a491d7e771dc..aa4cd9cba4a7 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -242,6 +242,18 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return mDrawableContainerState.isStateful(); } + /** @hide */ + @Override + public boolean hasFocusStateSpecified() { + if (mCurrDrawable != null) { + return mCurrDrawable.hasFocusStateSpecified(); + } + if (mLastDrawable != null) { + return mLastDrawable.hasFocusStateSpecified(); + } + return false; + } + @Override public void setAutoMirrored(boolean mirrored) { if (mDrawableContainerState.mAutoMirrored != mirrored) { diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java index 5887939b05a4..431b63bd64e5 100644 --- a/graphics/java/android/graphics/drawable/DrawableWrapper.java +++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java @@ -323,6 +323,12 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb return mDrawable != null && mDrawable.isStateful(); } + /** @hide */ + @Override + public boolean hasFocusStateSpecified() { + return mDrawable != null && mDrawable.hasFocusStateSpecified(); + } + @Override protected boolean onStateChange(int[] state) { if (mDrawable != null && mDrawable.isStateful()) { diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 7aad7df05fed..6c3aea2202a2 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -987,6 +987,15 @@ public class GradientDrawable extends Drawable { || (s.mTint != null && s.mTint.isStateful()); } + /** @hide */ + @Override + public boolean hasFocusStateSpecified() { + final GradientState s = mGradientState; + return (s.mSolidColors != null && s.mSolidColors.hasFocusStateSpecified()) + || (s.mStrokeColors != null && s.mStrokeColors.hasFocusStateSpecified()) + || (s.mTint != null && s.mTint.hasFocusStateSpecified()); + } + @Override public @Config int getChangingConfigurations() { return super.getChangingConfigurations() | mGradientState.getChangingConfigurations(); diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index b159f0f4a2be..4725c2c4c0e5 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -1476,6 +1476,12 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { return mLayerState.isStateful(); } + /** @hide */ + @Override + public boolean hasFocusStateSpecified() { + return mLayerState.hasFocusStateSpecified(); + } + @Override protected boolean onStateChange(int[] state) { boolean changed = false; @@ -2117,6 +2123,18 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { return isStateful; } + public final boolean hasFocusStateSpecified() { + final int N = mNumChildren; + final ChildDrawable[] array = mChildren; + for (int i = 0; i < N; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null && dr.hasFocusStateSpecified()) { + return true; + } + } + return false; + } + public final boolean canConstantState() { final ChildDrawable[] array = mChildren; final int N = mNumChildren; diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index c7183d9e8f50..17900204fa22 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -574,6 +574,12 @@ public class NinePatchDrawable extends Drawable { return super.isStateful() || (s.mTint != null && s.mTint.isStateful()); } + /** @hide */ + @Override + public boolean hasFocusStateSpecified() { + return mNinePatchState.mTint != null && mNinePatchState.mTint.hasFocusStateSpecified(); + } + final static class NinePatchState extends ConstantState { @Config int mChangingConfigurations; diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index f83c160b6c20..bfd0604a7c3f 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -377,6 +377,12 @@ public class RippleDrawable extends LayerDrawable { return true; } + /** @hide */ + @Override + public boolean hasFocusStateSpecified() { + return true; + } + /** * Sets the ripple color. * diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java index 67586077cd0a..c43899b30b0a 100644 --- a/graphics/java/android/graphics/drawable/ShapeDrawable.java +++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java @@ -352,6 +352,12 @@ public class ShapeDrawable extends Drawable { return super.isStateful() || (s.mTint != null && s.mTint.isStateful()); } + /** @hide */ + @Override + public boolean hasFocusStateSpecified() { + return mShapeState.mTint != null && mShapeState.mTint.hasFocusStateSpecified(); + } + /** * Subclasses override this to parse custom subelements. If you handle it, * return true, else return super.inflateTag(...). diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java index 64a9eb5380cd..c98f1608c665 100644 --- a/graphics/java/android/graphics/drawable/StateListDrawable.java +++ b/graphics/java/android/graphics/drawable/StateListDrawable.java @@ -90,6 +90,12 @@ public class StateListDrawable extends DrawableContainer { return true; } + /** @hide */ + @Override + public boolean hasFocusStateSpecified() { + return mStateListState.hasFocusStateSpecified(); + } + @Override protected boolean onStateChange(int[] stateSet) { final boolean changed = super.onStateChange(stateSet); @@ -342,6 +348,10 @@ public class StateListDrawable extends DrawableContainer { return -1; } + boolean hasFocusStateSpecified() { + return StateSet.containsAttribute(mStateSets, R.attr.state_focused); + } + @Override public Drawable newDrawable() { return new StateListDrawable(this, null); diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index a1539b8f805a..41e5af13358d 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -413,6 +413,12 @@ public class VectorDrawable extends Drawable { return super.isStateful() || (mVectorState != null && mVectorState.isStateful()); } + /** @hide */ + @Override + public boolean hasFocusStateSpecified() { + return mVectorState != null && mVectorState.hasFocusStateSpecified(); + } + @Override protected boolean onStateChange(int[] stateSet) { boolean changed = false; @@ -976,6 +982,11 @@ public class VectorDrawable extends Drawable { || (mRootGroup != null && mRootGroup.isStateful()); } + public boolean hasFocusStateSpecified() { + return mTint != null && mTint.hasFocusStateSpecified() + || (mRootGroup != null && mRootGroup.hasFocusStateSpecified()); + } + void setViewportSize(float viewportWidth, float viewportHeight) { mViewportWidth = viewportWidth; mViewportHeight = viewportHeight; @@ -1326,6 +1337,21 @@ public class VectorDrawable extends Drawable { } @Override + public boolean hasFocusStateSpecified() { + boolean result = false; + + final ArrayList children = mChildren; + for (int i = 0, count = children.size(); i < count; i++) { + final VObject child = children.get(i); + if (child.isStateful()) { + result |= child.hasFocusStateSpecified(); + } + } + + return result; + } + + @Override int getNativeSize() { // Return the native allocation needed for the subtree. int size = NATIVE_ALLOCATION_SIZE; @@ -1569,6 +1595,11 @@ public class VectorDrawable extends Drawable { } @Override + public boolean hasFocusStateSpecified() { + return false; + } + + @Override int getNativeSize() { return NATIVE_ALLOCATION_SIZE; } @@ -1819,6 +1850,14 @@ public class VectorDrawable extends Drawable { } @Override + public boolean hasFocusStateSpecified() { + return (mStrokeColors != null && mStrokeColors instanceof ColorStateList && + ((ColorStateList) mStrokeColors).hasFocusStateSpecified()) && + (mFillColors != null && mFillColors instanceof ColorStateList && + ((ColorStateList) mFillColors).hasFocusStateSpecified()); + } + + @Override int getNativeSize() { return NATIVE_ALLOCATION_SIZE; } @@ -2116,6 +2155,7 @@ public class VectorDrawable extends Drawable { abstract void applyTheme(Theme t); abstract boolean onStateChange(int[] state); abstract boolean isStateful(); + abstract boolean hasFocusStateSpecified(); abstract int getNativeSize(); abstract Property getProperty(String propertyName); } -- 2.11.0