Skip to content

Commit fcfe616

Browse files
Add accessibility screenshot
1 parent 1da7ef2 commit fcfe616

2 files changed

Lines changed: 106 additions & 31 deletions

File tree

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

Lines changed: 104 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,17 @@ 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+
}
6976
}
7077

7178
// [The Stack Overflow answer 14923144](https://stackoverflow.com/a/14923144)
@@ -91,10 +98,30 @@ private static void click(int x, int y, int millisecondsDuration) {
9198
}
9299

93100
// 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
101+
private static void dump(TermuxApiReceiver apiReceiver, Intent intent) {
102+
AccessibilityNodeInfo node = TermuxAccessibilityService.instance.getRootInActiveWindow();
103+
// On Signal *App permissions* for instance
104+
if (node == null) {
105+
ResultReturner.returnData(apiReceiver, intent, out -> {});
106+
return;
107+
}
108+
109+
String swString = dumpAuxiliary(node);
110+
111+
ResultReturner.returnData(apiReceiver, intent, out -> {
112+
out.write(swString);
113+
});
114+
}
115+
116+
private static String dumpAuxiliary(AccessibilityNodeInfo node) {
117+
// Create a DocumentBuilder
96118
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
97-
DocumentBuilder builder = factory.newDocumentBuilder();
119+
DocumentBuilder builder = null;
120+
try {
121+
builder = factory.newDocumentBuilder();
122+
} catch (ParserConfigurationException parserConfigurationException) {
123+
Logger.logDebug(LOG_TAG, "ParserConfigurationException");
124+
}
98125

99126
// Create a new Document
100127
Document document = builder.newDocument();
@@ -103,17 +130,16 @@ private static String dump() throws TransformerException, ParserConfigurationExc
103130
Element root = document.createElement("hierarchy");
104131
document.appendChild(root);
105132

106-
AccessibilityNodeInfo node = TermuxAccessibilityService.instance.getRootInActiveWindow();
107-
// On Signal *App permissions* for instance
108-
if (node == null) {
109-
return "";
110-
}
111-
112133
dumpNodeAuxiliary(document, root, node);
113134

114135
// Write as XML
115136
TransformerFactory transformerFactory = TransformerFactory.newInstance();
116-
Transformer transformer = transformerFactory.newTransformer();
137+
Transformer transformer = null;
138+
try {
139+
transformer = transformerFactory.newTransformer();
140+
} catch (TransformerException transformerException) {
141+
Logger.logDebug(LOG_TAG, "TransformerException transformerFactory.newTransformer");
142+
}
117143
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
118144
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
119145
// 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 +148,13 @@ private static String dump() throws TransformerException, ParserConfigurationExc
122148

123149
StringWriter sw = new StringWriter();
124150
StreamResult result = new StreamResult(sw);
125-
transformer.transform(source, result);
126-
127-
return sw.toString();
128-
}
151+
try {
152+
transformer.transform(source, result);
153+
} catch (TransformerException transformerException) {
154+
Logger.logDebug(LOG_TAG, "TransformerException transformer.transform");
155+
}
156+
return sw.toString();
157+
}
129158

130159
private static void dumpNodeAuxiliary(Document document, Element element, AccessibilityNodeInfo node) {
131160
for (int i = 0; i < node.getChildCount(); i++) {
@@ -194,7 +223,52 @@ private static void type(String toType) {
194223
focusedNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
195224
}
196225

197-
private static void performGlobalAction(String globalAction) throws NoSuchFieldException, IllegalAccessException {
198-
TermuxAccessibilityService.instance.performGlobalAction((int)AccessibilityService.class.getDeclaredField("GLOBAL_ACTION_" + globalAction.toUpperCase()).get(null));
226+
private static void performGlobalAction(String globalActionString) {
227+
String fieldName = "GLOBAL_ACTION_" + globalActionString.toUpperCase();
228+
Field field = null;
229+
try {
230+
field = AccessibilityService.class.getDeclaredField(fieldName);
231+
} catch (NoSuchFieldException noSuchFieldException) {
232+
Logger.logDebug(LOG_TAG, "NoSuchFieldException");
233+
}
234+
Object globalActionObject = null;
235+
try {
236+
globalActionObject = field.get(null);
237+
} catch(IllegalAccessException illegalAccessException) {
238+
Logger.logDebug(LOG_TAG, "IllegalAccessException");
239+
}
240+
int globalActionInt = (int)globalActionObject;
241+
TermuxAccessibilityService.instance.performGlobalAction(globalActionInt);
199242
}
243+
244+
private static void screenshot(TermuxApiReceiver apiReceiver, final Context context, Intent intent) {
245+
TermuxAccessibilityService.instance.takeScreenshot(
246+
Display.DEFAULT_DISPLAY,
247+
context.getMainExecutor(),
248+
new TakeScreenshotCallback() {
249+
250+
@Override
251+
public void onSuccess(ScreenshotResult screenshotResult) {
252+
Logger.logDebug(LOG_TAG, "onSuccess");
253+
HardwareBuffer buffer = screenshotResult.getHardwareBuffer();
254+
ColorSpace colorSpace = screenshotResult.getColorSpace();
255+
256+
Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, colorSpace);
257+
258+
ResultReturner.returnData(apiReceiver, intent, new ResultReturner.BinaryOutput()
259+
{
260+
@Override
261+
public void writeResult(OutputStream out) throws IOException {
262+
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
263+
}
264+
});
265+
}
266+
267+
@Override
268+
public void onFailure(int errorCode) {
269+
Logger.logDebug(LOG_TAG, "onFailure: " + errorCode);
270+
}
271+
}
272+
);
273+
}
200274
}

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)