blob: 486273d7faeffa101f373a0198853bfc528a7cbe [file] [log] [blame]
/*
* Copyright (C) 2014 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.support.test.espresso.matcher;
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.is;
import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import android.support.test.runner.lifecycle.Stage;
import android.support.test.espresso.NoActivityResumedException;
import android.support.test.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 that are popups - like autocomplete suggestions or the actionbar spinner.
*/
public static Matcher<Root> isPlatformPopup() {
return new TypeSafeMatcher<Root>() {
@Override
public boolean matchesSafely(Root item) {
return withDecorView(withClassName(
is("android.widget.PopupWindow$PopupViewContainer"))).matches(item);
}
@Override
public void describeTo(Description description) {
description.appendText("with decor view of type PopupWindow$PopupViewContainer");
}
};
}
/**
* 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;
}
}