/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v4.widget; import android.support.v4.view.GravityCompat; import android.support.v4.view.ViewCompat; import android.view.Gravity; import android.view.View; import android.view.WindowManager; import android.widget.PopupWindow; import java.lang.reflect.Method; /** * Helper for accessing features in PopupWindow introduced after API level 4 * in a backwards compatible fashion. */ public final class PopupWindowCompat { /** * Interface for the full API. */ interface PopupWindowImpl { void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff, int gravity); void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor); boolean getOverlapAnchor(PopupWindow popupWindow); void setWindowLayoutType(PopupWindow popupWindow, int layoutType); int getWindowLayoutType(PopupWindow popupWindow); } /** * Interface implementation that doesn't use anything above v4 APIs. */ static class BasePopupWindowImpl implements PopupWindowImpl { private static Method sSetWindowLayoutTypeMethod; private static boolean sSetWindowLayoutTypeMethodAttempted; private static Method sGetWindowLayoutTypeMethod; private static boolean sGetWindowLayoutTypeMethodAttempted; @Override public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff, int gravity) { final int hgrav = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(anchor)) & Gravity.HORIZONTAL_GRAVITY_MASK; if (hgrav == Gravity.RIGHT) { // Flip the location to align the right sides of the popup and // anchor instead of left. xoff -= (popup.getWidth() - anchor.getWidth()); } popup.showAsDropDown(anchor, xoff, yoff); } @Override public void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) { // noop } @Override public boolean getOverlapAnchor(PopupWindow popupWindow) { return false; } @Override public void setWindowLayoutType(PopupWindow popupWindow, int layoutType) { if (!sSetWindowLayoutTypeMethodAttempted) { try { sSetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod( "setWindowLayoutType", int.class); sSetWindowLayoutTypeMethod.setAccessible(true); } catch (Exception e) { // Reflection method fetch failed. Oh well. } sSetWindowLayoutTypeMethodAttempted = true; } if (sSetWindowLayoutTypeMethod != null) { try { sSetWindowLayoutTypeMethod.invoke(popupWindow, layoutType); } catch (Exception e) { // Reflection call failed. Oh well. } } } @Override public int getWindowLayoutType(PopupWindow popupWindow) { if (!sGetWindowLayoutTypeMethodAttempted) { try { sGetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod( "getWindowLayoutType"); sGetWindowLayoutTypeMethod.setAccessible(true); } catch (Exception e) { // Reflection method fetch failed. Oh well. } sGetWindowLayoutTypeMethodAttempted = true; } if (sGetWindowLayoutTypeMethod != null) { try { return (Integer) sGetWindowLayoutTypeMethod.invoke(popupWindow); } catch (Exception e) { // Reflection call failed. Oh well. } } return 0; } } /** * Interface implementation for devices with at least KitKat APIs. */ static class KitKatPopupWindowImpl extends BasePopupWindowImpl { @Override public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff, int gravity) { PopupWindowCompatKitKat.showAsDropDown(popup, anchor, xoff, yoff, gravity); } } static class Api21PopupWindowImpl extends KitKatPopupWindowImpl { @Override public void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) { PopupWindowCompatApi21.setOverlapAnchor(popupWindow, overlapAnchor); } @Override public boolean getOverlapAnchor(PopupWindow popupWindow) { return PopupWindowCompatApi21.getOverlapAnchor(popupWindow); } } static class Api23PopupWindowImpl extends Api21PopupWindowImpl { @Override public void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) { PopupWindowCompatApi23.setOverlapAnchor(popupWindow, overlapAnchor); } @Override public boolean getOverlapAnchor(PopupWindow popupWindow) { return PopupWindowCompatApi23.getOverlapAnchor(popupWindow); } @Override public void setWindowLayoutType(PopupWindow popupWindow, int layoutType) { PopupWindowCompatApi23.setWindowLayoutType(popupWindow, layoutType); } @Override public int getWindowLayoutType(PopupWindow popupWindow) { return PopupWindowCompatApi23.getWindowLayoutType(popupWindow); } } /** * Select the correct implementation to use for the current platform. */ static final PopupWindowImpl IMPL; static { final int version = android.os.Build.VERSION.SDK_INT; if (version >= 23) { IMPL = new Api23PopupWindowImpl(); } else if (version >= 21) { IMPL = new Api21PopupWindowImpl(); } else if (version >= 19) { IMPL = new KitKatPopupWindowImpl(); } else { IMPL = new BasePopupWindowImpl(); } } private PopupWindowCompat() { // This class is not publicly instantiable. } /** *

Display the content view in a popup window anchored to the bottom-left * corner of the anchor view offset by the specified x and y coordinates. * If there is not enough room on screen to show * the popup in its entirety, this method tries to find a parent scroll * view to scroll. If no parent scroll view can be scrolled, the bottom-left * corner of the popup is pinned at the top left corner of the anchor view.

*

If the view later scrolls to move anchor to a different * location, the popup will be moved correspondingly.

* * @param popup the PopupWindow to show * @param anchor the view on which to pin the popup window * @param xoff A horizontal offset from the anchor in pixels * @param yoff A vertical offset from the anchor in pixels * @param gravity Alignment of the popup relative to the anchor */ public static void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff, int gravity) { IMPL.showAsDropDown(popup, anchor, xoff, yoff, gravity); } /** * Sets whether the popup window should overlap its anchor view when * displayed as a drop-down. * * @param overlapAnchor Whether the popup should overlap its anchor. */ public static void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) { IMPL.setOverlapAnchor(popupWindow, overlapAnchor); } /** * Returns whether the popup window should overlap its anchor view when * displayed as a drop-down. * * @return Whether the popup should overlap its anchor. */ public static boolean getOverlapAnchor(PopupWindow popupWindow) { return IMPL.getOverlapAnchor(popupWindow); } /** * Set the layout type for this window. This value will be passed through to * {@link WindowManager.LayoutParams#type} therefore the value should match any value * {@link WindowManager.LayoutParams#type} accepts. * * @param layoutType Layout type for this window. * * @see WindowManager.LayoutParams#type */ public static void setWindowLayoutType(PopupWindow popupWindow, int layoutType) { IMPL.setWindowLayoutType(popupWindow, layoutType); } /** * Returns the layout type for this window. * * @see #setWindowLayoutType(PopupWindow popupWindow, int) */ public static int getWindowLayoutType(PopupWindow popupWindow) { return IMPL.getWindowLayoutType(popupWindow); } }