11/***************************************************************************************************
22 *
3- * Copyright (c) 2020 - 2025 Universitat Politecnica de Valencia - www.upv.es
4- * Copyright (c) 2020 - 2025 Open Universiteit - www.ou.nl
3+ * Copyright (c) 2020 - 2026 Universitat Politecnica de Valencia - www.upv.es
4+ * Copyright (c) 2020 - 2026 Open Universiteit - www.ou.nl
55 *
66 * Redistribution and use in source and binary forms, with or without
77 * modification, are permitted provided that the following conditions are met:
@@ -75,6 +75,7 @@ public class AndroidAppiumFramework extends SUTBase {
7575 public static AndroidAppiumFramework androidSUT = null ;
7676
7777 private static AndroidDriver driver = null ;
78+ private static volatile boolean driverUnresponsive = false ;
7879
7980 // Appium v2 do not use /wd/hub suffix anymore
8081 // It can be enabled using the "--base-path /wd/hub" command when launching the Appium server
@@ -138,6 +139,26 @@ public static AndroidDriver getDriver() {
138139 return driver ;
139140 }
140141
142+ static void markDriverUnresponsive (Throwable t ) {
143+ driverUnresponsive = true ;
144+ if (t != null ) {
145+ if (t .getMessage () != null ) {
146+ System .err .println ("AndroidAppiumFramework: Android driver is unresponsive: " + t .getClass ().getSimpleName () + " - " + t .getMessage ());
147+ } else {
148+ System .err .println ("AndroidAppiumFramework: Android driver is unresponsive" );
149+ t .printStackTrace ();
150+ }
151+ }
152+ }
153+
154+ static boolean isDriverUnresponsive () {
155+ return driverUnresponsive ;
156+ }
157+
158+ static void resetDriverUnresponsive () {
159+ driverUnresponsive = false ;
160+ }
161+
141162 public static List <WebElement > findElements (By by ){
142163 return driver .findElements (by );
143164 }
@@ -333,8 +354,12 @@ public static void generateText() {
333354 }
334355
335356 public static String getCurrentPackage () {
336- String currentPackage = driver .getCurrentPackage ();
337- return currentPackage ;
357+ try {
358+ return driver .getCurrentPackage ();
359+ } catch (WebDriverException wde ) {
360+ markDriverUnresponsive (wde );
361+ return "" ;
362+ }
338363 }
339364
340365 public static void pressKeyEvent (KeyEvent keyEvent ){
@@ -380,7 +405,12 @@ public static void pushFile(String remotePath, File file){
380405 }
381406
382407 public static String getActivity () {
383- return driver .currentActivity ();
408+ try {
409+ return driver .currentActivity ();
410+ } catch (WebDriverException wde ) {
411+ markDriverUnresponsive (wde );
412+ return "" ;
413+ }
384414 }
385415
386416 public static String getScreenshotSpyMode (String stateID ) throws IOException {
@@ -393,15 +423,27 @@ public static String getScreenshotSpyMode(String stateID) throws IOException {
393423 }
394424
395425 public static String getScreenshotState (State state ) throws IOException {
396- byte [] byteImage = driver .getScreenshotAs (OutputType .BYTES );
397- InputStream is = new ByteArrayInputStream (byteImage );
398- AWTCanvas canvas = AWTCanvas .fromInputStream (is );
399- return ScreenshotSerialiser .saveStateshot (state .get (Tags .ConcreteID , "NoConcreteIdAvailable" ), canvas );
426+ try {
427+ byte [] byteImage = driver .getScreenshotAs (OutputType .BYTES );
428+ InputStream is = new ByteArrayInputStream (byteImage );
429+ AWTCanvas canvas = AWTCanvas .fromInputStream (is );
430+ return ScreenshotSerialiser .saveStateshot (state .get (Tags .ConcreteID , "NoConcreteIdAvailable" ), canvas );
431+ } catch (WebDriverException wde ) {
432+ markDriverUnresponsive (wde );
433+ throw new IOException ("Exception: AndroidDriver getScreenshotState failed" , wde );
434+ }
400435 }
401436
402437 public static String getScreenshotAction (State state , Action action ) throws IOException {
403- byte [] byteImage = driver .getScreenshotAs (OutputType .BYTES );
404- InputStream is = new ByteArrayInputStream (byteImage );
438+ byte [] byteImage ;
439+ InputStream is ;
440+ try {
441+ byteImage = driver .getScreenshotAs (OutputType .BYTES );
442+ is = new ByteArrayInputStream (byteImage );
443+ } catch (WebDriverException wde ) {
444+ markDriverUnresponsive (wde );
445+ throw new IOException ("Exception: AndroidDriver getScreenshotAction failed" , wde );
446+ }
405447
406448 // Highlight the action on the screenshot:
407449 BufferedImage newBi = ImageIO .read (is );
@@ -432,9 +474,14 @@ public static String getScreenshotAction(State state, Action action) throws IOEx
432474 }
433475
434476 public static AWTCanvas getScreenshotBinary (State state ) throws IOException {
435- byte [] byteImage = driver .getScreenshotAs (OutputType .BYTES );
436- InputStream is = new ByteArrayInputStream (byteImage );
437- return AWTCanvas .fromInputStream (is );
477+ try {
478+ byte [] byteImage = driver .getScreenshotAs (OutputType .BYTES );
479+ InputStream is = new ByteArrayInputStream (byteImage );
480+ return AWTCanvas .fromInputStream (is );
481+ } catch (WebDriverException wde ) {
482+ markDriverUnresponsive (wde );
483+ throw new IOException ("Exception: AndroidDriver getScreenshotBinary failed" , wde );
484+ }
438485 }
439486
440487 public static void terminateApp (String bundleId ){
@@ -486,6 +533,98 @@ public static LogEntries getAppiumLogs() {
486533 return driver .manage ().logs ().get ("driver" );
487534 }
488535
536+ /**
537+ * Clear all logcat content.
538+ */
539+ public static void clearLogcat () {
540+ try {
541+ mobileShell ("logcat" , List .of ("-b" , "all" , "-c" ), Duration .ofSeconds (10 ));
542+ } catch (Exception ignored ) {}
543+ }
544+
545+ /**
546+ * Execute an Android shell command on the device via Appium ("mobile: shell").
547+ */
548+ private static void mobileShell (String command , List <String > args , Duration timeout ) {
549+ Map <String , Object > m = new HashMap <>();
550+ m .put ("command" , command );
551+ m .put ("args" , args );
552+ m .put ("timeout" , (int ) timeout .toMillis ());
553+ driver .executeScript ("mobile: shell" , m );
554+ }
555+
556+ /**
557+ * Dump logcat output (main + system + crash buffers).
558+ */
559+ public static String dumpLogcatThreadtimeForPackage (String pkg ) {
560+ if (pkg == null || pkg .isBlank ()) return "" ;
561+
562+ // 1) get PID
563+ String pid = mobileShellStdout ("pidof" , List .of ("-s" , pkg ), Duration .ofSeconds (5 )).trim ();
564+ if (pid .isEmpty ()) return "" ;
565+
566+ // 2) dump logcat for that PID
567+ return mobileShellStdout (
568+ "logcat" ,
569+ List .of ("-d" , "-v" , "threadtime" , "--pid" , pid , "-b" , "main" , "-b" , "system" , "-b" , "crash" ),
570+ Duration .ofSeconds (10 )
571+ );
572+ }
573+
574+ /**
575+ * Execute an Android shell command on the device via Appium ("mobile: shell") and return stdout.
576+ */
577+ private static String mobileShellStdout (String command , List <String > args , Duration timeout ) {
578+ Map <String , Object > m = new HashMap <>();
579+ m .put ("command" , command );
580+ m .put ("args" , args );
581+ m .put ("timeout" , (int ) timeout .toMillis ());
582+
583+ Object raw ;
584+ try {
585+ raw = driver .executeScript ("mobile: shell" , m );
586+ } catch (WebDriverException wde ) {
587+ markDriverUnresponsive (wde );
588+ return "" ;
589+ }
590+ if (raw == null ) return "" ;
591+ if (raw instanceof String ) return (String )raw ;
592+ if (raw instanceof Map <?, ?>) {
593+ Object out = ((Map <?, ?>)raw ).get ("stdout" );
594+ return out == null ? "" : out .toString ();
595+ }
596+ return raw .toString ();
597+ }
598+
599+ public static String getAppPackageFromCapabilitiesOrCurrent () {
600+ if (driver == null ) {
601+ return "" ;
602+ }
603+
604+ try {
605+ Object appPackage = driver .getCapabilities ().getCapability ("appium:appPackage" );
606+ if (appPackage == null ) {
607+ appPackage = driver .getCapabilities ().getCapability ("appPackage" );
608+ }
609+ if (appPackage != null ) {
610+ String pkg = appPackage .toString ().trim ();
611+ if (!pkg .isEmpty ()) {
612+ return pkg ;
613+ }
614+ }
615+ } catch (Exception ignored ) {
616+ }
617+
618+ try {
619+ return driver .getCurrentPackage ();
620+ } catch (WebDriverException wde ) {
621+ markDriverUnresponsive (wde );
622+ return "" ;
623+ } catch (Exception ignored ) {
624+ return "" ;
625+ }
626+ }
627+
489628 @ Override
490629 public void stop () throws SystemStopException {
491630 driver .quit ();
@@ -500,6 +639,10 @@ public boolean isRunning() {
500639 //driver.queryAppState(appId), equalTo(ApplicationState.RUNNING_IN_FOREGROUND)
501640 driver .getCurrentPackage ();
502641 }
642+ catch (WebDriverException wde ) {
643+ markDriverUnresponsive (wde );
644+ return false ;
645+ }
503646 catch (Exception e ) {
504647 return false ;
505648 }
@@ -510,11 +653,21 @@ public boolean isRunning() {
510653 @ Override
511654 public String getStatus () {
512655 //TODO: Check and select proper method to print the status
513- return "Android current package : " + driver .getCurrentPackage ();
656+ try {
657+ return "Android current package : " + driver .getCurrentPackage ();
658+ } catch (WebDriverException wde ) {
659+ markDriverUnresponsive (wde );
660+ return "Android current package : <unavailable>" ;
661+ }
514662 }
515663
516664 public static ApplicationState getStatus (String appId ) {
517- return driver .queryAppState (appId );
665+ try {
666+ return driver .queryAppState (appId );
667+ } catch (WebDriverException wde ) {
668+ markDriverUnresponsive (wde );
669+ return ApplicationState .NOT_RUNNING ;
670+ }
518671 }
519672
520673 @ Override
0 commit comments