android.support.v7.widget.AbstractXpListPopupWindow.java Source code

Java tutorial

Introduction

Here is the source code for android.support.v7.widget.AbstractXpListPopupWindow.java

Source

/*
 * Copyright (C) 2014 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.support.v7.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.support.annotation.Size;
import android.support.v4.text.TextUtilsCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.PopupWindowCompat;
import android.util.AttributeSet;
import android.util.LayoutDirection;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.PopupWindow;

import net.xpece.android.support.widget.spinner.R;

import java.lang.reflect.Method;
import java.util.Locale;

import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;

/**
 * Static library support version of the framework's {@link android.widget.ListPopupWindow}.
 * Used to write apps that run on platforms prior to Android L. When running
 * on Android L or above, this implementation is still used; it does not try
 * to switch to the framework's implementation. See the framework SDK
 * documentation for a class overview.
 *
 * @see android.widget.ListPopupWindow
 */
public abstract class AbstractXpListPopupWindow {
    private static final String TAG = AbstractXpListPopupWindow.class.getSimpleName();
    private static final boolean DEBUG = false;

    private static final boolean API_18 = Build.VERSION.SDK_INT >= 18;

    /**
     * This value controls the length of time that the user
     * must leave a pointer down without scrolling to expand
     * the autocomplete dropdown list to cover the IME.
     */
    private static final int EXPAND_LIST_TIMEOUT = 250;

    private static Method sClipToWindowEnabledMethod;
    private static Method sGetMaxAvailableHeightMethod;
    private static Method sSetAllowScrollingAnchorParentMethod;
    private static Method sSetEpicenterBoundsMethod;

    static {
        try {
            sClipToWindowEnabledMethod = PopupWindow.class.getDeclaredMethod("setClipToScreenEnabled",
                    boolean.class);
            sClipToWindowEnabledMethod.setAccessible(true);
        } catch (NoSuchMethodException e) {
            Log.i(TAG, "Could not find method setClipToScreenEnabled() on PopupWindow. Oh well.");
        }
        try {
            sGetMaxAvailableHeightMethod = PopupWindow.class.getDeclaredMethod("getMaxAvailableHeight", View.class,
                    int.class, boolean.class);
            sGetMaxAvailableHeightMethod.setAccessible(true);
        } catch (NoSuchMethodException e) {
            Log.i(TAG, "Could not find method getMaxAvailableHeight(View, int, boolean)"
                    + " on PopupWindow. Oh well.");
        }
        try {
            sSetAllowScrollingAnchorParentMethod = PopupWindow.class
                    .getDeclaredMethod("setAllowScrollingAnchorParent", boolean.class);
            sSetAllowScrollingAnchorParentMethod.setAccessible(true);
        } catch (NoSuchMethodException e) {
            Log.i(TAG,
                    "Could not find method setAllowScrollingAnchorParent(boolean)" + " on PopupWindow. Oh well.");
        }
        try {
            sSetEpicenterBoundsMethod = PopupWindow.class.getDeclaredMethod("setEpicenterBounds", Rect.class);
        } catch (NoSuchMethodException e) {
            Log.i(TAG, "Could not find method setEpicenterBounds(Rect) on PopupWindow. Oh well.");
        }
    }

    private Context mContext;
    PopupWindow mPopup;
    private ListAdapter mAdapter;
    XpDropDownListView mDropDownList;

    private int mDropDownMaxWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
    private float mDropDownPreferredWidthUnit = 0;

    private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
    private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
    private int mDropDownHorizontalOffset;
    private int mDropDownVerticalOffset;
    private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
    private boolean mDropDownVerticalOffsetSet;

    private int mDropDownGravity = Gravity.NO_GRAVITY;

    private boolean mDropDownAlwaysVisible = false;
    private boolean mForceIgnoreOutsideTouch = false;
    int mListItemExpandMaximum = Integer.MAX_VALUE;

    private View mPromptView;
    private int mPromptPosition = POSITION_PROMPT_ABOVE;

    private DataSetObserver mObserver;

    private View mDropDownAnchorView;
    private View mDropDownBoundsView;
    private final Rect mMargins = new Rect();

    private Drawable mDropDownListHighlight;

    private AdapterView.OnItemClickListener mItemClickListener;
    private AdapterView.OnItemSelectedListener mItemSelectedListener;

    final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
    private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
    private final PopupScrollListener mScrollListener = new PopupScrollListener();
    private final ListSelectorHider mHideSelector = new ListSelectorHider();
    private Runnable mShowDropDownRunnable;

    final Handler mHandler;

    private final Rect mTempRect = new Rect();
    private final int[] mTempLocation = new int[2];

    /**
     * Optional anchor-relative bounds to be used as the transition epicenter.
     * When {@code null}, the anchor bounds are used as the epicenter.
     */
    private Rect mEpicenterBounds;

    private boolean mModal;

    private int mLayoutDirection;

    /**
     * The provided prompt view should appear above list content.
     *
     * @see #setPromptPosition(int)
     * @see #getPromptPosition()
     * @see #setPromptView(View)
     */
    public static final int POSITION_PROMPT_ABOVE = 0;

    /**
     * The provided prompt view should appear below list content.
     *
     * @see #setPromptPosition(int)
     * @see #getPromptPosition()
     * @see #setPromptView(View)
     */
    public static final int POSITION_PROMPT_BELOW = 1;

    /**
     * Alias for {@link ViewGroup.LayoutParams#MATCH_PARENT}.
     * If used to specify a popup width, the popup will match the width of the anchor view.
     * If used to specify a popup height, the popup will fill available space.
     */
    public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;

    /**
     * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
     * If used to specify a popup width, the popup will use the width of its content.
     */
    public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;

    public static final int PREFERRED = -3;

    /**
     * Mode for {@link #setInputMethodMode(int)}: the requirements for the
     * input method should be based on the focusability of the popup.  That is
     * if it is focusable than it needs to work with the input method, else
     * it doesn't.
     */
    public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE;

    /**
     * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
     * work with an input method, regardless of whether it is focusable.  This
     * means that it will always be displayed so that the user can also operate
     * the input method while it is shown.
     */
    public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED;

    /**
     * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
     * work with an input method, regardless of whether it is focusable.  This
     * means that it will always be displayed to use as much space on the
     * screen as needed, regardless of whether this covers the input method.
     */
    public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED;

    public void setMarginTop(int px) {
        mMargins.top = px;
    }

    public int getMarginTop() {
        return mMargins.top;
    }

    public void setMarginBottom(int px) {
        mMargins.bottom = px;
    }

    public int getMarginBottom() {
        return mMargins.bottom;
    }

    public void setMarginLeft(int px) {
        mMargins.left = px;
    }

    public void setMarginStart(int px) {
        if (mLayoutDirection == LayoutDirection.RTL) {
            mMargins.right = px;
        } else {
            mMargins.left = px;
        }
    }

    public int getMarginStart(int px) {
        if (mLayoutDirection == LayoutDirection.RTL) {
            return mMargins.right;
        } else {
            return mMargins.left;
        }
    }

    public int getMarginLeft() {
        return mMargins.left;
    }

    public void setMarginRight(int px) {
        mMargins.right = px;
    }

    public void setMarginEnd(int px) {
        if (mLayoutDirection == LayoutDirection.RTL) {
            mMargins.left = px;
        } else {
            mMargins.right = px;
        }
    }

    public int getMarginEnd(int px) {
        if (mLayoutDirection == LayoutDirection.RTL) {
            return mMargins.left;
        } else {
            return mMargins.right;
        }
    }

    public int getMarginRight() {
        return mMargins.right;
    }

    public void setMargin(int margin) {
        mMargins.set(margin, margin, margin, margin);
    }

    public void setMargin(int horizontal, int vertical) {
        mMargins.set(horizontal, vertical, horizontal, vertical);
    }

    public void setMargin(int left, int top, int right, int bottom) {
        mMargins.set(left, top, right, bottom);
    }

    public void setMarginRelative(int start, int top, int end, int bottom) {
        int left, right;
        if (mLayoutDirection == LayoutDirection.RTL) {
            right = start;
            left = end;
        } else {
            left = start;
            right = end;
        }
        mMargins.set(left, top, right, bottom);
    }

    /**
     * @return
     */
    public boolean hasMultiLineItems() {
        if (mDropDownList == null) {
            buildDropDown();
        }
        final XpDropDownListView list = mDropDownList;
        if (list != null) {
            return list.hasMultiLineItems();
        }
        return false;
    }

    int measureItemsUpTo(int position) {
        if (mDropDownList == null) {
            buildDropDown();
        }
        final XpDropDownListView list = mDropDownList;
        if (list != null) {
            int widthSpec = MeasureSpec.makeMeasureSpec(getListWidthSpec(), MeasureSpec.AT_MOST);
            return list.measureHeightOfChildrenCompat(widthSpec, 0, position, Integer.MAX_VALUE, 1);
        }
        return 0;
    }

    int measureItems(int fromIncl, int toExcl) {
        if (mDropDownList == null) {
            buildDropDown();
        }
        final XpDropDownListView list = mDropDownList;
        if (list != null) {
            int widthSpec = MeasureSpec.makeMeasureSpec(getListWidthSpec(), MeasureSpec.AT_MOST);
            return list.measureHeightOfChildrenCompat(widthSpec, fromIncl, toExcl, Integer.MAX_VALUE, 1);
        }
        return 0;
    }

    int measureItem(int position) {
        if (mDropDownList == null) {
            buildDropDown();
        }
        final XpDropDownListView list = mDropDownList;
        if (list != null) {
            int widthSpec = MeasureSpec.makeMeasureSpec(getListWidthSpec(), MeasureSpec.AT_MOST);
            return list.measureHeightOfChildrenCompat(widthSpec, position, position + 1, Integer.MAX_VALUE, 1);
        }
        return 0;
    }

    /**
     * Create a new, empty popup window capable of displaying items from a ListAdapter.
     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
     *
     * @param context Context used for contained views.
     */
    public AbstractXpListPopupWindow(Context context) {
        this(context, null, R.attr.listPopupWindowStyle);
    }

    /**
     * Create a new, empty popup window capable of displaying items from a ListAdapter.
     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
     *
     * @param context Context used for contained views.
     * @param attrs Attributes from inflating parent views used to style the popup.
     */
    public AbstractXpListPopupWindow(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.listPopupWindowStyle);
    }

    /**
     * Create a new, empty popup window capable of displaying items from a ListAdapter.
     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
     *
     * @param context Context used for contained views.
     * @param attrs Attributes from inflating parent views used to style the popup.
     * @param defStyleAttr Default style attribute to use for popup content.
     */
    public AbstractXpListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    /**
     * Create a new, empty popup window capable of displaying items from a ListAdapter.
     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
     *
     * @param context Context used for contained views.
     * @param attrs Attributes from inflating parent views used to style the popup.
     * @param defStyleAttr Style attribute to read for default styling of popup content.
     * @param defStyleRes Style resource ID to use for default styling of popup content.
     */
    public AbstractXpListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        mContext = context;
        mHandler = new Handler(context.getMainLooper());

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow, defStyleAttr,
                defStyleRes);
        mDropDownHorizontalOffset = a
                .getDimensionPixelOffset(R.styleable.ListPopupWindow_android_dropDownHorizontalOffset, 0);
        mDropDownVerticalOffset = a
                .getDimensionPixelOffset(R.styleable.ListPopupWindow_android_dropDownVerticalOffset, 0);
        if (mDropDownVerticalOffset != 0) {
            mDropDownVerticalOffsetSet = true;
        }
        a.recycle();

        int defaultMargin = Util.dpToPxOffset(context, 8);
        final TypedArray b = context.obtainStyledAttributes(attrs, R.styleable.XpListPopupWindow, defStyleAttr,
                defStyleRes);
        if (b.hasValue(R.styleable.XpListPopupWindow_android_layout_margin)) {
            int margin = b.getDimensionPixelOffset(R.styleable.XpListPopupWindow_android_layout_margin,
                    defaultMargin);
            mMargins.bottom = margin;
            mMargins.top = margin;
            mMargins.left = margin;
            mMargins.right = margin;
        } else {
            if (API_18 && b.hasValue(R.styleable.XpListPopupWindow_android_layout_marginEnd)) {
                int margin = b.getDimensionPixelOffset(R.styleable.XpListPopupWindow_android_layout_marginEnd, 0);
                if (mLayoutDirection == LayoutDirection.RTL) {
                    mMargins.left = margin;
                } else {
                    mMargins.right = margin;
                }
            } else {
                mMargins.right = b.getDimensionPixelOffset(R.styleable.XpListPopupWindow_android_layout_marginRight,
                        defaultMargin);
            }
            if (API_18 && b.hasValue(R.styleable.XpListPopupWindow_android_layout_marginStart)) {
                int margin = b.getDimensionPixelOffset(R.styleable.XpListPopupWindow_android_layout_marginStart, 0);
                if (mLayoutDirection == LayoutDirection.RTL) {
                    mMargins.right = margin;
                } else {
                    mMargins.left = margin;
                }
            } else {
                mMargins.left = b.getDimensionPixelOffset(R.styleable.XpListPopupWindow_android_layout_marginLeft,
                        defaultMargin);
            }
            mMargins.top = b.getDimensionPixelOffset(R.styleable.XpListPopupWindow_android_layout_marginTop,
                    defaultMargin);
            mMargins.bottom = b.getDimensionPixelOffset(R.styleable.XpListPopupWindow_android_layout_marginBottom,
                    defaultMargin);
        }
        b.recycle();

        mPopup = new AppCompatPopupWindow(context, attrs, defStyleAttr);
        mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);

        // Set the default layout direction to match the default locale one
        final Locale locale = mContext.getResources().getConfiguration().locale;
        mLayoutDirection = TextUtilsCompat.getLayoutDirectionFromLocale(locale);

        setAllowScrollingAnchorParent(false);
    }

    /**
     * Sets the adapter that provides the data and the views to represent the data
     * in this popup window.
     *
     * @param adapter The adapter to use to create this window's content.
     */
    public void setAdapter(ListAdapter adapter) {
        if (mObserver == null) {
            mObserver = new PopupDataSetObserver();
        } else if (mAdapter != null) {
            mAdapter.unregisterDataSetObserver(mObserver);
        }
        mAdapter = adapter;
        if (mAdapter != null) {
            adapter.registerDataSetObserver(mObserver);
        }

        if (mDropDownList != null) {
            mDropDownList.setAdapter(mAdapter);
        }
    }

    /**
     * Set where the optional prompt view should appear. The default is
     * {@link #POSITION_PROMPT_ABOVE}.
     *
     * @param position A position constant declaring where the prompt should be displayed.
     * @see #POSITION_PROMPT_ABOVE
     * @see #POSITION_PROMPT_BELOW
     */
    public void setPromptPosition(int position) {
        mPromptPosition = position;
    }

    /**
     * @return Where the optional prompt view should appear.
     * @see #POSITION_PROMPT_ABOVE
     * @see #POSITION_PROMPT_BELOW
     */
    public int getPromptPosition() {
        return mPromptPosition;
    }

    /**
     * Set whether this window should be modal when shown.
     * <p/>
     * <p>If a popup window is modal, it will receive all touch and key input.
     * If the user touches outside the popup window's content area the popup window
     * will be dismissed.
     *
     * @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
     */
    public void setModal(boolean modal) {
        mModal = modal;
        mPopup.setFocusable(modal);
    }

    /**
     * Returns whether the popup window will be modal when shown.
     *
     * @return {@code true} if the popup window will be modal, {@code false} otherwise.
     */
    public boolean isModal() {
        return mModal;
    }

    /**
     * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
     * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we
     * ignore outside touch even when the drop down is not set to always visible.
     *
     * @hide Used only by AutoCompleteTextView to handle some internal special cases.
     */
    public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
        mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
    }

    /**
     * Sets whether the drop-down should remain visible under certain conditions.
     * <p/>
     * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless
     * of the size or content of the list.  {@link #getBackground()} will fill any space
     * that is not used by the list.
     *
     * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
     * @hide Only used by AutoCompleteTextView under special conditions.
     */
    public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
        mDropDownAlwaysVisible = dropDownAlwaysVisible;
    }

    /**
     * @return Whether the drop-down is visible under special conditions.
     * @hide Only used by AutoCompleteTextView under special conditions.
     */
    public boolean isDropDownAlwaysVisible() {
        return mDropDownAlwaysVisible;
    }

    /**
     * Sets the operating mode for the soft input area.
     *
     * @param mode The desired mode, see
     * {@link WindowManager.LayoutParams#softInputMode}
     * for the full list
     * @see WindowManager.LayoutParams#softInputMode
     * @see #getSoftInputMode()
     */
    public void setSoftInputMode(int mode) {
        mPopup.setSoftInputMode(mode);
    }

    /**
     * Returns the current value in {@link #setSoftInputMode(int)}.
     *
     * @see #setSoftInputMode(int)
     * @see WindowManager.LayoutParams#softInputMode
     */
    public int getSoftInputMode() {
        return mPopup.getSoftInputMode();
    }

    /**
     * Sets a drawable to use as the list item selector.
     *
     * @param selector List selector drawable to use in the popup.
     */
    public void setListSelector(Drawable selector) {
        mDropDownListHighlight = selector;
    }

    /**
     * @return The background drawable for the popup window.
     */
    public Drawable getBackground() {
        return mPopup.getBackground();
    }

    /**
     * Sets a drawable to be the background for the popup window.
     *
     * @param d A drawable to set as the background.
     */
    public void setBackgroundDrawable(Drawable d) {
        mPopup.setBackgroundDrawable(d);
    }

    /**
     * Set an animation style to use when the popup window is shown or dismissed.
     *
     * @param animationStyle Animation style to use.
     */
    public void setAnimationStyle(int animationStyle) {
        mPopup.setAnimationStyle(animationStyle);
    }

    /**
     * Returns the animation style that will be used when the popup window is shown or dismissed.
     *
     * @return Animation style that will be used.
     */
    public int getAnimationStyle() {
        return mPopup.getAnimationStyle();
    }

    /**
     * Returns the view that will be used to anchor this popup.
     *
     * @return The popup's anchor view
     */
    public View getAnchorView() {
        return mDropDownAnchorView;
    }

    /**
     * Sets the popup's anchor view. This popup will always be positioned relative to the anchor
     * view when shown.
     *
     * @param anchor The view to use as an anchor.
     */
    public void setAnchorView(View anchor) {
        mDropDownAnchorView = anchor;
    }

    public View getBoundsView() {
        return mDropDownBoundsView;
    }

    public void setBoundsView(View bounds) {
        mDropDownBoundsView = bounds;
    }

    /**
     * @return The horizontal offset of the popup from its anchor in pixels.
     */
    public int getHorizontalOffset() {
        return mDropDownHorizontalOffset;
    }

    /**
     * Set the horizontal offset of this popup from its anchor view in pixels.
     *
     * @param offset The horizontal offset of the popup from its anchor.
     */
    public void setHorizontalOffset(int offset) {
        mDropDownHorizontalOffset = offset;
    }

    /**
     * @return The vertical offset of the popup from its anchor in pixels.
     */
    public int getVerticalOffset() {
        if (!mDropDownVerticalOffsetSet) {
            return 0;
        }
        return mDropDownVerticalOffset;
    }

    /**
     * Set the vertical offset of this popup from its anchor view in pixels.
     *
     * @param offset The vertical offset of the popup from its anchor.
     */
    public void setVerticalOffset(int offset) {
        mDropDownVerticalOffset = offset;
        mDropDownVerticalOffsetSet = true;
    }

    /**
     * Specifies the anchor-relative bounds of the popup's transition
     * epicenter.
     *
     * @param bounds anchor-relative bounds
     */
    public void setEpicenterBounds(Rect bounds) {
        mEpicenterBounds = bounds;
    }

    public Rect getEpicenterBounds() {
        return mEpicenterBounds;
    }

    /**
     * Set the gravity of the dropdown list. This is commonly used to
     * set gravity to START or END for alignment with the anchor.
     *
     * @param gravity Gravity value to use
     */
    public void setDropDownGravity(int gravity) {
        mDropDownGravity = gravity;
    }

    public int getDropDownGravity() {
        if (mDropDownGravity == Gravity.NO_GRAVITY) {
            return Gravity.TOP | GravityCompat.START;
        }
        return mDropDownGravity;
    }

    /**
     * @return The width of the popup window in pixels.
     */
    public int getWidth() {
        return mDropDownWidth;
    }

    public int getMaxWidth() {
        return mDropDownMaxWidth;
    }

    public float getPreferredWidthUnit() {
        return mDropDownPreferredWidthUnit;
    }

    /**
     * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT}
     * or {@link #WRAP_CONTENT}.
     *
     * @param width Width of the popup window.
     */
    public void setWidth(int width) {
        mDropDownWidth = width;
    }

    public void setMaxWidth(int maxWidth) {
        mDropDownMaxWidth = maxWidth;
    }

    public void setPreferredWidthUnit(float unit) {
        mDropDownPreferredWidthUnit = unit;
    }

    /**
     * Sets the width of the popup window by the size of its content. The final width may be
     * larger to accommodate styled window dressing.
     *
     * @param width Desired width of content in pixels.
     */
    public void setContentWidth(int width) {
        Drawable popupBackground = mPopup.getBackground();
        if (popupBackground != null) {
            popupBackground.getPadding(mTempRect);
            mDropDownWidth = mTempRect.left + mTempRect.right + width;
        } else {
            setWidth(width);
        }
    }

    /**
     * @return The height of the popup window in pixels.
     */
    public int getHeight() {
        return mDropDownHeight;
    }

    /**
     * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}.
     *
     * @param height Height of the popup window.
     */
    public void setHeight(int height) {
        mDropDownHeight = height;
    }

    /**
     * Set the layout type for this popup window.
     * <p/>
     * See {@link WindowManager.LayoutParams#type} for possible values.
     *
     * @param layoutType Layout type for this window.
     * @see WindowManager.LayoutParams#type
     */
    public void setWindowLayoutType(int layoutType) {
        mDropDownWindowLayoutType = layoutType;
    }

    /**
     * Sets a listener to receive events when a list item is clicked.
     *
     * @param clickListener Listener to register
     * @see ListView#setOnItemClickListener(AdapterView.OnItemClickListener)
     */
    public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) {
        mItemClickListener = clickListener;
        final XpDropDownListView list = mDropDownList;
        if (list != null) {
            list.setOnItemClickListener(clickListener);
        }
    }

    /**
     * Sets a listener to receive events when a list item is selected.
     *
     * @param selectedListener Listener to register.
     * @see ListView#setOnItemSelectedListener(AdapterView.OnItemSelectedListener)
     */
    public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener selectedListener) {
        mItemSelectedListener = selectedListener;
        final XpDropDownListView list = mDropDownList;
        if (list != null) {
            list.setOnItemSelectedListener(selectedListener);
        }
    }

    /**
     * Set a view to act as a user prompt for this popup window. Where the prompt view will appear
     * is controlled by {@link #setPromptPosition(int)}.
     *
     * @param prompt View to use as an informational prompt.
     */
    public void setPromptView(View prompt) {
        boolean showing = isShowing();
        if (showing) {
            removePromptView();
        }
        mPromptView = prompt;
        if (showing) {
            show();
        }
    }

    /**
     * Post a {@link #show()} call to the UI thread.
     */
    public void postShow() {
        mHandler.post(mShowDropDownRunnable);
    }

    /**
     * Show the popup list. If the list is already showing, this method
     * will recalculate the popup's size and position.
     */
    public void show() {
        final int height = buildDropDown();
        final int widthSpec = getListWidthSpec();

        boolean noInputMethod = isInputMethodNotNeeded();
        PopupWindowCompat.setWindowLayoutType(mPopup, mDropDownWindowLayoutType);

        final int marginsLeft = mMargins.left;
        final int marginsTop = mMargins.top;
        final int marginsBottom = mMargins.bottom;
        final int marginsRight = mMargins.right;

        getBackgroundPadding(mTempRect);
        final int backgroundLeft = mTempRect.left;
        final int backgroundTop = mTempRect.top;
        final int backgroundBottom = mTempRect.bottom;
        final int backgroundRight = mTempRect.right;

        int verticalOffset = mDropDownVerticalOffset;
        int horizontalOffset = mDropDownHorizontalOffset;

        final int anchorWidth = mDropDownAnchorView.getWidth();
        final int anchorHeight = mDropDownAnchorView.getHeight();

        getLocationInWindow(mDropDownAnchorView, mTempLocation);
        final int anchorLeft = mTempLocation[0];
        final int anchorRight = anchorLeft + anchorWidth;
        final int anchorTop = mTempLocation[1];
        final int anchorBottom = anchorTop + anchorHeight;

        final boolean rightAligned = GravityCompat.getAbsoluteGravity(
                getDropDownGravity() & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK,
                mLayoutDirection) == Gravity.RIGHT;
        if (rightAligned) {
            horizontalOffset += anchorWidth - widthSpec - (marginsRight - backgroundRight);
        } else {
            horizontalOffset += (marginsLeft - backgroundLeft);
        }

        final int bottomDecorations = getWindowFrame(mDropDownAnchorView, noInputMethod, mTempRect);
        final int windowLeft = mTempRect.left;
        final int windowRight = mTempRect.right;
        final int windowTop = mTempRect.top;
        final int windowBottom = mTempRect.bottom;

        final int windowWidth = windowRight - windowLeft;
        final int windowHeight = windowBottom - windowTop;

        getBoundsInWindow(mTempRect);
        final int boundsTop = mTempRect.top;
        final int boundsRight = mTempRect.right;
        final int boundsLeft = mTempRect.left;
        final int boundsBottom = mTempRect.bottom;

        final int screenRight = windowRight - (marginsRight - backgroundRight) - boundsRight;
        final int screenLeft = windowLeft + (marginsLeft - backgroundLeft) + boundsLeft;
        final int screenWidth = screenRight - screenLeft;

        if (!rightAligned && windowWidth < anchorLeft + horizontalOffset + widthSpec) {
            // When right aligned due to insufficient space ignore negative horizontal offset.
            horizontalOffset = mDropDownHorizontalOffset < 0 ? 0 : mDropDownHorizontalOffset;
            horizontalOffset -= widthSpec - (windowWidth - anchorLeft);
            horizontalOffset -= marginsRight - backgroundRight;
        } else if (rightAligned && 0 > anchorLeft + horizontalOffset) {
            // When left aligned due to insufficient space ignore positive horizontal offset.
            horizontalOffset = mDropDownHorizontalOffset > 0 ? 0 : mDropDownHorizontalOffset;
            horizontalOffset -= anchorLeft;
            horizontalOffset += marginsLeft - backgroundLeft;
        }

        // Width spec should always be resolved to concrete value. widthSpec > 0;
        if (windowWidth < widthSpec + horizontalOffset + anchorLeft) {
            int diff = Math.abs(windowWidth - (widthSpec + horizontalOffset + anchorLeft));
            horizontalOffset -= diff;
        } else if (0 > anchorLeft + horizontalOffset) {
            int diff = Math.abs(horizontalOffset + anchorLeft);
            horizontalOffset += diff;
        }

        int maxHeight = getMaxAvailableHeight(mDropDownAnchorView, noInputMethod) + backgroundTop
                + backgroundBottom;
        int availableHeight = maxHeight;
        //        availableHeight -= Math.max(0, marginsTop - backgroundTop);
        //        availableHeight -= Math.max(0, marginsBottom - backgroundBottom);
        availableHeight -= marginsTop - backgroundTop;
        availableHeight -= marginsBottom - backgroundBottom;

        int limitHeight = Math.min(windowHeight, availableHeight);

        final int heightSpec;
        if (mPopup.isShowing()) {
            if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
                // The call to PopupWindow's update method below can accept -1 for any
                // value you do not want to update.
                //                heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
                //                if (noInputMethod) {
                //                    mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
                //                        ViewGroup.LayoutParams.MATCH_PARENT : 0);
                //                    mPopup.setHeight(0);
                //                } else {
                //                    mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
                //                        ViewGroup.LayoutParams.MATCH_PARENT : 0);
                //                    mPopup.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
                //                }
                heightSpec = limitHeight;
            } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
                heightSpec = Math.min(height, limitHeight);
            } else {
                heightSpec = Math.min(mDropDownHeight, limitHeight);
            }
        } else {
            if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
                //                heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
                heightSpec = limitHeight;
            } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
                heightSpec = Math.min(height, limitHeight);
            } else {
                heightSpec = Math.min(mDropDownHeight, limitHeight);
            }
        }

        final int screenBottom = windowBottom - (marginsBottom - backgroundBottom) - boundsBottom;
        final int screenTop = windowTop + (marginsTop - backgroundTop) + boundsTop;

        {
            // Position within bounds.

            final int popupTop = anchorBottom + verticalOffset;
            final int popupBottom = popupTop + heightSpec;
            final int popupHeight = popupBottom - popupTop;

            if (popupBottom > screenBottom) {
                verticalOffset -= (popupBottom - screenBottom);
            } else if (popupTop < screenTop) {
                verticalOffset += (screenTop - popupTop);
            }
        }

        {
            // Account for background padding.

            final int popupTop = anchorBottom + verticalOffset;
            final int popupBottom = popupTop + heightSpec;
            final int popupHeight = popupBottom - popupTop;

            if (windowBottom < popupBottom) {
                int diff = Math.abs(windowBottom - popupBottom);
                verticalOffset -= diff;
            } else if (windowTop > popupTop) {
                int diff = Math.abs(windowTop - popupTop);
                verticalOffset += diff;
            }
        }

        //        verticalOffset -= bottomDecorations;
        //        verticalOffset += Util.dpToPxOffset(mContext, 8);

        if (mPopup.isShowing()) {
            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);

            mPopup.update(getAnchorView(), horizontalOffset, verticalOffset, (widthSpec < 0) ? -1 : widthSpec,
                    (heightSpec < 0) ? -1 : heightSpec);
        } else {

            mPopup.setWidth(widthSpec);
            mPopup.setHeight(heightSpec);
            setPopupClipToScreenEnabled(true);

            // use outside touchable to dismiss drop down when touching outside of it, so
            // only set this if the dropdown is not always visible
            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
            mPopup.setTouchInterceptor(mTouchInterceptor);
            if (sSetEpicenterBoundsMethod != null) {
                try {
                    sSetEpicenterBoundsMethod.invoke(mPopup, mEpicenterBounds);
                } catch (Exception e) {
                    Log.e(TAG, "Could not invoke setEpicenterBounds on PopupWindow", e);
                }
            }
            // We handle gravity manually. Just as everything else.
            PopupWindowCompat.showAsDropDown(mPopup, getAnchorView(), horizontalOffset, verticalOffset,
                    Gravity.NO_GRAVITY);
            mDropDownList.setSelection(ListView.INVALID_POSITION);

            if (DEBUG)
                Log.e(TAG, "isAboveAnchor=" + mPopup.isAboveAnchor());

            if (!mModal || mDropDownList.isInTouchMode()) {
                clearListSelection();
            }
            if (!mModal) {
                mHandler.post(mHideSelector);
            }
        }
    }

    private int getListWidthSpec() {
        final int displayWidth = mContext.getResources().getDisplayMetrics().widthPixels;

        final int margins = mMargins.left + mMargins.right;
        final int paddings = getBackgroundHorizontalPadding();
        final int mps = margins - paddings;

        final int widthSpec;
        if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
            // The call to PopupWindow's update method below can accept -1 for any
            // value you do not want to update.
            if (mDropDownMaxWidth == MATCH_PARENT) {
                widthSpec = displayWidth - mps;//-1;
            } else if (mDropDownMaxWidth == WRAP_CONTENT) {
                widthSpec = getAnchorView().getWidth() - mps;
            } else {
                widthSpec = mDropDownMaxWidth - mps;
            }
        } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
            if (mDropDownMaxWidth < 0) {
                widthSpec = getAnchorView().getWidth() - mps;
            } else {
                widthSpec = mDropDownMaxWidth - mps;
            }
        } else if (mDropDownWidth == PREFERRED) {
            int preferredWidth = mDropDownList.compatMeasureContentWidth() + getBackgroundHorizontalPadding();
            if (mDropDownPreferredWidthUnit > 0) {
                int units = (int) Math.ceil(preferredWidth / mDropDownPreferredWidthUnit);
                preferredWidth = (int) (units * mDropDownPreferredWidthUnit);
            }
            if (mDropDownMaxWidth < 0) {
                int anchorWidthTemp = getAnchorView().getWidth() - mps;
                if (preferredWidth > anchorWidthTemp) {
                    if (mDropDownMaxWidth == MATCH_PARENT) {
                        widthSpec = Math.min(preferredWidth, displayWidth - mps);//-1;
                    } else { // WRAP_CONTENT
                        widthSpec = anchorWidthTemp;
                    }
                } else {
                    widthSpec = preferredWidth;
                }
            } else {
                if (preferredWidth > mDropDownMaxWidth - mps) {
                    widthSpec = mDropDownMaxWidth - mps;
                } else {
                    widthSpec = preferredWidth;
                }
            }
        } else {
            if (mDropDownMaxWidth < 0) {
                int anchorWidthTemp = getAnchorView().getWidth() - mps;
                if (mDropDownMaxWidth == WRAP_CONTENT && mDropDownWidth > anchorWidthTemp) {
                    widthSpec = anchorWidthTemp;
                } else {
                    widthSpec = mDropDownWidth;
                }
            } else {
                if (mDropDownWidth > mDropDownMaxWidth - mps) {
                    widthSpec = mDropDownMaxWidth - mps;
                } else {
                    widthSpec = mDropDownWidth;
                }
            }
        }

        return widthSpec;
    }

    private int getBackgroundHorizontalPadding() {
        Drawable background = mPopup.getBackground();
        if (background != null) {
            background.getPadding(mTempRect);
            return mTempRect.left + mTempRect.right;
        }
        return 0;
    }

    private void getBackgroundPadding(Rect out) {
        Drawable background = mPopup.getBackground();
        if (background != null) {
            background.getPadding(out);
        } else {
            out.set(0, 0, 0, 0);
        }
    }

    @Deprecated
    private int getBackgroundLeftPadding() {
        Drawable background = mPopup.getBackground();
        if (background != null) {
            background.getPadding(mTempRect);
            return mTempRect.left;
        }
        return 0;
    }

    @Deprecated
    private int getBackgroundRightPadding() {
        Drawable background = mPopup.getBackground();
        if (background != null) {
            background.getPadding(mTempRect);
            return mTempRect.right;
        }
        return 0;
    }

    @Deprecated
    private int getBackgroundBottomPadding() {
        Drawable background = mPopup.getBackground();
        if (background != null) {
            background.getPadding(mTempRect);
            return mTempRect.bottom;
        }
        return 0;
    }

    @Deprecated
    private int getBackgroundTopPadding() {
        Drawable background = mPopup.getBackground();
        if (background != null) {
            background.getPadding(mTempRect);
            return mTempRect.top;
        }
        return 0;
    }

    private int getBackgroundVerticalPadding() {
        Drawable background = mPopup.getBackground();
        if (background != null) {
            background.getPadding(mTempRect);
            return mTempRect.top + mTempRect.bottom;
        }
        return 0;
    }

    /**
     * Dismiss the popup window.
     */
    public void dismiss() {
        mPopup.dismiss();
        removePromptView();
        mPopup.setContentView(null);
        mDropDownList = null;
        mHandler.removeCallbacks(mResizePopupRunnable);
    }

    /**
     * Set a listener to receive a callback when the popup is dismissed.
     *
     * @param listener Listener that will be notified when the popup is dismissed.
     */
    public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
        mPopup.setOnDismissListener(listener);
    }

    private void removePromptView() {
        if (mPromptView != null) {
            final ViewParent parent = mPromptView.getParent();
            if (parent instanceof ViewGroup) {
                final ViewGroup group = (ViewGroup) parent;
                group.removeView(mPromptView);
            }
        }
    }

    /**
     * Control how the popup operates with an input method: one of
     * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
     * or {@link #INPUT_METHOD_NOT_NEEDED}.
     * <p/>
     * <p>If the popup is showing, calling this method will take effect only
     * the next time the popup is shown or through a manual call to the {@link #show()}
     * method.</p>
     *
     * @see #getInputMethodMode()
     * @see #show()
     */
    public void setInputMethodMode(int mode) {
        mPopup.setInputMethodMode(mode);
    }

    /**
     * Return the current value in {@link #setInputMethodMode(int)}.
     *
     * @see #setInputMethodMode(int)
     */
    public int getInputMethodMode() {
        return mPopup.getInputMethodMode();
    }

    /**
     * Set the selected position of the list.
     * Only valid when {@link #isShowing()} == {@code true}.
     *
     * @param position List position to set as selected.
     */
    public void setSelection(int position) {
        XpDropDownListView list = mDropDownList;
        if (isShowing() && list != null) {
            list.setListSelectionHidden(false);
            list.setSelection(position);

            if (position >= 0 && position != list.getCount() - 1) {
                if (Build.VERSION.SDK_INT >= 14 && list.canScrollVertically(-1)) {
                    list.scrollBy(0, -list.getPaddingTop());
                }
            }

        }
        setItemChecked(position);
    }

    private void setItemChecked(final int position) {
        final XpDropDownListView list = mDropDownList;
        if (list != null) {
            list.setItemChecked(position, true);
        }
    }

    public void setSelectionInitial(int position) {
        if (position > 0) {
            setSelection(position);
        } else {
            setItemChecked(position);
        }
    }

    public int getPreferredVerticalOffset(int position) {
        buildDropDown();

        final View anchor = getAnchorView();
        final AbstractXpListPopupWindow popup = this;

        final Context context = anchor.getContext();

        // Shadow is emulated below Lollipop, we have to account for that.
        final int backgroundPaddingTop = getBackgroundTopPadding();

        // Center selected item over anchor view.
        if (position < 0)
            position = 0;
        final int viewHeight = anchor.getHeight();
        final int dropDownListViewPaddingTop = mDropDownList.getPaddingTop();
        final int selectedItemHeight = popup.measureItem(position);
        final int beforeSelectedItemHeight = popup.measureItemsUpTo(position + 1);

        final int viewHeightAdjustedHalf = (viewHeight - anchor.getPaddingTop() - anchor.getPaddingBottom()) / 2
                + anchor.getPaddingBottom();

        final int offset;
        if (selectedItemHeight >= 0 && beforeSelectedItemHeight >= 0) {
            offset = -(beforeSelectedItemHeight + (viewHeightAdjustedHalf - selectedItemHeight / 2)
                    + dropDownListViewPaddingTop + backgroundPaddingTop);
        } else {
            final int height = Util.resolveDimensionPixelSize(context, R.attr.dropdownListPreferredItemHeight, 0);
            offset = -(height * (position + 1) + (viewHeightAdjustedHalf - height / 2) + dropDownListViewPaddingTop
                    + backgroundPaddingTop);
        }
        return offset;
    }

    /**
     * Clear any current list selection.
     * Only valid when {@link #isShowing()} == {@code true}.
     */
    public void clearListSelection() {
        final XpDropDownListView list = mDropDownList;
        if (list != null) {
            // WARNING: Please read the comment where mListSelectionHidden is declared
            list.setListSelectionHidden(true);
            //list.hideSelector();
            list.requestLayout();
        }
    }

    /**
     * @return {@code true} if the popup is currently showing, {@code false} otherwise.
     */
    public boolean isShowing() {
        return mPopup.isShowing();
    }

    /**
     * @return {@code true} if this popup is configured to assume the user does not need
     * to interact with the IME while it is showing, {@code false} otherwise.
     */
    public boolean isInputMethodNotNeeded() {
        return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED;
    }

    /**
     * Perform an item click operation on the specified list adapter position.
     *
     * @param position Adapter position for performing the click
     * @return true if the click action could be performed, false if not.
     * (e.g. if the popup was not showing, this method would return false.)
     */
    public boolean performItemClick(int position) {
        if (isShowing()) {
            if (mItemClickListener != null) {
                final XpDropDownListView list = mDropDownList;
                final View child = list.getChildAt(position - list.getFirstVisiblePosition());
                final ListAdapter adapter = list.getAdapter();
                mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position));
            }
            return true;
        }
        return false;
    }

    /**
     * @return The currently selected item or null if the popup is not showing.
     */
    public Object getSelectedItem() {
        if (!isShowing()) {
            return null;
        }
        return mDropDownList.getSelectedItem();
    }

    /**
     * @return The position of the currently selected item or {@link ListView#INVALID_POSITION}
     * if {@link #isShowing()} == {@code false}.
     * @see ListView#getSelectedItemPosition()
     */
    public int getSelectedItemPosition() {
        if (!isShowing()) {
            return ListView.INVALID_POSITION;
        }
        return mDropDownList.getSelectedItemPosition();
    }

    /**
     * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID}
     * if {@link #isShowing()} == {@code false}.
     * @see ListView#getSelectedItemId()
     */
    public long getSelectedItemId() {
        if (!isShowing()) {
            return ListView.INVALID_ROW_ID;
        }
        return mDropDownList.getSelectedItemId();
    }

    /**
     * @return The View for the currently selected item or null if
     * {@link #isShowing()} == {@code false}.
     * @see ListView#getSelectedView()
     */
    public View getSelectedView() {
        if (!isShowing()) {
            return null;
        }
        return mDropDownList.getSelectedView();
    }

    /**
     * @return The {@link ListView} displayed within the popup window.
     * Only valid when {@link #isShowing()} == {@code true}.
     */
    public XpDropDownListView getListView() {
        return mDropDownList;
    }

    /**
     * The maximum number of list items that can be visible and still have
     * the list expand when touched.
     *
     * @param max Max number of items that can be visible and still allow the list to expand.
     */
    void setListItemExpandMax(int max) {
        mListItemExpandMaximum = max;
    }

    /**
     * Filter key down events. By forwarding key down events to this function,
     * views using non-modal ListPopupWindow can have it handle key selection of items.
     *
     * @param keyCode keyCode param passed to the host view's onKeyDown
     * @param event event param passed to the host view's onKeyDown
     * @return true if the event was handled, false if it was ignored.
     * @see #setModal(boolean)
     */
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // when the drop down is shown, we drive it directly
        if (isShowing()) {
            // the key events are forwarded to the list in the drop down view
            // note that ListView handles space but we don't want that to happen
            // also if selection is not currently in the drop down, then don't
            // let center or enter presses go there since that would cause it
            // to select one of its items
            if (keyCode != KeyEvent.KEYCODE_SPACE
                    && (mDropDownList.getSelectedItemPosition() >= 0 || !isConfirmKey(keyCode))) {
                int curIndex = mDropDownList.getSelectedItemPosition();
                boolean consumed;

                final boolean below = !mPopup.isAboveAnchor();

                final ListAdapter adapter = mAdapter;

                boolean allEnabled;
                int firstItem = Integer.MAX_VALUE;
                int lastItem = Integer.MIN_VALUE;

                if (adapter != null) {
                    allEnabled = adapter.areAllItemsEnabled();
                    firstItem = allEnabled ? 0 : mDropDownList.lookForSelectablePosition(0, true);
                    lastItem = allEnabled ? adapter.getCount() - 1
                            : mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
                }

                if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem)
                        || (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
                    // When the selection is at the top, we block the key
                    // event to prevent focus from moving.
                    clearListSelection();
                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
                    show();
                    return true;
                } else {
                    // WARNING: Please read the comment where mListSelectionHidden
                    //          is declared
                    mDropDownList.setListSelectionHidden(false);
                }

                consumed = mDropDownList.onKeyDown(keyCode, event);
                if (DEBUG)
                    Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);

                if (consumed) {
                    // If it handled the key event, then the user is
                    // navigating in the list, so we should put it in front.
                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
                    // Here's a little trick we need to do to make sure that
                    // the list view is actually showing its focus indicator,
                    // by ensuring it has focus and getting its window out
                    // of touch mode.
                    mDropDownList.requestFocusFromTouch();
                    show();

                    switch (keyCode) {
                    // avoid passing the focus from the text view to the
                    // next component
                    case KeyEvent.KEYCODE_ENTER:
                    case KeyEvent.KEYCODE_DPAD_CENTER:
                    case KeyEvent.KEYCODE_DPAD_DOWN:
                    case KeyEvent.KEYCODE_DPAD_UP:
                        return true;
                    }
                } else {
                    if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                        // when the selection is at the bottom, we block the
                        // event to avoid going to the next focusable widget
                        if (curIndex == lastItem) {
                            return true;
                        }
                    } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex == firstItem) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    /**
     * Filter key down events. By forwarding key up events to this function,
     * views using non-modal ListPopupWindow can have it handle key selection of items.
     *
     * @param keyCode keyCode param passed to the host view's onKeyUp
     * @param event event param passed to the host view's onKeyUp
     * @return true if the event was handled, false if it was ignored.
     * @see #setModal(boolean)
     */
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
            boolean consumed = mDropDownList.onKeyUp(keyCode, event);
            if (consumed && isConfirmKey(keyCode)) {
                // if the list accepts the key events and the key event was a click, the text view
                // gets the selected item from the drop down as its content
                dismiss();
            }
            return consumed;
        }
        return false;
    }

    /**
     * Filter pre-IME key events. By forwarding {@link View#onKeyPreIme(int, KeyEvent)}
     * events to this function, views using ListPopupWindow can have it dismiss the popup
     * when the back key is pressed.
     *
     * @param keyCode keyCode param passed to the host view's onKeyPreIme
     * @param event event param passed to the host view's onKeyPreIme
     * @return true if the event was handled, false if it was ignored.
     * @see #setModal(boolean)
     */
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) {
            // special case for the back key, we do not even try to send it
            // to the drop down list but instead, consume it immediately
            final View anchorView = mDropDownAnchorView;
            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
                KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
                if (state != null) {
                    state.startTracking(event, this);
                }
                return true;
            } else if (event.getAction() == KeyEvent.ACTION_UP) {
                KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
                if (state != null) {
                    state.handleUpEvent(event);
                }
                if (event.isTracking() && !event.isCanceled()) {
                    dismiss();
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * <p>Builds the popup window's content and returns the height the popup
     * should have. Returns -1 when the content already exists.</p>
     *
     * @return the content's height or -1 if content already exists
     */
    private int buildDropDown() {
        ViewGroup dropDownView;
        int otherHeights = 0;

        if (mDropDownList == null) {
            Context context = mContext;

            /**
             * This Runnable exists for the sole purpose of checking if the view layout has got
             * completed and if so call showDropDown to display the drop down. This is used to show
             * the drop down as soon as possible after user opens up the search dialog, without
             * waiting for the normal UI pipeline to do it's job which is slower than this method.
             */
            mShowDropDownRunnable = new Runnable() {
                public void run() {
                    // View layout should be all done before displaying the drop down.
                    View view = getAnchorView();
                    if (view != null && view.getWindowToken() != null) {
                        show();
                    }
                }
            };

            mDropDownList = createDropDownListView(context, !mModal);
            if (mDropDownListHighlight != null) {
                mDropDownList.setSelector(mDropDownListHighlight);
            }
            mDropDownList.setAdapter(mAdapter);
            mDropDownList.setOnItemClickListener(mItemClickListener);
            mDropDownList.setFocusable(true);
            mDropDownList.setFocusableInTouchMode(true);
            mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
                public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

                    if (position != -1) {
                        XpDropDownListView dropDownList = mDropDownList;

                        if (dropDownList != null) {
                            dropDownList.setListSelectionHidden(false);
                        }
                    }
                }

                public void onNothingSelected(AdapterView<?> parent) {
                }
            });
            mDropDownList.setOnScrollListener(mScrollListener);

            if (mItemSelectedListener != null) {
                mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
            }

            dropDownView = mDropDownList;

            View hintView = mPromptView;
            if (hintView != null) {
                // if a hint has been specified, we accomodate more space for it and
                // add a text view in the drop down menu, at the bottom of the list
                LinearLayout hintContainer = new LinearLayout(context);
                hintContainer.setOrientation(LinearLayout.VERTICAL);

                LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f);

                switch (mPromptPosition) {
                case POSITION_PROMPT_BELOW:
                    hintContainer.addView(dropDownView, hintParams);
                    hintContainer.addView(hintView);
                    break;

                case POSITION_PROMPT_ABOVE:
                    hintContainer.addView(hintView);
                    hintContainer.addView(dropDownView, hintParams);
                    break;

                default:
                    Log.e(TAG, "Invalid hint position " + mPromptPosition);
                    break;
                }

                // Measure the hint's height to find how much more vertical
                // space we need to add to the drop down's height.
                final int widthSize;
                final int widthMode;
                if (mDropDownWidth >= 0) {
                    widthMode = MeasureSpec.AT_MOST;
                    widthSize = mDropDownWidth > mDropDownMaxWidth ? mDropDownMaxWidth : mDropDownWidth;
                    //                    widthSize = mDropDownWidth;
                } else {
                    if (mDropDownMaxWidth >= 0) {
                        widthMode = MeasureSpec.AT_MOST;
                        widthSize = mDropDownMaxWidth;
                    } else {
                        widthMode = MeasureSpec.UNSPECIFIED;
                        widthSize = 0;
                    }
                }
                //noinspection Range
                final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
                final int heightSpec = MeasureSpec.UNSPECIFIED;
                hintView.measure(widthSpec, heightSpec);

                hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
                otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin + hintParams.bottomMargin;

                dropDownView = hintContainer;
            }

            mPopup.setContentView(dropDownView);
        } else {
            dropDownView = (ViewGroup) mPopup.getContentView();
            final View view = mPromptView;
            if (view != null) {
                LinearLayout.LayoutParams hintParams = (LinearLayout.LayoutParams) view.getLayoutParams();
                otherHeights = view.getMeasuredHeight() + hintParams.topMargin + hintParams.bottomMargin;
            }
        }

        // getMaxAvailableHeight() subtracts the padding, so we put it back
        // to get the available height for the whole window
        int padding = 0;
        Drawable background = mPopup.getBackground();
        if (background != null) {
            background.getPadding(mTempRect);
            padding = mTempRect.top + mTempRect.bottom;

            // If we don't have an explicit vertical offset, determine one from the window
            // background so that content will line up.
            //            if (!mDropDownVerticalOffsetSet) {
            //                mDropDownVerticalOffset = -mTempRect.top;
            //            }
        } else {
            mTempRect.setEmpty();
        }

        final int verticalMargin = mMargins.top + mMargins.bottom;

        // Max height available on the screen for a popup.
        final boolean ignoreBottomDecorations = mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
        //        final int maxHeight = getMaxAvailableHeight(getAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
        final int maxHeight = getMaxAvailableHeight(getAnchorView(), ignoreBottomDecorations);
        if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
            return maxHeight - verticalMargin + padding;
        }

        final int childWidthSpec;
        switch (mDropDownWidth) {
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            childWidthSpec = MeasureSpec.makeMeasureSpec(getAnchorView().getWidth()
                    - (mMargins.left + mMargins.right) - (mTempRect.left + mTempRect.right), MeasureSpec.AT_MOST);
            break;
        case ViewGroup.LayoutParams.MATCH_PARENT:
            childWidthSpec = MeasureSpec.makeMeasureSpec(mContext.getResources().getDisplayMetrics().widthPixels
                    - (mMargins.left + mMargins.right) - (mTempRect.left + mTempRect.right), MeasureSpec.EXACTLY);
            break;
        case PREFERRED:
            int widthSize;
            int widthMode;
            if (mDropDownMaxWidth >= 0) {
                widthSize = mDropDownMaxWidth - (mMargins.left + mMargins.right)
                        - (mTempRect.left + mTempRect.right);
                widthMode = MeasureSpec.AT_MOST;
                childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
            } else {
                widthMode = MeasureSpec.AT_MOST;
                if (mDropDownMaxWidth == WRAP_CONTENT) {
                    widthSize = getAnchorView().getWidth() - (mMargins.left + mMargins.right)
                            - (mTempRect.left + mTempRect.right);
                } else { // MATCH_PARENT
                    widthSize = mContext.getResources().getDisplayMetrics().widthPixels
                            - (mMargins.left + mMargins.right) - (mTempRect.left + mTempRect.right);
                }
                childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
            }
            break;
        default:
            //noinspection Range
            childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY);
            break;
        }

        final int listPadding = mDropDownList.getPaddingTop() + mDropDownList.getPaddingBottom();
        final int listContent = mDropDownList.measureHeightOfChildrenCompat(childWidthSpec, 0,
                XpDropDownListView.NO_POSITION, maxHeight - otherHeights - verticalMargin - listPadding + padding,
                -1);
        // add padding only if the list has items in it, that way we don't show
        // the popup if it is not needed
        if (otherHeights > 0 || listContent > 0)
            otherHeights += padding + listPadding;

        return listContent + otherHeights;
    }

    @NonNull
    XpDropDownListView createDropDownListView(final Context context, final boolean hijackFocus) {
        final XpDropDownListView listView = new XpDropDownListView(context, hijackFocus);
        listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
        return listView;
    }

    private class PopupDataSetObserver extends DataSetObserver {
        @Override
        public void onChanged() {
            if (isShowing()) {
                // Resize the popup to fit new content
                show();
            }
        }

        @Override
        public void onInvalidated() {
            dismiss();
        }
    }

    private class ListSelectorHider implements Runnable {
        public void run() {
            clearListSelection();
        }
    }

    private class ResizePopupRunnable implements Runnable {
        public void run() {
            if (mDropDownList != null && ViewCompat.isAttachedToWindow(mDropDownList)
                    && mDropDownList.getCount() > mDropDownList.getChildCount()
                    && mDropDownList.getChildCount() <= mListItemExpandMaximum) {
                mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
                show();
            }
        }
    }

    private class PopupTouchInterceptor implements OnTouchListener {
        public boolean onTouch(View v, MotionEvent event) {
            final int action = event.getAction();
            final int x = (int) event.getX();
            final int y = (int) event.getY();

            if (action == MotionEvent.ACTION_DOWN && mPopup != null && mPopup.isShowing()
                    && (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) {
                mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
            } else if (action == MotionEvent.ACTION_UP) {
                mHandler.removeCallbacks(mResizePopupRunnable);
            }
            return false;
        }
    }

    private class PopupScrollListener implements ListView.OnScrollListener {
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

        }

        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if (scrollState == SCROLL_STATE_TOUCH_SCROLL && !isInputMethodNotNeeded()
                    && mPopup.getContentView() != null) {
                mHandler.removeCallbacks(mResizePopupRunnable);
                mResizePopupRunnable.run();
            }
        }
    }

    private static boolean isConfirmKey(int keyCode) {
        return keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_CENTER;
    }

    private void setPopupClipToScreenEnabled(boolean clip) {
        if (sClipToWindowEnabledMethod != null) {
            try {
                sClipToWindowEnabledMethod.invoke(mPopup, clip);
            } catch (Exception e) {
                Log.i(TAG, "Could not call setClipToScreenEnabled() on PopupWindow. Oh well.");
            }
        }
    }

    private int getMaxAvailableHeight(View anchor, boolean ignoreBottomDecorations) {
        final View bounds = mDropDownBoundsView;
        if (bounds != null) {
            int returnedHeight = bounds.getHeight();
            returnedHeight -= getBackgroundVerticalPadding();
            return returnedHeight;
        }

        getWindowFrame(anchor, ignoreBottomDecorations, mTempRect);
        int returnedHeight = mTempRect.height();
        returnedHeight -= getBackgroundVerticalPadding();

        // 1 dp extra as part of 25 dp status bar. Prevents 1 dp scrolling when landscape 360dp.
        if (Build.VERSION.SDK_INT < 23)
            returnedHeight += Util.dpToPxSize(mContext, 1);

        return returnedHeight;
    }

    private void setAllowScrollingAnchorParent(boolean enabled) {
        if (sSetAllowScrollingAnchorParentMethod != null) {
            try {
                sSetAllowScrollingAnchorParentMethod.invoke(mPopup, enabled);
            } catch (Exception e) {
                Log.i(TAG, "Could not call setAllowScrollingAnchorParent() on PopupWindow. Oh well.");
            }
        }
    }

    private int getWindowFrame(final View anchor, final boolean ignoreBottomDecorations, final Rect out) {
        int bottomDecorations = 0;
        anchor.getWindowVisibleDisplayFrame(out);
        //        if (ignoreBottomDecorations) {
        //            Resources res = anchor.getContext().getResources();
        //            int bottomEdge = res.getDisplayMetrics().heightPixels;
        //            bottomDecorations = bottomEdge - out.bottom;
        //            out.bottom = bottomEdge;
        //        }
        return bottomDecorations;
    }

    private void getLocationInWindow(View anchor, @Size(2) int[] out) {
        anchor.getLocationInWindow(out);
    }

    /**
     * @param out Margins relative to left, top, right and bottom of the window.
     */
    private void getBoundsInWindow(Rect out) {
        final View bounds = mDropDownBoundsView;
        if (bounds != null) {
            bounds.getWindowVisibleDisplayFrame(mTempRect);
            final int windowTop = mTempRect.top;
            final int windowRight = mTempRect.right;
            final int windowLeft = mTempRect.left;
            final int windowBottom = mTempRect.bottom;

            bounds.getLocationInWindow(mTempLocation);
            final int boundsTop = mTempLocation[1];
            final int boundsLeft = mTempLocation[0];

            final int boundsHeight = bounds.getHeight();
            final int boundsWidth = bounds.getWidth();

            out.top = boundsTop - windowTop;
            out.left = boundsLeft - windowLeft;
            out.bottom = windowBottom - (boundsTop + boundsHeight);
            out.right = windowRight - (boundsLeft + boundsWidth);
            return;
        }

        out.set(0, 0, 0, 0);
    }
}