/* MonkeyTalk - a cross-platform functional testing tool Copyright (C) 2012 Gorilla Logic, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.gorillalogic.fonemonkey; import java.lang.reflect.Field; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import com.gorillalogic.fonemonkey.automators.AutomationManager; import com.gorillalogic.fonemonkey.automators.IAutomator; import com.gorillalogic.fonemonkey.automators.WindowAutomator; public class FunctionalityAdder { static Set<View> processed = new HashSet<View>(); private static Set<View> roots = new LinkedHashSet<View>(); public static Set<View> getRoots() {return Collections.unmodifiableSet(roots);} // Walk the View tree and add functionality as necessary public static void walkTree(View root) { if (!processed.contains(root)) { processView(root); } if (!(root instanceof ViewGroup)) { return; } ViewGroup vg = (ViewGroup) root; for (int i = 0; i < vg.getChildCount(); ++i) { walkTree(vg.getChildAt(i)); } } private static void processView(View v) { if (v == null) { Log.log("null view passed, ignoring"); return; } // add the default listeners for this guy IAutomator automator = AutomationManager.findAutomator(v); if (automator != null) { boolean didInstall = automator.installDefaultListeners(); if (didInstall) { // Check if the view is clipped ActivityManager.checkIsClipped(v); processed.add(v); } } else { String id = Integer.toString(v.getId()); try { id = v.getContext().getResources().getResourceName(v.getId()); } catch (Throwable e) { } Log.log("no automator found for " + v + " (" + id + ")"); return; } View root = v.getRootView(); // if (roots.contains(root)) { if (isViewInRoots(root)) { return; } roots.add(root); Context c = root.getContext(); if (c instanceof Activity) { Activity act = (Activity) c; WindowAutomator.automate(act.getWindow()); } // Log.log("NEW ROOT " + root); root.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { public void onGlobalLayout() { Set<View> toRemove = new HashSet<View>(); HashSet<View> rootsAndDialogs = new HashSet<View>(roots); Dialog box = ActivityManager.getCurrentDialog(); if (box != null) { rootsAndDialogs.add(box.getWindow().getDecorView()); } // Log.log("GLOBAL LAYOUT"); for (View r : rootsAndDialogs) { /* * Need better expunge logic. if (!r.isShown()) { * toRemove.add(r); removeFromProcessed(r); * continue; } */ walkTree(r); } roots.removeAll(toRemove); } }); /* * root.getViewTreeObserver().addOnGlobalFocusChangeListener(new * ViewTreeObserver.OnGlobalFocusChangeListener() { public void * onGlobalFocusChanged(View oldView, View newView) { * Log.log("GLOBAL FOCUS " + newView); } }); */ } private static boolean isViewInRoots(View v) { for (View root: getRoots()) { if (isViewInRoot(v, root)) { return true; } } return false; } private static boolean isViewInRoot(View v, View root) { if (root.equals(v)) { return true; } if (root instanceof ViewGroup) { ViewGroup group=(ViewGroup)root; for (int i=0; i<group.getChildCount(); i++) { if (isViewInRoot(v, group.getChildAt(i))) { return true; } } } return false; } static void logWarn(String klass, View v) { Log.log("WARNING: You have a " + klass + " set on " + v + ". FoneMonkey needs to set its own listener in order to learn about any views added after onCreate() has finished. See FoneMonkey's Multiplexed" + klass + " class for a way to get around this problem."); } private static boolean hasHierarchyChangeListener(ViewGroup g) { // We need to get a handle to the actual parent ViewGroup Class klass = g.getClass(); while (!klass.equals(ViewGroup.class)) { klass = klass.getSuperclass(); } try { // Obviously this is not the best way to test if a listener // exists. But it's all we've got ... Field f = klass.getDeclaredField("mOnHierarchyChangeListener"); f.setAccessible(true); return f.get(g) != null; } catch (Exception e) { Log.log(e); } return false; } private static void removeFromProcessed(View root) { processed.remove(root); if (!(root instanceof ViewGroup)) return; ViewGroup vg = (ViewGroup) root; for (int i = 0; i < vg.getChildCount(); ++i) { removeFromProcessed(vg.getChildAt(i)); } } static void prTree(View root, String indent) { Log.log("" + root); if (!(root instanceof ViewGroup)) return; ViewGroup vg = (ViewGroup) root; for (int i = 0; i < vg.getChildCount(); ++i) { prTree(vg.getChildAt(i), indent + " "); } } public static Set<View> getRootsStripped() { View[] views=roots.toArray(new View[roots.size()]); // find and eliminate any roots which are children of other roots for (int i=0; i<views.length; i++) { View root = views[i]; if (root!=null) { for (int j=0; j<views.length; j++) { if (j!=i) { // don't compare to self View possibleParent = views[j]; if (isDescendant(root, possibleParent)) { views[i]=null; // this view is a descendant of another view break; } } } } } // everyone has been checked. Re-constitute the original roots Set<View> stripped = new LinkedHashSet<View>(); for (int i=0; i<views.length; i++) { View root = views[i]; if (root!=null) { stripped.add(root); } } return stripped; } private static boolean isDescendant(View v, View root) { if (v==null || root==null) { return false; } if (v.equals(root)) { return true; } if (root instanceof ViewGroup) { ViewGroup group = (ViewGroup)root; for (int i=0; i<group.getChildCount(); i++) { if (isDescendant(v, group.getChildAt(i))) { return true; } } } return false; } }