"""Explicit offline native-window integration checks; requires an existing JDK only. Run with ``uv run python tests/native_actions_fixture/control_windows_jvm_harness.py``. Compiles the repository's actual NativeControls/ControlWindowRoots against small Android fakes. No device, service, network, APK, or configured model is accessed. Generated sources/classes live only in a self-cleaning temporary directory. """ from pathlib import Path from tempfile import TemporaryDirectory from time import perf_counter from touch_jvm_harness import find_tool, run_command STUBS = { "android/os/Build.java": """ package android.os; public final class Build {public static final class VERSION {public static int SDK_INT=36;}} """, "android/os/Looper.java": """ package android.os; public final class Looper {private static final Looper MAIN=new Looper(); public static Looper getMainLooper(){return MAIN;}public static Looper myLooper(){return MAIN;}} """, "android/os/Handler.java": """ package android.os; public final class Handler {public Handler(Looper l){}public boolean post(Runnable r){r.run();return true;} public void removeCallbacks(Runnable r){}} """, "android/os/Bundle.java": """ package android.os;import java.util.*; public final class Bundle {private final Map values=new HashMap<>(); public void putFloat(String k,float v){values.put(k,v);}public void putInt(String k,int v){values.put(k,v);} public void putCharSequence(String k,CharSequence v){values.put(k,v);}public Object get(String k){return values.get(k);}} """, "android/graphics/Rect.java": """ package android.graphics; public final class Rect {public int left,top,right,bottom;public Rect(){} public Rect(int l,int t,int r,int b){left=l;top=t;right=r;bottom=b;} public boolean isEmpty(){return left>=right||top>=bottom;} public static boolean intersects(Rect a,Rect b){return a.left values=new ArrayList<>(); public JSONArray put(Object v){values.add(v);return this;}public int length(){return values.size();} public Object get(int i){return values.get(i);}public JSONObject getJSONObject(int i){return (JSONObject)get(i);} @Override public String toString(){return values.toString();}} """, "org/json/JSONObject.java": """ package org.json;import java.util.*; public final class JSONObject {public static final Object NULL=new Object(){public String toString(){return "null";}}; private final Map values=new LinkedHashMap<>(); public JSONObject put(String k,Object v){values.put(k,v==null?NULL:v);return this;} public boolean has(String k){return values.containsKey(k);}public Object opt(String k){return values.get(k);} public Object get(String k){if(!has(k))throw new IllegalArgumentException(k);return opt(k);} public String getString(String k){return (String)get(k);}public int getInt(String k){return ((Number)get(k)).intValue();} public boolean getBoolean(String k){return (Boolean)get(k);} public JSONObject getJSONObject(String k){return (JSONObject)get(k);} public JSONArray getJSONArray(String k){return (JSONArray)get(k);} public JSONObject optJSONObject(String k){Object v=opt(k);return v instanceof JSONObject?(JSONObject)v:null;} public JSONArray optJSONArray(String k){Object v=opt(k);return v instanceof JSONArray?(JSONArray)v:null;} public boolean optBoolean(String k,boolean d){Object v=opt(k);return v instanceof Boolean?(Boolean)v:d;} public Iterator keys(){return values.keySet().iterator();} @Override public String toString(){return values.toString();}} """, "android/view/accessibility/AccessibilityNodeInfo.java": """ package android.view.accessibility; import android.graphics.Rect;import android.os.Bundle;import java.util.*; public final class AccessibilityNodeInfo { public static final int ACTION_CLICK=16,ACTION_LONG_CLICK=32,ACTION_FOCUS=1,ACTION_SCROLL_FORWARD=4096, ACTION_SCROLL_BACKWARD=8192,ACTION_SET_SELECTION=131072,ACTION_SET_TEXT=2097152; public static final String ACTION_ARGUMENT_PROGRESS_VALUE="progress",ACTION_ARGUMENT_SELECTION_START_INT="start", ACTION_ARGUMENT_SELECTION_END_INT="end",ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE="text"; public static int live,acquired,recycled,doubleRecycle; public static final class Data { public final int identity,window;public String resource="",text="",pkg="app",cls="FrameLayout"; public boolean packageThrows,childThrows,editable,scrollSupport,disableAfterText,visible=true,enabled=true,refresh=true; public int left=10,top=100,right=110,bottom=200,selectionStart=0,selectionEnd=0; public int actionsPerformed;public Data parent;public final List children=new ArrayList<>(); public Data(int id,int win){identity=id;window=win;} public Data add(Data child){children.add(child);if(child!=null)child.parent=this;return this;} } private final Data data;private boolean released; public AccessibilityNodeInfo(Data d){data=d;live++;acquired++;} public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo n){n.valid();return new AccessibilityNodeInfo(n.data);} private void valid(){if(released)throw new IllegalStateException("recycled node reused");} public void recycle(){if(released){doubleRecycle++;throw new IllegalStateException("double recycle");} released=true;live--;recycled++;} public int getWindowId(){valid();return data.window;} public CharSequence getPackageName(){valid();if(data.packageThrows)throw new IllegalStateException("package failed");return data.pkg;} public CharSequence getViewIdResourceName(){valid();return data.resource;} public CharSequence getClassName(){valid();return data.cls;}public CharSequence getText(){valid();return data.text;} public CharSequence getContentDescription(){valid();return "";} public int getChildCount(){valid();return data.children.size();} public AccessibilityNodeInfo getChild(int index){valid();if(data.childThrows)throw new IllegalStateException("child failed"); Data d=data.children.get(index);return d==null?null:new AccessibilityNodeInfo(d);} public AccessibilityNodeInfo getParent(){valid();return data.parent==null?null:new AccessibilityNodeInfo(data.parent);} public void getBoundsInScreen(Rect r){valid();r.left=data.left;r.top=data.top;r.right=data.right;r.bottom=data.bottom;} public boolean refresh(){valid();return data.refresh;} public boolean performAction(int id,Bundle args){valid();data.actionsPerformed++; if(id==ACTION_SET_TEXT){data.text=(String)args.get(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE); if(data.disableAfterText)data.enabled=false;} if(id==ACTION_SET_SELECTION){data.selectionStart=(Integer)args.get(ACTION_ARGUMENT_SELECTION_START_INT); data.selectionEnd=(Integer)args.get(ACTION_ARGUMENT_SELECTION_END_INT);}return true;} public boolean isVisibleToUser(){valid();return data.visible;}public boolean isEnabled(){valid();return data.enabled;} public boolean isEditable(){valid();return data.editable;}public boolean isPassword(){valid();return false;} public boolean isChecked(){valid();return false;}public boolean isCheckable(){valid();return false;} public boolean isSelected(){valid();return false;}public boolean isFocused(){valid();return false;} public boolean isScrollable(){valid();return true;}public boolean isShowingHintText(){valid();return false;} public int getTextSelectionStart(){valid();return data.selectionStart;}public int getTextSelectionEnd(){valid();return data.selectionEnd;} public RangeInfo getRangeInfo(){valid();return null;} public List getActionList(){valid();List actions=new ArrayList<>(); actions.add(new AccessibilityAction(ACTION_CLICK));if(data.editable){actions.add(new AccessibilityAction(ACTION_SET_TEXT)); actions.add(new AccessibilityAction(ACTION_SET_SELECTION));}if(data.scrollSupport)actions.add(new AccessibilityAction(ACTION_SCROLL_FORWARD)); return actions;} @Override public boolean equals(Object o){return o instanceof AccessibilityNodeInfo && data.identity==((AccessibilityNodeInfo)o).data.identity&&data.window==((AccessibilityNodeInfo)o).data.window;} @Override public int hashCode(){return data.identity*31+data.window;} public static final class RangeInfo {public static final int RANGE_TYPE_INT=0; public float getMin(){return 0;}public float getMax(){return 100;}public float getCurrent(){return 50;}public int getType(){return 0;}} public static final class AccessibilityAction {private final int id;public AccessibilityAction(int i){id=i;}public int getId(){return id;} public static final AccessibilityAction ACTION_SHOW_ON_SCREEN=new AccessibilityAction(16908342), ACTION_SET_PROGRESS=new AccessibilityAction(16908349),ACTION_SCROLL_UP=new AccessibilityAction(16908344), ACTION_SCROLL_DOWN=new AccessibilityAction(16908346),ACTION_SCROLL_LEFT=new AccessibilityAction(16908345), ACTION_SCROLL_RIGHT=new AccessibilityAction(16908347),ACTION_IME_ENTER=new AccessibilityAction(16908372);} } """, "android/view/accessibility/AccessibilityWindowInfo.java": """ package android.view.accessibility;import android.graphics.Rect;import android.graphics.Region; public final class AccessibilityWindowInfo { public static final int TYPE_APPLICATION=1,TYPE_INPUT_METHOD=2,TYPE_SYSTEM=3; public static int live,recycled,doubleRecycle; public static final class Data { public final int id,layer;public int type=TYPE_APPLICATION,display; public boolean active,rootThrows,metadataThrows,boundsThrow,regionThrows; public int[][] regions; public int left=0,top=0,right=1080,bottom=2400; public AccessibilityNodeInfo.Data root;public Data(int i,int l,boolean a,AccessibilityNodeInfo.Data r){id=i;layer=l;active=a;root=r;} } private final Data data;private boolean released; public AccessibilityWindowInfo(Data d){data=d;live++;} private void valid(){if(released)throw new IllegalStateException("recycled window reused"); if(data.metadataThrows)throw new IllegalStateException("metadata unavailable");} public int getId(){valid();return data.id;}public int getType(){valid();return data.type;} public int getLayer(){valid();return data.layer;}public int getDisplayId(){valid();return data.display;} public boolean isActive(){valid();return data.active;} public void getBoundsInScreen(Rect r){valid();if(data.boundsThrow)throw new IllegalStateException("bounds failed"); r.left=data.left;r.top=data.top;r.right=data.right;r.bottom=data.bottom;} public void getRegionInScreen(Region r){valid();if(data.regionThrows)throw new IllegalStateException("region failed"); if(data.regions==null){r.set(new Rect(data.left,data.top,data.right,data.bottom));return;} r.setEmpty();for(int[] part:data.regions)r.op(new Rect(part[0],part[1],part[2],part[3]),Region.Op.UNION);} public AccessibilityNodeInfo getRoot(){valid();if(data.rootThrows)throw new IllegalStateException("root failed"); return data.root==null?null:new AccessibilityNodeInfo(data.root);} public void recycle(){if(released){doubleRecycle++;throw new IllegalStateException("double window recycle");} released=true;live--;recycled++;} } """, "android/accessibilityservice/AccessibilityService.java": """ package android.accessibilityservice; import android.view.accessibility.*;import java.util.*; public final class AccessibilityService { public AccessibilityNodeInfo.Data active;public final List windows=new ArrayList<>(); public boolean windowsThrow,windowsNull,activeThrows;public int windowReads;public Runnable beforeWindowRead; public AccessibilityNodeInfo getRootInActiveWindow(){if(activeThrows)throw new IllegalStateException("active root failed"); return active==null?null:new AccessibilityNodeInfo(active);} public List getWindows(){windowReads++;if(beforeWindowRead!=null)beforeWindowRead.run(); if(windowsThrow)throw new IllegalStateException("windows unavailable"); if(windowsNull)return null;List result=new ArrayList<>(); IdentityHashMap seen=new IdentityHashMap<>(); for(AccessibilityWindowInfo.Data d:windows){if(d==null){result.add(null);continue;} AccessibilityWindowInfo w=seen.get(d);if(w==null){w=new AccessibilityWindowInfo(d);seen.put(d,w);}result.add(w);}return result;} } """, "com/artemis/helper/ControlWindowsTest.java": """ package com.artemis.helper; import android.accessibilityservice.AccessibilityService;import android.view.accessibility.*; import android.os.Build;import java.util.concurrent.atomic.AtomicBoolean;import org.json.*; public final class ControlWindowsTest { static int checks,scenarios,nextId=1;static String scenario; static void check(boolean v,String m){checks++;if(!v)throw new AssertionError(scenario+": "+m);} static void eq(Object a,Object e,String m){check(e.equals(a),m+" actual="+a+" expected="+e);} static AccessibilityNodeInfo.Data node(int window,String resource){AccessibilityNodeInfo.Data d=new AccessibilityNodeInfo.Data(nextId++,window);d.resource=resource;return d;} static AccessibilityWindowInfo.Data window(AccessibilityNodeInfo.Data root,int layer,boolean active){return new AccessibilityWindowInfo.Data(root.window,layer,active,root);} static AccessibilityService app(){AccessibilityService s=new AccessibilityService();s.active=node(217,"empty-shell"); s.windows.add(window(s.active,0,true));return s;} static AccessibilityNodeInfo.Data add(AccessibilityService s,int id,int layer,String resource){ AccessibilityNodeInfo.Data d=node(id,resource);s.windows.add(window(d,layer,false));return d;} static void rect(AccessibilityNodeInfo.Data node,int l,int t,int r,int b){node.left=l;node.top=t;node.right=r;node.bottom=b;} static void rect(AccessibilityWindowInfo.Data window,int l,int t,int r,int b){window.left=l;window.top=t;window.right=r;window.bottom=b;} static JSONObject args(String resource){return new JSONObject().put("selector",new JSONObject().put("resource_id",resource));} static JSONObject inspect(AccessibilityService s,String resource){return NativeControls.inspect(s,args(resource));} static JSONObject click(AccessibilityService s,String resource){return NativeControls.act(s,args(resource).put("action","click"));} static void code(JSONObject r,String expected){eq(r.getString("code"),expected,"receipt code");} static void begin(String name){quiet();scenario=name;scenarios++;} static void quiet(){check(AccessibilityNodeInfo.live==0,"all node handles recycled: "+AccessibilityNodeInfo.live); check(AccessibilityWindowInfo.live==0,"all window handles recycled: "+AccessibilityWindowInfo.live); check(AccessibilityNodeInfo.doubleRecycle==0,"no node double recycle"); check(AccessibilityWindowInfo.doubleRecycle==0,"no window double recycle");} public static void main(String[] ignored){ begin("real empty active shell with inactive upper player");AccessibilityService s=app(); AccessibilityNodeInfo.Data player=add(s,216,1,"player");player.add(node(216,"tv_timeline_date")).add(node(216,"tab_play_or_pause")); JSONObject r=inspect(s,"tv_timeline_date");code(r,"OK");eq(r.getString("execution"),"observed","read only receipt"); eq(r.getJSONObject("node").getString("resource_id"),"tv_timeline_date","upper player resolved"); r=click(s,"tab_play_or_pause");code(r,"OK");eq(r.getString("execution"),"completed","write receipt"); eq(player.children.get(1).actionsPerformed,1,"exactly one upper-window action"); code(inspect(s,"empty-shell"),"OK"); begin("active modal excludes its lower application");s=app();s.windows.clear();s.windows.add(window(s.active,3,true)); AccessibilityNodeInfo.Data lower=add(s,216,1,"behind-dialog");code(inspect(s,"behind-dialog"),"NOT_FOUND"); code(click(s,"behind-dialog"),"NOT_FOUND");eq(lower.actionsPerformed,0,"no lower-window input");code(inspect(s,"empty-shell"),"OK"); begin("other packages, system and IME never become candidate controls");s=app(); AccessibilityNodeInfo.Data other=add(s,216,1,"outside");other.pkg="other.app"; AccessibilityNodeInfo.Data system=add(s,218,2,"outside");s.windows.get(2).type=AccessibilityWindowInfo.TYPE_SYSTEM; s.windows.get(2).rootThrows=true;AccessibilityNodeInfo.Data ime=add(s,219,3,"outside"); s.windows.get(3).type=AccessibilityWindowInfo.TYPE_INPUT_METHOD;s.windows.get(3).rootThrows=true; code(click(s,"outside"),"NOT_FOUND");eq(other.actionsPerformed+system.actionsPerformed+ime.actionsPerformed,0,"no foreign input"); begin("different display cannot share layer ordering");s=app();add(s,216,1,"outside");s.windows.get(1).display=7; code(inspect(s,"outside"),"NOT_FOUND"); begin("API 24 window metadata remains usable");Build.VERSION.SDK_INT=24;s=app();add(s,216,1,"old-api"); code(inspect(s,"old-api"),"OK");Build.VERSION.SDK_INT=36; begin("matches across active and upper roots are globally ambiguous");s=app();s.active.resource="duplicate"; AccessibilityNodeInfo.Data duplicate=add(s,216,1,"duplicate");r=click(s,"duplicate");code(r,"AMBIGUOUS"); eq(r.getInt("candidate_count"),2,"global count");eq(r.getJSONArray("candidates").length(),2,"global candidates"); eq(s.active.actionsPerformed+duplicate.actionsPerformed,0,"ambiguity performs no action"); begin("same-layer application still participates in uniqueness");s=app();s.active.resource="same-layer"; add(s,216,0,"same-layer");code(inspect(s,"same-layer"),"AMBIGUOUS"); begin("duplicate window handles and root snapshots do not invent ambiguity");s=app();add(s,216,1,"unique"); s.windows.add(s.windows.get(1));s.windows.add(new AccessibilityWindowInfo.Data(216,1,false,s.windows.get(1).root)); s.windows.add(s.windows.get(0));code(inspect(s,"unique"),"OK");code(click(s,"unique"),"OK"); eq(s.windows.get(1).root.actionsPerformed,1,"duplicate Region metadata permits only one native action"); begin("eligible unreadable roots invalidate an otherwise unique active match");s=app(); add(s,216,1,"anything");s.windows.get(1).root=null;code(click(s,"empty-shell"),"READ_FAILED"); eq(s.active.actionsPerformed,0,"unreadable candidate never permits fake uniqueness");code(inspect(s,"absent"),"READ_FAILED"); begin("candidate getRoot exception does not become absence");s=app();add(s,216,1,"anything"); s.windows.get(1).rootThrows=true;code(inspect(s,"absent"),"READ_FAILED"); begin("candidate package missing or unreadable fails closed");s=app();AccessibilityNodeInfo.Data bad=add(s,216,1,"anything"); bad.pkg=null;code(inspect(s,"absent"),"READ_FAILED");bad.pkg="";code(inspect(s,"absent"),"READ_FAILED"); bad.pkg="app";bad.packageThrows=true;code(inspect(s,"absent"),"READ_FAILED"); begin("candidate root identity changed during discovery");s=app();add(s,216,1,"anything"); s.windows.get(1).root=node(999,"anything");code(inspect(s,"absent"),"READ_FAILED"); begin("candidate child cannot be read");s=app();bad=add(s,216,1,"upper");bad.add(null); code(inspect(s,"absent"),"READ_FAILED");bad.children.clear();bad.add(node(216,"child"));bad.childThrows=true; code(inspect(s,"absent"),"READ_FAILED"); begin("scope resolves in upper window, target remains confined to its descendants");s=app(); AccessibilityNodeInfo.Data scope=add(s,216,1,"container");scope.add(node(216,"inside"));s.active.add(node(217,"outside")); JSONObject scoped=args("inside").put("scope",new JSONObject().put("resource_id","container")); code(NativeControls.inspect(s,scoped),"OK");scoped.put("selector",new JSONObject().put("resource_id","outside")); code(NativeControls.inspect(s,scoped),"NOT_FOUND"); begin("scopes themselves must remain globally unique");s=app();s.active.resource="container";add(s,216,1,"container"); code(NativeControls.inspect(s,args("inside").put("scope",new JSONObject().put("resource_id","container"))),"AMBIGUOUS"); begin("candidate array remains capped across windows");s=app();s.active.resource="duplicate"; for(int i=0;i<12;i++)add(s,300+i,1,"duplicate");r=inspect(s,"duplicate");code(r,"AMBIGUOUS"); eq(r.getInt("candidate_count"),13,"all matches counted");eq(r.getJSONArray("candidates").length(),10,"candidate snapshot cap"); begin("node budget is shared across roots, not reset for each window");s=app(); AccessibilityNodeInfo.Data upper=add(s,216,1,"upper");for(int i=0;i<3000;i++){ s.active.add(node(217,"active-child"));upper.add(node(216,"upper-child"));} code(inspect(s,"absent"),"LIMIT_EXCEEDED"); begin("search-limit failure cannot dispatch an early unique match");s.active.resource="unique"; code(click(s,"unique"),"LIMIT_EXCEEDED");eq(s.active.actionsPerformed,0,"bounded search proves no fake uniqueness"); begin("window enumeration has a hard upper bound with complete cleanup");s=app(); for(int i=0;i<65;i++)add(s,300+i,1,"upper");code(inspect(s,"absent"),"LIMIT_EXCEEDED"); begin("missing active root is unavailable, never absent");s=app();s.active=null; code(inspect(s,"absent"),"UNAVAILABLE"); begin("empty, missing and unavailable metadata cannot prove uniqueness or absence");s=app();s.windows.clear(); code(inspect(s,"empty-shell"),"UNAVAILABLE");code(inspect(s,"absent"),"UNAVAILABLE");s.windowsNull=true; code(inspect(s,"empty-shell"),"UNAVAILABLE");s.windowsNull=false;s.windowsThrow=true;code(inspect(s,"empty-shell"),"UNAVAILABLE"); begin("missing active window cannot broaden to an unrelated observed application");s=app();s.windows.clear();add(s,216,1,"upper"); code(inspect(s,"upper"),"UNAVAILABLE");code(inspect(s,"empty-shell"),"UNAVAILABLE"); begin("active metadata is not current");s=app();s.windows.get(0).active=false;add(s,216,1,"upper"); code(inspect(s,"upper"),"UNAVAILABLE"); begin("active metadata unavailable remains conservative");s=app();s.windows.get(0).metadataThrows=true;add(s,216,1,"upper"); code(inspect(s,"upper"),"UNAVAILABLE"); begin("declared candidate metadata missing after active metadata is established");s=app();s.windows.add(null); code(inspect(s,"absent"),"READ_FAILED"); begin("single known application window permits one visible native action");s=app(); code(click(s,"empty-shell"),"OK");eq(s.active.actionsPerformed,1,"single-window input"); begin("invisible targets remain observable but cannot receive native actions");s=app();s.active.visible=false; code(inspect(s,"empty-shell"),"OK");code(click(s,"empty-shell"),"NOT_VISIBLE"); eq(s.active.actionsPerformed,0,"invisible target never written"); begin("nonfocusable same-package higher modal blocks retained active root writes");s=app();add(s,216,1,"modal"); code(inspect(s,"empty-shell"),"OK");r=click(s,"empty-shell");code(r,"OCCLUDED"); eq(r.getString("execution"),"not_started","blocked before input");eq(s.active.actionsPerformed,0,"no modal penetration"); begin("any higher package or window type blocks full coverage"); for(int type:new int[]{1,2,3}){s=app();AccessibilityNodeInfo.Data blocker=add(s,216,1,"blocker"); blocker.pkg="other.app";s.windows.get(1).type=type;code(click(s,"empty-shell"),"OCCLUDED"); eq(s.active.actionsPerformed,0,"foreign/system/IME overlap blocked");} begin("higher nonoverlapping status bar leaves the app target actionable");s=app(); add(s,216,1,"status-bar");s.windows.get(1).type=AccessibilityWindowInfo.TYPE_SYSTEM;s.windows.get(1).bottom=48; code(click(s,"empty-shell"),"OK");eq(s.active.actionsPerformed,1,"nonoverlapping system bar allowed"); begin("empty higher bounds do not claim occlusion");s=app();add(s,216,1,"empty-window"); s.windows.get(1).right=0;code(click(s,"empty-shell"),"OK"); begin("higher windows on another display do not occlude this display");s=app();add(s,216,1,"other-display"); s.windows.get(1).display=7;code(click(s,"empty-shell"),"OK"); begin("own window disappearance between resolution and action is unavailable"); final AccessibilityService removed=app();removed.beforeWindowRead=()->{if(removed.windowReads==2)removed.windows.clear();}; code(click(removed,"empty-shell"),"UNAVAILABLE");eq(removed.active.actionsPerformed,0,"missing context blocks input"); begin("a new overlapping window is checked immediately before action"); final AccessibilityService newlyCovered=app();newlyCovered.beforeWindowRead=()->{ if(newlyCovered.windowReads==2)add(newlyCovered,216,1,"new-modal");}; code(click(newlyCovered,"empty-shell"),"OCCLUDED");eq(newlyCovered.active.actionsPerformed,0,"new modal blocks input"); begin("latest metadata read failure never permits input"); final AccessibilityService latestFailure=app();latestFailure.beforeWindowRead=()->{ if(latestFailure.windowReads==2)latestFailure.windowsThrow=true;}; code(click(latestFailure,"empty-shell"),"UNAVAILABLE");eq(latestFailure.active.actionsPerformed,0,"failed latest query blocks input"); begin("unknown target window bounds are unavailable");s=app();s.windows.get(0).boundsThrow=true; code(click(s,"empty-shell"),"UNAVAILABLE");eq(s.active.actionsPerformed,0,"unknown bounds block input"); begin("unknown active display cannot prove complete resolution");s=app();s.windows.get(0).display=-1; code(inspect(s,"empty-shell"),"UNAVAILABLE");code(inspect(s,"absent"),"UNAVAILABLE"); begin("unknown potential occluder display cannot authorize input");s=app();add(s,216,1,"system-window"); s.windows.get(1).type=AccessibilityWindowInfo.TYPE_SYSTEM;s.windows.get(1).display=-1; code(click(s,"empty-shell"),"UNAVAILABLE");eq(s.active.actionsPerformed,0,"unknown display blocks input"); begin("empty target or own-window bounds cannot authorize input");s=app();s.active.right=s.active.left; code(click(s,"empty-shell"),"UNAVAILABLE");s=app();s.windows.get(0).right=0; code(click(s,"empty-shell"),"UNAVAILABLE"); begin("post-edit caret action rechecks latest windows and retains unknown partial receipt"); final AccessibilityService edit=app();edit.active.editable=true;edit.active.text="abc"; edit.beforeWindowRead=()->{if(edit.windowReads==3)add(edit,216,1,"new-overlay");}; r=NativeControls.act(edit,args("empty-shell").put("action","replace_selection").put("text","Z") .put("selection_start",0).put("selection_end",1));code(r,"OCCLUDED"); eq(r.getString("execution"),"unknown","accepted text with blocked caret stays unknown"); eq(edit.active.text,"Zbc","accepted edit retained");eq(edit.active.actionsPerformed,1,"caret not dispatched through overlay"); begin("real code14 editor selection toolbar and handles leave editable area exposed"); s=new AccessibilityService();s.active=node(231,"activity");s.windows.add(window(s.active,0,true)); rect(s.windows.get(0),0,0,720,1600);AccessibilityNodeInfo.Data editor=node(231,"editor"); editor.editable=true;editor.text="A😀B中文";rect(editor,24,534,696,638);s.active.add(editor); add(s,232,4,"selection-toolbar");rect(s.windows.get(1),32,446,554,542); add(s,233,2,"selection-handle-left");rect(s.windows.get(2),1,611,76,691); add(s,234,3,"selection-handle-right");rect(s.windows.get(3),77,611,165,691); add(s,226,5,"status-bar");s.windows.get(4).type=AccessibilityWindowInfo.TYPE_SYSTEM;rect(s.windows.get(4),0,0,720,48); add(s,218,1,"keyboard");s.windows.get(5).type=AccessibilityWindowInfo.TYPE_INPUT_METHOD;rect(s.windows.get(5),0,1002,720,1600); r=NativeControls.act(s,args("editor").put("action","replace_selection").put("text"," X ") .put("selection_start",1).put("selection_end",3));code(r,"OK"); eq(r.getString("execution"),"completed","partial occlusion permits native edit");eq(editor.text,"A X B中文","UTF-16 replacement retained"); eq(editor.actionsPerformed,2,"text and caret each accepted");eq(editor.selectionStart,4,"caret restored after inserted whitespace"); begin("partial higher overlap permits a native action on the exposed part");s=app();add(s,216,1,"partial-overlay"); rect(s.windows.get(1),0,0,60,300);code(click(s,"empty-shell"),"OK");eq(s.active.actionsPerformed,1,"exposed part remains actionable"); begin("multiple higher windows jointly covering the target deny input");s=app();add(s,216,1,"cover-left"); rect(s.windows.get(1),0,0,60,300);add(s,218,2,"cover-right");rect(s.windows.get(2),60,0,200,300); code(click(s,"empty-shell"),"OCCLUDED");eq(s.active.actionsPerformed,0,"union coverage blocks input"); begin("one pixel gap in joint coverage remains exposed");s=app();add(s,216,1,"cover-left"); rect(s.windows.get(1),0,0,60,300);add(s,218,2,"cover-right");rect(s.windows.get(2),61,0,200,300); code(click(s,"empty-shell"),"OK");eq(s.active.actionsPerformed,1,"integer region gap preserved"); begin("target area outside its own window cannot bypass full coverage inside");s=app(); rect(s.windows.get(0),10,100,60,200);add(s,216,1,"cover-visible-half");rect(s.windows.get(1),10,100,60,200); code(click(s,"empty-shell"),"OCCLUDED");eq(s.active.actionsPerformed,0,"own-window clipping precedes occlusion"); begin("overlap only outside the own-window clip does not block exposed area");s=app(); rect(s.windows.get(0),10,100,60,200);add(s,216,1,"outside-own-window");rect(s.windows.get(1),60,100,110,200); code(click(s,"empty-shell"),"OK");eq(s.active.actionsPerformed,1,"outside clip is irrelevant"); begin("a target completely outside its own window is unavailable");s=app();rect(s.windows.get(0),0,0,5,50); code(click(s,"empty-shell"),"UNAVAILABLE");eq(s.active.actionsPerformed,0,"invalid clip cannot authorize input"); begin("screen-sized container can scroll between system and IME edge windows");s=app(); rect(s.active,0,0,720,1600);rect(s.windows.get(0),0,0,720,1600);s.active.scrollSupport=true; add(s,216,2,"status-bar");s.windows.get(1).type=AccessibilityWindowInfo.TYPE_SYSTEM;rect(s.windows.get(1),0,0,720,48); add(s,218,1,"keyboard");s.windows.get(2).type=AccessibilityWindowInfo.TYPE_INPUT_METHOD;rect(s.windows.get(2),0,1002,720,1600); code(NativeControls.act(s,args("empty-shell").put("action","scroll_forward")),"OK"); eq(s.active.actionsPerformed,1,"visible middle permits native scroll"); begin("API30 own L-shaped region does not expose a target inside its bounding-box hole");Build.VERSION.SDK_INT=30;s=app(); rect(s.active,50,100,100,150);rect(s.windows.get(0),0,0,200,300); s.windows.get(0).regions=new int[][]{{0,0,20,300},{0,0,200,50}}; code(click(s,"empty-shell"),"OCCLUDED");eq(s.active.actionsPerformed,0,"own L-shape hole blocks input");Build.VERSION.SDK_INT=36; begin("API30 own disconnected region cannot authorize a target in its gap");s=app(); rect(s.active,50,100,90,200);rect(s.windows.get(0),0,0,140,300); s.windows.get(0).regions=new int[][]{{0,0,40,300},{100,0,140,300}}; code(click(s,"empty-shell"),"OCCLUDED");eq(s.active.actionsPerformed,0,"own disconnected gap blocks input"); begin("API30 higher region hole leaves the target actionable despite bounding-box overlap");s=app(); add(s,216,1,"hollow-overlay");rect(s.windows.get(1),0,0,200,300); s.windows.get(1).regions=new int[][]{{0,0,200,100},{0,200,200,300},{0,100,10,200},{110,100,200,200}}; code(click(s,"empty-shell"),"OK");eq(s.active.actionsPerformed,1,"actual overlay hole stays exposed"); begin("API30 disconnected higher regions jointly covering all target area deny input");s=app(); add(s,216,1,"alternating-a");rect(s.windows.get(1),10,100,85,200); s.windows.get(1).regions=new int[][]{{10,100,35,200},{60,100,85,200}}; add(s,218,2,"alternating-b");rect(s.windows.get(2),35,100,110,200); s.windows.get(2).regions=new int[][]{{35,100,60,200},{85,100,110,200}}; code(click(s,"empty-shell"),"OCCLUDED");eq(s.active.actionsPerformed,0,"union of disconnected regions blocks input"); begin("API29 uses conservative rectangular bounds without calling unsupported Region getter"); Build.VERSION.SDK_INT=29;s=app();add(s,216,1,"hollow-overlay");rect(s.windows.get(1),0,0,200,300); s.windows.get(1).regions=new int[][]{{0,0,200,100},{0,200,200,300}};s.windows.get(1).regionThrows=true; s.windows.get(0).regionThrows=true;code(click(s,"empty-shell"),"OCCLUDED");Build.VERSION.SDK_INT=36; begin("API24 rectangular subtraction still permits partially exposed targets");Build.VERSION.SDK_INT=24; s=app();add(s,216,1,"partial-overlay");rect(s.windows.get(1),0,0,60,300); s.windows.get(0).regionThrows=true;s.windows.get(1).regionThrows=true; code(click(s,"empty-shell"),"OK");Build.VERSION.SDK_INT=36; begin("API30 failed Region metadata cannot silently fall back to rectangular bounds");s=app(); s.windows.get(0).regionThrows=true;code(click(s,"empty-shell"),"UNAVAILABLE");eq(s.active.actionsPerformed,0,"own Region failure blocks input"); s=app();add(s,216,1,"system-window");s.windows.get(1).type=AccessibilityWindowInfo.TYPE_SYSTEM; s.windows.get(1).regionThrows=true;code(click(s,"empty-shell"),"UNAVAILABLE"); eq(s.active.actionsPerformed,0,"higher Region failure blocks input"); begin("post-edit disabled control prevents the caret action and preserves partial unknown");s=app(); s.active.editable=true;s.active.text="abc";s.active.disableAfterText=true; r=NativeControls.act(s,args("empty-shell").put("action","replace_selection").put("text","Z") .put("selection_start",0).put("selection_end",1));code(r,"DISABLED");eq(r.getString("execution"),"unknown","disabled caret is partial"); eq(s.active.text,"Zbc","accepted edit remains");eq(s.active.actionsPerformed,1,"disabled caret not dispatched"); begin("pre-cancelled discovery does not acquire input or roots");s=app(); try(ControlWindowRoots roots=ControlWindowRoots.capture(s,new AtomicBoolean(true))){ eq(roots.error,"CANCELLED","cancelled discovery");eq(roots.size(),0,"no root acquired");} quiet();System.out.println("PASS "+checks+" checks across "+scenarios+" native-window scenarios"); } } """, } def main() -> int: started = perf_counter() try: java, javac = find_tool("java"), find_tool("javac") helper = ( Path(__file__).resolve().parents[2] / "packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper" ) actual = [helper / name for name in ("NativeControls.java", "ControlWindowRoots.java")] if not all(path.is_file() for path in actual): raise RuntimeError("The repository native-control Java sources are missing") print("Offline Android-window simulation against actual Java; no device input.", flush=True) with TemporaryDirectory(prefix="artemis-control-windows-jvm-") as temporary: workspace = Path(temporary) classes = workspace / "classes" classes.mkdir() generated = [] for name, source in STUBS.items(): destination = workspace / "src" / name destination.parent.mkdir(parents=True, exist_ok=True) destination.write_text(source.lstrip("\n"), encoding="utf-8") generated.append(str(destination)) run_command( [ javac, "-encoding", "UTF-8", "-source", "8", "-target", "8", "-Xlint:-options", "-d", str(classes), *generated, *(str(path) for path in actual), ], timeout=60, ) run_command([java, "-cp", str(classes), "com.artemis.helper.ControlWindowsTest"], 30) print(f"Native-window harness completed in {perf_counter() - started:.2f}s.") return 0 except Exception as error: print(f"FAIL: {error}") return 1 if __name__ == "__main__": raise SystemExit(main())