package com.google.android.apps.common.testing.ui.espresso.matcher; import static com.google.common.base.Preconditions.checkNotNull; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.anyOf; import com.google.android.apps.common.testing.testrunner.ActivityLifecycleMonitor; import com.google.android.apps.common.testing.testrunner.ActivityLifecycleMonitorRegistry; import com.google.android.apps.common.testing.testrunner.Stage; import com.google.android.apps.common.testing.ui.espresso.NoActivityResumedException; import com.google.android.apps.common.testing.ui.espresso.Root; import com.google.common.collect.Lists; import android.app.Activity; import android.os.IBinder; import android.view.View; import android.view.WindowManager; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import java.util.Collection; import java.util.List; /** * A collection of matchers for {@link Root} objects. */ public final class RootMatchers { private RootMatchers() {} /** * Espresso's default {@link Root} matcher. */ @SuppressWarnings("unchecked") public static final Matcher<Root> DEFAULT = allOf( hasWindowLayoutParams(), allOf( anyOf( allOf(isDialog(), withDecorView(hasWindowFocus())), isSubwindowOfCurrentActivity()), isFocusable())); /** * Matches {@link Root}s that can take window focus. */ public static Matcher<Root> isFocusable() { return new TypeSafeMatcher<Root>() { @Override public void describeTo(Description description) { description.appendText("is focusable"); } @Override public boolean matchesSafely(Root root) { int flags = root.getWindowLayoutParams().get().flags; boolean r = !((flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0); return r; } }; } /** * Matches {@link Root}s that can receive touch events. */ public static Matcher<Root> isTouchable() { return new TypeSafeMatcher<Root>() { @Override public void describeTo(Description description) { description.appendText("is touchable"); } @Override public boolean matchesSafely(Root root) { int flags = root.getWindowLayoutParams().get().flags; boolean r = !((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0); return r; } }; } /** * Matches {@link Root}s that are dialogs (i.e. is not a window of the currently resumed * activity). */ public static Matcher<Root> isDialog() { return new TypeSafeMatcher<Root>() { @Override public void describeTo(Description description) { description.appendText("is dialog"); } @Override public boolean matchesSafely(Root root) { int type = root.getWindowLayoutParams().get().type; if ((type != WindowManager.LayoutParams.TYPE_BASE_APPLICATION && type < WindowManager.LayoutParams.LAST_APPLICATION_WINDOW)) { IBinder windowToken = root.getDecorView().getWindowToken(); IBinder appToken = root.getDecorView().getApplicationWindowToken(); if (windowToken == appToken) { // windowToken == appToken means this window isn't contained by any other windows. // if it was a window for an activity, it would have TYPE_BASE_APPLICATION. // therefore it must be a dialog box. return true; } } return false; } }; } /** * Matches {@link Root}s with decor views that match the given view matcher. */ public static Matcher<Root> withDecorView(final Matcher<View> decorViewMatcher) { checkNotNull(decorViewMatcher); return new TypeSafeMatcher<Root>() { @Override public void describeTo(Description description) { description.appendText("with decor view "); decorViewMatcher.describeTo(description); } @Override public boolean matchesSafely(Root root) { return decorViewMatcher.matches(root.getDecorView()); } }; } private static Matcher<View> hasWindowFocus() { return new TypeSafeMatcher<View>() { @Override public void describeTo(Description description) { description.appendText("has window focus"); } @Override public boolean matchesSafely(View view) { return view.hasWindowFocus(); } }; } private static Matcher<Root> hasWindowLayoutParams() { return new TypeSafeMatcher<Root>() { @Override public void describeTo(Description description) { description.appendText("has window layout params"); } @Override public boolean matchesSafely(Root root) { if (!root.getWindowLayoutParams().isPresent()) { return false; } return true; } }; } private static Matcher<Root> isSubwindowOfCurrentActivity() { return new TypeSafeMatcher<Root>() { @Override public void describeTo(Description description) { description.appendText("is subwindow of current activity"); } @Override public boolean matchesSafely(Root root) { boolean r = getResumedActivityTokens().contains(root.getDecorView().getApplicationWindowToken()); return r; } }; } private static List<IBinder> getResumedActivityTokens() { ActivityLifecycleMonitor activityLifecycleMonitor = ActivityLifecycleMonitorRegistry.getInstance(); Collection<Activity> resumedActivities = activityLifecycleMonitor.getActivitiesInStage(Stage.RESUMED); if (resumedActivities.isEmpty()) { throw new NoActivityResumedException("At least one activity should be in RESUMED stage."); } List<IBinder> tokens = Lists.newArrayList(); for (Activity activity : resumedActivities) { tokens.add(activity.getWindow().getDecorView().getApplicationWindowToken()); } return tokens; } }