[go: nahoru, domu]

1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v7.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.database.DataSetObserver;
22import android.graphics.Rect;
23import android.graphics.drawable.Drawable;
24import android.os.Build;
25import android.os.Handler;
26import android.support.annotation.AttrRes;
27import android.support.annotation.NonNull;
28import android.support.annotation.Nullable;
29import android.support.annotation.StyleRes;
30import android.support.v4.os.BuildCompat;
31import android.support.v4.view.ViewCompat;
32import android.support.v4.widget.PopupWindowCompat;
33import android.support.v7.appcompat.R;
34import android.support.v7.view.menu.ShowableListMenu;
35import android.util.AttributeSet;
36import android.util.Log;
37import android.view.Gravity;
38import android.view.KeyEvent;
39import android.view.MotionEvent;
40import android.view.View;
41import android.view.View.MeasureSpec;
42import android.view.View.OnTouchListener;
43import android.view.ViewGroup;
44import android.view.ViewParent;
45import android.view.WindowManager;
46import android.widget.AbsListView;
47import android.widget.AdapterView;
48import android.widget.AdapterView.OnItemSelectedListener;
49import android.widget.LinearLayout;
50import android.widget.ListAdapter;
51import android.widget.ListView;
52import android.widget.PopupWindow;
53
54import java.lang.reflect.Method;
55
56/**
57 * Static library support version of the framework's {@link android.widget.ListPopupWindow}.
58 * Used to write apps that run on platforms prior to Android L. When running
59 * on Android L or above, this implementation is still used; it does not try
60 * to switch to the framework's implementation. See the framework SDK
61 * documentation for a class overview.
62 *
63 * @see android.widget.ListPopupWindow
64 */
65public class ListPopupWindow implements ShowableListMenu {
66    private static final String TAG = "ListPopupWindow";
67    private static final boolean DEBUG = false;
68
69    /**
70     * This value controls the length of time that the user
71     * must leave a pointer down without scrolling to expand
72     * the autocomplete dropdown list to cover the IME.
73     */
74    private static final int EXPAND_LIST_TIMEOUT = 250;
75
76    private static Method sClipToWindowEnabledMethod;
77    private static Method sGetMaxAvailableHeightMethod;
78    private static Method sSetEpicenterBoundsMethod;
79
80    static {
81        try {
82            sClipToWindowEnabledMethod = PopupWindow.class.getDeclaredMethod(
83                    "setClipToScreenEnabled", boolean.class);
84        } catch (NoSuchMethodException e) {
85            Log.i(TAG, "Could not find method setClipToScreenEnabled() on PopupWindow. Oh well.");
86        }
87        try {
88            sGetMaxAvailableHeightMethod = PopupWindow.class.getDeclaredMethod(
89                    "getMaxAvailableHeight", View.class, int.class, boolean.class);
90        } catch (NoSuchMethodException e) {
91            Log.i(TAG, "Could not find method getMaxAvailableHeight(View, int, boolean)"
92                    + " on PopupWindow. Oh well.");
93        }
94        try {
95            sSetEpicenterBoundsMethod = PopupWindow.class.getDeclaredMethod(
96                    "setEpicenterBounds", Rect.class);
97        } catch (NoSuchMethodException e) {
98            Log.i(TAG, "Could not find method setEpicenterBounds(Rect) on PopupWindow. Oh well.");
99        }
100    }
101
102    private Context mContext;
103    private ListAdapter mAdapter;
104    private DropDownListView mDropDownList;
105
106    private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
107    private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
108    private int mDropDownHorizontalOffset;
109    private int mDropDownVerticalOffset;
110    private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
111    private boolean mDropDownVerticalOffsetSet;
112    private boolean mIsAnimatedFromAnchor = true;
113
114    private int mDropDownGravity = Gravity.NO_GRAVITY;
115
116    private boolean mDropDownAlwaysVisible = false;
117    private boolean mForceIgnoreOutsideTouch = false;
118    int mListItemExpandMaximum = Integer.MAX_VALUE;
119
120    private View mPromptView;
121    private int mPromptPosition = POSITION_PROMPT_ABOVE;
122
123    private DataSetObserver mObserver;
124
125    private View mDropDownAnchorView;
126
127    private Drawable mDropDownListHighlight;
128
129    private AdapterView.OnItemClickListener mItemClickListener;
130    private OnItemSelectedListener mItemSelectedListener;
131
132    private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
133    private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
134    private final PopupScrollListener mScrollListener = new PopupScrollListener();
135    private final ListSelectorHider mHideSelector = new ListSelectorHider();
136    private Runnable mShowDropDownRunnable;
137
138    private final Handler mHandler;
139
140    private final Rect mTempRect = new Rect();
141
142    /**
143     * Optional anchor-relative bounds to be used as the transition epicenter.
144     * When {@code null}, the anchor bounds are used as the epicenter.
145     */
146    private Rect mEpicenterBounds;
147
148    private boolean mModal;
149
150    PopupWindow mPopup;
151
152    /**
153     * The provided prompt view should appear above list content.
154     *
155     * @see #setPromptPosition(int)
156     * @see #getPromptPosition()
157     * @see #setPromptView(View)
158     */
159    public static final int POSITION_PROMPT_ABOVE = 0;
160
161    /**
162     * The provided prompt view should appear below list content.
163     *
164     * @see #setPromptPosition(int)
165     * @see #getPromptPosition()
166     * @see #setPromptView(View)
167     */
168    public static final int POSITION_PROMPT_BELOW = 1;
169
170    /**
171     * Alias for {@link ViewGroup.LayoutParams#MATCH_PARENT}.
172     * If used to specify a popup width, the popup will match the width of the anchor view.
173     * If used to specify a popup height, the popup will fill available space.
174     */
175    public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;
176
177    /**
178     * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
179     * If used to specify a popup width, the popup will use the width of its content.
180     */
181    public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
182
183    /**
184     * Mode for {@link #setInputMethodMode(int)}: the requirements for the
185     * input method should be based on the focusability of the popup.  That is
186     * if it is focusable than it needs to work with the input method, else
187     * it doesn't.
188     */
189    public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE;
190
191    /**
192     * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
193     * work with an input method, regardless of whether it is focusable.  This
194     * means that it will always be displayed so that the user can also operate
195     * the input method while it is shown.
196     */
197    public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED;
198
199    /**
200     * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
201     * work with an input method, regardless of whether it is focusable.  This
202     * means that it will always be displayed to use as much space on the
203     * screen as needed, regardless of whether this covers the input method.
204     */
205    public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED;
206
207    /**
208     * Create a new, empty popup window capable of displaying items from a ListAdapter.
209     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
210     *
211     * @param context Context used for contained views.
212     */
213    public ListPopupWindow(@NonNull Context context) {
214        this(context, null, R.attr.listPopupWindowStyle);
215    }
216
217    /**
218     * Create a new, empty popup window capable of displaying items from a ListAdapter.
219     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
220     *
221     * @param context Context used for contained views.
222     * @param attrs   Attributes from inflating parent views used to style the popup.
223     */
224    public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs) {
225        this(context, attrs, R.attr.listPopupWindowStyle);
226    }
227
228    /**
229     * Create a new, empty popup window capable of displaying items from a ListAdapter.
230     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
231     *
232     * @param context Context used for contained views.
233     * @param attrs Attributes from inflating parent views used to style the popup.
234     * @param defStyleAttr Default style attribute to use for popup content.
235     */
236    public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
237            @AttrRes int defStyleAttr) {
238        this(context, attrs, defStyleAttr, 0);
239    }
240
241    /**
242     * Create a new, empty popup window capable of displaying items from a ListAdapter.
243     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
244     *
245     * @param context Context used for contained views.
246     * @param attrs Attributes from inflating parent views used to style the popup.
247     * @param defStyleAttr Style attribute to read for default styling of popup content.
248     * @param defStyleRes Style resource ID to use for default styling of popup content.
249     */
250    public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
251            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
252        mContext = context;
253        mHandler = new Handler(context.getMainLooper());
254
255        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow,
256                defStyleAttr, defStyleRes);
257        mDropDownHorizontalOffset = a.getDimensionPixelOffset(
258                R.styleable.ListPopupWindow_android_dropDownHorizontalOffset, 0);
259        mDropDownVerticalOffset = a.getDimensionPixelOffset(
260                R.styleable.ListPopupWindow_android_dropDownVerticalOffset, 0);
261        if (mDropDownVerticalOffset != 0) {
262            mDropDownVerticalOffsetSet = true;
263        }
264        a.recycle();
265
266        if (Build.VERSION.SDK_INT >= 11) {
267            mPopup = new AppCompatPopupWindow(context, attrs, defStyleAttr, defStyleRes);
268        } else {
269            mPopup = new AppCompatPopupWindow(context, attrs, defStyleAttr);
270        }
271        mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
272    }
273
274    /**
275     * Sets the adapter that provides the data and the views to represent the data
276     * in this popup window.
277     *
278     * @param adapter The adapter to use to create this window's content.
279     */
280    public void setAdapter(@Nullable ListAdapter adapter) {
281        if (mObserver == null) {
282            mObserver = new PopupDataSetObserver();
283        } else if (mAdapter != null) {
284            mAdapter.unregisterDataSetObserver(mObserver);
285        }
286        mAdapter = adapter;
287        if (mAdapter != null) {
288            adapter.registerDataSetObserver(mObserver);
289        }
290
291        if (mDropDownList != null) {
292            mDropDownList.setAdapter(mAdapter);
293        }
294    }
295
296    /**
297     * Set where the optional prompt view should appear. The default is
298     * {@link #POSITION_PROMPT_ABOVE}.
299     *
300     * @param position A position constant declaring where the prompt should be displayed.
301     *
302     * @see #POSITION_PROMPT_ABOVE
303     * @see #POSITION_PROMPT_BELOW
304     */
305    public void setPromptPosition(int position) {
306        mPromptPosition = position;
307    }
308
309    /**
310     * @return Where the optional prompt view should appear.
311     *
312     * @see #POSITION_PROMPT_ABOVE
313     * @see #POSITION_PROMPT_BELOW
314     */
315    public int getPromptPosition() {
316        return mPromptPosition;
317    }
318
319    /**
320     * Set whether this window should be modal when shown.
321     *
322     * <p>If a popup window is modal, it will receive all touch and key input.
323     * If the user touches outside the popup window's content area the popup window
324     * will be dismissed.
325     *
326     * @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
327     */
328    public void setModal(boolean modal) {
329        mModal = modal;
330        mPopup.setFocusable(modal);
331    }
332
333    /**
334     * Returns whether the popup window will be modal when shown.
335     *
336     * @return {@code true} if the popup window will be modal, {@code false} otherwise.
337     */
338    public boolean isModal() {
339        return mModal;
340    }
341
342    /**
343     * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
344     * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we
345     * ignore outside touch even when the drop down is not set to always visible.
346     *
347     * @hide Used only by AutoCompleteTextView to handle some internal special cases.
348     */
349    public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
350        mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
351    }
352
353    /**
354     * Sets whether the drop-down should remain visible under certain conditions.
355     *
356     * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless
357     * of the size or content of the list.  {@link #getBackground()} will fill any space
358     * that is not used by the list.
359     *
360     * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
361     *
362     * @hide Only used by AutoCompleteTextView under special conditions.
363     */
364    public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
365        mDropDownAlwaysVisible = dropDownAlwaysVisible;
366    }
367
368    /**
369     * @return Whether the drop-down is visible under special conditions.
370     *
371     * @hide Only used by AutoCompleteTextView under special conditions.
372     */
373    public boolean isDropDownAlwaysVisible() {
374        return mDropDownAlwaysVisible;
375    }
376
377    /**
378     * Sets the operating mode for the soft input area.
379     *
380     * @param mode The desired mode, see
381     *        {@link android.view.WindowManager.LayoutParams#softInputMode}
382     *        for the full list
383     *
384     * @see android.view.WindowManager.LayoutParams#softInputMode
385     * @see #getSoftInputMode()
386     */
387    public void setSoftInputMode(int mode) {
388        mPopup.setSoftInputMode(mode);
389    }
390
391    /**
392     * Returns the current value in {@link #setSoftInputMode(int)}.
393     *
394     * @see #setSoftInputMode(int)
395     * @see android.view.WindowManager.LayoutParams#softInputMode
396     */
397    public int getSoftInputMode() {
398        return mPopup.getSoftInputMode();
399    }
400
401    /**
402     * Sets a drawable to use as the list item selector.
403     *
404     * @param selector List selector drawable to use in the popup.
405     */
406    public void setListSelector(Drawable selector) {
407        mDropDownListHighlight = selector;
408    }
409
410    /**
411     * @return The background drawable for the popup window.
412     */
413    public @Nullable Drawable getBackground() {
414        return mPopup.getBackground();
415    }
416
417    /**
418     * Sets a drawable to be the background for the popup window.
419     *
420     * @param d A drawable to set as the background.
421     */
422    public void setBackgroundDrawable(@Nullable Drawable d) {
423        mPopup.setBackgroundDrawable(d);
424    }
425
426    /**
427     * Set an animation style to use when the popup window is shown or dismissed.
428     *
429     * @param animationStyle Animation style to use.
430     */
431    public void setAnimationStyle(@StyleRes int animationStyle) {
432        mPopup.setAnimationStyle(animationStyle);
433    }
434
435    /**
436     * Returns the animation style that will be used when the popup window is
437     * shown or dismissed.
438     *
439     * @return Animation style that will be used.
440     */
441    public @StyleRes int getAnimationStyle() {
442        return mPopup.getAnimationStyle();
443    }
444
445    /**
446     * Returns the view that will be used to anchor this popup.
447     *
448     * @return The popup's anchor view
449     */
450    public @Nullable View getAnchorView() {
451        return mDropDownAnchorView;
452    }
453
454    /**
455     * Sets the popup's anchor view. This popup will always be positioned relative to
456     * the anchor view when shown.
457     *
458     * @param anchor The view to use as an anchor.
459     */
460    public void setAnchorView(@Nullable View anchor) {
461        mDropDownAnchorView = anchor;
462    }
463
464    /**
465     * @return The horizontal offset of the popup from its anchor in pixels.
466     */
467    public int getHorizontalOffset() {
468        return mDropDownHorizontalOffset;
469    }
470
471    /**
472     * Set the horizontal offset of this popup from its anchor view in pixels.
473     *
474     * @param offset The horizontal offset of the popup from its anchor.
475     */
476    public void setHorizontalOffset(int offset) {
477        mDropDownHorizontalOffset = offset;
478    }
479
480    /**
481     * @return The vertical offset of the popup from its anchor in pixels.
482     */
483    public int getVerticalOffset() {
484        if (!mDropDownVerticalOffsetSet) {
485            return 0;
486        }
487        return mDropDownVerticalOffset;
488    }
489
490    /**
491     * Set the vertical offset of this popup from its anchor view in pixels.
492     *
493     * @param offset The vertical offset of the popup from its anchor.
494     */
495    public void setVerticalOffset(int offset) {
496        mDropDownVerticalOffset = offset;
497        mDropDownVerticalOffsetSet = true;
498    }
499
500    /**
501     * Specifies the anchor-relative bounds of the popup's transition
502     * epicenter.
503     *
504     * @param bounds anchor-relative bounds
505     * @hide
506     */
507    public void setEpicenterBounds(Rect bounds) {
508        mEpicenterBounds = bounds;
509    }
510
511    /**
512     * Set the gravity of the dropdown list. This is commonly used to
513     * set gravity to START or END for alignment with the anchor.
514     *
515     * @param gravity Gravity value to use
516     */
517    public void setDropDownGravity(int gravity) {
518        mDropDownGravity = gravity;
519    }
520
521    /**
522     * @return The width of the popup window in pixels.
523     */
524    public int getWidth() {
525        return mDropDownWidth;
526    }
527
528    /**
529     * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT}
530     * or {@link #WRAP_CONTENT}.
531     *
532     * @param width Width of the popup window.
533     */
534    public void setWidth(int width) {
535        mDropDownWidth = width;
536    }
537
538    /**
539     * Sets the width of the popup window by the size of its content. The final width may be
540     * larger to accommodate styled window dressing.
541     *
542     * @param width Desired width of content in pixels.
543     */
544    public void setContentWidth(int width) {
545        Drawable popupBackground = mPopup.getBackground();
546        if (popupBackground != null) {
547            popupBackground.getPadding(mTempRect);
548            mDropDownWidth = mTempRect.left + mTempRect.right + width;
549        } else {
550            setWidth(width);
551        }
552    }
553
554    /**
555     * @return The height of the popup window in pixels.
556     */
557    public int getHeight() {
558        return mDropDownHeight;
559    }
560
561    /**
562     * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}.
563     *
564     * @param height Height of the popup window.
565     */
566    public void setHeight(int height) {
567        mDropDownHeight = height;
568    }
569
570    /**
571     * Set the layout type for this popup window.
572     * <p>
573     * See {@link WindowManager.LayoutParams#type} for possible values.
574     *
575     * @param layoutType Layout type for this window.
576     *
577     * @see WindowManager.LayoutParams#type
578     */
579    public void setWindowLayoutType(int layoutType) {
580        mDropDownWindowLayoutType = layoutType;
581    }
582
583    /**
584     * Sets a listener to receive events when a list item is clicked.
585     *
586     * @param clickListener Listener to register
587     *
588     * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
589     */
590    public void setOnItemClickListener(@Nullable AdapterView.OnItemClickListener clickListener) {
591        mItemClickListener = clickListener;
592    }
593
594    /**
595     * Sets a listener to receive events when a list item is selected.
596     *
597     * @param selectedListener Listener to register.
598     *
599     * @see ListView#setOnItemSelectedListener(OnItemSelectedListener)
600     */
601    public void setOnItemSelectedListener(@Nullable OnItemSelectedListener selectedListener) {
602        mItemSelectedListener = selectedListener;
603    }
604
605    /**
606     * Set a view to act as a user prompt for this popup window. Where the prompt view will appear
607     * is controlled by {@link #setPromptPosition(int)}.
608     *
609     * @param prompt View to use as an informational prompt.
610     */
611    public void setPromptView(@Nullable View prompt) {
612        boolean showing = isShowing();
613        if (showing) {
614            removePromptView();
615        }
616        mPromptView = prompt;
617        if (showing) {
618            show();
619        }
620    }
621
622    /**
623     * Post a {@link #show()} call to the UI thread.
624     */
625    public void postShow() {
626        mHandler.post(mShowDropDownRunnable);
627    }
628
629    /**
630     * Show the popup list. If the list is already showing, this method
631     * will recalculate the popup's size and position.
632     */
633    @Override
634    public void show() {
635        int height = buildDropDown();
636
637        final boolean noInputMethod = isInputMethodNotNeeded();
638        PopupWindowCompat.setWindowLayoutType(mPopup, mDropDownWindowLayoutType);
639
640        if (mPopup.isShowing()) {
641            final int widthSpec;
642            if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
643                // The call to PopupWindow's update method below can accept -1 for any
644                // value you do not want to update.
645                widthSpec = -1;
646            } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
647                widthSpec = getAnchorView().getWidth();
648            } else {
649                widthSpec = mDropDownWidth;
650            }
651
652            final int heightSpec;
653            if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
654                // The call to PopupWindow's update method below can accept -1 for any
655                // value you do not want to update.
656                heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
657                if (noInputMethod) {
658                    mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
659                            ViewGroup.LayoutParams.MATCH_PARENT : 0);
660                    mPopup.setHeight(0);
661                } else {
662                    mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
663                                    ViewGroup.LayoutParams.MATCH_PARENT : 0);
664                    mPopup.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
665                }
666            } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
667                heightSpec = height;
668            } else {
669                heightSpec = mDropDownHeight;
670            }
671
672            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
673
674            mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
675                            mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec,
676                            (heightSpec < 0)? -1 : heightSpec);
677        } else {
678            final int widthSpec;
679            if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
680                widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
681            } else {
682                if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
683                    widthSpec = getAnchorView().getWidth();
684                } else {
685                    widthSpec = mDropDownWidth;
686                }
687            }
688
689            final int heightSpec;
690            if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
691                heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
692            } else {
693                if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
694                    heightSpec = height;
695                } else {
696                    heightSpec = mDropDownHeight;
697                }
698            }
699
700            mPopup.setWidth(widthSpec);
701            mPopup.setHeight(heightSpec);
702            setPopupClipToScreenEnabled(true);
703
704            // use outside touchable to dismiss drop down when touching outside of it, so
705            // only set this if the dropdown is not always visible
706            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
707            mPopup.setTouchInterceptor(mTouchInterceptor);
708            if (sSetEpicenterBoundsMethod != null) {
709                try {
710                    sSetEpicenterBoundsMethod.invoke(mPopup, mEpicenterBounds);
711                } catch (Exception e) {
712                    Log.e(TAG, "Could not invoke setEpicenterBounds on PopupWindow", e);
713                }
714            }
715            PopupWindowCompat.showAsDropDown(mPopup, getAnchorView(), mDropDownHorizontalOffset,
716                    mDropDownVerticalOffset, mDropDownGravity);
717            mDropDownList.setSelection(ListView.INVALID_POSITION);
718
719            if (!mModal || mDropDownList.isInTouchMode()) {
720                clearListSelection();
721            }
722            if (!mModal) {
723                mHandler.post(mHideSelector);
724            }
725        }
726    }
727
728    /**
729     * Dismiss the popup window.
730     */
731    @Override
732    public void dismiss() {
733        mPopup.dismiss();
734        removePromptView();
735        mPopup.setContentView(null);
736        mDropDownList = null;
737        mHandler.removeCallbacks(mResizePopupRunnable);
738    }
739
740    /**
741     * Set a listener to receive a callback when the popup is dismissed.
742     *
743     * @param listener Listener that will be notified when the popup is dismissed.
744     */
745    public void setOnDismissListener(@Nullable PopupWindow.OnDismissListener listener) {
746        mPopup.setOnDismissListener(listener);
747    }
748
749    private void removePromptView() {
750        if (mPromptView != null) {
751            final ViewParent parent = mPromptView.getParent();
752            if (parent instanceof ViewGroup) {
753                final ViewGroup group = (ViewGroup) parent;
754                group.removeView(mPromptView);
755            }
756        }
757    }
758
759    /**
760     * Control how the popup operates with an input method: one of
761     * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
762     * or {@link #INPUT_METHOD_NOT_NEEDED}.
763     *
764     * <p>If the popup is showing, calling this method will take effect only
765     * the next time the popup is shown or through a manual call to the {@link #show()}
766     * method.</p>
767     *
768     * @see #getInputMethodMode()
769     * @see #show()
770     */
771    public void setInputMethodMode(int mode) {
772        mPopup.setInputMethodMode(mode);
773    }
774
775    /**
776     * Return the current value in {@link #setInputMethodMode(int)}.
777     *
778     * @see #setInputMethodMode(int)
779     */
780    public int getInputMethodMode() {
781        return mPopup.getInputMethodMode();
782    }
783
784    /**
785     * Set the selected position of the list.
786     * Only valid when {@link #isShowing()} == {@code true}.
787     *
788     * @param position List position to set as selected.
789     */
790    public void setSelection(int position) {
791        DropDownListView list = mDropDownList;
792        if (isShowing() && list != null) {
793            list.setListSelectionHidden(false);
794            list.setSelection(position);
795
796            if (Build.VERSION.SDK_INT >= 11) {
797                if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
798                    list.setItemChecked(position, true);
799                }
800            }
801        }
802    }
803
804    /**
805     * Clear any current list selection.
806     * Only valid when {@link #isShowing()} == {@code true}.
807     */
808    public void clearListSelection() {
809        final DropDownListView list = mDropDownList;
810        if (list != null) {
811            // WARNING: Please read the comment where mListSelectionHidden is declared
812            list.setListSelectionHidden(true);
813            //list.hideSelector();
814            list.requestLayout();
815        }
816    }
817
818    /**
819     * @return {@code true} if the popup is currently showing, {@code false} otherwise.
820     */
821    @Override
822    public boolean isShowing() {
823        return mPopup.isShowing();
824    }
825
826    /**
827     * @return {@code true} if this popup is configured to assume the user does not need
828     * to interact with the IME while it is showing, {@code false} otherwise.
829     */
830    public boolean isInputMethodNotNeeded() {
831        return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED;
832    }
833
834    /**
835     * Perform an item click operation on the specified list adapter position.
836     *
837     * @param position Adapter position for performing the click
838     * @return true if the click action could be performed, false if not.
839     *         (e.g. if the popup was not showing, this method would return false.)
840     */
841    public boolean performItemClick(int position) {
842        if (isShowing()) {
843            if (mItemClickListener != null) {
844                final DropDownListView list = mDropDownList;
845                final View child = list.getChildAt(position - list.getFirstVisiblePosition());
846                final ListAdapter adapter = list.getAdapter();
847                mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position));
848            }
849            return true;
850        }
851        return false;
852    }
853
854    /**
855     * @return The currently selected item or null if the popup is not showing.
856     */
857    public @Nullable Object getSelectedItem() {
858        if (!isShowing()) {
859            return null;
860        }
861        return mDropDownList.getSelectedItem();
862    }
863
864    /**
865     * @return The position of the currently selected item or {@link ListView#INVALID_POSITION}
866     * if {@link #isShowing()} == {@code false}.
867     *
868     * @see ListView#getSelectedItemPosition()
869     */
870    public int getSelectedItemPosition() {
871        if (!isShowing()) {
872            return ListView.INVALID_POSITION;
873        }
874        return mDropDownList.getSelectedItemPosition();
875    }
876
877    /**
878     * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID}
879     * if {@link #isShowing()} == {@code false}.
880     *
881     * @see ListView#getSelectedItemId()
882     */
883    public long getSelectedItemId() {
884        if (!isShowing()) {
885            return ListView.INVALID_ROW_ID;
886        }
887        return mDropDownList.getSelectedItemId();
888    }
889
890    /**
891     * @return The View for the currently selected item or null if
892     * {@link #isShowing()} == {@code false}.
893     *
894     * @see ListView#getSelectedView()
895     */
896    public @Nullable View getSelectedView() {
897        if (!isShowing()) {
898            return null;
899        }
900        return mDropDownList.getSelectedView();
901    }
902
903    /**
904     * @return The {@link ListView} displayed within the popup window.
905     * Only valid when {@link #isShowing()} == {@code true}.
906     */
907    @Override
908    public @Nullable ListView getListView() {
909        return mDropDownList;
910    }
911
912    @NonNull DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
913        return new DropDownListView(context, hijackFocus);
914    }
915
916    /**
917     * The maximum number of list items that can be visible and still have
918     * the list expand when touched.
919     *
920     * @param max Max number of items that can be visible and still allow the list to expand.
921     */
922    void setListItemExpandMax(int max) {
923        mListItemExpandMaximum = max;
924    }
925
926    /**
927     * Filter key down events. By forwarding key down events to this function,
928     * views using non-modal ListPopupWindow can have it handle key selection of items.
929     *
930     * @param keyCode keyCode param passed to the host view's onKeyDown
931     * @param event event param passed to the host view's onKeyDown
932     * @return true if the event was handled, false if it was ignored.
933     *
934     * @see #setModal(boolean)
935     */
936    public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
937        // when the drop down is shown, we drive it directly
938        if (isShowing()) {
939            // the key events are forwarded to the list in the drop down view
940            // note that ListView handles space but we don't want that to happen
941            // also if selection is not currently in the drop down, then don't
942            // let center or enter presses go there since that would cause it
943            // to select one of its items
944            if (keyCode != KeyEvent.KEYCODE_SPACE
945                    && (mDropDownList.getSelectedItemPosition() >= 0
946                    || !isConfirmKey(keyCode))) {
947                int curIndex = mDropDownList.getSelectedItemPosition();
948                boolean consumed;
949
950                final boolean below = !mPopup.isAboveAnchor();
951
952                final ListAdapter adapter = mAdapter;
953
954                boolean allEnabled;
955                int firstItem = Integer.MAX_VALUE;
956                int lastItem = Integer.MIN_VALUE;
957
958                if (adapter != null) {
959                    allEnabled = adapter.areAllItemsEnabled();
960                    firstItem = allEnabled ? 0 :
961                            mDropDownList.lookForSelectablePosition(0, true);
962                    lastItem = allEnabled ? adapter.getCount() - 1 :
963                            mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
964                }
965
966                if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
967                        (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
968                    // When the selection is at the top, we block the key
969                    // event to prevent focus from moving.
970                    clearListSelection();
971                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
972                    show();
973                    return true;
974                } else {
975                    // WARNING: Please read the comment where mListSelectionHidden
976                    //          is declared
977                    mDropDownList.setListSelectionHidden(false);
978                }
979
980                consumed = mDropDownList.onKeyDown(keyCode, event);
981                if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
982
983                if (consumed) {
984                    // If it handled the key event, then the user is
985                    // navigating in the list, so we should put it in front.
986                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
987                    // Here's a little trick we need to do to make sure that
988                    // the list view is actually showing its focus indicator,
989                    // by ensuring it has focus and getting its window out
990                    // of touch mode.
991                    mDropDownList.requestFocusFromTouch();
992                    show();
993
994                    switch (keyCode) {
995                        // avoid passing the focus from the text view to the
996                        // next component
997                        case KeyEvent.KEYCODE_ENTER:
998                        case KeyEvent.KEYCODE_DPAD_CENTER:
999                        case KeyEvent.KEYCODE_DPAD_DOWN:
1000                        case KeyEvent.KEYCODE_DPAD_UP:
1001                            return true;
1002                    }
1003                } else {
1004                    if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
1005                        // when the selection is at the bottom, we block the
1006                        // event to avoid going to the next focusable widget
1007                        if (curIndex == lastItem) {
1008                            return true;
1009                        }
1010                    } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP &&
1011                            curIndex == firstItem) {
1012                        return true;
1013                    }
1014                }
1015            }
1016        }
1017
1018        return false;
1019    }
1020
1021    /**
1022     * Filter key down events. By forwarding key up events to this function,
1023     * views using non-modal ListPopupWindow can have it handle key selection of items.
1024     *
1025     * @param keyCode keyCode param passed to the host view's onKeyUp
1026     * @param event event param passed to the host view's onKeyUp
1027     * @return true if the event was handled, false if it was ignored.
1028     *
1029     * @see #setModal(boolean)
1030     */
1031    public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
1032        if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
1033            boolean consumed = mDropDownList.onKeyUp(keyCode, event);
1034            if (consumed && isConfirmKey(keyCode)) {
1035                // if the list accepts the key events and the key event was a click, the text view
1036                // gets the selected item from the drop down as its content
1037                dismiss();
1038            }
1039            return consumed;
1040        }
1041        return false;
1042    }
1043
1044    /**
1045     * Filter pre-IME key events. By forwarding {@link View#onKeyPreIme(int, KeyEvent)}
1046     * events to this function, views using ListPopupWindow can have it dismiss the popup
1047     * when the back key is pressed.
1048     *
1049     * @param keyCode keyCode param passed to the host view's onKeyPreIme
1050     * @param event event param passed to the host view's onKeyPreIme
1051     * @return true if the event was handled, false if it was ignored.
1052     *
1053     * @see #setModal(boolean)
1054     */
1055    public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) {
1056        if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) {
1057            // special case for the back key, we do not even try to send it
1058            // to the drop down list but instead, consume it immediately
1059            final View anchorView = mDropDownAnchorView;
1060            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
1061                KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
1062                if (state != null) {
1063                    state.startTracking(event, this);
1064                }
1065                return true;
1066            } else if (event.getAction() == KeyEvent.ACTION_UP) {
1067                KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
1068                if (state != null) {
1069                    state.handleUpEvent(event);
1070                }
1071                if (event.isTracking() && !event.isCanceled()) {
1072                    dismiss();
1073                    return true;
1074                }
1075            }
1076        }
1077        return false;
1078    }
1079
1080    /**
1081     * Returns an {@link OnTouchListener} that can be added to the source view
1082     * to implement drag-to-open behavior. Generally, the source view should be
1083     * the same view that was passed to {@link #setAnchorView}.
1084     * <p>
1085     * When the listener is set on a view, touching that view and dragging
1086     * outside of its bounds will open the popup window. Lifting will select the
1087     * currently touched list item.
1088     * <p>
1089     * Example usage:
1090     * <pre>
1091     * ListPopupWindow myPopup = new ListPopupWindow(context);
1092     * myPopup.setAnchor(myAnchor);
1093     * OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor);
1094     * myAnchor.setOnTouchListener(dragListener);
1095     * </pre>
1096     *
1097     * @param src the view on which the resulting listener will be set
1098     * @return a touch listener that controls drag-to-open behavior
1099     */
1100    public OnTouchListener createDragToOpenListener(View src) {
1101        return new ForwardingListener(src) {
1102            @Override
1103            public ListPopupWindow getPopup() {
1104                return ListPopupWindow.this;
1105            }
1106        };
1107    }
1108
1109    /**
1110     * <p>Builds the popup window's content and returns the height the popup
1111     * should have. Returns -1 when the content already exists.</p>
1112     *
1113     * @return the content's height or -1 if content already exists
1114     */
1115    private int buildDropDown() {
1116        ViewGroup dropDownView;
1117        int otherHeights = 0;
1118
1119        if (mDropDownList == null) {
1120            Context context = mContext;
1121
1122            /**
1123             * This Runnable exists for the sole purpose of checking if the view layout has got
1124             * completed and if so call showDropDown to display the drop down. This is used to show
1125             * the drop down as soon as possible after user opens up the search dialog, without
1126             * waiting for the normal UI pipeline to do it's job which is slower than this method.
1127             */
1128            mShowDropDownRunnable = new Runnable() {
1129                public void run() {
1130                    // View layout should be all done before displaying the drop down.
1131                    View view = getAnchorView();
1132                    if (view != null && view.getWindowToken() != null) {
1133                        show();
1134                    }
1135                }
1136            };
1137
1138            mDropDownList = createDropDownListView(context, !mModal);
1139            if (mDropDownListHighlight != null) {
1140                mDropDownList.setSelector(mDropDownListHighlight);
1141            }
1142            mDropDownList.setAdapter(mAdapter);
1143            mDropDownList.setOnItemClickListener(mItemClickListener);
1144            mDropDownList.setFocusable(true);
1145            mDropDownList.setFocusableInTouchMode(true);
1146            mDropDownList.setOnItemSelectedListener(new OnItemSelectedListener() {
1147                public void onItemSelected(AdapterView<?> parent, View view,
1148                        int position, long id) {
1149
1150                    if (position != -1) {
1151                        DropDownListView dropDownList = mDropDownList;
1152
1153                        if (dropDownList != null) {
1154                            dropDownList.setListSelectionHidden(false);
1155                        }
1156                    }
1157                }
1158
1159                public void onNothingSelected(AdapterView<?> parent) {
1160                }
1161            });
1162            mDropDownList.setOnScrollListener(mScrollListener);
1163
1164            if (mItemSelectedListener != null) {
1165                mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
1166            }
1167
1168            dropDownView = mDropDownList;
1169
1170            View hintView = mPromptView;
1171            if (hintView != null) {
1172                // if a hint has been specified, we accomodate more space for it and
1173                // add a text view in the drop down menu, at the bottom of the list
1174                LinearLayout hintContainer = new LinearLayout(context);
1175                hintContainer.setOrientation(LinearLayout.VERTICAL);
1176
1177                LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
1178                        ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
1179                );
1180
1181                switch (mPromptPosition) {
1182                    case POSITION_PROMPT_BELOW:
1183                        hintContainer.addView(dropDownView, hintParams);
1184                        hintContainer.addView(hintView);
1185                        break;
1186
1187                    case POSITION_PROMPT_ABOVE:
1188                        hintContainer.addView(hintView);
1189                        hintContainer.addView(dropDownView, hintParams);
1190                        break;
1191
1192                    default:
1193                        Log.e(TAG, "Invalid hint position " + mPromptPosition);
1194                        break;
1195                }
1196
1197                // Measure the hint's height to find how much more vertical
1198                // space we need to add to the drop down's height.
1199                final int widthSize;
1200                final int widthMode;
1201                if (mDropDownWidth >= 0) {
1202                    widthMode = MeasureSpec.AT_MOST;
1203                    widthSize = mDropDownWidth;
1204                } else {
1205                    widthMode = MeasureSpec.UNSPECIFIED;
1206                    widthSize = 0;
1207                }
1208                final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
1209                final int heightSpec = MeasureSpec.UNSPECIFIED;
1210                hintView.measure(widthSpec, heightSpec);
1211
1212                hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
1213                otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
1214                        + hintParams.bottomMargin;
1215
1216                dropDownView = hintContainer;
1217            }
1218
1219            mPopup.setContentView(dropDownView);
1220        } else {
1221            dropDownView = (ViewGroup) mPopup.getContentView();
1222            final View view = mPromptView;
1223            if (view != null) {
1224                LinearLayout.LayoutParams hintParams =
1225                        (LinearLayout.LayoutParams) view.getLayoutParams();
1226                otherHeights = view.getMeasuredHeight() + hintParams.topMargin
1227                        + hintParams.bottomMargin;
1228            }
1229        }
1230
1231        // getMaxAvailableHeight() subtracts the padding, so we put it back
1232        // to get the available height for the whole window.
1233        final int padding;
1234        final Drawable background = mPopup.getBackground();
1235        if (background != null) {
1236            background.getPadding(mTempRect);
1237            padding = mTempRect.top + mTempRect.bottom;
1238
1239            // If we don't have an explicit vertical offset, determine one from
1240            // the window background so that content will line up.
1241            if (!mDropDownVerticalOffsetSet) {
1242                mDropDownVerticalOffset = -mTempRect.top;
1243            }
1244        } else {
1245            mTempRect.setEmpty();
1246            padding = 0;
1247        }
1248
1249        // Max height available on the screen for a popup.
1250        final boolean ignoreBottomDecorations =
1251                mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
1252        final int maxHeight = getMaxAvailableHeight(getAnchorView(), mDropDownVerticalOffset,
1253                ignoreBottomDecorations);
1254        if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
1255            return maxHeight + padding;
1256        }
1257
1258        final int childWidthSpec;
1259        switch (mDropDownWidth) {
1260            case ViewGroup.LayoutParams.WRAP_CONTENT:
1261                childWidthSpec = MeasureSpec.makeMeasureSpec(
1262                        mContext.getResources().getDisplayMetrics().widthPixels
1263                                - (mTempRect.left + mTempRect.right),
1264                        MeasureSpec.AT_MOST);
1265                break;
1266            case ViewGroup.LayoutParams.MATCH_PARENT:
1267                childWidthSpec = MeasureSpec.makeMeasureSpec(
1268                        mContext.getResources().getDisplayMetrics().widthPixels
1269                                - (mTempRect.left + mTempRect.right),
1270                        MeasureSpec.EXACTLY);
1271                break;
1272            default:
1273                childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY);
1274                break;
1275        }
1276
1277        // Add padding only if the list has items in it, that way we don't show
1278        // the popup if it is not needed.
1279        final int listContent = mDropDownList.measureHeightOfChildrenCompat(childWidthSpec,
1280                0, DropDownListView.NO_POSITION, maxHeight - otherHeights, -1);
1281        if (listContent > 0) {
1282            final int listPadding = mDropDownList.getPaddingTop()
1283                    + mDropDownList.getPaddingBottom();
1284            otherHeights += padding + listPadding;
1285        }
1286
1287        return listContent + otherHeights;
1288    }
1289
1290    private class PopupDataSetObserver extends DataSetObserver {
1291        @Override
1292        public void onChanged() {
1293            if (isShowing()) {
1294                // Resize the popup to fit new content
1295                show();
1296            }
1297        }
1298
1299        @Override
1300        public void onInvalidated() {
1301            dismiss();
1302        }
1303    }
1304
1305    private class ListSelectorHider implements Runnable {
1306        public void run() {
1307            clearListSelection();
1308        }
1309    }
1310
1311    private class ResizePopupRunnable implements Runnable {
1312        public void run() {
1313            if (mDropDownList != null && ViewCompat.isAttachedToWindow(mDropDownList)
1314                    && mDropDownList.getCount() > mDropDownList.getChildCount()
1315                    && mDropDownList.getChildCount() <= mListItemExpandMaximum) {
1316                mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
1317                show();
1318            }
1319        }
1320    }
1321
1322    private class PopupTouchInterceptor implements OnTouchListener {
1323        public boolean onTouch(View v, MotionEvent event) {
1324            final int action = event.getAction();
1325            final int x = (int) event.getX();
1326            final int y = (int) event.getY();
1327
1328            if (action == MotionEvent.ACTION_DOWN &&
1329                    mPopup != null && mPopup.isShowing() &&
1330                    (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) {
1331                mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
1332            } else if (action == MotionEvent.ACTION_UP) {
1333                mHandler.removeCallbacks(mResizePopupRunnable);
1334            }
1335            return false;
1336        }
1337    }
1338
1339    private class PopupScrollListener implements ListView.OnScrollListener {
1340        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1341                int totalItemCount) {
1342
1343        }
1344
1345        public void onScrollStateChanged(AbsListView view, int scrollState) {
1346            if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
1347                    !isInputMethodNotNeeded() && mPopup.getContentView() != null) {
1348                mHandler.removeCallbacks(mResizePopupRunnable);
1349                mResizePopupRunnable.run();
1350            }
1351        }
1352    }
1353
1354    private static boolean isConfirmKey(int keyCode) {
1355        return keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_CENTER;
1356    }
1357
1358    private void setPopupClipToScreenEnabled(boolean clip) {
1359        if (sClipToWindowEnabledMethod != null) {
1360            try {
1361                sClipToWindowEnabledMethod.invoke(mPopup, clip);
1362            } catch (Exception e) {
1363                Log.i(TAG, "Could not call setClipToScreenEnabled() on PopupWindow. Oh well.");
1364            }
1365        }
1366    }
1367
1368    private int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) {
1369        if (sGetMaxAvailableHeightMethod != null) {
1370            try {
1371                return (int) sGetMaxAvailableHeightMethod.invoke(mPopup, anchor, yOffset,
1372                        ignoreBottomDecorations);
1373            } catch (Exception e) {
1374                Log.i(TAG, "Could not call getMaxAvailableHeightMethod(View, int, boolean)"
1375                        + " on PopupWindow. Using the public version.");
1376            }
1377        }
1378        return mPopup.getMaxAvailableHeight(anchor, yOffset);
1379    }
1380}