Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.GestureDescription;
import android.content.Context;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Build;
import android.provider.Settings;
import android.support.annotation.RequiresApi;
import android.text.TextUtils;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
public class AccessibilityUtils {
private static final String TAG = "AccessibilityUtils";
private static AccessibilityUtils instance;
public static AccessibilityUtils getInstance() {
if (instance == null) {
instance = new AccessibilityUtils();
}
return instance;
}
public String getAccessibilityEventType(int eventTypeCode) {
switch (eventTypeCode) {
case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
return "TYPE_NOTIFICATION_STATE_CHANGED";
case AccessibilityEvent.TYPE_VIEW_CLICKED:
return "TYPE_VIEW_CLICKED";
case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED:
return "TYPE_VIEW_LONG_CLICKED";
case AccessibilityEvent.TYPE_VIEW_SELECTED:
return "TYPE_VIEW_SELECTED";
case AccessibilityEvent.TYPE_VIEW_FOCUSED:
return "TYPE_VIEW_FOCUSED";
case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
return "TYPE_VIEW_TEXT_CHANGED";
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
return "TYPE_WINDOW_STATE_CHANGED";
case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
return "TYPE_VIEW_HOVER_ENTER";
case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT:
return "TYPE_VIEW_HOVER_EXIT";
case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:
return "TYPE_TOUCH_EXPLORATION_GESTURE_START";
case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:
return "TYPE_TOUCH_EXPLORATION_GESTURE_END";
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
return "TYPE_WINDOW_CONTENT_CHANGED";
case AccessibilityEvent.TYPE_VIEW_SCROLLED:
return "TYPE_VIEW_SCROLLED";
case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:
return "TYPE_VIEW_TEXT_SELECTION_CHANGED";
case AccessibilityEvent.TYPE_ANNOUNCEMENT:
return "TYPE_ANNOUNCEMENT";
case AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY:
return "TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY";
case AccessibilityEvent.TYPE_GESTURE_DETECTION_START:
return "TYPE_GESTURE_DETECTION_START";
case AccessibilityEvent.TYPE_GESTURE_DETECTION_END:
return "TYPE_GESTURE_DETECTION_END";
case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START:
return "TYPE_TOUCH_INTERACTION_START";
case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END:
return "TYPE_TOUCH_INTERACTION_END";
case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
return "TYPE_WINDOWS_CHANGED";
case AccessibilityEvent.TYPE_VIEW_CONTEXT_CLICKED:
return "TYPE_VIEW_CONTEXT_CLICKED";
}
return "";
}
public final void swipeCoordinatesAction(AccessibilityService service, int centerX, int centerY, int rectWidth) {
GestureDescription.Builder builder = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
builder = new GestureDescription.Builder();
Path path = new Path();
path.moveTo(((float) centerX) - (((float) rectWidth) / ((float) 2)), (float) centerY);
path.lineTo(((float) centerX) + (((float) rectWidth) / ((float) 2)), (float) centerY);
builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 400));
service.dispatchGesture(builder.build(), new AccessibilityService.GestureResultCallback() {
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onCompleted(GestureDescription gestureDescription) {
Log.e(TAG, "swipeCoordinatesAction Gesture Completed: " + gestureDescription.getStrokeCount());
super.onCompleted(gestureDescription);
}
}, null);
}
}
public final void tapCoordinatesAction(AccessibilityService service, int centerX, int centerY) {
GestureDescription.Builder builder = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
builder = new GestureDescription.Builder();
Path path = new Path();
path.moveTo(((float) centerX), (float) centerY);
builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 50));
service.dispatchGesture(builder.build(), new AccessibilityService.GestureResultCallback() {
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onCompleted(GestureDescription gestureDescription) {
Log.e(TAG, "tapCoordinatesAction Gesture Completed: " + gestureDescription.getStrokeCount());
super.onCompleted(gestureDescription);
}
}, null);
}
}
public final void tapCoordinatesAction(AccessibilityService service, int centerX, int centerY, final OnProcessClickListener listener) {
GestureDescription.Builder builder = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
builder = new GestureDescription.Builder();
Path path = new Path();
path.moveTo(((float) centerX), (float) centerY);
builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 50));
service.dispatchGesture(builder.build(), new AccessibilityService.GestureResultCallback() {
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onCompleted(GestureDescription gestureDescription) {
Log.e(TAG, "tapCoordinatesAction Gesture Completed: " + gestureDescription.getStrokeCount());
super.onCompleted(gestureDescription);
if (listener != null) {
listener.onClickComplete();
}
}
}, null);
}
}
public final void tapCoordinatesAction(AccessibilityService service, AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null) {
return;
}
if (nodeInfo.isClickable()) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
return;
}
GestureDescription.Builder builder = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
builder = new GestureDescription.Builder();
Path path = new Path();
Rect rect = new Rect();
nodeInfo.getBoundsInScreen(rect);
path.moveTo(rect.centerX(), rect.centerY());
builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 50));
service.dispatchGesture(builder.build(), new AccessibilityService.GestureResultCallback() {
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onCompleted(GestureDescription gestureDescription) {
Log.e(TAG, "tapCoordinatesAction Gesture Completed: " + gestureDescription.getStrokeCount());
super.onCompleted(gestureDescription);
}
}, null);
}
}
public final void tapCoordinatesAction(AccessibilityService service, AccessibilityNodeInfo nodeInfo, final OnProcessClickListener listener) {
if (nodeInfo == null) {
return;
}
GestureDescription.Builder builder = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
builder = new GestureDescription.Builder();
Path path = new Path();
Rect rect = new Rect();
nodeInfo.getBoundsInScreen(rect);
path.moveTo(rect.centerX(), rect.centerY());
builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 50));
service.dispatchGesture(builder.build(), new AccessibilityService.GestureResultCallback() {
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onCompleted(GestureDescription gestureDescription) {
Log.e(TAG, "tapCoordinatesAction Gesture Completed: " + gestureDescription.getStrokeCount());
super.onCompleted(gestureDescription);
if (listener != null) {
listener.onClickComplete();
}
}
}, null);
}
}
public void logViewHierarchy(AccessibilityNodeInfo nodeInfo, final int depth, String tag) {
if (nodeInfo == null) return;
String spacerString = "";
for (int i = 0; i < depth; ++i) {
spacerString += "- - ";
}
//Log the info you care about here... I choose classname and view resource name, because they are simple, but interesting.
Log.e(tag, spacerString + nodeInfo.toString());
for (int i = 0; i < nodeInfo.getChildCount(); ++i) {
logViewHierarchy(nodeInfo.getChild(i), depth + 1, tag);
}
}
public ArrayList<String> getLogViewHierarchyIterative(AccessibilityNodeInfo nodeInfo) {
Deque<AccessibilityNodeInfo> stack = new ArrayDeque<AccessibilityNodeInfo>();
final ArrayList<String> nodeList = new ArrayList<>();
stack.push(nodeInfo);
while (!stack.isEmpty()) {
AccessibilityNodeInfo info = stack.pop();
int depth = 0;
for (int i = 0; i < info.getChildCount(); i++) {
AccessibilityNodeInfo childNodeInfo = info.getChild(i);
if (childNodeInfo == null) {
continue;
}
if (childNodeInfo.getChildCount() > 0) {
stack.push(childNodeInfo);
depth++;
}
if (!TextUtils.isEmpty(childNodeInfo.toString())) {
StringBuilder builder = new StringBuilder();
builder.append("[").append(depth).append(", ");
builder.append("Class Name: ").append(childNodeInfo.getClassName()).append(", ");
String resourceId = "null";
if (!TextUtils.isEmpty(childNodeInfo.getViewIdResourceName())) {
resourceId = childNodeInfo.getViewIdResourceName();
}
builder.append("ResourceId: ").append(resourceId).append(", ");
String text = "null";
if (!TextUtils.isEmpty(childNodeInfo.getText())) {
text = childNodeInfo.getText().toString();
}
builder.append("Text: ").append(text).append(", ");
Rect rect = new Rect();
childNodeInfo.getBoundsInScreen(rect);
builder.append("BoundsInScreen: ").append(rect.toString()).append(", ");
childNodeInfo.getBoundsInParent(rect);
builder.append("BoundsInParent: ").append(rect.toString()).append("]");
nodeList.add(builder.toString());
}
}
}
/*for (String node : nodeList) {
Log.e(tag, node);
}*/
return nodeList;
}
public void logViewHierarchyIterative(AccessibilityNodeInfo nodeInfo, String tag) {
Deque<AccessibilityNodeInfo> stack = new ArrayDeque<AccessibilityNodeInfo>();
final List<String> nodeList = new LinkedList<>();
stack.push(nodeInfo);
while (!stack.isEmpty()) {
AccessibilityNodeInfo info = stack.pop();
int depth = 0;
for (int i = 0; i < info.getChildCount(); i++) {
AccessibilityNodeInfo childNodeInfo = info.getChild(i);
if (childNodeInfo == null) {
continue;
}
if (childNodeInfo.getChildCount() > 0) {
stack.push(childNodeInfo);
depth++;
}
if (!TextUtils.isEmpty(childNodeInfo.toString())) {
nodeList.add(childNodeInfo.toString());
}
}
}
for (String node : nodeList) {
Log.e(tag, node);
}
}
public boolean matchAnyTextFromArray(AccessibilityNodeInfo nodeInfo, ArrayList<String> textArray) {
Deque<AccessibilityNodeInfo> stack = new ArrayDeque<AccessibilityNodeInfo>();
stack.push(nodeInfo);
while (!stack.isEmpty()) {
AccessibilityNodeInfo info = stack.pop();
for (int i = 0; i < info.getChildCount(); i++) {
AccessibilityNodeInfo childNodeInfo = info.getChild(i);
if (childNodeInfo == null) {
continue;
}
if (childNodeInfo.getChildCount() > 0) {
stack.push(childNodeInfo);
}
if (!TextUtils.isEmpty(childNodeInfo.getText())) {
if (textArray.contains(childNodeInfo.getText().toString())) {
return true;
}
}
}
}
return false;
}
/**
* Check if Accessibility Service is enabled.
*
* @param mContext
* @return <code>true</code> if Accessibility Service is ON, otherwise <code>false</code>
*/
public boolean isAccessibilitySettingsOn(Context mContext, Class<?> serviceClass) {
int accessibilityEnabled = 0;
final String service = mContext.getPackageName() + "/" + serviceClass.getName();
Log.e(TAG, "isAccessibilitySettingsOn: " + service);
try {
accessibilityEnabled = Settings.Secure.getInt(
mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED);
Log.v(TAG, "accessibilityEnabled = " + accessibilityEnabled);
} catch (Settings.SettingNotFoundException e) {
Log.e(TAG, "Error finding setting, default accessibility to not found: "
+ e.getMessage());
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == 1) {
Log.v(TAG, "***ACCESSIBILIY IS ENABLED*** -----------------");
String settingValue = Settings.Secure.getString(
mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
splitter.setString(settingValue);
while (splitter.hasNext()) {
String accessabilityService = splitter.next();
Log.v(TAG, "-------------- > accessabilityService :: " + accessabilityService);
if (accessabilityService.equalsIgnoreCase(service)) {
Log.v(TAG, "We've found the correct setting - accessibility is switched on!");
return true;
}
}
}
} else {
Log.v(TAG, "***ACCESSIBILIY IS DISABLED***");
}
return false;
}
public AccessibilityNodeInfo getNodeInfoById(AccessibilityNodeInfo nodeInfo, String type, String id) {
if (nodeInfo == null) return null;
List<AccessibilityNodeInfo> nodeInfoList = nodeInfo.findAccessibilityNodeInfosByViewId(id);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
AccessibilityNodeInfo childNodeInfo = nodeInfoList.get(0);
if (childNodeInfo.getClassName().equals(type)) {
return childNodeInfo;
}
}
return null;
}
public AccessibilityNodeInfo getParentNodeFromChild(AccessibilityNodeInfo nodeInfo, String parentId) {
if (nodeInfo != null) {
AccessibilityNodeInfo info = nodeInfo.getParent();
while (info != null) {
if (!TextUtils.isEmpty(info.getViewIdResourceName()) && info.getViewIdResourceName().equals(parentId)) {
return info;
}
info = info.getParent();
}
}
return null;
}
public AccessibilityNodeInfo getNodeInfoById(AccessibilityNodeInfo nodeInfo, String id) {
if (nodeInfo == null) return null;
List<AccessibilityNodeInfo> nodeInfoList = nodeInfo.findAccessibilityNodeInfosByViewId(id);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
return nodeInfoList.get(0);
}
return null;
}
public AccessibilityNodeInfo getNodeInfoByIdNew(AccessibilityNodeInfo nodeInfo, String id) {
while (nodeInfo != null) {
List<AccessibilityNodeInfo> nodeInfoList = nodeInfo.findAccessibilityNodeInfosByViewId(id);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
return nodeInfoList.get(0);
}
nodeInfo = nodeInfo.getParent();
}
return null;
}
public AccessibilityNodeInfo getNodeInfoByIdArray(AccessibilityNodeInfo nodeInfo, ArrayList<String> idArray) {
if (nodeInfo == null) return null;
for (String id : idArray) {
List<AccessibilityNodeInfo> nodeInfoList = nodeInfo.findAccessibilityNodeInfosByViewId(id);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
return nodeInfoList.get(0);
}
}
return null;
}
public AccessibilityNodeInfo findNodeFromTextArray(AccessibilityNodeInfo nodeInfo, ArrayList<String> textArray) {
Deque<AccessibilityNodeInfo> stack = new ArrayDeque<AccessibilityNodeInfo>();
stack.push(nodeInfo);
while (!stack.isEmpty()) {
AccessibilityNodeInfo info = stack.pop();
for (int i = 0; i < info.getChildCount(); i++) {
AccessibilityNodeInfo childNodeInfo = info.getChild(i);
if (childNodeInfo == null) {
continue;
}
if (childNodeInfo.getChildCount() > 0) {
stack.push(childNodeInfo);
}
if (!TextUtils.isEmpty(childNodeInfo.getText())) {
if (textArray.contains(childNodeInfo.getText().toString())) {
return childNodeInfo;
}
}
}
}
return null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment