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.internal.widget;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.os.Build;
22import android.support.v4.widget.PopupWindowCompat;
23import android.support.v7.appcompat.R;
24import android.util.AttributeSet;
25import android.util.Log;
26import android.view.View;
27import android.view.ViewTreeObserver.OnScrollChangedListener;
28import android.widget.PopupWindow;
29
30import java.lang.ref.WeakReference;
31import java.lang.reflect.Field;
32
33/**
34 * @hide
35 */
36public class AppCompatPopupWindow extends PopupWindow {
37
38    private static final String TAG = "AppCompatPopupWindow";
39    private static final boolean COMPAT_OVERLAP_ANCHOR = Build.VERSION.SDK_INT < 21;
40
41    private boolean mOverlapAnchor;
42
43    public AppCompatPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
44        super(context, attrs, defStyleAttr);
45
46        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
47                R.styleable.PopupWindow, defStyleAttr, 0);
48        if (a.hasValue(R.styleable.PopupWindow_overlapAnchor)) {
49            setSupportOverlapAnchor(a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false));
50        }
51        // We re-set this for tinting purposes
52        setBackgroundDrawable(a.getDrawable(R.styleable.PopupWindow_android_popupBackground));
53        a.recycle();
54
55        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
56            // For devices pre-ICS, we need to wrap the internal OnScrollChangedListener
57            // due to NPEs.
58            wrapOnScrollChangedListener(this);
59        }
60    }
61
62    @Override
63    public void showAsDropDown(View anchor, int xoff, int yoff) {
64        if (COMPAT_OVERLAP_ANCHOR && mOverlapAnchor) {
65            // If we're pre-L, emulate overlapAnchor by modifying the yOff
66            yoff -= anchor.getHeight();
67        }
68        super.showAsDropDown(anchor, xoff, yoff);
69    }
70
71    @TargetApi(Build.VERSION_CODES.KITKAT)
72    @Override
73    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
74        if (COMPAT_OVERLAP_ANCHOR && mOverlapAnchor) {
75            // If we're pre-L, emulate overlapAnchor by modifying the yOff
76            yoff -= anchor.getHeight();
77        }
78        super.showAsDropDown(anchor, xoff, yoff, gravity);
79    }
80
81    @Override
82    public void update(View anchor, int xoff, int yoff, int width, int height) {
83        if (COMPAT_OVERLAP_ANCHOR && mOverlapAnchor) {
84            // If we're pre-L, emulate overlapAnchor by modifying the yOff
85            yoff -= anchor.getHeight();
86        }
87        super.update(anchor, xoff, yoff, width, height);
88    }
89
90    private static void wrapOnScrollChangedListener(final PopupWindow popup) {
91        try {
92            final Field fieldAnchor = PopupWindow.class.getDeclaredField("mAnchor");
93            fieldAnchor.setAccessible(true);
94
95            final Field fieldListener = PopupWindow.class
96                    .getDeclaredField("mOnScrollChangedListener");
97            fieldListener.setAccessible(true);
98
99            final OnScrollChangedListener originalListener =
100                    (OnScrollChangedListener) fieldListener.get(popup);
101
102            // Now set a new listener, wrapping the original and only proxying the call when
103            // we have an anchor view.
104            fieldListener.set(popup, new OnScrollChangedListener() {
105                @Override
106                public void onScrollChanged() {
107                    try {
108                        WeakReference<View> mAnchor = (WeakReference<View>) fieldAnchor.get(popup);
109                        if (mAnchor == null || mAnchor.get() == null) {
110                            return;
111                        } else {
112                            originalListener.onScrollChanged();
113                        }
114                    } catch (IllegalAccessException e) {
115                        // Oh well...
116                    }
117                }
118            });
119        } catch (Exception e) {
120            Log.d(TAG, "Exception while installing workaround OnScrollChangedListener", e);
121        }
122    }
123
124    /**
125     * @hide
126     */
127    public void setSupportOverlapAnchor(boolean overlapAnchor) {
128        if (COMPAT_OVERLAP_ANCHOR) {
129            mOverlapAnchor = overlapAnchor;
130        } else {
131            PopupWindowCompat.setOverlapAnchor(this, overlapAnchor);
132        }
133    }
134
135    /**
136     * @hide
137     */
138    public boolean getSupportOverlapAnchor() {
139        if (COMPAT_OVERLAP_ANCHOR) {
140            return mOverlapAnchor;
141        } else {
142            return PopupWindowCompat.getOverlapAnchor(this);
143        }
144    }
145
146}
147