2929
3030import android .graphics .Rect ;
3131
32- import android .content .ContentResolver ;
33-
3432import android .provider .Settings ;
3533
3634import android .view .accessibility .AccessibilityManager ;
4038import android .accessibilityservice .AccessibilityService ;
4139import android .os .Bundle ;
4240
41+ import android .view .Display ;
42+ import android .accessibilityservice .AccessibilityService .TakeScreenshotCallback ;
43+ import android .accessibilityservice .AccessibilityService .ScreenshotResult ;
44+ import android .hardware .HardwareBuffer ;
45+ import android .graphics .ColorSpace ;
46+ import android .graphics .Bitmap ;
47+ import java .io .OutputStream ;
48+ import java .io .IOException ;
49+ import java .lang .reflect .Field ;
50+
4351public class AccessibilityAPI {
4452
4553 private static final String LOG_TAG = "AccessibilityAPI" ;
@@ -54,18 +62,20 @@ public static void onReceive(TermuxApiReceiver apiReceiver, final Context contex
5462 context .startActivity (accessibilityIntent );
5563 }
5664
57- ResultReturner .returnData (apiReceiver , intent , out -> {
58- final ContentResolver contentResolver = context .getContentResolver ();
59- if (intent .hasExtra ("dump" )) {
60- out .print (dump ());
61- } else if (intent .hasExtra ("click" )) {
62- click (intent .getIntExtra ("x" , 0 ), intent .getIntExtra ("y" , 0 ), intent .getIntExtra ("duration" , 1 ));
63- } else if (intent .hasExtra ("type" )) {
64- type (intent .getStringExtra ("type" ));
65- } else if (intent .hasExtra ("global-action" )) {
66- performGlobalAction (intent .getStringExtra ("global-action" ));
67- }
68- });
65+ if (intent .hasExtra ("dump" )) {
66+ dump (apiReceiver , intent );
67+ } else if (intent .hasExtra ("click" )) {
68+ click (intent .getIntExtra ("x" , 0 ), intent .getIntExtra ("y" , 0 ), intent .getIntExtra ("duration" , 1 ));
69+ } else if (intent .hasExtra ("type" )) {
70+ type (intent .getStringExtra ("type" ));
71+ } else if (intent .hasExtra ("global-action" )) {
72+ performGlobalAction (intent .getStringExtra ("global-action" ));
73+ } else if (intent .hasExtra ("screenshot" )) {
74+ screenshot (apiReceiver , context , intent );
75+ }
76+
77+ // Necessary for void functions not to hang.
78+ ResultReturner .returnData (apiReceiver , intent , out -> {});
6979 }
7080
7181 // [The Stack Overflow answer 14923144](https://stackoverflow.com/a/14923144)
@@ -91,10 +101,30 @@ private static void click(int x, int y, int millisecondsDuration) {
91101 }
92102
93103 // The aim of this function is to give a compatible output with `adb` `uiautomator dump`.
94- private static String dump () throws TransformerException , ParserConfigurationException {
95- // Create a DocumentBuilder
104+ private static void dump (TermuxApiReceiver apiReceiver , Intent intent ) {
105+ AccessibilityNodeInfo node = TermuxAccessibilityService .instance .getRootInActiveWindow ();
106+ // On Signal *App permissions* for instance
107+ if (node == null ) {
108+ ResultReturner .returnData (apiReceiver , intent , out -> {});
109+ return ;
110+ }
111+
112+ String swString = dumpAuxiliary (node );
113+
114+ ResultReturner .returnData (apiReceiver , intent , out -> {
115+ out .write (swString );
116+ });
117+ }
118+
119+ private static String dumpAuxiliary (AccessibilityNodeInfo node ) {
120+ // Create a DocumentBuilder
96121 DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance ();
97- DocumentBuilder builder = factory .newDocumentBuilder ();
122+ DocumentBuilder builder = null ;
123+ try {
124+ builder = factory .newDocumentBuilder ();
125+ } catch (ParserConfigurationException parserConfigurationException ) {
126+ Logger .logDebug (LOG_TAG , "ParserConfigurationException" );
127+ }
98128
99129 // Create a new Document
100130 Document document = builder .newDocument ();
@@ -103,17 +133,16 @@ private static String dump() throws TransformerException, ParserConfigurationExc
103133 Element root = document .createElement ("hierarchy" );
104134 document .appendChild (root );
105135
106- AccessibilityNodeInfo node = TermuxAccessibilityService .instance .getRootInActiveWindow ();
107- // On Signal *App permissions* for instance
108- if (node == null ) {
109- return "" ;
110- }
111-
112136 dumpNodeAuxiliary (document , root , node );
113137
114138 // Write as XML
115139 TransformerFactory transformerFactory = TransformerFactory .newInstance ();
116- Transformer transformer = transformerFactory .newTransformer ();
140+ Transformer transformer = null ;
141+ try {
142+ transformer = transformerFactory .newTransformer ();
143+ } catch (TransformerException transformerException ) {
144+ Logger .logDebug (LOG_TAG , "TransformerException transformerFactory.newTransformer" );
145+ }
117146 transformer .setOutputProperty (OutputKeys .INDENT , "yes" );
118147 transformer .setOutputProperty ("{http://xml.apache.org/xslt}indent-amount" , "2" );
119148 // Necessary to not have surrogate pairs for emojis, see [Benjamin_Loison/Voice_assistant/issues/83#issue-3661619](https://codeberg.org/Benjamin_Loison/Voice_assistant/issues/83#issue-3661619)
@@ -122,10 +151,13 @@ private static String dump() throws TransformerException, ParserConfigurationExc
122151
123152 StringWriter sw = new StringWriter ();
124153 StreamResult result = new StreamResult (sw );
125- transformer .transform (source , result );
126-
127- return sw .toString ();
128- }
154+ try {
155+ transformer .transform (source , result );
156+ } catch (TransformerException transformerException ) {
157+ Logger .logDebug (LOG_TAG , "TransformerException transformer.transform" );
158+ }
159+ return sw .toString ();
160+ }
129161
130162 private static void dumpNodeAuxiliary (Document document , Element element , AccessibilityNodeInfo node ) {
131163 for (int i = 0 ; i < node .getChildCount (); i ++) {
@@ -194,7 +226,52 @@ private static void type(String toType) {
194226 focusedNode .performAction (AccessibilityNodeInfo .ACTION_SET_TEXT , arguments );
195227 }
196228
197- private static void performGlobalAction (String globalAction ) throws NoSuchFieldException , IllegalAccessException {
198- TermuxAccessibilityService .instance .performGlobalAction ((int )AccessibilityService .class .getDeclaredField ("GLOBAL_ACTION_" + globalAction .toUpperCase ()).get (null ));
229+ private static void performGlobalAction (String globalActionString ) {
230+ String fieldName = "GLOBAL_ACTION_" + globalActionString .toUpperCase ();
231+ Field field = null ;
232+ try {
233+ field = AccessibilityService .class .getDeclaredField (fieldName );
234+ } catch (NoSuchFieldException noSuchFieldException ) {
235+ Logger .logDebug (LOG_TAG , "NoSuchFieldException" );
236+ }
237+ Object globalActionObject = null ;
238+ try {
239+ globalActionObject = field .get (null );
240+ } catch (IllegalAccessException illegalAccessException ) {
241+ Logger .logDebug (LOG_TAG , "IllegalAccessException" );
242+ }
243+ int globalActionInt = (int )globalActionObject ;
244+ TermuxAccessibilityService .instance .performGlobalAction (globalActionInt );
199245 }
246+
247+ private static void screenshot (TermuxApiReceiver apiReceiver , final Context context , Intent intent ) {
248+ TermuxAccessibilityService .instance .takeScreenshot (
249+ Display .DEFAULT_DISPLAY ,
250+ context .getMainExecutor (),
251+ new TakeScreenshotCallback () {
252+
253+ @ Override
254+ public void onSuccess (ScreenshotResult screenshotResult ) {
255+ Logger .logDebug (LOG_TAG , "onSuccess" );
256+ HardwareBuffer buffer = screenshotResult .getHardwareBuffer ();
257+ ColorSpace colorSpace = screenshotResult .getColorSpace ();
258+
259+ Bitmap bitmap = Bitmap .wrapHardwareBuffer (buffer , colorSpace );
260+
261+ ResultReturner .returnData (apiReceiver , intent , new ResultReturner .BinaryOutput ()
262+ {
263+ @ Override
264+ public void writeResult (OutputStream out ) throws IOException {
265+ bitmap .compress (Bitmap .CompressFormat .PNG , 100 , out );
266+ }
267+ });
268+ }
269+
270+ @ Override
271+ public void onFailure (int errorCode ) {
272+ Logger .logDebug (LOG_TAG , "onFailure: " + errorCode );
273+ }
274+ }
275+ );
276+ }
200277}
0 commit comments