| /* |
| * Copyright (C) 2008 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.widget.cts; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertSame; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.Matchers.anyInt; |
| import static org.mockito.Mockito.any; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.app.Instrumentation; |
| import android.content.Context; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.PackageManager; |
| import android.content.res.Configuration; |
| import android.graphics.Color; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.os.SystemClock; |
| import android.support.test.InstrumentationRegistry; |
| import android.support.test.annotation.UiThreadTest; |
| import android.support.test.filters.FlakyTest; |
| import android.support.test.filters.SmallTest; |
| import android.support.test.rule.ActivityTestRule; |
| import android.support.test.runner.AndroidJUnit4; |
| import android.transition.Transition; |
| import android.transition.Transition.TransitionListener; |
| import android.transition.TransitionValues; |
| import android.util.AttributeSet; |
| import android.util.DisplayMetrics; |
| import android.view.Display; |
| import android.view.Gravity; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.View.OnTouchListener; |
| import android.view.ViewGroup; |
| import android.view.ViewGroup.LayoutParams; |
| import android.view.WindowInsets; |
| import android.view.WindowManager; |
| import android.widget.ImageView; |
| import android.widget.PopupWindow; |
| import android.widget.PopupWindow.OnDismissListener; |
| import android.widget.TextView; |
| |
| import com.android.compatibility.common.util.WidgetTestUtils; |
| |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| @FlakyTest |
| @SmallTest |
| @RunWith(AndroidJUnit4.class) |
| public class PopupWindowTest { |
| private static final int WINDOW_SIZE_DP = 50; |
| private static final int CONTENT_SIZE_DP = 30; |
| |
| private Instrumentation mInstrumentation; |
| private Context mContext; |
| private PopupWindowCtsActivity mActivity; |
| private PopupWindow mPopupWindow; |
| private TextView mTextView; |
| |
| @Rule |
| public ActivityTestRule<PopupWindowCtsActivity> mActivityRule = |
| new ActivityTestRule<>(PopupWindowCtsActivity.class); |
| |
| @Before |
| public void setup() { |
| mInstrumentation = InstrumentationRegistry.getInstrumentation(); |
| mContext = InstrumentationRegistry.getContext(); |
| mActivity = mActivityRule.getActivity(); |
| } |
| |
| @Test |
| public void testConstructor() { |
| new PopupWindow(mActivity); |
| |
| new PopupWindow(mActivity, null); |
| |
| new PopupWindow(mActivity, null, android.R.attr.popupWindowStyle); |
| |
| new PopupWindow(mActivity, null, 0, android.R.style.Widget_DeviceDefault_PopupWindow); |
| |
| new PopupWindow(mActivity, null, 0, android.R.style.Widget_DeviceDefault_Light_PopupWindow); |
| |
| new PopupWindow(mActivity, null, 0, android.R.style.Widget_Material_PopupWindow); |
| |
| new PopupWindow(mActivity, null, 0, android.R.style.Widget_Material_Light_PopupWindow); |
| } |
| |
| @UiThreadTest |
| @Test |
| public void testSize() { |
| mPopupWindow = new PopupWindow(); |
| assertEquals(0, mPopupWindow.getWidth()); |
| assertEquals(0, mPopupWindow.getHeight()); |
| |
| mPopupWindow = new PopupWindow(50, 50); |
| assertEquals(50, mPopupWindow.getWidth()); |
| assertEquals(50, mPopupWindow.getHeight()); |
| |
| mPopupWindow = new PopupWindow(-1, -1); |
| assertEquals(-1, mPopupWindow.getWidth()); |
| assertEquals(-1, mPopupWindow.getHeight()); |
| |
| TextView contentView = new TextView(mActivity); |
| mPopupWindow = new PopupWindow(contentView); |
| assertSame(contentView, mPopupWindow.getContentView()); |
| |
| mPopupWindow = new PopupWindow(contentView, 0, 0); |
| assertEquals(0, mPopupWindow.getWidth()); |
| assertEquals(0, mPopupWindow.getHeight()); |
| assertSame(contentView, mPopupWindow.getContentView()); |
| |
| mPopupWindow = new PopupWindow(contentView, 50, 50); |
| assertEquals(50, mPopupWindow.getWidth()); |
| assertEquals(50, mPopupWindow.getHeight()); |
| assertSame(contentView, mPopupWindow.getContentView()); |
| |
| mPopupWindow = new PopupWindow(contentView, -1, -1); |
| assertEquals(-1, mPopupWindow.getWidth()); |
| assertEquals(-1, mPopupWindow.getHeight()); |
| assertSame(contentView, mPopupWindow.getContentView()); |
| |
| mPopupWindow = new PopupWindow(contentView, 0, 0, true); |
| assertEquals(0, mPopupWindow.getWidth()); |
| assertEquals(0, mPopupWindow.getHeight()); |
| assertSame(contentView, mPopupWindow.getContentView()); |
| assertTrue(mPopupWindow.isFocusable()); |
| |
| mPopupWindow = new PopupWindow(contentView, 50, 50, false); |
| assertEquals(50, mPopupWindow.getWidth()); |
| assertEquals(50, mPopupWindow.getHeight()); |
| assertSame(contentView, mPopupWindow.getContentView()); |
| assertFalse(mPopupWindow.isFocusable()); |
| |
| mPopupWindow = new PopupWindow(contentView, -1, -1, true); |
| assertEquals(-1, mPopupWindow.getWidth()); |
| assertEquals(-1, mPopupWindow.getHeight()); |
| assertSame(contentView, mPopupWindow.getContentView()); |
| assertTrue(mPopupWindow.isFocusable()); |
| } |
| |
| @Test |
| public void testAccessEnterExitTransitions() { |
| PopupWindow w = new PopupWindow(mActivity, null, 0, 0); |
| assertNull(w.getEnterTransition()); |
| assertNull(w.getExitTransition()); |
| |
| w = new PopupWindow(mActivity, null, 0, R.style.PopupWindow_NullTransitions); |
| assertNull(w.getEnterTransition()); |
| assertNull(w.getExitTransition()); |
| |
| w = new PopupWindow(mActivity, null, 0, R.style.PopupWindow_CustomTransitions); |
| assertTrue(w.getEnterTransition() instanceof CustomTransition); |
| assertTrue(w.getExitTransition() instanceof CustomTransition); |
| |
| Transition enterTransition = new CustomTransition(); |
| Transition exitTransition = new CustomTransition(); |
| w = new PopupWindow(mActivity, null, 0, 0); |
| w.setEnterTransition(enterTransition); |
| w.setExitTransition(exitTransition); |
| assertEquals(enterTransition, w.getEnterTransition()); |
| assertEquals(exitTransition, w.getExitTransition()); |
| |
| w.setEnterTransition(null); |
| w.setExitTransition(null); |
| assertNull(w.getEnterTransition()); |
| assertNull(w.getExitTransition()); |
| } |
| |
| public static class CustomTransition extends Transition { |
| public CustomTransition() { |
| } |
| |
| // This constructor is needed for reflection-based creation of a transition when |
| // the transition is defined in layout XML via attribute. |
| @SuppressWarnings("unused") |
| public CustomTransition(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| } |
| |
| @Override |
| public void captureStartValues(TransitionValues transitionValues) {} |
| |
| @Override |
| public void captureEndValues(TransitionValues transitionValues) {} |
| } |
| |
| @Test |
| public void testAccessBackground() { |
| mPopupWindow = new PopupWindow(mActivity); |
| |
| Drawable drawable = new ColorDrawable(); |
| mPopupWindow.setBackgroundDrawable(drawable); |
| assertSame(drawable, mPopupWindow.getBackground()); |
| |
| mPopupWindow.setBackgroundDrawable(null); |
| assertNull(mPopupWindow.getBackground()); |
| } |
| |
| @Test |
| public void testAccessAnimationStyle() { |
| mPopupWindow = new PopupWindow(mActivity); |
| // default is -1 |
| assertEquals(-1, mPopupWindow.getAnimationStyle()); |
| |
| mPopupWindow.setAnimationStyle(android.R.style.Animation_Toast); |
| assertEquals(android.R.style.Animation_Toast, |
| mPopupWindow.getAnimationStyle()); |
| |
| // abnormal values |
| mPopupWindow.setAnimationStyle(-100); |
| assertEquals(-100, mPopupWindow.getAnimationStyle()); |
| } |
| |
| @Test |
| public void testAccessContentView() throws Throwable { |
| mPopupWindow = new PopupWindow(mActivity); |
| assertNull(mPopupWindow.getContentView()); |
| |
| mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity)); |
| mInstrumentation.waitForIdleSync(); |
| mPopupWindow.setContentView(mTextView); |
| assertSame(mTextView, mPopupWindow.getContentView()); |
| |
| mPopupWindow.setContentView(null); |
| assertNull(mPopupWindow.getContentView()); |
| |
| // can not set the content if the old content is shown |
| mPopupWindow.setContentView(mTextView); |
| assertFalse(mPopupWindow.isShowing()); |
| showPopup(); |
| ImageView img = new ImageView(mActivity); |
| assertTrue(mPopupWindow.isShowing()); |
| mPopupWindow.setContentView(img); |
| assertSame(mTextView, mPopupWindow.getContentView()); |
| dismissPopup(); |
| } |
| |
| @Test |
| public void testAccessFocusable() { |
| mPopupWindow = new PopupWindow(mActivity); |
| assertFalse(mPopupWindow.isFocusable()); |
| |
| mPopupWindow.setFocusable(true); |
| assertTrue(mPopupWindow.isFocusable()); |
| |
| mPopupWindow.setFocusable(false); |
| assertFalse(mPopupWindow.isFocusable()); |
| } |
| |
| @Test |
| public void testAccessHeight() { |
| mPopupWindow = new PopupWindow(mActivity); |
| assertEquals(WindowManager.LayoutParams.WRAP_CONTENT, mPopupWindow.getHeight()); |
| |
| int height = getDisplay().getHeight() / 2; |
| mPopupWindow.setHeight(height); |
| assertEquals(height, mPopupWindow.getHeight()); |
| |
| height = getDisplay().getHeight(); |
| mPopupWindow.setHeight(height); |
| assertEquals(height, mPopupWindow.getHeight()); |
| |
| mPopupWindow.setHeight(0); |
| assertEquals(0, mPopupWindow.getHeight()); |
| |
| height = getDisplay().getHeight() * 2; |
| mPopupWindow.setHeight(height); |
| assertEquals(height, mPopupWindow.getHeight()); |
| |
| height = -getDisplay().getHeight() / 2; |
| mPopupWindow.setHeight(height); |
| assertEquals(height, mPopupWindow.getHeight()); |
| } |
| |
| /** |
| * Gets the display. |
| * |
| * @return the display |
| */ |
| private Display getDisplay() { |
| WindowManager wm = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE); |
| return wm.getDefaultDisplay(); |
| } |
| |
| @Test |
| public void testAccessWidth() { |
| mPopupWindow = new PopupWindow(mActivity); |
| assertEquals(WindowManager.LayoutParams.WRAP_CONTENT, mPopupWindow.getWidth()); |
| |
| int width = getDisplay().getWidth() / 2; |
| mPopupWindow.setWidth(width); |
| assertEquals(width, mPopupWindow.getWidth()); |
| |
| width = getDisplay().getWidth(); |
| mPopupWindow.setWidth(width); |
| assertEquals(width, mPopupWindow.getWidth()); |
| |
| mPopupWindow.setWidth(0); |
| assertEquals(0, mPopupWindow.getWidth()); |
| |
| width = getDisplay().getWidth() * 2; |
| mPopupWindow.setWidth(width); |
| assertEquals(width, mPopupWindow.getWidth()); |
| |
| width = - getDisplay().getWidth() / 2; |
| mPopupWindow.setWidth(width); |
| assertEquals(width, mPopupWindow.getWidth()); |
| } |
| |
| private static final int TOP = 0x00; |
| private static final int BOTTOM = 0x01; |
| |
| private static final int LEFT = 0x00; |
| private static final int RIGHT = 0x01; |
| |
| private static final int GREATER_THAN = 1; |
| private static final int LESS_THAN = -1; |
| private static final int EQUAL_TO = 0; |
| |
| @Test |
| public void testShowAsDropDown() throws Throwable { |
| final PopupWindow popup = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, |
| CONTENT_SIZE_DP)); |
| popup.setClipToScreenEnabled(false); |
| popup.setOverlapAnchor(false); |
| popup.setAnimationStyle(0); |
| popup.setExitTransition(null); |
| popup.setEnterTransition(null); |
| |
| verifyPosition(popup, R.id.anchor_upper_left, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM); |
| verifyPosition(popup, R.id.anchor_upper, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM); |
| verifyPosition(popup, R.id.anchor_upper_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM); |
| |
| verifyPosition(popup, R.id.anchor_middle_left, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM); |
| verifyPosition(popup, R.id.anchor_middle, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM); |
| verifyPosition(popup, R.id.anchor_middle_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM); |
| |
| verifyPosition(popup, R.id.anchor_lower_left, |
| LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP); |
| verifyPosition(popup, R.id.anchor_lower, |
| LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP); |
| verifyPosition(popup, R.id.anchor_lower_right, |
| RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP); |
| } |
| |
| @Test |
| public void testShowAsDropDown_ClipToScreen() throws Throwable { |
| final PopupWindow popup = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, |
| CONTENT_SIZE_DP)); |
| popup.setClipToScreenEnabled(true); |
| popup.setOverlapAnchor(false); |
| popup.setAnimationStyle(0); |
| popup.setExitTransition(null); |
| popup.setEnterTransition(null); |
| |
| verifyPosition(popup, R.id.anchor_upper_left, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM); |
| verifyPosition(popup, R.id.anchor_upper, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM); |
| verifyPosition(popup, R.id.anchor_upper_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM); |
| |
| verifyPosition(popup, R.id.anchor_middle_left, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM); |
| verifyPosition(popup, R.id.anchor_middle, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM); |
| verifyPosition(popup, R.id.anchor_middle_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM); |
| |
| verifyPosition(popup, R.id.anchor_lower_left, |
| LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP); |
| verifyPosition(popup, R.id.anchor_lower, |
| LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP); |
| verifyPosition(popup, R.id.anchor_lower_right, |
| RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP); |
| } |
| |
| @Test |
| public void testShowAsDropDown_ClipToScreen_Overlap() throws Throwable { |
| final PopupWindow popup = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, |
| CONTENT_SIZE_DP)); |
| popup.setClipToScreenEnabled(true); |
| popup.setOverlapAnchor(true); |
| popup.setAnimationStyle(0); |
| popup.setExitTransition(null); |
| popup.setEnterTransition(null); |
| |
| verifyPosition(popup, R.id.anchor_upper_left, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP); |
| verifyPosition(popup, R.id.anchor_upper, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP); |
| verifyPosition(popup, R.id.anchor_upper_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP); |
| |
| verifyPosition(popup, R.id.anchor_middle_left, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP); |
| verifyPosition(popup, R.id.anchor_middle, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP); |
| verifyPosition(popup, R.id.anchor_middle_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP); |
| |
| verifyPosition(popup, R.id.anchor_lower_left, |
| LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP); |
| verifyPosition(popup, R.id.anchor_lower, |
| LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP); |
| verifyPosition(popup, R.id.anchor_lower_right, |
| RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP); |
| } |
| |
| @Test |
| public void testShowAsDropDown_ClipToScreen_Overlap_Offset() throws Throwable { |
| final PopupWindow popup = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, |
| CONTENT_SIZE_DP)); |
| popup.setClipToScreenEnabled(true); |
| popup.setOverlapAnchor(true); |
| popup.setAnimationStyle(0); |
| popup.setExitTransition(null); |
| popup.setEnterTransition(null); |
| |
| final int offsetX = mActivity.findViewById(R.id.anchor_upper).getWidth() / 2; |
| final int offsetY = mActivity.findViewById(R.id.anchor_upper).getHeight() / 2; |
| final int gravity = Gravity.TOP | Gravity.START; |
| |
| verifyPosition(popup, R.id.anchor_upper_left, |
| LEFT, GREATER_THAN, LEFT, TOP, GREATER_THAN, TOP, |
| offsetX, offsetY, gravity); |
| verifyPosition(popup, R.id.anchor_upper, |
| LEFT, GREATER_THAN, LEFT, TOP, GREATER_THAN, TOP, |
| offsetX, offsetY, gravity); |
| verifyPosition(popup, R.id.anchor_upper_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, GREATER_THAN, TOP, |
| offsetX, offsetY, gravity); |
| |
| verifyPosition(popup, R.id.anchor_middle_left, |
| LEFT, GREATER_THAN, LEFT, TOP, GREATER_THAN, TOP, |
| offsetX, offsetY, gravity); |
| verifyPosition(popup, R.id.anchor_middle, |
| LEFT, GREATER_THAN, LEFT, TOP, GREATER_THAN, TOP, |
| offsetX, offsetY, gravity); |
| verifyPosition(popup, R.id.anchor_middle_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, GREATER_THAN, TOP, |
| offsetX, offsetY, gravity); |
| |
| verifyPosition(popup, R.id.anchor_lower_left, |
| LEFT, GREATER_THAN, LEFT, BOTTOM, LESS_THAN, BOTTOM, |
| offsetX, offsetY, gravity); |
| verifyPosition(popup, R.id.anchor_lower, |
| LEFT, GREATER_THAN, LEFT, BOTTOM, LESS_THAN, BOTTOM, |
| offsetX, offsetY, gravity); |
| verifyPosition(popup, R.id.anchor_lower_right, |
| RIGHT, EQUAL_TO, RIGHT, BOTTOM, LESS_THAN, BOTTOM, |
| offsetX, offsetY, gravity); |
| } |
| |
| @Test |
| public void testShowAsDropDown_ClipToScreen_TooBig() throws Throwable { |
| final View rootView = mActivity.findViewById(R.id.anchor_upper_left).getRootView(); |
| final int width = rootView.getWidth() * 2; |
| final int height = rootView.getHeight() * 2; |
| |
| final PopupWindow popup = createPopupWindow(createPopupContent(width, height)); |
| popup.setWidth(width); |
| popup.setHeight(height); |
| |
| popup.setClipToScreenEnabled(true); |
| popup.setOverlapAnchor(false); |
| popup.setAnimationStyle(0); |
| popup.setExitTransition(null); |
| popup.setEnterTransition(null); |
| |
| verifyPosition(popup, R.id.anchor_upper_left, |
| LEFT, EQUAL_TO, LEFT, TOP, LESS_THAN, TOP); |
| verifyPosition(popup, R.id.anchor_upper, |
| LEFT, LESS_THAN, LEFT, TOP, LESS_THAN, TOP); |
| verifyPosition(popup, R.id.anchor_upper_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, LESS_THAN, TOP); |
| |
| verifyPosition(popup, R.id.anchor_middle_left, |
| LEFT, EQUAL_TO, LEFT, TOP, LESS_THAN, TOP); |
| verifyPosition(popup, R.id.anchor_middle, |
| LEFT, LESS_THAN, LEFT, TOP, LESS_THAN, TOP); |
| verifyPosition(popup, R.id.anchor_middle_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, LESS_THAN, TOP); |
| |
| verifyPosition(popup, R.id.anchor_lower_left, |
| LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, BOTTOM); |
| verifyPosition(popup, R.id.anchor_lower, |
| LEFT, LESS_THAN, LEFT, BOTTOM, EQUAL_TO, BOTTOM); |
| verifyPosition(popup, R.id.anchor_lower_right, |
| RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, BOTTOM); |
| } |
| |
| private void verifyPosition(PopupWindow popup, int anchorId, |
| int contentEdgeX, int operatorX, int anchorEdgeX, |
| int contentEdgeY, int operatorY, int anchorEdgeY) throws Throwable { |
| verifyPosition(popup, mActivity.findViewById(anchorId), |
| contentEdgeX, operatorX, anchorEdgeX, |
| contentEdgeY, operatorY, anchorEdgeY, |
| 0, 0, Gravity.TOP | Gravity.START); |
| } |
| |
| private void verifyPosition(PopupWindow popup, int anchorId, |
| int contentEdgeX, int operatorX, int anchorEdgeX, |
| int contentEdgeY, int operatorY, int anchorEdgeY, |
| int offsetX, int offsetY, int gravity) throws Throwable { |
| verifyPosition(popup, mActivity.findViewById(anchorId), |
| contentEdgeX, operatorX, anchorEdgeX, |
| contentEdgeY, operatorY, anchorEdgeY, offsetX, offsetY, gravity); |
| } |
| |
| private void verifyPosition(PopupWindow popup, View anchor, |
| int contentEdgeX, int operatorX, int anchorEdgeX, |
| int contentEdgeY, int operatorY, int anchorEdgeY) throws Throwable { |
| verifyPosition(popup, anchor, |
| contentEdgeX, operatorX, anchorEdgeX, |
| contentEdgeY, operatorY, anchorEdgeY, |
| 0, 0, Gravity.TOP | Gravity.START); |
| } |
| |
| private void verifyPosition(PopupWindow popup, View anchor, |
| int contentEdgeX, int operatorX, int anchorEdgeX, |
| int contentEdgeY, int operatorY, int anchorEdgeY, |
| int offsetX, int offsetY, int gravity) throws Throwable { |
| final View content = popup.getContentView(); |
| |
| mActivityRule.runOnUiThread(() -> popup.showAsDropDown( |
| anchor, offsetX, offsetY, gravity)); |
| mInstrumentation.waitForIdleSync(); |
| |
| assertTrue(popup.isShowing()); |
| verifyPositionX(content, contentEdgeX, operatorX, anchor, anchorEdgeX); |
| verifyPositionY(content, contentEdgeY, operatorY, anchor, anchorEdgeY); |
| |
| // Make sure it fits in the display frame. |
| final Rect displayFrame = new Rect(); |
| anchor.getWindowVisibleDisplayFrame(displayFrame); |
| final Rect contentFrame = new Rect(); |
| content.getBoundsOnScreen(contentFrame); |
| assertTrue("Content (" + contentFrame + ") extends outside display (" |
| + displayFrame + ")", displayFrame.contains(contentFrame)); |
| |
| mActivityRule.runOnUiThread(popup::dismiss); |
| mInstrumentation.waitForIdleSync(); |
| |
| assertFalse(popup.isShowing()); |
| } |
| |
| private void verifyPositionY(View content, int contentEdge, int flags, |
| View anchor, int anchorEdge) { |
| final int[] anchorOnScreenXY = new int[2]; |
| anchor.getLocationOnScreen(anchorOnScreenXY); |
| int anchorY = anchorOnScreenXY[1]; |
| if ((anchorEdge & BOTTOM) == BOTTOM) { |
| anchorY += anchor.getHeight(); |
| } |
| |
| final int[] contentOnScreenXY = new int[2]; |
| content.getLocationOnScreen(contentOnScreenXY); |
| int contentY = contentOnScreenXY[1]; |
| if ((contentEdge & BOTTOM) == BOTTOM) { |
| contentY += content.getHeight(); |
| } |
| |
| assertComparison(contentY, flags, anchorY); |
| } |
| |
| private void verifyPositionX(View content, int contentEdge, int flags, |
| View anchor, int anchorEdge) { |
| final int[] anchorOnScreenXY = new int[2]; |
| anchor.getLocationOnScreen(anchorOnScreenXY); |
| int anchorX = anchorOnScreenXY[0]; |
| if ((anchorEdge & RIGHT) == RIGHT) { |
| anchorX += anchor.getWidth(); |
| } |
| |
| final int[] contentOnScreenXY = new int[2]; |
| content.getLocationOnScreen(contentOnScreenXY); |
| int contentX = contentOnScreenXY[0]; |
| if ((contentEdge & RIGHT) == RIGHT) { |
| contentX += content.getWidth(); |
| } |
| |
| assertComparison(contentX, flags, anchorX); |
| } |
| |
| private void assertComparison(int left, int operator, int right) { |
| switch (operator) { |
| case GREATER_THAN: |
| assertTrue(left + " <= " + right, left > right); |
| break; |
| case LESS_THAN: |
| assertTrue(left + " >= " + right, left < right); |
| break; |
| case EQUAL_TO: |
| assertTrue(left + " != " + right, left == right); |
| break; |
| } |
| } |
| |
| @Test |
| public void testShowAtLocation() throws Throwable { |
| int[] popupContentViewInWindowXY = new int[2]; |
| int[] popupContentViewOnScreenXY = new int[2]; |
| Rect containingRect = new Rect(); |
| |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| // Do not attach within the decor; we will be measuring location |
| // with regard to screen coordinates. |
| mPopupWindow.setAttachedInDecor(false); |
| assertFalse(mPopupWindow.isAttachedInDecor()); |
| |
| final View upperAnchor = mActivity.findViewById(R.id.anchor_upper); |
| final WindowInsets windowInsets = upperAnchor.getRootWindowInsets(); |
| final int xOff = windowInsets.getSystemWindowInsetLeft() + 10; |
| final int yOff = windowInsets.getSystemWindowInsetTop() + 21; |
| assertFalse(mPopupWindow.isShowing()); |
| mPopupWindow.getContentView().getLocationInWindow(popupContentViewInWindowXY); |
| assertEquals(0, popupContentViewInWindowXY[0]); |
| assertEquals(0, popupContentViewInWindowXY[1]); |
| |
| mActivityRule.runOnUiThread( |
| () -> mPopupWindow.showAtLocation(upperAnchor, Gravity.NO_GRAVITY, xOff, yOff)); |
| mInstrumentation.waitForIdleSync(); |
| |
| assertTrue(mPopupWindow.isShowing()); |
| mPopupWindow.getContentView().getLocationInWindow(popupContentViewInWindowXY); |
| mPopupWindow.getContentView().getLocationOnScreen(popupContentViewOnScreenXY); |
| upperAnchor.getWindowDisplayFrame(containingRect); |
| |
| assertTrue(popupContentViewInWindowXY[0] >= 0); |
| assertTrue(popupContentViewInWindowXY[1] >= 0); |
| assertEquals(containingRect.left + popupContentViewInWindowXY[0] + xOff, popupContentViewOnScreenXY[0]); |
| assertEquals(containingRect.top + popupContentViewInWindowXY[1] + yOff, popupContentViewOnScreenXY[1]); |
| |
| dismissPopup(); |
| } |
| |
| @Test |
| public void testShowAsDropDownWithOffsets() throws Throwable { |
| int[] anchorXY = new int[2]; |
| int[] viewOnScreenXY = new int[2]; |
| int[] viewInWindowXY = new int[2]; |
| |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| final View upperAnchor = mActivity.findViewById(R.id.anchor_upper); |
| upperAnchor.getLocationOnScreen(anchorXY); |
| int height = upperAnchor.getHeight(); |
| |
| final int xOff = 11; |
| final int yOff = 12; |
| |
| mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(upperAnchor, xOff, yOff)); |
| mInstrumentation.waitForIdleSync(); |
| |
| mPopupWindow.getContentView().getLocationOnScreen(viewOnScreenXY); |
| mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY); |
| assertEquals(anchorXY[0] + xOff + viewInWindowXY[0], viewOnScreenXY[0]); |
| assertEquals(anchorXY[1] + height + yOff + viewInWindowXY[1], viewOnScreenXY[1]); |
| |
| dismissPopup(); |
| } |
| |
| @Test |
| public void testOverlapAnchor() throws Throwable { |
| int[] anchorXY = new int[2]; |
| int[] viewOnScreenXY = new int[2]; |
| int[] viewInWindowXY = new int[2]; |
| |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| final View upperAnchor = mActivity.findViewById(R.id.anchor_upper); |
| upperAnchor.getLocationOnScreen(anchorXY); |
| |
| assertFalse(mPopupWindow.getOverlapAnchor()); |
| mPopupWindow.setOverlapAnchor(true); |
| assertTrue(mPopupWindow.getOverlapAnchor()); |
| |
| mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(upperAnchor, 0, 0)); |
| mInstrumentation.waitForIdleSync(); |
| |
| mPopupWindow.getContentView().getLocationOnScreen(viewOnScreenXY); |
| mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY); |
| assertEquals(anchorXY[0] + viewInWindowXY[0], viewOnScreenXY[0]); |
| assertEquals(anchorXY[1] + viewInWindowXY[1], viewOnScreenXY[1]); |
| } |
| |
| @Test |
| public void testAccessWindowLayoutType() { |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| assertEquals(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL, |
| mPopupWindow.getWindowLayoutType()); |
| mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL); |
| assertEquals(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, |
| mPopupWindow.getWindowLayoutType()); |
| } |
| |
| @Test |
| public void testGetMaxAvailableHeight() { |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| Rect displayFrame = new Rect(); |
| View anchorView = mActivity.findViewById(R.id.anchor_upper); |
| anchorView.getWindowVisibleDisplayFrame(displayFrame); |
| int displayHeight = displayFrame.height(); |
| int available = displayHeight - anchorView.getHeight(); |
| int maxAvailableHeight = mPopupWindow.getMaxAvailableHeight(anchorView); |
| int maxAvailableHeightIgnoringBottomDecoration = |
| mPopupWindow.getMaxAvailableHeight(anchorView, 0, true); |
| assertTrue(maxAvailableHeight > 0); |
| assertTrue(maxAvailableHeight <= available); |
| assertTrue(maxAvailableHeightIgnoringBottomDecoration >= maxAvailableHeight); |
| assertTrue(maxAvailableHeightIgnoringBottomDecoration <= available); |
| |
| int maxAvailableHeightWithOffset = mPopupWindow.getMaxAvailableHeight(anchorView, 2); |
| assertEquals(maxAvailableHeight - 2, maxAvailableHeightWithOffset); |
| |
| maxAvailableHeightWithOffset = |
| mPopupWindow.getMaxAvailableHeight(anchorView, maxAvailableHeight); |
| assertTrue(maxAvailableHeightWithOffset > 0); |
| assertTrue(maxAvailableHeightWithOffset <= available); |
| |
| maxAvailableHeightWithOffset = |
| mPopupWindow.getMaxAvailableHeight(anchorView, maxAvailableHeight / 2 - 1); |
| assertTrue(maxAvailableHeightWithOffset > 0); |
| assertTrue(maxAvailableHeightWithOffset <= available); |
| |
| maxAvailableHeightWithOffset = mPopupWindow.getMaxAvailableHeight(anchorView, -1); |
| assertTrue(maxAvailableHeightWithOffset > 0); |
| assertTrue(maxAvailableHeightWithOffset <= available); |
| |
| int maxAvailableHeightWithOffsetIgnoringBottomDecoration = |
| mPopupWindow.getMaxAvailableHeight(anchorView, 2, true); |
| assertEquals(maxAvailableHeightIgnoringBottomDecoration - 2, |
| maxAvailableHeightWithOffsetIgnoringBottomDecoration); |
| |
| maxAvailableHeightWithOffsetIgnoringBottomDecoration = |
| mPopupWindow.getMaxAvailableHeight(anchorView, maxAvailableHeight, true); |
| assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration > 0); |
| assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration <= available); |
| |
| maxAvailableHeightWithOffsetIgnoringBottomDecoration = |
| mPopupWindow.getMaxAvailableHeight(anchorView, maxAvailableHeight / 2 - 1, true); |
| assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration > 0); |
| assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration <= available); |
| |
| maxAvailableHeightWithOffsetIgnoringBottomDecoration = |
| mPopupWindow.getMaxAvailableHeight(anchorView, -1, true); |
| assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration > 0); |
| assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration <= available); |
| |
| anchorView = mActivity.findViewById(R.id.anchor_lower); |
| // On some devices the view might actually have larger size than the physical display |
| // due to chin and content will be laid out as if outside of the display. We need to use |
| // larger from the display height and the main view height. |
| available = Math.max(displayHeight, |
| mActivity.findViewById(android.R.id.content).getHeight()) - anchorView.getHeight(); |
| maxAvailableHeight = mPopupWindow.getMaxAvailableHeight(anchorView); |
| assertTrue(maxAvailableHeight > 0); |
| assertTrue(maxAvailableHeight <= available); |
| |
| anchorView = mActivity.findViewById(R.id.anchor_middle_left); |
| available = displayHeight - anchorView.getHeight() |
| - mActivity.findViewById(R.id.anchor_upper).getHeight(); |
| maxAvailableHeight = mPopupWindow.getMaxAvailableHeight(anchorView); |
| assertTrue(maxAvailableHeight > 0); |
| assertTrue(maxAvailableHeight <= available); |
| } |
| |
| @UiThreadTest |
| @Test |
| public void testDismiss() { |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| assertFalse(mPopupWindow.isShowing()); |
| View anchorView = mActivity.findViewById(R.id.anchor_upper); |
| mPopupWindow.showAsDropDown(anchorView); |
| |
| mPopupWindow.dismiss(); |
| assertFalse(mPopupWindow.isShowing()); |
| |
| mPopupWindow.dismiss(); |
| assertFalse(mPopupWindow.isShowing()); |
| } |
| |
| @Test |
| public void testSetOnDismissListener() throws Throwable { |
| mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity)); |
| mInstrumentation.waitForIdleSync(); |
| mPopupWindow = new PopupWindow(mTextView); |
| mPopupWindow.setOnDismissListener(null); |
| |
| OnDismissListener onDismissListener = mock(OnDismissListener.class); |
| mPopupWindow.setOnDismissListener(onDismissListener); |
| showPopup(); |
| dismissPopup(); |
| verify(onDismissListener, times(1)).onDismiss(); |
| |
| showPopup(); |
| dismissPopup(); |
| verify(onDismissListener, times(2)).onDismiss(); |
| |
| mPopupWindow.setOnDismissListener(null); |
| showPopup(); |
| dismissPopup(); |
| verify(onDismissListener, times(2)).onDismiss(); |
| } |
| |
| @Test |
| public void testUpdate() throws Throwable { |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| mPopupWindow.setBackgroundDrawable(null); |
| showPopup(); |
| |
| mPopupWindow.setIgnoreCheekPress(); |
| mPopupWindow.setFocusable(true); |
| mPopupWindow.setTouchable(false); |
| mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); |
| mPopupWindow.setClippingEnabled(false); |
| mPopupWindow.setOutsideTouchable(true); |
| |
| WindowManager.LayoutParams p = (WindowManager.LayoutParams) |
| mPopupWindow.getContentView().getRootView().getLayoutParams(); |
| |
| assertEquals(0, WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES & p.flags); |
| assertEquals(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, |
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE & p.flags); |
| assertEquals(0, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE & p.flags); |
| assertEquals(0, WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH & p.flags); |
| assertEquals(0, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS & p.flags); |
| assertEquals(0, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM & p.flags); |
| |
| mActivityRule.runOnUiThread(mPopupWindow::update); |
| mInstrumentation.waitForIdleSync(); |
| |
| assertEquals(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES, |
| WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES & p.flags); |
| assertEquals(0, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE & p.flags); |
| assertEquals(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, |
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE & p.flags); |
| assertEquals(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, |
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH & p.flags); |
| assertEquals(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, |
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS & p.flags); |
| assertEquals(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, |
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM & p.flags); |
| } |
| |
| @Test |
| public void testEnterExitInterruption() throws Throwable { |
| final View anchorView = mActivity.findViewById(R.id.anchor_upper); |
| verifyEnterExitTransition( |
| () -> mPopupWindow.showAsDropDown(anchorView, 0, 0), true); |
| } |
| |
| @Test |
| public void testEnterExitTransitionAsDropDown() throws Throwable { |
| final View anchorView = mActivity.findViewById(R.id.anchor_upper); |
| verifyEnterExitTransition( |
| () -> mPopupWindow.showAsDropDown(anchorView, 0, 0), false); |
| } |
| |
| @Test |
| public void testEnterExitTransitionAtLocation() throws Throwable { |
| final View anchorView = mActivity.findViewById(R.id.anchor_upper); |
| verifyEnterExitTransition( |
| () -> mPopupWindow.showAtLocation(anchorView, Gravity.BOTTOM, 0, 0), false); |
| } |
| |
| private void verifyEnterExitTransition(Runnable showRunnable, boolean showAgain) |
| throws Throwable { |
| TransitionListener enterListener = mock(TransitionListener.class); |
| Transition enterTransition = new BaseTransition(); |
| enterTransition.addListener(enterListener); |
| |
| TransitionListener exitListener = mock(TransitionListener.class); |
| Transition exitTransition = new BaseTransition(); |
| exitTransition.addListener(exitListener); |
| |
| OnDismissListener dismissListener = mock(OnDismissListener.class); |
| |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| mPopupWindow.setEnterTransition(enterTransition); |
| mPopupWindow.setExitTransition(exitTransition); |
| mPopupWindow.setOnDismissListener(dismissListener); |
| |
| mActivityRule.runOnUiThread(showRunnable); |
| mInstrumentation.waitForIdleSync(); |
| verify(enterListener, times(1)).onTransitionStart(any(Transition.class)); |
| verify(exitListener, never()).onTransitionStart(any(Transition.class)); |
| verify(dismissListener, never()).onDismiss(); |
| |
| mActivityRule.runOnUiThread(mPopupWindow::dismiss); |
| |
| int times; |
| if (showAgain) { |
| // Interrupt dismiss by calling show again, then actually dismiss. |
| mActivityRule.runOnUiThread(showRunnable); |
| mInstrumentation.waitForIdleSync(); |
| mActivityRule.runOnUiThread(mPopupWindow::dismiss); |
| |
| times = 2; |
| } else { |
| times = 1; |
| } |
| |
| mInstrumentation.waitForIdleSync(); |
| verify(enterListener, times(times)).onTransitionStart(any(Transition.class)); |
| verify(exitListener, times(times)).onTransitionStart(any(Transition.class)); |
| verify(dismissListener, times(times)).onDismiss(); |
| } |
| |
| @Test |
| public void testUpdatePositionAndDimension() throws Throwable { |
| int[] fstXY = new int[2]; |
| int[] sndXY = new int[2]; |
| int[] viewInWindowXY = new int[2]; |
| Rect containingRect = new Rect(); |
| final Point popupPos = new Point(); |
| |
| mActivityRule.runOnUiThread(() -> { |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| // Do not attach within the decor; we will be measuring location |
| // with regard to screen coordinates. |
| mPopupWindow.setAttachedInDecor(false); |
| }); |
| |
| mInstrumentation.waitForIdleSync(); |
| // Do not update if it is not shown |
| assertFalse(mPopupWindow.isShowing()); |
| assertFalse(mPopupWindow.isAttachedInDecor()); |
| assertEquals(WINDOW_SIZE_DP, mPopupWindow.getWidth()); |
| assertEquals(WINDOW_SIZE_DP, mPopupWindow.getHeight()); |
| |
| showPopup(); |
| mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY); |
| final View containerView = mActivity.findViewById(R.id.main_container); |
| containerView.getWindowDisplayFrame(containingRect); |
| |
| // update if it is not shown |
| mActivityRule.runOnUiThread(() -> mPopupWindow.update(80, 80)); |
| |
| mInstrumentation.waitForIdleSync(); |
| assertTrue(mPopupWindow.isShowing()); |
| assertEquals(80, mPopupWindow.getWidth()); |
| assertEquals(80, mPopupWindow.getHeight()); |
| |
| final WindowInsets windowInsets = containerView.getRootWindowInsets(); |
| popupPos.set(windowInsets.getStableInsetLeft() + 20, windowInsets.getStableInsetTop() + 50); |
| |
| // update if it is not shown |
| mActivityRule.runOnUiThread(() -> mPopupWindow.update(popupPos.x, popupPos.y, 50, 50)); |
| |
| mInstrumentation.waitForIdleSync(); |
| assertTrue(mPopupWindow.isShowing()); |
| assertEquals(50, mPopupWindow.getWidth()); |
| assertEquals(50, mPopupWindow.getHeight()); |
| |
| mPopupWindow.getContentView().getLocationOnScreen(fstXY); |
| assertEquals(containingRect.left + popupPos.x + viewInWindowXY[0], fstXY[0]); |
| assertEquals(containingRect.top + popupPos.y + viewInWindowXY[1], fstXY[1]); |
| |
| popupPos.set(windowInsets.getStableInsetLeft() + 4, windowInsets.getStableInsetTop()); |
| |
| // ignore if width or height is -1 |
| mActivityRule.runOnUiThread( |
| () -> mPopupWindow.update(popupPos.x, popupPos.y, -1, -1, true)); |
| mInstrumentation.waitForIdleSync(); |
| |
| assertTrue(mPopupWindow.isShowing()); |
| assertEquals(50, mPopupWindow.getWidth()); |
| assertEquals(50, mPopupWindow.getHeight()); |
| |
| mPopupWindow.getContentView().getLocationOnScreen(sndXY); |
| assertEquals(containingRect.left + popupPos.x + viewInWindowXY[0], sndXY[0]); |
| assertEquals(containingRect.top + popupPos.y + viewInWindowXY[1], sndXY[1]); |
| |
| dismissPopup(); |
| } |
| |
| @Test |
| public void testUpdateDimensionAndAlignAnchorView() throws Throwable { |
| mActivityRule.runOnUiThread( |
| () -> mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, |
| CONTENT_SIZE_DP))); |
| mInstrumentation.waitForIdleSync(); |
| |
| final View anchorView = mActivity.findViewById(R.id.anchor_upper); |
| mPopupWindow.update(anchorView, 50, 50); |
| // Do not update if it is not shown |
| assertFalse(mPopupWindow.isShowing()); |
| assertEquals(WINDOW_SIZE_DP, mPopupWindow.getWidth()); |
| assertEquals(WINDOW_SIZE_DP, mPopupWindow.getHeight()); |
| |
| mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(anchorView)); |
| mInstrumentation.waitForIdleSync(); |
| // update if it is shown |
| mActivityRule.runOnUiThread(() -> mPopupWindow.update(anchorView, 50, 50)); |
| mInstrumentation.waitForIdleSync(); |
| assertTrue(mPopupWindow.isShowing()); |
| assertEquals(50, mPopupWindow.getWidth()); |
| assertEquals(50, mPopupWindow.getHeight()); |
| |
| // ignore if width or height is -1 |
| mActivityRule.runOnUiThread(() -> mPopupWindow.update(anchorView, -1, -1)); |
| mInstrumentation.waitForIdleSync(); |
| assertTrue(mPopupWindow.isShowing()); |
| assertEquals(50, mPopupWindow.getWidth()); |
| assertEquals(50, mPopupWindow.getHeight()); |
| |
| mActivityRule.runOnUiThread(mPopupWindow::dismiss); |
| mInstrumentation.waitForIdleSync(); |
| } |
| |
| @Test |
| public void testUpdateDimensionAndAlignAnchorViewWithOffsets() throws Throwable { |
| int[] anchorXY = new int[2]; |
| int[] viewInWindowOff = new int[2]; |
| int[] viewXY = new int[2]; |
| |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| final View anchorView = mActivity.findViewById(R.id.anchor_upper); |
| // Do not update if it is not shown |
| assertFalse(mPopupWindow.isShowing()); |
| assertEquals(WINDOW_SIZE_DP, mPopupWindow.getWidth()); |
| assertEquals(WINDOW_SIZE_DP, mPopupWindow.getHeight()); |
| |
| showPopup(); |
| anchorView.getLocationOnScreen(anchorXY); |
| mPopupWindow.getContentView().getLocationInWindow(viewInWindowOff); |
| |
| // update if it is not shown |
| mActivityRule.runOnUiThread(() -> mPopupWindow.update(anchorView, 20, 50, 50, 50)); |
| |
| mInstrumentation.waitForIdleSync(); |
| |
| assertTrue(mPopupWindow.isShowing()); |
| assertEquals(50, mPopupWindow.getWidth()); |
| assertEquals(50, mPopupWindow.getHeight()); |
| |
| mPopupWindow.getContentView().getLocationOnScreen(viewXY); |
| |
| // The popup should appear below and to right with an offset. |
| assertEquals(anchorXY[0] + 20 + viewInWindowOff[0], viewXY[0]); |
| assertEquals(anchorXY[1] + anchorView.getHeight() + 50 + viewInWindowOff[1], viewXY[1]); |
| |
| // ignore width and height but change location |
| mActivityRule.runOnUiThread(() -> mPopupWindow.update(anchorView, 10, 50, -1, -1)); |
| mInstrumentation.waitForIdleSync(); |
| |
| assertTrue(mPopupWindow.isShowing()); |
| assertEquals(50, mPopupWindow.getWidth()); |
| assertEquals(50, mPopupWindow.getHeight()); |
| |
| mPopupWindow.getContentView().getLocationOnScreen(viewXY); |
| |
| // The popup should appear below and to right with an offset. |
| assertEquals(anchorXY[0] + 10 + viewInWindowOff[0], viewXY[0]); |
| assertEquals(anchorXY[1] + anchorView.getHeight() + 50 + viewInWindowOff[1], viewXY[1]); |
| |
| final View anotherView = mActivity.findViewById(R.id.anchor_middle_left); |
| mActivityRule.runOnUiThread(() -> mPopupWindow.update(anotherView, 0, 0, 60, 60)); |
| mInstrumentation.waitForIdleSync(); |
| |
| assertTrue(mPopupWindow.isShowing()); |
| assertEquals(60, mPopupWindow.getWidth()); |
| assertEquals(60, mPopupWindow.getHeight()); |
| |
| int[] newXY = new int[2]; |
| anotherView.getLocationOnScreen(newXY); |
| mPopupWindow.getContentView().getLocationOnScreen(viewXY); |
| |
| // The popup should appear below and to the right. |
| assertEquals(newXY[0] + viewInWindowOff[0], viewXY[0]); |
| assertEquals(newXY[1] + anotherView.getHeight() + viewInWindowOff[1], viewXY[1]); |
| |
| dismissPopup(); |
| } |
| |
| @Test |
| public void testAccessInputMethodMode() { |
| mPopupWindow = new PopupWindow(mActivity); |
| assertEquals(0, mPopupWindow.getInputMethodMode()); |
| |
| mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_FROM_FOCUSABLE); |
| assertEquals(PopupWindow.INPUT_METHOD_FROM_FOCUSABLE, mPopupWindow.getInputMethodMode()); |
| |
| mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); |
| assertEquals(PopupWindow.INPUT_METHOD_NEEDED, mPopupWindow.getInputMethodMode()); |
| |
| mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); |
| assertEquals(PopupWindow.INPUT_METHOD_NOT_NEEDED, mPopupWindow.getInputMethodMode()); |
| |
| mPopupWindow.setInputMethodMode(-1); |
| assertEquals(-1, mPopupWindow.getInputMethodMode()); |
| } |
| |
| @Test |
| public void testAccessClippingEnabled() { |
| mPopupWindow = new PopupWindow(mActivity); |
| assertTrue(mPopupWindow.isClippingEnabled()); |
| |
| mPopupWindow.setClippingEnabled(false); |
| assertFalse(mPopupWindow.isClippingEnabled()); |
| } |
| |
| @Test |
| public void testAccessOutsideTouchable() { |
| mPopupWindow = new PopupWindow(mActivity); |
| assertFalse(mPopupWindow.isOutsideTouchable()); |
| |
| mPopupWindow.setOutsideTouchable(true); |
| assertTrue(mPopupWindow.isOutsideTouchable()); |
| } |
| |
| @Test |
| public void testAccessTouchable() { |
| mPopupWindow = new PopupWindow(mActivity); |
| assertTrue(mPopupWindow.isTouchable()); |
| |
| mPopupWindow.setTouchable(false); |
| assertFalse(mPopupWindow.isTouchable()); |
| } |
| |
| @Test |
| public void testIsAboveAnchor() throws Throwable { |
| mActivityRule.runOnUiThread(() -> mPopupWindow = createPopupWindow(createPopupContent( |
| CONTENT_SIZE_DP, CONTENT_SIZE_DP))); |
| mInstrumentation.waitForIdleSync(); |
| final View upperAnchor = mActivity.findViewById(R.id.anchor_upper); |
| |
| mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(upperAnchor)); |
| mInstrumentation.waitForIdleSync(); |
| assertFalse(mPopupWindow.isAboveAnchor()); |
| dismissPopup(); |
| |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| final View lowerAnchor = mActivity.findViewById(R.id.anchor_lower); |
| |
| mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(lowerAnchor, 0, 0)); |
| mInstrumentation.waitForIdleSync(); |
| assertTrue(mPopupWindow.isAboveAnchor()); |
| dismissPopup(); |
| } |
| |
| @Test |
| public void testSetTouchInterceptor() throws Throwable { |
| mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity)); |
| mInstrumentation.waitForIdleSync(); |
| mPopupWindow = new PopupWindow(mTextView); |
| |
| OnTouchListener onTouchListener = mock(OnTouchListener.class); |
| when(onTouchListener.onTouch(any(View.class), any(MotionEvent.class))).thenReturn(true); |
| |
| mPopupWindow.setTouchInterceptor(onTouchListener); |
| mPopupWindow.setFocusable(true); |
| mPopupWindow.setOutsideTouchable(true); |
| Drawable drawable = new ColorDrawable(); |
| mPopupWindow.setBackgroundDrawable(drawable); |
| showPopup(); |
| |
| int[] xy = new int[2]; |
| mPopupWindow.getContentView().getLocationOnScreen(xy); |
| final int viewWidth = mPopupWindow.getContentView().getWidth(); |
| final int viewHeight = mPopupWindow.getContentView().getHeight(); |
| final float x = xy[0] + (viewWidth / 2.0f); |
| float y = xy[1] + (viewHeight / 2.0f); |
| |
| long downTime = SystemClock.uptimeMillis(); |
| long eventTime = SystemClock.uptimeMillis(); |
| MotionEvent event = MotionEvent.obtain(downTime, eventTime, |
| MotionEvent.ACTION_DOWN, x, y, 0); |
| mInstrumentation.sendPointerSync(event); |
| verify(onTouchListener, times(1)).onTouch(any(View.class), any(MotionEvent.class)); |
| |
| downTime = SystemClock.uptimeMillis(); |
| eventTime = SystemClock.uptimeMillis(); |
| event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); |
| mInstrumentation.sendPointerSync(event); |
| verify(onTouchListener, times(2)).onTouch(any(View.class), any(MotionEvent.class)); |
| |
| mPopupWindow.setTouchInterceptor(null); |
| downTime = SystemClock.uptimeMillis(); |
| eventTime = SystemClock.uptimeMillis(); |
| event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0); |
| mInstrumentation.sendPointerSync(event); |
| verify(onTouchListener, times(2)).onTouch(any(View.class), any(MotionEvent.class)); |
| } |
| |
| @Test |
| public void testSetWindowLayoutMode() throws Throwable { |
| mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity)); |
| mInstrumentation.waitForIdleSync(); |
| mPopupWindow = new PopupWindow(mTextView); |
| showPopup(); |
| |
| ViewGroup.LayoutParams p = mPopupWindow.getContentView().getRootView().getLayoutParams(); |
| assertEquals(0, p.width); |
| assertEquals(0, p.height); |
| |
| mPopupWindow.setWindowLayoutMode(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); |
| mActivityRule.runOnUiThread(() -> mPopupWindow.update(20, 50, 50, 50)); |
| |
| assertEquals(LayoutParams.WRAP_CONTENT, p.width); |
| assertEquals(LayoutParams.MATCH_PARENT, p.height); |
| } |
| |
| @Test |
| public void testAccessElevation() throws Throwable { |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| mActivityRule.runOnUiThread(() -> mPopupWindow.setElevation(2.0f)); |
| |
| showPopup(); |
| assertEquals(2.0f, mPopupWindow.getElevation(), 0.0f); |
| |
| dismissPopup(); |
| mActivityRule.runOnUiThread(() -> mPopupWindow.setElevation(4.0f)); |
| showPopup(); |
| assertEquals(4.0f, mPopupWindow.getElevation(), 0.0f); |
| |
| dismissPopup(); |
| mActivityRule.runOnUiThread(() -> mPopupWindow.setElevation(10.0f)); |
| showPopup(); |
| assertEquals(10.0f, mPopupWindow.getElevation(), 0.0f); |
| } |
| |
| @Test |
| public void testAccessSoftInputMode() throws Throwable { |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| mActivityRule.runOnUiThread( |
| () -> mPopupWindow.setSoftInputMode( |
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)); |
| |
| showPopup(); |
| assertEquals(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE, |
| mPopupWindow.getSoftInputMode()); |
| |
| dismissPopup(); |
| mActivityRule.runOnUiThread( |
| () -> mPopupWindow.setSoftInputMode( |
| WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)); |
| showPopup(); |
| assertEquals(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN, |
| mPopupWindow.getSoftInputMode()); |
| } |
| |
| @Test |
| public void testAccessSplitTouchEnabled() throws Throwable { |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| mActivityRule.runOnUiThread(() -> mPopupWindow.setSplitTouchEnabled(true)); |
| |
| showPopup(); |
| assertTrue(mPopupWindow.isSplitTouchEnabled()); |
| |
| dismissPopup(); |
| mActivityRule.runOnUiThread(() -> mPopupWindow.setSplitTouchEnabled(false)); |
| showPopup(); |
| assertFalse(mPopupWindow.isSplitTouchEnabled()); |
| |
| dismissPopup(); |
| mActivityRule.runOnUiThread(() -> mPopupWindow.setSplitTouchEnabled(true)); |
| showPopup(); |
| assertTrue(mPopupWindow.isSplitTouchEnabled()); |
| } |
| |
| @Test |
| public void testVerticallyClippedBeforeAdjusted() throws Throwable { |
| View parentWindowView = mActivity.getWindow().getDecorView(); |
| int parentWidth = parentWindowView.getMeasuredWidth(); |
| int parentHeight = parentWindowView.getMeasuredHeight(); |
| |
| // We make a popup which is too large to fit within the parent window. |
| // After showing it, we verify that it is shrunk to fit the window, |
| // rather than adjusted up. |
| mPopupWindow = createPopupWindow(createPopupContent(parentWidth*2, parentHeight*2)); |
| mPopupWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT); |
| mPopupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT); |
| |
| showPopup(R.id.anchor_middle); |
| |
| View popupRoot = mPopupWindow.getContentView(); |
| int measuredWidth = popupRoot.getMeasuredWidth(); |
| int measuredHeight = popupRoot.getMeasuredHeight(); |
| View anchor = mActivity.findViewById(R.id.anchor_middle); |
| |
| // The popup should occupy all available vertical space. |
| int[] anchorLocationInWindowXY = new int[2]; |
| anchor.getLocationInWindow(anchorLocationInWindowXY); |
| assertEquals(measuredHeight, |
| parentHeight - (anchorLocationInWindowXY[1] + anchor.getHeight())); |
| |
| // The popup should be vertically aligned to the anchor's bottom edge. |
| int[] anchorLocationOnScreenXY = new int[2]; |
| anchor.getLocationOnScreen(anchorLocationOnScreenXY); |
| int[] popupLocationOnScreenXY = new int[2]; |
| popupRoot.getLocationOnScreen(popupLocationOnScreenXY); |
| assertEquals(anchorLocationOnScreenXY[1] + anchor.getHeight(), popupLocationOnScreenXY[1]); |
| } |
| |
| @Test |
| public void testClipToScreenClipsToInsets() throws Throwable { |
| int[] orientationValues = {ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, |
| ActivityInfo.SCREEN_ORIENTATION_PORTRAIT}; |
| int currentOrientation = mActivity.getResources().getConfiguration().orientation; |
| if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) { |
| orientationValues[0] = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; |
| orientationValues[1] = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; |
| } |
| |
| for (int i = 0; i < 2; i++) { |
| final int orientation = orientationValues[i]; |
| if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT |
| && !hasDeviceFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)) { |
| // skip test for devices not supporting portrait orientation |
| continue; |
| } |
| mActivity.runOnUiThread(() -> |
| mActivity.setRequestedOrientation(orientation)); |
| mActivity.waitForConfigurationChanged(); |
| // Wait for main thread to be idle to make sure layout and draw have been performed |
| // before continuing. |
| mInstrumentation.waitForIdleSync(); |
| |
| View parentWindowView = mActivity.getWindow().getDecorView(); |
| int parentWidth = parentWindowView.getMeasuredWidth(); |
| int parentHeight = parentWindowView.getMeasuredHeight(); |
| |
| mPopupWindow = createPopupWindow(createPopupContent(parentWidth*2, parentHeight*2)); |
| mPopupWindow.setWidth(WindowManager.LayoutParams.MATCH_PARENT); |
| mPopupWindow.setHeight(WindowManager.LayoutParams.MATCH_PARENT); |
| mPopupWindow.setClipToScreenEnabled(true); |
| |
| showPopup(R.id.anchor_upper_left); |
| |
| View popupRoot = mPopupWindow.getContentView().getRootView(); |
| int measuredWidth = popupRoot.getMeasuredWidth(); |
| int measuredHeight = popupRoot.getMeasuredHeight(); |
| |
| // The visible frame will not include the insets. |
| Rect visibleFrame = new Rect(); |
| parentWindowView.getWindowVisibleDisplayFrame(visibleFrame); |
| |
| assertEquals(measuredWidth, visibleFrame.width()); |
| assertEquals(measuredHeight, visibleFrame.height()); |
| } |
| } |
| |
| @Test |
| public void testPositionAfterParentScroll() throws Throwable { |
| View.OnScrollChangeListener scrollChangeListener = mock( |
| View.OnScrollChangeListener.class); |
| |
| mActivityRule.runOnUiThread(() -> { |
| mActivity.setContentView(R.layout.popup_window_scrollable); |
| |
| View anchor = mActivity.findViewById(R.id.anchor_upper); |
| PopupWindow window = createPopupWindow(); |
| window.showAsDropDown(anchor); |
| }); |
| |
| mActivityRule.runOnUiThread(() -> { |
| View parent = mActivity.findViewById(R.id.main_container); |
| parent.scrollBy(0, 500); |
| parent.setOnScrollChangeListener(scrollChangeListener); |
| }); |
| |
| verify(scrollChangeListener, never()).onScrollChange( |
| any(View.class), anyInt(), anyInt(), anyInt(), anyInt()); |
| } |
| |
| @Test |
| public void testPositionAfterAnchorRemoval() throws Throwable { |
| mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| showPopup(R.id.anchor_middle); |
| |
| final ViewGroup container = (ViewGroup) mActivity.findViewById(R.id.main_container); |
| final View anchor = mActivity.findViewById(R.id.anchor_middle); |
| final LayoutParams anchorLayoutParams = anchor.getLayoutParams(); |
| |
| final int[] originalLocation = mPopupWindow.getContentView().getLocationOnScreen(); |
| |
| final int deltaX = 30; |
| final int deltaY = 20; |
| |
| // Scroll the container, the popup should move along with the anchor. |
| WidgetTestUtils.runOnMainAndLayoutSync( |
| mActivityRule, |
| mPopupWindow.getContentView().getRootView(), |
| () -> container.scrollBy(deltaX, deltaY), |
| false /* force layout */); |
| // Since the first layout might have been caused by the original scroll event (and not by |
| // the anchor change), we need to wait until all traversals are done. |
| mInstrumentation.waitForIdleSync(); |
| assertPopupLocation(originalLocation, deltaX, deltaY); |
| |
| // Detach the anchor, the popup should stay in the same location. |
| WidgetTestUtils.runOnMainAndLayoutSync( |
| mActivityRule, |
| mActivity.getWindow().getDecorView(), |
| () -> container.removeView(anchor), |
| false /* force layout */); |
| assertPopupLocation(originalLocation, deltaX, deltaY); |
| |
| // Scroll the container while the anchor is detached, the popup should not move. |
| WidgetTestUtils.runOnMainAndLayoutSync( |
| mActivityRule, |
| mActivity.getWindow().getDecorView(), |
| () -> container.scrollBy(deltaX, deltaY), |
| true /* force layout */); |
| mInstrumentation.waitForIdleSync(); |
| assertPopupLocation(originalLocation, deltaX, deltaY); |
| |
| // Re-attach the anchor, the popup should snap back to the new anchor location. |
| WidgetTestUtils.runOnMainAndLayoutSync( |
| mActivityRule, |
| mPopupWindow.getContentView().getRootView(), |
| () -> container.addView(anchor, anchorLayoutParams), |
| false /* force layout */); |
| assertPopupLocation(originalLocation, deltaX * 2, deltaY * 2); |
| } |
| |
| @Test |
| public void testAnchorInPopup() throws Throwable { |
| DisplayMetrics displayMetrics = mActivity.getResources().getDisplayMetrics(); |
| float dpWidth = displayMetrics.widthPixels / displayMetrics.density; |
| float dpHeight = displayMetrics.heightPixels / displayMetrics.density; |
| final int minDisplaySize = 320; |
| if (dpWidth < minDisplaySize || dpHeight < minDisplaySize) { |
| // On smaller screens the popups that this test is creating |
| // are not guaranteed to be properly aligned to their anchors. |
| return; |
| } |
| |
| mPopupWindow = createPopupWindow( |
| mActivity.getLayoutInflater().inflate(R.layout.popup_window, null)); |
| |
| final PopupWindow subPopup = |
| createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP)); |
| |
| // Check alignment without overlapping the anchor. |
| assertFalse(subPopup.getOverlapAnchor()); |
| |
| verifySubPopupPosition(subPopup, R.id.anchor_upper_left, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM); |
| verifySubPopupPosition(subPopup, R.id.anchor_middle_left, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM); |
| verifySubPopupPosition(subPopup, R.id.anchor_lower_left, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP); |
| |
| verifySubPopupPosition(subPopup, R.id.anchor_upper, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM); |
| verifySubPopupPosition(subPopup, R.id.anchor_middle, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM); |
| verifySubPopupPosition(subPopup, R.id.anchor_lower, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP); |
| |
| verifySubPopupPosition(subPopup, R.id.anchor_upper_right, R.id.anchor_lower_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM); |
| verifySubPopupPosition(subPopup, R.id.anchor_middle_right, R.id.anchor_lower_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM); |
| verifySubPopupPosition(subPopup, R.id.anchor_lower_right, R.id.anchor_lower_right, |
| RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP); |
| |
| // Check alignment while overlapping the anchor. |
| subPopup.setOverlapAnchor(true); |
| |
| final int anchorHeight = mActivity.findViewById(R.id.anchor_lower_right).getHeight(); |
| // To simplify the math assert that all three lower anchors are the same height. |
| assertEquals(anchorHeight, mActivity.findViewById(R.id.anchor_lower_left).getHeight()); |
| assertEquals(anchorHeight, mActivity.findViewById(R.id.anchor_lower).getHeight()); |
| |
| final int verticalSpaceBelowAnchor = anchorHeight * 2; |
| // Ensure that the subpopup is flipped vertically. |
| subPopup.setHeight(verticalSpaceBelowAnchor + 1); |
| |
| verifySubPopupPosition(subPopup, R.id.anchor_upper_left, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP); |
| verifySubPopupPosition(subPopup, R.id.anchor_middle_left, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP); |
| verifySubPopupPosition(subPopup, R.id.anchor_lower_left, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP); |
| |
| verifySubPopupPosition(subPopup, R.id.anchor_upper, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP); |
| verifySubPopupPosition(subPopup, R.id.anchor_middle, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP); |
| verifySubPopupPosition(subPopup, R.id.anchor_lower, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP); |
| |
| verifySubPopupPosition(subPopup, R.id.anchor_upper_right, R.id.anchor_lower_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP); |
| verifySubPopupPosition(subPopup, R.id.anchor_middle_right, R.id.anchor_lower_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP); |
| verifySubPopupPosition(subPopup, R.id.anchor_lower_right, R.id.anchor_lower_right, |
| RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP); |
| |
| // Re-test for the bottom anchor row ensuring that the subpopup not flipped vertically. |
| subPopup.setHeight(verticalSpaceBelowAnchor - 1); |
| |
| verifySubPopupPosition(subPopup, R.id.anchor_lower_left, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP); |
| verifySubPopupPosition(subPopup, R.id.anchor_lower, R.id.anchor_lower_right, |
| LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP); |
| verifySubPopupPosition(subPopup, R.id.anchor_lower_right, R.id.anchor_lower_right, |
| RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP); |
| |
| // Check that scrolling scrolls the sub popup along with the main popup. |
| showPopup(R.id.anchor_middle); |
| |
| mActivityRule.runOnUiThread(() -> subPopup.showAsDropDown( |
| mPopupWindow.getContentView().findViewById(R.id.anchor_middle))); |
| mInstrumentation.waitForIdleSync(); |
| |
| final int[] popupLocation = mPopupWindow.getContentView().getLocationOnScreen(); |
| final int[] subPopupLocation = subPopup.getContentView().getLocationOnScreen(); |
| |
| final int deltaX = 20; |
| final int deltaY = 30; |
| |
| final ViewGroup container = (ViewGroup) mActivity.findViewById(R.id.main_container); |
| WidgetTestUtils.runOnMainAndLayoutSync( |
| mActivityRule, |
| subPopup.getContentView().getRootView(), |
| () -> container.scrollBy(deltaX, deltaY), |
| false /* force layout */); |
| |
| // Since the first layout might have been caused by the original scroll event (and not by |
| // the anchor change), we need to wait until all traversals are done. |
| mInstrumentation.waitForIdleSync(); |
| |
| final int[] newPopupLocation = mPopupWindow.getContentView().getLocationOnScreen(); |
| assertEquals(popupLocation[0] - deltaX, newPopupLocation[0]); |
| assertEquals(popupLocation[1] - deltaY, newPopupLocation[1]); |
| |
| final int[] newSubPopupLocation = subPopup.getContentView().getLocationOnScreen(); |
| assertEquals(subPopupLocation[0] - deltaX, newSubPopupLocation[0]); |
| assertEquals(subPopupLocation[1] - deltaY, newSubPopupLocation[1]); |
| } |
| |
| private void verifySubPopupPosition(PopupWindow subPopup, int mainAnchorId, int subAnchorId, |
| int contentEdgeX, int operatorX, int anchorEdgeX, |
| int contentEdgeY, int operatorY, int anchorEdgeY) throws Throwable { |
| showPopup(mainAnchorId); |
| verifyPosition(subPopup, mPopupWindow.getContentView().findViewById(subAnchorId), |
| contentEdgeX, operatorX, anchorEdgeX, contentEdgeY, operatorY, anchorEdgeY); |
| dismissPopup(); |
| } |
| |
| private void assertPopupLocation(int[] originalLocation, int deltaX, int deltaY) { |
| final int[] actualLocation = mPopupWindow.getContentView().getLocationOnScreen(); |
| assertEquals(originalLocation[0] - deltaX, actualLocation[0]); |
| assertEquals(originalLocation[1] - deltaY, actualLocation[1]); |
| } |
| |
| private static class BaseTransition extends Transition { |
| @Override |
| public void captureStartValues(TransitionValues transitionValues) {} |
| |
| @Override |
| public void captureEndValues(TransitionValues transitionValues) {} |
| } |
| |
| private View createPopupContent(int width, int height) { |
| final View popupView = new View(mActivity); |
| popupView.setLayoutParams(new ViewGroup.LayoutParams(width, height)); |
| popupView.setBackgroundColor(Color.MAGENTA); |
| |
| return popupView; |
| } |
| |
| private PopupWindow createPopupWindow() { |
| PopupWindow window = new PopupWindow(mActivity); |
| window.setWidth(WINDOW_SIZE_DP); |
| window.setHeight(WINDOW_SIZE_DP); |
| window.setBackgroundDrawable(new ColorDrawable(Color.YELLOW)); |
| return window; |
| } |
| |
| private PopupWindow createPopupWindow(View content) { |
| PopupWindow window = createPopupWindow(); |
| window.setContentView(content); |
| return window; |
| } |
| |
| private boolean hasDeviceFeature(final String requiredFeature) { |
| return mContext.getPackageManager().hasSystemFeature(requiredFeature); |
| } |
| |
| private void showPopup(int resourceId) throws Throwable { |
| mActivityRule.runOnUiThread(() -> { |
| if (mPopupWindow == null || mPopupWindow.isShowing()) { |
| return; |
| } |
| View anchor = mActivity.findViewById(resourceId); |
| mPopupWindow.showAsDropDown(anchor); |
| assertTrue(mPopupWindow.isShowing()); |
| }); |
| mInstrumentation.waitForIdleSync(); |
| } |
| |
| private void showPopup() throws Throwable { |
| showPopup(R.id.anchor_upper_left); |
| } |
| |
| private void dismissPopup() throws Throwable { |
| mActivityRule.runOnUiThread(() -> { |
| if (mPopupWindow == null || !mPopupWindow.isShowing()) { |
| return; |
| } |
| mPopupWindow.dismiss(); |
| }); |
| mInstrumentation.waitForIdleSync(); |
| } |
| } |