/*
* 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.ServerInstrumentationProvider;
import io.selendroid.server.model.Factories;
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.app.Activity;
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<View> 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) {
Object viewsVal = views.get(instance);
if (viewsVal == null) {
return new HashSet<View>();
} else if (android.os.Build.VERSION.SDK_INT <= 18) {
return new HashSet<View>(Arrays.asList((View[]) viewsVal));
} else {
return new HashSet<View>((ArrayList) viewsVal);
}
}
} catch (Exception e) {
SelendroidLogger.error("Cannot get top level views", e);
return new HashSet<View>();
}
}
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<View> views) {
Predicate<Object> decorViewPredicate =
Factories.getPredicatesFactory().createDecorViewPredicate();
Collection<View> decorViews =
(Collection<View>) ListUtil.filter(new ArrayList<View>(views), 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;
}
public Collection<View> getViews(List<View> rootViews) {
final List<View> views = new ArrayList<View>();
for (View rootView : rootViews) {
if (rootView == null) {
continue;
}
if (rootView instanceof ViewGroup) {
addAllChilren((ViewGroup) rootView, views);
}
}
return views;
}
private void addAllChilren(ViewGroup viewGroup, List<View> 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) {
if (view == null || view.getId() == View.NO_ID) {
return "";
}
String id = "";
try {
Activity currentActivity = ServerInstrumentationProvider.getServerInstrumentationInstance().getCurrentActivity();
Resources resources;
if (currentActivity != null) {
resources = currentActivity.getResources();
} else {
resources = view.getResources();
}
if (resources != null) {
id = resources.getResourceName(view.getId());
// remove the package name
id = id.substring(id.indexOf(':') + 1);
}
} catch (Resources.NotFoundException e) {
// can happen
}
return id;
}
public List<WebView> findWebViews() {
final List<WebView> webViews =
(List<WebView>) ListUtil.filter(getViews(Arrays.asList(getRecentDecorView())),
new InstanceOfPredicate(WebView.class));
if (webViews.isEmpty()) {
return null;
}
return webViews;
}
public List<View> findScrollableContainer() {
Collection<View> allViews = getViews(Arrays.asList(getRecentDecorView()));
List<View> container = new ArrayList<View>();
List<AbsListView> listview =
(List<AbsListView>) ListUtil.filter(allViews, new InstanceOfPredicate(AbsListView.class));
if (listview != null && !listview.isEmpty()) {
container.addAll(listview);
}
List<ScrollView> scrollview =
(List<ScrollView>) ListUtil.filter(allViews, new InstanceOfPredicate(ScrollView.class));
container.addAll(scrollview);
List<WebView> webview =
(List<WebView>) 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);
}
}