content: /*
   * Copyright (C) 2010 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.widget;
  
  import android.annotation.AttrRes;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.StyleRes;
  import android.annotation.UnsupportedAppUsage;
  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.AdapterView.OnItemSelectedListener;
  
  import com.android.internal.R;
  import com.android.internal.view.menu.ShowableListMenu;
  
  /**
   * A ListPopupWindow anchors itself to a host view and displays a
   * list of choices.
   *
   * <p>ListPopupWindow contains a number of tricky behaviors surrounding
   * positioning, scrolling parents to fit the dropdown, interacting
   * sanely with the IME if present, and others.
   *
   * @see android.widget.AutoCompleteTextView
   * @see android.widget.Spinner
   */
  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.
       */
      private static final int EXPAND_LIST_TIMEOUT = 250;
  
      private Context mContext;
      private ListAdapter mAdapter;
      @UnsupportedAppUsage
      private 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 mIsAnimatedFromAnchor = true;
      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 AdapterView.OnItemSelectedListener mItemSelectedListener;
  
      private 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;
  
      private 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;
  
      @UnsupportedAppUsage
      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, com.android.internal.R.attr.listPopupWindowStyle, 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.
       */
      public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs) {
          this(context, attrs, com.android.internal.R.attr.listPopupWindowStyle, 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 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_dropDownHorizontalOffset, 0);
          mDropDownVerticalOffset = a.getDimensionPixelOffset(
                  R.styleable.ListPopupWindow_dropDownVerticalOffset, 0);
          if (mDropDownVerticalOffset != 0) {
              mDropDownVerticalOffsetSet = true;
          }
          a.recycle();
  
          mPopup = new PopupWindow(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 (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>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.
       */
      @UnsupportedAppUsage
      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.
       */
      @UnsupportedAppUsage
      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.
       */
      @UnsupportedAppUsage
      public boolean isDropDownAlwaysVisible() {
          return mDropDownAlwaysVisible;
      }
  
      /**
       * Sets the operating mode for the soft input area.
       *
       * @param mode The desired mode, see
       *        {@link android.view.WindowManager.LayoutParams#softInputMode}
       *        for the full list
       *
       * @see android.view.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 android.view.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 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;
      }
  
      /**
       * Returns bounds which are used as a popup's epicenter
       * of the enter and exit transitions.
       *
       * @return bounds relative to anchor view, 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) {
              if (mContext.getApplicationInfo().targetSdkVersion
                      < Build.VERSION_CODES.O) {
                  Log.e(TAG, "Negative value " + height + " passed to ListPopupWindow#setHeight"
                          + " produces undefined results");
              } else {
                  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(android.widget.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();
          mPopup.setAllowScrollingAnchorParent(!noInputMethod);
          mPopup.setWindowLayoutType(mDropDownWindowLayoutType);
  
          if (mPopup.isShowing()) {
              if (!getAnchorView().isAttachedToWindow()) {
                  //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);
              mPopup.getContentView().restoreDefaultFocus();
          } 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);
              mPopup.setIsClippedToScreen(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);
              mPopup.setEpicenterBounds(mEpicenterBounds);
              if (mOverlapAnchorSet) {
                  mPopup.setOverlapAnchor(mOverlapAnchor);
              }
              mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
                      mDropDownVerticalOffset, mDropDownGravity);
              mDropDownList.setSelection(ListView.INVALID_POSITION);
              mPopup.getContentView().restoreDefaultFocus();
  
              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.
       */
      @UnsupportedAppUsage
      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
                              || !KeyEvent.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 && KeyEvent.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 ShowableListMenu 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
       */
      @UnsupportedAppUsage
      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) {
                          DropDownListView 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;
                  } 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 {
              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 = mPopup.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.measureHeightOfChildren(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
       */
      public void setOverlapAnchor(boolean overlap) {
          mOverlapAnchorSet = true;
          mOverlapAnchor = overlap;
      }
  
      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 && mDropDownList.isAttachedToWindow()
                      && 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();
              }
          }
      }
  }
  