Skip to content

Commit aa45422

Browse files
Add accessibility screenshot
1 parent 1da7ef2 commit aa45422

2 files changed

Lines changed: 109 additions & 31 deletions

File tree

app/src/main/java/com/termux/api/apis/AccessibilityAPI.java

Lines changed: 107 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929

3030
import android.graphics.Rect;
3131

32-
import android.content.ContentResolver;
33-
3432
import android.provider.Settings;
3533

3634
import android.view.accessibility.AccessibilityManager;
@@ -40,6 +38,16 @@
4038
import android.accessibilityservice.AccessibilityService;
4139
import 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+
4351
public 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
}

app/src/main/res/xml/accessibility_service_config.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
xmlns:android="http://schemas.android.com/apk/res/android"
44
android:canRetrieveWindowContent="true"
55
android:accessibilityFlags="flagReportViewIds|flagIncludeNotImportantViews"
6-
android:canPerformGestures="true"/>
6+
android:canPerformGestures="true"
7+
android:canTakeScreenshot="true"/>

0 commit comments

Comments
 (0)