1/* 2 * Copyright (C) 2013 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.v4.widget; 18 19import android.os.Build; 20import android.support.annotation.RequiresApi; 21import android.support.v4.view.GravityCompat; 22import android.support.v4.view.ViewCompat; 23import android.util.Log; 24import android.view.Gravity; 25import android.view.View; 26import android.widget.PopupWindow; 27 28import java.lang.reflect.Field; 29import java.lang.reflect.Method; 30 31/** 32 * Helper for accessing features in PopupWindow in a backwards compatible fashion. 33 */ 34public final class PopupWindowCompat { 35 36 static class PopupWindowCompatBaseImpl { 37 private static Method sSetWindowLayoutTypeMethod; 38 private static boolean sSetWindowLayoutTypeMethodAttempted; 39 private static Method sGetWindowLayoutTypeMethod; 40 private static boolean sGetWindowLayoutTypeMethodAttempted; 41 42 public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff, 43 int gravity) { 44 final int hgrav = GravityCompat.getAbsoluteGravity(gravity, 45 ViewCompat.getLayoutDirection(anchor)) & Gravity.HORIZONTAL_GRAVITY_MASK; 46 if (hgrav == Gravity.RIGHT) { 47 // Flip the location to align the right sides of the popup and 48 // anchor instead of left. 49 xoff -= (popup.getWidth() - anchor.getWidth()); 50 } 51 popup.showAsDropDown(anchor, xoff, yoff); 52 } 53 54 public void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) { 55 // noop 56 } 57 58 public boolean getOverlapAnchor(PopupWindow popupWindow) { 59 return false; 60 } 61 62 public void setWindowLayoutType(PopupWindow popupWindow, int layoutType) { 63 if (!sSetWindowLayoutTypeMethodAttempted) { 64 try { 65 sSetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod( 66 "setWindowLayoutType", int.class); 67 sSetWindowLayoutTypeMethod.setAccessible(true); 68 } catch (Exception e) { 69 // Reflection method fetch failed. Oh well. 70 } 71 sSetWindowLayoutTypeMethodAttempted = true; 72 } 73 74 if (sSetWindowLayoutTypeMethod != null) { 75 try { 76 sSetWindowLayoutTypeMethod.invoke(popupWindow, layoutType); 77 } catch (Exception e) { 78 // Reflection call failed. Oh well. 79 } 80 } 81 } 82 83 public int getWindowLayoutType(PopupWindow popupWindow) { 84 if (!sGetWindowLayoutTypeMethodAttempted) { 85 try { 86 sGetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod( 87 "getWindowLayoutType"); 88 sGetWindowLayoutTypeMethod.setAccessible(true); 89 } catch (Exception e) { 90 // Reflection method fetch failed. Oh well. 91 } 92 sGetWindowLayoutTypeMethodAttempted = true; 93 } 94 95 if (sGetWindowLayoutTypeMethod != null) { 96 try { 97 return (Integer) sGetWindowLayoutTypeMethod.invoke(popupWindow); 98 } catch (Exception e) { 99 // Reflection call failed. Oh well. 100 } 101 } 102 return 0; 103 } 104 } 105 106 /** 107 * Interface implementation for devices with at least KitKat APIs. 108 */ 109 @RequiresApi(19) 110 static class PopupWindowCompatApi19Impl extends PopupWindowCompatBaseImpl { 111 @Override 112 public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff, 113 int gravity) { 114 popup.showAsDropDown(anchor, xoff, yoff, gravity); 115 } 116 } 117 118 @RequiresApi(21) 119 static class PopupWindowCompatApi21Impl extends PopupWindowCompatApi19Impl { 120 private static final String TAG = "PopupWindowCompatApi21"; 121 122 private static Field sOverlapAnchorField; 123 124 static { 125 try { 126 sOverlapAnchorField = PopupWindow.class.getDeclaredField("mOverlapAnchor"); 127 sOverlapAnchorField.setAccessible(true); 128 } catch (NoSuchFieldException e) { 129 Log.i(TAG, "Could not fetch mOverlapAnchor field from PopupWindow", e); 130 } 131 } 132 133 @Override 134 public void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) { 135 if (sOverlapAnchorField != null) { 136 try { 137 sOverlapAnchorField.set(popupWindow, overlapAnchor); 138 } catch (IllegalAccessException e) { 139 Log.i(TAG, "Could not set overlap anchor field in PopupWindow", e); 140 } 141 } 142 } 143 144 @Override 145 public boolean getOverlapAnchor(PopupWindow popupWindow) { 146 if (sOverlapAnchorField != null) { 147 try { 148 return (Boolean) sOverlapAnchorField.get(popupWindow); 149 } catch (IllegalAccessException e) { 150 Log.i(TAG, "Could not get overlap anchor field in PopupWindow", e); 151 } 152 } 153 return false; 154 } 155 } 156 157 @RequiresApi(23) 158 static class PopupWindowCompatApi23Impl extends PopupWindowCompatApi21Impl { 159 @Override 160 public void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) { 161 popupWindow.setOverlapAnchor(overlapAnchor); 162 } 163 164 @Override 165 public boolean getOverlapAnchor(PopupWindow popupWindow) { 166 return popupWindow.getOverlapAnchor(); 167 } 168 169 @Override 170 public void setWindowLayoutType(PopupWindow popupWindow, int layoutType) { 171 popupWindow.setWindowLayoutType(layoutType); 172 } 173 174 @Override 175 public int getWindowLayoutType(PopupWindow popupWindow) { 176 return popupWindow.getWindowLayoutType(); 177 } 178 } 179 180 /** 181 * Select the correct implementation to use for the current platform. 182 */ 183 static final PopupWindowCompatBaseImpl IMPL; 184 static { 185 if (Build.VERSION.SDK_INT >= 23) { 186 IMPL = new PopupWindowCompatApi23Impl(); 187 } else if (Build.VERSION.SDK_INT >= 21) { 188 IMPL = new PopupWindowCompatApi21Impl(); 189 } else if (Build.VERSION.SDK_INT >= 19) { 190 IMPL = new PopupWindowCompatApi19Impl(); 191 } else { 192 IMPL = new PopupWindowCompatBaseImpl(); 193 } 194 } 195 196 private PopupWindowCompat() { 197 // This class is not publicly instantiable. 198 } 199 200 /** 201 * <p>Display the content view in a popup window anchored to the bottom-left 202 * corner of the anchor view offset by the specified x and y coordinates. 203 * If there is not enough room on screen to show 204 * the popup in its entirety, this method tries to find a parent scroll 205 * view to scroll. If no parent scroll view can be scrolled, the bottom-left 206 * corner of the popup is pinned at the top left corner of the anchor view.</p> 207 * <p>If the view later scrolls to move <code>anchor</code> to a different 208 * location, the popup will be moved correspondingly.</p> 209 * 210 * @param popup the PopupWindow to show 211 * @param anchor the view on which to pin the popup window 212 * @param xoff A horizontal offset from the anchor in pixels 213 * @param yoff A vertical offset from the anchor in pixels 214 * @param gravity Alignment of the popup relative to the anchor 215 */ 216 public static void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff, 217 int gravity) { 218 IMPL.showAsDropDown(popup, anchor, xoff, yoff, gravity); 219 } 220 221 /** 222 * Sets whether the popup window should overlap its anchor view when 223 * displayed as a drop-down. 224 * 225 * @param overlapAnchor Whether the popup should overlap its anchor. 226 */ 227 public static void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) { 228 IMPL.setOverlapAnchor(popupWindow, overlapAnchor); 229 } 230 231 /** 232 * Returns whether the popup window should overlap its anchor view when 233 * displayed as a drop-down. 234 * 235 * @return Whether the popup should overlap its anchor. 236 */ 237 public static boolean getOverlapAnchor(PopupWindow popupWindow) { 238 return IMPL.getOverlapAnchor(popupWindow); 239 } 240 241 /** 242 * Set the layout type for this window. This value will be passed through to 243 * {@link android.view.WindowManager.LayoutParams#type} therefore the value should match any 244 * value {@link android.view.WindowManager.LayoutParams#type} accepts. 245 * 246 * @param layoutType Layout type for this window. 247 * 248 * @see android.view.WindowManager.LayoutParams#type 249 */ 250 public static void setWindowLayoutType(PopupWindow popupWindow, int layoutType) { 251 IMPL.setWindowLayoutType(popupWindow, layoutType); 252 } 253 254 /** 255 * Returns the layout type for this window. 256 * 257 * @see #setWindowLayoutType(PopupWindow popupWindow, int) 258 */ 259 public static int getWindowLayoutType(PopupWindow popupWindow) { 260 return IMPL.getWindowLayoutType(popupWindow); 261 } 262} 263