4242# Throttle check_airplane_mode: at most once per interval to reduce ADB load
4343_last_airplane_check : dict [int , float ] = {}
4444_AIRPLANE_CHECK_INTERVAL = 30.0
45+ _A11Y_FORWARDER_SERVICE = (
46+ 'com.google.androidenv.accessibilityforwarder/'
47+ 'com.google.androidenv.accessibilityforwarder.AccessibilityForwarder'
48+ )
49+ _A11Y_FORWARDER_FLAGS_RECEIVER = (
50+ 'com.google.androidenv.accessibilityforwarder/'
51+ 'com.google.androidenv.accessibilityforwarder.FlagsBroadcastReceiver'
52+ )
4553
4654
4755def _has_wrapper (
@@ -109,7 +117,7 @@ def get_a11y_tree(
109117 try :
110118 forest = env .accumulate_new_extras ()['accessibility_tree' ][- 1 ] # pytype:disable=attribute-error
111119 return forest
112- except KeyError :
120+ except ( KeyError , IndexError ) :
113121 logging .warning ('Could not get a11y tree, retrying.' )
114122 time .sleep (sleep_duration )
115123
@@ -454,18 +462,34 @@ def _restart_a11y_forwarder(self) -> bool:
454462 server_port = str (self ._adb_server_port )
455463 device_args = ['-s' , self ._device_name ] if self ._device_name else []
456464
457- # Step 1: Re-enable the accessibility service
458- cmd = [adb_path , '-P' , server_port ] + device_args + [
459- 'shell' , 'settings' , 'put' , 'secure' ,
460- 'enabled_accessibility_services' ,
461- 'com.google.androidenv.accessibilityforwarder/'
462- 'com.google.androidenv.accessibilityforwarder.AccessibilityForwarder' ,
463- ]
464- result = subprocess .run (cmd , capture_output = True , text = True , timeout = 30 )
465- if result .returncode != 0 :
466- logging .warning ('Failed to re-enable a11y service: %s' , result .stderr )
465+ if self ._is_remote and not self .ensure_adb_connection ():
467466 return False
468467
468+ def run_shell_command (args : list [str ]) -> subprocess .CompletedProcess :
469+ cmd = [adb_path , '-P' , server_port ] + device_args + ['shell' ] + args
470+ return subprocess .run (cmd , capture_output = True , text = True , timeout = 30 )
471+
472+ # Step 1: Re-enable Android accessibility and the forwarder service.
473+ # Some failures leave the service listed but global accessibility off.
474+ for shell_args in (
475+ ['settings' , 'put' , 'secure' , 'accessibility_enabled' , '1' ],
476+ [
477+ 'settings' ,
478+ 'put' ,
479+ 'secure' ,
480+ 'enabled_accessibility_services' ,
481+ _A11Y_FORWARDER_SERVICE ,
482+ ],
483+ ):
484+ result = run_shell_command (shell_args )
485+ if result .returncode != 0 :
486+ logging .warning (
487+ 'Failed to update a11y setting %s: %s' ,
488+ ' ' .join (shell_args ),
489+ result .stderr ,
490+ )
491+ return False
492+
469493 logging .info ('Re-enabled AccessibilityForwarder service' )
470494 time .sleep (2.0 ) # Give the service time to start
471495
@@ -480,8 +504,7 @@ def _restart_a11y_forwarder(self) -> bool:
480504 'shell' , 'am' , 'broadcast' ,
481505 '-a' , 'accessibility_forwarder.intent.action.SET_GRPC' ,
482506 '--ei' , 'port' , str (self ._a11y_port ),
483- '-n' , 'com.google.androidenv.accessibilityforwarder/'
484- 'com.google.androidenv.accessibilityforwarder.FlagsBroadcastReceiver' ,
507+ '-n' , _A11Y_FORWARDER_FLAGS_RECEIVER ,
485508 ]
486509 subprocess .run (cmd , capture_output = True , text = True , timeout = 30 )
487510
@@ -501,6 +524,8 @@ def get_a11y_forest(
501524 2. Restart AccessibilityForwarder service (handles uiautomator disruption)
502525 3. Full environment refresh (handles ADB disconnection / deep failures)
503526 """
527+ self .ensure_adb_connection ()
528+
504529 try :
505530 return self ._get_a11y_forest ()
506531 except RuntimeError :
@@ -535,25 +560,51 @@ def get_ui_elements(self) -> list[representation_utils.UIElement]:
535560 self .ensure_adb_connection ()
536561
537562 if self ._a11y_method == A11yMethod .A11Y_FORWARDER_APP :
538- return representation_utils .forest_to_ui_elements (
539- self .get_a11y_forest (),
540- exclude_invisible_elements = True ,
541- )
563+ try :
564+ return representation_utils .forest_to_ui_elements (
565+ self .get_a11y_forest (),
566+ exclude_invisible_elements = True ,
567+ )
568+ except RuntimeError as e :
569+ logging .warning (
570+ 'A11y tree unavailable after recovery; falling back to '
571+ 'uiautomator UI elements: %s' ,
572+ e ,
573+ )
574+ return self ._get_uiautomator_ui_elements ()
542575 elif self ._a11y_method == A11yMethod .UIAUTOMATOR :
576+ return self ._get_uiautomator_ui_elements ()
577+ else :
578+ return []
579+
580+ def _get_uiautomator_ui_elements (self ) -> list [representation_utils .UIElement ]:
581+ """Returns UI elements from uiautomator, or an empty list if it fails."""
582+ try :
543583 return representation_utils .xml_dump_to_ui_elements (
544584 adb_utils .uiautomator_dump (self ._env )
545585 )
546- else :
586+ except Exception as e :
587+ logging .warning ('Failed to get UI elements via uiautomator: %s' , e )
547588 return []
548589
549590 def _process_timestep (self , timestep : dm_env .TimeStep ) -> dm_env .TimeStep :
550591 """Adds a11y tree info to the observation."""
551592 if self ._a11y_method == A11yMethod .A11Y_FORWARDER_APP :
552- forest = self .get_a11y_forest ()
553- ui_elements = representation_utils .forest_to_ui_elements (
554- forest ,
555- exclude_invisible_elements = True ,
556- )
593+ try :
594+ forest = self .get_a11y_forest ()
595+ except RuntimeError as e :
596+ logging .warning (
597+ 'A11y tree unavailable after recovery; falling back to '
598+ 'uiautomator UI elements: %s' ,
599+ e ,
600+ )
601+ forest = None
602+ ui_elements = self ._get_uiautomator_ui_elements ()
603+ else :
604+ ui_elements = representation_utils .forest_to_ui_elements (
605+ forest ,
606+ exclude_invisible_elements = True ,
607+ )
557608 else :
558609 forest = None
559610 ui_elements = self .get_ui_elements ()
0 commit comments