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,20 +62,27 @@ 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+ returnEmptyString (apiReceiver , intent );
70+ } else if (intent .hasExtra ("type" )) {
71+ type (intent .getStringExtra ("type" ));
72+ returnEmptyString (apiReceiver , intent );
73+ } else if (intent .hasExtra ("global-action" )) {
74+ performGlobalAction (intent .getStringExtra ("global-action" ));
75+ returnEmptyString (apiReceiver , intent );
76+ } else if (intent .hasExtra ("screenshot" )) {
77+ screenshot (apiReceiver , context , intent );
78+ }
6979 }
7080
81+ // Necessary for void functions not to hang.
82+ private static void returnEmptyString (TermuxApiReceiver apiReceiver , Intent intent ) {
83+ ResultReturner .returnData (apiReceiver , intent , out -> {});
84+ }
85+
7186 // [The Stack Overflow answer 14923144](https://stackoverflow.com/a/14923144)
7287 public static boolean isAccessibilityServiceEnabled (Context context , Class <? extends AccessibilityService > service ) {
7388 AccessibilityManager am = (AccessibilityManager ) context .getSystemService (Context .ACCESSIBILITY_SERVICE );
@@ -91,10 +106,30 @@ private static void click(int x, int y, int millisecondsDuration) {
91106 }
92107
93108 // 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
109+ private static void dump (TermuxApiReceiver apiReceiver , Intent intent ) {
110+ AccessibilityNodeInfo node = TermuxAccessibilityService .instance .getRootInActiveWindow ();
111+ // On Signal *App permissions* for instance
112+ if (node == null ) {
113+ ResultReturner .returnData (apiReceiver , intent , out -> {});
114+ return ;
115+ }
116+
117+ String swString = dumpAuxiliary (node );
118+
119+ ResultReturner .returnData (apiReceiver , intent , out -> {
120+ out .write (swString );
121+ });
122+ }
123+
124+ private static String dumpAuxiliary (AccessibilityNodeInfo node ) {
125+ // Create a DocumentBuilder
96126 DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance ();
97- DocumentBuilder builder = factory .newDocumentBuilder ();
127+ DocumentBuilder builder = null ;
128+ try {
129+ builder = factory .newDocumentBuilder ();
130+ } catch (ParserConfigurationException parserConfigurationException ) {
131+ Logger .logDebug (LOG_TAG , "ParserConfigurationException" );
132+ }
98133
99134 // Create a new Document
100135 Document document = builder .newDocument ();
@@ -103,17 +138,16 @@ private static String dump() throws TransformerException, ParserConfigurationExc
103138 Element root = document .createElement ("hierarchy" );
104139 document .appendChild (root );
105140
106- AccessibilityNodeInfo node = TermuxAccessibilityService .instance .getRootInActiveWindow ();
107- // On Signal *App permissions* for instance
108- if (node == null ) {
109- return "" ;
110- }
111-
112141 dumpNodeAuxiliary (document , root , node );
113142
114143 // Write as XML
115144 TransformerFactory transformerFactory = TransformerFactory .newInstance ();
116- Transformer transformer = transformerFactory .newTransformer ();
145+ Transformer transformer = null ;
146+ try {
147+ transformer = transformerFactory .newTransformer ();
148+ } catch (TransformerException transformerException ) {
149+ Logger .logDebug (LOG_TAG , "TransformerException transformerFactory.newTransformer" );
150+ }
117151 transformer .setOutputProperty (OutputKeys .INDENT , "yes" );
118152 transformer .setOutputProperty ("{http://xml.apache.org/xslt}indent-amount" , "2" );
119153 // 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 +156,13 @@ private static String dump() throws TransformerException, ParserConfigurationExc
122156
123157 StringWriter sw = new StringWriter ();
124158 StreamResult result = new StreamResult (sw );
125- transformer .transform (source , result );
126-
127- return sw .toString ();
128- }
159+ try {
160+ transformer .transform (source , result );
161+ } catch (TransformerException transformerException ) {
162+ Logger .logDebug (LOG_TAG , "TransformerException transformer.transform" );
163+ }
164+ return sw .toString ();
165+ }
129166
130167 private static void dumpNodeAuxiliary (Document document , Element element , AccessibilityNodeInfo node ) {
131168 for (int i = 0 ; i < node .getChildCount (); i ++) {
@@ -194,7 +231,52 @@ private static void type(String toType) {
194231 focusedNode .performAction (AccessibilityNodeInfo .ACTION_SET_TEXT , arguments );
195232 }
196233
197- private static void performGlobalAction (String globalAction ) throws NoSuchFieldException , IllegalAccessException {
198- TermuxAccessibilityService .instance .performGlobalAction ((int )AccessibilityService .class .getDeclaredField ("GLOBAL_ACTION_" + globalAction .toUpperCase ()).get (null ));
234+ private static void performGlobalAction (String globalActionString ) {
235+ String fieldName = "GLOBAL_ACTION_" + globalActionString .toUpperCase ();
236+ Field field = null ;
237+ try {
238+ field = AccessibilityService .class .getDeclaredField (fieldName );
239+ } catch (NoSuchFieldException noSuchFieldException ) {
240+ Logger .logDebug (LOG_TAG , "NoSuchFieldException" );
241+ }
242+ Object globalActionObject = null ;
243+ try {
244+ globalActionObject = field .get (null );
245+ } catch (IllegalAccessException illegalAccessException ) {
246+ Logger .logDebug (LOG_TAG , "IllegalAccessException" );
247+ }
248+ int globalActionInt = (int )globalActionObject ;
249+ TermuxAccessibilityService .instance .performGlobalAction (globalActionInt );
199250 }
251+
252+ private static void screenshot (TermuxApiReceiver apiReceiver , final Context context , Intent intent ) {
253+ TermuxAccessibilityService .instance .takeScreenshot (
254+ Display .DEFAULT_DISPLAY ,
255+ context .getMainExecutor (),
256+ new TakeScreenshotCallback () {
257+
258+ @ Override
259+ public void onSuccess (ScreenshotResult screenshotResult ) {
260+ Logger .logDebug (LOG_TAG , "onSuccess" );
261+ HardwareBuffer buffer = screenshotResult .getHardwareBuffer ();
262+ ColorSpace colorSpace = screenshotResult .getColorSpace ();
263+
264+ Bitmap bitmap = Bitmap .wrapHardwareBuffer (buffer , colorSpace );
265+
266+ ResultReturner .returnData (apiReceiver , intent , new ResultReturner .BinaryOutput ()
267+ {
268+ @ Override
269+ public void writeResult (OutputStream out ) throws IOException {
270+ bitmap .compress (Bitmap .CompressFormat .PNG , 100 , out );
271+ }
272+ });
273+ }
274+
275+ @ Override
276+ public void onFailure (int errorCode ) {
277+ Logger .logDebug (LOG_TAG , "onFailure: " + errorCode );
278+ }
279+ }
280+ );
281+ }
200282}
0 commit comments