15ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes/*
25ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * Copyright (C) 2014 The Android Open Source Project
35ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes *
45ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * Licensed under the Apache License, Version 2.0 (the "License");
55ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * you may not use this file except in compliance with the License.
65ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * You may obtain a copy of the License at
75ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes *
85ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes *      http://www.apache.org/licenses/LICENSE-2.0
95ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes *
105ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * Unless required by applicable law or agreed to in writing, software
115ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * distributed under the License is distributed on an "AS IS" BASIS,
125ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * See the License for the specific language governing permissions and
145ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes * limitations under the License.
155ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes */
165ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes
1766698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banespackage android.support.v7.widget;
185ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes
198e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikasimport static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
208e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas
215ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banesimport android.content.Context;
225ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banesimport android.os.Build;
23b2d5231158a58f03b611a8e4065de02938c54e26Chris Banesimport android.support.annotation.AttrRes;
24b2d5231158a58f03b611a8e4065de02938c54e26Chris Banesimport android.support.annotation.NonNull;
25b2d5231158a58f03b611a8e4065de02938c54e26Chris Banesimport android.support.annotation.Nullable;
26c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viveretteimport android.support.annotation.RestrictTo;
27b2d5231158a58f03b611a8e4065de02938c54e26Chris Banesimport android.support.annotation.StyleRes;
2844918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banesimport android.support.v4.widget.PopupWindowCompat;
295ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banesimport android.support.v7.appcompat.R;
305ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banesimport android.util.AttributeSet;
3145266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banesimport android.util.Log;
325ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banesimport android.view.View;
3345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banesimport android.view.ViewTreeObserver.OnScrollChangedListener;
345ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banesimport android.widget.PopupWindow;
355ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes
3645266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banesimport java.lang.ref.WeakReference;
3745266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banesimport java.lang.reflect.Field;
3845266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes
39b2d5231158a58f03b611a8e4065de02938c54e26Chris Banesclass AppCompatPopupWindow extends PopupWindow {
405ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes
4145266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes    private static final String TAG = "AppCompatPopupWindow";
4244918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes    private static final boolean COMPAT_OVERLAP_ANCHOR = Build.VERSION.SDK_INT < 21;
4345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes
4444918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes    private boolean mOverlapAnchor;
455ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes
46b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes    public AppCompatPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
47b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes            @AttrRes int defStyleAttr) {
485ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes        super(context, attrs, defStyleAttr);
49b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes        init(context, attrs, defStyleAttr, 0);
50b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes    }
51b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes
52b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes    public AppCompatPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
53b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
54b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes        super(context, attrs, defStyleAttr, defStyleRes);
55b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes        init(context, attrs, defStyleAttr, defStyleRes);
56b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes    }
575ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes
58b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes    private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
59469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
60b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes                R.styleable.PopupWindow, defStyleAttr, defStyleRes);
6144918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        if (a.hasValue(R.styleable.PopupWindow_overlapAnchor)) {
6244918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes            setSupportOverlapAnchor(a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false));
6344918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        }
64469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        // We re-set this for tinting purposes
65469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        setBackgroundDrawable(a.getDrawable(R.styleable.PopupWindow_android_popupBackground));
66b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes
67b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes        final int sdk = Build.VERSION.SDK_INT;
68b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes        if (defStyleRes != 0 && sdk < 11) {
69b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes            // If we have a defStyleRes, but we're on < API 11, we need to manually set attributes
70b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes            // from the style
7164dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov            // android:popupAnimationStyle was added in API 9
7264dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov            if (a.hasValue(R.styleable.PopupWindow_android_popupAnimationStyle)) {
7364dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov                setAnimationStyle(a.getResourceId(
7464dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov                        R.styleable.PopupWindow_android_popupAnimationStyle, -1));
75b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes            }
76b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes        }
77b2d5231158a58f03b611a8e4065de02938c54e26Chris Banes
785ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes        a.recycle();
7945266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes
8045266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
8145266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            // For devices pre-ICS, we need to wrap the internal OnScrollChangedListener
8245266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            // due to NPEs.
8345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            wrapOnScrollChangedListener(this);
8445266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes        }
855ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes    }
865ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes
875ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes    @Override
885ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes    public void showAsDropDown(View anchor, int xoff, int yoff) {
8944918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        if (COMPAT_OVERLAP_ANCHOR && mOverlapAnchor) {
905ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes            // If we're pre-L, emulate overlapAnchor by modifying the yOff
915ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes            yoff -= anchor.getHeight();
925ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes        }
935ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes        super.showAsDropDown(anchor, xoff, yoff);
945ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes    }
955ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes
965ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes    @Override
975ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
9844918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        if (COMPAT_OVERLAP_ANCHOR && mOverlapAnchor) {
995ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes            // If we're pre-L, emulate overlapAnchor by modifying the yOff
1005ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes            yoff -= anchor.getHeight();
1015ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes        }
1025ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes        super.showAsDropDown(anchor, xoff, yoff, gravity);
1035ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes    }
1045ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes
1055ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes    @Override
1065ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes    public void update(View anchor, int xoff, int yoff, int width, int height) {
10744918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        if (COMPAT_OVERLAP_ANCHOR && mOverlapAnchor) {
1085ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes            // If we're pre-L, emulate overlapAnchor by modifying the yOff
1095ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes            yoff -= anchor.getHeight();
1105ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes        }
1115ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes        super.update(anchor, xoff, yoff, width, height);
1125ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes    }
11345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes
11445266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes    private static void wrapOnScrollChangedListener(final PopupWindow popup) {
11545266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes        try {
11645266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            final Field fieldAnchor = PopupWindow.class.getDeclaredField("mAnchor");
11745266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            fieldAnchor.setAccessible(true);
11845266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes
11945266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            final Field fieldListener = PopupWindow.class
12045266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                    .getDeclaredField("mOnScrollChangedListener");
12145266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            fieldListener.setAccessible(true);
12245266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes
12345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            final OnScrollChangedListener originalListener =
12445266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                    (OnScrollChangedListener) fieldListener.get(popup);
12545266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes
12645266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            // Now set a new listener, wrapping the original and only proxying the call when
12745266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            // we have an anchor view.
12845266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            fieldListener.set(popup, new OnScrollChangedListener() {
12945266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                @Override
13045266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                public void onScrollChanged() {
13145266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                    try {
13245266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                        WeakReference<View> mAnchor = (WeakReference<View>) fieldAnchor.get(popup);
13345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                        if (mAnchor == null || mAnchor.get() == null) {
13445266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                            return;
13545266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                        } else {
13645266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                            originalListener.onScrollChanged();
13745266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                        }
13845266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                    } catch (IllegalAccessException e) {
13945266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                        // Oh well...
14045266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                    }
14145266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes                }
14245266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            });
14345266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes        } catch (Exception e) {
14445266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes            Log.d(TAG, "Exception while installing workaround OnScrollChangedListener", e);
14545266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes        }
14645266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes    }
14745266b3d81b349b34c5b8adb99e007d3c5b57d28Chris Banes
14844918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes    /**
14944918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes     * @hide
15044918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes     */
1518e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas    @RestrictTo(LIBRARY_GROUP)
15244918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes    public void setSupportOverlapAnchor(boolean overlapAnchor) {
15344918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        if (COMPAT_OVERLAP_ANCHOR) {
15444918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes            mOverlapAnchor = overlapAnchor;
15544918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        } else {
15644918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes            PopupWindowCompat.setOverlapAnchor(this, overlapAnchor);
15744918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        }
15844918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes    }
15944918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes
16044918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes    /**
16144918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes     * @hide
16244918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes     */
1638e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas    @RestrictTo(LIBRARY_GROUP)
16444918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes    public boolean getSupportOverlapAnchor() {
16544918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        if (COMPAT_OVERLAP_ANCHOR) {
16644918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes            return mOverlapAnchor;
16744918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        } else {
16844918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes            return PopupWindowCompat.getOverlapAnchor(this);
16944918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        }
17044918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes    }
17144918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes
1725ec2faa01bc6790bc015e0d5748dc0482ae8c0f2Chris Banes}
173