Loading core/java/android/view/FocusFinder.java +34 −3 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageManager; import android.graphics.Rect; import android.util.ArrayMap; import android.util.SparseArray; Loading Loading @@ -88,8 +89,9 @@ public class FocusFinder { private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) { View next = null; ViewGroup effectiveRoot = getEffectiveRoot(root, focused); if (focused != null) { next = findNextUserSpecifiedFocus(root, focused, direction); next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction); } if (next != null) { return next; Loading @@ -97,9 +99,9 @@ public class FocusFinder { ArrayList<View> focusables = mTempList; try { focusables.clear(); root.addFocusables(focusables, direction); effectiveRoot.addFocusables(focusables, direction); if (!focusables.isEmpty()) { next = findNextFocus(root, focused, focusedRect, direction, focusables); next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables); } } finally { focusables.clear(); Loading @@ -107,6 +109,35 @@ public class FocusFinder { return next; } /** * Returns the "effective" root of a view. The "effective" root is the closest ancestor * within-which focus should cycle. * <p> * For example: normal focus navigation would stay within a ViewGroup marked as * touchscreenBlocksFocus and keyboardNavigationCluster until a cluster-jump out. * @return the "effective" root of {@param focused} */ private ViewGroup getEffectiveRoot(ViewGroup root, View focused) { if (focused == null) { return root; } ViewParent effective = focused.getParent(); do { if (effective == root) { return root; } ViewGroup vg = (ViewGroup) effective; if (vg.getTouchscreenBlocksFocus() && focused.getContext().getPackageManager().hasSystemFeature( PackageManager.FEATURE_TOUCHSCREEN) && vg.isKeyboardNavigationCluster()) { return vg; } effective = effective.getParent(); } while (effective != null); return root; } /** * Find the root of the next keyboard navigation cluster after the current one. * @param root The view tree to look inside. Cannot be null Loading core/java/android/view/ViewGroup.java +44 −4 Original line number Diff line number Diff line Loading @@ -1190,7 +1190,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final int focusableCount = views.size(); final int descendantFocusability = getDescendantFocusability(); final boolean focusSelf = (isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen()); final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen(); final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen); if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) { if (focusSelf) { Loading @@ -1199,7 +1200,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return; } if (shouldBlockFocusForTouchscreen()) { if (blockFocusForTouchscreen) { focusableMode |= FOCUSABLES_TOUCH_MODE; } Loading Loading @@ -1234,7 +1235,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager public void addKeyboardNavigationClusters(Collection<View> views, int direction) { final int focusableCount = views.size(); if (isKeyboardNavigationCluster()) { // Cluster-navigation can enter a touchscreenBlocksFocus cluster, so temporarily // disable touchscreenBlocksFocus to evaluate whether it contains focusables. final boolean blockedFocus = getTouchscreenBlocksFocus(); try { setTouchscreenBlocksFocusNoRefocus(false); super.addKeyboardNavigationClusters(views, direction); } finally { setTouchscreenBlocksFocusNoRefocus(blockedFocus); } } else { super.addKeyboardNavigationClusters(views, direction); } if (focusableCount != views.size()) { // No need to look for groups inside a group. Loading Loading @@ -1280,6 +1293,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } private void setTouchscreenBlocksFocusNoRefocus(boolean touchscreenBlocksFocus) { if (touchscreenBlocksFocus) { mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS; } else { mGroupFlags &= ~FLAG_TOUCHSCREEN_BLOCKS_FOCUS; } } /** * Check whether this ViewGroup should ignore focus requests for itself and its children. */ Loading @@ -1288,8 +1309,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } boolean shouldBlockFocusForTouchscreen() { // There is a special case for keyboard-navigation clusters. We allow cluster navigation // to jump into blockFocusForTouchscreen ViewGroups which are clusters. Once in the // cluster, focus is free to move around within it. return getTouchscreenBlocksFocus() && mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN); mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) && (!hasFocus() || !isKeyboardNavigationCluster()); } @Override Loading Loading @@ -3175,6 +3200,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @TestApi @Override public boolean restoreFocusInCluster(@FocusRealDirection int direction) { // Allow cluster-navigation to enter touchscreenBlocksFocus ViewGroups. if (isKeyboardNavigationCluster()) { final boolean blockedFocus = getTouchscreenBlocksFocus(); try { setTouchscreenBlocksFocusNoRefocus(false); return restoreFocusInClusterInternal(direction); } finally { setTouchscreenBlocksFocusNoRefocus(blockedFocus); } } else { return restoreFocusInClusterInternal(direction); } } private boolean restoreFocusInClusterInternal(@FocusRealDirection int direction) { if (mFocusedInCluster != null && !mFocusedInCluster.isKeyboardNavigationCluster() && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS && (mFocusedInCluster.mViewFlags & VISIBILITY_MASK) == VISIBLE Loading core/java/android/widget/Toolbar.java +3 −0 Original line number Diff line number Diff line Loading @@ -343,6 +343,9 @@ public class Toolbar extends ViewGroup { final ViewGroup vgParent = (ViewGroup) parent; if (vgParent.isKeyboardNavigationCluster()) { setKeyboardNavigationCluster(false); if (vgParent.getTouchscreenBlocksFocus()) { setTouchscreenBlocksFocus(false); } break; } parent = vgParent.getParent(); Loading core/res/res/layout/screen_action_bar.xml +2 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ This is an optimized layout for a screen with the Action Bar enabled. android:layout_alignParentTop="true" style="?attr/actionBarStyle" android:transitionName="android:action_bar" android:touchscreenBlocksFocus="true" android:keyboardNavigationCluster="true" android:gravity="top"> <com.android.internal.widget.ActionBarView Loading @@ -54,6 +55,7 @@ This is an optimized layout for a screen with the Action Bar enabled. android:layout_height="wrap_content" style="?attr/actionBarSplitStyle" android:visibility="gone" android:touchscreenBlocksFocus="true" android:keyboardNavigationCluster="true" android:gravity="center"/> </com.android.internal.widget.ActionBarOverlayLayout> core/res/res/layout/screen_toolbar.xml +1 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ This is an optimized layout for a screen with a toolbar enabled. android:layout_alignParentTop="true" style="?attr/actionBarStyle" android:transitionName="android:action_bar" android:touchscreenBlocksFocus="true" android:keyboardNavigationCluster="true" android:gravity="top"> <Toolbar Loading Loading
core/java/android/view/FocusFinder.java +34 −3 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageManager; import android.graphics.Rect; import android.util.ArrayMap; import android.util.SparseArray; Loading Loading @@ -88,8 +89,9 @@ public class FocusFinder { private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) { View next = null; ViewGroup effectiveRoot = getEffectiveRoot(root, focused); if (focused != null) { next = findNextUserSpecifiedFocus(root, focused, direction); next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction); } if (next != null) { return next; Loading @@ -97,9 +99,9 @@ public class FocusFinder { ArrayList<View> focusables = mTempList; try { focusables.clear(); root.addFocusables(focusables, direction); effectiveRoot.addFocusables(focusables, direction); if (!focusables.isEmpty()) { next = findNextFocus(root, focused, focusedRect, direction, focusables); next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables); } } finally { focusables.clear(); Loading @@ -107,6 +109,35 @@ public class FocusFinder { return next; } /** * Returns the "effective" root of a view. The "effective" root is the closest ancestor * within-which focus should cycle. * <p> * For example: normal focus navigation would stay within a ViewGroup marked as * touchscreenBlocksFocus and keyboardNavigationCluster until a cluster-jump out. * @return the "effective" root of {@param focused} */ private ViewGroup getEffectiveRoot(ViewGroup root, View focused) { if (focused == null) { return root; } ViewParent effective = focused.getParent(); do { if (effective == root) { return root; } ViewGroup vg = (ViewGroup) effective; if (vg.getTouchscreenBlocksFocus() && focused.getContext().getPackageManager().hasSystemFeature( PackageManager.FEATURE_TOUCHSCREEN) && vg.isKeyboardNavigationCluster()) { return vg; } effective = effective.getParent(); } while (effective != null); return root; } /** * Find the root of the next keyboard navigation cluster after the current one. * @param root The view tree to look inside. Cannot be null Loading
core/java/android/view/ViewGroup.java +44 −4 Original line number Diff line number Diff line Loading @@ -1190,7 +1190,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final int focusableCount = views.size(); final int descendantFocusability = getDescendantFocusability(); final boolean focusSelf = (isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen()); final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen(); final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen); if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) { if (focusSelf) { Loading @@ -1199,7 +1200,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return; } if (shouldBlockFocusForTouchscreen()) { if (blockFocusForTouchscreen) { focusableMode |= FOCUSABLES_TOUCH_MODE; } Loading Loading @@ -1234,7 +1235,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager public void addKeyboardNavigationClusters(Collection<View> views, int direction) { final int focusableCount = views.size(); if (isKeyboardNavigationCluster()) { // Cluster-navigation can enter a touchscreenBlocksFocus cluster, so temporarily // disable touchscreenBlocksFocus to evaluate whether it contains focusables. final boolean blockedFocus = getTouchscreenBlocksFocus(); try { setTouchscreenBlocksFocusNoRefocus(false); super.addKeyboardNavigationClusters(views, direction); } finally { setTouchscreenBlocksFocusNoRefocus(blockedFocus); } } else { super.addKeyboardNavigationClusters(views, direction); } if (focusableCount != views.size()) { // No need to look for groups inside a group. Loading Loading @@ -1280,6 +1293,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } private void setTouchscreenBlocksFocusNoRefocus(boolean touchscreenBlocksFocus) { if (touchscreenBlocksFocus) { mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS; } else { mGroupFlags &= ~FLAG_TOUCHSCREEN_BLOCKS_FOCUS; } } /** * Check whether this ViewGroup should ignore focus requests for itself and its children. */ Loading @@ -1288,8 +1309,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } boolean shouldBlockFocusForTouchscreen() { // There is a special case for keyboard-navigation clusters. We allow cluster navigation // to jump into blockFocusForTouchscreen ViewGroups which are clusters. Once in the // cluster, focus is free to move around within it. return getTouchscreenBlocksFocus() && mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN); mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) && (!hasFocus() || !isKeyboardNavigationCluster()); } @Override Loading Loading @@ -3175,6 +3200,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @TestApi @Override public boolean restoreFocusInCluster(@FocusRealDirection int direction) { // Allow cluster-navigation to enter touchscreenBlocksFocus ViewGroups. if (isKeyboardNavigationCluster()) { final boolean blockedFocus = getTouchscreenBlocksFocus(); try { setTouchscreenBlocksFocusNoRefocus(false); return restoreFocusInClusterInternal(direction); } finally { setTouchscreenBlocksFocusNoRefocus(blockedFocus); } } else { return restoreFocusInClusterInternal(direction); } } private boolean restoreFocusInClusterInternal(@FocusRealDirection int direction) { if (mFocusedInCluster != null && !mFocusedInCluster.isKeyboardNavigationCluster() && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS && (mFocusedInCluster.mViewFlags & VISIBILITY_MASK) == VISIBLE Loading
core/java/android/widget/Toolbar.java +3 −0 Original line number Diff line number Diff line Loading @@ -343,6 +343,9 @@ public class Toolbar extends ViewGroup { final ViewGroup vgParent = (ViewGroup) parent; if (vgParent.isKeyboardNavigationCluster()) { setKeyboardNavigationCluster(false); if (vgParent.getTouchscreenBlocksFocus()) { setTouchscreenBlocksFocus(false); } break; } parent = vgParent.getParent(); Loading
core/res/res/layout/screen_action_bar.xml +2 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ This is an optimized layout for a screen with the Action Bar enabled. android:layout_alignParentTop="true" style="?attr/actionBarStyle" android:transitionName="android:action_bar" android:touchscreenBlocksFocus="true" android:keyboardNavigationCluster="true" android:gravity="top"> <com.android.internal.widget.ActionBarView Loading @@ -54,6 +55,7 @@ This is an optimized layout for a screen with the Action Bar enabled. android:layout_height="wrap_content" style="?attr/actionBarSplitStyle" android:visibility="gone" android:touchscreenBlocksFocus="true" android:keyboardNavigationCluster="true" android:gravity="center"/> </com.android.internal.widget.ActionBarOverlayLayout>
core/res/res/layout/screen_toolbar.xml +1 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ This is an optimized layout for a screen with a toolbar enabled. android:layout_alignParentTop="true" style="?attr/actionBarStyle" android:transitionName="android:action_bar" android:touchscreenBlocksFocus="true" android:keyboardNavigationCluster="true" android:gravity="top"> <Toolbar Loading