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);
     }