OSDN Git Service

Separate cluster-focus and default-focus.
authorEvan Rosky <erosky@google.com>
Thu, 26 Jan 2017 22:37:55 +0000 (14:37 -0800)
committerEvan Rosky <erosky@google.com>
Mon, 6 Feb 2017 18:33:04 +0000 (10:33 -0800)
Now properly cleans-up cluster-focus and default-focus chains
on view-removal (was only cleaning 1 level previously).
Separated cluster-focus and default-focus concepts.
Made default-focus robust to changes in default-focusable
views (previously un-marking a default focus wouldn't reassign
default focus to still-existing default focusables).
Cluster-focus falls back on default-focus when no saved
cluster-focus exists.

Bug: 34394367
Test: Associated CTS tests.
Change-Id: I903c22ded5643d0d4ca3bc5427b38833742aa354

api/current.txt
api/system-current.txt
api/test-current.txt
core/java/android/view/View.java
core/java/android/view/ViewGroup.java
core/java/android/view/ViewRootImpl.java

index 4f63ff6..8471a97 100644 (file)
@@ -44423,7 +44423,7 @@ package android.view {
     method public final void requestUnbufferedDispatch(android.view.MotionEvent);
     method public static int resolveSize(int, int);
     method public static int resolveSizeAndState(int, int, int);
-    method public boolean restoreDefaultFocus(int);
+    method public boolean restoreDefaultFocus();
     method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>);
     method public void saveHierarchyState(android.util.SparseArray<android.os.Parcelable>);
     method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
index 03710e6..736a65f 100644 (file)
@@ -47829,7 +47829,7 @@ package android.view {
     method public final void requestUnbufferedDispatch(android.view.MotionEvent);
     method public static int resolveSize(int, int);
     method public static int resolveSizeAndState(int, int, int);
-    method public boolean restoreDefaultFocus(int);
+    method public boolean restoreDefaultFocus();
     method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>);
     method public void saveHierarchyState(android.util.SparseArray<android.os.Parcelable>);
     method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
index 29d152a..d40ad6b 100644 (file)
@@ -44730,7 +44730,7 @@ package android.view {
     method public final void requestUnbufferedDispatch(android.view.MotionEvent);
     method public static int resolveSize(int, int);
     method public static int resolveSizeAndState(int, int, int);
-    method public boolean restoreDefaultFocus(int);
+    method public boolean restoreDefaultFocus();
     method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>);
     method public void saveHierarchyState(android.util.SparseArray<android.os.Parcelable>);
     method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
index e2eee95..3d38179 100644 (file)
@@ -6121,9 +6121,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
 
             if (mParent != null) {
                 mParent.requestChildFocus(this, this);
-                if (mParent instanceof ViewGroup) {
-                    ((ViewGroup) mParent).setDefaultFocus(this);
-                }
+                setFocusedInCluster();
             }
 
             if (mAttachInfo != null) {
@@ -9160,6 +9158,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     }
 
     /**
+     * Sets this View as the one which receives focus the next time cluster navigation jumps
+     * to the cluster containing this View. This does NOT change focus even if the cluster
+     * containing this view is current.
+     *
+     * @hide
+     */
+    public void setFocusedInCluster() {
+        if (mParent instanceof ViewGroup) {
+            ((ViewGroup) mParent).setFocusInCluster(this);
+        }
+    }
+
+    /**
      * Returns whether this View should receive focus when the focus is restored for the view
      * hierarchy containing this view.
      * <p>
@@ -9205,7 +9216,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
             if (isFocusedByDefault) {
                 ((ViewGroup) mParent).setDefaultFocus(this);
             } else {
-                ((ViewGroup) mParent).cleanDefaultFocus(this);
+                ((ViewGroup) mParent).clearDefaultFocus(this);
             }
         }
     }
@@ -9585,15 +9596,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     }
 
     /**
+     * Public for testing. This will request focus for whichever View was last focused within this
+     * cluster before a focus-jump out of it.
+     *
+     * @hide
+     */
+    public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
+        // Prioritize focusableByDefault over algorithmic focus selection.
+        if (restoreDefaultFocus()) {
+            return true;
+        }
+        return requestFocus(direction);
+    }
+
+    /**
      * Gives focus to the default-focus view in the view hierarchy that has this view as a root.
      * If the default-focus view cannot be found, falls back to calling {@link #requestFocus(int)}.
-     * Nested keyboard navigation clusters are excluded from the hierarchy.
      *
-     * @param direction The direction of the focus
      * @return Whether this view or one of its descendants actually took focus
      */
-    public boolean restoreDefaultFocus(@FocusDirection int direction) {
-        return requestFocus(direction);
+    public boolean restoreDefaultFocus() {
+        return requestFocus(View.FOCUS_DOWN);
     }
 
     /**
index b11b3d7..a932f5d 100644 (file)
@@ -140,6 +140,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     // The view contained within this ViewGroup (excluding nested keyboard navigation clusters)
     // that is or contains a default-focus view.
     private View mDefaultFocus;
+    // The last child of this ViewGroup which held focus within the current cluster
+    private View mFocusedInCluster;
 
     /**
      * A Transformation used when drawing children, to
@@ -723,7 +725,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
         if (mFocused != null) {
             mFocused.unFocus(this);
             mFocused = null;
-            mDefaultFocus = null;
+            mFocusedInCluster = null;
         }
         super.handleFocusGainInternal(direction, previouslyFocusedRect);
     }
@@ -753,14 +755,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
         }
     }
 
-    /**
-     * Sets the specified child view as the default focus for this view and all its ancestors.
-     * If the view is inside a keyboard navigation cluster, stops at the root of the cluster since
-     * the cluster forms a separate keyboard navigation hierarchy from the default focus point of
-     * view.
-     */
     void setDefaultFocus(View child) {
-        if (child.isKeyboardNavigationCluster()) {
+        // Stop at any higher view which is explicitly focused-by-default
+        if (mDefaultFocus != null && mDefaultFocus.isFocusedByDefault()) {
             return;
         }
 
@@ -772,21 +769,33 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     }
 
     /**
-     * Destroys the default focus chain.
+     * Clears the default-focus chain from {@param child} up to the first parent which has another
+     * default-focusable branch below it or until there is no default-focus chain.
+     *
+     * @param child
      */
-    void cleanDefaultFocus(View child) {
-        if (mDefaultFocus != child) {
-            return;
-        }
-
-        if (child.isKeyboardNavigationCluster()) {
+    void clearDefaultFocus(View child) {
+        // Stop at any higher view which is explicitly focused-by-default
+        if (mDefaultFocus != child && mDefaultFocus != null
+                && mDefaultFocus.isFocusedByDefault()) {
             return;
         }
 
         mDefaultFocus = null;
 
+        // Search child siblings for default focusables.
+        for (int i = 0; i < mChildrenCount; ++i) {
+            View sibling = mChildren[i];
+            if (sibling.isFocusedByDefault()) {
+                mDefaultFocus = sibling;
+                return;
+            } else if (mDefaultFocus == null && sibling.hasDefaultFocus()) {
+                mDefaultFocus = sibling;
+            }
+        }
+
         if (mParent instanceof ViewGroup) {
-            ((ViewGroup) mParent).cleanDefaultFocus(this);
+            ((ViewGroup) mParent).clearDefaultFocus(this);
         }
     }
 
@@ -795,6 +804,35 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
         return mDefaultFocus != null || super.hasDefaultFocus();
     }
 
+    void setFocusInCluster(View child) {
+        // Stop at the root of the cluster
+        if (child.isKeyboardNavigationCluster()) {
+            return;
+        }
+
+        mFocusedInCluster = child;
+
+        if (mParent instanceof ViewGroup) {
+            ((ViewGroup) mParent).setFocusInCluster(this);
+        }
+    }
+
+    void clearFocusInCluster(View child) {
+        if (mFocusedInCluster != child) {
+            return;
+        }
+
+        if (child.isKeyboardNavigationCluster()) {
+            return;
+        }
+
+        mFocusedInCluster = null;
+
+        if (mParent instanceof ViewGroup) {
+            ((ViewGroup) mParent).clearFocusInCluster(this);
+        }
+    }
+
     @Override
     public void focusableViewAvailable(View v) {
         if (mParent != null
@@ -3098,14 +3136,28 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     }
 
     @Override
-    public boolean restoreDefaultFocus(@FocusDirection int direction) {
-        if (mDefaultFocus != null && !mDefaultFocus.isKeyboardNavigationCluster()
+    public boolean restoreDefaultFocus() {
+        if (mDefaultFocus != null
                 && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
                 && (mDefaultFocus.mViewFlags & VISIBILITY_MASK) == VISIBLE
-                && mDefaultFocus.restoreDefaultFocus(direction)) {
+                && mDefaultFocus.restoreDefaultFocus()) {
             return true;
         }
-        return super.restoreDefaultFocus(direction);
+        return super.restoreDefaultFocus();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
+        if (mFocusedInCluster != null && !mFocusedInCluster.isKeyboardNavigationCluster()
+                && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
+                && (mFocusedInCluster.mViewFlags & VISIBILITY_MASK) == VISIBLE
+                && mFocusedInCluster.restoreFocusInCluster(direction)) {
+            return true;
+        }
+        return super.restoreFocusInCluster(direction);
     }
 
     /**
@@ -4988,8 +5040,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
             view.unFocus(null);
             clearChildFocus = true;
         }
-        if (view == mDefaultFocus) {
-            mDefaultFocus = null;
+        if (view == mFocusedInCluster) {
+            clearFocusInCluster(view);
         }
 
         view.clearAccessibilityFocus();
@@ -5012,6 +5064,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
 
         removeFromArray(index);
 
+        if (view == mDefaultFocus) {
+            clearDefaultFocus(view);
+        }
         if (clearChildFocus) {
             clearChildFocus(view);
             if (!rootViewRequestFocus()) {
@@ -5087,6 +5142,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
         final View focused = mFocused;
         final boolean detach = mAttachInfo != null;
         boolean clearChildFocus = false;
+        View clearDefaultFocus = null;
 
         final View[] children = mChildren;
 
@@ -5102,7 +5158,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                 clearChildFocus = true;
             }
             if (view == mDefaultFocus) {
-                mDefaultFocus = null;
+                clearDefaultFocus = view;
+            }
+            if (view == mFocusedInCluster) {
+                clearFocusInCluster(view);
             }
 
             view.clearAccessibilityFocus();
@@ -5128,6 +5187,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
 
         removeFromArray(start, count);
 
+        if (clearDefaultFocus != null) {
+            clearDefaultFocus(clearDefaultFocus);
+        }
         if (clearChildFocus) {
             clearChildFocus(focused);
             if (!rootViewRequestFocus()) {
@@ -5177,7 +5239,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
         boolean clearChildFocus = false;
 
         needGlobalAttributesUpdate(false);
-        mDefaultFocus = null;
 
         for (int i = count - 1; i >= 0; i--) {
             final View view = children[i];
@@ -5213,6 +5274,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
             children[i] = null;
         }
 
+        if (mDefaultFocus != null) {
+            clearDefaultFocus(mDefaultFocus);
+        }
         if (clearChildFocus) {
             clearChildFocus(focused);
             if (!rootViewRequestFocus()) {
@@ -5250,7 +5314,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
             child.clearFocus();
         }
         if (child == mDefaultFocus) {
-            mDefaultFocus = null;
+            clearDefaultFocus(child);
+        }
+        if (child == mFocusedInCluster) {
+            clearFocusInCluster(child);
         }
 
         child.clearAccessibilityFocus();
@@ -6235,6 +6302,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
             Log.d(VIEW_LOG_TAG, output);
             mDefaultFocus.debug(depth + 1);
         }
+        if (mFocusedInCluster != null) {
+            output = debugIndent(depth);
+            output += "mFocusedInCluster";
+            Log.d(VIEW_LOG_TAG, output);
+            mFocusedInCluster.debug(depth + 1);
+        }
         if (mChildrenCount != 0) {
             output = debugIndent(depth);
             output += "{";
index 9bfc260..3c30f95 100644 (file)
@@ -2162,7 +2162,7 @@ public final class ViewRootImpl implements ViewParent,
                     + mView.hasFocus());
             if (mView != null) {
                 if (!mView.hasFocus()) {
-                    mView.restoreDefaultFocus(View.FOCUS_FORWARD);
+                    mView.restoreDefaultFocus();
                     if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
                             + mView.findFocus());
                 } else {
@@ -4441,7 +4441,14 @@ public final class ViewRootImpl implements ViewParent,
                     ? focused.keyboardNavigationClusterSearch(null, direction)
                     : keyboardNavigationClusterSearch(null, direction);
 
-            if (cluster != null && cluster.restoreDefaultFocus(View.FOCUS_DOWN)) {
+            // Since requestFocus only takes "real" focus directions (and therefore also
+            // restoreFocusInCluster), convert forward/backward focus into FOCUS_DOWN.
+            int realDirection = direction;
+            if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
+                realDirection = View.FOCUS_DOWN;
+            }
+
+            if (cluster != null && cluster.restoreFocusInCluster(realDirection)) {
                 return true;
             }