native/src/com/android/commands/monkey/MonkeySourceApe.java:338-375 SHA256 200f76ef08f336dd0e70daeee6829d5f55e86d77a03775c9c7bfe6e31e9165ae 338: 339: private boolean validateClickAction(ModelAction action) { 340: return validateBounds(action); 341: } 342: 343: private boolean validateBounds(ModelAction action) { 344: GUITreeNode node = action.getResolvedNode(); 345: return !node.isEmpty() && !node.isOutOfRoot(); 346: } 347: 348: private boolean validateScrollAction(ModelAction action) { 349: return validateBounds(action); 350: } 351: 352: public boolean validateResolvedAction(ModelAction action) { 353: switch (action.getType()) { 354: case EVENT_START: 355: case EVENT_RESTART: 356: case EVENT_CLEAN_RESTART: 357: case EVENT_NOP: 358: case EVENT_ACTIVATE: 359: return true; 360: case MODEL_BACK: 361: return true; 362: case MODEL_CLICK: 363: case MODEL_LONG_CLICK: 364: return validateClickAction(action); 365: case MODEL_SCROLL_BOTTOM_UP: 366: case MODEL_SCROLL_TOP_DOWN: 367: case MODEL_SCROLL_LEFT_RIGHT: 368: case MODEL_SCROLL_RIGHT_LEFT: 369: return validateScrollAction(action); 370: default: 371: throw new RuntimeException("Should not reach here"); 372: } 373: } 374: 375: protected void generateActivateEvent() { native/src/com/android/commands/monkey/MonkeySourceApe.java:425-475 SHA256 200f76ef08f336dd0e70daeee6829d5f55e86d77a03775c9c7bfe6e31e9165ae 425: protected void generateEvents() { 426: if (hasEvent()) { 427: return; 428: } 429: 430: ComponentName topComp = null; 431: AccessibilityNodeInfo info = null; 432: int repeat = refectchInfoCount; 433: 434: Action action = null; 435: while (repeat-- > 0) { 436: topComp = this.getTopActivityComponentName(); 437: info = getRootInActiveWindow(); 438: // this two operations may not be the same 439: if (info == null) { 440: sleep(refectchInfoWaitingInterval); 441: continue; 442: } 443: if (info != null) { 444: nullInfoCounter = 0; 445: action = mAgent.updateState(topComp, info); 446: if (action == null) { 447: throw new NullPointerException("Resolved action should not be null"); 448: } 449: break; 450: } 451: } 452: if (info == null) { 453: Logger.wprintln("Null info root node returned by UiTestAutomationBridge, generate activate action..."); 454: nullInfoCounter++; 455: if (nullInfoCounter > 10) { 456: stopTopActivity(); 457: nullInfoCounter = 0; 458: } 459: mAgent.onVoidGUITree(nullInfoCounter); 460: AndroidDevice.checkInteractive(); 461: action = Action.ACTIVATE; 462: } 463: if (action == null) { 464: action = Action.NOP; 465: } 466: generateEventsForAction(action); 467: if (doFuzzing && RandomHelper.toss(fuzzingRate)) { 468: if (mAgent.canFuzzing()) { 469: Action fuzzingAction = mAgent.generateFuzzingAction(); 470: generateEventsForAction(fuzzingAction); 471: } 472: } 473: mAgent.notifyActionConsumed(); 474: } 475: native/src/com/android/commands/monkey/MonkeySourceApe.java:480-573 SHA256 200f76ef08f336dd0e70daeee6829d5f55e86d77a03775c9c7bfe6e31e9165ae 480: private void generateEventsForActionInternal(Action action) { 481: switch (action.getType()) { 482: case FUZZ: 483: generateFuzzingEvents((FuzzAction) action); 484: break; 485: case EVENT_START: 486: generateActivityEvents(((StartAction) action).getActivity(), false); 487: break; 488: case EVENT_RESTART: 489: restartPackage(((StartAction) action).getActivity(), false, "start action"); 490: break; 491: case EVENT_CLEAN_RESTART: 492: restartPackage(((StartAction) action).getActivity(), true, "start action"); 493: break; 494: case EVENT_NOP: 495: generateThrottleEvent(action.getThrottle()); 496: break; 497: case EVENT_ACTIVATE: 498: generateActivateEvent(); 499: break; 500: case MODEL_BACK: 501: generateKeyBackEvent(); 502: break; 503: case MODEL_CLICK: 504: GUITreeNode node = action.getResolvedNode(); 505: generateClickEventAt(action.getResolvedNode().getBoundsInScreen(), CLICK_WAIT_TIME); 506: if (node != null && node.getInputText() != null) { 507: generateThrottleEvent(200); // Let the click event to be scheduled 508: doInput(action, node); 509: } 510: break; 511: case MODEL_LONG_CLICK: 512: generateClickEventAt(action.getResolvedNode().getBoundsInScreen(), LONG_CLICK_WAIT_TIME); 513: break; 514: case MODEL_SCROLL_BOTTOM_UP: 515: case MODEL_SCROLL_TOP_DOWN: 516: case MODEL_SCROLL_LEFT_RIGHT: 517: case MODEL_SCROLL_RIGHT_LEFT: 518: generateScrollEventAt(action); 519: break; 520: default: 521: throw new RuntimeException("Should not reach here"); 522: } 523: } 524: 525: private void generateFuzzingEvents(FuzzAction action) { 526: List events = action.getFuzzingEvents(); 527: mGeneratedFuzzActions++; 528: mGeneratedFuzzEvents += events.size(); 529: Logger.iformat("Fuzzing: action #%d events=%d total_events=%d", 530: mGeneratedFuzzActions, events.size(), mGeneratedFuzzEvents); 531: long throttle = mThrottle + action.getThrottle(); 532: for (ApeEvent event : events) { 533: List monkeyEvents = event.generateMonkeyEvents(); 534: for (MonkeyEvent me : monkeyEvents) { 535: if (me == null) { 536: throw new RuntimeException(); 537: } 538: addEvent(me); 539: } 540: generateThrottleEvent(throttle); 541: } 542: Logger.iprintln("Fuzzing: reset rotation."); 543: resetRotation(); 544: // avoid non-deterministic actions 545: startEpisode("fuzz"); 546: } 547: 548: private void generateEventsForAction(Action action) { 549: if(mAgent instanceof com.android.commands.monkey.ape.agent.StatefulAgent) 550: ((com.android.commands.monkey.ape.agent.StatefulAgent)mAgent).jevBeforeInput(action); 551: int jevFirstEvent=mQ.size(); 552: long clockTimestamp = System.currentTimeMillis(); 553: if (action instanceof ModelAction) { 554: ModelAction modelAction = (ModelAction) action; 555: if (modelAction.requireTarget() && modelAction.getResolvedNode() != null) { 556: Logger.iformat("EXECUTE action_fp=%s target_fp=%s action=%s", 557: modelAction.getActionFingerprint(), modelAction.getTargetFingerprint(), modelAction); 558: } 559: } 560: mAgent.appendToActionHistory(clockTimestamp, action); 561: generateEventsForActionInternal(action); 562: long throttle = mThrottle + action.getThrottle(); 563: generateThrottleEvent(throttle); 564: if(mAgent instanceof com.android.commands.monkey.ape.agent.StatefulAgent) 565: ((com.android.commands.monkey.ape.agent.StatefulAgent)mAgent).jevEventsGenerated(action,mQ,jevFirstEvent); 566: } 567: 568: private void startEpisode(String reason) { 569: if(mAgent instanceof com.android.commands.monkey.ape.agent.StatefulAgent) 570: ((com.android.commands.monkey.ape.agent.StatefulAgent)mAgent).jevTraceReset(reason); 571: mAgent.startNewEpisode(); 572: } 573: public void jevEventInjected(MonkeyEvent event,int code) { native/src/com/android/commands/monkey/MonkeySourceApeInput.java:26-57 SHA256 f4f9f8cf2bf91d8c872ead794e0775f91531d79a67b5a5f8f51c37c7b44e58e4 26: 27: void generateScrollEventAt(MonkeySourceApe source, Action action) { 28: Rect displayBounds = AndroidDevice.getDisplayBounds(); 29: Rect nodeRect = action.getResolvedNode().getBoundsInScreen(); 30: if (!nodeRect.intersect(displayBounds)) { 31: Logger.wformat("Action " + action + " should be validated first!"); 32: nodeRect = displayBounds; 33: } 34: PointF start = new PointF(nodeRect.exactCenterX(), nodeRect.exactCenterY()); 35: PointF end = getScrollEnd(source, action.getType(), start, displayBounds); 36: long downAt = SystemClock.uptimeMillis(); 37: source.addEvent(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN).setDownTime(downAt).addPointer(0, start.x, start.y) 38: .setIntermediateNote(false)); 39: 40: int steps = 10; 41: long waitTime = swipeDuration / steps; 42: for (int i = 0; i < steps; i++) { 43: float alpha = i / (float) steps; 44: source.addEvent(new MonkeyTouchEvent(MotionEvent.ACTION_MOVE).setDownTime(downAt) 45: .addPointer(0, lerp(start.x, end.x, alpha), lerp(start.y, end.y, alpha)).setIntermediateNote(true)); 46: source.addEvent(new MonkeyWaitEvent(waitTime)); 47: } 48: source.addEvent(new MonkeyTouchEvent(MotionEvent.ACTION_UP).setDownTime(downAt).addPointer(0, end.x, end.y) 49: .setIntermediateNote(false)); 50: } 51: 52: private PointF getScrollEnd(MonkeySourceApe source, ActionType type, PointF start, Rect displayBounds) { 53: switch (type) { 54: case MODEL_SCROLL_BOTTOM_UP: 55: int top = getScrollTop(source, displayBounds); 56: return new PointF(start.x, top); 57: case MODEL_SCROLL_TOP_DOWN: native/src/com/android/commands/monkey/MonkeySourceApeInput.java:80-130 SHA256 f4f9f8cf2bf91d8c872ead794e0775f91531d79a67b5a5f8f51c37c7b44e58e4 80: void doInput(MonkeySourceApe source, Action action, GUITreeNode node) { 81: if (lastInputTimestamp == source.getAgent().getTimestamp()) { 82: Logger.wprintln("checkVirtualKeyboard: Input only once."); 83: } else { 84: lastInputTimestamp = source.getAgent().getTimestamp(); 85: } 86: String inputText = node.getInputText(); 87: if (inputText != null) { 88: Logger.iprintln("Input text is " + inputText); 89: generateClearEvent(source, node); 90: boolean sent = AndroidDevice.sendText(inputText); 91: if (source.getAgent() instanceof com.android.commands.monkey.ape.agent.StatefulAgent) 92: ((com.android.commands.monkey.ape.agent.StatefulAgent) source.getAgent()).jevDirectTextInput(action, inputText, sent); 93: if (!sent) { 94: attemptToSendTextByKeyEvents(source, inputText); 95: } 96: } else if (node.isEditText() || AndroidDevice.isVirtualKeyboardOpened()) { 97: source.generateKeyEvent(KeyEvent.KEYCODE_ESCAPE); 98: } 99: } 100: 101: private void generateClearEvent(MonkeySourceApe source, GUITreeNode node) { 102: Rect bounds = node.getBoundsInScreen(); 103: int lines = getLineCount(node); 104: if (lines == 0) { 105: return; 106: } 107: source.generateClickEventAt(bounds, 0L, MonkeySourceApe.ClickPoint.TOP_LEFT); 108: source.generateThrottleEvent(50); 109: while (lines-- > 0) { 110: source.generateKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT); 111: } 112: source.generateThrottleEvent(50); 113: source.generateKeyEvent(KeyEvent.KEYCODE_DEL); 114: source.generateThrottleEvent(100); 115: } 116: 117: private int getLineCount(GUITreeNode node) { 118: if (node.getNodeInfo() == null) { 119: return 1; 120: } 121: AccessibilityNodeInfo info = node.getNodeInfo(); 122: if (info.getText() == null) { 123: return 1; 124: } 125: String fullText = info.getText().toString(); 126: if (fullText.isEmpty()) { 127: return 0; 128: } 129: return fullText.split(System.lineSeparator()).length; 130: } native/src/com/android/commands/monkey/ape/tree/GUITreeBuilder.java:129-145 SHA256 43b7dafb82a3e053d894ad48bbd428e47094f1a4748fd344ffbc9731e04848cb 129: private static void applyXPathlets(Document document) { 130: for (XPathlet xpathlet : xPathlets) { 131: XPathExpression expr = xpathlet.getExpr(); 132: try { 133: NodeList e = (NodeList) expr.evaluate(document, XPathConstants.NODESET); 134: Logger.iformat("Select %d nodes by %s", e.getLength(), xpathlet.getExprStr()); 135: for (int i = 0; i < e.getLength(); i++) { 136: org.w3c.dom.Node domNode = e.item(i); 137: GUITreeNode n = getGUITreeNode(domNode); 138: n.resetActions(xpathlet.getActions()); 139: n.setExtraThrottle(xpathlet.getThrottle()); 140: n.setInputText(xpathlet.getText()); 141: } 142: } catch (XPathExpressionException e) { 143: Logger.wprintln("Evaluating XPath " + xpathlet.getExprStr() + " failed .."); 144: } 145: } native/src/com/android/commands/monkey/ape/tree/GUITreeBuilder.java:199-240 SHA256 43b7dafb82a3e053d894ad48bbd428e47094f1a4748fd344ffbc9731e04848cb 199: protected GUITree buildGUITree(AccessibilityNodeInfo rootInfo, Bitmap bitmap) { 200: long auditStarted=System.nanoTime(),auditWall=System.currentTimeMillis(); 201: Rect display = null; 202: Integer rotation = null; 203: if (com.android.commands.monkey.ape.utils.Config.isJevDecisionEnabled()) { 204: try { 205: display = com.android.commands.monkey.ape.AndroidDevice.getDisplayBounds(); 206: int capturedRotation = com.android.commands.monkey.ape.AndroidDevice.getRotation(); 207: if (capturedRotation >= 0) rotation = capturedRotation; 208: } catch (LinkageError unavailable) { } 209: catch (RuntimeException unavailable) { } 210: } 211: com.android.commands.monkey.ape.jev.JevImages.Snapshot image = null; 212: if (com.android.commands.monkey.ape.utils.Config.isJevDecisionEnabled() 213: && com.android.commands.monkey.ape.utils.Config.getBoolean("ape.jev.images", false)) 214: image = com.android.commands.monkey.ape.jev.JevImages.capture(bitmap); 215: GUITreeNode rootNode = buildNodeAndXmlFromNodeInfo(rootInfo, bitmap); 216: tree = new GUITree(rootNode, activity, display, rotation); 217: tree.setJevImage(image); 218: tree.setAuditObservationTimes(auditStarted,System.nanoTime(),auditWall); 219: if (com.android.commands.monkey.ape.utils.Config.isJevDecisionEnabled()) 220: tree.setJevObservationIdentity(com.android.commands.monkey.ape.jev.JevEvidence.accessibilityIdentity(rootInfo)); 221: // Set ape-activity on root element in the original build path. 222: // buildDocumentFromGUITree() also sets it, but that only runs on rebuild 223: // (when document was released). During normal save, this path is used. 224: if (document != null && document.getDocumentElement() != null) { 225: document.getDocumentElement().setAttribute("ape-activity", 226: activity.getPackageName() + "/" + activity.getClassName()); 227: } 228: tree.setDocument(document); 229: Naming current = nm.getNaming(tree); 230: NamingResult results = current.naming(tree, true); 231: tree.setCurrentNaming(current, results.getNames(), results.getNodes()); 232: return tree; 233: } 234: 235: private void rebuildGUITree() { 236: Naming current = nm.getNaming(tree); 237: NamingResult results = current.naming(tree, true); 238: tree.setCurrentNaming(current, results.getNames(), results.getNodes()); 239: tree.setCurrentState(null); 240: } native/src/com/android/commands/monkey/ape/tree/GUITreeBuilder.java:263-285 SHA256 43b7dafb82a3e053d894ad48bbd428e47094f1a4748fd344ffbc9731e04848cb 263: } 264: } 265: 266: protected GUITreeNode buildNodeAndXmlFromNodeInfo(AccessibilityNodeInfo info, Bitmap image) { 267: document = createDocument(); 268: GUITreeNode root = buildNodeAndXmlFromNodeInfo(null, document, info, 0); 269: if (document != null) { 270: document.appendChild(root.getDomNode()); 271: applyXPathlets(document); 272: } 273: if (patchGUITree) { 274: patchGUITree(root); 275: } 276: if (computeImageText) { 277: if (image != null) { 278: computeImageText(root, image); 279: image.recycle(); 280: } 281: } 282: if (alwaysIgnoreWebViewAction && !alwaysIgnoreWebView) { 283: ignoreActionsInWebView(root, root.isWebView()); 284: } 285: return root; native/src/com/android/commands/monkey/ape/tree/GUITreeBuilder.java:309-351 SHA256 43b7dafb82a3e053d894ad48bbd428e47094f1a4748fd344ffbc9731e04848cb 309: private void patchGUITree(GUITreeNode node) { 310: if (node.getChildCount() == 0) { 311: return; 312: } 313: if (node.isWebView()) { 314: return; 315: } 316: Rect nodeBounds = node.getBoundsInScreen(); 317: Rect childrenBounds = new Rect(); 318: Iterator nodeIt = node.getChildren(); 319: while (nodeIt.hasNext()) { 320: GUITreeNode child = nodeIt.next(); 321: childrenBounds.union(child.getBoundsInScreen()); 322: } 323: if (nodeBounds.contains(childrenBounds)) { 324: if (doPatchingChildren(node)) { 325: Logger.iprintln("Patching this node: " + node.getClassName() + "@" + node.getResourceID() + "@" 326: + node.getText()); 327: nodeIt = node.getChildren(); 328: while (nodeIt.hasNext()) { 329: GUITreeNode child = nodeIt.next(); 330: if (!child.isClickable()) { 331: Logger.iprintln("Patching child node: " + child.getClassName() + "@" + child.getResourceID() 332: + "@" + child.getText()); 333: child.setClickable(true); 334: if (node.getChildCount() == 1) { 335: if (node.getIndex() != child.getIndex()) { 336: child.setIndex(node.getIndex()); 337: } 338: } 339: } 340: } 341: if (childrenBounds.contains(nodeBounds.centerX(), nodeBounds.centerY())) { 342: node.setClickable(false); 343: } 344: } 345: } 346: // Keep patching until leaf nodes. 347: nodeIt = node.getChildren(); 348: while (nodeIt.hasNext()) { 349: patchGUITree(nodeIt.next()); 350: } 351: } native/src/com/android/commands/monkey/ape/tree/GUITreeBuilder.java:480-516 SHA256 43b7dafb82a3e053d894ad48bbd428e47094f1a4748fd344ffbc9731e04848cb 480: Element xml = null; 481: if (parentXML != null) { 482: xml = createNodeElement(node); 483: node.setDomNode(xml); 484: } 485: for (int i = 0; i < childCount; i++) { 486: AccessibilityNodeInfo child = info.getChild(i); 487: GUITreeNode childNode; 488: if (child == null) { 489: if (!excludeEmptyChild) { 490: childNode = GUITreeNode.buildEmptyNode(node); 491: if (xml != null) { 492: Element xmlChild = createNodeElement(); 493: childNode.setDomNode(xmlChild); 494: } 495: node.addChild(childNode); 496: } 497: } else if (!child.isVisibleToUser()) { 498: if (!excludeInvisibleNode) { 499: childNode = buildNodeAndXmlFromNodeInfo(node, xml, child, i); 500: if (alwaysIgnoreWebView && childNode.isWebView()) { 501: continue; 502: } 503: node.addChild(childNode); 504: } 505: } else { 506: childNode = buildNodeAndXmlFromNodeInfo(node, xml, child, i); 507: if (alwaysIgnoreWebView && childNode.isWebView()) { 508: continue; 509: } 510: node.addChild(childNode); 511: } 512: } 513: if (!alwaysIgnoreWebView && node.isWebView()) { 514: checkAndRemoveWebView(node); 515: } 516: return node; native/src/com/android/commands/monkey/ape/tree/GUITreeBuilder.java:662-695 SHA256 43b7dafb82a3e053d894ad48bbd428e47094f1a4748fd344ffbc9731e04848cb 662: private void fillNode(GUITreeNode node, AccessibilityNodeInfo info) { 663: node.setNodeInfo(info); 664: if (com.android.commands.monkey.ape.utils.Config.isJevDecisionEnabled()) { 665: node.captureJevEvidence(info.getText(), info.getContentDescription(), info.isSelected(), info.isPassword()); 666: node.captureAuditObservation(info); 667: node.captureJevLayoutEvidence(DrawingOrderAccess.read(info), info.isVisibleToUser()); 668: } 669: node.setPackageName(StringCache.cacheStringEmptyOnNull(info.getPackageName())); 670: node.setClassName(StringCache.cacheStringEmptyOnNull(info.getClassName())); 671: node.setText(StringCache.cacheStringEmptyOnNull(StringCache.truncateText(StringCache.removeQuotes(info.getText())), true)); 672: node.setResourceID(StringCache.cacheStringEmptyOnNull(info.getViewIdResourceName())); 673: node.setContentDesc(StringCache.cacheStringEmptyOnNull(StringCache.removeQuotes(info.getContentDescription()), true)); 674: 675: node.setChecked(info.isChecked()); 676: node.setEnabled(info.isEnabled()); 677: 678: node.setCheckable(info.isCheckable()); 679: node.setClickable(info.isClickable()); 680: node.setLongClickable(info.isLongClickable()); 681: node.setScrollable(info.isScrollable()); 682: node.setFocusable(info.isFocusable()); 683: 684: node.setFocused(info.isFocused()); 685: 686: Rect temp = new Rect(); 687: info.getBoundsInScreen(temp); 688: node.setBoundsInScreen(temp); 689: info.getBoundsInParent(temp); 690: node.setBoundsInParent(temp); 691: } 692: 693: private Element getFirstChildElement(Element e) { 694: NodeList nl = e.getChildNodes(); 695: for (int i = 0; i < nl.getLength(); i++) { native/src/com/android/commands/monkey/ape/tree/GUITree.java:180-216 SHA256 c284435e80200369697cdd13305f87a2399cf36549659cc73616166d0f0be72c 180: GUITreeNode[] nodes = (GUITreeNode[]) nodeOrNodes; 181: return nodes[0]; 182: } 183: 184: public List getNodes(Name widget) { 185: int index = Arrays.binarySearch(currentNames, widget); 186: if (index < 0) { 187: printGUITree(); 188: throw new IllegalStateException("Cannot find widget " + widget); 189: } 190: Object nodeOrNodes = currentNodes[index]; 191: if (nodeOrNodes instanceof GUITreeNode) { 192: return Collections.singletonList((GUITreeNode) nodeOrNodes); 193: } 194: GUITreeNode[] nodes = (GUITreeNode[]) nodeOrNodes; 195: return Arrays.asList(nodes); 196: } 197: 198: public GUITreeNode[] pickNodes(ModelAction action) { 199: int index = Arrays.binarySearch(currentNames, action.getTarget()); 200: if (index < 0) { 201: printGUITree(); 202: throw new IllegalStateException("Cannot find widget " + action.getTarget()); 203: } 204: Object nodeOrNodes = currentNodes[index]; 205: if (nodeOrNodes instanceof GUITreeNode) { 206: return new GUITreeNode[] { (GUITreeNode) nodeOrNodes }; 207: } 208: return (GUITreeNode[]) nodeOrNodes; 209: } 210: 211: public GUITreeNode pickNode(Agent agent, ModelAction action) { 212: GUITreeNode[] nodes = pickNodes(action); 213: return nodes[agent.nextInt(nodes.length)]; 214: } 215: 216: public int getCountOfTargetNodes(String target) { native/src/com/android/commands/monkey/ape/tree/GUITreeNode.java:251-281 SHA256 b42f7bfb2a1d6f523144668a9364999f5a147c94576fc3255799028658c3a28e 251: 252: public boolean isEditText() { 253: return GUITreeBuilder.isEditText(this.className); 254: } 255: 256: public GUITreeNode getRoot() { 257: if (parent == null) { 258: return this; 259: } 260: return parent.getRoot(); 261: } 262: 263: public boolean isOutOfRoot() { 264: GUITreeNode root = getRoot(); 265: return !root.getBoundsInScreen().intersect(screenLeft, screenTop, screenRight, screenBottom); 266: } 267: 268: public boolean hasOutOfRootChild() { 269: for (GUITreeNode child = children; child != null; child = child.sibling) { 270: if (child.isOutOfRoot()) { 271: return true; 272: } 273: } 274: return false; 275: } 276: 277: public boolean isEmpty() { 278: return screenLeft >= screenRight || screenTop >= screenBottom; 279: } 280: 281: public String getTypeSignature() { native/src/com/android/commands/monkey/ape/tree/GUITreeNode.java:499-507 SHA256 b42f7bfb2a1d6f523144668a9364999f5a147c94576fc3255799028658c3a28e 499: public Rect getBoundsInScreen() { 500: return new Rect(screenLeft, screenTop, screenRight, screenBottom); 501: } 502: 503: public void setBoundsInScreen(Rect boundsInScreen) { 504: this.screenLeft = boundsInScreen.left; 505: this.screenTop = boundsInScreen.top; 506: this.screenRight = boundsInScreen.right; 507: this.screenBottom = boundsInScreen.bottom; native/src/com/android/commands/monkey/ape/naming/Naming.java:212-242 SHA256 0eca98e7ae51da40cdbba5b58508850ac70183e4a9886913ef22fab3ea75e5f2 212: static boolean addNamedNode(Map nameToNodes, Name name, GUITreeNode node) { 213: if (ignoreEmpty) { 214: if (node.isEmpty()) { 215: return true; 216: } 217: } 218: 219: if (ignoreOutOfBounds) { 220: if (node.isOutOfRoot()) { 221: return true; 222: } 223: } 224: 225: Object existing = nameToNodes.get(name); 226: if (existing == null) { 227: nameToNodes.put(name, node); 228: } 229: if (existing instanceof GUITreeNode) { 230: nameToNodes.put(name, new GUITreeNode[] { (GUITreeNode) existing, node }); 231: } else if (existing instanceof GUITreeNode[]) { 232: GUITreeNode[] oldNodes = (GUITreeNode[]) existing; 233: GUITreeNode[] newNodes = new GUITreeNode[oldNodes.length + 1]; 234: System.arraycopy(oldNodes, 0, newNodes, 0, oldNodes.length); 235: newNodes[oldNodes.length] = node; 236: nameToNodes.put(name, newNodes); 237: } 238: return true; 239: } 240: 241: static Comparator comparator = new Comparator() { 242: native/src/com/android/commands/monkey/ape/naming/ActionPatchNamer.java:50-78 SHA256 7003b2143f66461a31a6a42bfa7e2b6de7f0021d1ce6c53b5b942cbe7f987343 50: 51: private List buildActions() { 52: List actionTypes = new ArrayList<>(6); 53: String patch = patches[this.patch]; 54: if (patch.contains("clickable=true") || patch.contains("checkable=true")) { 55: actionTypes.add(ActionType.MODEL_CLICK); 56: } 57: if (patch.contains("long-clickable=true")) { 58: actionTypes.add(ActionType.MODEL_LONG_CLICK); 59: } 60: switch (scrollType) { 61: case all: 62: actionTypes.add(ActionType.MODEL_SCROLL_BOTTOM_UP); 63: actionTypes.add(ActionType.MODEL_SCROLL_TOP_DOWN); 64: actionTypes.add(ActionType.MODEL_SCROLL_LEFT_RIGHT); 65: actionTypes.add(ActionType.MODEL_SCROLL_RIGHT_LEFT); 66: break; 67: case horizontal: 68: actionTypes.add(ActionType.MODEL_SCROLL_LEFT_RIGHT); 69: actionTypes.add(ActionType.MODEL_SCROLL_RIGHT_LEFT); 70: break; 71: case vertical: 72: actionTypes.add(ActionType.MODEL_SCROLL_BOTTOM_UP); 73: actionTypes.add(ActionType.MODEL_SCROLL_TOP_DOWN); 74: break; 75: default: 76: } 77: return Collections.unmodifiableList(actionTypes); 78: } native/src/com/android/commands/monkey/ape/model/State.java:50-90 SHA256 d70a7de8924666eae4e2fa508a24bcf09c4a2449864a07d67928d36ed26decf7 50: private StateKey stateKey; 51: private ModelAction[] actions; 52: private ModelAction backAction; 53: 54: private List treeHistory; 55: 56: public State(StateKey stateKey) { 57: this.stateKey = stateKey; 58: List c = new ArrayList(); 59: for (Name widget : stateKey.getWidgets()) { 60: buildActions(c, widget); 61: } 62: backAction = new ModelAction(this, ActionType.MODEL_BACK); 63: c.add(backAction); 64: actions = c.toArray(new ModelAction[c.size()]); 65: } 66: 67: /** 68: * GUI actions, i.e., action with a target 69: * @return 70: */ 71: public List targetedActions() { 72: List results = new ArrayList (actions.length); 73: collectActions(results, ActionFilter.WITH_TARGET); 74: return results; 75: } 76: 77: public List collectActions(ActionFilter filter) { 78: List results = new ArrayList (actions.length); 79: collectActions(results, filter); 80: return results; 81: } 82: 83: public void collectActions(Collection results, ActionFilter filter) { 84: for (ModelAction action : actions) { 85: if (filter.include(action)) { 86: results.add(action); 87: } 88: } 89: } 90: native/src/com/android/commands/monkey/ape/model/State.java:210-218 SHA256 d70a7de8924666eae4e2fa508a24bcf09c4a2449864a07d67928d36ed26decf7 210: private void buildActions(List actions, Name widget) { 211: List actionTypes = NamerFactory.decodeActions(widget); 212: for (ActionType actionType : actionTypes) { 213: ModelAction action = new ModelAction(this, widget, actionType); 214: actions.add(action); 215: } 216: } 217: 218: public List getActions() { native/src/com/android/commands/monkey/ape/model/State.java:303-360 SHA256 d70a7de8924666eae4e2fa508a24bcf09c4a2449864a07d67928d36ed26decf7 303: public void append(GUITree tree) { 304: Naming naming = stateKey.getNaming(); 305: if (!tree.getCurrentNaming().equals(naming)) { 306: Logger.wprintln("Different naming results in the same state."); 307: Logger.wprintln("Expected " + naming); 308: if (naming != null) 309: naming.dump(); 310: Naming get = tree.getCurrentNaming(); 311: Logger.wprintln("Get: " + get); 312: if (get != null) { 313: get.dump(); 314: } 315: if (naming != null && get != null) { 316: Logger.wprintln("Equivalent? " + naming.equivalent(get)); 317: } 318: // throw new RuntimeException("Different naming results in the same 319: // state."); 320: } 321: if (tree.getCurrentState() == this) { 322: Logger.println(this); 323: throw new RuntimeException("Cannot be appended twice."); 324: } 325: tree.setCurrentState(this); 326: if (treeHistory == null) { 327: treeHistory = new ArrayList(); 328: } 329: treeHistory.add(tree); 330: } 331: 332: public List getGUITrees() { 333: if (this.treeHistory == null) { 334: return Collections.emptyList(); 335: } 336: return this.treeHistory; 337: } 338: 339: public GUITree getLatestGUITree() { 340: if (treeHistory == null) { 341: return null; 342: } 343: return treeHistory.get(treeHistory.size() - 1); 344: } 345: 346: public ModelAction resolveAction(StatefulAgent agent, ModelAction action, int throttle) { 347: if (treeHistory == null) { 348: throw new IllegalStateException("Empty GUI tree history"); 349: } 350: GUITree latest = getLatestGUITree(); 351: if (!action.getType().requireTarget()) { 352: action.resolveAt(agent.getTimestamp(), throttle, latest, null, null); 353: return action; 354: } 355: GUITreeNode[] nodes = latest.pickNodes(action); 356: GUITreeNode node = RandomHelper.randomPick(Arrays.asList(nodes)); 357: action.resolveAt(agent.getTimestamp(), throttle, latest, node, nodes); 358: return action; 359: } 360: native/src/com/android/commands/monkey/ape/model/ModelAction.java:35-45 SHA256 bf0d035315349057987deaf63610c68acdd552befe5714ecc100d29dcc540883 35: // Resolution information 36: private final State state; 37: private final Name target; 38: private int resovledTimestamp = -1; 39: private GUITreeNode[] resolvedNodes; 40: private GUITreeNode resolvedNode; 41: private GUITreeAction resolvedGUITreeAction; 42: private float resolvedSaturation; 43: private GUITree resolvedTree; 44: 45: public ModelAction(State state, ActionType type) { native/src/com/android/commands/monkey/ape/model/ModelAction.java:90-98 SHA256 bf0d035315349057987deaf63610c68acdd552befe5714ecc100d29dcc540883 90: public boolean isSaturated() { 91: if (!requireTarget()) { 92: return this.isVisited(); 93: } 94: return this.resolvedSaturation >= 1.0F; 95: } 96: 97: public boolean isOverAbstracted() { 98: if (!requireTarget()) { native/src/com/android/commands/monkey/ape/model/ModelAction.java:186-244 SHA256 bf0d035315349057987deaf63610c68acdd552befe5714ecc100d29dcc540883 186: public boolean isResolvedAt(int timestamp) { 187: if (this.resovledTimestamp != timestamp) { 188: return false; 189: } 190: if (!requireTarget()) { 191: return true; 192: } 193: return this.resolvedNode != null; 194: } 195: 196: public GUITreeAction getResolvedGUITreeAction() { 197: return this.resolvedGUITreeAction; 198: } 199: 200: public void setResolvedGUITreeAction(GUITreeAction resolvedGUITreeAction) { 201: this.resolvedGUITreeAction = resolvedGUITreeAction; 202: } 203: 204: public GUITree getResolvedGUITree() { 205: return this.resolvedTree; 206: } 207: 208: public void resolveAt(int timestamp, int throttle, GUITree tree, GUITreeNode node, GUITreeNode[] nodes) { 209: super.setThrottle(throttle); 210: this.resovledTimestamp = timestamp; 211: this.resolvedTree = tree; 212: this.resolvedGUITreeAction = new GUITreeAction(tree, node, this); 213: this.resolvedGUITreeAction.setThrottle(throttle); 214: if (!requireTarget()) { 215: return; 216: } 217: this.resolvedNode = node; 218: this.resolvedNodes = nodes; 219: if (nodes.length == 0) { 220: throw new IllegalStateException("Fail to resolve a node for this action: " + this); 221: } 222: if (nodes.length == 1) { 223: this.resolvedSaturation = this.isVisited() ? 1.0F : 0.0F; 224: } else { 225: float total = Math.min(nodes.length, saturatedVisitedThreshold); 226: this.resolvedSaturation = Math.min(1.0F, this.visitedCount / total); 227: } 228: return; 229: } 230: 231: public GUITreeNode getResolvedNode() { 232: return this.resolvedNode; 233: } 234: 235: public GUITreeNode[] getResolvedNodes() { 236: return this.resolvedNodes; 237: } 238: 239: public float getResolvedSaturation() { 240: if (!requireTarget()) { 241: return isVisited() ? 1.0F : 0; 242: } 243: return Math.min(Math.max(0, this.resolvedSaturation), 1.0F); 244: } native/src/com/android/commands/monkey/ape/model/Graph.java:145-194 SHA256 5676b123e0aaf93065e72c76ac33aab20d738b0a7262415977c8f1aa35a80022 145: State state = action.getState(); 146: Name name = action.getTarget(); 147: if (name == null) { 148: return null; 149: } 150: String activity = state.getActivity(); 151: Set actions = Utils.getFromMapMap(nameToActions, activity, name); 152: if (actions == null) { 153: return null; 154: } 155: if (actions.size() <= 1) { 156: return null; 157: } 158: Set targets = new HashSet<>(); 159: for (ModelAction shared : actions) { 160: if (!shared.getType().equals(action.getType())) { 161: continue; 162: } 163: for (StateTransition st : getOutStateTransitions(shared)) { 164: targets.add(st.getTarget()); 165: } 166: } 167: if (targets.size() == 1) { 168: return targets.iterator().next(); 169: } 170: return null; 171: } 172: 173: public boolean isNameGlobalAction(ModelAction action) { 174: return getNameGlobalTarget(action) != null; 175: } 176: 177: public boolean isActionUnvisitedByName(ModelAction action) { 178: if (!action.requireTarget()) { 179: return action.isUnvisited(); 180: } 181: State state = action.getState(); 182: String activity = state.getActivity(); 183: Set actions = Utils.getFromMapMap(nameToActions, activity, action.getTarget()); 184: if (actions == null) { 185: return action.isUnvisited(); 186: } 187: for (ModelAction shared : actions) { 188: if (shared.isVisited() && shared.getType().equals(action.getType())) { 189: return false; 190: } 191: } 192: return true; 193: } 194: native/src/com/android/commands/monkey/ape/model/Graph.java:508-542 SHA256 5676b123e0aaf93065e72c76ac33aab20d738b0a7262415977c8f1aa35a80022 508: public void markVisited(ModelAction action, int timestamp) { 509: if (!action.getType().isModelAction()) { 510: return; 511: } 512: if (action.isUnvisited()) { 513: if (!this.unvisitedActions.remove(action)) { 514: throw new RuntimeException("sanity check failed, action should be added " + action); 515: } 516: action.visitedAt(timestamp); 517: this.visitedActions.add(action); 518: } else if (visitedActions.contains(action)) { 519: action.visitedAt(timestamp); 520: } else { 521: Logger.println("Untracked action: " + action); 522: Logger.println("State is " + action.getState()); 523: if (this.unvisitedActions.contains(action)) { 524: throw new RuntimeException("Unvisited actions should not be marked visited."); 525: } 526: throw new RuntimeException("sanity check failed"); 527: } 528: } 529: 530: public void markVisited(State node, int timestamp) { 531: node.visitedAt(timestamp); 532: markVisited(node.getActivity(), timestamp); 533: } 534: 535: private void markVisited(StateTransition edge, int timestamp) { 536: if (!edges.containsKey(edge)) { 537: throw new RuntimeException("Sanity check failed!"); 538: } 539: edge.visitedAt(timestamp); 540: } 541: 542: private void markVisited(String activity, int timestamp) { native/src/com/android/commands/monkey/ape/model/GraphElement.java:25-88 SHA256 f385978f594adf6b37aeb60299529177c68e0ae72bba957a76d8a58cbc83e829 25: private static final long serialVersionUID = 1L; 26: protected int firstVisitTimestamp = -1; 27: protected int lastVisitTimestamp; 28: protected int visitedCount; 29: 30: protected String id = null; 31: 32: public String getGraphId() { 33: if (id == null) { 34: return ""; 35: } 36: return id; 37: } 38: 39: public String toString() { 40: return "" + getGraphId() + '[' + firstVisitTimestamp + ',' + lastVisitTimestamp + "][" + visitedCount + ']'; 41: } 42: 43: void setGraphId(String id) { 44: this.id = id; 45: } 46: 47: public boolean isUnvisited() { 48: return firstVisitTimestamp == -1; 49: } 50: 51: public final boolean isVisited() { 52: return !isUnvisited(); 53: } 54: 55: public void visitedAt(int timestamp) { 56: lastVisitTimestamp = timestamp; 57: if (firstVisitTimestamp == -1) { 58: firstVisitTimestamp = timestamp; 59: if (firstVisitTimestamp == -1) { 60: throw new IllegalStateException("Invalid timestamp " + timestamp); 61: } 62: } 63: visitedCount++; 64: } 65: 66: public int getFirstVisitedTimestamp() { 67: return this.firstVisitTimestamp; 68: } 69: 70: public int getLastVisitedTimestamp() { 71: return this.lastVisitTimestamp; 72: } 73: 74: public int getVisitedCount() { 75: return this.visitedCount; 76: } 77: } native/src/com/android/commands/monkey/ape/model/StateActionDiffer.java:43-110 SHA256 f792029e39fe9f35065a52ffc948337b014090faed22c246bd7f7a1f888b3bb7 43: 44: protected void checkAndAdd(List results, ModelAction action) { 45: if (ActionFilter.ENABLED_VALID.include(action)) { 46: results.add(action); 47: } 48: } 49: 50: public List getUnsaturated(State from, State to, boolean verbose) { 51: if (from == null) { 52: return to.getUnsaturatedActions(); 53: } 54: if (!from.getActivity().equals(to.getActivity())) { 55: return to.getUnsaturatedActions(); 56: } 57: List added = new ArrayList<>(); 58: List matched = new ArrayList<>(); 59: List deleted = new ArrayList<>(); 60: 61: List results = new ArrayList<>(); 62: List fromActions = from.targetedActions(); 63: List toActions = to.targetedActions(); 64: Collections.sort(fromActions, comparator); 65: Collections.sort(toActions, comparator); 66: 67: int i1 = 0; 68: int i2 = 0; 69: while (i1 < fromActions.size() && i2 < toActions.size()) { 70: ModelAction a1 = fromActions.get(i1); 71: ModelAction a2 = toActions.get(i2); 72: int cmp = comparator.compare(a1, a2); 73: if (cmp == 0) { 74: if (!(a1.isSaturated() || a2.isSaturated())) { 75: checkAndAdd(results, a2); 76: } 77: i1++; 78: i2++; 79: matched.add(a1); 80: matched.add(a2); 81: } else if (cmp < 0) { 82: deleted.add(a1); 83: i1++; 84: } else { 85: if (!a2.isSaturated()) { 86: checkAndAdd(results, a2); 87: } 88: added.add(a2); 89: i2++; 90: } 91: } 92: 93: while (i1 < fromActions.size()) { 94: ModelAction a = fromActions.get(i1++); 95: deleted.add(a); 96: } 97: while (i2 < toActions.size()) { 98: ModelAction a = toActions.get(i2++); 99: if (!a.isSaturated()) { 100: checkAndAdd(results, a); 101: } 102: added.add(a); 103: } 104: 105: if (verbose) { 106: Logger.println("##################################"); 107: int count = 0; 108: for (ModelAction d : deleted) { 109: Logger.format("%3d - %s", count++, d); 110: } native/src/com/android/commands/monkey/ape/model/Action.java:59-86 SHA256 963b0b195a2a1f012275f3b0ef15102dd2e2e14ec6c4f3f4af2662dc8399cc80 59: } 60: return val; 61: } 62: 63: private final ActionType type; 64: private boolean enabled = true; 65: private boolean valid; 66: private int priority; 67: private int throttle; 68: 69: public Action(ActionType type) { 70: this.type = type; 71: } 72: 73: public boolean isEnabled() { 74: return this.enabled; 75: } 76: 77: /** 78: * The widget of a GUI action may be enabled or not. 79: * @param value 80: */ 81: public void setEnabled(boolean value) { 82: this.enabled = value; 83: } 84: 85: /** 86: * Is this action a scroll action? native/src/com/android/commands/monkey/ape/ActionFilter.java:20-130 SHA256 71f422eab63c69e13c4d8b2815ac7f39b9740c3b156c26d2dc028d3d0efb8360 20: public interface ActionFilter { 21: 22: ActionFilter ALL = new BaseActionFilter() { 23: 24: @Override 25: public boolean include(ModelAction action) { 26: return true; 27: } 28: 29: }; 30: 31: ActionFilter WITH_TARGET = new BaseActionFilter() { 32: 33: @Override 34: public boolean include(ModelAction action) { 35: return action.requireTarget(); 36: } 37: 38: }; 39: 40: ActionFilter VALID = new BaseActionFilter() { 41: 42: @Override 43: public boolean include(ModelAction action) { 44: return action.isValid(); 45: } 46: 47: }; 48: 49: ActionFilter ENABLED_VALID_UNVISITED = new BaseActionFilter() { 50: 51: @Override 52: public boolean include(ModelAction action) { 53: return action.isEnabled() && action.isValid() && action.isUnvisited(); 54: } 55: 56: }; 57: 58: ActionFilter ENABLED_VALID_UNSATURATED = new BaseActionFilter() { 59: 60: @Override 61: public boolean include(ModelAction action) { 62: return action.isEnabled() && action.isValid() && !action.isSaturated(); 63: } 64: 65: }; 66: 67: ActionFilter ENABLED_VALID = new BaseActionFilter() { 68: @Override 69: public boolean include(ModelAction action) { 70: return action.isEnabled() && action.isValid(); 71: } 72: }; 73: 74: boolean include(ModelAction action); 75: } native/src/com/android/commands/monkey/ape/agent/AgentActionScorer.java:48-94 SHA256 d37817dbbc2defe8dad20d82088890de683df38b6a72e1e1646fbeb389576f2b 48: void adjustActionsByGUITree(StatefulAgent agent, State state, GUITree tree, int timestamp) { 49: for (ModelAction action : state.getActions()) { 50: int basePriority = getActionBasePriority(action.getType()) << 3; 51: action.setPriority(basePriority); 52: if (!action.requireTarget()) { 53: if (action.isUnvisited()) { 54: action.setPriority(action.getPriority() + 5); 55: } 56: continue; 57: } 58: if (!action.isValid() || !action.isResolvedAt(timestamp)) { 59: continue; 60: } 61: GUITreeNode node = action.getResolvedNode(); 62: action.setEnabled(node.isEnabled()); 63: Collection edges = agent.getGraph().getOutStateTransitions(action); 64: int priority = action.getPriority(); 65: if (action.isUnvisited()) { 66: priority += 20; 67: } 68: if (!action.isSaturated()) { 69: List nodes = tree.getNodes(action.getTarget()); 70: int size = nodes.size(); 71: if (size > 1) { 72: priority += Math.min(size, maxExtraPriorityAliasedActions) * getActionBasePriority(action.getType()); 73: } 74: } 75: for (StateTransition edge : edges) { 76: if (edge.isStrong()) { 77: if (edge.getTarget().isSaturated()) { 78: priority += -10; 79: } else if (edge.isSameActivity()) { 80: priority += 10; 81: } 82: } else if (edges.size() > 1) { 83: priority += 10; 84: } 85: } 86: action.setPriority(priority <= 0 ? 1 : priority); 87: } 88: } 89: 90: boolean isTopLeftClick(ModelAction action, Rect displayBounds) { 91: if (!action.requireTarget() || !action.isClick()) { 92: return false; 93: } 94: GUITreeNode node = action.getResolvedNode(); native/src/com/android/commands/monkey/ape/agent/ApeAgent.java:175-185 SHA256 2f9ec06251a91a2c4714edb38cafdc1c4a90a2a4c41a924209d6ff5837e8feae 175: protected Action checkInput(Action action) { 176: if (action.requireTarget()) { 177: GUITreeNode node = ((ModelAction) action).getResolvedNode(); 178: if (node.isEditText() && node.getInputText() == null) { 179: if (RandomHelper.toss(inputRate)) { 180: String text = StringCache.nextString(); 181: node.setInputText(text); 182: } 183: } 184: } 185: return action; native/src/com/android/commands/monkey/ape/agent/ApeAgent.java:278-292 SHA256 2f9ec06251a91a2c4714edb38cafdc1c4a90a2a4c41a924209d6ff5837e8feae 278: } 279: 280: private Action updateStateWrapper(ComponentName topComp, AccessibilityNodeInfo info) { 281: try { 282: Logger.format(">>>>>>>> %s begin step [%d][Elapsed: %s]", getLoggerName(), ++timestamp, 283: getElapsedTestingTime()); 284: printExploredActivity(); 285: printMemoryUsage(); 286: try { 287: disableRestart = false; 288: disableFuzzing = false; 289: return checkInput(checkFuzzing(checkRestart(updateStateInternal(topComp, info)))); 290: } catch (BadStateException e) { 291: Logger.wprintln("Bad state, retrieve the Window node."); 292: info = ape.getRootInActiveWindowSlow(); native/src/com/android/commands/monkey/ape/agent/StatefulAgent.java:537-574 SHA256 631f746e263ed72bf16cc849ebf95f45abaa4eea51fc9977b89d732f9269c2e2 537: protected State buildAndValidateNewState(ComponentName topComp, AccessibilityNodeInfo info) { 538: newState = buildState(topComp, info, captureBitmap()); 539: preCheckTrivialNewState(); 540: validateAllNewActions(); 541: newGUITree = newState.getLatestGUITree(); 542: newGUITree.setTimestamp(getTimestamp()); 543: return newState; 544: } 545: 546: /** 547: * 548: */ 549: protected Action updateStateInternal(ComponentName topComp, AccessibilityNodeInfo info) { 550: recoverCurrentState(); 551: buildAndValidateNewState(topComp, info); 552: preEvolveModel(); 553: getGraph().markVisited(newState, getTimestamp()); 554: updateGraph(); 555: // checkCircleTransition(); 556: checkNonDeterministicTransitions(); 557: if (newState.isUnvisited()) { 558: getGraph().markVisited(newState, getTimestamp()); 559: } 560: Action action = resolveNewAction(); 561: if (action.isModelAction()) { 562: getGraph().markVisited((ModelAction) action, getTimestamp()); 563: moveForward(); 564: } else { 565: jevTraceReset("non_model_action_"+action.getType().name()); 566: this.resetTrace(); 567: } 568: if (debug) { 569: return Action.NOP; 570: } 571: return action; // newAction are moved to currentAction in moveForward 572: } 573: 574: public void notifyActionConsumed() { native/src/com/android/commands/monkey/ape/agent/StatefulAgent.java:811-861 SHA256 631f746e263ed72bf16cc849ebf95f45abaa4eea51fc9977b89d732f9269c2e2 811: protected boolean validateResolvedAction(ModelAction action) {return ape.validateResolvedAction(action);} 812: 813: protected ModelAction validateNewAction(ModelAction action) { 814: if (action == null) { 815: return null; 816: } 817: // BadState recovery can build another GUI tree without advancing the step. 818: // Decision-mode exact inputs must bind to that tree, not the previous observation. 819: if (newState != null && action.getState() == newState && action.isResolvedAt(getTimestamp()) && action.isValid() 820: && (!jevDecisionEnabled() || action.getResolvedGUITree() == newState.getLatestGUITree())) { 821: return action; 822: } 823: action = newState.resolveAction(this, action, getThrottleForNewAction(newState, action)); 824: if (validateResolvedAction(action)) { 825: action.setValid(true); 826: return action; 827: } 828: Logger.wformat("Mark an action (%s) invalid", action); 829: action.setValid(false); 830: return null; 831: } 832: 833: protected void validateAllNewActions() { 834: Utils.assertNotNull(newState); 835: for (ModelAction action : newState.getActions()) { 836: validateNewAction(action); 837: } 838: } 839: 840: protected Action resolveNewAction() { 841: Utils.assertNotNull(newState); 842: if(jev!=null)jev.safePoint(model,newState,newGUITree,getTimestamp()); 843: adjustActionsByGUITree(); 844: // The legacy asynchronous semantic branch prefetches on page arrival; 845: // synchronous review is deliberately deferred to Sata's post-local 846: // selection barrier so navigation and native choices keep priority. 847: if(jev!=null && semanticReviewActive() && !semanticReviewSynchronous()) 848: jev.prepareSemanticReviews(model,newState,newGUITree); 849: if(regionTelemetryEnabled())jev.beforeSelection(model,newState,newGUITree,getTimestamp()); 850: pathSelectedAction=null;pendingSelectionStrategy=null; 851: Action action=selectNewActionNonnull();Utils.assertNotNull(action); 852: if(action.isModelAction()) { 853: newAction=(ModelAction)action;newGUITreeAction=newAction.getResolvedGUITreeAction();Utils.assertNotNull(newGUITreeAction); 854: } 855: if(jevDecisionEnabled() && pendingSelectionStrategy!=null)jev.recordSelectionDecision(pendingSelectionStrategy,newState,action,actionBufferSize()); 856: return action; 857: } 858: 859: protected void adjustActionsByGUITree() { 860: actionScorer.adjustActionsByGUITree(this, newState, newGUITree, timestamp); 861: } native/src/com/android/commands/monkey/ape/agent/SataAgent.java:304-319 SHA256 92ec6370d5e2879372f897f4c854a6e7b32dcbc4dc01a361900099b343728bff 304: private boolean returnSelectedAction(Action action, SataEventType type) { 305: if (action == null) { 306: return false; 307: } 308: Binding attempted=action instanceof ModelAction?new Binding((ModelAction)action):null; 309: if (!acceptSelectedAction(action,type.name())) { 310: if(action instanceof ModelAction)regionRejected((ModelAction)action,type,attempted); 311: return false; 312: } 313: logActionSelected(action, type); 314: if (jev != null) { 315: if(jevDecisionEnabled())pendingSelectionStrategy=type.name(); 316: else jev.recordSelectionDecision(type.name(), newState, action, actionBufferSize()); 317: } 318: return true; 319: } native/src/com/android/commands/monkey/ape/agent/SataAgent.java:387-505 SHA256 92ec6370d5e2879372f897f4c854a6e7b32dcbc4dc01a361900099b343728bff 387: protected Action selectNewActionNonnull() { 388: Action selected=null; 389: epsilonSelectionGreedy=null; 390: regionOriginals=null;regionRejected=null;regionExhausted=null;regionFallback=null; 391: try { selected=selectNewActionInRound(); return selected; } 392: finally { 393: if(regionOriginals!=null)for(Map.Entry entry:regionOriginals.entrySet()) 394: if(entry.getKey()!=selected)entry.getValue().restore(entry.getKey(),getTimestamp()); 395: regionOriginals=null;regionRejected=null;regionExhausted=null;regionFallback=null;regionFallbackBinding=null;epsilonSelectionGreedy=null; 396: } 397: } 398: 399: private Action selectNewActionInRound() { 400: { 401: // Logging 402: printStrategy(); 403: Logger.iprintln("Check global actions."); 404: for (ModelAction action : newState.targetedActions()) { 405: if (action.isVisited()) { 406: continue; 407: } 408: if (getGraph().isNameGlobalAction(action)) { 409: Logger.iformat("- %s", action); 410: } 411: } 412: } 413: Action resolved = null; 414: while ((resolved = selectNewActionFromBuffer()) != null) { 415: if (returnSelectedAction(resolved, SataEventType.USE_BUFFER))return resolved; 416: } 417: while ((resolved = selectNewActionBackToActivity()) != null) { 418: if (returnSelectedAction(resolved, SataEventType.TRIVIAL_ACTIVITY))return resolved; 419: } 420: while ((resolved = selectNewActionEarlyStageForABA()) != null) { 421: if (returnSelectedAction(resolved, SataEventType.EARLY_STAGE))return resolved; 422: } 423: 424: boolean localRejected=false; 425: while ((resolved = selectNewActionEarlyStageForwardLocalGreedy()) != null) { 426: localRejected=true; 427: if (returnSelectedAction(resolved, SataEventType.EARLY_STAGE))return resolved; 428: } 429: 430: // Probe the current page only between local greedy and native remote greedy. 431: // A rejected native local action is not a probe opportunity. 432: if(!localRejected && jevDecisionEnabled()) { 433: // Navigation and native local-greedy choices have priority. Only 434: // after both are exhausted may the current page enter semantic 435: // review; a synchronous review then completes in this same action 436: // cycle before ordinary fallback exploration resumes. 437: if (semanticReviewActive()) 438: jev.prepareSemanticReviews(model,newState,newGUITree,new com.android.commands.monkey.ape.jev.JevRuntime.Validator(){ 439: public boolean valid(ModelAction action){return validateResolvedAction(action);} 440: }); 441: beginProbeSelection(); 442: try { 443: while((resolved=selectProbe())!=null) 444: if(returnSelectedAction(resolved,SataEventType.PROBE))return resolved; 445: } finally { 446: endProbeSelection(); 447: } 448: } 449: 450: while ((resolved = selectNewActionEarlyStageForwardRemoteGreedy()) != null) { 451: if (returnSelectedAction(resolved, SataEventType.EARLY_STAGE))return resolved; 452: } 453: while ((resolved = selectNewActionForTrivialActivity()) != null) { 454: if (returnSelectedAction(resolved, SataEventType.TRIVIAL_ACTIVITY))return resolved; 455: } 456: while ((resolved = selectNewActionEarlyStageBackward()) != null) { 457: if (returnSelectedAction(resolved, SataEventType.EARLY_STAGE))return resolved; 458: } 459: while ((resolved = selectNewActionEpsilonGreedyRandomly()) != null) { 460: if (returnSelectedAction(resolved, SataEventType.EPSILON_GREEDY))return resolved; 461: } 462: while ((resolved = selectNullActionInRound()) != null) { 463: if (returnSelectedAction(resolved, SataEventType.NULL))return resolved; 464: } 465: if(regionFallback!=null) { 466: regionFallbackBinding.restore(regionFallback,getTimestamp()); 467: // The chain is exhausted: restore its first native legal choice, 468: // bypassing only Region and preserving the original strategy receipt. 469: logActionSelected(regionFallback,regionFallbackStrategy); 470: if(jev!=null)pendingSelectionStrategy=regionFallbackStrategy.name(); 471: return regionFallback; 472: } 473: throw new BadStateException("No available action on the current state"); 474: } 475: 476: private ModelAction selectNullActionInRound() { 477: if(regionFallback==null)return handleNullAction(); 478: ModelAction action=newState.randomlyPickAction(getRandom(),regionSelectionFilter(validatedActionFilter)); 479: return action==null?null:validateNewAction(action); 480: } 481: 482: protected ModelAction selectNewActionEarlyStageBackward() { 483: return selectNewActionEarlyStageBackwardGreedy(); 484: } 485: 486: public void onBufferLoss(State actual, State expected) { 487: logEvent(SataEventType.BUFFER_LOSS); 488: } 489: 490: protected ModelAction selectNewActionEpsilonGreedyRandomly() { 491: ModelAction back = newState.getBackAction(); 492: if (back.isValid() && regionAvailable(back)) { 493: if (back.isUnvisited()) { 494: Logger.iprintln("Select Back because Back action is unvisited."); 495: return back; 496: } 497: } 498: if(epsilonSelectionGreedy==null)epsilonSelectionGreedy=egreedy(); 499: if (epsilonSelectionGreedy) { // TODO: this is different from Sarsa. 500: Logger.iformat("Try to select the least visited action."); 501: return newState.greedyPickLeastVisited(regionSelectionFilter(ActionFilter.ENABLED_VALID)); 502: } 503: Logger.iformat("Try to randomly select a visited action."); 504: return newState.randomlyPickAction(getRandom(), regionSelectionFilter(ActionFilter.ENABLED_VALID)); 505: } native/src/com/android/commands/monkey/ape/agent/SataAgent.java:599-621 SHA256 92ec6370d5e2879372f897f4c854a6e7b32dcbc4dc01a361900099b343728bff 599: protected List getGreedyActions(State from, State to) { 600: if (useActionDiffer) { 601: return this.actionDiffer.getUnsaturated(from, to); 602: } 603: return to.collectActions(new BaseActionFilter() { 604: 605: @Override 606: public boolean include(ModelAction action) { 607: if (!ActionFilter.ENABLED_VALID.include(action)) { 608: return false; 609: } 610: if (!action.requireTarget()) { 611: return false; 612: } 613: if (action.isScroll()) { 614: return action.isUnvisited(); 615: } 616: return getGraph().isActionUnvisitedByName(action); 617: } 618: 619: }); 620: } 621: native/src/com/android/commands/monkey/ape/agent/SataAgent.java:982-1005 SHA256 92ec6370d5e2879372f897f4c854a6e7b32dcbc4dc01a361900099b343728bff 982: protected ModelAction findGreedyActionForwardLocal(State prev,State next) { 983: List actions=new ArrayList(getGreedyActions(prev,next)); 984: if(regionExhausted!=null)actions.removeAll(regionExhausted); 985: if (!actions.isEmpty()) { 986: ModelAction action = RandomHelper.randomPickWithPriority(actions); 987: if (action != null) { 988: Logger.iprintln("Find a greedy action in 0-step"); 989: // super.disableRestart(); 990: checkDisableRestart(action); 991: return action; 992: } 993: } 994: return null; 995: } 996: 997: protected ModelAction findGreedyActionForwardRemote(State prev,State next) { 998: for (ModelAction a : next.targetedActions()) { 999: if (!regionAvailable(a) || !ActionFilter.ENABLED_VALID_UNVISITED.include(a)) { // unvisited hence no transitions. 1000: continue; 1001: } 1002: State t = getGraph().getNameGlobalTarget(a); 1003: boolean probe = t != null && jev != null && jev.pendingProbeNavigationCandidate(t); 1004: if (t != null && (isGreedyState(next, t) || probe)) { 1005: Logger.iformat("Find a greedy state %s via a global action %s.", t, a); native/src/com/android/commands/monkey/ape/jev/JevRuntime.java:764-793 SHA256 e5b22f7d4a5613f50c24938d3cf06d49da186078f8dc873c8084f79583cc535e 764: private void inputSummary(InputTrace trace) { 765: evidenceAudit.input(trace.action,step,registry.version(),trace.phase(),trace.total,trace.success,trace.failed,trace.discarded); 766: boolean added=false; 767: if(trace.confirmed()){ 768: added=confirmedInputs.add(trace.action);confirmedInputBranches.put(trace.action,trace.branch); 769: if(semanticReview!=null && trace.action!=null && trace.action.getGUITree()!=null) 770: semanticReview.markExecuted(trace.action.getGUITree().getCurrentState(),trace.action.getModelAction(),trace.action.getGUITreeNode()); 771: } 772: if(Config.getBoolean("ape.jev.historyAudit",false)) { 773: if(trace.confirmed())diagnosticConfirmedSummaries++; 774: GUITree tree=trace.action==null?null:trace.action.getGUITree(); 775: ModelAction action=trace.action==null?null:trace.action.getModelAction(); 776: events.emit("HISTORY_INPUT_SUMMARY",step,registry.version(),action==null?null:action.getState().getGraphId(), 777: action==null?null:action.getGraphId(),JevRegistry.fields("input_confirmed",trace.confirmed(), 778: "new_confirmation_object",added,"confirmation_set_objects",confirmedInputs.size(), 779: "confirmed_summary_events",diagnosticConfirmedSummaries,"event_count",trace.total, 780: "injected_success",trace.success,"injected_failed",trace.failed,"discarded",trace.discarded, 781: "unreported",trace.remaining(),"source_tree",tree==null?JSONObject.NULL:tree.getId(), 782: "source_gui_timestamp",tree==null?JSONObject.NULL:tree.getTimestamp())); 783: } 784: if(trace.entry==null && trace.semanticEntryId==null)return; 785: JSONObject fields=JevRegistry.fields("input_phase",trace.phase(),"event_count",trace.total, 786: "injected_success",trace.success,"injected_failed",trace.failed,"discarded",trace.discarded,"unreported",trace.remaining()); 787: if(trace.entry!=null)entryEvent("PROBE_INPUT_COMPLETED",trace.entry,fields); 788: else events.emit("PROBE_INPUT_COMPLETED",step,registry.version(),null,null,JevRegistry.merge(fields,JevRegistry.fields( 789: "entry_id",trace.semanticEntryId,"request_id",trace.semanticRequestId,"candidate_id",trace.semanticCandidateId))); 790: } 791: public void selectionFailed(String reason) { 792: if(region!=null)region.interrupted(reason); 793: if(selected==null && !semanticSelected)return; native/src/com/android/commands/monkey/ape/jev/JevRuntime.java:848-874 SHA256 e5b22f7d4a5613f50c24938d3cf06d49da186078f8dc873c8084f79583cc535e 848: public void recordSelectionDecision(String strategy,State state,Action selected,int bufferAfter) { 849: finishSemanticTiming(state,selected,selected==null?"no_native_action":"native_action_selected"); 850: if(historyAuditSelectionStart!=0) { 851: if(historyAuditSelectionStep==step)timing.record("history_audit_post_prepare_to_selection",System.nanoTime()-historyAuditSelectionStart); 852: historyAuditSelectionStart=0; 853: } 854: if(state==null)return; 855: JSONObject fields=stateTelemetry(state,"selection"); 856: JevRegistry.merge(fields,JevRegistry.fields("selection_strategy",strategy==null?"unknown":strategy, 857: "selection_context",activePath!=null?"navigation":"exploration", 858: "selection_buffer_size_after",bufferAfter)); 859: if(selected instanceof ModelAction) { 860: ModelAction action=(ModelAction)selected; 861: JevRegistry.merge(fields,JevRegistry.fields("selected_model_action_type",action.getType().name(), 862: "selected_model_action_saturated",action.isSaturated(),"selected_model_action_unvisited",action.isUnvisited(), 863: 864: "selected_effective_unvisited",action.isUnvisited(),"selected_navigation_target",!action.isSaturated(), 865: "selected_model_action_visits",action.getVisitedCount(),"selected_action_fingerprint",action.getActionFingerprint(), 866: "selected_resolved_node_count",action.getResolvedNodes()==null?0:action.getResolvedNodes().length)); 867: events.emit("SELECTION_DECISION",step,registry.version(),state.getGraphId(),action.getGraphId(),fields); 868: return; 869: } 870: events.emit("SELECTION_DECISION",step,registry.version(),state.getGraphId(),null,fields); 871: } 872: 873: private void finishSemanticTiming(State state,Action selected,String reason) { 874: if(semanticReviewStart==0)return; native/src/com/android/commands/monkey/ape/jev/JevRuntime.java:1022-1067 SHA256 e5b22f7d4a5613f50c24938d3cf06d49da186078f8dc873c8084f79583cc535e 1022: private JSONObject stateTelemetry(State state,String reason) { 1023: StringBuilder unvisited=new StringBuilder(); 1024: int modelActions=0,enabledValid=0; 1025: int unsaturated=state.getUnsaturatedActions().size(); 1026: int nativeUnsaturated=0; 1027: int unvisitedCount=0,enabledUnvisited=0; 1028: int effectiveUnvisited=0,navigation=0,local=0; 1029: for(ModelAction action:state.getActions()) { 1030: modelActions++; 1031: if(ActionFilter.ENABLED_VALID.include(action))enabledValid++; 1032: if(ActionFilter.ENABLED_VALID.include(action) && !action.isSaturated())nativeUnsaturated++; 1033: if(action.isUnvisited()) { 1034: if(unvisited.length()>0)unvisited.append(','); 1035: unvisited.append(action.getGraphId()); 1036: unvisitedCount++; 1037: } 1038: if(ActionFilter.ENABLED_VALID.include(action) && action.isUnvisited())enabledUnvisited++; 1039: if(action.isUnvisited())effectiveUnvisited++; 1040: if(ActionFilter.ENABLED_VALID_UNSATURATED.include(action))navigation++; 1041: } 1042: float saturation=modelActions==0?1.0F:0.0F; 1043: for(ModelAction action:state.getActions())saturation+=action.getResolvedSaturation(); 1044: if(modelActions>0)saturation/=modelActions; 1045: return JevRegistry.fields("snapshot_reason",reason,"state_visits",state.getVisitedCount(), 1046: "state_first_seen",state.getFirstVisitedTimestamp(),"state_last_seen",state.getLastVisitedTimestamp(), 1047: "state_saturated",nativeStateSaturated(state),"state_saturation",saturation, 1048: "native_state_saturated",nativeStateSaturated(state),"native_unsaturated_model_action_count",nativeUnsaturated, 1049: "model_action_count",modelActions,"enabled_valid_action_count",enabledValid, 1050: "unsaturated_model_action_count",unsaturated,"unvisited_model_action_count",unvisitedCount, 1051: "effective_unvisited_model_action_count",effectiveUnvisited,"effective_state_saturated",state.isSaturated(), 1052: "local_probe_action_count",local,"navigation_action_count",navigation, 1053: "enabled_valid_unvisited_action_count",enabledUnvisited,"unvisited_model_action_ids",unvisited.toString()); 1054: } 1055: 1056: private boolean nativeStateSaturated(State state) { 1057: for(ModelAction action:state.getActions()) 1058: if(ActionFilter.ENABLED_VALID.include(action) && !action.isSaturated())return false; 1059: return true; 1060: } 1061: } native/src/com/android/commands/monkey/ape/jev/JevEvidenceAudit.java:90-125 SHA256 3e59be5d8e352e8e2b10fd4bd5bcf1a071db334f3975e6a99bd10a0e44b5e6d6 90: static JSONArray nodes(GUITree tree) { 91: JSONArray rows=new JSONArray();ArrayDeque pending=new ArrayDeque(); 92: pending.add(tree.getRootNode());IdentityHashMap ids=new IdentityHashMap(); 93: ids.put(tree.getRootNode(),0); 94: while(!pending.isEmpty()) { 95: GUITreeNode node=pending.removeFirst();JSONArray children=new JSONArray();Iterator it=node.getChildren(); 96: while(it.hasNext()){GUITreeNode child=it.next();int id=ids.size();ids.put(child,id);children.put(id);pending.addLast(child);} 97: rows.put(JevRegistry.fields("id",ids.get(node),"parent",node.getParent()==null?JSONObject.NULL:ids.get(node.getParent()), 98: "children",children,"index",node.getIndex(),"class",node.getClassName(),"package",node.getPackageName(), 99: "resource_id",node.getResourceID(),"native_text",node.getText(),"native_content_desc",node.getContentDesc(), 100: "raw_observation",node.getAuditObservation(),"bounds",rect(node.getBoundsInScreen()), 101: "bounds_in_parent",rect(node.getBoundsInParent()),"enabled",node.isEnabled(),"clickable",node.isClickable(), 102: "long_clickable",node.isLongClickable(),"checkable",node.isCheckable(),"checked",node.isChecked(), 103: "scrollable",node.isScrollable(),"focused",node.isFocused(),"focusable",node.isFocusable(), 104: "selected",node.getJevSelected(),"visible_to_user",node.getJevVisibleToUser(),"drawing_order",node.getJevDrawingOrder(), 105: "generated_image_text",node.isGeneratedImageText())); 106: } 107: return rows; 108: } 109: synchronized JSONObject tree(GUITree tree,long step,long version,String phase) { 110: if(tree==null)return JevRegistry.fields("status","tree_unavailable"); 111: JSONObject old=trees.get(tree);if(old!=null)return old; 112: long start=System.nanoTime(); 113: try { 114: String path="jev-audit/gui/gui-"+tree.getId()+"-t"+tree.getTimestamp()+"-"+sequence.incrementAndGet()+".json"; 115: JSONObject value=stamp("native_gui_tree",step,version); 116: JevRegistry.merge(value,JevRegistry.fields("gui_id",tree.getId(),"gui_timestamp",tree.getTimestamp(),"activity",tree.getActivityClassName(), 117: "state_id",tree.getCurrentState()==null?JSONObject.NULL:tree.getCurrentState().getGraphId(),"phase",phase, 118: "observation_identity",tree.getJevObservationIdentity(),"display_bounds",rect(tree.getDisplayBounds()),"rotation",tree.getDisplayRotation(), 119: "tree_observation_started_ns",tree.getAuditObservationTimes()[0],"tree_observation_finished_ns",tree.getAuditObservationTimes()[1], 120: "tree_observation_wall_time_ms",tree.getAuditObservationTimes()[2], 121: "coverage","complete retained native GUITree; Native preprocessing/pruning is not reconstructed","nodes",nodes(tree))); 122: JSONObject ref=save(path,value);JevRegistry.merge(ref,JevRegistry.fields("gui_id",tree.getId(),"gui_timestamp",tree.getTimestamp(),"snapshot_phase",phase)); 123: if("queued".equals(ref.optString("status")))trees.put(tree,ref);return ref; 124: }catch(Exception error){failures.incrementAndGet();return JevRegistry.fields("status","tree_snapshot_failed");} 125: finally{recordTiming("audit_tree_freeze",System.nanoTime()-start);} native/src/com/android/commands/monkey/ape/jev/JevEvidenceAudit.java:203-243 SHA256 3e59be5d8e352e8e2b10fd4bd5bcf1a071db334f3975e6a99bd10a0e44b5e6d6 203: } 204: void beforeInput(MonkeySourceApe ape,ModelAction action,long step,long version,boolean probe,JSONObject probeShot,String entry,String request,String candidate) { 205: try { 206: GUITreeAction concrete=action.getResolvedGUITreeAction();GUITree source=action.getResolvedGUITree();if(concrete==null||source==null)return; 207: JSONObject value=stamp("native_input",step,version);String id="input-"+sequence.incrementAndGet(); 208: String sampleKey=action.getState().getActivity()+"|"+action.getState().getGraphId()+"|"+(action.getResolvedNode()==null?action.getType().name():auditIdentity(action,action.getResolvedNode()).toString()); 209: boolean history=action.getResolvedNodes()!=null&&action.getResolvedNodes().length>=JevSemanticReview.MIN_CONCRETE_ACTIONS&&sampled.add(sampleKey); 210: JevRegistry.merge(value,JevRegistry.fields("input_id",id,"kind",probe?"probe_input":"native_input","entry_id",entry,"request_id",request,"candidate_id",candidate, 211: "source_gui_id",source.getId(),"source_gui_timestamp",source.getTimestamp(),"concrete",concrete(action,action.getResolvedNode()), 212: "planned_input_text",action.getResolvedNode()==null?JSONObject.NULL:action.getResolvedNode().getInputText(),"probe",probe,"sampled",probe||history)); 213: if(probe||history)value.put("source_gui",tree(source,step,version,"before_input")).put("source_screenshot",probeShot==null?capture(ape,source,step,version,"history_before_input"):probeShot); 214: inputs.put(concrete,value);lastOperation=value;lastInputSource=new NativeSnapshot(source,null,value); 215: synchronized(recent){recent.addLast(value);while(recent.size()>32)recent.removeFirst();} 216: }catch(Exception error){failures.incrementAndGet();} 217: } 218: void input(GUITreeAction action,long step,long version,String phase,int total,int success,int failed,int discarded) { 219: try { 220: JSONObject source=inputs.get(action);JSONObject value=stamp("input_lifecycle",step,version); 221: JevRegistry.merge(value,JevRegistry.fields("input_id",source==null?JSONObject.NULL:source.opt("input_id"),"phase",phase,"event_count",total, 222: "injected_success",success,"injected_failed",failed,"discarded",discarded,"business_success",JSONObject.NULL, 223: "direct_text_injection_limitation","shell sendText/IME business effects are not proven by queued-event completion")); 224: if(source!=null)value.put("input",source);save("jev-audit/inputs/lifecycle-"+sequence.incrementAndGet()+".json",value); 225: }catch(Exception error){failures.incrementAndGet();} 226: } 227: void transition(MonkeySourceApe ape,GUITreeTransition fact,String id,long step,long version,boolean confirmed,boolean probe,JSONObject targetShot) { 228: try { 229: JSONObject transition=JevRegistry.fields("transition_id",id,"input_confirmed",confirmed,"probe",probe, 230: "source_gui_id",fact.getSource().getId(),"target_gui_id",fact.getTarget().getId(),"native_receipt","jev-receipts/transition-"+id.replace(':','-')+".json"); 231: NativeSnapshot observation=new NativeSnapshot(fact.getSource(),fact.getTarget(),transition); 232: lastNative=observation;if(confirmed)lastConfirmedNative=observation; 233: factIds.put(fact,id);List list=facts.get(fact.getAction());if(list==null){list=new ArrayList();facts.put(fact.getAction(),list);}list.add(fact); 234: JSONObject source=inputs.get(fact.getAction());if(source==null||!source.optBoolean("sampled"))return; 235: JSONObject value=stamp("execution_transition",step,version); 236: JevRegistry.merge(value,JevRegistry.fields("transition_id",id,"transition_receipt","jev-receipts/transition-"+id.replace(':','-')+".json", 237: "input_confirmed",confirmed,"probe",probe,"input",source,"business_success",JSONObject.NULL, 238: "target_gui",tree(fact.getTarget(),step,version,"first_native_target"), 239: "target_screenshot",targetShot==null?capture(ape,fact.getTarget(),step,version,"history_first_native_target"):targetShot)); 240: executionEvidence.put(fact,value); 241: save("jev-audit/executions/transition-"+id.replace(':','-')+".json",value); 242: }catch(Exception error){failures.incrementAndGet();} 243: } native/src/com/android/commands/monkey/ape/jev/JevRegistry.java:295-389 SHA256 1f6ab9e42e01565c9831c8b054ee4bbce4d39ff81bfca813adacbd4444b988b3 295: void confirmationSource(com.android.commands.monkey.ape.model.Model model,Set inputs) { 296: if(!confirmationTrackingStarted) { 297: confirmationHistoryComplete=model!=null && model.getGraph().getTreeHistory().isEmpty(); 298: if(model!=null)for(com.android.commands.monkey.ape.model.Model.ActionRecord record:model.getActionHistory()) 299: if(record.modelAction.isModelAction() && record.modelAction.requireTarget())confirmationHistoryComplete=false; 300: confirmationTrackingStarted=true; 301: } 302: // Native refinement mutates this Model in place. An unrelated replacement 303: // cannot inherit the original continuous-confirmation completeness claim. 304: else if(confirmationModel!=model || confirmedInputs!=inputs)confirmationHistoryComplete=false; 305: confirmationModel=model;confirmedInputs=inputs; 306: } 307: static final class ConfirmedHistory { 308: final IdentityHashMap> groups=new IdentityHashMap>(); 309: boolean complete;int total,unknown;long enumerationNanos; 310: List members(ModelAction action) { 311: List found=groups.get(action); 312: return found==null?Collections.emptyList():found; 313: } 314: } 315: ConfirmedHistory confirmedHistory(State state) { 316: long profileStart=JevReviewProfile.start(); 317: long begin=System.nanoTime();ConfirmedHistory view=new ConfirmedHistory(); 318: int before=confirmedInputs.size(); 319: view.complete=confirmationHistoryComplete && confirmationModel!=null && confirmationModel.getVersion()==version 320: && JevHistoryAudit.identityContains(confirmationModel.getGraph().getStates(),state); 321: if(confirmationModel==null){view.enumerationNanos=System.nanoTime()-begin;JevReviewProfile.end("history_scan_group_sort",profileStart);return view;} 322: for(GUITreeAction input:confirmedInputs) { 323: JevDeadline.check();view.total++; 324: if(confirmedMappingProblem(input)!=null){view.unknown++;continue;} 325: if(input.getGUITree().getCurrentState()!=state)continue; 326: ModelAction action=input.getModelAction(); 327: if(!action.requireTarget())continue; 328: List group=view.groups.get(action); 329: if(group==null){group=new ArrayList();view.groups.put(action,group);}group.add(input); 330: } 331: for(List group:view.groups.values())Collections.sort(group,new Comparator() { 332: public int compare(GUITreeAction a,GUITreeAction b) { 333: int c=Integer.compare(a.getGUITree().getTimestamp(),b.getGUITree().getTimestamp()); 334: if(c==0)c=Integer.compare(a.getGUITree().getId(),b.getGUITree().getId()); 335: if(c==0)c=ConcreteActionIdentity.of(a.getActionType().name(),a.getGUITreeNode()).toString() 336: .compareTo(ConcreteActionIdentity.of(b.getActionType().name(),b.getGUITreeNode()).toString()); 337: return c; 338: } 339: }); 340: view.complete &= view.unknown==0 && before==confirmedInputs.size() && confirmationModel.getVersion()==version; 341: view.enumerationNanos=System.nanoTime()-begin;JevReviewProfile.end("history_scan_group_sort",profileStart);return view; 342: } 343: int confirmationCount(){return confirmedInputs.size();} 344: private boolean confirmedExact(GUITreeAction candidate) { 345: long profileStart=JevReviewProfile.start(),examined=0; 346: try { 347: if(confirmationModel==null)return false; 348: for(GUITreeAction prior:confirmedInputs) { 349: examined++; 350: JevDeadline.check(); 351: if(prior.getModelAction()!=candidate.getModelAction())continue; 352: if(confirmedMappingProblem(prior)==null && sameConcrete(candidate,prior))return true; 353: } 354: return false; 355: } finally {JevReviewProfile.end("confirmed_exact",profileStart);JevReviewProfile.add("confirmed_inputs_examined",0,examined);} 356: } 357: 358: /** Exact execution in the current State/tree. This never consults the cross-State index. */ 359: public boolean hasExactExecution(State state,ModelAction action,GUITree tree,GUITreeNode node) { 360: if(state==null || action==null || tree==null || node==null || tree.getCurrentState()!=state)return false; 361: long profileStart=JevReviewProfile.start(); 362: try {GUITreeAction candidate=new GUITreeAction(tree,node,action); 363: return hardMatch(candidate)!=null || confirmedExact(candidate); 364: } finally {JevReviewProfile.end("exact_execution_filter",profileStart);} 365: } 366: 367: /** Confirmed concrete execution in any State of the same activity. */ 368: public boolean hasRelatedConcreteExecution(String actionType,String sourceActivity,String canonicalFullName) { 369: if(actionType==null || sourceActivity==null || canonicalFullName==null || canonicalFullName.length()==0)return false; 370: ConcreteActionIdentity identity=new ConcreteActionIdentity(actionType,canonicalFullName); 371: List values=relatedIndex.get(relatedKey(sourceActivity,identity)); 372: if(values!=null && !values.isEmpty())return true; 373: // Input may be confirmed before its transition is observed/indexed. 374: // Consult the existing authority; do not invent another history index. 375: for(GUITreeAction input:confirmedInputs) { 376: if(input.getGUITreeNode()!=null && confirmedMappingProblem(input)==null && input.getActionType().name().equals(actionType) 377: && sourceActivity.equals(input.getModelAction().getState().getActivity()) 378: && identity.equals(ConcreteActionIdentity.of(actionType,input.getGUITreeNode())))return true; 379: } 380: return false; 381: } 382: private static String relatedKey(String activity,ConcreteActionIdentity identity) { 383: return new JSONArray().put(identity.actionType()).put(activity).put(identity.canonicalFullName()).toString(); 384: } 385: public List suggestions() { 386: List out=new ArrayList(); 387: for(Entry e:audits.values())if(probeAvailable(e))out.add(e); 388: return out; 389: } native/src/com/android/commands/monkey/ape/jev/JevRegistry.java:802-842 SHA256 1f6ab9e42e01565c9831c8b054ee4bbce4d39ff81bfca813adacbd4444b988b3 802: private boolean related(Execution e,String actionType,String sourceActivity,Member candidate) { 803: if(!e.member.type.equals(actionType))return false; 804: if(!e.sourceActivities.contains(sourceActivity==null?"":sourceActivity))return false; 805: return !e.member.fullName.isEmpty() && new ConcreteActionIdentity(e.member.type,e.member.fullName) 806: .equals(new ConcreteActionIdentity(actionType,candidate.fullName)); 807: } 808: private boolean relatedToAny(Execution e,String actionType,String sourceActivity,List candidates) { 809: for(Member candidate:candidates)if(related(e,actionType,sourceActivity,candidate))return true; 810: return false; 811: } 812: private boolean sameStateAction(Execution e,String actionType,State state,ModelAction modelAction) { 813: if(e.anchor==null || !belongsToState(state,e.anchor.tree) || !e.member.type.equals(actionType))return false; 814: return modelAction!=null && e.concreteAction!=null && e.concreteAction.getModelAction()==modelAction; 815: } 816: private boolean belongsToState(State state,GUITree tree) { 817: if(state==null || tree==null)return false; 818: for(GUITree candidate:state.getGUITrees())if(candidate==tree)return true; 819: return false; 820: } 821: private JSONArray history(List currentActions,String actionType,String sourceActivity,State state,ModelAction modelAction,List candidates,List selected,JSONObject contexts) throws JSONException { 822: List sameStateAction=new ArrayList(), relatedOther=new ArrayList(); 823: Set relevant=Collections.newSetFromMap(new IdentityHashMap()); 824: if(!currentActions.isEmpty())relevant.addAll(indexed(executionIndex,currentActions.get(0))); 825: for(Member member:candidates) { 826: List related=relatedIndex.get(relatedKey(sourceActivity==null?"":sourceActivity, 827: new ConcreteActionIdentity(actionType,member.fullName))); 828: if(related!=null)relevant.addAll(related); 829: } 830: for(Execution e:relevant) { 831: boolean current=false; 832: for(GUITreeAction action:currentActions)if(sameConcrete(action,e.concreteAction)){current=true;break;} 833: if(current)continue; 834: if(sameStateAction(e,actionType,state,modelAction))sameStateAction.add(e); 835: else if(belongsToState(state,e.anchor.tree))continue; 836: else if(relatedToAny(e,actionType,sourceActivity,candidates))relatedOther.add(e); 837: } 838: Comparator order=new Comparator() {public int compare(Execution a,Execution b) { 839: String x=a.transitions.isEmpty()?"":a.transitions.get(0),y=b.transitions.isEmpty()?"":b.transitions.get(0); 840: int compared=x.compareTo(y);if(compared!=0)return compared; 841: refreshExecutionOrder();return Integer.compare(executionOrder.get(a),executionOrder.get(b)); 842: }}; native/src/com/android/commands/monkey/ape/jev/JevIdentity.java:50-76 SHA256 ad8b4e330097ec3b2aa134000143afa18f790c8d3626a32536fddf750a5a0850 50: */ 51: public static String text(GUITreeNode node) { 52: if (node == null) { 53: throw new IllegalArgumentException("node must not be null"); 54: } 55: return node.isGeneratedImageText() ? "" : node.getText(); 56: } 57: 58: /** 59: * Returns the node's full native name (type, text, index, ancestor path, 60: * and action patch) with every synthetic image-hash text in the chain 61: * normalized to the empty string. Ordinary nodes keep their names exactly. 62: */ 63: public static Name fullName(GUITreeNode node) { 64: if (node == null) { 65: throw new IllegalArgumentException("node must not be null"); 66: } 67: for (GUITreeNode n = node; n != null; n = n.getParent()) { 68: if (n.isGeneratedImageText()) return NamerFactory.fullNamer().naming(shadowChain(node)); 69: } 70: return NamerFactory.fullNamer().naming(node); 71: } 72: 73: /** 74: * Builds a throwaway copy of the node's ancestor chain (root to node, 75: * inclusive) so the shared full namer can be evaluated without touching 76: * the live nodes. Only the attributes the full namer reads are copied; native/src/com/android/commands/monkey/ape/jev/ConcreteActionIdentity.java:20-100 SHA256 17b1ccffaa18a863b342f75c67b508c0b7854756480c69dde6825b94580c95e6 20: } 21: 22: public static ConcreteActionIdentity of(String actionType, GUITreeNode node) { 23: if (node == null) throw new IllegalArgumentException("node must not be null"); 24: return new ConcreteActionIdentity(actionType, canonical(node)); 25: } 26: 27: /** 28: * Production nodes use the exact native fullName. Small JVM fixtures may 29: * omit optional accessibility strings that the legacy Name renderer 30: * assumes are non-null; use a deterministic attribute chain there rather 31: * than making identity lookup crash or guessing from bounds. 32: */ 33: public static String canonical(final GUITreeNode node) { 34: // Observing an identity returns a string, never a native Name. Do not 35: // assign global Name order to controls merely inspected by review. 36: long start=JevReviewProfile.start(); 37: try {return com.android.commands.monkey.ape.naming.NameManager.withoutInterning( 38: new java.util.function.Supplier() {public String get(){return canonicalReadOnly(node);}}); 39: } finally {JevReviewProfile.end("canonical_render",start);} 40: } 41: 42: private static String canonicalReadOnly(GUITreeNode node) { 43: try { return JevIdentity.fullName(node).toXPath(); } 44: catch (RuntimeException unavailable) { 45: StringBuilder out=new StringBuilder("fallback:"); 46: for(GUITreeNode current=node;current!=null;current=current.getParent()) { 47: out.append('[').append(String.valueOf(current.getClassName())).append('|') 48: .append(String.valueOf(current.getResourceID())).append('|').append(current.getIndex()).append('|') 49: .append(String.valueOf(JevIdentity.text(current))).append('|').append(String.valueOf(current.getContentDesc())) 50: .append('|').append(current.isClickable()).append('|').append(current.isLongClickable()).append('|') 51: .append(current.isCheckable()).append('|').append(current.isScrollable()).append(']'); 52: } 53: return out.toString(); 54: } 55: } 56: 57: public String actionType() { return actionType; } 58: public String canonicalFullName() { return canonicalFullName; } 59: 60: @Override public int hashCode() { 61: return 31 * actionType.hashCode() + canonicalFullName.hashCode(); 62: } 63: 64: @Override public boolean equals(Object other) { 65: if (!(other instanceof ConcreteActionIdentity)) return false; 66: ConcreteActionIdentity value = (ConcreteActionIdentity) other; 67: return actionType.equals(value.actionType) 68: && canonicalFullName.equals(value.canonicalFullName); 69: } 70: 71: @Override public String toString() { 72: return actionType + "|" + canonicalFullName; 73: } 74: }