Merge "Don't allow 0-sized views to be focused."
This commit is contained in:
@@ -4163,6 +4163,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
*/
|
||||
private static boolean sUseDefaultFocusHighlight;
|
||||
|
||||
/**
|
||||
* True if zero-sized views can be focused.
|
||||
*/
|
||||
private static boolean sCanFocusZeroSized;
|
||||
|
||||
private String mTransitionName;
|
||||
|
||||
static class TintInfo {
|
||||
@@ -4745,6 +4750,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
sUseDefaultFocusHighlight = context.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_useDefaultFocusHighlight);
|
||||
|
||||
sCanFocusZeroSized = targetSdkVersion < Build.VERSION_CODES.P;
|
||||
|
||||
sCompatibilityDone = true;
|
||||
}
|
||||
}
|
||||
@@ -6952,6 +6959,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
|
||||
if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
|
||||
mPrivateFlags &= ~PFLAG_FOCUSED;
|
||||
clearParentsWantFocus();
|
||||
|
||||
if (propagate && mParent != null) {
|
||||
mParent.clearChildFocus(this);
|
||||
@@ -10933,8 +10941,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
* descendants.
|
||||
*
|
||||
* A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
|
||||
* false), or if it is focusable and it is not focusable in touch mode
|
||||
* ({@link #isFocusableInTouchMode}) while the device is in touch mode.
|
||||
* false), or if it can't be focused due to other conditions (not focusable in touch mode
|
||||
* ({@link #isFocusableInTouchMode}) while the device is in touch mode, not visible, not
|
||||
* enabled, or has no size).
|
||||
*
|
||||
* See also {@link #focusSearch(int)}, which is what you call to say that you
|
||||
* have focus, and you want your parent to look for the next one.
|
||||
@@ -10972,17 +10981,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
*/
|
||||
@TestApi
|
||||
public boolean restoreFocusNotInCluster() {
|
||||
return requestFocus(View.FOCUS_DOWN);
|
||||
return requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)}.
|
||||
* If the default-focus view cannot be found, falls back to calling {@link #requestFocus()}.
|
||||
*
|
||||
* @return Whether this view or one of its descendants actually took focus
|
||||
*/
|
||||
public boolean restoreDefaultFocus() {
|
||||
return requestFocus(View.FOCUS_DOWN);
|
||||
return requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -11041,9 +11050,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
|
||||
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
|
||||
// need to be focusable
|
||||
if ((mViewFlags & FOCUSABLE) != FOCUSABLE
|
||||
|| (mViewFlags & VISIBILITY_MASK) != VISIBLE
|
||||
|| (mViewFlags & ENABLED_MASK) != ENABLED) {
|
||||
if (!canTakeFocus()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -11058,10 +11065,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isLaidOut()) {
|
||||
mPrivateFlags |= PFLAG_WANTS_FOCUS;
|
||||
}
|
||||
|
||||
handleFocusGainInternal(direction, previouslyFocusedRect);
|
||||
return true;
|
||||
}
|
||||
|
||||
void clearParentsWantFocus() {
|
||||
if (mParent instanceof View) {
|
||||
((View) mParent).mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
|
||||
((View) mParent).clearParentsWantFocus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to try to give focus to a specific view or to one of its descendants. This is a
|
||||
* special variant of {@link #requestFocus() } that will allow views that are not focusable in
|
||||
@@ -13433,6 +13451,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
mAttachInfo.mUnbufferedDispatchRequested = true;
|
||||
}
|
||||
|
||||
private boolean canTakeFocus() {
|
||||
return ((mViewFlags & VISIBILITY_MASK) == VISIBLE)
|
||||
&& ((mViewFlags & FOCUSABLE) == FOCUSABLE)
|
||||
&& ((mViewFlags & ENABLED_MASK) == ENABLED)
|
||||
&& (sCanFocusZeroSized || !isLaidOut() || (mBottom > mTop) && (mRight > mLeft));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set flags controlling behavior of this view.
|
||||
*
|
||||
@@ -13452,6 +13477,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
return;
|
||||
}
|
||||
int privateFlags = mPrivateFlags;
|
||||
boolean shouldNotifyFocusableAvailable = false;
|
||||
|
||||
// If focusable is auto, update the FOCUSABLE bit.
|
||||
int focusableChangedByAuto = 0;
|
||||
@@ -13490,7 +13516,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
|| focusableChangedByAuto == 0
|
||||
|| viewRootImpl == null
|
||||
|| viewRootImpl.mThread == Thread.currentThread()) {
|
||||
mParent.focusableViewAvailable(this);
|
||||
shouldNotifyFocusableAvailable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13513,10 +13539,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
// about in case nothing has focus. even if this specific view
|
||||
// isn't focusable, it may contain something that is, so let
|
||||
// the root view try to give this focus if nothing else does.
|
||||
if ((mParent != null) && ((mViewFlags & ENABLED_MASK) == ENABLED)
|
||||
&& (mBottom > mTop) && (mRight > mLeft)) {
|
||||
mParent.focusableViewAvailable(this);
|
||||
}
|
||||
shouldNotifyFocusableAvailable = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13525,17 +13548,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
// a view becoming enabled should notify the parent as long as the view is also
|
||||
// visible and the parent wasn't already notified by becoming visible during this
|
||||
// setFlags invocation.
|
||||
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE
|
||||
&& ((changed & VISIBILITY_MASK) == 0)) {
|
||||
if ((mParent != null) && (mViewFlags & ENABLED_MASK) == ENABLED) {
|
||||
mParent.focusableViewAvailable(this);
|
||||
}
|
||||
}
|
||||
shouldNotifyFocusableAvailable = true;
|
||||
} else {
|
||||
if (hasFocus()) clearFocus();
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldNotifyFocusableAvailable) {
|
||||
if (mParent != null && canTakeFocus()) {
|
||||
mParent.focusableViewAvailable(this);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the GONE bit has changed */
|
||||
if ((changed & GONE) != 0) {
|
||||
needGlobalAttributesUpdate(false);
|
||||
@@ -20034,15 +20058,58 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
}
|
||||
}
|
||||
|
||||
final boolean wasLaidOut = isLaidOut();
|
||||
|
||||
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
|
||||
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
|
||||
|
||||
if (!wasLaidOut && isFocused()) {
|
||||
mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
|
||||
if (canTakeFocus()) {
|
||||
// We have a robust focus, so parents should no longer be wanting focus.
|
||||
clearParentsWantFocus();
|
||||
} else if (!getViewRootImpl().isInLayout()) {
|
||||
// This is a weird case. Most-likely the user, rather than ViewRootImpl, called
|
||||
// layout. In this case, there's no guarantee that parent layouts will be evaluated
|
||||
// and thus the safest action is to clear focus here.
|
||||
clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
|
||||
clearParentsWantFocus();
|
||||
} else if (!hasParentWantsFocus()) {
|
||||
// original requestFocus was likely on this view directly, so just clear focus
|
||||
clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
|
||||
}
|
||||
// otherwise, we let parents handle re-assigning focus during their layout passes.
|
||||
} else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
|
||||
mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
|
||||
View focused = findFocus();
|
||||
if (focused != null) {
|
||||
// Try to restore focus as close as possible to our starting focus.
|
||||
if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
|
||||
// Give up and clear focus once we've reached the top-most parent which wants
|
||||
// focus.
|
||||
focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
|
||||
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
|
||||
notifyEnterOrExitForAutoFillIfNeeded(true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasParentWantsFocus() {
|
||||
ViewParent parent = mParent;
|
||||
while (parent instanceof ViewGroup) {
|
||||
ViewGroup pv = (ViewGroup) parent;
|
||||
if ((pv.mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
|
||||
return true;
|
||||
}
|
||||
parent = pv.mParent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from layout when this view should
|
||||
* assign a size and position to each of its children.
|
||||
@@ -20149,6 +20216,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
mOverlay.getOverlayView().setRight(newWidth);
|
||||
mOverlay.getOverlayView().setBottom(newHeight);
|
||||
}
|
||||
// If this isn't laid out yet, focus assignment will be handled during the "deferment/
|
||||
// backtracking" of requestFocus during layout, so don't touch focus here.
|
||||
if (!sCanFocusZeroSized && isLaidOut()) {
|
||||
if (newWidth <= 0 || newHeight <= 0) {
|
||||
if (hasFocus()) {
|
||||
clearFocus();
|
||||
if (mParent instanceof ViewGroup) {
|
||||
((ViewGroup) mParent).clearFocusedInCluster();
|
||||
}
|
||||
}
|
||||
clearAccessibilityFocus();
|
||||
} else if (oldWidth <= 0 || oldHeight <= 0) {
|
||||
if (mParent != null && canTakeFocus()) {
|
||||
mParent.focusableViewAvailable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
rebuildOutline();
|
||||
}
|
||||
|
||||
|
||||
@@ -3215,22 +3215,31 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
}
|
||||
int descendantFocusability = getDescendantFocusability();
|
||||
|
||||
boolean result;
|
||||
switch (descendantFocusability) {
|
||||
case FOCUS_BLOCK_DESCENDANTS:
|
||||
return super.requestFocus(direction, previouslyFocusedRect);
|
||||
result = super.requestFocus(direction, previouslyFocusedRect);
|
||||
break;
|
||||
case FOCUS_BEFORE_DESCENDANTS: {
|
||||
final boolean took = super.requestFocus(direction, previouslyFocusedRect);
|
||||
return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
|
||||
result = took ? took : onRequestFocusInDescendants(direction,
|
||||
previouslyFocusedRect);
|
||||
break;
|
||||
}
|
||||
case FOCUS_AFTER_DESCENDANTS: {
|
||||
final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
|
||||
return took ? took : super.requestFocus(direction, previouslyFocusedRect);
|
||||
result = took ? took : super.requestFocus(direction, previouslyFocusedRect);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("descendant focusability must be "
|
||||
+ "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
|
||||
+ "but is " + descendantFocusability);
|
||||
}
|
||||
if (result && !isLaidOut() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) {
|
||||
mPrivateFlags |= PFLAG_WANTS_FOCUS;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user