Merge changes from topics 'KeyboardFocusHighlight-oc-dev', 'KeyboardFocusHighlight_ mDefaultFocusHighlightEnabled-oc-dev', 'KeyboardFocusHighlight_ hasFocusStateSpecified-oc-dev' into oc-dev
* changes:
Fix broken CTS tests.
Draw a default focus highlight if needed.
Add a public API defaultFocusHighlightEnabled.
Detect unhandled keyboard focused state.
diff --git a/api/current.txt b/api/current.txt
index 463e555..c490b17 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -471,6 +471,7 @@
field public static final deprecated int dayOfWeekBackground = 16843924; // 0x1010494
field public static final deprecated int dayOfWeekTextAppearance = 16843925; // 0x1010495
field public static final int debuggable = 16842767; // 0x101000f
+ field public static final int defaultFocusHighlightEnabled = 16844133; // 0x1010565
field public static final int defaultHeight = 16844021; // 0x10104f5
field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504
field public static final int defaultValue = 16843245; // 0x10101ed
@@ -45343,6 +45344,7 @@
method public java.lang.CharSequence getContentDescription();
method public final android.content.Context getContext();
method protected android.view.ContextMenu.ContextMenuInfo getContextMenuInfo();
+ method public final boolean getDefaultFocusHighlightEnabled();
method public static int getDefaultSize(int, int);
method public android.view.Display getDisplay();
method public final int[] getDrawableState();
@@ -45661,6 +45663,7 @@
method public void setClipToOutline(boolean);
method public void setContentDescription(java.lang.CharSequence);
method public void setContextClickable(boolean);
+ method public void setDefaultFocusHighlightEnabled(boolean);
method public void setDrawingCacheBackgroundColor(int);
method public void setDrawingCacheEnabled(boolean);
method public void setDrawingCacheQuality(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 95d1264..ed486b8 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -587,6 +587,7 @@
field public static final deprecated int dayOfWeekBackground = 16843924; // 0x1010494
field public static final deprecated int dayOfWeekTextAppearance = 16843925; // 0x1010495
field public static final int debuggable = 16842767; // 0x101000f
+ field public static final int defaultFocusHighlightEnabled = 16844133; // 0x1010565
field public static final int defaultHeight = 16844021; // 0x10104f5
field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504
field public static final int defaultValue = 16843245; // 0x10101ed
@@ -48900,6 +48901,7 @@
method public java.lang.CharSequence getContentDescription();
method public final android.content.Context getContext();
method protected android.view.ContextMenu.ContextMenuInfo getContextMenuInfo();
+ method public final boolean getDefaultFocusHighlightEnabled();
method public static int getDefaultSize(int, int);
method public android.view.Display getDisplay();
method public final int[] getDrawableState();
@@ -49218,6 +49220,7 @@
method public void setClipToOutline(boolean);
method public void setContentDescription(java.lang.CharSequence);
method public void setContextClickable(boolean);
+ method public void setDefaultFocusHighlightEnabled(boolean);
method public void setDrawingCacheBackgroundColor(int);
method public void setDrawingCacheEnabled(boolean);
method public void setDrawingCacheQuality(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index b202b44..35e5bfc 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -471,6 +471,7 @@
field public static final deprecated int dayOfWeekBackground = 16843924; // 0x1010494
field public static final deprecated int dayOfWeekTextAppearance = 16843925; // 0x1010495
field public static final int debuggable = 16842767; // 0x101000f
+ field public static final int defaultFocusHighlightEnabled = 16844133; // 0x1010565
field public static final int defaultHeight = 16844021; // 0x10104f5
field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504
field public static final int defaultValue = 16843245; // 0x10101ed
@@ -14029,6 +14030,7 @@
method public boolean getPadding(android.graphics.Rect);
method public int[] getState();
method public android.graphics.Region getTransparentRegion();
+ method public boolean hasFocusStateSpecified();
method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public void invalidateSelf();
@@ -45715,6 +45717,7 @@
method public java.lang.CharSequence getContentDescription();
method public final android.content.Context getContext();
method protected android.view.ContextMenu.ContextMenuInfo getContextMenuInfo();
+ method public final boolean getDefaultFocusHighlightEnabled();
method public static int getDefaultSize(int, int);
method public android.view.Display getDisplay();
method public final int[] getDrawableState();
@@ -46037,6 +46040,7 @@
method public void setClipToOutline(boolean);
method public void setContentDescription(java.lang.CharSequence);
method public void setContextClickable(boolean);
+ method public void setDefaultFocusHighlightEnabled(boolean);
method public void setDrawingCacheBackgroundColor(int);
method public void setDrawingCacheEnabled(boolean);
method public void setDrawingCacheQuality(int);
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index a46db06..faf2381 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -91,7 +91,7 @@
* 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.
* <p>
- * 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.
*
* <a name="ItemAttributes"></a>
@@ -521,6 +521,15 @@
}
/**
+ * 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 051de8a..4bbc0f8 100644
--- a/core/java/android/util/StateSet.java
+++ b/core/java/android/util/StateSet.java
@@ -228,6 +228,29 @@
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/core/java/android/view/View.java b/core/java/android/view/View.java
index a09db9c..9072bf9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3920,6 +3920,14 @@
private int mBackgroundResource;
private boolean mBackgroundSizeChanged;
+ /** The default focus highlight.
+ * @see #mDefaultFocusHighlightEnabled
+ * @see Drawable#hasFocusStateSpecified()
+ */
+ private Drawable mDefaultFocusHighlight;
+ private Drawable mDefaultFocusHighlightCache;
+ private boolean mDefaultFocusHighlightSizeChanged;
+
private String mTransitionName;
static class TintInfo {
@@ -4102,6 +4110,12 @@
*/
int mNextClusterForwardId = View.NO_ID;
+ /**
+ * Whether this View should use a default focus highlight when it gets focused but doesn't
+ * have {@link android.R.attr#state_focused} defined in its background.
+ */
+ boolean mDefaultFocusHighlightEnabled = true;
+
private CheckForLongPress mPendingCheckForLongPress;
private CheckForTap mPendingCheckForTap = null;
private PerformClick mPerformClick;
@@ -5086,6 +5100,11 @@
setImportantForAutofill(a.getInt(attr, IMPORTANT_FOR_AUTOFILL_AUTO));
}
break;
+ case R.styleable.View_defaultFocusHighlightEnabled:
+ if (a.peekValue(attr) != null) {
+ setDefaultFocusHighlightEnabled(a.getBoolean(attr, true));
+ }
+ break;
}
}
@@ -6800,6 +6819,9 @@
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
+ // Here we check whether we still need the default focus highlight, and switch it on/off.
+ switchDefaultFocusHighlight();
+
InputMethodManager imm = InputMethodManager.peekInstance();
if (!gainFocus) {
if (isPressed()) {
@@ -9986,6 +10008,33 @@
}
/**
+ * Sets whether this View should use a default focus highlight when it gets focused but doesn't
+ * have {@link android.R.attr#state_focused} defined in its background.
+ *
+ * @param defaultFocusHighlightEnabled {@code true} to set this view to use a default focus
+ * highlight, {@code false} otherwise.
+ *
+ * @attr ref android.R.styleable#View_defaultFocusHighlightEnabled
+ */
+ public void setDefaultFocusHighlightEnabled(boolean defaultFocusHighlightEnabled) {
+ mDefaultFocusHighlightEnabled = defaultFocusHighlightEnabled;
+ }
+
+ /**
+
+ /**
+ * Returns whether this View should use a default focus highlight when it gets focused but
+ * doesn't have {@link android.R.attr#state_focused} defined in its background.
+ *
+ * @return True if this View should use a default focus highlight.
+ * @attr ref android.R.styleable#View_defaultFocusHighlightEnabled
+ */
+ @ViewDebug.ExportedProperty(category = "defaultFocusHighlightEnabled")
+ public final boolean getDefaultFocusHighlightEnabled() {
+ return mDefaultFocusHighlightEnabled;
+ }
+
+ /**
* If a user manually specified the next view id for a particular direction,
* use the root to look up the view.
* @param root The root view of the hierarchy containing this view.
@@ -11752,6 +11801,10 @@
if (dr != null && isVisible != dr.isVisible()) {
dr.setVisible(isVisible, false);
}
+ final Drawable hl = mDefaultFocusHighlight;
+ if (hl != null && isVisible != hl.isVisible()) {
+ hl.setVisible(isVisible, false);
+ }
final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (fg != null && isVisible != fg.isVisible()) {
fg.setVisible(isVisible, false);
@@ -12965,6 +13018,7 @@
if ((changed & DRAW_MASK) != 0) {
if ((mViewFlags & WILL_NOT_DRAW) != 0) {
if (mBackground != null
+ || mDefaultFocusHighlight != null
|| (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
} else {
@@ -13036,6 +13090,7 @@
}
mBackgroundSizeChanged = true;
+ mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
@@ -13927,6 +13982,7 @@
invalidate(true);
}
mBackgroundSizeChanged = true;
+ mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
@@ -13995,6 +14051,7 @@
invalidate(true);
}
mBackgroundSizeChanged = true;
+ mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
@@ -14057,6 +14114,7 @@
invalidate(true);
}
mBackgroundSizeChanged = true;
+ mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
@@ -14116,6 +14174,7 @@
invalidate(true);
}
mBackgroundSizeChanged = true;
+ mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
@@ -18720,6 +18779,9 @@
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
+ // Step 7, draw the default focus highlight
+ drawDefaultFocusHighlight(canvas);
+
if (debugDraw()) {
debugDrawFocus(canvas);
}
@@ -19278,6 +19340,7 @@
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
+ mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
@@ -19429,6 +19492,9 @@
if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
mForegroundInfo.mDrawable.setLayoutDirection(layoutDirection);
}
+ if (mDefaultFocusHighlight != null) {
+ mDefaultFocusHighlight.setLayoutDirection(layoutDirection);
+ }
mPrivateFlags2 |= PFLAG2_DRAWABLE_RESOLVED;
onResolveDrawables(layoutDirection);
}
@@ -19487,7 +19553,8 @@
// Avoid verifying the scroll bar drawable so that we don't end up in
// an invalidation loop. This effectively prevents the scroll bar
// drawable from triggering invalidations and scheduling runnables.
- return who == mBackground || (mForegroundInfo != null && mForegroundInfo.mDrawable == who);
+ return who == mBackground || (mForegroundInfo != null && mForegroundInfo.mDrawable == who)
+ || (mDefaultFocusHighlight == who);
}
/**
@@ -19511,6 +19578,11 @@
changed |= bg.setState(state);
}
+ final Drawable hl = mDefaultFocusHighlight;
+ if (hl != null && hl.isStateful()) {
+ changed |= hl.setState(state);
+ }
+
final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (fg != null && fg.isStateful()) {
changed |= fg.setState(state);
@@ -19550,6 +19622,9 @@
if (mBackground != null) {
mBackground.setHotspot(x, y);
}
+ if (mDefaultFocusHighlight != null) {
+ mDefaultFocusHighlight.setHotspot(x, y);
+ }
if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
mForegroundInfo.mDrawable.setHotspot(x, y);
}
@@ -19586,6 +19661,106 @@
}
/**
+ * Create a default focus highlight if it doesn't exist.
+ * @return a default focus highlight.
+ */
+ private Drawable getDefaultFocusHighlightDrawable() {
+ if (mDefaultFocusHighlightCache == null) {
+ if (mContext != null) {
+ final int[] attrs = new int[] { android.R.attr.selectableItemBackground };
+ final TypedArray ta = mContext.obtainStyledAttributes(attrs);
+ mDefaultFocusHighlightCache = ta.getDrawable(0);
+ ta.recycle();
+ }
+ }
+ return mDefaultFocusHighlightCache;
+ }
+
+ /**
+ * Set the current default focus highlight.
+ * @param highlight the highlight drawable, or {@code null} if it's no longer needed.
+ */
+ private void setDefaultFocusHighlight(Drawable highlight) {
+ mDefaultFocusHighlight = highlight;
+ mDefaultFocusHighlightSizeChanged = true;
+ if (highlight != null) {
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
+ mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+ }
+ highlight.setLayoutDirection(getLayoutDirection());
+ if (highlight.isStateful()) {
+ highlight.setState(getDrawableState());
+ }
+ if (isAttachedToWindow()) {
+ highlight.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
+ }
+ // Set callback last, since the view may still be initializing.
+ highlight.setCallback(this);
+ } else if ((mViewFlags & WILL_NOT_DRAW) != 0 && mBackground == null
+ && (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
+ mPrivateFlags |= PFLAG_SKIP_DRAW;
+ }
+ requestLayout();
+ invalidate();
+ }
+
+ /**
+ * Check whether we need to draw a default focus highlight when this view gets focused,
+ * which requires:
+ * <ul>
+ * <li>In the background, {@link android.R.attr#state_focused} is not defined.</li>
+ * <li>This view is not in touch mode.</li>
+ * <li>This view doesn't opt out for a default focus highlight, via
+ * {@link #setDefaultFocusHighlightEnabled(boolean)}.</li>
+ * <li>This view is attached to window.</li>
+ * </ul>
+ * @return {@code true} if a default focus highlight is needed.
+ */
+ private boolean isDefaultFocusHighlightNeeded(Drawable background) {
+ final boolean hasFocusStateSpecified = background == null || !background.isStateful()
+ || !background.hasFocusStateSpecified();
+ return !isInTouchMode() && getDefaultFocusHighlightEnabled() && hasFocusStateSpecified
+ && isAttachedToWindow();
+ }
+
+ /**
+ * When this view is focused, switches on/off the default focused highlight.
+ * <p>
+ * This always happens when this view is focused, and only at this moment the default focus
+ * highlight can be visible.
+ */
+ private void switchDefaultFocusHighlight() {
+ if (isFocused()) {
+ final boolean needed = isDefaultFocusHighlightNeeded(mBackground);
+ final boolean active = mDefaultFocusHighlight != null;
+ if (needed && !active) {
+ setDefaultFocusHighlight(getDefaultFocusHighlightDrawable());
+ } else if (!needed && active) {
+ // The highlight is no longer needed, so tear it down.
+ setDefaultFocusHighlight(null);
+ }
+ }
+ }
+
+ /**
+ * Draw the default focus highlight onto the canvas.
+ * @param canvas the canvas where we're drawing the highlight.
+ */
+ private void drawDefaultFocusHighlight(Canvas canvas) {
+ if (mDefaultFocusHighlight != null) {
+ if (mDefaultFocusHighlightSizeChanged) {
+ mDefaultFocusHighlightSizeChanged = false;
+ final int l = mScrollX;
+ final int r = l + mRight - mLeft;
+ final int t = mScrollY;
+ final int b = t + mBottom - mTop;
+ mDefaultFocusHighlight.setBounds(l, t, r, b);
+ }
+ mDefaultFocusHighlight.draw(canvas);
+ }
+ }
+
+ /**
* Return an array of resource IDs of the drawable states representing the
* current state of the view.
*
@@ -19725,6 +19900,9 @@
if (mStateListAnimator != null) {
mStateListAnimator.jumpToCurrentState();
}
+ if (mDefaultFocusHighlight != null) {
+ mDefaultFocusHighlight.jumpToCurrentState();
+ }
if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
mForegroundInfo.mDrawable.jumpToCurrentState();
}
@@ -19869,6 +20047,7 @@
/* Remove the background */
mBackground = null;
if ((mViewFlags & WILL_NOT_DRAW) != 0
+ && (mDefaultFocusHighlight == null)
&& (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
@@ -20060,7 +20239,8 @@
}
// Set callback last, since the view may still be initializing.
foreground.setCallback(this);
- } else if ((mViewFlags & WILL_NOT_DRAW) != 0 && mBackground == null) {
+ } else if ((mViewFlags & WILL_NOT_DRAW) != 0 && mBackground == null
+ && (mDefaultFocusHighlight == null)) {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
requestLayout();
@@ -21875,6 +22055,11 @@
// Similarly, we remove the foreground drawable's non-transparent parts.
applyDrawableToTransparentRegion(mForegroundInfo.mDrawable, region);
}
+ if (mDefaultFocusHighlight != null
+ && mDefaultFocusHighlight.getOpacity() != PixelFormat.TRANSPARENT) {
+ // Similarly, we remove the default focus highlight's non-transparent parts.
+ applyDrawableToTransparentRegion(mDefaultFocusHighlight, region);
+ }
}
}
return true;
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d26d952..221e736 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2953,6 +2953,9 @@
See {@link android.view.View#setFocusedByDefault(boolean)}. -->
<attr name="focusedByDefault" format="boolean" />
+ <!-- Whether this View should use a default focus highlight when it gets focused but
+ doesn't have {@link android.R.attr#state_focused} defined in its background. -->
+ <attr name="defaultFocusHighlightEnabled" format="boolean" />
</declare-styleable>
<!-- Attributes that can be assigned to a tag for a particular View. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f364e70..ed940b3 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2814,6 +2814,7 @@
<public name="iconTintMode" />
<public name="maxAspectRatio"/>
<public name="iconSpaceReserved"/>
+ <public name="defaultFocusHighlightEnabled" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 8bbb929..2ae2ca0 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -322,6 +322,7 @@
<item name="scrollbars">vertical</item>
<item name="fadingEdge">vertical</item>
<item name="fastScrollStyle">?attr/fastScrollStyle</item>
+ <item name="defaultFocusHighlightEnabled">false</item>
</style>
<style name="Widget.GestureOverlayView">
@@ -538,6 +539,7 @@
<item name="gravity">center_vertical</item>
<item name="breakStrategy">simple</item>
<item name="hyphenationFrequency">normal</item>
+ <item name="defaultFocusHighlightEnabled">false</item>
</style>
<style name="Widget.ExpandableListView" parent="Widget.ListView">
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index 643c0da..6dfe03d 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -728,6 +728,12 @@
return mLayerState.isStateful();
}
+ /** @hide */
+ @Override
+ public boolean hasFocusStateSpecified() {
+ return mLayerState.hasFocusStateSpecified();
+ }
+
@Override
protected boolean onStateChange(int[] state) {
boolean changed = false;
@@ -1035,6 +1041,17 @@
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 6deeb0d..5004667 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -737,6 +737,12 @@
|| 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 7524cac..559e3d3 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -207,6 +207,12 @@
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 850f40e..44fb1c7 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -26,6 +26,7 @@
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 @@
}
/**
+ * Indicates whether this drawable has at least one state spec explicitly
+ * specifying {@link android.R.attr#state_focused}.
+ *
+ * <p>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.</p>
+ *
+ * @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 a491d7e..aa4cd9c 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -242,6 +242,18 @@
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 5887939..431b63b 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -323,6 +323,12 @@
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 7aad7df..6c3aea2 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -987,6 +987,15 @@
|| (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 b159f0f..4725c2c 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -1476,6 +1476,12 @@
return mLayerState.isStateful();
}
+ /** @hide */
+ @Override
+ public boolean hasFocusStateSpecified() {
+ return mLayerState.hasFocusStateSpecified();
+ }
+
@Override
protected boolean onStateChange(int[] state) {
boolean changed = false;
@@ -2117,6 +2123,18 @@
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 c7183d9..1790020 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -574,6 +574,12 @@
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 f83c160..bfd0604 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -377,6 +377,12 @@
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 6758607..c43899b 100644
--- a/graphics/java/android/graphics/drawable/ShapeDrawable.java
+++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java
@@ -352,6 +352,12 @@
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 <em>super.inflateTag(...)</em>.
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index 64a9eb5..c98f160 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -90,6 +90,12 @@
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 @@
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 a1539b8..41e5af1 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -413,6 +413,12 @@
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 @@
|| (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 @@
}
@Override
+ public boolean hasFocusStateSpecified() {
+ boolean result = false;
+
+ final ArrayList<VObject> 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 @@
}
@Override
+ public boolean hasFocusStateSpecified() {
+ return false;
+ }
+
+ @Override
int getNativeSize() {
return NATIVE_ALLOCATION_SIZE;
}
@@ -1819,6 +1850,14 @@
}
@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 @@
abstract void applyTheme(Theme t);
abstract boolean onStateChange(int[] state);
abstract boolean isStateful();
+ abstract boolean hasFocusStateSpecified();
abstract int getNativeSize();
abstract Property getProperty(String propertyName);
}