public class

ListPopupWindow

extends java.lang.Object

implements ShowableListMenu

 java.lang.Object

↳androidx.appcompat.widget.ListPopupWindow

Subclasses:

MenuPopupWindow

Gradle dependencies

compile group: 'androidx.appcompat', name: 'appcompat', version: '1.6.0-alpha04'

  • groupId: androidx.appcompat
  • artifactId: appcompat
  • version: 1.6.0-alpha04

Artifact androidx.appcompat:appcompat:1.6.0-alpha04 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.appcompat:appcompat com.android.support:appcompat-v7

Androidx class mapping:

androidx.appcompat.widget.ListPopupWindow android.support.v7.widget.ListPopupWindow

Overview

Static library support version of the framework's . 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.

Summary

Fields
public static final intINPUT_METHOD_FROM_FOCUSABLE

Mode for ListPopupWindow.setInputMethodMode(int): the requirements for the input method should be based on the focusability of the popup.

public static final intINPUT_METHOD_NEEDED

Mode for ListPopupWindow.setInputMethodMode(int): this popup always needs to work with an input method, regardless of whether it is focusable.

public static final intINPUT_METHOD_NOT_NEEDED

Mode for ListPopupWindow.setInputMethodMode(int): this popup never needs to work with an input method, regardless of whether it is focusable.

public static final intMATCH_PARENT

Alias for .

public static final intPOSITION_PROMPT_ABOVE

The provided prompt view should appear above list content.

public static final intPOSITION_PROMPT_BELOW

The provided prompt view should appear below list content.

public static final intWRAP_CONTENT

Alias for .

Constructors
publicListPopupWindow(Context context)

Create a new, empty popup window capable of displaying items from a ListAdapter.

publicListPopupWindow(Context context, AttributeSet attrs)

Create a new, empty popup window capable of displaying items from a ListAdapter.

publicListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr)

Create a new, empty popup window capable of displaying items from a ListAdapter.

publicListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)

Create a new, empty popup window capable of displaying items from a ListAdapter.

Methods
public voidclearListSelection()

Clear any current list selection.

public OnTouchListenercreateDragToOpenListener(View src)

Returns an that can be added to the source view to implement drag-to-open behavior.

public voiddismiss()

Dismiss the popup window.

public ViewgetAnchorView()

Returns the view that will be used to anchor this popup.

public intgetAnimationStyle()

Returns the animation style that will be used when the popup window is shown or dismissed.

public DrawablegetBackground()

public RectgetEpicenterBounds()

Return custom anchor-relative bounds of the popup's transition epicenter

public intgetHeight()

public intgetHorizontalOffset()

public intgetInputMethodMode()

Return the current value in ListPopupWindow.setInputMethodMode(int).

public ListViewgetListView()

public intgetPromptPosition()

public java.lang.ObjectgetSelectedItem()

public longgetSelectedItemId()

public intgetSelectedItemPosition()

public ViewgetSelectedView()

public intgetSoftInputMode()

Returns the current value in ListPopupWindow.setSoftInputMode(int).

public intgetVerticalOffset()

public intgetWidth()

public booleanisDropDownAlwaysVisible()

public booleanisInputMethodNotNeeded()

public booleanisModal()

Returns whether the popup window will be modal when shown.

public booleanisShowing()

public booleanonKeyDown(int keyCode, KeyEvent event)

Filter key down events.

public booleanonKeyPreIme(int keyCode, KeyEvent event)

Filter pre-IME key events.

public booleanonKeyUp(int keyCode, KeyEvent event)

Filter key up events.

public booleanperformItemClick(int position)

Perform an item click operation on the specified list adapter position.

public voidpostShow()

Post a ListPopupWindow.show() call to the UI thread.

public voidsetAdapter(ListAdapter adapter)

Sets the adapter that provides the data and the views to represent the data in this popup window.

public voidsetAnchorView(View anchor)

Sets the popup's anchor view.

public voidsetAnimationStyle(int animationStyle)

Set an animation style to use when the popup window is shown or dismissed.

public voidsetBackgroundDrawable(Drawable d)

Sets a drawable to be the background for the popup window.

public voidsetContentWidth(int width)

Sets the width of the popup window by the size of its content.

public voidsetDropDownAlwaysVisible(boolean dropDownAlwaysVisible)

Sets whether the drop-down should remain visible under certain conditions.

public voidsetDropDownGravity(int gravity)

Set the gravity of the dropdown list.

public voidsetEpicenterBounds(Rect bounds)

Specifies the custom anchor-relative bounds of the popup's transition epicenter.

public voidsetForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch)

Forces outside touches to be ignored.

public voidsetHeight(int height)

Sets the height of the popup window in pixels.

public voidsetHorizontalOffset(int offset)

Set the horizontal offset of this popup from its anchor view in pixels.

public voidsetInputMethodMode(int mode)

Control how the popup operates with an input method: one of ListPopupWindow.INPUT_METHOD_FROM_FOCUSABLE, ListPopupWindow.INPUT_METHOD_NEEDED, or ListPopupWindow.INPUT_METHOD_NOT_NEEDED.

public voidsetListSelector(Drawable selector)

Sets a drawable to use as the list item selector.

public voidsetModal(boolean modal)

Set whether this window should be modal when shown.

public voidsetOnDismissListener(PopupWindow.OnDismissListener listener)

Set a listener to receive a callback when the popup is dismissed.

public voidsetOnItemClickListener(AdapterView.OnItemClickListener clickListener)

Sets a listener to receive events when a list item is clicked.

public voidsetOnItemSelectedListener(OnItemSelectedListener selectedListener)

Sets a listener to receive events when a list item is selected.

public voidsetOverlapAnchor(boolean overlapAnchor)

public voidsetPromptPosition(int position)

Set where the optional prompt view should appear.

public voidsetPromptView(View prompt)

Set a view to act as a user prompt for this popup window.

public voidsetSelection(int position)

Set the selected position of the list.

public voidsetSoftInputMode(int mode)

Sets the operating mode for the soft input area.

public voidsetVerticalOffset(int offset)

Set the vertical offset of this popup from its anchor view in pixels.

public voidsetWidth(int width)

Sets the width of the popup window in pixels.

public voidsetWindowLayoutType(int layoutType)

Set the layout type for this popup window.

public voidshow()

Show the popup list.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int POSITION_PROMPT_ABOVE

The provided prompt view should appear above list content.

See also: ListPopupWindow.setPromptPosition(int), ListPopupWindow.getPromptPosition(), ListPopupWindow.setPromptView(View)

public static final int POSITION_PROMPT_BELOW

The provided prompt view should appear below list content.

See also: ListPopupWindow.setPromptPosition(int), ListPopupWindow.getPromptPosition(), ListPopupWindow.setPromptView(View)

public static final int MATCH_PARENT

Alias for . 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 WRAP_CONTENT

Alias for . If used to specify a popup width, the popup will use the width of its content.

public static final int INPUT_METHOD_FROM_FOCUSABLE

Mode for ListPopupWindow.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_NEEDED

Mode for ListPopupWindow.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_NOT_NEEDED

Mode for ListPopupWindow.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.

Constructors

public ListPopupWindow(Context context)

Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds should be set using ListPopupWindow.setBackgroundDrawable(Drawable).

Parameters:

context: Context used for contained views.

public ListPopupWindow(Context context, AttributeSet attrs)

Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds should be set using ListPopupWindow.setBackgroundDrawable(Drawable).

Parameters:

context: Context used for contained views.
attrs: Attributes from inflating parent views used to style the popup.

public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr)

Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds should be set using ListPopupWindow.setBackgroundDrawable(Drawable).

Parameters:

context: Context used for contained views.
attrs: Attributes from inflating parent views used to style the popup.
defStyleAttr: Default style attribute to use for popup content.

public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)

Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds should be set using ListPopupWindow.setBackgroundDrawable(Drawable).

Parameters:

context: Context used for contained views.
attrs: Attributes from inflating parent views used to style the popup.
defStyleAttr: Style attribute to read for default styling of popup content.
defStyleRes: Style resource ID to use for default styling of popup content.

Methods

public void setAdapter(ListAdapter adapter)

Sets the adapter that provides the data and the views to represent the data in this popup window.

Parameters:

adapter: The adapter to use to create this window's content.

public void setPromptPosition(int position)

Set where the optional prompt view should appear. The default is ListPopupWindow.POSITION_PROMPT_ABOVE.

Parameters:

position: A position constant declaring where the prompt should be displayed.

See also: ListPopupWindow.POSITION_PROMPT_ABOVE, ListPopupWindow.POSITION_PROMPT_BELOW

public int getPromptPosition()

Returns:

Where the optional prompt view should appear.

See also: ListPopupWindow.POSITION_PROMPT_ABOVE, ListPopupWindow.POSITION_PROMPT_BELOW

public void setModal(boolean modal)

Set whether this window should be modal when shown.

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.

Parameters:

modal: true if the popup window should be modal, false otherwise.

public boolean isModal()

Returns whether the popup window will be modal when shown.

Returns:

true if the popup window will be modal, false otherwise.

public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch)

Forces outside touches to be ignored. Normally if ListPopupWindow.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.

public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible)

Sets whether the drop-down should remain visible under certain conditions. The drop-down will occupy the entire screen below ListPopupWindow.getAnchorView() regardless of the size or content of the list. ListPopupWindow.getBackground() will fill any space that is not used by the list.

Parameters:

dropDownAlwaysVisible: Whether to keep the drop-down visible.

public boolean isDropDownAlwaysVisible()

Returns:

Whether the drop-down is visible under special conditions.

public void setSoftInputMode(int mode)

Sets the operating mode for the soft input area.

Parameters:

mode: The desired mode, see for the full list

See also: , ListPopupWindow.getSoftInputMode()

public int getSoftInputMode()

Returns the current value in ListPopupWindow.setSoftInputMode(int).

See also: ListPopupWindow.setSoftInputMode(int)

public void setListSelector(Drawable selector)

Sets a drawable to use as the list item selector.

Parameters:

selector: List selector drawable to use in the popup.

public Drawable getBackground()

Returns:

The background drawable for the popup window.

public void setBackgroundDrawable(Drawable d)

Sets a drawable to be the background for the popup window.

Parameters:

d: A drawable to set as the background.

public void setAnimationStyle(int animationStyle)

Set an animation style to use when the popup window is shown or dismissed.

Parameters:

animationStyle: Animation style to use.

public int getAnimationStyle()

Returns the animation style that will be used when the popup window is shown or dismissed.

Returns:

Animation style that will be used.

public View getAnchorView()

Returns the view that will be used to anchor this popup.

Returns:

The popup's anchor view

public void setAnchorView(View anchor)

Sets the popup's anchor view. This popup will always be positioned relative to the anchor view when shown.

Parameters:

anchor: The view to use as an anchor.

public int getHorizontalOffset()

Returns:

The horizontal offset of the popup from its anchor in pixels.

public void setHorizontalOffset(int offset)

Set the horizontal offset of this popup from its anchor view in pixels.

Parameters:

offset: The horizontal offset of the popup from its anchor.

public int getVerticalOffset()

Returns:

The vertical offset of the popup from its anchor in pixels.

public void setVerticalOffset(int offset)

Set the vertical offset of this popup from its anchor view in pixels.

Parameters:

offset: The vertical offset of the popup from its anchor.

public void setEpicenterBounds(Rect bounds)

Specifies the custom anchor-relative bounds of the popup's transition epicenter.

Parameters:

bounds: anchor-relative bounds or null to use default epicenter

See also: ListPopupWindow.getEpicenterBounds()

public Rect getEpicenterBounds()

Return custom anchor-relative bounds of the popup's transition epicenter

Returns:

anchor-relative bounds, or @null if not set

See also: ListPopupWindow.setEpicenterBounds(Rect)

public void setDropDownGravity(int gravity)

Set the gravity of the dropdown list. This is commonly used to set gravity to START or END for alignment with the anchor.

Parameters:

gravity: Gravity value to use

public int getWidth()

Returns:

The width of the popup window in pixels.

public void setWidth(int width)

Sets the width of the popup window in pixels. Can also be ListPopupWindow.MATCH_PARENT or ListPopupWindow.WRAP_CONTENT.

Parameters:

width: Width of the popup window.

public void setContentWidth(int width)

Sets the width of the popup window by the size of its content. The final width may be larger to accommodate styled window dressing.

Parameters:

width: Desired width of content in pixels.

public int getHeight()

Returns:

The height of the popup window in pixels.

public void setHeight(int height)

Sets the height of the popup window in pixels. Can also be ListPopupWindow.MATCH_PARENT.

Parameters:

height: Height of the popup window must be a positive value, ListPopupWindow.MATCH_PARENT, or ListPopupWindow.WRAP_CONTENT.

public void setWindowLayoutType(int layoutType)

Set the layout type for this popup window.

See for possible values.

Parameters:

layoutType: Layout type for this window.

See also:

public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener)

Sets a listener to receive events when a list item is clicked.

Parameters:

clickListener: Listener to register

See also: ListView

public void setOnItemSelectedListener(OnItemSelectedListener selectedListener)

Sets a listener to receive events when a list item is selected.

Parameters:

selectedListener: Listener to register.

See also: ListView

public void setPromptView(View prompt)

Set a view to act as a user prompt for this popup window. Where the prompt view will appear is controlled by ListPopupWindow.setPromptPosition(int).

Parameters:

prompt: View to use as an informational prompt.

public void postShow()

Post a ListPopupWindow.show() call to the UI thread.

public void show()

Show the popup list. If the list is already showing, this method will recalculate the popup's size and position.

public void dismiss()

Dismiss the popup window.

public void setOnDismissListener(PopupWindow.OnDismissListener listener)

Set a listener to receive a callback when the popup is dismissed.

Parameters:

listener: Listener that will be notified when the popup is dismissed.

public void setInputMethodMode(int mode)

Control how the popup operates with an input method: one of ListPopupWindow.INPUT_METHOD_FROM_FOCUSABLE, ListPopupWindow.INPUT_METHOD_NEEDED, or ListPopupWindow.INPUT_METHOD_NOT_NEEDED.

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 ListPopupWindow.show() method.

See also: ListPopupWindow.getInputMethodMode(), ListPopupWindow.show()

public int getInputMethodMode()

Return the current value in ListPopupWindow.setInputMethodMode(int).

See also: ListPopupWindow.setInputMethodMode(int)

public void setSelection(int position)

Set the selected position of the list. Only valid when ListPopupWindow.isShowing() == true.

Parameters:

position: List position to set as selected.

public void clearListSelection()

Clear any current list selection. Only valid when ListPopupWindow.isShowing() == true.

public boolean isShowing()

Returns:

true if the popup is currently showing, false otherwise.

public boolean isInputMethodNotNeeded()

Returns:

true if this popup is configured to assume the user does not need to interact with the IME while it is showing, false otherwise.

public boolean performItemClick(int position)

Perform an item click operation on the specified list adapter position.

Parameters:

position: Adapter position for performing the click

Returns:

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 java.lang.Object getSelectedItem()

Returns:

The currently selected item or null if the popup is not showing.

public int getSelectedItemPosition()

Returns:

The position of the currently selected item or ListView if ListPopupWindow.isShowing() == false.

See also: ListView

public long getSelectedItemId()

Returns:

The ID of the currently selected item or ListView if ListPopupWindow.isShowing() == false.

See also: ListView

public View getSelectedView()

Returns:

The View for the currently selected item or null if ListPopupWindow.isShowing() == false.

See also: ListView

public ListView getListView()

Returns:

The ListView displayed within the popup window. Only valid when ListPopupWindow.isShowing() == true.

public boolean onKeyDown(int keyCode, KeyEvent event)

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.

Parameters:

keyCode: keyCode param passed to the host view's onKeyDown
event: event param passed to the host view's onKeyDown

Returns:

true if the event was handled, false if it was ignored.

See also: ListPopupWindow.setModal(boolean), ListPopupWindow.onKeyUp(int, KeyEvent)

public boolean onKeyUp(int keyCode, KeyEvent event)

Filter key up events. By forwarding key up events to this function, views using non-modal ListPopupWindow can have it handle key selection of items.

Parameters:

keyCode: keyCode param passed to the host view's onKeyUp
event: event param passed to the host view's onKeyUp

Returns:

true if the event was handled, false if it was ignored.

See also: ListPopupWindow.setModal(boolean), ListPopupWindow.onKeyDown(int, KeyEvent)

public boolean onKeyPreIme(int keyCode, KeyEvent event)

Filter pre-IME key events. By forwarding View events to this function, views using ListPopupWindow can have it dismiss the popup when the back key is pressed.

Parameters:

keyCode: keyCode param passed to the host view's onKeyPreIme
event: event param passed to the host view's onKeyPreIme

Returns:

true if the event was handled, false if it was ignored.

See also: ListPopupWindow.setModal(boolean)

public OnTouchListener createDragToOpenListener(View src)

Returns an that can be added to the source view to implement drag-to-open behavior. Generally, the source view should be the same view that was passed to ListPopupWindow.setAnchorView(View).

When the listener is set on a view, touching that view and dragging outside of its bounds will open the popup window. Lifting will select the currently touched list item.

Example usage:

 ListPopupWindow myPopup = new ListPopupWindow(context);
 myPopup.setAnchor(myAnchor);
 OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor);
 myAnchor.setOnTouchListener(dragListener);
 

Parameters:

src: the view on which the resulting listener will be set

Returns:

a touch listener that controls drag-to-open behavior

public void setOverlapAnchor(boolean overlapAnchor)

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 androidx.appcompat.widget;

import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;

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.util.AttributeSet;
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.AdapterView.OnItemSelectedListener;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.PopupWindow;

import androidx.annotation.AttrRes;
import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
import androidx.appcompat.R;
import androidx.appcompat.view.menu.ShowableListMenu;
import androidx.core.view.ViewCompat;
import androidx.core.widget.PopupWindowCompat;

import java.lang.reflect.Method;

/**
 * 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
 */
@SuppressWarnings("HiddenSuperclass")
public class ListPopupWindow implements ShowableListMenu {
    private static final String TAG = "ListPopupWindow";
    private static final boolean DEBUG = false;

    /**
     * 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.
     */
    static final int EXPAND_LIST_TIMEOUT = 250;

    private static Method sSetClipToWindowEnabledMethod;
    private static Method sGetMaxAvailableHeightMethod;
    private static Method sSetEpicenterBoundsMethod;

    static {
        if (Build.VERSION.SDK_INT <= 28) {
            try {
                sSetClipToWindowEnabledMethod = PopupWindow.class.getDeclaredMethod(
                        "setClipToScreenEnabled", boolean.class);
            } catch (NoSuchMethodException e) {
                Log.i(TAG,
                        "Could not find method setClipToScreenEnabled() 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.");
            }
        }
        if (Build.VERSION.SDK_INT <= 23) {
            try {
                sGetMaxAvailableHeightMethod = PopupWindow.class.getDeclaredMethod(
                        "getMaxAvailableHeight", View.class, int.class, boolean.class);
            } catch (NoSuchMethodException e) {
                Log.i(TAG, "Could not find method getMaxAvailableHeight(View, int, boolean)"
                        + " on PopupWindow. Oh well.");
            }
        }
    }

    private Context mContext;
    private ListAdapter mAdapter;
    DropDownListView mDropDownList;

    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 boolean mOverlapAnchor;
    private boolean mOverlapAnchorSet;

    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 Drawable mDropDownListHighlight;

    private AdapterView.OnItemClickListener mItemClickListener;
    private 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();

    /**
     * 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;

    PopupWindow mPopup;

    /**
     * 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;

    /**
     * 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;

    /**
     * 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 ListPopupWindow(@NonNull 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 ListPopupWindow(@NonNull Context context, @Nullable 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 ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
            @AttrRes 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 ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
            @AttrRes int defStyleAttr, @StyleRes 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();

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

    /**
     * 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(@Nullable ListAdapter adapter) {
        if (mObserver == null) {
            mObserver = new PopupDataSetObserver();
        } else if (mAdapter != null) {
            mAdapter.unregisterDataSetObserver(mObserver);
        }
        mAdapter = adapter;
        if (adapter != 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>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.
     */
    @RestrictTo(LIBRARY_GROUP_PREFIX)
    public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
        mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
    }

    /**
     * Sets whether the drop-down should remain visible under certain conditions.
     *
     * 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.
     */
    @RestrictTo(LIBRARY_GROUP_PREFIX)
    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.
     */
    @RestrictTo(LIBRARY_GROUP_PREFIX)
    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 @Nullable 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(@Nullable 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(@StyleRes 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 @StyleRes int getAnimationStyle() {
        return mPopup.getAnimationStyle();
    }

    /**
     * Returns the view that will be used to anchor this popup.
     *
     * @return The popup's anchor view
     */
    public @Nullable 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(@Nullable View anchor) {
        mDropDownAnchorView = anchor;
    }

    /**
     * @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 custom anchor-relative bounds of the popup's transition
     * epicenter.
     *
     * @param bounds anchor-relative bounds or {@code null} to use default epicenter
     * @see #getEpicenterBounds()
     */
    public void setEpicenterBounds(@Nullable Rect bounds) {
        mEpicenterBounds = bounds != null ? new Rect(bounds) : null;
    }

    /**
     * Return custom anchor-relative bounds of the popup's transition epicenter
     *
     * @return anchor-relative bounds, or @{@code null} if not set
     * @see #setEpicenterBounds(Rect)
     */
    @Nullable
    public Rect getEpicenterBounds() {
        return mEpicenterBounds != null ? new Rect(mEpicenterBounds) : null;
    }

    /**
     * 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;
    }

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

    /**
     * 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;
    }

    /**
     * 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 must be a positive value,
     *               {@link #MATCH_PARENT}, or {@link #WRAP_CONTENT}.
     *
     * @throws IllegalArgumentException if height is set to negative value
     */
    public void setHeight(int height) {
        if (height < 0 && ViewGroup.LayoutParams.WRAP_CONTENT != height
                && ViewGroup.LayoutParams.MATCH_PARENT != height) {
            throw new IllegalArgumentException(
                   "Invalid height. Must be a positive value, MATCH_PARENT, or WRAP_CONTENT.");
        }
        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(@Nullable AdapterView.OnItemClickListener clickListener) {
        mItemClickListener = clickListener;
    }

    /**
     * Sets a listener to receive events when a list item is selected.
     *
     * @param selectedListener Listener to register.
     *
     * @see ListView#setOnItemSelectedListener(OnItemSelectedListener)
     */
    public void setOnItemSelectedListener(@Nullable OnItemSelectedListener selectedListener) {
        mItemSelectedListener = 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(@Nullable 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.
     */
    @Override
    public void show() {
        int height = buildDropDown();

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

        if (mPopup.isShowing()) {
            if (!ViewCompat.isAttachedToWindow(getAnchorView())) {
                //Don't update position if the anchor view is detached from window.
                return;
            }
            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.
                widthSpec = -1;
            } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
                widthSpec = getAnchorView().getWidth();
            } else {
                widthSpec = mDropDownWidth;
            }

            final int heightSpec;
            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);
                }
            } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
                heightSpec = height;
            } else {
                heightSpec = mDropDownHeight;
            }

            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);

            mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
                            mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec,
                            (heightSpec < 0)? -1 : heightSpec);
        } else {
            final int widthSpec;
            if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
                widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
            } else {
                if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    widthSpec = getAnchorView().getWidth();
                } else {
                    widthSpec = mDropDownWidth;
                }
            }

            final int heightSpec;
            if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
                heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
            } else {
                if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    heightSpec = height;
                } else {
                    heightSpec = mDropDownHeight;
                }
            }

            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 (mOverlapAnchorSet) {
                PopupWindowCompat.setOverlapAnchor(mPopup, mOverlapAnchor);
            }
            if (Build.VERSION.SDK_INT <= 28) {
                if (sSetEpicenterBoundsMethod != null) {
                    try {
                        sSetEpicenterBoundsMethod.invoke(mPopup, mEpicenterBounds);
                    } catch (Exception e) {
                        Log.e(TAG, "Could not invoke setEpicenterBounds on PopupWindow", e);
                    }
                }
            } else {
                Api29Impl.setEpicenterBounds(mPopup, mEpicenterBounds);
            }
            PopupWindowCompat.showAsDropDown(mPopup, getAnchorView(), mDropDownHorizontalOffset,
                    mDropDownVerticalOffset, mDropDownGravity);
            mDropDownList.setSelection(ListView.INVALID_POSITION);

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

    /**
     * Dismiss the popup window.
     */
    @Override
    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(@Nullable 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>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) {
        DropDownListView list = mDropDownList;
        if (isShowing() && list != null) {
            list.setListSelectionHidden(false);
            list.setSelection(position);

            if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
                list.setItemChecked(position, true);
            }
        }
    }

    /**
     * Clear any current list selection.
     * Only valid when {@link #isShowing()} == {@code true}.
     */
    public void clearListSelection() {
        final DropDownListView 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.
     */
    @Override
    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 DropDownListView 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 @Nullable 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 @Nullable 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}.
     */
    @Override
    public @Nullable ListView getListView() {
        return mDropDownList;
    }

    @NonNull DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
        return new DropDownListView(context, hijackFocus);
    }

    /**
     * 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)
     * @see #onKeyUp(int, KeyEvent)
     */
    public boolean onKeyDown(int keyCode, @NonNull 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 up 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)
     * @see #onKeyDown(int, KeyEvent)
     */
    public boolean onKeyUp(int keyCode, @NonNull 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, @NonNull 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;
    }

    /**
     * Returns an {@link OnTouchListener} that can be added to the source view
     * to implement drag-to-open behavior. Generally, the source view should be
     * the same view that was passed to {@link #setAnchorView}.
     * <p>
     * When the listener is set on a view, touching that view and dragging
     * outside of its bounds will open the popup window. Lifting will select the
     * currently touched list item.
     * <p>
     * Example usage:
     * <pre>
     * ListPopupWindow myPopup = new ListPopupWindow(context);
     * myPopup.setAnchor(myAnchor);
     * OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor);
     * myAnchor.setOnTouchListener(dragListener);
     * </pre>
     *
     * @param src the view on which the resulting listener will be set
     * @return a touch listener that controls drag-to-open behavior
     */
    public OnTouchListener createDragToOpenListener(View src) {
        return new ForwardingListener(src) {
            @Override
            public ListPopupWindow getPopup() {
                return ListPopupWindow.this;
            }
        };
    }

    /**
     * <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 its job which is slower than this method.
             */
            mShowDropDownRunnable = new Runnable() {
                @Override
                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 OnItemSelectedListener() {
                @Override
                public void onItemSelected(AdapterView<?> parent, View view,
                        int position, long id) {

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

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

                @Override
                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 accommodate 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;
                } else {
                    widthMode = MeasureSpec.UNSPECIFIED;
                    widthSize = 0;
                }
                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.
        final int padding;
        final 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();
            padding = 0;
        }

        // 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);
        if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
            return maxHeight + padding;
        }

        final int childWidthSpec;
        switch (mDropDownWidth) {
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                childWidthSpec = MeasureSpec.makeMeasureSpec(
                        mContext.getResources().getDisplayMetrics().widthPixels
                                - (mTempRect.left + mTempRect.right),
                        MeasureSpec.AT_MOST);
                break;
            case ViewGroup.LayoutParams.MATCH_PARENT:
                childWidthSpec = MeasureSpec.makeMeasureSpec(
                        mContext.getResources().getDisplayMetrics().widthPixels
                                - (mTempRect.left + mTempRect.right),
                        MeasureSpec.EXACTLY);
                break;
            default:
                childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY);
                break;
        }

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

        return listContent + otherHeights;
    }

    /**
     * @hide Only used by {@link androidx.appcompat.view.menu.CascadingMenuPopup} to position
     * a submenu correctly.
     */
    @RestrictTo(LIBRARY_GROUP_PREFIX)
    public void setOverlapAnchor(boolean overlapAnchor) {
        mOverlapAnchorSet = true;
        mOverlapAnchor = overlapAnchor;
    }

    private class PopupDataSetObserver extends DataSetObserver {
        PopupDataSetObserver() {
        }

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

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

    private class ListSelectorHider implements Runnable {
        ListSelectorHider() {
        }

        @Override
        public void run() {
            clearListSelection();
        }
    }

    private class ResizePopupRunnable implements Runnable {
        ResizePopupRunnable() {
        }

        @Override
        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 {
        PopupTouchInterceptor() {
        }

        @Override
        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 {
        PopupScrollListener() {
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                int totalItemCount) {

        }

        @Override
        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 (Build.VERSION.SDK_INT <= 28) {
            if (sSetClipToWindowEnabledMethod != null) {
                try {
                    sSetClipToWindowEnabledMethod.invoke(mPopup, clip);
                } catch (Exception e) {
                    Log.i(TAG, "Could not call setClipToScreenEnabled() on PopupWindow. Oh well.");
                }
            }
        } else {
            Api29Impl.setIsClippedToScreen(mPopup, clip);
        }
    }

    private int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) {
        if (Build.VERSION.SDK_INT <= 23) {
            if (sGetMaxAvailableHeightMethod != null) {
                try {
                    return (int) sGetMaxAvailableHeightMethod.invoke(mPopup, anchor, yOffset,
                            ignoreBottomDecorations);
                } catch (Exception e) {
                    Log.i(TAG, "Could not call getMaxAvailableHeightMethod(View, int, boolean)"
                            + " on PopupWindow. Using the public version.");
                }
            }
            return mPopup.getMaxAvailableHeight(anchor, yOffset);
        } else {
            return Api24Impl.getMaxAvailableHeight(mPopup, anchor, yOffset,
                    ignoreBottomDecorations);
        }
    }

    @RequiresApi(29)
    static class Api29Impl {
        private Api29Impl() {
            // This class is not instantiable.
        }

        @DoNotInline
        static void setEpicenterBounds(PopupWindow popupWindow, Rect bounds) {
            popupWindow.setEpicenterBounds(bounds);
        }

        @DoNotInline
        static void setIsClippedToScreen(PopupWindow popupWindow, boolean enabled) {
            popupWindow.setIsClippedToScreen(enabled);
        }
    }

    @RequiresApi(24)
    static class Api24Impl {
        private Api24Impl() {
            // This class is not instantiable.
        }

        @DoNotInline
        static int getMaxAvailableHeight(PopupWindow popupWindow, View anchor, int yOffset,
                boolean ignoreBottomDecorations) {
            return popupWindow.getMaxAvailableHeight(anchor, yOffset, ignoreBottomDecorations);
        }
    }
}