package com.eaway.appcrawler.common; import android.support.test.InstrumentationRegistry; import android.support.test.uiautomator.Configurator; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; import android.support.test.uiautomator.UiObjectNotFoundException; import android.support.test.uiautomator.UiSelector; import android.util.Log; import com.eaway.appcrawler.Config; import java.util.ArrayList; import java.util.List; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; /** * UiScreen represent a unique screen that is worth testing. * UI of Android Activity acn change dynamically (e.g. using Fragment), thus one Activity may produce more than one UiScreens */ public class UiScreen { private static final String TAG = Config.TAG; private static final String TAG_MAIN = Config.TAG_MAIN; private static final String TAG_DEBUG = Config.TAG_DEBUG; public UiDevice device; public UiScreen parentScreen; public UiWidget parentWidget; // Widget in parent screen that can take us to this screen public List<UiScreen> childScreenList; // Child screens public List<UiWidget> widgetList; // Widgets in the screen that we interested in test (e.g. Clickable) public UiObject rootObject; // First UiObject in the screen public String pkg; // Packages public String signature; // Use to identify itself between other screens public String name; // Activity name public int depth = -1; // Depth in the UiTree public int id = -1; public int loop = 0; // Avoid infinite loop private boolean mFinished = false; // True if all the child widgets have been tested public UiScreen(UiScreen parent, UiWidget widget) { device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); UiObject root = device.findObject(new UiSelector().index(0)); init(parent, widget, root); } public UiScreen(UiScreen parent, UiWidget widget, UiObject root) { init(parent, widget, root); } public void init(UiScreen parent, UiWidget widget, UiObject root) { assertThat(root, notNullValue()); device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); parentScreen = parent; parentWidget = widget; rootObject = root; childScreenList = new ArrayList<UiScreen>(); widgetList = new ArrayList<UiWidget>(); try { pkg = root.getPackageName(); } catch (UiObjectNotFoundException e) { e.printStackTrace(); } signature = ""; name = device.getCurrentActivityName(); // FIXME: deprecated depth = (parentScreen == null) ? 0 : parentScreen.depth + 1; id = -1; mFinished = false; // Keep current timeout configurations Configurator conf = Configurator.getInstance(); long WaitForIdleTimeout = conf.getWaitForIdleTimeout(); long WaitForSelectorTimeout = conf.getWaitForSelectorTimeout(); long ActionAcknowledgmentTimeout = conf.getActionAcknowledgmentTimeout(); long ScrollAcknowledgmentTimeout = conf.getScrollAcknowledgmentTimeout(); conf.setWaitForIdleTimeout(0L); conf.setWaitForSelectorTimeout(0L); conf.setActionAcknowledgmentTimeout(0L); conf.setScrollAcknowledgmentTimeout(0L); // Build screen signature parseSignature(rootObject); // Rollback timeout configurations conf.setWaitForIdleTimeout(WaitForIdleTimeout); conf.setWaitForSelectorTimeout(WaitForSelectorTimeout); conf.setActionAcknowledgmentTimeout(ActionAcknowledgmentTimeout); conf.setScrollAcknowledgmentTimeout(ScrollAcknowledgmentTimeout); if (0 != pkg.compareToIgnoreCase(Config.sTargetPackage)) { return; } // Clickable int i = 0; UiObject clickable = null; do { clickable = null; clickable = device.findObject(new UiSelector().clickable(true).instance(i++)); if (clickable != null && clickable.exists()) widgetList.add(new UiWidget(clickable)); } while (clickable != null && clickable.exists()); // Nothing testable if (widgetList.size() == 0) mFinished = true; // Debug if (Config.sDebug) { Log.d(TAG_DEBUG, signature); for (UiWidget tmp : widgetList) { try { Log.d(TAG_DEBUG, tmp.uiObject.getClassName()); } catch (UiObjectNotFoundException e) { e.printStackTrace(); } } } } @Override public String toString() { // FIXME: Better to use StringBuilder String str = "name:" + name + ", id:" + id + ", depth:" + depth + ", finished:" + mFinished + ", signature:" + signature + ", widgets:" + widgetList.size(); for (int i = 0; i < widgetList.size(); i++) { UiWidget widget = widgetList.get(i); str += " " + i + ":" + widget.isFinished(); } return str; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof UiScreen)) { return false; } UiScreen c = (UiScreen) o; return this.signature.equals(c.signature); } public void update() { isFinished(); } public void setFinished(boolean finished) { mFinished = finished; if (mFinished == true) { for (UiWidget child : widgetList) { child.setFinished(mFinished); } } } public boolean isFinished() { if (mFinished == true) return mFinished; for (UiWidget child : widgetList) { if (!child.isFinished()) return false; } mFinished = true; return mFinished; } private boolean parseSignature(UiObject uiObject) { //Log.v(TAG, new Exception().getStackTrace()[0].getMethodName() + "()"); if ((uiObject == null) || !uiObject.exists()) return false; if (signature.length() > Config.sScreenSignatueLength) return true; try { // Screen signature: use classname list in the view hierarchy String classname = ""; for (String tmp : uiObject.getClassName().split("\\.")) { classname = tmp; } signature = signature.concat(classname + ";"); // Add all children recursively for (int i = 0; i < uiObject.getChildCount(); i++) { parseSignature(uiObject.getChild(new UiSelector().index(i))); } } catch (UiObjectNotFoundException e) { Log.e(TAG, "uiObject does not exists during parsing"); return false; } return true; } }