Skip to content

Commit f9bc7ff

Browse files
Prevent random crash due to UI root being null
1 parent c093de4 commit f9bc7ff

1 file changed

Lines changed: 120 additions & 115 deletions

File tree

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

Lines changed: 120 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -47,52 +47,52 @@ public class AccessibilityAPI {
4747
public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) {
4848
Logger.logDebug(LOG_TAG, "onReceive");
4949

50-
boolean isAccessibilityEnabled = isAccessibilityServiceEnabled(context, TermuxAccessibilityService.class);
51-
if (!isAccessibilityEnabled) {
52-
Intent accessibilityIntent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
53-
accessibilityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
54-
context.startActivity(accessibilityIntent);
55-
}
56-
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));
63-
} else if (intent.hasExtra("type")) {
64-
type(intent.getStringExtra("type"));
65-
} else if (intent.hasExtra("global-action")) {
50+
boolean isAccessibilityEnabled = isAccessibilityServiceEnabled(context, TermuxAccessibilityService.class);
51+
if (!isAccessibilityEnabled) {
52+
Intent accessibilityIntent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
53+
accessibilityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
54+
context.startActivity(accessibilityIntent);
55+
}
56+
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));
63+
} else if (intent.hasExtra("type")) {
64+
type(intent.getStringExtra("type"));
65+
} else if (intent.hasExtra("global-action")) {
6666
performGlobalAction(intent.getStringExtra("global-action"));
67-
}
68-
});
69-
}
70-
71-
// [The Stack Overflow answer 14923144](https://stackoverflow.com/a/14923144)
72-
public static boolean isAccessibilityServiceEnabled(Context context, Class<? extends AccessibilityService> service) {
73-
AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
74-
List<AccessibilityServiceInfo> enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
75-
76-
for (AccessibilityServiceInfo enabledService : enabledServices) {
77-
ServiceInfo enabledServiceInfo = enabledService.getResolveInfo().serviceInfo;
78-
if (enabledServiceInfo.packageName.equals(context.getPackageName()) && enabledServiceInfo.name.equals(service.getName()))
79-
return true;
80-
}
81-
82-
return false;
83-
}
84-
85-
private static void click(int x, int y) {
86-
Path swipePath = new Path();
87-
swipePath.moveTo(x, y);
88-
GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
89-
gestureBuilder.addStroke(new GestureDescription.StrokeDescription(swipePath, 0, 1));
90-
TermuxAccessibilityService.instance.dispatchGesture(gestureBuilder.build(), null, null);
91-
}
92-
93-
// 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
67+
}
68+
});
69+
}
70+
71+
// [The Stack Overflow answer 14923144](https://stackoverflow.com/a/14923144)
72+
public static boolean isAccessibilityServiceEnabled(Context context, Class<? extends AccessibilityService> service) {
73+
AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
74+
List<AccessibilityServiceInfo> enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
75+
76+
for (AccessibilityServiceInfo enabledService : enabledServices) {
77+
ServiceInfo enabledServiceInfo = enabledService.getResolveInfo().serviceInfo;
78+
if (enabledServiceInfo.packageName.equals(context.getPackageName()) && enabledServiceInfo.name.equals(service.getName()))
79+
return true;
80+
}
81+
82+
return false;
83+
}
84+
85+
private static void click(int x, int y) {
86+
Path swipePath = new Path();
87+
swipePath.moveTo(x, y);
88+
GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
89+
gestureBuilder.addStroke(new GestureDescription.StrokeDescription(swipePath, 0, 1));
90+
TermuxAccessibilityService.instance.dispatchGesture(gestureBuilder.build(), null, null);
91+
}
92+
93+
// 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
9696
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
9797
DocumentBuilder builder = factory.newDocumentBuilder();
9898

@@ -103,92 +103,97 @@ private static String dump() throws TransformerException, ParserConfigurationExc
103103
Element root = document.createElement("hierarchy");
104104
document.appendChild(root);
105105

106-
AccessibilityNodeInfo node = TermuxAccessibilityService.instance.getRootInActiveWindow();
106+
AccessibilityNodeInfo node = TermuxAccessibilityService.instance.getRootInActiveWindow();
107+
// Randomly faced [Benjamin_Loison/Voice_assistant/issues/84#issue-3661682](https://codeberg.org/Benjamin_Loison/Voice_assistant/issues/84#issue-3661682)
108+
if (node == null) {
109+
return "";
110+
}
107111

108-
dumpNodeAuxiliary(document, root, node);
112+
dumpNodeAuxiliary(document, root, node);
109113

110114
// Write as XML
111115
TransformerFactory transformerFactory = TransformerFactory.newInstance();
112116
Transformer transformer = transformerFactory.newTransformer();
113-
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
114-
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
117+
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
118+
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
115119
DOMSource source = new DOMSource(document);
116120

117-
StringWriter sw = new StringWriter();
121+
StringWriter sw = new StringWriter();
118122
StreamResult result = new StreamResult(sw);
119123
transformer.transform(source, result);
120124

121-
return sw.toString();
122-
}
123-
124-
private static void dumpNodeAuxiliary(Document document, Element element, AccessibilityNodeInfo node) {
125-
for (int i = 0; i < node.getChildCount(); i++) {
126-
AccessibilityNodeInfo nodeChild = node.getChild(i);
127-
// May be faced randomly, see [Benjamin-Loison/android/issues/28#issuecomment-3975714760](https://github.com/Benjamin-Loison/android/issues/28#issuecomment-3975714760)
128-
if (nodeChild == null)
129-
{
130-
continue;
131-
}
132-
Element elementChild = document.createElement("node");
133-
134-
elementChild.setAttribute("index", String.valueOf(i));
135-
136-
elementChild.setAttribute("text", getCharSequenceAsString(nodeChild.getText()));
137-
138-
String nodeChildViewIdResourceName = nodeChild.getViewIdResourceName();
139-
elementChild.setAttribute("resource-id", nodeChildViewIdResourceName != null ? nodeChildViewIdResourceName : "");
140-
141-
elementChild.setAttribute("class", nodeChild.getClassName().toString());
142-
143-
elementChild.setAttribute("package", nodeChild.getPackageName().toString());
144-
145-
elementChild.setAttribute("content-desc", getCharSequenceAsString(nodeChild.getContentDescription()));
146-
147-
elementChild.setAttribute("checkable", String.valueOf(nodeChild.isCheckable()));
148-
149-
elementChild.setAttribute("checked", String.valueOf(nodeChild.isChecked()));
150-
151-
elementChild.setAttribute("clickable", String.valueOf(nodeChild.isClickable()));
152-
153-
elementChild.setAttribute("enabled", String.valueOf(nodeChild.isEnabled()));
154-
155-
elementChild.setAttribute("focusable", String.valueOf(nodeChild.isFocusable()));
156-
157-
elementChild.setAttribute("focused", String.valueOf(nodeChild.isFocused()));
158-
159-
elementChild.setAttribute("scrollable", String.valueOf(nodeChild.isScrollable()));
160-
161-
elementChild.setAttribute("long-clickable", String.valueOf(nodeChild.isLongClickable()));
162-
163-
elementChild.setAttribute("password", String.valueOf(nodeChild.isPassword()));
164-
165-
elementChild.setAttribute("selected", String.valueOf(nodeChild.isSelected()));
166-
167-
Rect nodeChildBounds = new Rect();
168-
nodeChild.getBoundsInScreen(nodeChildBounds);
169-
elementChild.setAttribute("bounds", nodeChildBounds.toShortString());
170-
171-
elementChild.setAttribute("drawing-order", String.valueOf(nodeChild.getDrawingOrder()));
172-
173-
elementChild.setAttribute("hint", getCharSequenceAsString(nodeChild.getHintText()));
174-
175-
element.appendChild(elementChild);
125+
return sw.toString();
126+
}
127+
128+
private static void dumpNodeAuxiliary(Document document, Element element, AccessibilityNodeInfo node) {
129+
for (int i = 0; i < node.getChildCount(); i++) {
130+
AccessibilityNodeInfo nodeChild = node.getChild(i);
131+
// May be faced randomly, see [Benjamin-Loison/android/issues/28#issuecomment-3975714760](https://github.com/Benjamin-Loison/android/issues/28#issuecomment-3975714760)
132+
if (nodeChild == null)
133+
{
134+
continue;
135+
}
136+
Element elementChild = document.createElement("node");
137+
138+
elementChild.setAttribute("index", String.valueOf(i));
139+
140+
elementChild.setAttribute("text", getCharSequenceAsString(nodeChild.getText()));
141+
Logger.logInfo(LOG_TAG, getCharSequenceAsString(nodeChild.getText()));
142+
143+
String nodeChildViewIdResourceName = nodeChild.getViewIdResourceName();
144+
elementChild.setAttribute("resource-id", nodeChildViewIdResourceName != null ? nodeChildViewIdResourceName : "");
145+
146+
elementChild.setAttribute("class", nodeChild.getClassName().toString());
147+
148+
elementChild.setAttribute("package", nodeChild.getPackageName().toString());
149+
150+
elementChild.setAttribute("content-desc", getCharSequenceAsString(nodeChild.getContentDescription()));
151+
152+
elementChild.setAttribute("checkable", String.valueOf(nodeChild.isCheckable()));
153+
154+
elementChild.setAttribute("checked", String.valueOf(nodeChild.isChecked()));
155+
156+
elementChild.setAttribute("clickable", String.valueOf(nodeChild.isClickable()));
157+
158+
elementChild.setAttribute("enabled", String.valueOf(nodeChild.isEnabled()));
159+
160+
elementChild.setAttribute("focusable", String.valueOf(nodeChild.isFocusable()));
161+
162+
elementChild.setAttribute("focused", String.valueOf(nodeChild.isFocused()));
163+
164+
elementChild.setAttribute("scrollable", String.valueOf(nodeChild.isScrollable()));
165+
166+
elementChild.setAttribute("long-clickable", String.valueOf(nodeChild.isLongClickable()));
167+
168+
elementChild.setAttribute("password", String.valueOf(nodeChild.isPassword()));
169+
170+
elementChild.setAttribute("selected", String.valueOf(nodeChild.isSelected()));
171+
172+
Rect nodeChildBounds = new Rect();
173+
nodeChild.getBoundsInScreen(nodeChildBounds);
174+
elementChild.setAttribute("bounds", nodeChildBounds.toShortString());
175+
176+
elementChild.setAttribute("drawing-order", String.valueOf(nodeChild.getDrawingOrder()));
177+
178+
elementChild.setAttribute("hint", getCharSequenceAsString(nodeChild.getHintText()));
179+
180+
element.appendChild(elementChild);
176181
dumpNodeAuxiliary(document, elementChild, nodeChild);
177182
}
178-
}
183+
}
179184

180-
private static String getCharSequenceAsString(CharSequence charSequence) {
181-
return charSequence != null ? charSequence.toString() : "";
182-
}
185+
private static String getCharSequenceAsString(CharSequence charSequence) {
186+
return charSequence != null ? charSequence.toString() : "";
187+
}
183188

184-
private static void type(String toType) {
185-
AccessibilityNodeInfo focusedNode = TermuxAccessibilityService.instance.getRootInActiveWindow().findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
189+
private static void type(String toType) {
190+
AccessibilityNodeInfo focusedNode = TermuxAccessibilityService.instance.getRootInActiveWindow().findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
186191
Bundle arguments = new Bundle();
187192
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, toType);
188193
focusedNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
189-
}
194+
}
190195

191-
private static void performGlobalAction(String globalAction) throws NoSuchFieldException, IllegalAccessException {
192-
TermuxAccessibilityService.instance.performGlobalAction((int)AccessibilityService.class.getDeclaredField("GLOBAL_ACTION_" + globalAction).get(null));
193-
}
196+
private static void performGlobalAction(String globalAction) throws NoSuchFieldException, IllegalAccessException {
197+
TermuxAccessibilityService.instance.performGlobalAction((int)AccessibilityService.class.getDeclaredField("GLOBAL_ACTION_" + globalAction).get(null));
198+
}
194199
}

0 commit comments

Comments
 (0)