Merge "Accessibility focus traversal in virtual nodes." into jb-dev
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index e1f1db2..16f9a18 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -491,20 +491,28 @@
                 if ((direction & View.FOCUS_ACCESSIBILITY) ==  View.FOCUS_ACCESSIBILITY) {
                     AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
                     if (provider != null) {
-                        next = provider.accessibilityFocusSearch(direction,
-                                virtualDescendantId);
-                    } else if (virtualDescendantId == View.NO_ID) {
-                        View nextView = root.focusSearch(direction);
-                        if (nextView != null) {
-                            // If the focus search reached a node with a provider
-                            // we delegate to the provider to find the next one.
-                            provider = nextView.getAccessibilityNodeProvider();
-                            if (provider != null) {
-                                next = provider.accessibilityFocusSearch(direction,
-                                        virtualDescendantId);
-                            } else {
-                                next = nextView.createAccessibilityNodeInfo();
-                             }
+                        next = provider.accessibilityFocusSearch(direction, virtualDescendantId);
+                        if (next != null) {
+                            return;
+                        }
+                    }
+                    View nextView = root.focusSearch(direction);
+                    while (nextView != null) {
+                        // If the focus search reached a node with a provider
+                        // we delegate to the provider to find the next one.
+                        // If the provider does not return a virtual view to
+                        // take accessibility focus we try the next view found
+                        // by the focus search algorithm.
+                        provider = nextView.getAccessibilityNodeProvider();
+                        if (provider != null) {
+                            next = provider.accessibilityFocusSearch(direction, View.NO_ID);
+                            if (next != null) {
+                                break;
+                            }
+                            nextView = nextView.focusSearch(direction);
+                        } else {
+                            next = nextView.createAccessibilityNodeInfo();
+                            break;
                         }
                     }
                 } else {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 14523d3..a4fcd41 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6027,7 +6027,7 @@
             return;
         }
         if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
-            if (canTakeAccessibilityFocusFromHover()) {
+            if (canTakeAccessibilityFocusFromHover() || getAccessibilityNodeProvider() != null) {
                 views.add(this);
                 return;
             }
@@ -6156,12 +6156,15 @@
      * @hide
      */
     public void clearAccessibilityFocus() {
-        if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) {
-            mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED;
-            ViewRootImpl viewRootImpl = getViewRootImpl();
-            if (viewRootImpl != null) {
+        ViewRootImpl viewRootImpl = getViewRootImpl();
+        if (viewRootImpl != null) {
+            View focusHost = viewRootImpl.getAccessibilityFocusedHost();
+            if (focusHost != null && ViewRootImpl.isViewDescendantOf(focusHost, this)) {
                 viewRootImpl.setAccessibilityFocusedHost(null);
             }
+        }
+        if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) {
+            mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED;
             invalidate();
             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
             notifyAccessibilityStateChanged();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f86e036..553abac 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -488,7 +488,9 @@
                 mFallbackEventHandler.setView(view);
                 mWindowAttributes.copyFrom(attrs);
                 attrs = mWindowAttributes;
-                
+
+                setAccessibilityFocusedHost(null);
+
                 if (view instanceof RootViewSurfaceTaker) {
                     mSurfaceHolderCallback =
                             ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
@@ -556,6 +558,7 @@
                     mInputChannel = null;
                     mFallbackEventHandler.setView(null);
                     unscheduleTraversals();
+                    setAccessibilityFocusedHost(null);
                     throw new RuntimeException("Adding window failed", e);
                 } finally {
                     if (restore) {
@@ -575,6 +578,7 @@
                     mAdded = false;
                     mFallbackEventHandler.setView(null);
                     unscheduleTraversals();
+                    setAccessibilityFocusedHost(null);
                     switch (res) {
                         case WindowManagerImpl.ADD_BAD_APP_TOKEN:
                         case WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN:
@@ -635,8 +639,6 @@
                 if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                     view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
                 }
-
-                setAccessibilityFocusedHost(null);
             }
         }
     }
@@ -2543,11 +2545,51 @@
     }
 
     void setAccessibilityFocusedHost(View host) {
-        if (mAccessibilityFocusedHost != null && mAccessibilityFocusedVirtualView == null) {
+        // If we have a virtual view with accessibility focus we need
+        // to clear the focus and invalidate the virtual view bounds.
+        if (mAccessibilityFocusedVirtualView != null) {
+
+            AccessibilityNodeInfo focusNode = mAccessibilityFocusedVirtualView;
+            View focusHost = mAccessibilityFocusedHost;
+            focusHost.clearAccessibilityFocusNoCallbacks();
+
+            // Wipe the state of the current accessibility focus since
+            // the call into the provider to clear accessibility focus
+            // will fire an accessibility event which will end up calling
+            // this method and we want to have clean state when this
+            // invocation happens.
+            mAccessibilityFocusedHost = null;
+            mAccessibilityFocusedVirtualView = null;
+
+            AccessibilityNodeProvider provider = focusHost.getAccessibilityNodeProvider();
+            if (provider != null) {
+                // Invalidate the area of the cleared accessibility focus.
+                focusNode.getBoundsInParent(mTempRect);
+                focusHost.invalidate(mTempRect);
+                // Clear accessibility focus in the virtual node.
+                final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
+                        focusNode.getSourceNodeId());
+                provider.performAction(virtualNodeId,
+                        AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
+            }
+        }
+        if (mAccessibilityFocusedHost != null) {
+            // Clear accessibility focus in the view.
             mAccessibilityFocusedHost.clearAccessibilityFocusNoCallbacks();
         }
+
+        // Set the new focus host.
         mAccessibilityFocusedHost = host;
-        mAccessibilityFocusedVirtualView = null;
+
+        // If the host has a provide find the virtual descendant that has focus.
+        if (mAccessibilityFocusedHost != null) {
+            AccessibilityNodeProvider provider =
+                mAccessibilityFocusedHost.getAccessibilityNodeProvider();
+            if (provider != null) {
+                mAccessibilityFocusedVirtualView = provider.findAccessibilityFocus(View.NO_ID);
+                return;
+            }
+        }
     }
 
     public void requestChildFocus(View child, View focused) {
@@ -2633,6 +2675,8 @@
 
         destroyHardwareRenderer();
 
+        setAccessibilityFocusedHost(null);
+
         mView = null;
         mAttachInfo.mRootView = null;
         mAttachInfo.mSurface = null;
@@ -4608,6 +4652,31 @@
         if (mView == null) {
             return false;
         }
+        // Watch for accessibility focus change events from virtual nodes
+        // to keep track of accessibility focus being on a virtual node.
+        final int eventType = event.getEventType();
+        switch (eventType) {
+            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
+                final long sourceId = event.getSourceNodeId();
+                // If the event is not from a virtual node we are not interested.
+                final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(sourceId);
+                if (virtualViewId == AccessibilityNodeInfo.UNDEFINED) {
+                    break;
+                }
+                final int realViewId = AccessibilityNodeInfo.getAccessibilityViewId(sourceId);
+                View focusHost = mView.findViewByAccessibilityId(realViewId);
+                setAccessibilityFocusedHost(focusHost);
+            } break;
+            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+                final long sourceId = event.getSourceNodeId();
+                // If the event is not from a virtual node we are not interested.
+                final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(sourceId);
+                if (virtualViewId == AccessibilityNodeInfo.UNDEFINED) {
+                    break;
+                }
+                setAccessibilityFocusedHost(null);
+            } break;
+        }
         mAccessibilityManager.sendAccessibilityEvent(event);
         return true;
     }
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 7c809b3..78d570e 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -950,6 +950,8 @@
                     provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
                             AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
                     mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
+                    provider.performAction(hoveredVirtualViewId,
+                            AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
                 } break;
                 case MotionEvent.ACTION_HOVER_MOVE: {
                     if (mLastHoveredChildVirtualViewId != hoveredVirtualViewId
@@ -960,6 +962,8 @@
                         provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
                                 AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
                         mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
+                        provider.performAction(hoveredVirtualViewId,
+                                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
                     }
                 } break;
                 case MotionEvent.ACTION_HOVER_EXIT: {
@@ -1413,9 +1417,16 @@
     }
 
     @Override
-    public void sendAccessibilityEvent(int eventType) {
-        // Do not send accessibility events - we want the user to
-        // perceive this widget as several controls rather as a whole.
+    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+        // We do not want the real descendant to be considered focus search
+        // since it is managed by the accessibility node provider.
+        if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
+            if (canTakeAccessibilityFocusFromHover() || getAccessibilityNodeProvider() != null) {
+                views.add(this);
+                return;
+            }
+        }
+        super.addFocusables(views, direction, focusableMode);
     }
 
     @Override
@@ -2072,7 +2083,12 @@
         }
     }
 
+    /**
+     * Class for managing virtual view tree rooted at this picker.
+     */
     class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider {
+        private static final int UNDEFINED = Integer.MIN_VALUE;
+
         private static final int VIRTUAL_VIEW_ID_INCREMENT = 1;
 
         private static final int VIRTUAL_VIEW_ID_INPUT = 2;
@@ -2083,6 +2099,8 @@
 
         private final int[] mTempArray = new int[2];
 
+        private int mAccessibilityFocusedView = UNDEFINED;
+
         @Override
         public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
             switch (virtualViewId) {
@@ -2137,6 +2155,25 @@
         @Override
         public boolean performAction(int virtualViewId, int action, Bundle arguments) {
             switch (virtualViewId) {
+                case View.NO_ID: {
+                    switch (action) {
+                        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
+                            if (mAccessibilityFocusedView != virtualViewId) {
+                                mAccessibilityFocusedView = virtualViewId;
+                                requestAccessibilityFocus();
+                                return true;
+                            }
+                        } return false;
+                        case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
+                            if (mAccessibilityFocusedView == virtualViewId) {
+                                mAccessibilityFocusedView = UNDEFINED;
+                                clearAccessibilityFocus();
+                                return true;
+                            }
+                            return false;
+                        }
+                    }
+                } break;
                 case VIRTUAL_VIEW_ID_INPUT: {
                     switch (action) {
                         case AccessibilityNodeInfo.ACTION_FOCUS: {
@@ -2149,25 +2186,182 @@
                                 mInputText.clearFocus();
                                 return true;
                             }
-                        } break;
+                            return false;
+                        }
+                        case AccessibilityNodeInfo.ACTION_CLICK: {
+                            showSoftInput();
+                            return true;
+                        }
+                        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
+                            if (mAccessibilityFocusedView != virtualViewId) {
+                                mAccessibilityFocusedView = virtualViewId;
+                                sendAccessibilityEventForVirtualView(virtualViewId,
+                                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+                                mInputText.invalidate();
+                                return true;
+                            }
+                        } return false;
+                        case  AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
+                            if (mAccessibilityFocusedView == virtualViewId) {
+                                mAccessibilityFocusedView = UNDEFINED;
+                                sendAccessibilityEventForVirtualView(virtualViewId,
+                                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+                                mInputText.invalidate();
+                                return true;
+                            }
+                        } return false;
+                        default: {
+                            return mInputText.performAccessibilityAction(action, arguments);
+                        }
+                    }
+                } return false;
+                case VIRTUAL_VIEW_ID_INCREMENT: {
+                    switch (action) {
+                        case AccessibilityNodeInfo.ACTION_CLICK: {
+                            NumberPicker.this.changeValueByOne(true);
+                            sendAccessibilityEventForVirtualView(virtualViewId,
+                                    AccessibilityEvent.TYPE_VIEW_CLICKED);
+                        } return true;
+                        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
+                            if (mAccessibilityFocusedView != virtualViewId) {
+                                mAccessibilityFocusedView = virtualViewId;
+                                sendAccessibilityEventForVirtualView(virtualViewId,
+                                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+                                invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
+                                return true;
+                            }
+                        } return false;
+                        case  AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
+                            if (mAccessibilityFocusedView == virtualViewId) {
+                                mAccessibilityFocusedView = UNDEFINED;
+                                sendAccessibilityEventForVirtualView(virtualViewId,
+                                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+                                invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
+                                return true;
+                            }
+                        } return false;
+                    }
+                } return false;
+                case VIRTUAL_VIEW_ID_DECREMENT: {
+                    switch (action) {
+                        case AccessibilityNodeInfo.ACTION_CLICK: {
+                            final boolean increment = (virtualViewId == VIRTUAL_VIEW_ID_INCREMENT);
+                            NumberPicker.this.changeValueByOne(increment);
+                            sendAccessibilityEventForVirtualView(virtualViewId,
+                                    AccessibilityEvent.TYPE_VIEW_CLICKED);
+                        } return true;
+                        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
+                            if (mAccessibilityFocusedView != virtualViewId) {
+                                mAccessibilityFocusedView = virtualViewId;
+                                sendAccessibilityEventForVirtualView(virtualViewId,
+                                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+                                invalidate(0, 0, mRight, mTopSelectionDividerTop);
+                                return true;
+                            }
+                        } return false;
+                        case  AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
+                            if (mAccessibilityFocusedView == virtualViewId) {
+                                mAccessibilityFocusedView = UNDEFINED;
+                                sendAccessibilityEventForVirtualView(virtualViewId,
+                                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+                                invalidate(0, 0, mRight, mTopSelectionDividerTop);
+                                return true;
+                            }
+                        } return false;
+                    }
+                } return false;
+            }
+            return super.performAction(virtualViewId, action, arguments);
+        }
+
+        @Override
+        public AccessibilityNodeInfo findAccessibilityFocus(int virtualViewId) {
+            return createAccessibilityNodeInfo(mAccessibilityFocusedView);
+        }
+
+        @Override
+        public AccessibilityNodeInfo accessibilityFocusSearch(int direction, int virtualViewId) {
+            switch (direction) {
+                case View.ACCESSIBILITY_FOCUS_DOWN:
+                case View.ACCESSIBILITY_FOCUS_FORWARD: {
+                    switch (mAccessibilityFocusedView) {
+                        case UNDEFINED: {
+                            return createAccessibilityNodeInfo(View.NO_ID);
+                        }
+                        case View.NO_ID: {
+                            if (hasVirtualDecrementButton()) {
+                                return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT);
+                            }
+                        }
+                        //$FALL-THROUGH$
+                        case VIRTUAL_VIEW_ID_DECREMENT: {
+                            return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT);
+                        }
+                        case VIRTUAL_VIEW_ID_INPUT: {
+                            if (hasVirtualIncrementButton()) {
+                                return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT);
+                            }
+                        }
+                        //$FALL-THROUGH$
+                        case VIRTUAL_VIEW_ID_INCREMENT: {
+                            View nextFocus = NumberPicker.this.focusSearch(direction);
+                            if (nextFocus != null) {
+                                return nextFocus.createAccessibilityNodeInfo();
+                            }
+                            return null;
+                        }
+                    }
+                } break;
+                case View.ACCESSIBILITY_FOCUS_UP:
+                case View.ACCESSIBILITY_FOCUS_BACKWARD: {
+                    switch (mAccessibilityFocusedView) {
+                        case UNDEFINED: {
+                            return createAccessibilityNodeInfo(View.NO_ID);
+                        }
+                        case View.NO_ID: {
+                            if (hasVirtualIncrementButton()) {
+                                return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT);
+                            }
+                        }
+                        //$FALL-THROUGH$
+                        case VIRTUAL_VIEW_ID_INCREMENT: {
+                            return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT);
+                        }
+                        case VIRTUAL_VIEW_ID_INPUT: {
+                            if (hasVirtualDecrementButton()) {
+                                return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT);
+                            }
+                        }
+                        //$FALL-THROUGH$
+                        case VIRTUAL_VIEW_ID_DECREMENT: {
+                            View nextFocus = NumberPicker.this.focusSearch(direction);
+                            if (nextFocus != null) {
+                                return nextFocus.createAccessibilityNodeInfo();
+                            }
+                            return null;
+                        }
                     }
                 } break;
             }
-            return super.performAction(virtualViewId, action, arguments);
+            return null;
         }
 
         public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) {
             switch (virtualViewId) {
                 case VIRTUAL_VIEW_ID_DECREMENT: {
-                    sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
-                            getVirtualDecrementButtonText());
+                    if (hasVirtualDecrementButton()) {
+                        sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
+                                getVirtualDecrementButtonText());
+                    }
                 } break;
                 case VIRTUAL_VIEW_ID_INPUT: {
                     sendAccessibilityEventForVirtualText(eventType);
                 } break;
                 case VIRTUAL_VIEW_ID_INCREMENT: {
-                    sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
-                            getVirtualIncrementButtonText());
+                    if (hasVirtualIncrementButton()) {
+                        sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
+                                getVirtualIncrementButtonText());
+                    }
                 } break;
             }
         }
@@ -2227,8 +2421,13 @@
 
         private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText() {
             AccessibilityNodeInfo info = mInputText.createAccessibilityNodeInfo();
-            info.setLongClickable(true);
             info.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
+            if (mAccessibilityFocusedView != VIRTUAL_VIEW_ID_INPUT) {
+                info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+            }
+            if (mAccessibilityFocusedView == VIRTUAL_VIEW_ID_INPUT) {
+                info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+            }
             return info;
         }
 
@@ -2252,6 +2451,15 @@
             getLocationOnScreen(locationOnScreen);
             boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
             info.setBoundsInScreen(boundsInScreen);
+
+            if (mAccessibilityFocusedView != virtualViewId) {
+                info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+            }
+            if (mAccessibilityFocusedView == virtualViewId) {
+                info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+            }
+            info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+
             return info;
         }
 
@@ -2261,9 +2469,15 @@
             info.setClassName(NumberPicker.class.getName());
             info.setPackageName(mContext.getPackageName());
             info.setSource(NumberPicker.this);
-            info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_DECREMENT);
+
+            if (hasVirtualDecrementButton()) {
+                info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_DECREMENT);
+            }
             info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
-            info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT);
+            if (hasVirtualIncrementButton()) {
+                info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT);
+            }
+
             info.setParent((View) getParent());
             info.setEnabled(NumberPicker.this.isEnabled());
             info.setScrollable(true);
@@ -2276,9 +2490,25 @@
             getLocationOnScreen(locationOnScreen);
             boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
             info.setBoundsInScreen(boundsInScreen);
+
+            if (mAccessibilityFocusedView != View.NO_ID) {
+                info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+            }
+            if (mAccessibilityFocusedView == View.NO_ID) {
+                info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+            }
+
             return info;
         }
 
+        private boolean hasVirtualDecrementButton() {
+            return getWrapSelectorWheel() || getValue() > getMinValue();
+        }
+
+        private boolean hasVirtualIncrementButton() {
+            return getWrapSelectorWheel() || getValue() < getMaxValue();
+        }
+
         private String getVirtualDecrementButtonText() {
             int value = mValue - 1;
             if (mWrapSelectorWheel) {
diff --git a/core/res/res/layout/time_picker_holo.xml b/core/res/res/layout/time_picker_holo.xml
index 91e66bc..7b91022 100644
--- a/core/res/res/layout/time_picker_holo.xml
+++ b/core/res/res/layout/time_picker_holo.xml
@@ -44,6 +44,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
+        android:importantForAccessibility="no"
         />
 
     <!-- minute -->