// Copyright 2012 Georg-August-Universität Göttingen, Germany // // 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 de.ugoe.cs.autoquest.plugin.android.guimodel; import java.util.List; import de.ugoe.cs.autoquest.eventcore.IEventTargetSpec; import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec; /** *

* Implements the {@link IGUIElementSpec} for {@link ANDROIDGUIElement}s. *

* * @version 1.0 * @author Florian Unger */ public class ANDROIDGUIElementSpec implements IGUIElementSpec { /** *

* Default serial version UID *

*/ private static final long serialVersionUID = 1L; /* * (non-Javadoc) * * @see de.ugoe.cs.autoquest.androidmonitor.AndroidmonitorLogFile#logComponent() */ /** *

* Hash code of the GUI element. Used as unique identifier during parsing a log file. Note that * it is possible that the hash code of an element changes over several log files even if they * come from the same target. *

*/ private int elementHash; /** *

* Path to an element in an activity. e.g. a path of a button could look like * MainActivity/DecorView/ActionBarOverlayLayout/FrameLayout/RelativeLayout/Button *

*/ private String path; /* * (non-Javadoc) * * @see http://developer.android.com/reference/android/view/View.html#getId() */ /** *

* Id of the object as it is returned by view.getId(). *

*/ private int index; /** *

* Current name of the GUI element *

*/ private String name; /** *

* The type of GUI element, i.e., the class of the android GUI element. *

*/ private String type; /** *

* The position of the element in the original GUI. *

*/ private int elementPosition; /** *

* Type hierarchy of the class itself. *

*/ private List typeHierarchy = null; /* * (non-Javadoc) * * @see * de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec#getSecificationSimilarity(IGUIElementSpec * ) */ @Override public boolean getSimilarity(IEventTargetSpec other) { if (this == other) { return true; } if (!(other instanceof ANDROIDGUIElementSpec)) { return false; } ANDROIDGUIElementSpec otherSpec = (ANDROIDGUIElementSpec) other; if (type == null ? otherSpec.type != null : !type.equals(otherSpec.type)) { return false; } /* * Up to now, we compared, if the basics match. Due to testing with different virtual * devices it seems to be the case that the id of a view (named index here) keeps the same * even on different devices even if the hashCode changes. Some of the GUI elements does not * have an id (id is -1). */ if (otherSpec.getIndex() > 1 && getIndex() == otherSpec.getIndex()) { return true; } /* * Path and label of the elements fits together. In this case it is most likely that this * elements fits together. This only makes sense in the case a label exists. */ if (!"NOT_SET".equals(otherSpec.getName()) && !"NOT_SET".equals(getName()) && !otherSpec.getName().contains("image:") && !getName().contains("image:") && otherSpec.getName().equals(getName()) && otherSpec.getPath().equals(getPath())) { return true; } /* * Path and position fits together. In this case it is most likely that this elements fits * together. */ if (otherSpec.getPath().equals(getPath()) && otherSpec.getElementPosition() == getElementPosition()) { return true; } /* * Two elements could also be similar if the path of the elements is equal and the name is * set, equal and not equal to "image:" even if index <= 1. This comparison is not * implemented up to know due to the reason that all recorded elements up to 2015/01 either * have an index > 1 or no name to be compared. * * In all other cases up to know it is not clear if two elements are similar. * * Not working: * * - Position of the elements: Due to the reason that there are a lot of different displays * on the android market and the in the most cases the layout depends on the display size * (different resolutions) similar elements have different positions. */ return false; } /* * (non-Javadoc) * * @see de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec#getType() */ @Override public String getType() { return type; } /* * (non-Javadoc) * * @see de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec#getTypeHierarchy () */ @Override public String[] getTypeHierarchy() { if (typeHierarchy == null) { return new String[] { (getType()) }; } else return typeHierarchy.toArray(new String[typeHierarchy.size()]); } /** *

* Returns the object hash of the specified GUI element. *

* * @return the elementHash */ public int getElementHash() { return elementHash; } /** *

* Returns the path associated with the specified GUI element. *

* * @return the path to an element */ public String getPath() { return path; } /** *

* Returns the GUI element identifier. *

* * @return identifier of the GUI element */ public int getIndex() { return index; } /** *

* Returns the name of the specified GUI element. Displayed text in the application or image * name. *

* * @return text or image of the GUI element. */ public String getName() { if (name == null || name.trim().length() == 0) { return "NOT_SET"; } return name; } /** * *

* Return the position of the element in the original GUI. *

* * @return position of the element in the original GUI. */ public int getElementPosition() { return elementPosition; } /** * *

* Set the position of the element in the original GUI. *

*/ public void setElementPosition(int elementPosition) { this.elementPosition = elementPosition; } /** *

* Sets the GUI element identifier. *

* * @param indentifier */ public void setIndex(int index) { this.index = index; } /** * Set the hash code associated with the GUI element. * * @param hash * the hash of an element object */ public void setElementHash(int hash) { this.elementHash = hash; } /** * Set the path associated with the specified GUI element. * * @param path * the path to an element */ public void setPath(String path) { this.path = path; } /** *

* Sets the type of the specified GUI element. *

* * @param type * the type */ public void setType(String type) { this.type = type; } /** *

* Sets the name of the specified GUI element. Displayed text in the application or image name. *

* * @param name * the name */ public void setName(String name) { this.name = name; } /** *

* Sets the type hierarchy of the specified GUI element. *

*/ public void setTypeHierarchy(List typeHierarchy) { this.typeHierarchy = typeHierarchy; } /* * (non-Javadoc) * * @see de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec#equals(IGUIElementSpec) */ @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof ANDROIDGUIElementSpec)) { return false; } ANDROIDGUIElementSpec otherSpec = (ANDROIDGUIElementSpec) other; return (elementHash == otherSpec.elementHash) && (path == null ? otherSpec.path == null : path.equals(otherSpec.path)) && (index == otherSpec.index) && (elementPosition == otherSpec.elementPosition) && (name == null ? otherSpec.name == null : name.equals(otherSpec.name)) && (type == null ? otherSpec.type == null : type.equals(otherSpec.type)); } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { // 17 due to the reason that this is a prime number. int result = 17; /* * 31 due to the reason that a lot of VM's could optimize this multiplication by a shift. * Source: Effective Java, Joshua Bloch, 2008, p.48 */ result = 31 * result + elementHash; result = 31 * result + path.hashCode(); result = 31 * result + index; result = 31 * result + getName().hashCode(); result = 31 * result + type.hashCode(); result = 31 * result + elementPosition; return result; } }