PopupWindow.java revision 01ce2e9eee41cc0c24b0d16465710a28ea337d5d
1/*
2 * Copyright (C) 2007 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.widget;
18
19import com.android.internal.R;
20
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.view.KeyEvent;
24import android.view.MotionEvent;
25import android.view.View;
26import android.view.WindowManager;
27import android.view.Gravity;
28import android.view.ViewGroup;
29import android.view.ViewTreeObserver;
30import android.view.ViewTreeObserver.OnScrollChangedListener;
31import android.view.View.OnTouchListener;
32import android.graphics.PixelFormat;
33import android.graphics.Rect;
34import android.graphics.drawable.Drawable;
35import android.graphics.drawable.StateListDrawable;
36import android.os.IBinder;
37import android.util.AttributeSet;
38
39import java.lang.ref.WeakReference;
40
41/**
42 * <p>A popup window that can be used to display an arbitrary view. The popup
43 * windows is a floating container that appears on top of the current
44 * activity.</p>
45 *
46 * @see android.widget.AutoCompleteTextView
47 * @see android.widget.Spinner
48 */
49public class PopupWindow {
50    /**
51     * Mode for {@link #setInputMethodMode(int)}: the requirements for the
52     * input method should be based on the focusability of the popup.  That is
53     * if it is focusable than it needs to work with the input method, else
54     * it doesn't.
55     */
56    public static final int INPUT_METHOD_FROM_FOCUSABLE = 0;
57
58    /**
59     * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
60     * work with an input method, regardless of whether it is focusable.  This
61     * means that it will always be displayed so that the user can also operate
62     * the input method while it is shown.
63     */
64    public static final int INPUT_METHOD_NEEDED = 1;
65
66    /**
67     * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
68     * work with an input method, regardless of whether it is focusable.  This
69     * means that it will always be displayed to use as much space on the
70     * screen as needed, regardless of whether this covers the input method.
71     */
72    public static final int INPUT_METHOD_NOT_NEEDED = 2;
73
74    private Context mContext;
75    private WindowManager mWindowManager;
76
77    private boolean mIsShowing;
78    private boolean mIsDropdown;
79
80    private View mContentView;
81    private View mPopupView;
82    private boolean mFocusable;
83    private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE;
84    private int mSoftInputMode;
85    private boolean mTouchable = true;
86    private boolean mOutsideTouchable = false;
87    private boolean mClippingEnabled = true;
88    private boolean mSplitTouchEnabled;
89
90    private OnTouchListener mTouchInterceptor;
91
92    private int mWidthMode;
93    private int mWidth;
94    private int mLastWidth;
95    private int mHeightMode;
96    private int mHeight;
97    private int mLastHeight;
98
99    private int mPopupWidth;
100    private int mPopupHeight;
101
102    private int[] mDrawingLocation = new int[2];
103    private int[] mScreenLocation = new int[2];
104    private Rect mTempRect = new Rect();
105
106    private Drawable mBackground;
107    private Drawable mAboveAnchorBackgroundDrawable;
108    private Drawable mBelowAnchorBackgroundDrawable;
109
110    private boolean mAboveAnchor;
111
112    private OnDismissListener mOnDismissListener;
113    private boolean mIgnoreCheekPress = false;
114
115    private int mAnimationStyle = -1;
116
117    private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
118        com.android.internal.R.attr.state_above_anchor
119    };
120
121    private WeakReference<View> mAnchor;
122    private OnScrollChangedListener mOnScrollChangedListener =
123        new OnScrollChangedListener() {
124            public void onScrollChanged() {
125                View anchor = mAnchor.get();
126                if (anchor != null && mPopupView != null) {
127                    WindowManager.LayoutParams p = (WindowManager.LayoutParams)
128                            mPopupView.getLayoutParams();
129
130                    updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff));
131                    update(p.x, p.y, -1, -1, true);
132                }
133            }
134        };
135    private int mAnchorXoff, mAnchorYoff;
136
137    /**
138     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
139     *
140     * <p>The popup does provide a background.</p>
141     */
142    public PopupWindow(Context context) {
143        this(context, null);
144    }
145
146    /**
147     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
148     *
149     * <p>The popup does provide a background.</p>
150     */
151    public PopupWindow(Context context, AttributeSet attrs) {
152        this(context, attrs, com.android.internal.R.attr.popupWindowStyle);
153    }
154
155    /**
156     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
157     *
158     * <p>The popup does provide a background.</p>
159     */
160    public PopupWindow(Context context, AttributeSet attrs, int defStyle) {
161        mContext = context;
162        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
163
164        TypedArray a =
165            context.obtainStyledAttributes(
166                attrs, com.android.internal.R.styleable.PopupWindow, defStyle, 0);
167
168        mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
169        mAnimationStyle = a.getResourceId(R.styleable.PopupWindow_windowAnimationStyle, -1);
170
171        // If this is a StateListDrawable, try to find and store the drawable to be
172        // used when the drop-down is placed above its anchor view, and the one to be
173        // used when the drop-down is placed below its anchor view. We extract
174        // the drawables ourselves to work around a problem with using refreshDrawableState
175        // that it will take into account the padding of all drawables specified in a
176        // StateListDrawable, thus adding superfluous padding to drop-down views.
177        //
178        // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and
179        // at least one other drawable, intended for the 'below-anchor state'.
180        if (mBackground instanceof StateListDrawable) {
181            StateListDrawable background = (StateListDrawable) mBackground;
182
183            // Find the above-anchor view - this one's easy, it should be labeled as such.
184            int aboveAnchorStateIndex = background.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET);
185
186            // Now, for the below-anchor view, look for any other drawable specified in the
187            // StateListDrawable which is not for the above-anchor state and use that.
188            int count = background.getStateCount();
189            int belowAnchorStateIndex = -1;
190            for (int i = 0; i < count; i++) {
191                if (i != aboveAnchorStateIndex) {
192                    belowAnchorStateIndex = i;
193                    break;
194                }
195            }
196
197            // Store the drawables we found, if we found them. Otherwise, set them both
198            // to null so that we'll just use refreshDrawableState.
199            if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) {
200                mAboveAnchorBackgroundDrawable = background.getStateDrawable(aboveAnchorStateIndex);
201                mBelowAnchorBackgroundDrawable = background.getStateDrawable(belowAnchorStateIndex);
202            } else {
203                mBelowAnchorBackgroundDrawable = null;
204                mAboveAnchorBackgroundDrawable = null;
205            }
206        }
207
208        a.recycle();
209    }
210
211    /**
212     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
213     *
214     * <p>The popup does not provide any background. This should be handled
215     * by the content view.</p>
216     */
217    public PopupWindow() {
218        this(null, 0, 0);
219    }
220
221    /**
222     * <p>Create a new non focusable popup window which can display the
223     * <tt>contentView</tt>. The dimension of the window are (0,0).</p>
224     *
225     * <p>The popup does not provide any background. This should be handled
226     * by the content view.</p>
227     *
228     * @param contentView the popup's content
229     */
230    public PopupWindow(View contentView) {
231        this(contentView, 0, 0);
232    }
233
234    /**
235     * <p>Create a new empty, non focusable popup window. The dimension of the
236     * window must be passed to this constructor.</p>
237     *
238     * <p>The popup does not provide any background. This should be handled
239     * by the content view.</p>
240     *
241     * @param width the popup's width
242     * @param height the popup's height
243     */
244    public PopupWindow(int width, int height) {
245        this(null, width, height);
246    }
247
248    /**
249     * <p>Create a new non focusable popup window which can display the
250     * <tt>contentView</tt>. The dimension of the window must be passed to
251     * this constructor.</p>
252     *
253     * <p>The popup does not provide any background. This should be handled
254     * by the content view.</p>
255     *
256     * @param contentView the popup's content
257     * @param width the popup's width
258     * @param height the popup's height
259     */
260    public PopupWindow(View contentView, int width, int height) {
261        this(contentView, width, height, false);
262    }
263
264    /**
265     * <p>Create a new popup window which can display the <tt>contentView</tt>.
266     * The dimension of the window must be passed to this constructor.</p>
267     *
268     * <p>The popup does not provide any background. This should be handled
269     * by the content view.</p>
270     *
271     * @param contentView the popup's content
272     * @param width the popup's width
273     * @param height the popup's height
274     * @param focusable true if the popup can be focused, false otherwise
275     */
276    public PopupWindow(View contentView, int width, int height, boolean focusable) {
277        if (contentView != null) {
278            mContext = contentView.getContext();
279            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
280        }
281        setContentView(contentView);
282        setWidth(width);
283        setHeight(height);
284        setFocusable(focusable);
285    }
286
287    /**
288     * <p>Return the drawable used as the popup window's background.</p>
289     *
290     * @return the background drawable or null
291     */
292    public Drawable getBackground() {
293        return mBackground;
294    }
295
296    /**
297     * <p>Change the background drawable for this popup window. The background
298     * can be set to null.</p>
299     *
300     * @param background the popup's background
301     */
302    public void setBackgroundDrawable(Drawable background) {
303        mBackground = background;
304    }
305
306    /**
307     * <p>Return the animation style to use the popup appears and disappears</p>
308     *
309     * @return the animation style to use the popup appears and disappears
310     */
311    public int getAnimationStyle() {
312        return mAnimationStyle;
313    }
314
315    /**
316     * Set the flag on popup to ignore cheek press eventt; by default this flag
317     * is set to false
318     * which means the pop wont ignore cheek press dispatch events.
319     *
320     * <p>If the popup is showing, calling this method will take effect only
321     * the next time the popup is shown or through a manual call to one of
322     * the {@link #update()} methods.</p>
323     *
324     * @see #update()
325     */
326    public void setIgnoreCheekPress() {
327        mIgnoreCheekPress = true;
328    }
329
330
331    /**
332     * <p>Change the animation style resource for this popup.</p>
333     *
334     * <p>If the popup is showing, calling this method will take effect only
335     * the next time the popup is shown or through a manual call to one of
336     * the {@link #update()} methods.</p>
337     *
338     * @param animationStyle animation style to use when the popup appears
339     *      and disappears.  Set to -1 for the default animation, 0 for no
340     *      animation, or a resource identifier for an explicit animation.
341     *
342     * @see #update()
343     */
344    public void setAnimationStyle(int animationStyle) {
345        mAnimationStyle = animationStyle;
346    }
347
348    /**
349     * <p>Return the view used as the content of the popup window.</p>
350     *
351     * @return a {@link android.view.View} representing the popup's content
352     *
353     * @see #setContentView(android.view.View)
354     */
355    public View getContentView() {
356        return mContentView;
357    }
358
359    /**
360     * <p>Change the popup's content. The content is represented by an instance
361     * of {@link android.view.View}.</p>
362     *
363     * <p>This method has no effect if called when the popup is showing.  To
364     * apply it while a popup is showing, call </p>
365     *
366     * @param contentView the new content for the popup
367     *
368     * @see #getContentView()
369     * @see #isShowing()
370     */
371    public void setContentView(View contentView) {
372        if (isShowing()) {
373            return;
374        }
375
376        mContentView = contentView;
377
378        if (mContext == null) {
379            mContext = mContentView.getContext();
380        }
381
382        if (mWindowManager == null) {
383            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
384        }
385    }
386
387    /**
388     * Set a callback for all touch events being dispatched to the popup
389     * window.
390     */
391    public void setTouchInterceptor(OnTouchListener l) {
392        mTouchInterceptor = l;
393    }
394
395    /**
396     * <p>Indicate whether the popup window can grab the focus.</p>
397     *
398     * @return true if the popup is focusable, false otherwise
399     *
400     * @see #setFocusable(boolean)
401     */
402    public boolean isFocusable() {
403        return mFocusable;
404    }
405
406    /**
407     * <p>Changes the focusability of the popup window. When focusable, the
408     * window will grab the focus from the current focused widget if the popup
409     * contains a focusable {@link android.view.View}.  By default a popup
410     * window is not focusable.</p>
411     *
412     * <p>If the popup is showing, calling this method will take effect only
413     * the next time the popup is shown or through a manual call to one of
414     * the {@link #update()} methods.</p>
415     *
416     * @param focusable true if the popup should grab focus, false otherwise.
417     *
418     * @see #isFocusable()
419     * @see #isShowing()
420     * @see #update()
421     */
422    public void setFocusable(boolean focusable) {
423        mFocusable = focusable;
424    }
425
426    /**
427     * Return the current value in {@link #setInputMethodMode(int)}.
428     *
429     * @see #setInputMethodMode(int)
430     */
431    public int getInputMethodMode() {
432        return mInputMethodMode;
433
434    }
435
436    /**
437     * Control how the popup operates with an input method: one of
438     * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
439     * or {@link #INPUT_METHOD_NOT_NEEDED}.
440     *
441     * <p>If the popup is showing, calling this method will take effect only
442     * the next time the popup is shown or through a manual call to one of
443     * the {@link #update()} methods.</p>
444     *
445     * @see #getInputMethodMode()
446     * @see #update()
447     */
448    public void setInputMethodMode(int mode) {
449        mInputMethodMode = mode;
450    }
451
452    /**
453     * Sets the operating mode for the soft input area.
454     *
455     * @param mode The desired mode, see
456     *        {@link android.view.WindowManager.LayoutParams#softInputMode}
457     *        for the full list
458     *
459     * @see android.view.WindowManager.LayoutParams#softInputMode
460     * @see #getSoftInputMode()
461     */
462    public void setSoftInputMode(int mode) {
463        mSoftInputMode = mode;
464    }
465
466    /**
467     * Returns the current value in {@link #setSoftInputMode(int)}.
468     *
469     * @see #setSoftInputMode(int)
470     * @see android.view.WindowManager.LayoutParams#softInputMode
471     */
472    public int getSoftInputMode() {
473        return mSoftInputMode;
474    }
475
476    /**
477     * <p>Indicates whether the popup window receives touch events.</p>
478     *
479     * @return true if the popup is touchable, false otherwise
480     *
481     * @see #setTouchable(boolean)
482     */
483    public boolean isTouchable() {
484        return mTouchable;
485    }
486
487    /**
488     * <p>Changes the touchability of the popup window. When touchable, the
489     * window will receive touch events, otherwise touch events will go to the
490     * window below it. By default the window is touchable.</p>
491     *
492     * <p>If the popup is showing, calling this method will take effect only
493     * the next time the popup is shown or through a manual call to one of
494     * the {@link #update()} methods.</p>
495     *
496     * @param touchable true if the popup should receive touch events, false otherwise
497     *
498     * @see #isTouchable()
499     * @see #isShowing()
500     * @see #update()
501     */
502    public void setTouchable(boolean touchable) {
503        mTouchable = touchable;
504    }
505
506    /**
507     * <p>Indicates whether the popup window will be informed of touch events
508     * outside of its window.</p>
509     *
510     * @return true if the popup is outside touchable, false otherwise
511     *
512     * @see #setOutsideTouchable(boolean)
513     */
514    public boolean isOutsideTouchable() {
515        return mOutsideTouchable;
516    }
517
518    /**
519     * <p>Controls whether the pop-up will be informed of touch events outside
520     * of its window.  This only makes sense for pop-ups that are touchable
521     * but not focusable, which means touches outside of the window will
522     * be delivered to the window behind.  The default is false.</p>
523     *
524     * <p>If the popup is showing, calling this method will take effect only
525     * the next time the popup is shown or through a manual call to one of
526     * the {@link #update()} methods.</p>
527     *
528     * @param touchable true if the popup should receive outside
529     * touch events, false otherwise
530     *
531     * @see #isOutsideTouchable()
532     * @see #isShowing()
533     * @see #update()
534     */
535    public void setOutsideTouchable(boolean touchable) {
536        mOutsideTouchable = touchable;
537    }
538
539    /**
540     * <p>Indicates whether clipping of the popup window is enabled.</p>
541     *
542     * @return true if the clipping is enabled, false otherwise
543     *
544     * @see #setClippingEnabled(boolean)
545     */
546    public boolean isClippingEnabled() {
547        return mClippingEnabled;
548    }
549
550    /**
551     * <p>Allows the popup window to extend beyond the bounds of the screen. By default the
552     * window is clipped to the screen boundaries. Setting this to false will allow windows to be
553     * accurately positioned.</p>
554     *
555     * <p>If the popup is showing, calling this method will take effect only
556     * the next time the popup is shown or through a manual call to one of
557     * the {@link #update()} methods.</p>
558     *
559     * @param enabled false if the window should be allowed to extend outside of the screen
560     * @see #isShowing()
561     * @see #isClippingEnabled()
562     * @see #update()
563     */
564    public void setClippingEnabled(boolean enabled) {
565        mClippingEnabled = enabled;
566    }
567
568    /**
569     * <p>Indicates whether the popup window supports splitting touches.</p>
570     *
571     * @return true if the touch splitting is enabled, false otherwise
572     *
573     * @see #setSplitTouchEnabled(boolean)
574     * @hide
575     */
576    public boolean isSplitTouchEnabled() {
577        return mSplitTouchEnabled;
578    }
579
580    /**
581     * <p>Allows the popup window to split touches across other windows that also
582     * support split touch.  When this flag is not set, the first pointer
583     * that goes down determines the window to which all subsequent touches
584     * go until all pointers go up.  When this flag is set, each pointer
585     * (not necessarily the first) that goes down determines the window
586     * to which all subsequent touches of that pointer will go until that
587     * pointer goes up thereby enabling touches with multiple pointers
588     * to be split across multiple windows.</p>
589     *
590     * @param enabled true if the split touches should be enabled, false otherwise
591     * @see #isSplitTouchEnabled()
592     * @hide
593     */
594    public void setSplitTouchEnabled(boolean enabled) {
595        mSplitTouchEnabled = enabled;
596    }
597
598    /**
599     * <p>Change the width and height measure specs that are given to the
600     * window manager by the popup.  By default these are 0, meaning that
601     * the current width or height is requested as an explicit size from
602     * the window manager.  You can supply
603     * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
604     * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure
605     * spec supplied instead, replacing the absolute width and height that
606     * has been set in the popup.</p>
607     *
608     * <p>If the popup is showing, calling this method will take effect only
609     * the next time the popup is shown.</p>
610     *
611     * @param widthSpec an explicit width measure spec mode, either
612     * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
613     * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
614     * width.
615     * @param heightSpec an explicit height measure spec mode, either
616     * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
617     * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
618     * height.
619     */
620    public void setWindowLayoutMode(int widthSpec, int heightSpec) {
621        mWidthMode = widthSpec;
622        mHeightMode = heightSpec;
623    }
624
625    /**
626     * <p>Return this popup's height MeasureSpec</p>
627     *
628     * @return the height MeasureSpec of the popup
629     *
630     * @see #setHeight(int)
631     */
632    public int getHeight() {
633        return mHeight;
634    }
635
636    /**
637     * <p>Change the popup's height MeasureSpec</p>
638     *
639     * <p>If the popup is showing, calling this method will take effect only
640     * the next time the popup is shown.</p>
641     *
642     * @param height the height MeasureSpec of the popup
643     *
644     * @see #getHeight()
645     * @see #isShowing()
646     */
647    public void setHeight(int height) {
648        mHeight = height;
649    }
650
651    /**
652     * <p>Return this popup's width MeasureSpec</p>
653     *
654     * @return the width MeasureSpec of the popup
655     *
656     * @see #setWidth(int)
657     */
658    public int getWidth() {
659        return mWidth;
660    }
661
662    /**
663     * <p>Change the popup's width MeasureSpec</p>
664     *
665     * <p>If the popup is showing, calling this method will take effect only
666     * the next time the popup is shown.</p>
667     *
668     * @param width the width MeasureSpec of the popup
669     *
670     * @see #getWidth()
671     * @see #isShowing()
672     */
673    public void setWidth(int width) {
674        mWidth = width;
675    }
676
677    /**
678     * <p>Indicate whether this popup window is showing on screen.</p>
679     *
680     * @return true if the popup is showing, false otherwise
681     */
682    public boolean isShowing() {
683        return mIsShowing;
684    }
685
686    /**
687     * <p>
688     * Display the content view in a popup window at the specified location. If the popup window
689     * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams}
690     * for more information on how gravity and the x and y parameters are related. Specifying
691     * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying
692     * <code>Gravity.LEFT | Gravity.TOP</code>.
693     * </p>
694     *
695     * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
696     * @param gravity the gravity which controls the placement of the popup window
697     * @param x the popup's x location offset
698     * @param y the popup's y location offset
699     */
700    public void showAtLocation(View parent, int gravity, int x, int y) {
701        if (isShowing() || mContentView == null) {
702            return;
703        }
704
705        unregisterForScrollChanged();
706
707        mIsShowing = true;
708        mIsDropdown = false;
709
710        WindowManager.LayoutParams p = createPopupLayout(parent.getWindowToken());
711        p.windowAnimations = computeAnimationResource();
712
713        preparePopup(p);
714        if (gravity == Gravity.NO_GRAVITY) {
715            gravity = Gravity.TOP | Gravity.LEFT;
716        }
717        p.gravity = gravity;
718        p.x = x;
719        p.y = y;
720        invokePopup(p);
721    }
722
723    /**
724     * <p>Display the content view in a popup window anchored to the bottom-left
725     * corner of the anchor view. If there is not enough room on screen to show
726     * the popup in its entirety, this method tries to find a parent scroll
727     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
728     * corner of the popup is pinned at the top left corner of the anchor view.</p>
729     *
730     * @param anchor the view on which to pin the popup window
731     *
732     * @see #dismiss()
733     */
734    public void showAsDropDown(View anchor) {
735        showAsDropDown(anchor, 0, 0);
736    }
737
738    /**
739     * <p>Display the content view in a popup window anchored to the bottom-left
740     * corner of the anchor view offset by the specified x and y coordinates.
741     * If there is not enough room on screen to show
742     * the popup in its entirety, this method tries to find a parent scroll
743     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
744     * corner of the popup is pinned at the top left corner of the anchor view.</p>
745     * <p>If the view later scrolls to move <code>anchor</code> to a different
746     * location, the popup will be moved correspondingly.</p>
747     *
748     * @param anchor the view on which to pin the popup window
749     *
750     * @see #dismiss()
751     */
752    public void showAsDropDown(View anchor, int xoff, int yoff) {
753        if (isShowing() || mContentView == null) {
754            return;
755        }
756
757        registerForScrollChanged(anchor, xoff, yoff);
758
759        mIsShowing = true;
760        mIsDropdown = true;
761
762        WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
763        preparePopup(p);
764
765        updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff));
766
767        if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
768        if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
769
770        p.windowAnimations = computeAnimationResource();
771
772        invokePopup(p);
773    }
774
775    private void updateAboveAnchor(boolean aboveAnchor) {
776        if (aboveAnchor != mAboveAnchor) {
777            mAboveAnchor = aboveAnchor;
778
779            if (mBackground != null) {
780                // If the background drawable provided was a StateListDrawable with above-anchor
781                // and below-anchor states, use those. Otherwise rely on refreshDrawableState to
782                // do the job.
783                if (mAboveAnchorBackgroundDrawable != null) {
784                    if (mAboveAnchor) {
785                        mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable);
786                    } else {
787                        mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable);
788                    }
789                } else {
790                    mPopupView.refreshDrawableState();
791                }
792            }
793        }
794    }
795
796    /**
797     * Indicates whether the popup is showing above (the y coordinate of the popup's bottom
798     * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate
799     * of the popup is greater than y coordinate of the anchor's bottom).
800     *
801     * The value returned
802     * by this method is meaningful only after {@link #showAsDropDown(android.view.View)}
803     * or {@link #showAsDropDown(android.view.View, int, int)} was invoked.
804     *
805     * @return True if this popup is showing above the anchor view, false otherwise.
806     */
807    public boolean isAboveAnchor() {
808        return mAboveAnchor;
809    }
810
811    /**
812     * <p>Prepare the popup by embedding in into a new ViewGroup if the
813     * background drawable is not null. If embedding is required, the layout
814     * parameters' height is mnodified to take into account the background's
815     * padding.</p>
816     *
817     * @param p the layout parameters of the popup's content view
818     */
819    private void preparePopup(WindowManager.LayoutParams p) {
820        if (mContentView == null || mContext == null || mWindowManager == null) {
821            throw new IllegalStateException("You must specify a valid content view by "
822                    + "calling setContentView() before attempting to show the popup.");
823        }
824
825        if (mBackground != null) {
826            final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
827            int height = ViewGroup.LayoutParams.MATCH_PARENT;
828            if (layoutParams != null &&
829                    layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
830                height = ViewGroup.LayoutParams.WRAP_CONTENT;
831            }
832
833            // when a background is available, we embed the content view
834            // within another view that owns the background drawable
835            PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
836            PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
837                    ViewGroup.LayoutParams.MATCH_PARENT, height
838            );
839            popupViewContainer.setBackgroundDrawable(mBackground);
840            popupViewContainer.addView(mContentView, listParams);
841
842            mPopupView = popupViewContainer;
843        } else {
844            mPopupView = mContentView;
845        }
846        mPopupWidth = p.width;
847        mPopupHeight = p.height;
848    }
849
850    /**
851     * <p>Invoke the popup window by adding the content view to the window
852     * manager.</p>
853     *
854     * <p>The content view must be non-null when this method is invoked.</p>
855     *
856     * @param p the layout parameters of the popup's content view
857     */
858    private void invokePopup(WindowManager.LayoutParams p) {
859        p.packageName = mContext.getPackageName();
860        mWindowManager.addView(mPopupView, p);
861    }
862
863    /**
864     * <p>Generate the layout parameters for the popup window.</p>
865     *
866     * @param token the window token used to bind the popup's window
867     *
868     * @return the layout parameters to pass to the window manager
869     */
870    private WindowManager.LayoutParams createPopupLayout(IBinder token) {
871        // generates the layout parameters for the drop down
872        // we want a fixed size view located at the bottom left of the anchor
873        WindowManager.LayoutParams p = new WindowManager.LayoutParams();
874        // these gravity settings put the view at the top left corner of the
875        // screen. The view is then positioned to the appropriate location
876        // by setting the x and y offsets to match the anchor's bottom
877        // left corner
878        p.gravity = Gravity.LEFT | Gravity.TOP;
879        p.width = mLastWidth = mWidth;
880        p.height = mLastHeight = mHeight;
881        if (mBackground != null) {
882            p.format = mBackground.getOpacity();
883        } else {
884            p.format = PixelFormat.TRANSLUCENT;
885        }
886        p.flags = computeFlags(p.flags);
887        p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
888        p.token = token;
889        p.softInputMode = mSoftInputMode;
890        p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
891
892        return p;
893    }
894
895    private int computeFlags(int curFlags) {
896        curFlags &= ~(
897                WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
898                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
899                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
900                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
901                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
902                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
903        if(mIgnoreCheekPress) {
904            curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
905        }
906        if (!mFocusable) {
907            curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
908            if (mInputMethodMode == INPUT_METHOD_NEEDED) {
909                curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
910            }
911        } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
912            curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
913        }
914        if (!mTouchable) {
915            curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
916        }
917        if (mOutsideTouchable) {
918            curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
919        }
920        if (!mClippingEnabled) {
921            curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
922        }
923        if (mSplitTouchEnabled) {
924            curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
925        }
926        return curFlags;
927    }
928
929    private int computeAnimationResource() {
930        if (mAnimationStyle == -1) {
931            if (mIsDropdown) {
932                return mAboveAnchor
933                        ? com.android.internal.R.style.Animation_DropDownUp
934                        : com.android.internal.R.style.Animation_DropDownDown;
935            }
936            return 0;
937        }
938        return mAnimationStyle;
939    }
940
941    /**
942     * <p>Positions the popup window on screen. When the popup window is too
943     * tall to fit under the anchor, a parent scroll view is seeked and scrolled
944     * up to reclaim space. If scrolling is not possible or not enough, the
945     * popup window gets moved on top of the anchor.</p>
946     *
947     * <p>The height must have been set on the layout parameters prior to
948     * calling this method.</p>
949     *
950     * @param anchor the view on which the popup window must be anchored
951     * @param p the layout parameters used to display the drop down
952     *
953     * @return true if the popup is translated upwards to fit on screen
954     */
955    private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p,
956            int xoff, int yoff) {
957
958        anchor.getLocationInWindow(mDrawingLocation);
959        p.x = mDrawingLocation[0] + xoff;
960        p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
961
962        boolean onTop = false;
963
964        p.gravity = Gravity.LEFT | Gravity.TOP;
965
966        anchor.getLocationOnScreen(mScreenLocation);
967        final Rect displayFrame = new Rect();
968        anchor.getWindowVisibleDisplayFrame(displayFrame);
969
970        final View root = anchor.getRootView();
971        if (p.y + mPopupHeight > displayFrame.bottom || p.x + mPopupWidth - root.getWidth() > 0) {
972            // if the drop down disappears at the bottom of the screen. we try to
973            // scroll a parent scrollview or move the drop down back up on top of
974            // the edit box
975            int scrollX = anchor.getScrollX();
976            int scrollY = anchor.getScrollY();
977            Rect r = new Rect(scrollX, scrollY,  scrollX + mPopupWidth + xoff,
978                    scrollY + mPopupHeight + anchor.getHeight() + yoff);
979            anchor.requestRectangleOnScreen(r, true);
980
981            // now we re-evaluate the space available, and decide from that
982            // whether the pop-up will go above or below the anchor.
983            anchor.getLocationInWindow(mDrawingLocation);
984            p.x = mDrawingLocation[0] + xoff;
985            p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
986
987            // determine whether there is more space above or below the anchor
988            anchor.getLocationOnScreen(mScreenLocation);
989
990            onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getHeight() - yoff) <
991                    (mScreenLocation[1] - yoff - displayFrame.top);
992            if (onTop) {
993                p.gravity = Gravity.LEFT | Gravity.BOTTOM;
994                p.y = root.getHeight() - mDrawingLocation[1] + yoff;
995            } else {
996                p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
997            }
998        }
999
1000        p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
1001
1002        return onTop;
1003    }
1004
1005    /**
1006     * Returns the maximum height that is available for the popup to be
1007     * completely shown. It is recommended that this height be the maximum for
1008     * the popup's height, otherwise it is possible that the popup will be
1009     * clipped.
1010     *
1011     * @param anchor The view on which the popup window must be anchored.
1012     * @return The maximum available height for the popup to be completely
1013     *         shown.
1014     */
1015    public int getMaxAvailableHeight(View anchor) {
1016        return getMaxAvailableHeight(anchor, 0);
1017    }
1018
1019    /**
1020     * Returns the maximum height that is available for the popup to be
1021     * completely shown. It is recommended that this height be the maximum for
1022     * the popup's height, otherwise it is possible that the popup will be
1023     * clipped.
1024     *
1025     * @param anchor The view on which the popup window must be anchored.
1026     * @param yOffset y offset from the view's bottom edge
1027     * @return The maximum available height for the popup to be completely
1028     *         shown.
1029     */
1030    public int getMaxAvailableHeight(View anchor, int yOffset) {
1031        return getMaxAvailableHeight(anchor, yOffset, false);
1032    }
1033
1034    /**
1035     * Returns the maximum height that is available for the popup to be
1036     * completely shown, optionally ignoring any bottom decorations such as
1037     * the input method. It is recommended that this height be the maximum for
1038     * the popup's height, otherwise it is possible that the popup will be
1039     * clipped.
1040     *
1041     * @param anchor The view on which the popup window must be anchored.
1042     * @param yOffset y offset from the view's bottom edge
1043     * @param ignoreBottomDecorations if true, the height returned will be
1044     *        all the way to the bottom of the display, ignoring any
1045     *        bottom decorations
1046     * @return The maximum available height for the popup to be completely
1047     *         shown.
1048     *
1049     * @hide Pending API council approval.
1050     */
1051    public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) {
1052        final Rect displayFrame = new Rect();
1053        anchor.getWindowVisibleDisplayFrame(displayFrame);
1054
1055        final int[] anchorPos = mDrawingLocation;
1056        anchor.getLocationOnScreen(anchorPos);
1057
1058        int bottomEdge = displayFrame.bottom;
1059        if (ignoreBottomDecorations) {
1060            bottomEdge = anchor.getContext().getResources().getDisplayMetrics().heightPixels;
1061        }
1062        final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
1063        final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
1064
1065        // anchorPos[1] is distance from anchor to top of screen
1066        int returnedHeight = Math.max(distanceToBottom, distanceToTop);
1067        if (mBackground != null) {
1068            mBackground.getPadding(mTempRect);
1069            returnedHeight -= mTempRect.top + mTempRect.bottom;
1070        }
1071
1072        return returnedHeight;
1073    }
1074
1075    /**
1076     * <p>Dispose of the popup window. This method can be invoked only after
1077     * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling
1078     * this method will have no effect.</p>
1079     *
1080     * @see #showAsDropDown(android.view.View)
1081     */
1082    public void dismiss() {
1083        if (isShowing() && mPopupView != null) {
1084            unregisterForScrollChanged();
1085
1086            try {
1087                mWindowManager.removeView(mPopupView);
1088            } finally {
1089                if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
1090                    ((ViewGroup) mPopupView).removeView(mContentView);
1091                }
1092                mPopupView = null;
1093                mIsShowing = false;
1094
1095                if (mOnDismissListener != null) {
1096                    mOnDismissListener.onDismiss();
1097                }
1098            }
1099        }
1100    }
1101
1102    /**
1103     * Sets the listener to be called when the window is dismissed.
1104     *
1105     * @param onDismissListener The listener.
1106     */
1107    public void setOnDismissListener(OnDismissListener onDismissListener) {
1108        mOnDismissListener = onDismissListener;
1109    }
1110
1111    /**
1112     * Updates the state of the popup window, if it is currently being displayed,
1113     * from the currently set state.  This include:
1114     * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)},
1115     * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)},
1116     * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}.
1117     */
1118    public void update() {
1119        if (!isShowing() || mContentView == null) {
1120            return;
1121        }
1122
1123        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
1124                mPopupView.getLayoutParams();
1125
1126        boolean update = false;
1127
1128        final int newAnim = computeAnimationResource();
1129        if (newAnim != p.windowAnimations) {
1130            p.windowAnimations = newAnim;
1131            update = true;
1132        }
1133
1134        final int newFlags = computeFlags(p.flags);
1135        if (newFlags != p.flags) {
1136            p.flags = newFlags;
1137            update = true;
1138        }
1139
1140        if (update) {
1141            mWindowManager.updateViewLayout(mPopupView, p);
1142        }
1143    }
1144
1145    /**
1146     * <p>Updates the dimension of the popup window. Calling this function
1147     * also updates the window with the current popup state as described
1148     * for {@link #update()}.</p>
1149     *
1150     * @param width the new width
1151     * @param height the new height
1152     */
1153    public void update(int width, int height) {
1154        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
1155                mPopupView.getLayoutParams();
1156        update(p.x, p.y, width, height, false);
1157    }
1158
1159    /**
1160     * <p>Updates the position and the dimension of the popup window. Width and
1161     * height can be set to -1 to update location only.  Calling this function
1162     * also updates the window with the current popup state as
1163     * described for {@link #update()}.</p>
1164     *
1165     * @param x the new x location
1166     * @param y the new y location
1167     * @param width the new width, can be -1 to ignore
1168     * @param height the new height, can be -1 to ignore
1169     */
1170    public void update(int x, int y, int width, int height) {
1171        update(x, y, width, height, false);
1172    }
1173
1174    /**
1175     * <p>Updates the position and the dimension of the popup window. Width and
1176     * height can be set to -1 to update location only.  Calling this function
1177     * also updates the window with the current popup state as
1178     * described for {@link #update()}.</p>
1179     *
1180     * @param x the new x location
1181     * @param y the new y location
1182     * @param width the new width, can be -1 to ignore
1183     * @param height the new height, can be -1 to ignore
1184     * @param force reposition the window even if the specified position
1185     *              already seems to correspond to the LayoutParams
1186     */
1187    public void update(int x, int y, int width, int height, boolean force) {
1188        if (width != -1) {
1189            mLastWidth = width;
1190            setWidth(width);
1191        }
1192
1193        if (height != -1) {
1194            mLastHeight = height;
1195            setHeight(height);
1196        }
1197
1198        if (!isShowing() || mContentView == null) {
1199            return;
1200        }
1201
1202        WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
1203
1204        boolean update = force;
1205
1206        final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
1207        if (width != -1 && p.width != finalWidth) {
1208            p.width = mLastWidth = finalWidth;
1209            update = true;
1210        }
1211
1212        final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight;
1213        if (height != -1 && p.height != finalHeight) {
1214            p.height = mLastHeight = finalHeight;
1215            update = true;
1216        }
1217
1218        if (p.x != x) {
1219            p.x = x;
1220            update = true;
1221        }
1222
1223        if (p.y != y) {
1224            p.y = y;
1225            update = true;
1226        }
1227
1228        final int newAnim = computeAnimationResource();
1229        if (newAnim != p.windowAnimations) {
1230            p.windowAnimations = newAnim;
1231            update = true;
1232        }
1233
1234        final int newFlags = computeFlags(p.flags);
1235        if (newFlags != p.flags) {
1236            p.flags = newFlags;
1237            update = true;
1238        }
1239
1240        if (update) {
1241            mWindowManager.updateViewLayout(mPopupView, p);
1242        }
1243    }
1244
1245    /**
1246     * <p>Updates the position and the dimension of the popup window. Calling this
1247     * function also updates the window with the current popup state as described
1248     * for {@link #update()}.</p>
1249     *
1250     * @param anchor the popup's anchor view
1251     * @param width the new width, can be -1 to ignore
1252     * @param height the new height, can be -1 to ignore
1253     */
1254    public void update(View anchor, int width, int height) {
1255        update(anchor, false, 0, 0, true, width, height);
1256    }
1257
1258    /**
1259     * <p>Updates the position and the dimension of the popup window. Width and
1260     * height can be set to -1 to update location only.  Calling this function
1261     * also updates the window with the current popup state as
1262     * described for {@link #update()}.</p>
1263     * <p>If the view later scrolls to move <code>anchor</code> to a different
1264     * location, the popup will be moved correspondingly.</p>
1265     *
1266     * @param anchor the popup's anchor view
1267     * @param xoff x offset from the view's left edge
1268     * @param yoff y offset from the view's bottom edge
1269     * @param width the new width, can be -1 to ignore
1270     * @param height the new height, can be -1 to ignore
1271     */
1272    public void update(View anchor, int xoff, int yoff, int width, int height) {
1273        update(anchor, true, xoff, yoff, true, width, height);
1274    }
1275
1276    private void update(View anchor, boolean updateLocation, int xoff, int yoff,
1277            boolean updateDimension, int width, int height) {
1278
1279        if (!isShowing() || mContentView == null) {
1280            return;
1281        }
1282
1283        WeakReference<View> oldAnchor = mAnchor;
1284        if (oldAnchor == null || oldAnchor.get() != anchor ||
1285                (updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff))) {
1286            registerForScrollChanged(anchor, xoff, yoff);
1287        }
1288
1289        WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
1290
1291        if (updateDimension) {
1292            if (width == -1) {
1293                width = mPopupWidth;
1294            } else {
1295                mPopupWidth = width;
1296            }
1297            if (height == -1) {
1298                height = mPopupHeight;
1299            } else {
1300                mPopupHeight = height;
1301            }
1302        }
1303
1304        int x = p.x;
1305        int y = p.y;
1306
1307        if (updateLocation) {
1308            updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff));
1309        } else {
1310            updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff));
1311        }
1312
1313        update(p.x, p.y, width, height, x != p.x || y != p.y);
1314    }
1315
1316    /**
1317     * Listener that is called when this popup window is dismissed.
1318     */
1319    public interface OnDismissListener {
1320        /**
1321         * Called when this popup window is dismissed.
1322         */
1323        public void onDismiss();
1324    }
1325
1326    private void unregisterForScrollChanged() {
1327        WeakReference<View> anchorRef = mAnchor;
1328        View anchor = null;
1329        if (anchorRef != null) {
1330            anchor = anchorRef.get();
1331        }
1332        if (anchor != null) {
1333            ViewTreeObserver vto = anchor.getViewTreeObserver();
1334            vto.removeOnScrollChangedListener(mOnScrollChangedListener);
1335        }
1336        mAnchor = null;
1337    }
1338
1339    private void registerForScrollChanged(View anchor, int xoff, int yoff) {
1340        unregisterForScrollChanged();
1341
1342        mAnchor = new WeakReference<View>(anchor);
1343        ViewTreeObserver vto = anchor.getViewTreeObserver();
1344        if (vto != null) {
1345            vto.addOnScrollChangedListener(mOnScrollChangedListener);
1346        }
1347
1348        mAnchorXoff = xoff;
1349        mAnchorYoff = yoff;
1350    }
1351
1352    private class PopupViewContainer extends FrameLayout {
1353
1354        public PopupViewContainer(Context context) {
1355            super(context);
1356        }
1357
1358        @Override
1359        protected int[] onCreateDrawableState(int extraSpace) {
1360            if (mAboveAnchor) {
1361                // 1 more needed for the above anchor state
1362                final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
1363                View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
1364                return drawableState;
1365            } else {
1366                return super.onCreateDrawableState(extraSpace);
1367            }
1368        }
1369
1370        @Override
1371        public boolean dispatchKeyEvent(KeyEvent event) {
1372            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
1373                if (event.getAction() == KeyEvent.ACTION_DOWN
1374                        && event.getRepeatCount() == 0) {
1375                    getKeyDispatcherState().startTracking(event, this);
1376                    return true;
1377                } else if (event.getAction() == KeyEvent.ACTION_UP
1378                        && getKeyDispatcherState().isTracking(event) && !event.isCanceled()) {
1379                    dismiss();
1380                    return true;
1381                }
1382                return super.dispatchKeyEvent(event);
1383            } else {
1384                return super.dispatchKeyEvent(event);
1385            }
1386        }
1387
1388        @Override
1389        public boolean dispatchTouchEvent(MotionEvent ev) {
1390            if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
1391                return true;
1392            }
1393            return super.dispatchTouchEvent(ev);
1394        }
1395
1396        @Override
1397        public boolean onTouchEvent(MotionEvent event) {
1398            final int x = (int) event.getX();
1399            final int y = (int) event.getY();
1400
1401            if ((event.getAction() == MotionEvent.ACTION_DOWN)
1402                    && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
1403                dismiss();
1404                return true;
1405            } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
1406                dismiss();
1407                return true;
1408            } else {
1409                return super.onTouchEvent(event);
1410            }
1411        }
1412
1413        @Override
1414        public void sendAccessibilityEvent(int eventType) {
1415            // clinets are interested in the content not the container, make it event source
1416            if (mContentView != null) {
1417                mContentView.sendAccessibilityEvent(eventType);
1418            } else {
1419                super.sendAccessibilityEvent(eventType);
1420            }
1421        }
1422    }
1423
1424}
1425