/* * 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); } }