12da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov/*
22da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * Copyright (C) 2016 The Android Open Source Project
32da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov *
42da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * Licensed under the Apache License, Version 2.0 (the "License");
52da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * you may not use this file except in compliance with the License.
62da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * You may obtain a copy of the License at
72da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov *
82da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov *      http://www.apache.org/licenses/LICENSE-2.0
92da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov *
102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * Unless required by applicable law or agreed to in writing, software
112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * distributed under the License is distributed on an "AS IS" BASIS,
122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * See the License for the specific language governing permissions and
142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * limitations under the License.
152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov */
162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovpackage android.support.v7.widget;
172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
1870058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.Espresso.onData;
1970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.Espresso.onView;
2070058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.action.ViewActions.click;
2170058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
2270058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.assertion.ViewAssertions.matches;
2370058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.matcher.RootMatchers.isPlatformPopup;
2470058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.matcher.RootMatchers.withDecorView;
2570058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
2670058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.matcher.ViewMatchers.withClassName;
2770058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.matcher.ViewMatchers.withId;
2870058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.matcher.ViewMatchers.withText;
2970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas
3070058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.hamcrest.Matchers.allOf;
3170058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.hamcrest.Matchers.anything;
3270058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.hamcrest.core.Is.is;
3370058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.hamcrest.core.IsNot.not;
3470058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.junit.Assert.assertEquals;
3570058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.junit.Assert.assertNotNull;
3670058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.mockito.Mockito.any;
3770058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.mockito.Mockito.mock;
3870058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.mockito.Mockito.never;
3970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.mockito.Mockito.times;
4070058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.mockito.Mockito.verify;
410f3f71c172e87b05b0a6b766cf129f4be56179d5Chris Banes
422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.app.Instrumentation;
432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.content.res.Resources;
442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.graphics.Rect;
452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.graphics.drawable.Drawable;
462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.os.SystemClock;
4770058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport android.support.test.InstrumentationRegistry;
482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.support.test.espresso.Root;
492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.support.test.espresso.UiController;
502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.support.test.espresso.ViewAction;
5178f46d60d55f30fb384aa9da9eddb588f368e092Aurimas Liutikasimport android.support.test.filters.FlakyTest;
5278f46d60d55f30fb384aa9da9eddb588f368e092Aurimas Liutikasimport android.support.test.filters.LargeTest;
534bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikasimport android.support.test.filters.MediumTest;
5444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheevimport android.support.v4.view.MenuItemCompat;
552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.support.v7.app.BaseInstrumentationTestCase;
562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.support.v7.appcompat.test.R;
5744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheevimport android.text.TextUtils;
582da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.MenuInflater;
592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.MenuItem;
602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.MotionEvent;
612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.View;
622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.ViewParent;
632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.widget.Button;
642da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.widget.FrameLayout;
652da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6670058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.hamcrest.Description;
6770058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.hamcrest.Matcher;
6870058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.hamcrest.Matchers;
6970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.hamcrest.TypeSafeMatcher;
70c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikasimport org.junit.After;
7170058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.junit.Before;
7270058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.junit.Test;
732da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovpublic class PopupMenuTest extends BaseInstrumentationTestCase<PopupTestActivity> {
752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    // Since PopupMenu doesn't expose any access to the underlying
762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    // implementation (like ListPopupWindow.getListView), we're relying on the
7794940fe6cff4af4f084b8cb5692e0501e3fe7912Kirill Grouchnikov    // class name of the list view from MenuPopupWindow that is being used
782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    // in PopupMenu. This is not the cleanest, but it's not making any assumptions
792da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    // on the platform-specific details of the popup windows.
802da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private static final String DROP_DOWN_CLASS_NAME =
8194940fe6cff4af4f084b8cb5692e0501e3fe7912Kirill Grouchnikov            "android.support.v7.widget.MenuPopupWindow$MenuDropDownListView";
822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private FrameLayout mContainer;
832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private Button mButton;
852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private PopupMenu mPopupMenu;
872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
88eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov    private Resources mResources;
89eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov
90eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov    private View mMainDecorView;
91eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov
922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public PopupMenuTest() {
932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        super(PopupTestActivity.class);
942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
960f3f71c172e87b05b0a6b766cf129f4be56179d5Chris Banes    @Before
972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public void setUp() throws Exception {
98eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        final PopupTestActivity activity = mActivityTestRule.getActivity();
992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        mContainer = (FrameLayout) activity.findViewById(R.id.container);
1002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        mButton = (Button) mContainer.findViewById(R.id.test_button);
101eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        mResources = mActivityTestRule.getActivity().getResources();
102eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        mMainDecorView = mActivityTestRule.getActivity().getWindow().getDecorView();
1032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
1042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
105c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas    @After
106c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas    public void tearDown() {
107c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas        if (mPopupMenu != null) {
108c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
109c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas                @Override
110c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas                public void run() {
111c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas                    mPopupMenu.dismiss();
112c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas                }
113c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas            });
114c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas        }
115c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas    }
116c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas
1172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
1184bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
11944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev    public void testBasicContent() throws Throwable {
1202da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final Builder menuBuilder = new Builder();
1212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
1222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
1232da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
1242da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        assertNotNull("Popup menu created", mPopupMenu);
12544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev
12644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        final MenuItem hightlightItem = mPopupMenu.getMenu().findItem(R.id.action_highlight);
12744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        assertEquals(mResources.getString(R.string.popup_menu_highlight_description),
12844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                MenuItemCompat.getContentDescription(hightlightItem));
12944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        assertEquals(mResources.getString(R.string.popup_menu_highlight_tooltip),
13044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                MenuItemCompat.getTooltipText(hightlightItem));
13144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev
13244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        final MenuItem editItem = mPopupMenu.getMenu().findItem(R.id.action_edit);
13344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        assertNotNull(MenuItemCompat.getContentDescription(hightlightItem));
13444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        assertNotNull(MenuItemCompat.getTooltipText(hightlightItem));
13544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        mActivityTestRule.runOnUiThread(new Runnable() {
13644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            @Override
13744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            public void run() {
13844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                MenuItemCompat.setContentDescription(editItem,
13944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                        mResources.getString(R.string.popup_menu_edit_description));
14044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                MenuItemCompat.setTooltipText(editItem,
14144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                        mResources.getString(R.string.popup_menu_edit_tooltip));
14244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            }
14344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        });
14444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev
1452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
1462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Use a custom matcher to check the visibility of the drop down list view instead.
1472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
1482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .inRoot(isPlatformPopup()).check(matches(isDisplayed()));
1492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
1502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Note that MenuItem.isVisible() refers to the current "data" visibility state
1512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // and not the "on screen" visibility state. This is why we're testing the display
1522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // visibility of our main and sub menu items.
15370058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        // onData(anything()).atPosition(x) operates on the content of the popup. It is used to
15470058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        // scroll the popup if necessary to make sure the menu item views are visible.
15570058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        onData(anything()).atPosition(0).check(matches(isDisplayed()));
156eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_highlight)))
157eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
15844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                .check(matches(isDisplayed()))
15944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                .check(matches(selfOrParentWithContentDescription(
16044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                        mResources.getString(R.string.popup_menu_highlight_description))));
16170058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        onData(anything()).atPosition(1).check(matches(isDisplayed()));
162eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_edit)))
163eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
16444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                .check(matches(isDisplayed()))
16544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                .check(matches(selfOrParentWithContentDescription(
16644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                        mResources.getString(R.string.popup_menu_edit_description))));
16770058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        onData(anything()).atPosition(2).check(matches(isDisplayed()));
168eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_delete)))
169eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
1702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
17170058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        onData(anything()).atPosition(3).check(matches(isDisplayed()));
172eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_ignore)))
173eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
1742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
17570058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        onData(anything()).atPosition(4).check(matches(isDisplayed()));
176eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share)))
177eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
1782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
17970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        onData(anything()).atPosition(5).check(matches(isDisplayed()));
180eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_print)))
181eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
1822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
1832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
1842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Share submenu items should not be visible
185eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_email)))
186eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
1872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(doesNotExist());
188eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
189eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
1902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(doesNotExist());
1912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
1922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
1932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    /**
1942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * Returns the location of our popup menu in its window.
1952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     */
1962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private int[] getPopupLocationInWindow() {
1972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final int[] location = new int[2];
1982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
1992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .inRoot(isPlatformPopup()).perform(new ViewAction() {
2002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public Matcher<View> getConstraints() {
2022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return isDisplayed();
2032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public String getDescription() {
2072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return "Popup matcher";
2082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2092da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void perform(UiController uiController, View view) {
2122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                view.getLocationInWindow(location);
2132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
2152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        return location;
2162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
2172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2182da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    /**
2192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * Returns the location of our popup menu on the screen.
2202da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     */
2212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private int[] getPopupLocationOnScreen() {
2222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final int[] location = new int[2];
2232da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
2242da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .inRoot(isPlatformPopup()).perform(new ViewAction() {
2252da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2262da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public Matcher<View> getConstraints() {
2272da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return isDisplayed();
2282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2292da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2302da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public String getDescription() {
2322da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return "Popup matcher";
2332da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2342da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void perform(UiController uiController, View view) {
2372da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                view.getLocationOnScreen(location);
2382da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2392da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
2402da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        return location;
2412da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
2422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    /**
2442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * Returns the combined padding around the content of our popup menu.
2452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     */
2462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private Rect getPopupPadding() {
2472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final Rect result = new Rect();
2482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
2492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .inRoot(isPlatformPopup()).perform(new ViewAction() {
2502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public Matcher<View> getConstraints() {
2522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return isDisplayed();
2532da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public String getDescription() {
2572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return "Popup matcher";
2582da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void perform(UiController uiController, View view) {
2622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                // Traverse the parent hierarchy and combine all their paddings
2632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                result.setEmpty();
2642da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                final Rect current = new Rect();
2652da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                while (true) {
2662da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    ViewParent parent = view.getParent();
2672da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    if (parent == null || !(parent instanceof View)) {
2682da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        return;
2692da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    }
2702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2712da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    view = (View) parent;
2722da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    Drawable currentBackground = view.getBackground();
2732da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    if (currentBackground != null) {
2742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        currentBackground.getPadding(current);
2752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        result.left += current.left;
2762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        result.right += current.right;
2772da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        result.top += current.top;
2782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        result.bottom += current.bottom;
2792da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    }
2802da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                }
2812da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
2832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        return result;
2842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
2852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    /**
2872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * Returns a root matcher that matches roots that have window focus on their decor view.
2882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     */
2892da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private static Matcher<Root> hasWindowFocus() {
2902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        return new TypeSafeMatcher<Root>() {
2912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void describeTo(Description description) {
2932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                description.appendText("has window focus");
2942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public boolean matchesSafely(Root root) {
2982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                View rootView = root.getDecorView();
2992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return rootView.hasWindowFocus();
3002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
3012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        };
3022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
3032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
30478f46d60d55f30fb384aa9da9eddb588f368e092Aurimas Liutikas    @FlakyTest(bugId = 33669575)
3052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
30678f46d60d55f30fb384aa9da9eddb588f368e092Aurimas Liutikas    @LargeTest
3072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public void testAnchoring() {
3082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder();
3092da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
3102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
3122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final int[] anchorOnScreenXY = new int[2];
3142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final int[] popupOnScreenXY = getPopupLocationOnScreen();
3152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final int[] popupInWindowXY = getPopupLocationInWindow();
3162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final Rect popupPadding = getPopupPadding();
3172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3182da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        mButton.getLocationOnScreen(anchorOnScreenXY);
3192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3202da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Allow for off-by-one mismatch in anchoring
3212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        assertEquals("Anchoring X", anchorOnScreenXY[0] + popupInWindowXY[0],
3222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                popupOnScreenXY[0], 1);
323d7d01383deb3aac00ea54aab340cb43ff1ba3984Kirill Grouchnikov        assertEquals("Anchoring Y",
324d7d01383deb3aac00ea54aab340cb43ff1ba3984Kirill Grouchnikov                anchorOnScreenXY[1] + popupInWindowXY[1] + mButton.getHeight() - popupPadding.top,
32594940fe6cff4af4f084b8cb5692e0501e3fe7912Kirill Grouchnikov                popupOnScreenXY[1], 1);
3262da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
3272da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
3294bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
33042e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas    public void testDismissalViaAPI() throws Throwable {
3312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder().withDismissListener();
3322da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
3332da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3342da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
3352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Since PopupMenu is not a View, we can't use Espresso's view actions to invoke
3372da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // the dismiss() API
33842e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
3392da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
3402da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void run() {
3412da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                mPopupMenu.dismiss();
3422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
3432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
3442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
34531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnDismissListener, times(1)).onDismiss(mPopupMenu);
34631c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
3472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
3482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Use a custom matcher to check the visibility of the drop down list view instead.
3492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
3502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
3512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
3534bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
3542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public void testDismissalViaTouch() throws Throwable {
3552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder().withDismissListener();
3562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
3572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3582da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
3592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Determine the location of the popup on the screen so that we can emulate
3612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // a tap outside of its bounds to dismiss it
3622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final int[] popupOnScreenXY = getPopupLocationOnScreen();
3632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final Rect popupPadding = getPopupPadding();
3642da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3652da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3662da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        int emulatedTapX = popupOnScreenXY[0] - popupPadding.left - 20;
3672da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        int emulatedTapY = popupOnScreenXY[1] + popupPadding.top + 20;
3682da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3692da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // The logic below uses Instrumentation to emulate a tap outside the bounds of the
3702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // displayed popup menu. This tap is then treated by the framework to be "split" as
3712da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // the ACTION_OUTSIDE for the popup itself, as well as DOWN / MOVE / UP for the underlying
3722da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // view root if the popup is not modal.
3732da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // It is not correct to emulate these two sequences separately in the test, as it
3742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // wouldn't emulate the user-facing interaction for this test. Note that usage
3752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // of Instrumentation is necessary here since Espresso's actions operate at the level
3762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // of view or data. Also, we don't want to use View.dispatchTouchEvent directly as
3772da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // that would require emulation of two separate sequences as well.
3782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
379eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
3802da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3812da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Inject DOWN event
3822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        long downTime = SystemClock.uptimeMillis();
3832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        MotionEvent eventDown = MotionEvent.obtain(
3842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                downTime, downTime, MotionEvent.ACTION_DOWN, emulatedTapX, emulatedTapY, 1);
3852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        instrumentation.sendPointerSync(eventDown);
3862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Inject MOVE event
3882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        long moveTime = SystemClock.uptimeMillis();
3892da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        MotionEvent eventMove = MotionEvent.obtain(
3902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                moveTime, moveTime, MotionEvent.ACTION_MOVE, emulatedTapX, emulatedTapY, 1);
3912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        instrumentation.sendPointerSync(eventMove);
3922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Inject UP event
3942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        long upTime = SystemClock.uptimeMillis();
3952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        MotionEvent eventUp = MotionEvent.obtain(
3962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                upTime, upTime, MotionEvent.ACTION_UP, emulatedTapX, emulatedTapY, 1);
3972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        instrumentation.sendPointerSync(eventUp);
3982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Wait for the system to process all events in the queue
4002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        instrumentation.waitForIdleSync();
4012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // At this point our popup should not be showing and should have notified its
4032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // dismiss listener
40431c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnDismissListener, times(1)).onDismiss(mPopupMenu);
4052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
4062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
4072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
4094bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
4102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public void testSimpleMenuItemClickViaEvent() {
4112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder().withMenuItemClickListener();
4122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
4132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
4152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
41631c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that our menu item click listener hasn't been called yet
41731c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
4182da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
419eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_delete)))
420eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
4212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .perform(click());
42231c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
42331c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that out menu item click listener has been called with the expected menu item
42431c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
42531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_delete));
4262da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4272da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Popup menu should be automatically dismissed on selecting an item
4282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
4292da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
4302da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
4324bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
43342e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas    public void testSimpleMenuItemClickViaAPI() throws Throwable {
4342da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder().withMenuItemClickListener();
4352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
4362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4372da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
4382da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
43931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that our menu item click listener hasn't been called yet
44031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
44131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
44242e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
4432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
4442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void run() {
4452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                mPopupMenu.getMenu().performIdentifierAction(R.id.action_highlight, 0);
4462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
4472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
4482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
44931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that out menu item click listener has been called with the expected menu item
45031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
45131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_highlight));
4522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4532da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Popup menu should be automatically dismissed on selecting an item
4542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
4552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
4562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
4584bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
4592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public void testSubMenuClicksViaEvent() throws Throwable {
4602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder().withMenuItemClickListener();
4612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
4622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
4642da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
46531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that our menu item click listener hasn't been called yet
46631c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
4672da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
468eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share)))
469eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
4702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .perform(click());
47131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
47231c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that out menu item click listener has been called with the expected menu item
47331c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
47431c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_share));
4752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Sleep for a bit to allow the menu -> submenu transition to complete
4772da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Thread.sleep(1000);
4782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4792da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // At this point we should now have our sub-menu displayed. At this point on newer
4802da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // platform versions (L+) we have two view roots on the screen - one for the main popup
4812da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // menu and one for the submenu that has just been activated. If we only use the
4822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // logic based on decor view, Espresso will time out on waiting for the first root
4832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // to acquire window focus. This is why from this point on in this test we are using
4842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // two root conditions to detect the submenu - one with decor view not being the same
4852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // as the decor view of our main activity window, and the other that checks for window
4862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // focus.
4872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
4892da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Use a custom matcher to check the visibility of the drop down list view instead.
4902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
491eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
4922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
4932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Note that MenuItem.isVisible() refers to the current "data" visibility state
4952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // and not the "on screen" visibility state. This is why we're testing the display
4962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // visibility of our main and sub menu items.
4972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Share submenu items should now be visible
499eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_email)))
500eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
502eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
503eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
5052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Now click an item in the sub-menu
507eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
508eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5092da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .perform(click());
51031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
51131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that out menu item click listener has been called with the expected menu item
51231c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
51331c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_share_circles));
5142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Popup menu should be automatically dismissed on selecting an item in the submenu
5162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
5172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
5182da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
5204bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
5212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public void testSubMenuClicksViaAPI() throws Throwable {
5222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder().withMenuItemClickListener();
5232da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
5242da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5252da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
5262da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
52731c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that our menu item click listener hasn't been called yet
52831c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
52931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
53042e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
5312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
5322da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void run() {
5332da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                mPopupMenu.getMenu().performIdentifierAction(R.id.action_share, 0);
5342da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
5352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
5362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
53731c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that out menu item click listener has been called with the expected menu item
53831c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
53931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_share));
5402da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5412da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Sleep for a bit to allow the menu -> submenu transition to complete
5422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Thread.sleep(1000);
5432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // At this point we should now have our sub-menu displayed. At this point on newer
5452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // platform versions (L+) we have two view roots on the screen - one for the main popup
5462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // menu and one for the submenu that has just been activated. If we only use the
5472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // logic based on decor view, Espresso will time out on waiting for the first root
5482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // to acquire window focus. This is why from this point on in this test we are using
5492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // two root conditions to detect the submenu - one with decor view not being the same
5502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // as the decor view of our main activity window, and the other that checks for window
5512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // focus.
5522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5532da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
5542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Use a custom matcher to check the visibility of the drop down list view instead.
5552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
556eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
5582da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Note that MenuItem.isVisible() refers to the current "data" visibility state
5602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // and not the "on screen" visibility state. This is why we're testing the display
5612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // visibility of our main and sub menu items.
5622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Share submenu items should now be visible
564eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_email)))
565eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5662da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
567eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
568eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5692da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
5702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5712da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Now ask the share submenu to perform an action on its specific menu item
57242e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
5732da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
5742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void run() {
5752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_share).getSubMenu().
5762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        performIdentifierAction(R.id.action_share_email, 0);
5772da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
5782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
57931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
58031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that out menu item click listener has been called with the expected menu item
58131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
58231c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_share_email));
5832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Popup menu should be automatically dismissed on selecting an item in the submenu
5852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
5862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
5872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    /**
5892da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * Inner helper class to configure an instance of <code>PopupMenu</code> for the
5902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * specific test. The main reason for its existence is that once a popup menu is shown
5912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * with the show() method, most of its configuration APIs are no-ops. This means that
5922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * we can't add logic that is specific to a certain test once it's shown and we have a
5932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * reference to a displayed PopupMenu.
5942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     */
59531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov    public class Builder {
5962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        private boolean mHasDismissListener;
5972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        private boolean mHasMenuItemClickListener;
5982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
59931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        private PopupMenu.OnMenuItemClickListener mOnMenuItemClickListener;
60031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        private PopupMenu.OnDismissListener mOnDismissListener;
6012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        public Builder withMenuItemClickListener() {
6032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            mHasMenuItemClickListener = true;
6042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            return this;
6052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        }
6062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        public Builder withDismissListener() {
6082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            mHasDismissListener = true;
6092da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            return this;
6102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        }
6112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        private void show() {
6132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            mPopupMenu = new PopupMenu(mContainer.getContext(), mButton);
6142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            final MenuInflater menuInflater = mPopupMenu.getMenuInflater();
6152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            menuInflater.inflate(R.menu.popup_menu, mPopupMenu.getMenu());
6162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            if (mHasMenuItemClickListener) {
61831c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                // Register a mock listener to be notified when a menu item in our popup menu has
6192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                // been clicked.
62031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mOnMenuItemClickListener = mock(PopupMenu.OnMenuItemClickListener.class);
62131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.setOnMenuItemClickListener(mOnMenuItemClickListener);
6222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
6232da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6242da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            if (mHasDismissListener) {
62531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                // Register a mock listener to be notified when our popup menu is dismissed.
62631c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mOnDismissListener = mock(PopupMenu.OnDismissListener.class);
62731c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.setOnDismissListener(mOnDismissListener);
6282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
6292da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6302da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            // Show the popup menu
6312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            mPopupMenu.show();
6322da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        }
6332da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6342da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        public void wireToActionButton() {
6352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            mButton.setOnClickListener(new View.OnClickListener() {
6362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                @Override
6372da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                public void onClick(View v) {
6382da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    show();
6392da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                }
6402da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            });
6412da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        }
6422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
64344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev
64444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev    private static Matcher<View> selfOrParentWithContentDescription(final CharSequence expected) {
64544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        return new TypeSafeMatcher<View>() {
64644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            @Override
64744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            public void describeTo(Description description) {
64844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                description.appendText("self of parent has content description: " + expected);
64944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            }
65044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev
65144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            @Override
65244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            public boolean matchesSafely(View view) {
65344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                return TextUtils.equals(expected, getSelfOrParentContentDescription(view));
65444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            }
65544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev
65644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            private CharSequence getSelfOrParentContentDescription(View view) {
65744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                while (view != null) {
65844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    final CharSequence contentDescription = view.getContentDescription();
65944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    if (contentDescription != null) {
66044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                        return contentDescription;
66144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    }
66244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    final ViewParent parent = view.getParent();
66344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    if (!(parent instanceof View)) {
66444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                        break;
66544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    }
66644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    view = (View) parent;
66744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                }
66844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                return null;
66944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            }
67044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        };
67144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev    }
6722da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov}
673