Change the way of action pefrom(no longer wait for event), add loading jar file support, fix simple action memory leak

This commit is contained in:
hyb1996
2017-04-05 22:35:31 +08:00
parent b762ceb60b
commit 6844eb9841
88 changed files with 1797 additions and 832 deletions

View File

@@ -3,6 +3,7 @@ package com.stardust.automator;
import android.accessibilityservice.AccessibilityService;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityDelegate;
@@ -21,6 +22,24 @@ public class AccessibilityEventCommandHost implements AccessibilityDelegate {
public interface Command {
void execute(AccessibilityService service, AccessibilityEvent event);
boolean isValid();
void setValid(boolean valid);
}
public abstract static class AbstractCommand implements Command {
private boolean mValid;
public synchronized boolean isValid() {
return mValid;
}
public synchronized void setValid(boolean valid) {
mValid = valid;
}
}
private static final String TAG = "CommandHostDelegate";
@@ -31,8 +50,8 @@ public class AccessibilityEventCommandHost implements AccessibilityDelegate {
private final Queue<Command> mCommands = new LinkedList<>();
private Executor mExecutor = Executors.newFixedThreadPool(5);
private int mRunMode = 0;
private Executor mExecutor = Executors.newSingleThreadExecutor();
private int mRunMode = RUN_MODE_THREAD_POOL;
@Override
public boolean onAccessibilityEvent(final AccessibilityService service, final AccessibilityEvent event) {
@@ -42,20 +61,24 @@ public class AccessibilityEventCommandHost implements AccessibilityDelegate {
}
while (!mCommands.isEmpty()) {
final Command command = mCommands.poll();
executeCommand(command, service, event);
if (command.isValid()) {
executeCommand(command, service, event);
}
}
}
return false;
}
private void executeCommand(final Command command, final AccessibilityService service, final AccessibilityEvent event) {
Runnable r = new Runnable() {
final Runnable r = new Runnable() {
@Override
public void run() {
if (!command.isValid()) {
return;
}
Log.v(TAG, "executing " + command);
command.execute(service, event);
synchronized (command) {
Log.v(TAG, "notify " + mCommands.size() + " commands");
command.notify();
}
}
@@ -78,6 +101,7 @@ public class AccessibilityEventCommandHost implements AccessibilityDelegate {
try {
command.wait();
} catch (InterruptedException e) {
command.setValid(false);
throw new RuntimeException(e);
}
}

View File

@@ -1,12 +0,0 @@
package com.stardust.automator;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/3/9.
*/
public class GC {
}

View File

@@ -1,15 +0,0 @@
package com.stardust.automator;
/**
* Created by Stardust on 2017/3/9.
*/
public class UiBridge {
public UiBridge() {
}
}

View File

@@ -13,6 +13,7 @@ import com.stardust.automator.filter.TextFilter;
import com.stardust.automator.filter.ClassNameFilter;
import com.stardust.automator.filter.DescFilter;
import com.stardust.automator.filter.ListFilter;
import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -360,6 +361,15 @@ public class UiGlobalSelector {
return UiObjectCollection.of(list);
}
public UiObjectCollection findOf(AccessibilityNodeInfoAllocator allocator, AccessibilityNodeInfo node) {
List<AccessibilityNodeInfo> list = new ArrayList<>();
list.add(node);
for (ListFilter filter : mFilters) {
list = filter.filter(list);
}
return UiObjectCollection.of(list);
}
public UiObject findOneOf(AccessibilityNodeInfo node) {
// TODO: 2017/3/9 优化
return new UiObject(findOf(node).get(0).getInfo());

View File

@@ -1,10 +1,15 @@
package com.stardust.automator;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator;
import com.stardust.view.accessibility.AccessibilityNodeInfoHelper;
import java.util.ArrayList;
import java.util.List;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CONTEXT_CLICK;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_DOWN;
@@ -21,10 +26,17 @@ import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.
public class UiObject extends AccessibilityNodeInfoCompat {
private AccessibilityNodeInfoAllocator mAllocator = null;
public UiObject(Object info) {
super(info);
}
public UiObject(Object info, AccessibilityNodeInfoAllocator allocator) {
super(info);
mAllocator = allocator;
}
public UiObject parent() {
return new UiObject(getParent().getInfo());
}
@@ -49,6 +61,22 @@ public class UiObject extends AccessibilityNodeInfoCompat {
return UiObjectCollection.ofCompat(list);
}
public int childCount() {
return getChildCount();
}
public Rect bounds() {
return AccessibilityNodeInfoHelper.getBoundsInScreen(this);
}
public Rect boundsInParent() {
return AccessibilityNodeInfoHelper.getBoundsInParent(this);
}
public int drawingOrder() {
return getDrawingOrder();
}
public String id() {
return getViewIdResourceName();
}
@@ -188,4 +216,33 @@ public class UiObject extends AccessibilityNodeInfoCompat {
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_COLUMN_INT, column));
}
@Override
public AccessibilityNodeInfoCompat getChild(int index) {
if (mAllocator == null)
return super.getChild(index);
return mAllocator.getChild(this, index);
}
@Override
public AccessibilityNodeInfoCompat getParent() {
if (mAllocator == null)
return super.getParent();
return mAllocator.getParent(this);
}
@Override
public List<AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByText(String text) {
if (mAllocator == null)
return super.findAccessibilityNodeInfosByText(text);
return mAllocator.findAccessibilityNodeInfosByText(this, text);
}
@Override
public List<AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByViewId(String viewId) {
if (mAllocator == null)
return super.findAccessibilityNodeInfosByViewId(viewId);
return mAllocator.findAccessibilityNodeInfosByViewId(this, viewId);
}
}

View File

@@ -2,6 +2,8 @@ package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator;
import java.util.ArrayList;
import java.util.List;
@@ -34,7 +36,7 @@ public abstract class DfsFilter implements ListFilter, Filter {
private void filterChildren(AccessibilityNodeInfo parent, List<AccessibilityNodeInfo> list) {
for (int i = 0; i < parent.getChildCount(); i++) {
AccessibilityNodeInfo child = parent.getChild(i);
AccessibilityNodeInfo child = AccessibilityNodeInfoAllocator.getGlobal().getChild(parent, i);
if (child == null)
continue;
boolean included = isIncluded(child);

View File

@@ -0,0 +1,49 @@
package com.stardust.automator.simple_action;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.util.SparseArrayEntries;
/**
* Created by Stardust on 2017/1/27.
*/
public interface Able {
SparseArray<Able> ABLE_MAP = new SparseArrayEntries<Able>()
.entry(AccessibilityNodeInfo.ACTION_CLICK, new Able() {
@Override
public boolean isAble(AccessibilityNodeInfo nodeInfo) {
return nodeInfo.isClickable();
}
})
.entry(AccessibilityNodeInfo.ACTION_LONG_CLICK, new Able() {
@Override
public boolean isAble(AccessibilityNodeInfo nodeInfo) {
return nodeInfo.isLongClickable();
}
})
.entry(AccessibilityNodeInfo.ACTION_FOCUS, new Able() {
@Override
public boolean isAble(AccessibilityNodeInfo nodeInfo) {
return nodeInfo.isFocusable();
}
})
.entry(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, new Able() {
@Override
public boolean isAble(AccessibilityNodeInfo nodeInfo) {
return nodeInfo.isScrollable();
}
})
.entry(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, new Able() {
@Override
public boolean isAble(AccessibilityNodeInfo nodeInfo) {
return nodeInfo.isScrollable();
}
})
.sparseArray();
boolean isAble(AccessibilityNodeInfo node);
}

View File

@@ -0,0 +1,67 @@
package com.stardust.automator.simple_action;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.util.MapEntries;
import java.util.Map;
/**
* Created by Stardust on 2017/1/27.
*/
public class ActionFactory {
private static Map<Integer, Object> searchUpAction = new MapEntries<Integer, Object>()
.entry(AccessibilityNodeInfo.ACTION_CLICK, null)
.entry(AccessibilityNodeInfo.ACTION_LONG_CLICK, null)
.entry(AccessibilityNodeInfo.ACTION_SELECT, null)
.entry(AccessibilityNodeInfo.ACTION_FOCUS, null)
.entry(AccessibilityNodeInfo.ACTION_SELECT, null)
.entry(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, null)
.entry(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, null)
.map();
public static SimpleAction createActionWithTextFilter(int action, String text, int index) {
if (searchUpAction.containsKey(action))
return new SearchUpTargetAction(action, new FilterAction.TextFilter(text, index));
else
return new DepthFirstSearchTargetAction(action, new FilterAction.TextFilter(text, index));
}
public static SimpleAction createActionWithBoundsFilter(int action, Rect rect) {
if (searchUpAction.containsKey(action))
return new SearchUpTargetAction(action, new FilterAction.BoundsFilter(rect));
else
return new DepthFirstSearchTargetAction(action, new FilterAction.BoundsFilter(rect));
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static SimpleAction createActionWithEditableFilter(int action, int index, final String text) {
return new SearchTargetAction(action, new FilterAction.EditableFilter(index)) {
@Override
protected void performAction(AccessibilityNodeInfo node) {
Bundle args = new Bundle();
args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
node.performAction(getAction(), args);
}
};
}
public static SimpleAction createScrollMaxAction(int action) {
return new ScrollMaxAction(action);
}
public static SimpleAction createScrollAction(int action, int i) {
return new ScrollAction(action, i);
}
public static SimpleAction createActionWithIdFilter(int action, String id) {
return new FilterAction.SimpleFilterAction(action, new FilterAction.IdFilter(id));
}
}

View File

@@ -0,0 +1,71 @@
package com.stardust.automator.simple_action;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.RequiresApi;
/**
* Created by Stardust on 2017/1/27.
*/
public interface ActionTarget {
SimpleAction createAction(int action, Object... params);
class TextActionTarget implements ActionTarget {
String mText;
int mIndex;
public TextActionTarget(String text, int i) {
mText = text;
mIndex = i;
}
@Override
public SimpleAction createAction(int action, Object... params) {
return ActionFactory.createActionWithTextFilter(action, mText, mIndex);
}
}
class BoundsActionTarget implements ActionTarget {
Rect mBoundsInRect;
public BoundsActionTarget(Rect rect) {
mBoundsInRect = rect;
}
@Override
public SimpleAction createAction(int action, Object... params) {
return ActionFactory.createActionWithBoundsFilter(action, mBoundsInRect);
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
class EditableActionTarget implements ActionTarget {
private int mIndex;
public EditableActionTarget(int index) {
mIndex = index;
}
@Override
public SimpleAction createAction(int action, Object... params) {
return ActionFactory.createActionWithEditableFilter(action, mIndex, params[0].toString());
}
}
class IdActionTarget implements ActionTarget {
private String mId;
public IdActionTarget(String id) {
mId = id;
}
@Override
public SimpleAction createAction(int action, Object... params) {
return ActionFactory.createActionWithIdFilter(action, mId);
}
}
}

View File

@@ -0,0 +1,43 @@
package com.stardust.automator.simple_action;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator;
/**
* Created by Stardust on 2017/1/27.
*/
public class DepthFirstSearchTargetAction extends SearchTargetAction {
private Able mAble;
private static final long TIME_LIMIT = 500;
public DepthFirstSearchTargetAction(int action, Filter filter) {
super(action, filter);
mAble = Able.ABLE_MAP.get(action);
}
@Override
public AccessibilityNodeInfo searchTarget(AccessibilityNodeInfo n) {
if (n == null)
return null;
if (mAble.isAble(n))
return n;
for (int i = 0; i < n.getChildCount(); i++) {
AccessibilityNodeInfo child = AccessibilityNodeInfoAllocator.getGlobal().getChild(n, i);
if (child == null)
continue;
AccessibilityNodeInfo node = searchTarget(child);
if (node != null)
return node;
else
child.recycle();
}
return null;
}
}

View File

@@ -0,0 +1,203 @@
package com.stardust.automator.simple_action;
import android.graphics.Rect;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* Created by Stardust on 2017/1/27.
*/
public abstract class FilterAction extends SimpleAction {
public interface Filter {
List<AccessibilityNodeInfo> filter(AccessibilityNodeInfo root);
}
public static class TextFilter implements Filter {
String mText;
int mIndex;
public TextFilter(String text, int index) {
mText = text;
mIndex = index;
}
@Override
public List<AccessibilityNodeInfo> filter(AccessibilityNodeInfo root) {
List<AccessibilityNodeInfo> list = AccessibilityNodeInfoAllocator.getGlobal().findAccessibilityNodeInfosByText(root, mText);
if (mIndex == -1)
return list;
if (mIndex >= list.size())
return Collections.EMPTY_LIST;
return Collections.singletonList(list.get(mIndex));
}
@Override
public String toString() {
return "TextFilter{" +
"mText='" + mText + '\'' +
", mIndex=" + mIndex +
'}';
}
}
public static class BoundsFilter implements Filter {
Rect mBoundsInScreen;
public BoundsFilter(Rect boundsInScreen) {
mBoundsInScreen = boundsInScreen;
}
@Override
public List<AccessibilityNodeInfo> filter(AccessibilityNodeInfo root) {
return Collections.singletonList(findAccessibilityNodeInfosByBounds(root));
}
private AccessibilityNodeInfo findAccessibilityNodeInfosByBounds(AccessibilityNodeInfo root) {
if (root == null)
return null;
Rect rect = new Rect();
root.getBoundsInScreen(rect);
if (rect.equals(mBoundsInScreen)) {
return root;
}
for (int i = 0; i < root.getChildCount(); i++) {
AccessibilityNodeInfo child = AccessibilityNodeInfoAllocator.getGlobal().getChild(root, i);
if (child == null)
continue;
AccessibilityNodeInfo nodeInfo = findAccessibilityNodeInfosByBounds(child);
if (nodeInfo != null)
return nodeInfo;
else
child.recycle();
}
return null;
}
@Override
public String toString() {
return "BoundsFilter{" +
"mBoundsInScreen=" + mBoundsInScreen +
'}';
}
}
public static class EditableFilter implements Filter {
private int mIndex;
public EditableFilter(int index) {
mIndex = index;
}
@Override
public List<AccessibilityNodeInfo> filter(AccessibilityNodeInfo root) {
List<AccessibilityNodeInfo> editableList = findEditable(root);
if (mIndex == -1)
return editableList;
if (mIndex >= editableList.size())
return Collections.EMPTY_LIST;
return Collections.singletonList(editableList.get(mIndex));
}
public static List<AccessibilityNodeInfo> findEditable(AccessibilityNodeInfo root) {
if (root == null) {
return Collections.emptyList();
}
if (root.isEditable()) {
return Collections.singletonList(root);
}
List<AccessibilityNodeInfo> list = new LinkedList<>();
for (int i = 0; i < root.getChildCount(); i++) {
list.addAll(findEditable(AccessibilityNodeInfoAllocator.getGlobal().getChild(root, i)));
}
return list;
}
@Override
public String toString() {
return "EditableFilter{" +
"mIndex=" + mIndex +
'}';
}
}
public static class IdFilter implements Filter {
private final String mId;
public IdFilter(String id) {
this.mId = id;
}
@Override
public List<AccessibilityNodeInfo> filter(AccessibilityNodeInfo root) {
return AccessibilityNodeInfoAllocator.getGlobal().findAccessibilityNodeInfosByViewId(root, mId);
}
@Override
public String toString() {
return "IdFilter{" +
"mId='" + mId + '\'' +
'}';
}
}
private Filter mFilter;
public FilterAction(Filter filter) {
mFilter = filter;
}
public boolean perform(AccessibilityNodeInfo root) {
if (root == null)
return false;
List<AccessibilityNodeInfo> list = mFilter.filter(root);
boolean succeed = perform(list);
AccessibilityNodeInfoAllocator.recycleList(root, list);
return succeed;
}
public abstract boolean perform(List<AccessibilityNodeInfo> nodes);
public static class SimpleFilterAction extends FilterAction {
private int mAction;
public SimpleFilterAction(int action, Filter filter) {
super(filter);
mAction = action;
}
@Override
public boolean perform(List<AccessibilityNodeInfo> nodes) {
if (nodes == null || nodes.isEmpty())
return false;
boolean succeed = true;
for (AccessibilityNodeInfo nodeInfo : nodes) {
if (!nodeInfo.performAction(mAction)) {
succeed = false;
}
}
return succeed;
}
}
@Override
public String toString() {
return "FilterAction{" +
"mFilter=" + mFilter +
'}';
}
}

View File

@@ -0,0 +1,44 @@
package com.stardust.automator.simple_action;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/2/14.
*/
public class GetTextAction extends SimpleAction {
@Override
public boolean perform(AccessibilityNodeInfo root) {
List<String> texts = new ArrayList<>();
getText(root, texts);
super.setResult(texts);
return true;
}
private void getText(AccessibilityNodeInfo nodeInfo, List<String> texts) {
CharSequence text = nodeInfo.getText();
if (text != null && text.length() != 0) {
texts.add(text.toString());
}
for (int i = 0; i < nodeInfo.getChildCount(); i++) {
AccessibilityNodeInfo child = AccessibilityNodeInfoAllocator.getGlobal().getChild(nodeInfo, i);
if (child != null) {
getText(child, texts);
child.recycle();
}
}
}
@Override
public void setResult(Object result) {
if (result instanceof List)
super.setResult(result);
}
}

View File

@@ -0,0 +1,59 @@
package com.stardust.automator.simple_action;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/2/12.
*/
public class ScrollAction extends SimpleAction {
private int mIndex, mAction;
public ScrollAction(int action, int i) {
mAction = action;
mIndex = i;
}
@Override
public boolean perform(AccessibilityNodeInfo root) {
List<AccessibilityNodeInfo> scrollableNodes = findScrollableNodes(root);
boolean result = mIndex < scrollableNodes.size() && scrollableNodes.get(mIndex).performAction(mAction);
recycle(scrollableNodes, root);
return result;
}
private void recycle(List<AccessibilityNodeInfo> list, AccessibilityNodeInfo root) {
for (AccessibilityNodeInfo nodeInfo : list) {
if (nodeInfo != root)
nodeInfo.recycle();
}
}
private List<AccessibilityNodeInfo> findScrollableNodes(AccessibilityNodeInfo root) {
List<AccessibilityNodeInfo> list = new ArrayList<>();
findScrollableNodes(root, list);
return list;
}
private static boolean findScrollableNodes(AccessibilityNodeInfo node, List<AccessibilityNodeInfo> list) {
if (node == null) {
return false;
}
if (node.isScrollable()) {
list.add(node);
}
for (int i = 0; i < node.getChildCount(); i++) {
AccessibilityNodeInfo child = AccessibilityNodeInfoAllocator.getGlobal().getChild(node, i);
if (child == null)
continue;
if (!findScrollableNodes(child, list))
child.recycle();
}
return node.isScrollable();
}
}

View File

@@ -0,0 +1,72 @@
package com.stardust.automator.simple_action;
import android.graphics.Rect;
import android.util.Log;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator;
/**
* Created by Stardust on 2017/1/27.
*/
public class ScrollMaxAction extends SimpleAction {
private static final String TAG = ScrollMaxAction.class.getSimpleName();
private int mScrollAction;
private AccessibilityNodeInfo mMaxScrollableNode;
private AccessibilityNodeInfo mRootNode;
public ScrollMaxAction(int scrollAction) {
mScrollAction = scrollAction;
}
@Override
public boolean perform(AccessibilityNodeInfo rootNodeInfo) {
reset();
mRootNode = rootNodeInfo;
findMaxScrollableNodeInfo(rootNodeInfo);
boolean result = mMaxScrollableNode != null && mMaxScrollableNode.performAction(mScrollAction);
reset();
return result;
}
private void reset() {
if (mMaxScrollableNode != null && mMaxScrollableNode != mRootNode) {
mMaxScrollableNode.recycle();
}
mMaxScrollableNode = mRootNode = null;
}
private void findMaxScrollableNodeInfo(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null)
return;
if (nodeInfo.isScrollable()) {
if (mMaxScrollableNode == null) {
mMaxScrollableNode = nodeInfo;
} else if (getAreaInScreen(mMaxScrollableNode) < getAreaInScreen(nodeInfo)) {
if (mMaxScrollableNode != mRootNode)
mMaxScrollableNode.recycle();
mMaxScrollableNode = nodeInfo;
}
}
for (int i = 0; i < nodeInfo.getChildCount(); i++) {
AccessibilityNodeInfo child = AccessibilityNodeInfoAllocator.getGlobal().getChild(nodeInfo, i);
if (child != null) {
findMaxScrollableNodeInfo(child);
if (mMaxScrollableNode != child) {
child.recycle();
}
}
}
}
private long getAreaInScreen(AccessibilityNodeInfo nodeInfo) {
Rect rect = new Rect();
nodeInfo.getBoundsInScreen(rect);
long area = ((long) rect.width()) * rect.height();
Log.v(TAG, "area=" + area);
return area;
}
}

View File

@@ -0,0 +1,54 @@
package com.stardust.automator.simple_action;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.List;
/**
* Created by Stardust on 2017/1/27.
*/
public abstract class SearchTargetAction extends FilterAction {
private int mAction;
public SearchTargetAction(int action, Filter filter) {
super(filter);
mAction = action;
}
@Override
public boolean perform(List<AccessibilityNodeInfo> nodes) {
boolean performed = false;
for (AccessibilityNodeInfo node : nodes) {
node = searchTarget(node);
if (node != null) {
performAction(node);
performed = true;
node.recycle();
}
}
return performed;
}
protected void performAction(AccessibilityNodeInfo node) {
node.performAction(mAction);
}
public int getAction() {
return mAction;
}
public AccessibilityNodeInfo searchTarget(AccessibilityNodeInfo node) {
return node;
}
@Override
public String toString() {
return "SearchTargetAction{" +
"mAction=" + mAction + ", " +
super.toString() +
"}";
}
}

View File

@@ -0,0 +1,50 @@
package com.stardust.automator.simple_action;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/1/27.
*/
public class SearchUpTargetAction extends SearchTargetAction {
private static final String TAG = SearchUpTargetAction.class.getSimpleName();
private static final int LOOP_MAX = 20;
private Able mAble;
public SearchUpTargetAction(int action, Filter filter) {
super(action, filter);
mAble = Able.ABLE_MAP.get(action);
}
@Override
public AccessibilityNodeInfo searchTarget(AccessibilityNodeInfo n) {
AccessibilityNodeInfo node = n;
int i = 0;
List<AccessibilityNodeInfo> list = new ArrayList<>();
while (node != null && !mAble.isAble(node)) {
i++;
if (i > LOOP_MAX) {
node.recycle();
return null;
}
AccessibilityNodeInfo parent = AccessibilityNodeInfoAllocator.getGlobal().getParent(node);
list.add(node);
node = parent;
}
return node;
}
@Override
public String toString() {
return "SearchUpTargetAction{" +
"mAble=" + mAble + ", " +
super.toString() +
'}';
}
}

View File

@@ -0,0 +1,31 @@
package com.stardust.automator.simple_action;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/1/27.
*/
public abstract class SimpleAction {
private boolean mValid = true;
private Object mResult = false;
public abstract boolean perform(AccessibilityNodeInfo root);
public synchronized Object getResult() {
return mResult;
}
public synchronized void setResult(Object result) {
mResult = result;
}
public synchronized void setValid(boolean valid) {
mValid = valid;
}
public synchronized boolean isValid() {
return mValid;
}
}

View File

@@ -0,0 +1,90 @@
package com.stardust.automator.simple_action;
import android.accessibilityservice.AccessibilityService;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator;
import com.stardust.view.accessibility.AccessibilityDelegate;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* Created by Stardust on 2017/1/21.
*/
public class SimpleActionPerformHost implements AccessibilityDelegate {
private static final String TAG = "ActionPerformDelegate";
private BlockingQueue<SimpleAction> mSimpleAction = new ArrayBlockingQueue<>(1);
private Executor mExecutor = Executors.newSingleThreadExecutor();
public void addToActionQueueAndWait(SimpleAction simpleAction) throws InterruptedException {
mSimpleAction.put(simpleAction);
synchronized (simpleAction) {
simpleAction.wait();
}
}
@Override
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
SimpleAction action = mSimpleAction.poll();
if (action == null)
return false;
AccessibilityNodeInfo root = service.getRootInActiveWindow();
if (root == null) {
return false;
}
performAction(root, action);
return false;
}
private void performAction(AccessibilityNodeInfo root, SimpleAction simpleAction) {
mExecutor.execute(new SimpleActionPerformRunnable(root, simpleAction));
}
private synchronized void onActionPerformed(SimpleAction simpleAction) {
AccessibilityNodeInfoAllocator.getGlobal().recycleAll();
synchronized (simpleAction) {
simpleAction.notify();
}
}
private class SimpleActionPerformRunnable implements Runnable {
private final SimpleAction mSimpleAction;
private final AccessibilityNodeInfo mRoot;
SimpleActionPerformRunnable(AccessibilityNodeInfo root, SimpleAction simpleAction) {
mRoot = root;
mSimpleAction = simpleAction;
}
@Override
public void run() {
if (!mSimpleAction.isValid()) {
return;
}
Log.i(TAG, "perform simpleAction: " + mSimpleAction);
if (mSimpleAction.perform(mRoot)) {
mSimpleAction.setResult(true);
onActionPerformed(mSimpleAction);
}
}
}
}

View File

@@ -1,37 +0,0 @@
package com.stardust.view.accessibility;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.List;
import java.util.Vector;
/**
* Created by Stardust on 2017/3/22.
*/
public class AccessibilityAllocator {
private List<AccessibilityNodeInfo> mAccessibilityNodeInfoList = new Vector<>();
public AccessibilityNodeInfo getChild(AccessibilityNodeInfo parent, int i) {
return add(parent.getChild(i));
}
private AccessibilityNodeInfo add(AccessibilityNodeInfo nodeInfo) {
mAccessibilityNodeInfoList.add(nodeInfo);
return nodeInfo;
}
public int recycleAll() {
int recycled = 0;
for (AccessibilityNodeInfo nodeInfo : mAccessibilityNodeInfoList) {
try {
nodeInfo.recycle();
recycled++;
} catch (IllegalStateException ignored) {
}
}
return recycled;
}
}

View File

@@ -2,6 +2,7 @@ package com.stardust.view.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/2/14.

View File

@@ -6,6 +6,7 @@ import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityDelegate;

View File

@@ -0,0 +1,196 @@
package com.stardust.view.accessibility;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.util.Log;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.automator.BuildConfig;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by Stardust on 2017/3/22.
*/
public class AccessibilityNodeInfoAllocator {
public static final AccessibilityNodeInfoAllocator NONE = new NoOpAllocator();
private static final String TAG = "AccessibilityAllocator";
private static final boolean DEBUG = BuildConfig.DEBUG;
private static final AccessibilityNodeInfoAllocator GLOBAL = new AccessibilityNodeInfoAllocator();
public static AccessibilityNodeInfoAllocator getGlobal() {
return GLOBAL;
}
private Map<AccessibilityNodeInfo, String> mAccessibilityNodeInfoList = new HashMap<>();
public AccessibilityNodeInfo getChild(AccessibilityNodeInfo parent, int i) {
return add(parent.getChild(i));
}
public AccessibilityNodeInfoCompat getChild(AccessibilityNodeInfoCompat parent, int i) {
AccessibilityNodeInfoCompat compat = parent.getChild(i);
add((AccessibilityNodeInfo) compat.getInfo());
return compat;
}
public AccessibilityNodeInfo getParent(AccessibilityNodeInfo n) {
return add(n.getParent());
}
public AccessibilityNodeInfoCompat getParent(AccessibilityNodeInfoCompat n) {
AccessibilityNodeInfoCompat compat = n.getParent();
add((AccessibilityNodeInfo) compat.getInfo());
return compat;
}
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(AccessibilityNodeInfo root, String text) {
List<AccessibilityNodeInfo> list = root.findAccessibilityNodeInfosByText(text);
addAll(list);
return list;
}
public List<AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByText(AccessibilityNodeInfoCompat root, String text) {
List<AccessibilityNodeInfoCompat> list = root.findAccessibilityNodeInfosByText(text);
addAll(list);
return list;
}
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(AccessibilityNodeInfo root, String id) {
List<AccessibilityNodeInfo> list = root.findAccessibilityNodeInfosByViewId(id);
addAll(list);
return list;
}
public List<AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByViewId(AccessibilityNodeInfoCompat root, String id) {
List<AccessibilityNodeInfoCompat> list = root.findAccessibilityNodeInfosByViewId(id);
addAll(list);
return list;
}
public void recycle(AccessibilityNodeInfo nodeInfo) {
nodeInfo.recycle();
mAccessibilityNodeInfoList.remove(nodeInfo);
}
public void recycle(AccessibilityNodeInfoCompat nodeInfo) {
recycle((AccessibilityNodeInfo) nodeInfo.getInfo());
}
public int recycleAll() {
int notRecycledCount = 0;
int size = mAccessibilityNodeInfoList.size();
for (Map.Entry<AccessibilityNodeInfo, String> pair : mAccessibilityNodeInfoList.entrySet()) {
try {
pair.getKey().recycle();
notRecycledCount++;
if (DEBUG)
Log.w(TAG, pair.getValue());
} catch (IllegalStateException ignored) {
}
}
Log.i(TAG, "Total: " + size + " Not recycled: " + notRecycledCount);
return notRecycledCount;
}
private AccessibilityNodeInfo add(AccessibilityNodeInfo nodeInfo) {
String stackTrace = DEBUG ? Arrays.toString(Thread.currentThread().getStackTrace()) : null;
mAccessibilityNodeInfoList.put(nodeInfo, stackTrace);
return nodeInfo;
}
private void addAll(Collection<?> nodeInfos) {
String stackTrace = DEBUG ? Arrays.toString(Thread.currentThread().getStackTrace()) : null;
for (Object nodeInfo : nodeInfos) {
if (nodeInfo instanceof AccessibilityNodeInfo) {
mAccessibilityNodeInfoList.put((AccessibilityNodeInfo) nodeInfo, stackTrace);
} else if (nodeInfo instanceof AccessibilityNodeInfoCompat) {
mAccessibilityNodeInfoList.put((AccessibilityNodeInfo) ((AccessibilityNodeInfoCompat) nodeInfo).getInfo(), stackTrace);
} else {
throw new IllegalArgumentException("nodeInfo: " + nodeInfo + " nodeInfos:" + nodeInfos);
}
}
}
public static void recycleList(AccessibilityNodeInfo root, List<AccessibilityNodeInfo> list) {
for (AccessibilityNodeInfo nodeInfo : list) {
if (nodeInfo != root) {
nodeInfo.recycle();
}
}
}
private static class NoOpAllocator extends AccessibilityNodeInfoAllocator {
@Override
public AccessibilityNodeInfoCompat getParent(AccessibilityNodeInfoCompat n) {
return n.getParent();
}
@Override
public AccessibilityNodeInfo getParent(AccessibilityNodeInfo n) {
return n.getParent();
}
@Override
public AccessibilityNodeInfoCompat getChild(AccessibilityNodeInfoCompat parent, int i) {
return parent.getChild(i);
}
@Override
public AccessibilityNodeInfo getChild(AccessibilityNodeInfo parent, int i) {
return parent.getChild(i);
}
@Override
public List<AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByViewId(AccessibilityNodeInfoCompat root, String id) {
return root.findAccessibilityNodeInfosByViewId(id);
}
@Override
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(AccessibilityNodeInfo root, String id) {
return root.findAccessibilityNodeInfosByViewId(id);
}
@Override
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(AccessibilityNodeInfo root, String text) {
return root.findAccessibilityNodeInfosByText(text);
}
@Override
public List<AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByText(AccessibilityNodeInfoCompat root, String text) {
return root.findAccessibilityNodeInfosByText(text);
}
@Override
public void recycle(AccessibilityNodeInfo nodeInfo) {
super.recycle(nodeInfo);
}
@Override
public void recycle(AccessibilityNodeInfoCompat nodeInfo) {
super.recycle(nodeInfo);
}
@Override
public int recycleAll() {
return -1;
}
}
}

View File

@@ -1,8 +1,11 @@
package com.stardust.view.accessibility;
import android.graphics.Rect;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.automator.UiObject;
/**
* Created by Stardust on 2017/3/6.
*/
@@ -45,4 +48,16 @@ public class AccessibilityNodeInfoHelper {
nodeInfo.getBoundsInScreen(rect);
return rect;
}
public static Rect getBoundsInScreen(AccessibilityNodeInfoCompat nodeInfo) {
Rect rect = new Rect();
nodeInfo.getBoundsInScreen(rect);
return rect;
}
public static Rect getBoundsInParent(AccessibilityNodeInfoCompat nodeInfo) {
Rect rect = new Rect();
nodeInfo.getBoundsInParent(rect);
return rect;
}
}