io.selendroid.server.android.ViewHierarchyAnalyzer Maven / Gradle / Ivy
/*
* Copyright 2012-2014 eBay Software Foundation and selendroid committers.
*
* 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 io.selendroid.server.android;
import io.selendroid.server.ServerInstrumentation;
import io.selendroid.server.util.InstanceOfPredicate;
import io.selendroid.server.util.ListUtil;
import io.selendroid.server.util.SelendroidLogger;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import android.content.res.Resources;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.AbsListView;
import android.widget.ScrollView;
import com.android.internal.util.Predicate;
public class ViewHierarchyAnalyzer {
private static final ViewHierarchyAnalyzer INSTANCE = new ViewHierarchyAnalyzer();
public static ViewHierarchyAnalyzer getDefaultInstance() {
return INSTANCE;
}
public Set getTopLevelViews() {
Class> windowManager;
try {
String windowManagerClassName;
if (android.os.Build.VERSION.SDK_INT >= 17) {
windowManagerClassName = "android.view.WindowManagerGlobal";
} else {
windowManagerClassName = "android.view.WindowManagerImpl";
}
windowManager = Class.forName(windowManagerClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SecurityException e) {
throw new RuntimeException(e);
}
Field views;
Field instanceField;
try {
views = windowManager.getDeclaredField("mViews");
instanceField = windowManager.getDeclaredField(getWindowManagerString());
views.setAccessible(true);
instanceField.setAccessible(true);
Object instance = instanceField.get(null);
synchronized (windowManager) {
if (android.os.Build.VERSION.SDK_INT <= 18) {
return new HashSet(Arrays.asList(((View[]) views.get(instance))));
} else {
return new HashSet((ArrayList) views.get(instance));
}
}
} catch (Exception e) {
SelendroidLogger.error("Cannot get top level views", e);
return new HashSet();
}
}
private String getWindowManagerString() {
if (android.os.Build.VERSION.SDK_INT >= 17) {
return "sDefaultWindowManager";
} else if (android.os.Build.VERSION.SDK_INT >= 13) {
return "sWindowManager";
} else {
return "mWindowManager";
}
}
public View getRecentDecorView() {
return getRecentDecorView(getTopLevelViews());
}
private View getRecentDecorView(Set views) {
Collection decorViews =
(Collection) ListUtil.filter(new ArrayList(views), new DecorViewPredicate());
if (decorViews.isEmpty()) {
SelendroidLogger.warning("In class ViewHierarchyAnalyzer, no top level decor views!");
SelendroidLogger.warning("Top level views:");
for (View view: views) {
SelendroidLogger.warning(view.toString());
}
}
View container = null;
// candidate is to fall back to most recent 'shown' view if none have the 'window focus'
// this seems to be able to happen with menus
View candidate = null;
long drawingTime = 0;
long candidateTime = 0;
for (View view : decorViews) {
if (view.isShown() && view.getDrawingTime() > drawingTime) {
if (view.hasWindowFocus()) {
container = view;
drawingTime = view.getDrawingTime();
} else if (view.getDrawingTime() > candidateTime) {
candidate = view;
candidateTime = view.getDrawingTime();
}
}
}
if (container == null) {
container = candidate;
}
return container;
}
private static class DecorViewPredicate implements Predicate {
@Override
public boolean apply(E view) {
// PopupViewContainer can be a top level menu shown
// MultiPhoneDecorView is for Samsung devices
return "DecorView".equals(view.getClass().getSimpleName())
|| "PopupViewContainer".equals(view.getClass().getSimpleName())
|| "MultiPhoneDecorView".equals(view.getClass().getSimpleName());
}
}
public Collection getViews(List rootViews) {
final List views = new ArrayList();
for (View rootView : rootViews) {
if (rootView == null) {
continue;
}
if (rootView instanceof ViewGroup) {
addAllChilren((ViewGroup) rootView, views);
}
}
return views;
}
private void addAllChilren(ViewGroup viewGroup, List list) {
if (viewGroup == null || viewGroup.getChildCount() == 0) {
return;
}
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View childView = viewGroup.getChildAt(i);
list.add(childView);
if (childView instanceof ViewGroup) {
addAllChilren((ViewGroup) childView, list);
}
}
}
public static String getNativeId(View view) {
String id = "";
try {
id =
ServerInstrumentation.getInstance().getCurrentActivity().getResources()
.getResourceName(view.getId());
// remove the package name
id = id.substring(id.indexOf(':') + 1);
} catch (Resources.NotFoundException e) {
// can happen
}
return id;
}
public List findWebViews() {
final List webViews =
(List) ListUtil.filter(getViews(Arrays.asList(getRecentDecorView())),
new InstanceOfPredicate(WebView.class));
if (webViews.isEmpty()) {
return null;
}
return webViews;
}
public List findScrollableContainer() {
Collection allViews = getViews(Arrays.asList(getRecentDecorView()));
List container = new ArrayList();
List listview =
(List) ListUtil.filter(allViews, new InstanceOfPredicate(AbsListView.class));
if (listview != null && !listview.isEmpty()) {
container.addAll(listview);
}
List scrollview =
(List) ListUtil.filter(allViews, new InstanceOfPredicate(ScrollView.class));
container.addAll(scrollview);
List webview =
(List) ListUtil.filter(allViews, new InstanceOfPredicate(WebView.class));
container.addAll(webview);
return container;
}
public boolean isViewChieldOfCurrentRootView(View view) {
if (view == null) {
return false;
}
View rootView = ViewHierarchyAnalyzer.getDefaultInstance().getRecentDecorView();
return view.getRootView().equals(rootView);
}
}