Add: floating window, layout inspector

This commit is contained in:
hyb1996
2017-03-12 23:10:58 +08:00
parent a211d208d0
commit e81b661974
164 changed files with 7045 additions and 1206 deletions

View File

@@ -0,0 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.stardust.automator"
>
<application
android:supportsRtl="true"
>
</application>
</manifest>

View File

@@ -0,0 +1,60 @@
package com.stardust.automator;
import android.accessibilityservice.AccessibilityService;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import com.stardust.view.accessibility.AccessibilityDelegate;
import java.util.LinkedList;
import java.util.Queue;
/**
* Created by Stardust on 2017/3/9.
*/
public class AccessibilityEventCommandHost implements AccessibilityDelegate {
public interface Command {
void execute(AccessibilityService service, AccessibilityEvent event);
}
public static final AccessibilityEventCommandHost instance = new AccessibilityEventCommandHost();
private static final String TAG = "CommandHostDelegate";
private final Queue<Command> mCommands = new LinkedList<>();
@Override
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
synchronized (mCommands) {
if (!mCommands.isEmpty()) {
Log.v(TAG, "execute " + mCommands.size() + " commands");
}
while (!mCommands.isEmpty()) {
Command command = mCommands.poll();
command.execute(service, event);
synchronized (command) {
command.notify();
}
}
}
return false;
}
public void executeAndWaitForEvent(Command command) {
synchronized (mCommands) {
mCommands.offer(command);
}
synchronized (command) {
try {
command.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@@ -0,0 +1,63 @@
package com.stardust.automator;
import android.os.Bundle;
/**
* Created by Stardust on 2017/3/9.
*/
public abstract class ActionArgument {
protected final String mKey;
private ActionArgument(String key) {
mKey = key;
}
public abstract void putIn(Bundle bundle);
public static class IntActionArgument extends ActionArgument {
private final int mInt;
public IntActionArgument(String name, int i) {
super(name);
mInt = i;
}
@Override
public void putIn(Bundle bundle) {
bundle.putInt(mKey, mInt);
}
}
public static class CharSequenceActionArgument extends ActionArgument {
private final CharSequence mCharSequence;
public CharSequenceActionArgument(String name, CharSequence charSequence) {
super(name);
mCharSequence = charSequence;
}
@Override
public void putIn(Bundle bundle) {
bundle.putCharSequence(mKey, mCharSequence);
}
}
public static class FloatActionArgument extends ActionArgument {
private final float mFloat;
public FloatActionArgument(String name, float value) {
super(name);
mFloat = value;
}
@Override
public void putIn(Bundle bundle) {
bundle.putFloat(mKey, mFloat);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,289 @@
package com.stardust.automator;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.automator.filter.BooleanFilter;
import com.stardust.automator.filter.BoundsFilter;
import com.stardust.automator.filter.IdFilter;
import com.stardust.automator.filter.PackageNameFilter;
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 java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.*;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CONTEXT_CLICK;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_DOWN;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_LEFT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_RIGHT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_TO_POSITION;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_UP;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SET_PROGRESS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SHOW_ON_SCREEN;
/**
* Created by Stardust on 2017/3/8.
*/
public class UiGlobalSelector {
private Queue<ListFilter> mFilters = new LinkedList<>();
//// 第一类筛选条件
public UiGlobalSelector id(String id) {
mFilters.add(IdFilter.equals(id));
return this;
}
public UiGlobalSelector idContains(String str) {
mFilters.add(IdFilter.contains(str));
return this;
}
public UiGlobalSelector idStartsWith(String prefix) {
mFilters.add(IdFilter.startsWith(prefix));
return this;
}
public UiGlobalSelector idEndsWith(String suffix) {
mFilters.add(IdFilter.endsWith(suffix));
return this;
}
public UiGlobalSelector idMatches(String regex) {
mFilters.add(IdFilter.matches(regex));
return this;
}
public UiGlobalSelector text(String text) {
mFilters.add(TextFilter.equals(text));
return this;
}
public UiGlobalSelector textContains(String str) {
mFilters.add(TextFilter.contains(str));
return this;
}
public UiGlobalSelector textStartsWith(String prefix) {
mFilters.add(TextFilter.startsWith(prefix));
return this;
}
public UiGlobalSelector textEndsWith(String suffix) {
mFilters.add(TextFilter.endsWith(suffix));
return this;
}
public UiGlobalSelector textMatches(String regex) {
mFilters.add(TextFilter.matches(regex));
return this;
}
public UiGlobalSelector desc(String desc) {
mFilters.add(DescFilter.equals(desc));
return this;
}
public UiGlobalSelector descContains(String str) {
mFilters.add(DescFilter.contains(str));
return this;
}
public UiGlobalSelector descStartsWith(String prefix) {
mFilters.add(DescFilter.startsWith(prefix));
return this;
}
public UiGlobalSelector descEndsWith(String suffix) {
mFilters.add(DescFilter.endsWith(suffix));
return this;
}
public UiGlobalSelector descMatches(String regex) {
mFilters.add(DescFilter.matches(regex));
return this;
}
public UiGlobalSelector className(String className) {
mFilters.add(ClassNameFilter.equals(className));
return this;
}
public UiGlobalSelector classNameContains(String str) {
mFilters.add(ClassNameFilter.contains(str));
return this;
}
public UiGlobalSelector classNameStartsWith(String prefix) {
mFilters.add(ClassNameFilter.startsWith(prefix));
return this;
}
public UiGlobalSelector classNameEndsWith(String suffix) {
mFilters.add(ClassNameFilter.endsWith(suffix));
return this;
}
public UiGlobalSelector classNameMatches(String regex) {
mFilters.add(ClassNameFilter.matches(regex));
return this;
}
public UiGlobalSelector packageName(String packageName) {
mFilters.add(PackageNameFilter.equals(packageName));
return this;
}
public UiGlobalSelector packageNameContains(String str) {
mFilters.add(PackageNameFilter.contains(str));
return this;
}
public UiGlobalSelector packageNameStartsWith(String prefix) {
mFilters.add(PackageNameFilter.startsWith(prefix));
return this;
}
public UiGlobalSelector packageNameEndsWith(String suffix) {
mFilters.add(PackageNameFilter.endsWith(suffix));
return this;
}
public UiGlobalSelector packageNameMatches(String regex) {
mFilters.add(PackageNameFilter.matches(regex));
return this;
}
public UiGlobalSelector bounds(int l, int t, int r, int b) {
mFilters.add(new BoundsFilter(new Rect(l, t, r, b), BoundsFilter.TYPE_EQUALS));
return this;
}
public UiGlobalSelector boundsInside(int l, int t, int r, int b) {
mFilters.add(new BoundsFilter(new Rect(l, t, r, b), BoundsFilter.TYPE_INSIDE));
return this;
}
public UiGlobalSelector boundsInParent(int l, int t, int r, int b) {
mFilters.add(new BoundsFilter(new Rect(l, t, r, b), BoundsFilter.TYPE_PARENT));
return this;
}
//// 第二类筛选条件 -able
public UiGlobalSelector checkable(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.CHECKABLE, b));
return this;
}
public UiGlobalSelector checked(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.CHECKED, b));
return this;
}
public UiGlobalSelector focusable(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.FOCUSABLE, b));
return this;
}
public UiGlobalSelector focused(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.FOCUSED, b));
return this;
}
public UiGlobalSelector visibleToUser(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.VISIBLE_TO_USER, b));
return this;
}
public UiGlobalSelector accessibilityFocused(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.ACCESSIBILITY_FOCUSED, b));
return this;
}
public UiGlobalSelector selected(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.SELECTED, b));
return this;
}
public UiGlobalSelector clickable(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.CLICKABLE, b));
return this;
}
public UiGlobalSelector longClickable(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.LONG_CLICKABLE, b));
return this;
}
public UiGlobalSelector enabled(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.ENABLED, b));
return this;
}
public UiGlobalSelector password(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.PASSWORD, b));
return this;
}
public UiGlobalSelector scrollable(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.SCROLLABLE, b));
return this;
}
public UiGlobalSelector editable(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.EDITABLE, b));
return this;
}
public UiGlobalSelector contentInvalid(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.CONTENT_INVALID, b));
return this;
}
public UiGlobalSelector contextClickable(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.CONTEXT_CLICKABLE, b));
return this;
}
public UiGlobalSelector multiLine(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.MULTI_LINE, b));
return this;
}
public UiGlobalSelector dismissable(boolean b) {
mFilters.add(BooleanFilter.get(BooleanFilter.DISMISSABLE, b));
return this;
}
public UiObjectCollection findOf(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());
}
public UiGlobalSelector addFilter(ListFilter filter) {
mFilters.add(filter);
return this;
}
}

View File

@@ -0,0 +1,184 @@
package com.stardust.automator;
import android.os.Bundle;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.ArrayList;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CONTEXT_CLICK;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_DOWN;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_LEFT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_RIGHT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_TO_POSITION;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_UP;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SET_PROGRESS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SHOW_ON_SCREEN;
/**
* Created by Stardust on 2017/3/9.
*/
public class UiObject extends AccessibilityNodeInfoCompat {
public UiObject(Object info) {
super(info);
}
public UiObject parent() {
return new UiObject(getParent().getInfo());
}
public UiObject child(int i) {
return new UiObject(getChild(i).getInfo());
}
public UiObjectCollection children() {
ArrayList<AccessibilityNodeInfoCompat> list = new ArrayList<>(getChildCount());
for (int i = 0; i < getChildCount(); i++) {
list.add(getChild(i));
}
return UiObjectCollection.ofCompat(list);
}
public String id() {
return getViewIdResourceName();
}
public CharSequence text() {
return getText();
}
public CharSequence desc() {
return getContentDescription();
}
public CharSequence className() {
return getClassName();
}
public CharSequence packageName() {
return getPackageName();
}
public boolean performAction(int action, ActionArgument... arguments) {
Bundle bundle = argumentsToBundle(arguments);
return performAction(action, bundle);
}
private static Bundle argumentsToBundle(ActionArgument[] arguments) {
Bundle bundle = new Bundle();
for (ActionArgument arg : arguments) {
arg.putIn(bundle);
}
return bundle;
}
public boolean click() {
return performAction(ACTION_CLICK);
}
public boolean longClick() {
return performAction(ACTION_LONG_CLICK);
}
public boolean accessibilityFocus() {
return performAction(ACTION_ACCESSIBILITY_FOCUS);
}
public boolean clearAccessibilityFocus() {
return performAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS);
}
public boolean focus() {
return performAction(ACTION_FOCUS);
}
public boolean clearFocus() {
return performAction(ACTION_CLEAR_FOCUS);
}
public boolean copy() {
return performAction(ACTION_COPY);
}
public boolean paste() {
return performAction(ACTION_PASTE);
}
public boolean select() {
return performAction(ACTION_SELECT);
}
public boolean cut() {
return performAction(ACTION_CUT);
}
public boolean collapse() {
return performAction(ACTION_COLLAPSE);
}
public boolean expand() {
return performAction(ACTION_EXPAND);
}
public boolean dismiss() {
return performAction(ACTION_DISMISS);
}
public boolean show() {
return performAction(ACTION_SHOW_ON_SCREEN.getId());
}
public boolean scrollForward() {
return performAction(ACTION_SCROLL_FORWARD);
}
public boolean scrollBackward() {
return performAction(ACTION_SCROLL_BACKWARD);
}
public boolean scrollUp() {
return performAction(ACTION_SCROLL_UP.getId());
}
public boolean scrollDown() {
return performAction(ACTION_SCROLL_DOWN.getId());
}
public boolean scrollLeft() {
return performAction(ACTION_SCROLL_LEFT.getId());
}
public boolean scrollRight() {
return performAction(ACTION_SCROLL_RIGHT.getId());
}
public boolean contextClick() {
return performAction(ACTION_CONTEXT_CLICK.getId());
}
public boolean setSelection(int s, int e) {
return performAction(ACTION_SET_SELECTION,
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_SELECTION_START_INT, s),
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_SELECTION_END_INT, e));
}
public boolean setText(String text) {
return performAction(ACTION_SET_TEXT,
new ActionArgument.CharSequenceActionArgument(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text));
}
public boolean setProgress(float value) {
return performAction(ACTION_SET_PROGRESS.getId(),
new ActionArgument.FloatActionArgument(ACTION_ARGUMENT_PROGRESS_VALUE, value));
}
public boolean scrollTo(int row, int column) {
return performAction(ACTION_SCROLL_TO_POSITION.getId(),
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_ROW_INT, row),
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_COLUMN_INT, column));
}
}

View File

@@ -0,0 +1,211 @@
package com.stardust.automator;
import android.os.Bundle;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.util.Consumer;
import java.util.ArrayList;
import java.util.List;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.*;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CONTEXT_CLICK;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_DOWN;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_LEFT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_RIGHT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_TO_POSITION;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_UP;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SET_PROGRESS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SHOW_ON_SCREEN;
/**
* Created by Stardust on 2017/3/9.
*/
public class UiObjectCollection {
public static UiObjectCollection ofCompat(List<AccessibilityNodeInfoCompat> list) {
return new UiObjectCollection(list);
}
public static UiObjectCollection of(List<AccessibilityNodeInfo> list) {
List<AccessibilityNodeInfoCompat> compatList = new ArrayList<>(list.size());
for (AccessibilityNodeInfo nodeInfo : list) {
compatList.add(new AccessibilityNodeInfoCompat(nodeInfo));
}
return new UiObjectCollection(compatList);
}
private List<AccessibilityNodeInfoCompat> mNodes;
private UiObject[] mUiObjects;
private UiObjectCollection(List<AccessibilityNodeInfoCompat> list) {
mNodes = list;
}
public boolean performAction(int action) {
boolean fail = false;
for (AccessibilityNodeInfoCompat node : mNodes) {
if (!node.performAction(action)) {
fail = true;
}
}
return !fail;
}
public boolean performAction(int action, ActionArgument... arguments) {
boolean fail = false;
Bundle bundle = argumentsToBundle(arguments);
for (AccessibilityNodeInfoCompat node : mNodes) {
if (!node.performAction(action, bundle)) {
fail = true;
}
}
return !fail;
}
private Bundle argumentsToBundle(ActionArgument[] arguments) {
Bundle bundle = new Bundle();
for (ActionArgument arg : arguments) {
arg.putIn(bundle);
}
return bundle;
}
public boolean click() {
return performAction(ACTION_CLICK);
}
public boolean longClick() {
return performAction(ACTION_LONG_CLICK);
}
public boolean accessibilityFocus() {
return performAction(ACTION_ACCESSIBILITY_FOCUS);
}
public boolean clearAccessibilityFocus() {
return performAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS);
}
public boolean focus() {
return performAction(ACTION_FOCUS);
}
public boolean clearFocus() {
return performAction(ACTION_CLEAR_FOCUS);
}
public boolean copy() {
return performAction(ACTION_COPY);
}
public boolean paste() {
return performAction(ACTION_PASTE);
}
public boolean select() {
return performAction(ACTION_SELECT);
}
public boolean cut() {
return performAction(ACTION_CUT);
}
public boolean collapse() {
return performAction(ACTION_COLLAPSE);
}
public boolean expand() {
return performAction(ACTION_EXPAND);
}
public boolean dismiss() {
return performAction(ACTION_DISMISS);
}
public boolean show() {
return performAction(ACTION_SHOW_ON_SCREEN.getId());
}
public boolean scrollForward() {
return performAction(ACTION_SCROLL_FORWARD);
}
public boolean scrollBackward() {
return performAction(ACTION_SCROLL_BACKWARD);
}
public boolean scrollUp() {
return performAction(ACTION_SCROLL_UP.getId());
}
public boolean scrollDown() {
return performAction(ACTION_SCROLL_DOWN.getId());
}
public boolean scrollLeft() {
return performAction(ACTION_SCROLL_LEFT.getId());
}
public boolean scrollRight() {
return performAction(ACTION_SCROLL_RIGHT.getId());
}
public boolean contextClick() {
return performAction(ACTION_CONTEXT_CLICK.getId());
}
public boolean setSelection(int s, int e) {
return performAction(ACTION_SET_SELECTION,
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_SELECTION_START_INT, s),
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_SELECTION_END_INT, e));
}
public boolean setText(CharSequence text) {
return performAction(ACTION_SET_TEXT,
new ActionArgument.CharSequenceActionArgument(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text));
}
public boolean setProgress(float value) {
return performAction(ACTION_SET_PROGRESS.getId(),
new ActionArgument.FloatActionArgument(ACTION_ARGUMENT_PROGRESS_VALUE, value));
}
public boolean scrollTo(int row, int column) {
return performAction(ACTION_SCROLL_TO_POSITION.getId(),
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_ROW_INT, row),
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_COLUMN_INT, column));
}
public UiObject get(int i) {
ensureUiObjectAt(i);
return mUiObjects[i];
}
private void ensureUiObjectAt(int i) {
if (mUiObjects == null) {
mUiObjects = new UiObject[mNodes.size()];
}
if (mUiObjects[i] == null) {
mUiObjects[i] = new UiObject(mNodes.get(i).getInfo());
}
}
public int size() {
return mNodes.size();
}
public UiObjectCollection each(Consumer<UiObject> consumer) {
for (int i = 0; i < mNodes.size(); i++) {
ensureUiObjectAt(i);
consumer.accept(mUiObjects[i]);
}
return this;
}
}

View File

@@ -0,0 +1,193 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Stardust on 2017/3/9.
*/
public class BooleanFilter extends DfsFilter {
private static Map<BooleanSupplier, BooleanFilter[]> cache = new HashMap<>();
public static BooleanFilter get(BooleanSupplier supplier, boolean b) {
BooleanFilter[] booleanFilters = cache.get(supplier);
if (booleanFilters == null) {
booleanFilters = new BooleanFilter[2];
cache.put(supplier, booleanFilters);
}
int i = b ? 1 : 0;
if (booleanFilters[i] == null) {
booleanFilters[i] = new BooleanFilter(supplier, b);
}
return booleanFilters[i];
}
public interface BooleanSupplier {
boolean get(AccessibilityNodeInfo node);
}
public static final BooleanSupplier CHECKABLE = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isCheckable();
}
};
public static final BooleanSupplier CHECKED = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isChecked();
}
};
public static final BooleanSupplier FOCUSABLE = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isFocusable();
}
};
public static final BooleanSupplier FOCUSED = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isFocused();
}
};
public static final BooleanSupplier VISIBLE_TO_USER = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isVisibleToUser();
}
};
public static final BooleanSupplier ACCESSIBILITY_FOCUSED = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isAccessibilityFocused();
}
};
public static final BooleanSupplier SELECTED = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isSelected();
}
};
public static final BooleanSupplier CLICKABLE = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isClickable();
}
};
public static final BooleanSupplier LONG_CLICKABLE = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isLongClickable();
}
};
public static final BooleanSupplier ENABLED = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isEnabled();
}
};
public static final BooleanSupplier PASSWORD = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isPassword();
}
};
public static final BooleanSupplier SCROLLABLE = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isScrollable();
}
};
public static final BooleanSupplier EDITABLE = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isEditable();
}
};
public static final BooleanSupplier CONTENT_INVALID = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isContentInvalid();
}
};
public static final BooleanSupplier CONTEXT_CLICKABLE = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isContextClickable();
}
};
public static final BooleanSupplier MULTI_LINE = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isMultiLine();
}
};
public static final BooleanSupplier DISMISSABLE = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isDismissable();
}
};
public static final BooleanSupplier importantForAccessibility = new BooleanSupplier() {
@Override
public boolean get(AccessibilityNodeInfo node) {
return node.isImportantForAccessibility();
}
};
private BooleanSupplier mBooleanSupplier;
private boolean mExceptedValue;
public BooleanFilter(BooleanSupplier booleanSupplier, boolean exceptedValue) {
mBooleanSupplier = booleanSupplier;
mExceptedValue = exceptedValue;
}
@Override
protected boolean isIncluded(AccessibilityNodeInfo nodeInfo) {
return nodeInfo != null && mBooleanSupplier.get(nodeInfo) == mExceptedValue;
}
}

View File

@@ -0,0 +1,36 @@
package com.stardust.automator.filter;
import android.graphics.Rect;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityNodeInfoHelper;
/**
* Created by Stardust on 2017/3/9.
*/
public class BoundsFilter extends DfsFilter {
public static final int TYPE_EQUALS = 0;
public static final int TYPE_INSIDE = 1;
public static final int TYPE_PARENT = 2;
private Rect mBounds;
private int mType;
public BoundsFilter(Rect bounds, int type) {
mBounds = bounds;
mType = type;
}
@Override
protected boolean isIncluded(AccessibilityNodeInfo nodeInfo) {
if (mType == TYPE_PARENT) {
return AccessibilityNodeInfoHelper.getBoundsInParent(nodeInfo).equals(mBounds);
}
Rect boundsInScreen = AccessibilityNodeInfoHelper.getBoundsInScreen(nodeInfo);
if (mType == TYPE_EQUALS)
return boundsInScreen.equals(mBounds);
return mBounds.contains(boundsInScreen);
}
}

View File

@@ -0,0 +1,45 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/3/9.
*/
public class ClassNameFilter {
private static final KeyGetter CLASS_NAME_GETTER = new KeyGetter() {
@Override
public String getKey(AccessibilityNodeInfo nodeInfo) {
CharSequence charSequence = nodeInfo.getClassName();
return charSequence == null ? null : charSequence.toString();
}
};
public static ListFilter equals(String text) {
if (!text.contains(".")) {
text = "android.widget." + text;
}
return new EqualsFilter(text, CLASS_NAME_GETTER);
}
public static ListFilter contains(String str) {
return new ContainsFilter(str, CLASS_NAME_GETTER);
}
public static ListFilter startsWith(String prefix) {
return new StartsWithFilter(prefix, CLASS_NAME_GETTER);
}
public static ListFilter endsWith(String suffix) {
return new EndsWithFilter(suffix, CLASS_NAME_GETTER);
}
public static ListFilter matches(String regex) {
return new MatchesFilter(regex, CLASS_NAME_GETTER);
}
private ClassNameFilter() {
}
}

View File

@@ -0,0 +1,24 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/3/9.
*/
public class ContainsFilter extends DfsFilter {
private final KeyGetter mKeyGetter;
private final String mContains;
ContainsFilter(String contains, KeyGetter keyGetter) {
mContains = contains;
mKeyGetter = keyGetter;
}
@Override
protected boolean isIncluded(AccessibilityNodeInfo nodeInfo) {
String key = mKeyGetter.getKey(nodeInfo);
return key != null && key.contains(mContains);
}
}

View File

@@ -0,0 +1,41 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/3/9.
*/
public class DescFilter {
private static final KeyGetter DESC_GETTER = new KeyGetter() {
@Override
public String getKey(AccessibilityNodeInfo nodeInfo) {
CharSequence charSequence = nodeInfo.getContentDescription();
return charSequence == null ? null : charSequence.toString();
}
};
public static ListFilter equals(String text) {
return new EqualsFilter(text, DESC_GETTER);
}
public static ListFilter contains(String str) {
return new ContainsFilter(str, DESC_GETTER);
}
public static ListFilter startsWith(String prefix) {
return new StartsWithFilter(prefix, DESC_GETTER);
}
public static ListFilter endsWith(String suffix) {
return new EndsWithFilter(suffix, DESC_GETTER);
}
public static ListFilter matches(String regex) {
return new MatchesFilter(regex, DESC_GETTER);
}
private DescFilter(){
}
}

View File

@@ -0,0 +1,52 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/3/9.
*/
public abstract class DfsFilter implements ListFilter, Filter {
@Override
public List<AccessibilityNodeInfo> filter(List<AccessibilityNodeInfo> nodes) {
ArrayList<AccessibilityNodeInfo> list = new ArrayList<>();
for (AccessibilityNodeInfo node : nodes) {
if (isIncluded(node)) {
list.add(node);
}
filterChildren(node, list);
}
return list;
}
public List<AccessibilityNodeInfo> filter(AccessibilityNodeInfo node) {
ArrayList<AccessibilityNodeInfo> list = new ArrayList<>();
if (isIncluded(node)) {
list.add(node);
}
filterChildren(node, list);
return list;
}
private void filterChildren(AccessibilityNodeInfo parent, List<AccessibilityNodeInfo> list) {
for (int i = 0; i < parent.getChildCount(); i++) {
AccessibilityNodeInfo child = parent.getChild(i);
if (child == null)
continue;
boolean included = isIncluded(child);
if (included) {
list.add(child);
}
filterChildren(child, list);
if (!included) {
child.recycle();
}
}
}
protected abstract boolean isIncluded(AccessibilityNodeInfo nodeInfo);
}

View File

@@ -0,0 +1,25 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/3/9.
*/
public class EndsWithFilter extends DfsFilter {
private final String mSuffix;
private final KeyGetter mKeyGetter;
public EndsWithFilter(String suffix, KeyGetter keyGetter) {
mSuffix = suffix;
mKeyGetter = keyGetter;
}
@Override
protected boolean isIncluded(AccessibilityNodeInfo nodeInfo) {
String key = mKeyGetter.getKey(nodeInfo);
return key != null && key.endsWith(mSuffix);
}
}

View File

@@ -0,0 +1,27 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/3/9.
*/
public class EqualsFilter extends DfsFilter {
private String mText;
private KeyGetter mKeyGetter;
public EqualsFilter(String text, KeyGetter getter) {
mText = text;
mKeyGetter = getter;
}
@Override
protected boolean isIncluded(AccessibilityNodeInfo nodeInfo) {
String key = mKeyGetter.getKey(nodeInfo);
if(key != null){
return key.equals(mText);
}
return false;
}
}

View File

@@ -0,0 +1,15 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.List;
/**
* Created by Stardust on 2017/3/9.
*/
public interface Filter {
List<AccessibilityNodeInfo> filter(AccessibilityNodeInfo node);
}

View File

@@ -0,0 +1,53 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.List;
/**
* Created by Stardust on 2017/3/9.
*/
public class IdFilter extends ListFilter.Default {
private static final KeyGetter ID_GETTER = new KeyGetter() {
@Override
public String getKey(AccessibilityNodeInfo nodeInfo) {
return nodeInfo.getViewIdResourceName();
}
};
public static IdFilter equals(String id) {
return new IdFilter(id);
}
public static StartsWithFilter startsWith(String prefix) {
return new StartsWithFilter(prefix, ID_GETTER);
}
public static ListFilter endsWith(String suffix) {
return new EndsWithFilter(suffix, ID_GETTER);
}
public static ContainsFilter contains(String contains) {
return new ContainsFilter(contains, ID_GETTER);
}
public static MatchesFilter matches(String regex) {
return new MatchesFilter(regex, ID_GETTER);
}
private String mId;
private IdFilter(String id) {
mId = id;
}
@Override
public List<AccessibilityNodeInfo> filter(AccessibilityNodeInfo node) {
return node.findAccessibilityNodeInfosByViewId(mId);
}
}

View File

@@ -0,0 +1,12 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/3/9.
*/
public interface KeyGetter {
String getKey(AccessibilityNodeInfo nodeInfo);
}

View File

@@ -0,0 +1,28 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/3/9.
*/
public interface ListFilter {
List<AccessibilityNodeInfo> filter(List<AccessibilityNodeInfo> nodes);
abstract class Default implements Filter, ListFilter {
@Override
public List<AccessibilityNodeInfo> filter(List<AccessibilityNodeInfo> nodes) {
List<AccessibilityNodeInfo> list = new ArrayList<>();
for (AccessibilityNodeInfo node : nodes) {
list.addAll(filter(node));
}
return list;
}
}
}

View File

@@ -0,0 +1,25 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/3/9.
*/
public class MatchesFilter extends DfsFilter {
private final String mRegex;
private final KeyGetter mKeyGetter;
MatchesFilter(String regex, KeyGetter keyGetter) {
mRegex = regex;
mKeyGetter = keyGetter;
}
@Override
protected boolean isIncluded(AccessibilityNodeInfo nodeInfo) {
String key = mKeyGetter.getKey(nodeInfo);
return key != null && key.matches(mRegex);
}
}

View File

@@ -0,0 +1,42 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/3/9.
*/
public class PackageNameFilter {
private static final KeyGetter PACKAGE_NAME_GETTER = new KeyGetter() {
@Override
public String getKey(AccessibilityNodeInfo nodeInfo) {
CharSequence charSequence = nodeInfo.getPackageName();
return charSequence == null ? null : charSequence.toString();
}
};
public static ListFilter equals(String text) {
return new EqualsFilter(text, PACKAGE_NAME_GETTER);
}
public static ListFilter contains(String str) {
return new ContainsFilter(str, PACKAGE_NAME_GETTER);
}
public static ListFilter startsWith(String prefix) {
return new StartsWithFilter(prefix, PACKAGE_NAME_GETTER);
}
public static ListFilter endsWith(String suffix) {
return new EndsWithFilter(suffix, PACKAGE_NAME_GETTER);
}
public static ListFilter matches(String regex) {
return new MatchesFilter(regex, PACKAGE_NAME_GETTER);
}
private PackageNameFilter() {
}
}

View File

@@ -0,0 +1,25 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/3/9.
*/
public class StartsWithFilter extends DfsFilter {
private final String mPrefix;
private final KeyGetter mKeyGetter;
public StartsWithFilter(String prefix, KeyGetter keyGetter) {
mPrefix = prefix;
mKeyGetter = keyGetter;
}
@Override
protected boolean isIncluded(AccessibilityNodeInfo nodeInfo) {
String key = mKeyGetter.getKey(nodeInfo);
return key != null && key.startsWith(mPrefix);
}
}

View File

@@ -0,0 +1,52 @@
package com.stardust.automator.filter;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.List;
/**
* Created by Stardust on 2017/3/9.
*/
public class TextFilter extends ListFilter.Default {
private static final KeyGetter TEXT_GETTER = new KeyGetter() {
@Override
public String getKey(AccessibilityNodeInfo nodeInfo) {
CharSequence charSequence = nodeInfo.getText();
return charSequence == null ? null : charSequence.toString();
}
};
public static ListFilter equals(String text) {
return new EqualsFilter(text, TEXT_GETTER);
}
public static ListFilter contains(String str) {
return new TextFilter(str);
}
public static ListFilter startsWith(String prefix) {
return new StartsWithFilter(prefix, TEXT_GETTER);
}
public static ListFilter endsWith(String suffix) {
return new EndsWithFilter(suffix, TEXT_GETTER);
}
public static ListFilter matches(String regex) {
return new MatchesFilter(regex, TEXT_GETTER);
}
private String mText;
private TextFilter(String text) {
mText = text;
}
@Override
public List<AccessibilityNodeInfo> filter(AccessibilityNodeInfo node) {
return node.findAccessibilityNodeInfosByText(mText);
}
}

View File

@@ -0,0 +1,11 @@
package com.stardust.util;
/**
* Created by Stardust on 2017/3/10.
*/
public interface Consumer<T> {
void accept(T t);
}

View File

@@ -0,0 +1,22 @@
package com.stardust.util;
import android.util.SparseArray;
/**
* Created by Stardust on 2017/1/26.
*/
public class SparseArrayEntries<E> {
private SparseArray<E> mSparseArray = new SparseArray<>();
public SparseArrayEntries<E> entry(int key, E value) {
mSparseArray.put(key, value);
return this;
}
public SparseArray<E> sparseArray() {
return mSparseArray;
}
}

View File

@@ -0,0 +1,14 @@
package com.stardust.view.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;
/**
* Created by Stardust on 2017/2/14.
*/
public interface AccessibilityDelegate {
boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event);
}

View File

@@ -0,0 +1,48 @@
package com.stardust.view.accessibility;
import android.graphics.Rect;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/3/6.
*/
public class AccessibilityNodeInfoHelper {
/**
* Returns the node's bounds clipped to the size of the display
*
* @param node
* @param width pixel width of the display
* @param height pixel height of the display
* @return null if node is null, else a Rect containing visible bounds
*/
public static Rect getVisibleBoundsInScreen(AccessibilityNodeInfo node, int width, int height) {
if (node == null) {
return null;
}
// targeted node's bounds
Rect nodeRect = new Rect();
node.getBoundsInScreen(nodeRect);
Rect displayRect = new Rect();
displayRect.top = 0;
displayRect.left = 0;
displayRect.right = width;
displayRect.bottom = height;
boolean intersect = nodeRect.intersect(displayRect);
return nodeRect;
}
public static Rect getBoundsInParent(AccessibilityNodeInfo nodeInfo) {
Rect rect = new Rect();
nodeInfo.getBoundsInParent(rect);
return rect;
}
public static Rect getBoundsInScreen(AccessibilityNodeInfo nodeInfo) {
Rect rect = new Rect();
nodeInfo.getBoundsInScreen(rect);
return rect;
}
}

View File

@@ -0,0 +1,43 @@
package com.stardust.view.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
import android.text.TextUtils;
/**
* Created by Stardust on 2017/1/26.
*/
public class AccessibilityServiceUtils {
public static void goToAccessibilitySetting(Context context) {
context.startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
public static boolean isAccessibilityServiceEnabled(Context context, Class<? extends AccessibilityService> accessibilityService) {
ComponentName expectedComponentName = new ComponentName(context, accessibilityService);
String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (enabledServicesSetting == null)
return false;
TextUtils.SimpleStringSplitter colonSplitter = new TextUtils.SimpleStringSplitter(':');
colonSplitter.setString(enabledServicesSetting);
while (colonSplitter.hasNext()) {
String componentNameString = colonSplitter.next();
ComponentName enabledService = ComponentName.unflattenFromString(componentNameString);
if (enabledService != null && enabledService.equals(expectedComponentName))
return true;
}
return false;
}
}

View File

@@ -0,0 +1,2 @@
<resources>
</resources>