blob: baa38bb2f6048d9036196ec5eeb75b192094e7b9 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import android.animation.LayoutTransition;
import android.annotation.CallSuper;
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.annotation.UiThread;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pools;
import android.util.Pools.SynchronizedPool;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.LayoutAnimationController;
import android.view.animation.Transformation;
import android.view.autofill.Helper;
import com.android.internal.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
/**
* <p>
* A <code>ViewGroup</code> is a special view that can contain other views
* (called children.) The view group is the base class for layouts and views
* containers. This class also defines the
* {@link android.view.ViewGroup.LayoutParams} class which serves as the base
* class for layouts parameters.
* </p>
*
* <p>
* Also see {@link LayoutParams} for layout attributes.
* </p>
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about creating user interface layouts, read the
* <a href="{@docRoot}guide/topics/ui/declaring-layout.html">XML Layouts</a> developer
* guide.</p></div>
*
* <p>Here is a complete implementation of a custom ViewGroup that implements
* a simple {@link android.widget.FrameLayout} along with the ability to stack
* children in left and right gutters.</p>
*
* {@sample development/samples/ApiDemos/src/com/example/android/apis/view/CustomLayout.java
* Complete}
*
* <p>If you are implementing XML layout attributes as shown in the example, this is the
* corresponding definition for them that would go in <code>res/values/attrs.xml</code>:</p>
*
* {@sample development/samples/ApiDemos/res/values/attrs.xml CustomLayout}
*
* <p>Finally the layout manager can be used in an XML layout like so:</p>
*
* {@sample development/samples/ApiDemos/res/layout/custom_layout.xml Complete}
*
* @attr ref android.R.styleable#ViewGroup_clipChildren
* @attr ref android.R.styleable#ViewGroup_clipToPadding
* @attr ref android.R.styleable#ViewGroup_layoutAnimation
* @attr ref android.R.styleable#ViewGroup_animationCache
* @attr ref android.R.styleable#ViewGroup_persistentDrawingCache
* @attr ref android.R.styleable#ViewGroup_alwaysDrawnWithCache
* @attr ref android.R.styleable#ViewGroup_addStatesFromChildren
* @attr ref android.R.styleable#ViewGroup_descendantFocusability
* @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
* @attr ref android.R.styleable#ViewGroup_splitMotionEvents
* @attr ref android.R.styleable#ViewGroup_layoutMode
*/
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
private static final String TAG = "ViewGroup";
private static final boolean DBG = false;
/**
* Views which have been hidden or removed which need to be animated on
* their way out.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected ArrayList<View> mDisappearingChildren;
/**
* Listener used to propagate events indicating when children are added
* and/or removed from a view group.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnHierarchyChangeListener mOnHierarchyChangeListener;
// The view contained within this ViewGroup that has or contains focus.
private View mFocused;
// 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
View mFocusedInCluster;
/**
* A Transformation used when drawing children, to
* apply on the child being drawn.
*/
private Transformation mChildTransformation;
/**
* Used to track the current invalidation region.
*/
RectF mInvalidateRegion;
/**
* A Transformation used to calculate a correct
* invalidation area when the application is autoscaled.
*/
Transformation mInvalidationTransformation;
// Current frontmost child that can accept drag and lies under the drag location.
// Used only to generate ENTER/EXIT events for pre-Nougat aps.
private View mCurrentDragChild;
// Metadata about the ongoing drag
private DragEvent mCurrentDragStartEvent;
private boolean mIsInterestedInDrag;
private HashSet<View> mChildrenInterestedInDrag;
// Used during drag dispatch
private PointF mLocalPoint;
// Lazily-created holder for point computations.
private float[] mTempPoint;
// Layout animation
private LayoutAnimationController mLayoutAnimationController;
private Animation.AnimationListener mAnimationListener;
// First touch target in the linked list of touch targets.
private TouchTarget mFirstTouchTarget;
// For debugging only. You can see these in hierarchyviewer.
@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
@ViewDebug.ExportedProperty(category = "events")
private long mLastTouchDownTime;
@ViewDebug.ExportedProperty(category = "events")
private int mLastTouchDownIndex = -1;
@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
@ViewDebug.ExportedProperty(category = "events")
private float mLastTouchDownX;
@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
@ViewDebug.ExportedProperty(category = "events")
private float mLastTouchDownY;
// First hover target in the linked list of hover targets.
// The hover targets are children which have received ACTION_HOVER_ENTER.
// They might not have actually handled the hover event, but we will
// continue sending hover events to them as long as the pointer remains over
// their bounds and the view group does not intercept hover.
private HoverTarget mFirstHoverTarget;
// True if the view group itself received a hover event.
// It might not have actually handled the hover event.
private boolean mHoveredSelf;
// The child capable of showing a tooltip and currently under the pointer.
private View mTooltipHoverTarget;
// True if the view group is capable of showing a tooltip and the pointer is directly
// over the view group but not one of its child views.
private boolean mTooltipHoveredSelf;
/**
* Internal flags.
*
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
@ViewDebug.ExportedProperty(flagMapping = {
@ViewDebug.FlagToString(mask = FLAG_CLIP_CHILDREN, equals = FLAG_CLIP_CHILDREN,
name = "CLIP_CHILDREN"),
@ViewDebug.FlagToString(mask = FLAG_CLIP_TO_PADDING, equals = FLAG_CLIP_TO_PADDING,
name = "CLIP_TO_PADDING"),
@ViewDebug.FlagToString(mask = FLAG_PADDING_NOT_NULL, equals = FLAG_PADDING_NOT_NULL,
name = "PADDING_NOT_NULL")
}, formatToHexString = true)
protected int mGroupFlags;
/**
* Either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
*/
private int mLayoutMode = LAYOUT_MODE_UNDEFINED;
/**
* NOTE: If you change the flags below make sure to reflect the changes
* the DisplayList class
*/
// When set, ViewGroup invalidates only the child's rectangle
// Set by default
static final int FLAG_CLIP_CHILDREN = 0x1;
// When set, ViewGroup excludes the padding area from the invalidate rectangle
// Set by default
private static final int FLAG_CLIP_TO_PADDING = 0x2;
// When set, dispatchDraw() will invoke invalidate(); this is set by drawChild() when
// a child needs to be invalidated and FLAG_OPTIMIZE_INVALIDATE is set
static final int FLAG_INVALIDATE_REQUIRED = 0x4;
// When set, dispatchDraw() will run the layout animation and unset the flag
private static final int FLAG_RUN_ANIMATION = 0x8;
// When set, there is either no layout animation on the ViewGroup or the layout
// animation is over
// Set by default
static final int FLAG_ANIMATION_DONE = 0x10;
// If set, this ViewGroup has padding; if unset there is no padding and we don't need
// to clip it, even if FLAG_CLIP_TO_PADDING is set
private static final int FLAG_PADDING_NOT_NULL = 0x20;
/** @deprecated - functionality removed */
@Deprecated
private static final int FLAG_ANIMATION_CACHE = 0x40;
// When set, this ViewGroup converts calls to invalidate(Rect) to invalidate() during a
// layout animation; this avoid clobbering the hierarchy
// Automatically set when the layout animation starts, depending on the animation's
// characteristics
static final int FLAG_OPTIMIZE_INVALIDATE = 0x80;
// When set, the next call to drawChild() will clear mChildTransformation's matrix
static final int FLAG_CLEAR_TRANSFORMATION = 0x100;
// When set, this ViewGroup invokes mAnimationListener.onAnimationEnd() and removes
// the children's Bitmap caches if necessary
// This flag is set when the layout animation is over (after FLAG_ANIMATION_DONE is set)
private static final int FLAG_NOTIFY_ANIMATION_LISTENER = 0x200;
/**
* When set, the drawing method will call {@link #getChildDrawingOrder(int, int)}
* to get the index of the child to draw for that iteration.
*
* @hide
*/
protected static final int FLAG_USE_CHILD_DRAWING_ORDER = 0x400;
/**
* When set, this ViewGroup supports static transformations on children; this causes
* {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} to be
* invoked when a child is drawn.
*
* Any subclass overriding
* {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} should
* set this flags in {@link #mGroupFlags}.
*
* {@hide}
*/
protected static final int FLAG_SUPPORT_STATIC_TRANSFORMATIONS = 0x800;
// UNUSED FLAG VALUE: 0x1000;
/**
* When set, this ViewGroup's drawable states also include those
* of its children.
*/
private static final int FLAG_ADD_STATES_FROM_CHILDREN = 0x2000;
/** @deprecated functionality removed */
@Deprecated
private static final int FLAG_ALWAYS_DRAWN_WITH_CACHE = 0x4000;
/** @deprecated functionality removed */
@Deprecated
private static final int FLAG_CHILDREN_DRAWN_WITH_CACHE = 0x8000;
/**
* When set, this group will go through its list of children to notify them of
* any drawable state change.
*/
private static final int FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE = 0x10000;
private static final int FLAG_MASK_FOCUSABILITY = 0x60000;
/**
* This view will get focus before any of its descendants.
*/
public static final int FOCUS_BEFORE_DESCENDANTS = 0x20000;
/**
* This view will get focus only if none of its descendants want it.
*/
public static final int FOCUS_AFTER_DESCENDANTS = 0x40000;
/**
* This view will block any of its descendants from getting focus, even
* if they are focusable.
*/
public static final int FOCUS_BLOCK_DESCENDANTS = 0x60000;
/**
* Used to map between enum in attrubutes and flag values.
*/
private static final int[] DESCENDANT_FOCUSABILITY_FLAGS =
{FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS,
FOCUS_BLOCK_DESCENDANTS};
/**
* When set, this ViewGroup should not intercept touch events.
* {@hide}
*/
protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
/**
* When set, this ViewGroup will split MotionEvents to multiple child Views when appropriate.
*/
private static final int FLAG_SPLIT_MOTION_EVENTS = 0x200000;
/**
* When set, this ViewGroup will not dispatch onAttachedToWindow calls
* to children when adding new views. This is used to prevent multiple
* onAttached calls when a ViewGroup adds children in its own onAttached method.
*/
private static final int FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW = 0x400000;
/**
* When true, indicates that a layoutMode has been explicitly set, either with
* an explicit call to {@link #setLayoutMode(int)} in code or from an XML resource.
* This distinguishes the situation in which a layout mode was inherited from
* one of the ViewGroup's ancestors and cached locally.
*/
private static final int FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET = 0x800000;
static final int FLAG_IS_TRANSITION_GROUP = 0x1000000;
static final int FLAG_IS_TRANSITION_GROUP_SET = 0x2000000;
/**
* When set, focus will not be permitted to enter this group if a touchscreen is present.
*/
static final int FLAG_TOUCHSCREEN_BLOCKS_FOCUS = 0x4000000;
/**
* When true, indicates that a call to startActionModeForChild was made with the type parameter
* and should not be ignored. This helps in backwards compatibility with the existing method
* without a type.
*
* @see #startActionModeForChild(View, android.view.ActionMode.Callback)
* @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
*/
private static final int FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED = 0x8000000;
/**
* When true, indicates that a call to startActionModeForChild was made without the type
* parameter. This helps in backwards compatibility with the existing method
* without a type.
*
* @see #startActionModeForChild(View, android.view.ActionMode.Callback)
* @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
*/
private static final int FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED = 0x10000000;
/**
* When set, indicates that a call to showContextMenuForChild was made with explicit
* coordinates within the initiating child view.
*/
private static final int FLAG_SHOW_CONTEXT_MENU_WITH_COORDS = 0x20000000;
/**
* Indicates which types of drawing caches are to be kept in memory.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected int mPersistentDrawingCache;
/**
* Used to indicate that no drawing cache should be kept in memory.
*
* @deprecated The view drawing cache was largely made obsolete with the introduction of
* hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
* layers are largely unnecessary and can easily result in a net loss in performance due to the
* cost of creating and updating the layer. In the rare cases where caching layers are useful,
* such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
* rendering. For software-rendered snapshots of a small part of the View hierarchy or
* individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
* {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
* software-rendered usages are discouraged and have compatibility issues with hardware-only
* rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
* bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
* reports or unit testing the {@link PixelCopy} API is recommended.
*/
@Deprecated
public static final int PERSISTENT_NO_CACHE = 0x0;
/**
* Used to indicate that the animation drawing cache should be kept in memory.
*
* @deprecated The view drawing cache was largely made obsolete with the introduction of
* hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
* layers are largely unnecessary and can easily result in a net loss in performance due to the
* cost of creating and updating the layer. In the rare cases where caching layers are useful,
* such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
* rendering. For software-rendered snapshots of a small part of the View hierarchy or
* individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
* {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
* software-rendered usages are discouraged and have compatibility issues with hardware-only
* rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
* bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
* reports or unit testing the {@link PixelCopy} API is recommended.
*/
@Deprecated
public static final int PERSISTENT_ANIMATION_CACHE = 0x1;
/**
* Used to indicate that the scrolling drawing cache should be kept in memory.
*
* @deprecated The view drawing cache was largely made obsolete with the introduction of
* hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
* layers are largely unnecessary and can easily result in a net loss in performance due to the
* cost of creating and updating the layer. In the rare cases where caching layers are useful,
* such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
* rendering. For software-rendered snapshots of a small part of the View hierarchy or
* individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
* {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
* software-rendered usages are discouraged and have compatibility issues with hardware-only
* rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
* bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
* reports or unit testing the {@link PixelCopy} API is recommended.
*/
@Deprecated
public static final int PERSISTENT_SCROLLING_CACHE = 0x2;
/**
* Used to indicate that all drawing caches should be kept in memory.
*
* @deprecated The view drawing cache was largely made obsolete with the introduction of
* hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
* layers are largely unnecessary and can easily result in a net loss in performance due to the
* cost of creating and updating the layer. In the rare cases where caching layers are useful,
* such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
* rendering. For software-rendered snapshots of a small part of the View hierarchy or
* individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
* {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
* software-rendered usages are discouraged and have compatibility issues with hardware-only
* rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
* bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
* reports or unit testing the {@link PixelCopy} API is recommended.
*/
@Deprecated
public static final int PERSISTENT_ALL_CACHES = 0x3;
// Layout Modes
private static final int LAYOUT_MODE_UNDEFINED = -1;
/**
* This constant is a {@link #setLayoutMode(int) layoutMode}.
* Clip bounds are the raw values of {@link #getLeft() left}, {@link #getTop() top},
* {@link #getRight() right} and {@link #getBottom() bottom}.
*/
public static final int LAYOUT_MODE_CLIP_BOUNDS = 0;
/**
* This constant is a {@link #setLayoutMode(int) layoutMode}.
* Optical bounds describe where a widget appears to be. They sit inside the clip
* bounds which need to cover a larger area to allow other effects,
* such as shadows and glows, to be drawn.
*/
public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1;
/** @hide */
public static int LAYOUT_MODE_DEFAULT = LAYOUT_MODE_CLIP_BOUNDS;
/**
* We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL
* are set at the same time.
*/
protected static final int CLIP_TO_PADDING_MASK = FLAG_CLIP_TO_PADDING | FLAG_PADDING_NOT_NULL;
// Index of the child's left position in the mLocation array
private static final int CHILD_LEFT_INDEX = 0;
// Index of the child's top position in the mLocation array
private static final int CHILD_TOP_INDEX = 1;
// Child views of this ViewGroup
private View[] mChildren;
// Number of valid children in the mChildren array, the rest should be null or not
// considered as children
private int mChildrenCount;
// Whether layout calls are currently being suppressed, controlled by calls to
// suppressLayout()
boolean mSuppressLayout = false;
// Whether any layout calls have actually been suppressed while mSuppressLayout
// has been true. This tracks whether we need to issue a requestLayout() when
// layout is later re-enabled.
private boolean mLayoutCalledWhileSuppressed = false;
private static final int ARRAY_INITIAL_CAPACITY = 12;
private static final int ARRAY_CAPACITY_INCREMENT = 12;
private static float[] sDebugLines;
// Used to draw cached views
Paint mCachePaint;
// Used to animate add/remove changes in layout
private LayoutTransition mTransition;
// The set of views that are currently being transitioned. This list is used to track views
// being removed that should not actually be removed from the parent yet because they are
// being animated.
private ArrayList<View> mTransitioningViews;
// List of children changing visibility. This is used to potentially keep rendering
// views during a transition when they otherwise would have become gone/invisible
private ArrayList<View> mVisibilityChangingChildren;
// Temporary holder of presorted children, only used for
// input/software draw dispatch for correctly Z ordering.
private ArrayList<View> mPreSortedChildren;
// Indicates how many of this container's child subtrees contain transient state
@ViewDebug.ExportedProperty(category = "layout")
private int mChildCountWithTransientState = 0;
/**
* Currently registered axes for nested scrolling. Flag set consisting of
* {@link #SCROLL_AXIS_HORIZONTAL} {@link #SCROLL_AXIS_VERTICAL} or {@link #SCROLL_AXIS_NONE}
* for null.
*/
private int mNestedScrollAxes;
// Used to manage the list of transient views, added by addTransientView()
private List<Integer> mTransientIndices = null;
private List<View> mTransientViews = null;
/**
* Keeps track of how many child views have UnhandledKeyEventListeners. This should only be
* updated on the UI thread so shouldn't require explicit synchronization.
*/
int mChildUnhandledKeyListeners = 0;
/**
* Empty ActionMode used as a sentinel in recursive entries to startActionModeForChild.
*
* @see #startActionModeForChild(View, android.view.ActionMode.Callback)
* @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
*/
private static final ActionMode SENTINEL_ACTION_MODE = new ActionMode() {
@Override
public void setTitle(CharSequence title) {}
@Override
public void setTitle(int resId) {}
@Override
public void setSubtitle(CharSequence subtitle) {}
@Override
public void setSubtitle(int resId) {}
@Override
public void setCustomView(View view) {}
@Override
public void invalidate() {}
@Override
public void finish() {}
@Override
public Menu getMenu() {
return null;
}
@Override
public CharSequence getTitle() {
return null;
}
@Override
public CharSequence getSubtitle() {
return null;
}
@Override
public View getCustomView() {
return null;
}
@Override
public MenuInflater getMenuInflater() {
return null;
}
};
public ViewGroup(Context context) {
this(context, null);
}
public ViewGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initViewGroup();
initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
}
private void initViewGroup() {
// ViewGroup doesn't draw by default
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
mGroupFlags |= FLAG_CLIP_CHILDREN;
mGroupFlags |= FLAG_CLIP_TO_PADDING;
mGroupFlags |= FLAG_ANIMATION_DONE;
mGroupFlags |= FLAG_ANIMATION_CACHE;
mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
}
setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
mChildren = new View[ARRAY_INITIAL_CAPACITY];
mChildrenCount = 0;
mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
}
private void initFromAttributes(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewGroup, defStyleAttr,
defStyleRes);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.ViewGroup_clipChildren:
setClipChildren(a.getBoolean(attr, true));
break;
case R.styleable.ViewGroup_clipToPadding:
setClipToPadding(a.getBoolean(attr, true));
break;
case R.styleable.ViewGroup_animationCache:
setAnimationCacheEnabled(a.getBoolean(attr, true));
break;
case R.styleable.ViewGroup_persistentDrawingCache:
setPersistentDrawingCache(a.getInt(attr, PERSISTENT_SCROLLING_CACHE));
break;
case R.styleable.ViewGroup_addStatesFromChildren:
setAddStatesFromChildren(a.getBoolean(attr, false));
break;
case R.styleable.ViewGroup_alwaysDrawnWithCache:
setAlwaysDrawnWithCacheEnabled(a.getBoolean(attr, true));
break;
case R.styleable.ViewGroup_layoutAnimation:
int id = a.getResourceId(attr, -1);
if (id > 0) {
setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id));
}
break;
case R.styleable.ViewGroup_descendantFocusability:
setDescendantFocusability(DESCENDANT_FOCUSABILITY_FLAGS[a.getInt(attr, 0)]);
break;
case R.styleable.ViewGroup_splitMotionEvents:
setMotionEventSplittingEnabled(a.getBoolean(attr, false));
break;
case R.styleable.ViewGroup_animateLayoutChanges:
boolean animateLayoutChanges = a.getBoolean(attr, false);
if (animateLayoutChanges) {
setLayoutTransition(new LayoutTransition());
}
break;
case R.styleable.ViewGroup_layoutMode:
setLayoutMode(a.getInt(attr, LAYOUT_MODE_UNDEFINED));
break;
case R.styleable.ViewGroup_transitionGroup:
setTransitionGroup(a.getBoolean(attr, false));
break;
case R.styleable.ViewGroup_touchscreenBlocksFocus:
setTouchscreenBlocksFocus(a.getBoolean(attr, false));
break;
}
}
a.recycle();
}
/**
* Gets the descendant focusability of this view group. The descendant
* focusability defines the relationship between this view group and its
* descendants when looking for a view to take focus in
* {@link #requestFocus(int, android.graphics.Rect)}.
*
* @return one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS},
* {@link #FOCUS_BLOCK_DESCENDANTS}.
*/
@ViewDebug.ExportedProperty(category = "focus", mapping = {
@ViewDebug.IntToString(from = FOCUS_BEFORE_DESCENDANTS, to = "FOCUS_BEFORE_DESCENDANTS"),
@ViewDebug.IntToString(from = FOCUS_AFTER_DESCENDANTS, to = "FOCUS_AFTER_DESCENDANTS"),
@ViewDebug.IntToString(from = FOCUS_BLOCK_DESCENDANTS, to = "FOCUS_BLOCK_DESCENDANTS")
})
public int getDescendantFocusability() {
return mGroupFlags & FLAG_MASK_FOCUSABILITY;
}
/**
* Set the descendant focusability of this view group. This defines the relationship
* between this view group and its descendants when looking for a view to
* take focus in {@link #requestFocus(int, android.graphics.Rect)}.
*
* @param focusability one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS},
* {@link #FOCUS_BLOCK_DESCENDANTS}.
*/
public void setDescendantFocusability(int focusability) {
switch (focusability) {
case FOCUS_BEFORE_DESCENDANTS:
case FOCUS_AFTER_DESCENDANTS:
case FOCUS_BLOCK_DESCENDANTS:
break;
default:
throw new IllegalArgumentException("must be one of FOCUS_BEFORE_DESCENDANTS, "
+ "FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS");
}
mGroupFlags &= ~FLAG_MASK_FOCUSABILITY;
mGroupFlags |= (focusability & FLAG_MASK_FOCUSABILITY);
}
@Override
void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {
if (mFocused != null) {
mFocused.unFocus(this);
mFocused = null;
mFocusedInCluster = null;
}
super.handleFocusGainInternal(direction, previouslyFocusedRect);
}
@Override
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
// Unfocus us, if necessary
super.unFocus(focused);
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus(focused);
}
mFocused = child;
}
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
}
void setDefaultFocus(View child) {
// Stop at any higher view which is explicitly focused-by-default
if (mDefaultFocus != null && mDefaultFocus.isFocusedByDefault()) {
return;
}
mDefaultFocus = child;
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).setDefaultFocus(this);
}
}
/**
* 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 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).clearDefaultFocus(this);
}
}
@Override
boolean hasDefaultFocus() {
return mDefaultFocus != null || super.hasDefaultFocus();
}
/**
* Removes {@code child} (and associated focusedInCluster chain) from the cluster containing
* it.
* <br>
* This is intended to be run on {@code child}'s immediate parent. This is necessary because
* the chain is sometimes cleared after {@code child} has been detached.
*/
void clearFocusedInCluster(View child) {
if (mFocusedInCluster != child) {
return;
}
clearFocusedInCluster();
}
/**
* Removes the focusedInCluster chain from this up to the cluster containing it.
*/
void clearFocusedInCluster() {
View top = findKeyboardNavigationCluster();
ViewParent parent = this;
do {
((ViewGroup) parent).mFocusedInCluster = null;
if (parent == top) {
break;
}
parent = parent.getParent();
} while (parent instanceof ViewGroup);
}
@Override
public void focusableViewAvailable(View v) {
if (mParent != null
// shortcut: don't report a new focusable view if we block our descendants from
// getting focus or if we're not visible
&& (getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS)
&& ((mViewFlags & VISIBILITY_MASK) == VISIBLE)
&& (isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen())
// shortcut: don't report a new focusable view if we already are focused
// (and we don't prefer our descendants)
//
// note: knowing that mFocused is non-null is not a good enough reason
// to break the traversal since in that case we'd actually have to find
// the focused view and make sure it wasn't FOCUS_AFTER_DESCENDANTS and
// an ancestor of v; this will get checked for at ViewAncestor
&& !(isFocused() && getDescendantFocusability() != FOCUS_AFTER_DESCENDANTS)) {
mParent.focusableViewAvailable(v);
}
}
@Override
public boolean showContextMenuForChild(View originalView) {
if (isShowingContextMenuWithCoords()) {
// We're being called for compatibility. Return false and let the version
// with coordinates recurse up.
return false;
}
return mParent != null && mParent.showContextMenuForChild(originalView);
}
/**
* @hide used internally for compatibility with existing app code only
*/
public final boolean isShowingContextMenuWithCoords() {
return (mGroupFlags & FLAG_SHOW_CONTEXT_MENU_WITH_COORDS) != 0;
}
@Override
public boolean showContextMenuForChild(View originalView, float x, float y) {
try {
mGroupFlags |= FLAG_SHOW_CONTEXT_MENU_WITH_COORDS;
if (showContextMenuForChild(originalView)) {
return true;
}
} finally {
mGroupFlags &= ~FLAG_SHOW_CONTEXT_MENU_WITH_COORDS;
}
return mParent != null && mParent.showContextMenuForChild(originalView, x, y);
}
@Override
public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
if ((mGroupFlags & FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED) == 0) {
// This is the original call.
try {
mGroupFlags |= FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED;
return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
} finally {
mGroupFlags &= ~FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED;
}
} else {
// We are being called from the new method with type.
return SENTINEL_ACTION_MODE;
}
}
@Override
public ActionMode startActionModeForChild(
View originalView, ActionMode.Callback callback, int type) {
if ((mGroupFlags & FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED) == 0
&& type == ActionMode.TYPE_PRIMARY) {
ActionMode mode;
try {
mGroupFlags |= FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED;
mode = startActionModeForChild(originalView, callback);
} finally {
mGroupFlags &= ~FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED;
}
if (mode != SENTINEL_ACTION_MODE) {
return mode;
}
}
if (mParent != null) {
try {
return mParent.startActionModeForChild(originalView, callback, type);
} catch (AbstractMethodError ame) {
// Custom view parents might not implement this method.
return mParent.startActionModeForChild(originalView, callback);
}
}
return null;
}
/**
* @hide
*/
@Override
public boolean dispatchActivityResult(
String who, int requestCode, int resultCode, Intent data) {
if (super.dispatchActivityResult(who, requestCode, resultCode, data)) {
return true;
}
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.dispatchActivityResult(who, requestCode, resultCode, data)) {
return true;
}
}
return false;
}
/**
* Find the nearest view in the specified direction that wants to take
* focus.
*
* @param focused The view that currently has focus
* @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and
* FOCUS_RIGHT, or 0 for not applicable.
*/
@Override
public View focusSearch(View focused, int direction) {
if (isRootNamespace()) {
// root namespace means we should consider ourselves the top of the
// tree for focus searching; otherwise we could be focus searching
// into other tabs. see LocalActivityManager and TabHost for more info.
return FocusFinder.getInstance().findNextFocus(this, focused, direction);
} else if (mParent != null) {
return mParent.focusSearch(focused, direction);
}
return null;
}
@Override
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
return false;
}
@Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
ViewParent parent = mParent;
if (parent == null) {
return false;
}
final boolean propagate = onRequestSendAccessibilityEvent(child, event);
if (!propagate) {
return false;
}
return parent.requestSendAccessibilityEvent(this, event);
}
/**
* Called when a child has requested sending an {@link AccessibilityEvent} and
* gives an opportunity to its parent to augment the event.
* <p>
* If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
* {@link android.view.View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
* {@link android.view.View.AccessibilityDelegate#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent)}
* is responsible for handling this call.
* </p>
*
* @param child The child which requests sending the event.
* @param event The event to be sent.
* @return True if the event should be sent.
*
* @see #requestSendAccessibilityEvent(View, AccessibilityEvent)
*/
public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
if (mAccessibilityDelegate != null) {
return mAccessibilityDelegate.onRequestSendAccessibilityEvent(this, child, event);
} else {
return onRequestSendAccessibilityEventInternal(child, event);
}
}
/**
* @see #onRequestSendAccessibilityEvent(View, AccessibilityEvent)
*
* Note: Called from the default {@link View.AccessibilityDelegate}.
*
* @hide
*/
public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
return true;
}
/**
* Called when a child view has changed whether or not it is tracking transient state.
*/
@Override
public void childHasTransientStateChanged(View child, boolean childHasTransientState) {
final boolean oldHasTransientState = hasTransientState();
if (childHasTransientState) {
mChildCountWithTransientState++;
} else {
mChildCountWithTransientState--;
}
final boolean newHasTransientState = hasTransientState();
if (mParent != null && oldHasTransientState != newHasTransientState) {
try {
mParent.childHasTransientStateChanged(this, newHasTransientState);
} catch (AbstractMethodError e) {
Log.e(TAG, mParent.getClass().getSimpleName() +
" does not fully implement ViewParent", e);
}
}
}
@Override
public boolean hasTransientState() {
return mChildCountWithTransientState > 0 || super.hasTransientState();
}
@Override
public boolean dispatchUnhandledMove(View focused, int direction) {
return mFocused != null &&
mFocused.dispatchUnhandledMove(focused, direction);
}
@Override
public void clearChildFocus(View child) {
if (DBG) {
System.out.println(this + " clearChildFocus()");
}
mFocused = null;
if (mParent != null) {
mParent.clearChildFocus(this);
}
}
@Override
public void clearFocus() {
if (DBG) {
System.out.println(this + " clearFocus()");
}
if (mFocused == null) {
super.clearFocus();
} else {
View focused = mFocused;
mFocused = null;
focused.clearFocus();
}
}
@Override
void unFocus(View focused) {
if (DBG) {
System.out.println(this + " unFocus()");
}
if (mFocused == null) {
super.unFocus(focused);
} else {
mFocused.unFocus(focused);
mFocused = null;
}
}
/**
* Returns the focused child of this view, if any. The child may have focus
* or contain focus.
*
* @return the focused child or null.
*/
public View getFocusedChild() {
return mFocused;
}
View getDeepestFocusedChild() {
View v = this;
while (v != null) {
if (v.isFocused()) {
return v;
}
v = v instanceof ViewGroup ? ((ViewGroup) v).getFocusedChild() : null;
}
return null;
}
/**
* Returns true if this view has or contains focus
*
* @return true if this view has or contains focus
*/
@Override
public boolean hasFocus() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null;
}
/*
* (non-Javadoc)
*
* @see android.view.View#findFocus()
*/
@Override
public View findFocus() {
if (DBG) {
System.out.println("Find focus in " + this + ": flags="
+ isFocused() + ", child=" + mFocused);
}
if (isFocused()) {
return this;
}
if (mFocused != null) {
return mFocused.findFocus();
}
return null;
}
@Override
boolean hasFocusable(boolean allowAutoFocus, boolean dispatchExplicit) {
// This should probably be super.hasFocusable, but that would change
// behavior. Historically, we have not checked the ancestor views for
// shouldBlockFocusForTouchscreen() in ViewGroup.hasFocusable.
// Invisible and gone views are never focusable.
if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
// Only use effective focusable value when allowed.
if ((allowAutoFocus || getFocusable() != FOCUSABLE_AUTO) && isFocusable()) {
return true;
}
// Determine whether we have a focused descendant.
final int descendantFocusability = getDescendantFocusability();
if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
return hasFocusableChild(dispatchExplicit);
}
return false;
}
boolean hasFocusableChild(boolean dispatchExplicit) {
// Determine whether we have a focusable descendant.
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
// In case the subclass has overridden has[Explicit]Focusable, dispatch
// to the expected one for each child even though we share logic here.
if ((dispatchExplicit && child.hasExplicitFocusable())
|| (!dispatchExplicit && child.hasFocusable())) {
return true;
}
}
return false;
}
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
final int focusableCount = views.size();
final int descendantFocusability = getDescendantFocusability();
final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();
final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);
if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
if (focusSelf) {
super.addFocusables(views, direction, focusableMode);
}
return;
}
if (blockFocusForTouchscreen) {
focusableMode |= FOCUSABLES_TOUCH_MODE;
}
if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) {
super.addFocusables(views, direction, focusableMode);
}
int count = 0;
final View[] children = new View[mChildrenCount];
for (int i = 0; i < mChildrenCount; ++i) {
View child = mChildren[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
children[count++] = child;
}
}
FocusFinder.sort(children, 0, count, this, isLayoutRtl());
for (int i = 0; i < count; ++i) {
children[i].addFocusables(views, direction, focusableMode);
}
// When set to FOCUS_AFTER_DESCENDANTS, we only add ourselves if
// there aren't any focusable descendants. this is
// to avoid the focus search finding layouts when a more precise search
// among the focusable children would be more interesting.
if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf
&& focusableCount == views.size()) {
super.addFocusables(views, direction, focusableMode);
}
}
@Override
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.
return;
}
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
int count = 0;
final View[] visibleChildren = new View[mChildrenCount];
for (int i = 0; i < mChildrenCount; ++i) {
final View child = mChildren[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
visibleChildren[count++] = child;
}
}
FocusFinder.sort(visibleChildren, 0, count, this, isLayoutRtl());
for (int i = 0; i < count; ++i) {
visibleChildren[i].addKeyboardNavigationClusters(views, direction);
}
}
/**
* Set whether this ViewGroup should ignore focus requests for itself and its children.
* If this option is enabled and the ViewGroup or a descendant currently has focus, focus
* will proceed forward.
*
* @param touchscreenBlocksFocus true to enable blocking focus in the presence of a touchscreen
*/
public void setTouchscreenBlocksFocus(boolean touchscreenBlocksFocus) {
if (touchscreenBlocksFocus) {
mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
if (hasFocus() && !isKeyboardNavigationCluster()) {
final View focusedChild = getDeepestFocusedChild();
if (!focusedChild.isFocusableInTouchMode()) {
final View newFocus = focusSearch(FOCUS_FORWARD);
if (newFocus != null) {
newFocus.requestFocus();
}
}
}
} else {
mGroupFlags &= ~FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
}
}
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.
*/
@ViewDebug.ExportedProperty(category = "focus")
public boolean getTouchscreenBlocksFocus() {
return (mGroupFlags & FLAG_TOUCHSCREEN_BLOCKS_FOCUS) != 0;
}
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)
&& !(isKeyboardNavigationCluster()
&& (hasFocus() || (findKeyboardNavigationCluster() != this)));
}
@Override
public void findViewsWithText(ArrayList<View> outViews, CharSequence text, int flags) {
super.findViewsWithText(outViews, text, flags);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < childrenCount; i++) {
View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
&& (child.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
child.findViewsWithText(outViews, text, flags);
}
}
}
/** @hide */
@Override
public View findViewByAccessibilityIdTraversal(int accessibilityId) {
View foundView = super.findViewByAccessibilityIdTraversal(accessibilityId);
if (foundView != null) {
return foundView;
}
if (getAccessibilityNodeProvider() != null) {
return null;
}
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < childrenCount; i++) {
View child = children[i];
foundView = child.findViewByAccessibilityIdTraversal(accessibilityId);
if (foundView != null) {
return foundView;
}
}
return null;
}
/** @hide */
@Override
public View findViewByAutofillIdTraversal(int autofillId) {
View foundView = super.findViewByAutofillIdTraversal(autofillId);
if (foundView != null) {
return foundView;
}
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < childrenCount; i++) {
View child = children[i];
foundView = child.findViewByAutofillIdTraversal(autofillId);
if (foundView != null) {
return foundView;
}
}
return null;
}
@Override
public void dispatchWindowFocusChanged(boolean hasFocus) {
super.dispatchWindowFocusChanged(hasFocus);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].dispatchWindowFocusChanged(hasFocus);
}
}
@Override
public void addTouchables(ArrayList<View> views) {
super.addTouchables(views);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
child.addTouchables(views);
}
}
}
/**
* @hide
*/
@Override
public void makeOptionalFitsSystemWindows() {
super.makeOptionalFitsSystemWindows();
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].makeOptionalFitsSystemWindows();
}
}
@Override
public void dispatchDisplayHint(int hint) {
super.dispatchDisplayHint(hint);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].dispatchDisplayHint(hint);
}
}
/**
* Called when a view's visibility has changed. Notify the parent to take any appropriate
* action.
*
* @param child The view whose visibility has changed
* @param oldVisibility The previous visibility value (GONE, INVISIBLE, or VISIBLE).
* @param newVisibility The new visibility value (GONE, INVISIBLE, or VISIBLE).
* @hide
*/
protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
if (mTransition != null) {
if (newVisibility == VISIBLE) {
mTransition.showChild(this, child, oldVisibility);
} else {
mTransition.hideChild(this, child, newVisibility);
if (mTransitioningViews != null && mTransitioningViews.contains(child)) {
// Only track this on disappearing views - appearing views are already visible
// and don't need special handling during drawChild()
if (mVisibilityChangingChildren == null) {
mVisibilityChangingChildren = new ArrayList<View>();
}
mVisibilityChangingChildren.add(child);
addDisappearingView(child);
}
}
}
// in all cases, for drags
if (newVisibility == VISIBLE && mCurrentDragStartEvent != null) {
if (!mChildrenInterestedInDrag.contains(child)) {
notifyChildOfDragStart(child);
}
}
}
@Override
protected void dispatchVisibilityChanged(View changedView, int visibility) {
super.dispatchVisibilityChanged(changedView, visibility);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].dispatchVisibilityChanged(changedView, visibility);
}
}
@Override
public void dispatchWindowVisibilityChanged(int visibility) {
super.dispatchWindowVisibilityChanged(visibility);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].dispatchWindowVisibilityChanged(visibility);
}
}
@Override
boolean dispatchVisibilityAggregated(boolean isVisible) {
isVisible = super.dispatchVisibilityAggregated(isVisible);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
// Only dispatch to visible children. Not visible children and their subtrees already
// know that they aren't visible and that's not going to change as a result of
// whatever triggered this dispatch.
if (children[i].getVisibility() == VISIBLE) {
children[i].dispatchVisibilityAggregated(isVisible);
}
}
return isVisible;
}
@Override
public void dispatchConfigurationChanged(Configuration newConfig) {
super.dispatchConfigurationChanged(newConfig);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].dispatchConfigurationChanged(newConfig);
}
}
@Override
public void recomputeViewAttributes(View child) {
if (mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
ViewParent parent = mParent;
if (parent != null) parent.recomputeViewAttributes(this);
}
}
@Override
void dispatchCollectViewAttributes(AttachInfo attachInfo, int visibility) {
if ((visibility & VISIBILITY_MASK) == VISIBLE) {
super.dispatchCollectViewAttributes(attachInfo, visibility);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchCollectViewAttributes(attachInfo,
visibility | (child.mViewFlags&VISIBILITY_MASK));
}
}
}
@Override
public void bringChildToFront(View child) {
final int index = indexOfChild(child);
if (index >= 0) {
removeFromArray(index);
addInArray(child, mChildrenCount);
child.mParent = this;
requestLayout();
invalidate();
}
}
private PointF getLocalPoint() {
if (mLocalPoint == null) mLocalPoint = new PointF();
return mLocalPoint;
}
@Override
boolean dispatchDragEnterExitInPreN(DragEvent event) {
if (event.mAction == DragEvent.ACTION_DRAG_EXITED && mCurrentDragChild != null) {
// The drag exited a sub-tree of views; notify of the exit all descendants that are in
// entered state.
// We don't need this recursive delivery for ENTERED events because they get generated
// from the recursive delivery of LOCATION/DROP events, and hence, don't need their own
// recursion.
mCurrentDragChild.dispatchDragEnterExitInPreN(event);
mCurrentDragChild = null;
}
return mIsInterestedInDrag && super.dispatchDragEnterExitInPreN(event);
}
// TODO: Write real docs
@Override
public boolean dispatchDragEvent(DragEvent event) {
boolean retval = false;
final float tx = event.mX;
final float ty = event.mY;
final ClipData td = event.mClipData;
// Dispatch down the view hierarchy
final PointF localPoint = getLocalPoint();
switch (event.mAction) {
case DragEvent.ACTION_DRAG_STARTED: {
// Clear the state to recalculate which views we drag over.
mCurrentDragChild = null;
// Set up our tracking of drag-started notifications
mCurrentDragStartEvent = DragEvent.obtain(event);
if (mChildrenInterestedInDrag == null) {
mChildrenInterestedInDrag = new HashSet<View>();
} else {
mChildrenInterestedInDrag.clear();
}
// Now dispatch down to our children, caching the responses
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
child.mPrivateFlags2 &= ~View.DRAG_MASK;
if (child.getVisibility() == VISIBLE) {
if (notifyChildOfDragStart(children[i])) {
retval = true;
}
}
}
// Notify itself of the drag start.
mIsInterestedInDrag = super.dispatchDragEvent(event);
if (mIsInterestedInDrag) {
retval = true;
}
if (!retval) {
// Neither us nor any of our children are interested in this drag, so stop tracking
// the current drag event.
mCurrentDragStartEvent.recycle();
mCurrentDragStartEvent = null;
}
} break;
case DragEvent.ACTION_DRAG_ENDED: {
// Release the bookkeeping now that the drag lifecycle has ended
final HashSet<View> childrenInterestedInDrag = mChildrenInterestedInDrag;
if (childrenInterestedInDrag != null) {
for (View child : childrenInterestedInDrag) {
// If a child was interested in the ongoing drag, it's told that it's over
if (child.dispatchDragEvent(event)) {
retval = true;
}
}
childrenInterestedInDrag.clear();
}
if (mCurrentDragStartEvent != null) {
mCurrentDragStartEvent.recycle();
mCurrentDragStartEvent = null;
}
if (mIsInterestedInDrag) {
if (super.dispatchDragEvent(event)) {
retval = true;
}
mIsInterestedInDrag = false;
}
} break;
case DragEvent.ACTION_DRAG_LOCATION:
case DragEvent.ACTION_DROP: {
// Find the [possibly new] drag target
View target = findFrontmostDroppableChildAt(event.mX, event.mY, localPoint);
if (target != mCurrentDragChild) {
if (sCascadedDragDrop) {
// For pre-Nougat apps, make sure that the whole hierarchy of views that contain
// the drag location is kept in the state between ENTERED and EXITED events.
// (Starting with N, only the innermost view will be in that state).
final int action = event.mAction;
// Position should not be available for ACTION_DRAG_ENTERED and
// ACTION_DRAG_EXITED.
event.mX = 0;
event.mY = 0;
event.mClipData = null;
if (mCurrentDragChild != null) {
event.mAction = DragEvent.ACTION_DRAG_EXITED;
mCurrentDragChild.dispatchDragEnterExitInPreN(event);
}
if (target != null) {
event.mAction = DragEvent.ACTION_DRAG_ENTERED;
target.dispatchDragEnterExitInPreN(event);
}
event.mAction = action;
event.mX = tx;
event.mY = ty;
event.mClipData = td;
}
mCurrentDragChild = target;
}
if (target == null && mIsInterestedInDrag) {
target = this;
}
// Dispatch the actual drag notice, localized into the target coordinates.
if (target != null) {
if (target != this) {
event.mX = localPoint.x;
event.mY = localPoint.y;
retval = target.dispatchDragEvent(event);
event.mX = tx;
event.mY = ty;
if (mIsInterestedInDrag) {
final boolean eventWasConsumed;
if (sCascadedDragDrop) {
eventWasConsumed = retval;
} else {
eventWasConsumed = event.mEventHandlerWasCalled;
}
if (!eventWasConsumed) {
retval = super.dispatchDragEvent(event);
}
}
} else {
retval = super.dispatchDragEvent(event);
}
}
} break;
}
return retval;
}
// Find the frontmost child view that lies under the given point, and calculate
// the position within its own local coordinate system.
View findFrontmostDroppableChildAt(float x, float y, PointF outLocalPoint) {
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if (!child.canAcceptDrag()) {
continue;
}
if (isTransformedTouchPointInView(x, y, child, outLocalPoint)) {
return child;
}
}
return null;
}
boolean notifyChildOfDragStart(View child) {
// The caller guarantees that the child is not in mChildrenInterestedInDrag yet.
if (ViewDebug.DEBUG_DRAG) {
Log.d(View.VIEW_LOG_TAG, "Sending drag-started to view: " + child);
}
final float tx = mCurrentDragStartEvent.mX;
final float ty = mCurrentDragStartEvent.mY;
final float[] point = getTempPoint();
point[0] = tx;
point[1] = ty;
transformPointToViewLocal(point, child);
mCurrentDragStartEvent.mX = point[0];
mCurrentDragStartEvent.mY = point[1];
final boolean canAccept = child.dispatchDragEvent(mCurrentDragStartEvent);
mCurrentDragStartEvent.mX = tx;
mCurrentDragStartEvent.mY = ty;
mCurrentDragStartEvent.mEventHandlerWasCalled = false;
if (canAccept) {
mChildrenInterestedInDrag.add(child);
if (!child.canAcceptDrag()) {
child.mPrivateFlags2 |= View.PFLAG2_DRAG_CAN_ACCEPT;
child.refreshDrawableState();
}
}
return canAccept;
}
@Override
public void dispatchWindowSystemUiVisiblityChanged(int visible) {
super.dispatchWindowSystemUiVisiblityChanged(visible);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i=0; i <count; i++) {
final View child = children[i];
child.dispatchWindowSystemUiVisiblityChanged(visible);
}
}
@Override
public void dispatchSystemUiVisibilityChanged(int visible) {
super.dispatchSystemUiVisibilityChanged(visible);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i=0; i <count; i++) {
final View child = children[i];
child.dispatchSystemUiVisibilityChanged(visible);
}
}
@Override
boolean updateLocalSystemUiVisibility(int localValue, int localChanges) {
boolean changed = super.updateLocalSystemUiVisibility(localValue, localChanges);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i=0; i <count; i++) {
final View child = children[i];
changed |= child.updateLocalSystemUiVisibility(localValue, localChanges);
}
return changed;
}
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
return super.dispatchKeyEventPreIme(event);
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
return mFocused.dispatchKeyEventPreIme(event);
}
return false;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
return super.dispatchKeyShortcutEvent(event);
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
return mFocused.dispatchKeyShortcutEvent(event);
}
return false;
}
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTrackballEvent(event, 1);
}
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchTrackballEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchTrackballEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
@Override
public boolean dispatchCapturedPointerEvent(MotionEvent event) {
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchCapturedPointerEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchCapturedPointerEvent(event)) {
return true;
}
}
return false;
}
@Override
public void dispatchPointerCaptureChanged(boolean hasCapture) {
exitHoverTargets();
super.dispatchPointerCaptureChanged(hasCapture);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].dispatchPointerCaptureChanged(hasCapture);
}
}
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
if (isOnScrollbarThumb(x, y) || isDraggingScrollBar()) {
return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
}
// Check what the child under the pointer says about the pointer.
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
final PointerIcon pointerIcon =
dispatchResolvePointerIcon(event, pointerIndex, child);
if (pointerIcon != null) {
if (preorderedList != null) preorderedList.clear();
return pointerIcon;
}
}
if (preorderedList != null) preorderedList.clear();
}
// The pointer is not a child or the child has no preferences, returning the default
// implementation.
return super.onResolvePointerIcon(event, pointerIndex);
}
private PointerIcon dispatchResolvePointerIcon(MotionEvent event, int pointerIndex,
View child) {
final PointerIcon pointerIcon;
if (!child.hasIdentityMatrix()) {
MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
pointerIcon = child.onResolvePointerIcon(transformedEvent, pointerIndex);
transformedEvent.recycle();
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
pointerIcon = child.onResolvePointerIcon(event, pointerIndex);
event.offsetLocation(-offsetX, -offsetY);
}
return pointerIcon;
}
private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
final int childIndex;
if (customOrder) {
final int childIndex1 = getChildDrawingOrder(childrenCount, i);
if (childIndex1 >= childrenCount) {
throw new IndexOutOfBoundsException("getChildDrawingOrder() "
+ "returned invalid index " + childIndex1
+ " (child count is " + childrenCount + ")");
}
childIndex = childIndex1;
} else {
childIndex = i;
}
return childIndex;
}
@SuppressWarnings({"ConstantConditions"})
@Override
protected boolean dispatchHoverEvent(MotionEvent event) {
final int action = event.getAction();
// First check whether the view group wants to intercept the hover event.
final boolean interceptHover = onInterceptHoverEvent(event);
event.setAction(action); // restore action in case it was changed
MotionEvent eventNoHistory = event;
boolean handled = false;
// Send events to the hovered children and build a new list of hover targets until
// one is found that handles the event.
HoverTarget firstOldHoverTarget = mFirstHoverTarget;
mFirstHoverTarget = null;
if (!interceptHover && action != MotionEvent.ACTION_HOVER_EXIT) {
final float x = event.getX();
final float y = event.getY();
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
HoverTarget lastHoverTarget = null;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
// Obtain a hover target for this child. Dequeue it from the
// old hover target list if the child was previously hovered.
HoverTarget hoverTarget = firstOldHoverTarget;
final boolean wasHovered;
for (HoverTarget predecessor = null; ;) {
if (hoverTarget == null) {
hoverTarget = HoverTarget.obtain(child);
wasHovered = false;
break;
}
if (hoverTarget.child == child) {
if (predecessor != null) {
predecessor.next = hoverTarget.next;
} else {
firstOldHoverTarget = hoverTarget.next;
}
hoverTarget.next = null;
wasHovered = true;
break;
}
predecessor = hoverTarget;
hoverTarget = hoverTarget.next;
}
// Enqueue the hover target onto the new hover target list.
if (lastHoverTarget != null) {
lastHoverTarget.next = hoverTarget;
} else {
mFirstHoverTarget = hoverTarget;
}
lastHoverTarget = hoverTarget;
// Dispatch the event to the child.
if (action == MotionEvent.ACTION_HOVER_ENTER) {
if (!wasHovered) {
// Send the enter as is.
handled |= dispatchTransformedGenericPointerEvent(
event, child); // enter
}
} else if (action == MotionEvent.ACTION_HOVER_MOVE) {
if (!wasHovered) {
// Synthesize an enter from a move.
eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
handled |= dispatchTransformedGenericPointerEvent(
eventNoHistory, child); // enter
eventNoHistory.setAction(action);
handled |= dispatchTransformedGenericPointerEvent(
eventNoHistory, child); // move
} else {
// Send the move as is.
handled |= dispatchTransformedGenericPointerEvent(event, child);
}
}
if (handled) {
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
}
// Send exit events to all previously hovered children that are no longer hovered.
while (firstOldHoverTarget != null) {
final View child = firstOldHoverTarget.child;
// Exit the old hovered child.
if (action == MotionEvent.ACTION_HOVER_EXIT) {
// Send the exit as is.
handled |= dispatchTransformedGenericPointerEvent(
event, child); // exit
} else {
// Synthesize an exit from a move or enter.
// Ignore the result because hover focus has moved to a different view.
if (action == MotionEvent.ACTION_HOVER_MOVE) {
final boolean hoverExitPending = event.isHoverExitPending();
event.setHoverExitPending(true);
dispatchTransformedGenericPointerEvent(
event, child); // move
event.setHoverExitPending(hoverExitPending);
}
eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
dispatchTransformedGenericPointerEvent(
eventNoHistory, child); // exit
eventNoHistory.setAction(action);
}
final HoverTarget nextOldHoverTarget = firstOldHoverTarget.next;
firstOldHoverTarget.recycle();
firstOldHoverTarget = nextOldHoverTarget;
}
// Send events to the view group itself if no children have handled it and the view group
// itself is not currently being hover-exited.
boolean newHoveredSelf = !handled &&
(action != MotionEvent.ACTION_HOVER_EXIT) && !event.isHoverExitPending();
if (newHoveredSelf == mHoveredSelf) {
if (newHoveredSelf) {
// Send event to the view group as before.
handled |= super.dispatchHoverEvent(event);
}
} else {
if (mHoveredSelf) {
// Exit the view group.
if (action == MotionEvent.ACTION_HOVER_EXIT) {
// Send the exit as is.
handled |= super.dispatchHoverEvent(event); // exit
} else {
// Synthesize an exit from a move or enter.
// Ignore the result because hover focus is moving to a different view.
if (action == MotionEvent.ACTION_HOVER_MOVE) {
super.dispatchHoverEvent(event); // move
}
eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
super.dispatchHoverEvent(eventNoHistory); // exit
eventNoHistory.setAction(action);
}
mHoveredSelf = false;
}
if (newHoveredSelf) {
// Enter the view group.
if (action == MotionEvent.ACTION_HOVER_ENTER) {
// Send the enter as is.
handled |= super.dispatchHoverEvent(event); // enter
mHoveredSelf = true;
} else if (action == MotionEvent.ACTION_HOVER_MOVE) {
// Synthesize an enter from a move.
eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
handled |= super.dispatchHoverEvent(eventNoHistory); // enter
eventNoHistory.setAction(action);
handled |= super.dispatchHoverEvent(eventNoHistory); // move
mHoveredSelf = true;
}
}
}
// Recycle the copy of the event that we made.
if (eventNoHistory != event) {
eventNoHistory.recycle();
}
// Done.
return handled;
}
private void exitHoverTargets() {
if (mHoveredSelf || mFirstHoverTarget != null) {
final long now = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
dispatchHoverEvent(event);
event.recycle();
}
}
private void cancelHoverTarget(View view) {
HoverTarget predecessor = null;
HoverTarget target = mFirstHoverTarget;
while (target != null) {
final HoverTarget next = target.next;
if (target.child == view) {
if (predecessor == null) {
mFirstHoverTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
final long now = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
view.dispatchHoverEvent(event);
event.recycle();
return;
}
predecessor = target;
target = next;
}
}
@Override
boolean dispatchTooltipHoverEvent(MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_HOVER_ENTER:
break;
case MotionEvent.ACTION_HOVER_MOVE:
View newTarget = null;
// Check what the child under the pointer says about the tooltip.
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final float x = event.getX();
final float y = event.getY();
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex =
getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child =
getAndVerifyPreorderedView(preorderedList, children, childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
if (dispatchTooltipHoverEvent(event, child)) {
newTarget = child;
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
if (mTooltipHoverTarget != newTarget) {
if (mTooltipHoverTarget != null) {
event.setAction(MotionEvent.ACTION_HOVER_EXIT);
mTooltipHoverTarget.dispatchTooltipHoverEvent(event);
event.setAction(action);
}
mTooltipHoverTarget = newTarget;
}
if (mTooltipHoverTarget != null) {
if (mTooltipHoveredSelf) {
mTooltipHoveredSelf = false;
event.setAction(MotionEvent.ACTION_HOVER_EXIT);
super.dispatchTooltipHoverEvent(event);
event.setAction(action);
}
return true;
}
mTooltipHoveredSelf = super.dispatchTooltipHoverEvent(event);
return mTooltipHoveredSelf;
case MotionEvent.ACTION_HOVER_EXIT:
if (mTooltipHoverTarget != null) {
mTooltipHoverTarget.dispatchTooltipHoverEvent(event);
mTooltipHoverTarget = null;
} else if (mTooltipHoveredSelf) {
super.dispatchTooltipHoverEvent(event);
mTooltipHoveredSelf = false;
}
break;
}
return false;
}
private boolean dispatchTooltipHoverEvent(MotionEvent event, View child) {
final boolean result;
if (!child.hasIdentityMatrix()) {
MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
result = child.dispatchTooltipHoverEvent(transformedEvent);
transformedEvent.recycle();
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
result = child.dispatchTooltipHoverEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return result;
}
private void exitTooltipHoverTargets() {
if (mTooltipHoveredSelf || mTooltipHoverTarget != null) {
final long now = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
dispatchTooltipHoverEvent(event);
event.recycle();
}
}
/** @hide */
@Override
protected boolean hasHoveredChild() {
return mFirstHoverTarget != null;
}
@Override
public void addChildrenForAccessibility(ArrayList<View> outChildren) {
if (getAccessibilityNodeProvider() != null) {
return;
}
ChildListForAccessibility children = ChildListForAccessibility.obtain(this, true);
try {
final int childrenCount = children.getChildCount();
for (int i = 0; i < childrenCount; i++) {
View child = children.getChildAt(i);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
if (child.includeForAccessibility()) {
outChildren.add(child);
} else {
child.addChildrenForAccessibility(outChildren);
}
}
}
} finally {
children.recycle();
}
}
/**
* Implement this method to intercept hover events before they are handled
* by child views.
* <p>
* This method is called before dispatching a hover event to a child of
* the view group or to the view group's own {@link #onHoverEvent} to allow
* the view group a chance to intercept the hover event.
* This method can also be used to watch all pointer motions that occur within
* the bounds of the view group even when the pointer is hovering over
* a child of the view group rather than over the view group itself.
* </p><p>
* The view group can prevent its children from receiving hover events by
* implementing this method and returning <code>true</code> to indicate
* that it would like to intercept hover events. The view group must
* continuously return <code>true</code> from {@link #onInterceptHoverEvent}
* for as long as it wishes to continue intercepting hover events from
* its children.
* </p><p>
* Interception preserves the invariant that at most one view can be
* hovered at a time by transferring hover focus from the currently hovered
* child to the view group or vice-versa as needed.
* </p><p>
* If this method returns <code>true</code> and a child is already hovered, then the
* child view will first receive a hover exit event and then the view group
* itself will receive a hover enter event in {@link #onHoverEvent}.
* Likewise, if this method had previously returned <code>true</code> to intercept hover
* events and instead returns <code>false</code> while the pointer is hovering
* within the bounds of one of a child, then the view group will first receive a
* hover exit event in {@link #onHoverEvent} and then the hovered child will
* receive a hover enter event.
* </p><p>
* The default implementation handles mouse hover on the scroll bars.
* </p>
*
* @param event The motion event that describes the hover.
* @return True if the view group would like to intercept the hover event
* and prevent its children from receiving it.
*/
public boolean onInterceptHoverEvent(MotionEvent event) {
if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
final int action = event.getAction();
final float x = event.getX();
final float y = event.getY();
if ((action == MotionEvent.ACTION_HOVER_MOVE
|| action == MotionEvent.ACTION_HOVER_ENTER) && isOnScrollbar(x, y)) {
return true;
}
}
return false;
}
private static MotionEvent obtainMotionEventNoHistoryOrSelf(MotionEvent event) {
if (event.getHistorySize() == 0) {
return event;
}
return MotionEvent.obtainNoHistory(event);
}
@Override
protected boolean dispatchGenericPointerEvent(MotionEvent event) {
// Send the event to the child under the pointer.
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final float x = event.getX();
final float y = event.getY();
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
if (dispatchTransformedGenericPointerEvent(event, child)) {
if (preorderedList != null) preorderedList.clear();
return true;
}
}
if (preorderedList != null) preorderedList.clear();
}
// No child handled the event. Send it to this view group.
return super.dispatchGenericPointerEvent(event);
}
@Override
protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
// Send the event to the focused child or to this view group if it has focus.
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
return super.dispatchGenericFocusedEvent(event);
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
return mFocused.dispatchGenericMotionEvent(event);
}
return false;
}
/**
* Dispatches a generic pointer event to a child, taking into account
* transformations that apply to the child.
*
* @param event The event to send.
* @param child The view to send the event to.
* @return {@code true} if the child handled the event.
*/
private boolean dispatchTransformedGenericPointerEvent(MotionEvent event, View child) {
boolean handled;
if (!child.hasIdentityMatrix()) {
MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
handled = child.dispatchGenericMotionEvent(transformedEvent);
transformedEvent.recycle();
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchGenericMotionEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
/**
* Returns a MotionEvent that's been transformed into the child's local coordinates.
*
* It's the responsibility of the caller to recycle it once they're finished with it.
* @param event The event to transform.
* @param child The view whose coordinate space is to be used.
* @return A copy of the the given MotionEvent, transformed into the given View's coordinate
* space.
*/
private MotionEvent getTransformedMotionEvent(MotionEvent event, View child) {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
final MotionEvent transformedEvent = MotionEvent.obtain(event);
transformedEvent.offsetLocation(offsetX, offsetY);
if (!child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
return transformedEvent;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();