In order to fix Bug
18920212, we have to track when a View enters
temporarily detached state and when it exits from that state. To do
that, ListView needs to use View#dispatchStartTemporaryDetach() instead
of directly calling View#onStartTemporaryDetach() because there is no
guarantee that existing applications have internally followed Call-Super
pattern.
With this CL, we are going to expose temporary detach state and its
dispatching methods as public APIs. Major changes are:
1. ListView's indirect children will start receiving temporary
dispatch callbacks. Previously only direct children have received
View#on{Start, Finish}TemporaryDetach() callbacks.
2. TextView can no longer assume that ListView never calls
View#View#dispatchStartTemporaryDetach() but directly call
View#onStartTemporaryDetach() instead. See the commit message
of [1] for details.
This also enables us to do the following fixes, which will be handled in
subsequent CLs.
A. ViewCompat support lib is finally able to rely on temporary
dispatch mechanism without reflection.
B. InputMethodManager is now able to ignore focus-in events from
temporarily detached Views. This will be done in the next CL [2].
[1]:
a440b002aa59e1455bdfa2c5a1ca51c74bbc19ac
[2]: Ia79bbd8468f768d546354382b47b39dd31ef7bb5
Bug:
18920212
Bug:
27905921
Change-Id: If8f780f8b71754f7533a65097304113ae1f5cf12
method public boolean dispatchDragEvent(android.view.DragEvent);
method protected void dispatchDraw(android.graphics.Canvas);
method public void dispatchDrawableHotspotChanged(float, float);
+ method public void dispatchFinishTemporaryDetach();
method protected boolean dispatchGenericFocusedEvent(android.view.MotionEvent);
method public boolean dispatchGenericMotionEvent(android.view.MotionEvent);
method protected boolean dispatchGenericPointerEvent(android.view.MotionEvent);
method protected void dispatchSetActivated(boolean);
method protected void dispatchSetPressed(boolean);
method protected void dispatchSetSelected(boolean);
+ method public void dispatchStartTemporaryDetach();
method public void dispatchSystemUiVisibilityChanged(int);
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public boolean isSelected();
method public boolean isShown();
method public boolean isSoundEffectsEnabled();
+ method public final boolean isTemporarilyDetached();
method public boolean isTextAlignmentResolved();
method public boolean isTextDirectionResolved();
method public boolean isVerticalFadingEdgeEnabled();
method public boolean dispatchDragEvent(android.view.DragEvent);
method protected void dispatchDraw(android.graphics.Canvas);
method public void dispatchDrawableHotspotChanged(float, float);
+ method public void dispatchFinishTemporaryDetach();
method protected boolean dispatchGenericFocusedEvent(android.view.MotionEvent);
method public boolean dispatchGenericMotionEvent(android.view.MotionEvent);
method protected boolean dispatchGenericPointerEvent(android.view.MotionEvent);
method protected void dispatchSetActivated(boolean);
method protected void dispatchSetPressed(boolean);
method protected void dispatchSetSelected(boolean);
+ method public void dispatchStartTemporaryDetach();
method public void dispatchSystemUiVisibilityChanged(int);
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public boolean isSelected();
method public boolean isShown();
method public boolean isSoundEffectsEnabled();
+ method public final boolean isTemporarilyDetached();
method public boolean isTextAlignmentResolved();
method public boolean isTextDirectionResolved();
method public boolean isVerticalFadingEdgeEnabled();
method public boolean dispatchDragEvent(android.view.DragEvent);
method protected void dispatchDraw(android.graphics.Canvas);
method public void dispatchDrawableHotspotChanged(float, float);
+ method public void dispatchFinishTemporaryDetach();
method protected boolean dispatchGenericFocusedEvent(android.view.MotionEvent);
method public boolean dispatchGenericMotionEvent(android.view.MotionEvent);
method protected boolean dispatchGenericPointerEvent(android.view.MotionEvent);
method protected void dispatchSetActivated(boolean);
method protected void dispatchSetPressed(boolean);
method protected void dispatchSetSelected(boolean);
+ method public void dispatchStartTemporaryDetach();
method public void dispatchSystemUiVisibilityChanged(int);
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public boolean isSelected();
method public boolean isShown();
method public boolean isSoundEffectsEnabled();
+ method public final boolean isTemporarilyDetached();
method public boolean isTextAlignmentResolved();
method public boolean isTextDirectionResolved();
method public boolean isVerticalFadingEdgeEnabled();
* 11111111 PFLAG3_POINTER_ICON_MASK
* 1 PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE
* 1 PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED
+ * 1 PFLAG3_TEMPORARY_DETACH
* |-------|-------|-------|-------|
*/
*/
private static final int PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED = 0x1000000;
+ /**
+ * Flag indicating that the view is temporarily detached from the parent view.
+ *
+ * @see #onStartTemporaryDetach()
+ * @see #onFinishTemporaryDetach()
+ */
+ static final int PFLAG3_TEMPORARY_DETACH = 0x2000000;
+
/* End of masks for mPrivateFlags3 */
/**
}
/**
- * @hide
+ * @return {@code true} when the View is in the state between {@link #onStartTemporaryDetach()}
+ * and {@link #onFinishTemporaryDetach()}.
+ */
+ public final boolean isTemporarilyDetached() {
+ return (mPrivateFlags3 & PFLAG3_TEMPORARY_DETACH) != 0;
+ }
+
+ /**
+ * Dispatch {@link #onStartTemporaryDetach()} to this View and its direct children if this is
+ * a container View.
*/
+ @CallSuper
public void dispatchStartTemporaryDetach() {
+ mPrivateFlags3 |= PFLAG3_TEMPORARY_DETACH;
onStartTemporaryDetach();
}
}
/**
- * @hide
+ * Dispatch {@link #onFinishTemporaryDetach()} to this View and its direct children if this is
+ * a container View.
*/
+ @CallSuper
public void dispatchFinishTemporaryDetach() {
onFinishTemporaryDetach();
+ mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;
}
/**
protected void onDetachedFromWindowInternal() {
mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+ mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;
removeUnsetPressCallback();
removeLongPressCallback();
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
- if (mTemporaryDetach) {
+ if (isTemporarilyDetached()) {
// If we are temporarily in the detach state, then do nothing.
return;
}
boolean mShowSoftInputOnFocus = true;
private boolean mPreserveSelection;
private boolean mRestartActionModeOnNextRefresh;
- boolean mTemporaryDetach;
boolean mIsBeingLongClicked;
showError();
mShowErrorAfterAttach = false;
}
- mTemporaryDetach = false;
final ViewTreeObserver observer = mTextView.getViewTreeObserver();
// No need to create the controller.
hideCursorAndSpanControllers();
stopTextActionModeWithPreservingSelection();
- mTemporaryDetach = false;
}
private void discardTextDisplayLists() {
stopTextActionModeWithPreservingSelection();
} else {
hideCursorAndSpanControllers();
- if (mTemporaryDetach) {
+ if (mTextView.isTemporarilyDetached()) {
stopTextActionModeWithPreservingSelection();
} else {
stopTextActionMode();
focusLayoutRestoreView = findFocus();
if (focusLayoutRestoreView != null) {
// Tell it we are going to mess with it.
- focusLayoutRestoreView.onStartTemporaryDetach();
+ focusLayoutRestoreView.dispatchStartTemporaryDetach();
}
}
requestFocus();
// our view hierarchy.
if (focusLayoutRestoreView != null
&& focusLayoutRestoreView.getWindowToken() != null) {
- focusLayoutRestoreView.onFinishTemporaryDetach();
+ focusLayoutRestoreView.dispatchFinishTemporaryDetach();
}
mLayoutMode = LAYOUT_NORMAL;
private int mCurTextColor;
private int mCurHintTextColor;
private boolean mFreezesText;
- private boolean mDispatchTemporaryDetach;
-
- /** Whether this view is temporarily detached from the parent view. */
- boolean mTemporaryDetach;
private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mTemporaryDetach = false;
-
if (mEditor != null) mEditor.onAttachedToWindow();
if (mPreDrawListenerDetached) {
}
}
- /**
- * @hide
- */
- @Override
- public void dispatchFinishTemporaryDetach() {
- mDispatchTemporaryDetach = true;
- super.dispatchFinishTemporaryDetach();
- mDispatchTemporaryDetach = false;
- }
-
- @Override
- public void onStartTemporaryDetach() {
- super.onStartTemporaryDetach();
- // Only track when onStartTemporaryDetach() is called directly,
- // usually because this instance is an editable field in a list
- if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
-
- // Tell the editor that we are temporarily detached. It can use this to preserve
- // selection state as needed.
- if (mEditor != null) mEditor.mTemporaryDetach = true;
- }
-
- @Override
- public void onFinishTemporaryDetach() {
- super.onFinishTemporaryDetach();
- // Only track when onStartTemporaryDetach() is called directly,
- // usually because this instance is an editable field in a list
- if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
- if (mEditor != null) mEditor.mTemporaryDetach = false;
- }
-
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
- if (mTemporaryDetach) {
+ if (isTemporarilyDetached()) {
// If we are temporarily in the detach state, then do nothing.
super.onFocusChanged(focused, direction, previouslyFocusedRect);
return;