diff options
22 files changed, 398 insertions, 3 deletions
diff --git a/api/current.txt b/api/current.txt index 463e55569a64..c490b17b51b2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -471,6 +471,7 @@ package android { 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 @@ package android.view { 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 @@ package android.view { 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 95d1264c61f7..ed486b8eae32 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -587,6 +587,7 @@ package android { 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 @@ package android.view { 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 @@ package android.view { 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 b202b44ebcce..35e5bfc63b9f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -471,6 +471,7 @@ package android { 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 @@ package android.graphics.drawable { 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 @@ package android.view { 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 @@ package android.view { 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 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. * <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 @@ 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/core/java/android/view/View.java b/core/java/android/view/View.java index a09db9c3972d..9072bf93d9d8 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3920,6 +3920,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mBackgroundSizeChanged = true; + mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo != null) { mForegroundInfo.mBoundsChanged = true; } @@ -13927,6 +13982,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidate(true); } mBackgroundSizeChanged = true; + mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo != null) { mForegroundInfo.mBoundsChanged = true; } @@ -13995,6 +14051,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidate(true); } mBackgroundSizeChanged = true; + mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo != null) { mForegroundInfo.mBoundsChanged = true; } @@ -14057,6 +14114,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidate(true); } mBackgroundSizeChanged = true; + mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo != null) { mForegroundInfo.mBoundsChanged = true; } @@ -14116,6 +14174,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidate(true); } mBackgroundSizeChanged = true; + mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo != null) { mForegroundInfo.mBoundsChanged = true; } @@ -18720,6 +18779,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= drawn; mBackgroundSizeChanged = true; + mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo != null) { mForegroundInfo.mBoundsChanged = true; } @@ -19429,6 +19492,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mStateListAnimator != null) { mStateListAnimator.jumpToCurrentState(); } + if (mDefaultFocusHighlight != null) { + mDefaultFocusHighlight.jumpToCurrentState(); + } if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) { mForegroundInfo.mDrawable.jumpToCurrentState(); } @@ -19869,6 +20047,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /* 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } // 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // 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 d26d952224f1..221e73676514 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 f364e7061de0..ed940b358784 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 8bbb9298f6b4..2ae2ca00c167 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -322,6 +322,7 @@ please see styles_device_defaults.xml. <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 @@ please see styles_device_defaults.xml. <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 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}. + * + * <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 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 <em>super.inflateTag(...)</em>. 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<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 @@ 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); } |