blob: 2c483d700d2bc24ff4bb2f2e1302408b0d3f40c3 [file] [log] [blame]
/*
* 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();
}
}