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

@@ -8,6 +8,9 @@ if(__engine__ == "rhino"){
__importClassOld__(pack);
}
}
var loadJar = function(path){
__runtime__.loadJar(path);
}
}
var toast = function(text){

View File

@@ -30,7 +30,6 @@ public abstract class JavaScriptEngineManager {
private Map<String, Object> mGlobalVariableMap = new HashMap<>();
private final Set<JavaScriptEngine> mEngines = new HashSet<>();
private boolean mIsStopping = false;
private EngineLifecycleCallback mEngineLifecycleCallback;
private final ScriptSource INIT_SCRIPT;
@@ -82,8 +81,6 @@ public abstract class JavaScriptEngineManager {
void removeEngine(JavaScriptEngine engine) {
synchronized (mEngines) {
if (mIsStopping)
return;
mEngines.remove(engine);
if(mEngineLifecycleCallback != null){
mEngineLifecycleCallback.onEngineRemove(engine);
@@ -111,16 +108,11 @@ public abstract class JavaScriptEngineManager {
public int stopAll() {
synchronized (mEngines) {
mIsStopping = true;
int n = mEngines.size();
for (JavaScriptEngine engine : mEngines) {
engine.forceStop();
if(mEngineLifecycleCallback != null){
mEngineLifecycleCallback.onEngineRemove(engine);
}
}
mEngines.clear();
mIsStopping = false;
return n;
}
}

View File

@@ -1,5 +1,7 @@
package com.stardust.autojs.engine;
import com.stardust.autojs.rhino_android.AndroidContextFactory;
import com.stardust.autojs.rhino_android.RhinoAndroidHelper;
import com.stardust.autojs.runtime.ScriptStopException;
import com.stardust.autojs.script.ScriptSource;
@@ -9,16 +11,14 @@ import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import java.io.File;
/**
* Created by Stardust on 2017/4/2.
*/
public class RhinoJavaScriptEngine implements JavaScriptEngine {
static {
ContextFactory.initGlobal(new InterruptibleContextFactory());
}
private Context mContext;
private Scriptable mScriptable;
private Thread mThread;
@@ -69,7 +69,6 @@ public class RhinoJavaScriptEngine implements JavaScriptEngine {
mContext.evaluateString(mScriptable, mEngineManager.getInitScript().getScript(), "<init>", 1, null);
}
public Context getContext() {
return mContext;
}
@@ -85,23 +84,32 @@ public class RhinoJavaScriptEngine implements JavaScriptEngine {
}
protected Context createContext() {
Context context = Context.enter();
if (!ContextFactory.hasExplicitGlobal()) {
ContextFactory.initGlobal(new InterruptibleAndroidContextFactory(new File(mEngineManager.getContext().getCacheDir(), "classes")));
}
Context context = new RhinoAndroidHelper(mEngineManager.getContext()).enterContext();
context.setOptimizationLevel(-1);
context.setLanguageVersion(Context.VERSION_1_7);
return context;
}
private static class InterruptibleContextFactory extends ContextFactory {
private static class InterruptibleAndroidContextFactory extends AndroidContextFactory {
public InterruptibleAndroidContextFactory(File cacheDirectory) {
super(cacheDirectory);
}
@Override
protected void observeInstructionCount(Context cx, int instructionCount) {
if (Thread.currentThread().isInterrupted()) {
throw new ScriptStopException(new InterruptedException());
}
}
@Override
protected Context makeContext() {
Context cx = super.makeContext();
cx.setInstructionObserverThreshold(10000);
return cx;

View File

@@ -0,0 +1,148 @@
package com.stardust.autojs.rhino_android;
import com.android.dx.command.dexer.Main;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipParameters;
import org.mozilla.javascript.GeneratedClassLoader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import dalvik.system.DexFile;
/**
* Created by Stardust on 2017/4/5.
*/
public class AndroidClassLoader extends ClassLoader implements GeneratedClassLoader {
private final ClassLoader parent;
private List<DexFile> dx;
private final File dexFile;
private final File odexOatFile;
private final File classFile;
/**
* Create a new instance with the given parent classloader and cache dierctory
*
* @param parent the parent
* @param dir the cache directory
*/
public AndroidClassLoader(ClassLoader parent, File dir) {
this.parent = parent;
dx = new ArrayList<>();
dexFile = new File(dir, "dex-" + hashCode() + ".jar");
odexOatFile = new File(dir, "odex_oat-" + hashCode() + ".tmp");
classFile = new File(dir, "class-" + hashCode() + ".jar");
dir.mkdirs();
}
/**
* {@inheritDoc}
*/
@Override
public Class<?> defineClass(String name, byte[] data) {
try {
final ZipFile zipFile = new ZipFile(classFile);
final ZipParameters parameters = new ZipParameters();
parameters.setFileNameInZip(name.replace('.', '/') + ".class");
parameters.setSourceExternalStream(true);
zipFile.addStream(new ByteArrayInputStream(data), parameters);
return dexJar().loadClass(name, parent);
} catch (IOException | ZipException e) {
throw new FatalLoadingException(e);
} finally {
dexFile.delete();
odexOatFile.delete();
}
}
public void loadJar(File jar) throws IOException {
try {
final ZipFile zipFile = new ZipFile(classFile);
final ZipFile jarFile = new ZipFile(jar);
//noinspection unchecked
for (FileHeader header : (List<FileHeader>) jarFile.getFileHeaders()) {
if (!header.isDirectory()) {
final ZipParameters parameters = new ZipParameters();
parameters.setFileNameInZip(header.getFileName());
parameters.setSourceExternalStream(true);
zipFile.addStream(jarFile.getInputStream(header), parameters);
}
}
dexJar();
} catch (ZipException e) {
IOException exception = new IOException();
//noinspection UnnecessaryInitCause
exception.initCause(e);
throw exception;
}
}
private DexFile dexJar() throws IOException {
if (!classFile.exists()) {
classFile.createNewFile();
}
final Main.Arguments arguments = new Main.Arguments();
arguments.fileNames = new String[]{classFile.getPath()};
arguments.outName = dexFile.getPath();
arguments.jarOutput = true;
Main.run(arguments);
DexFile dex = DexFile.loadDex(dexFile.getPath(), odexOatFile.getPath(), 0);
dx.add(dex);
return dex;
}
/**
* Does nothing
*
* @param aClass ignored
*/
@Override
public void linkClass(Class<?> aClass) {
//doesn't make sense on android
}
/**
* Try to load a class. This will search all defined classes, all loaded jars and the parent class loader.
*
* @param name the name of the class to load
* @param resolve ignored
* @return the class
* @throws ClassNotFoundException if the class could not be found in any of the locations
*/
@Override
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null) {
for (DexFile dex : dx) {
loadedClass = dex.loadClass(name, parent);
if (loadedClass != null) {
break;
}
}
if (loadedClass == null) {
loadedClass = parent.loadClass(name);
}
}
return loadedClass;
}
/**
* Might be thrown in any Rhino method that loads bytecode if the loading failed
*/
public static class FatalLoadingException extends RuntimeException {
FatalLoadingException(Throwable t) {
super("Failed to define class", t);
}
}
}

View File

@@ -0,0 +1,52 @@
package com.stardust.autojs.rhino_android;
import com.stardust.autojs.runtime.ScriptStopException;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import java.io.File;
/**
* Created by Stardust on 2017/4/5.
*/
public class AndroidContextFactory extends ContextFactory {
private final File cacheDirectory;
/**
* Create a new factory. It will cache generated code in the given directory
*
* @param cacheDirectory the cache directory
*/
public AndroidContextFactory(File cacheDirectory) {
this.cacheDirectory = cacheDirectory;
initApplicationClassLoader(createClassLoader(com.faendir.rhino_android.AndroidContextFactory.class.getClassLoader()));
}
/**
* Create a ClassLoader which is able to deal with bytecode
*
* @param parent the parent of the create classloader
* @return a new ClassLoader
*/
@Override
protected AndroidClassLoader createClassLoader(ClassLoader parent) {
return new AndroidClassLoader(parent, cacheDirectory);
}
@Override
protected void observeInstructionCount(Context cx, int instructionCount) {
if (Thread.currentThread().isInterrupted()) {
throw new ScriptStopException(new InterruptedException());
}
}
@Override
protected Context makeContext() {
Context cx = super.makeContext();
cx.setInstructionObserverThreshold(10000);
return cx;
}
}

View File

@@ -0,0 +1,23 @@
package com.stardust.autojs.rhino_android;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.GeneratedClassLoader;
import org.mozilla.javascript.SecurityController;
import java.io.Serializable;
/**
* Created by Stardust on 2017/4/5.
*/
public class NoSecurityController extends SecurityController implements Serializable {
@Override
public GeneratedClassLoader createClassLoader(ClassLoader classLoader, Object o) {
return Context.getCurrentContext().createClassLoader(classLoader);
}
@Override
public Object getDynamicSecurityDomain(Object o) {
return null;
}
}

View File

@@ -0,0 +1,93 @@
package com.stardust.autojs.rhino_android;
import android.support.annotation.VisibleForTesting;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.SecurityController;
import java.io.File;
import java.io.IOException;
/**
* Created by Stardust on 2017/4/5.
*/
public class RhinoAndroidHelper {
private final File cacheDirectory;
/**
* Constructs a new helper using the default temporary directory.
* Note: It is recommended to use a custom directory, so no permission problems occur.
*/
public RhinoAndroidHelper() {
this(new File(System.getProperty("java.io.tmpdir", "."), "classes"));
}
/**
* Constructs a new helper using a directory in the applications cache.
*
* @param context any context
*/
public RhinoAndroidHelper(android.content.Context context) {
this(new File(context.getCacheDir(), "classes"));
}
/**
* Constructs a helper using the specified directory as cache.
*
* @param cacheDirectory the cache directory to use
*/
public RhinoAndroidHelper(File cacheDirectory) {
this.cacheDirectory = cacheDirectory;
}
/**
* call this instead of {@link Context#enter()}
*
* @return a context prepared for android
*/
public Context enterContext() {
if (!SecurityController.hasGlobal())
SecurityController.initGlobal(new NoSecurityController());
return getContextFactory().enterContext();
}
/**
* @return The Context factory which has to be used on android.
*/
@VisibleForTesting
public AndroidContextFactory getContextFactory() {
AndroidContextFactory factory;
if (!ContextFactory.hasExplicitGlobal()) {
factory = new AndroidContextFactory(cacheDirectory);
ContextFactory.getGlobalSetter().setContextFactoryGlobal(factory);
} else if (!(ContextFactory.getGlobal() instanceof AndroidContextFactory)) {
throw new IllegalStateException("Cannot initialize factory for Android Rhino: There is already another factory");
} else {
factory = (AndroidContextFactory) ContextFactory.getGlobal();
}
return factory;
}
/**
* Compiles all classes in a jar file. They will be available in Rhino.
*
* @param jar the jar to load
* @throws IOException if the jar cannot be read or is invalid or there is a problem with the cache
*/
public void loadClassJar(File jar) throws IOException {
((AndroidClassLoader) getContextFactory().getApplicationClassLoader()).loadJar(jar);
}
/**
* @return a context prepared for android
* @deprecated use {@link #enterContext()} instead
*/
@Deprecated
public static Context prepareContext() {
return new RhinoAndroidHelper().enterContext();
}
}

View File

@@ -1,7 +1,10 @@
package com.stardust.autojs.runtime;
import com.stardust.autojs.runtime.action.ActionPerformAccessibilityDelegate;
import android.accessibilityservice.AccessibilityService;
import android.support.annotation.Nullable;
import com.stardust.automator.AccessibilityEventCommandHost;
import com.stardust.automator.simple_action.SimpleActionPerformHost;
import com.stardust.view.accessibility.AccessibilityInfoProvider;
/**
@@ -16,7 +19,10 @@ public interface AccessibilityBridge {
AccessibilityEventCommandHost getCommandHost();
ActionPerformAccessibilityDelegate getActionPerformHost();
SimpleActionPerformHost getActionPerformHost();
@Nullable
AccessibilityService getService();
}

View File

@@ -6,15 +6,22 @@ import android.os.Handler;
import android.widget.Toast;
import com.stardust.autojs.R;
import com.stardust.autojs.runtime.action.ActionAutomator;
import com.stardust.autojs.rhino_android.AndroidClassLoader;
import com.stardust.autojs.runtime.simple_action.SimpleActionAutomator;
import com.stardust.autojs.runtime.api.AppUtils;
import com.stardust.autojs.runtime.api.Console;
import com.stardust.autojs.runtime.api.UiSelector;
import com.stardust.pio.UncheckedIOException;
import com.stardust.util.ClipboardUtil;
import com.stardust.util.SdkVersionUtil;
import com.stardust.util.Shell;
import com.stardust.view.accessibility.AccessibilityInfoProvider;
import org.mozilla.javascript.ContextFactory;
import java.io.File;
import java.io.IOException;
/**
* Created by Stardust on 2017/1/27.
@@ -35,7 +42,7 @@ public class ScriptRuntime {
public Console console;
@JavascriptField
public ActionAutomator automator;
public SimpleActionAutomator automator;
@JavascriptField
public AccessibilityInfoProvider info;
@@ -47,7 +54,7 @@ public class ScriptRuntime {
app = new AppUtils(context);
info = accessibilityBridge.getInfoProvider();
this.console = console;
automator = new ActionAutomator(accessibilityBridge, this);
automator = new SimpleActionAutomator(accessibilityBridge, this);
}
@JavascriptInterface
@@ -101,6 +108,15 @@ public class ScriptRuntime {
}
}
@JavascriptInterface
public void loadJar(String path) {
try {
((AndroidClassLoader) ContextFactory.getGlobal().getApplicationClassLoader()).loadJar(new File(path));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@JavascriptInterface
public void stop() {
Thread.interrupted();

View File

@@ -1,49 +0,0 @@
package com.stardust.autojs.runtime.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

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

View File

@@ -1,67 +0,0 @@
package com.stardust.autojs.runtime.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 Action 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 Action 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 Action 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 Action createScrollMaxAction(int action) {
return new ScrollMaxAction(action);
}
public static Action createScrollAction(int action, int i) {
return new ScrollAction(action, i);
}
public static Action createActionWithIdFilter(int action, String id) {
return new FilterAction.SimpleFilterAction(action, new FilterAction.IdFilter(id));
}
}

View File

@@ -1,77 +0,0 @@
package com.stardust.autojs.runtime.action;
import android.accessibilityservice.AccessibilityService;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityDelegate;
import java.util.LinkedList;
import java.util.Queue;
/**
* Created by Stardust on 2017/1/21.
*/
public class ActionPerformAccessibilityDelegate implements AccessibilityDelegate {
private static final String TAG = "ActionPerformDelegate";
private final Queue<Action> mActions = new LinkedList<>();
private Action mCurrentAction;
public void addAction(Action action) {
synchronized (mActions) {
mActions.offer(action);
}
}
@Override
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
synchronized (mActions) {
mCurrentAction = getCurrentAction();
if (mCurrentAction == null)
return false;
AccessibilityNodeInfo root = service.getRootInActiveWindow();
if (root == null) {
return false;
}
performAction(root, mCurrentAction);
return false;
}
}
private Action getCurrentAction() {
if (isCurrentActionValid()) {
return mCurrentAction;
}
if (mActions.isEmpty())
return null;
return mActions.poll();
}
private boolean isCurrentActionValid() {
return mCurrentAction != null && mCurrentAction.isValid();
}
private void performAction(final AccessibilityNodeInfo root, final Action action) {
Log.i(TAG, "perform action:" + action);
if (action.perform(root)) {
action.setResult(true);
onActionPerformed(action);
}
}
private void onActionPerformed(Action action) {
mCurrentAction = null;
synchronized (action) {
action.notify();
}
}
}

View File

@@ -1,71 +0,0 @@
package com.stardust.autojs.runtime.action;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.RequiresApi;
/**
* Created by Stardust on 2017/1/27.
*/
public interface ActionTarget {
Action 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 Action 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 Action 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 Action 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 Action createAction(int action, Object... params) {
return ActionFactory.createActionWithIdFilter(action, mId);
}
}
}

View File

@@ -1,40 +0,0 @@
package com.stardust.autojs.runtime.action;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/1/27.
*/
public class DepthFirstSearchTargetAction extends SearchTargetAction {
private Able mAble;
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 = n.getChild(i);
if (child == null)
continue;
AccessibilityNodeInfo node = searchTarget(child);
if (node != null)
return node;
else
child.recycle();
}
return null;
}
}

View File

@@ -1,165 +0,0 @@
package com.stardust.autojs.runtime.action;
import android.graphics.Rect;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* Created by Stardust on 2017/1/27.
*/
public abstract class FilterAction extends Action {
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 = root.findAccessibilityNodeInfosByText(mText);
if (mIndex == -1)
return list;
if (mIndex >= list.size())
return Collections.EMPTY_LIST;
return Collections.singletonList(list.get(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 = root.getChild(i);
if (child == null)
continue;
AccessibilityNodeInfo nodeInfo = findAccessibilityNodeInfosByBounds(child);
if (nodeInfo != null)
return nodeInfo;
else
child.recycle();
}
return null;
}
}
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));
}
@SuppressWarnings("unchecked")
public static List<AccessibilityNodeInfo> findEditable(AccessibilityNodeInfo root) {
if (root == null) {
return Collections.EMPTY_LIST;
}
if (root.isEditable()) {
return Collections.singletonList(root);
}
List<AccessibilityNodeInfo> list = new LinkedList<>();
for (int i = 0; i < root.getChildCount(); i++) {
list.addAll(findEditable(root.getChild(i)));
}
return list;
}
}
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 root.findAccessibilityNodeInfosByViewId(mId);
}
}
private Filter mFilter;
public FilterAction(Filter filter) {
mFilter = filter;
}
public boolean perform(AccessibilityNodeInfo root) {
if (root == null)
return false;
return perform(mFilter.filter(root));
}
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;
}
}
}

View File

@@ -1,42 +0,0 @@
package com.stardust.autojs.runtime.action;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/2/14.
*/
public class GetTextAction extends Action {
@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 = nodeInfo.getChild(i);
if (child != null) {
getText(child, texts);
child.recycle();
}
}
}
@Override
public void setResult(Object result) {
if (result instanceof List)
super.setResult(result);
}
}

View File

@@ -1,57 +0,0 @@
package com.stardust.autojs.runtime.action;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/2/12.
*/
public class ScrollAction extends Action {
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 = node.getChild(i);
if (child == null)
continue;
if (!findScrollableNodes(child, list))
child.recycle();
}
return node.isScrollable();
}
}

View File

@@ -1,70 +0,0 @@
package com.stardust.autojs.runtime.action;
import android.graphics.Rect;
import android.util.Log;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/1/27.
*/
public class ScrollMaxAction extends Action {
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 = nodeInfo.getChild(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

@@ -1,47 +0,0 @@
package com.stardust.autojs.runtime.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;
}
}
return performed;
}
protected void performAction(AccessibilityNodeInfo node) {
node.performAction(mAction);
}
public int getAction(){
return mAction;
}
public AccessibilityNodeInfo searchTarget(AccessibilityNodeInfo node) {
return node;
}
}

View File

@@ -1,30 +0,0 @@
package com.stardust.autojs.runtime.action;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/1/27.
*/
public class SearchUpTargetAction extends SearchTargetAction {
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;
while (node != null && !mAble.isAble(node)) {
AccessibilityNodeInfo parent = node.getParent();
if (node != n) {
node.recycle();
}
node = parent;
}
return node;
}
}

View File

@@ -4,6 +4,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import com.stardust.autojs.runtime.JavascriptInterface;
import com.stardust.util.IntentUtil;
import java.util.List;
@@ -21,6 +22,7 @@ public class AppUtils {
}
@JavascriptInterface
public boolean launchPackage(String packageName) {
try {
PackageManager packageManager = mContext.getPackageManager();
@@ -32,10 +34,12 @@ public class AppUtils {
}
@JavascriptInterface
public boolean launchApp(String appName) {
return launchPackage(getPackageName(appName));
}
@JavascriptInterface
public String getPackageName(String appName) {
PackageManager packageManager = mContext.getPackageManager();
List<ApplicationInfo> installedApplications = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
@@ -47,6 +51,7 @@ public class AppUtils {
return "";
}
@JavascriptInterface
public boolean openAppSetting(String packageName) {
return IntentUtil.goToAppDetailSettings(mContext, packageName);
}

View File

@@ -2,30 +2,42 @@ package com.stardust.autojs.runtime.api;
import android.support.annotation.Nullable;
import com.stardust.autojs.runtime.JavascriptInterface;
/**
* Created by Stardust on 2017/4/2.
*/
public interface Console {
@JavascriptInterface
void i(@Nullable Object o);
@JavascriptInterface
void i(String str);
@JavascriptInterface
void e(String message);
@JavascriptInterface
void e(@Nullable Object o);
@JavascriptInterface
void v(String v);
@JavascriptInterface
void v(@Nullable Object o);
@JavascriptInterface
void log(@Nullable Object o);
@JavascriptInterface
void log(String string);
@JavascriptInterface
void show();
@JavascriptInterface
void clear();
}

View File

@@ -6,12 +6,14 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.autojs.runtime.AccessibilityBridge;
import com.stardust.autojs.runtime.JavascriptInterface;
import com.stardust.automator.AccessibilityEventCommandHost;
import com.stardust.automator.ActionArgument;
import com.stardust.automator.UiGlobalSelector;
import com.stardust.automator.UiObject;
import com.stardust.automator.UiObjectCollection;
import com.stardust.automator.filter.DfsFilter;
import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_COLUMN_INT;
@@ -51,7 +53,7 @@ import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.
public class UiSelector extends UiGlobalSelector {
private class FindCommand implements AccessibilityEventCommandHost.Command {
private class FindCommand extends AccessibilityEventCommandHost.AbstractCommand {
UiObjectCollection result;
@@ -62,6 +64,7 @@ public class UiSelector extends UiGlobalSelector {
result = findOf(root);
}
}
}
private static final String TAG = "UiSelector";
@@ -73,11 +76,17 @@ public class UiSelector extends UiGlobalSelector {
}
@JavascriptInterface
public UiObjectCollection find() {
ensureAccessibilityServiceEnabled();
FindCommand command = new FindCommand();
mAccessibilityBridge.getCommandHost().executeAndWaitForEvent(command);
return command.result;
AccessibilityService service = mAccessibilityBridge.getService();
if (service != null) {
AccessibilityNodeInfo root = service.getRootInActiveWindow();
if (root != null) {
return findOf(root);
}
}
return null;
}
private void ensureAccessibilityServiceEnabled() {
@@ -85,6 +94,7 @@ public class UiSelector extends UiGlobalSelector {
}
@JavascriptInterface
@NonNull
public UiObjectCollection untilFind() {
UiObjectCollection uiObjectCollection;
@@ -94,6 +104,7 @@ public class UiSelector extends UiGlobalSelector {
return uiObjectCollection;
}
@JavascriptInterface
public UiObject findOne() {
return untilFindOne();
}
@@ -104,6 +115,7 @@ public class UiSelector extends UiGlobalSelector {
return new UiObject(collection.get(0).getInfo());
}
@JavascriptInterface
public UiSelector id(final String id) {
if (!id.contains(":")) {
addFilter(new DfsFilter() {
@@ -120,110 +132,135 @@ public class UiSelector extends UiGlobalSelector {
}
public boolean performAction(int action, ActionArgument... arguments) {
private boolean performAction(int action, ActionArgument... arguments) {
return untilFind().performAction(action, arguments);
}
@JavascriptInterface
public boolean click() {
return performAction(ACTION_CLICK);
}
@JavascriptInterface
public boolean longClick() {
return performAction(ACTION_LONG_CLICK);
}
@JavascriptInterface
public boolean accessibilityFocus() {
return performAction(ACTION_ACCESSIBILITY_FOCUS);
}
@JavascriptInterface
public boolean clearAccessibilityFocus() {
return performAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS);
}
@JavascriptInterface
public boolean focus() {
return performAction(ACTION_FOCUS);
}
@JavascriptInterface
public boolean clearFocus() {
return performAction(ACTION_CLEAR_FOCUS);
}
@JavascriptInterface
public boolean copy() {
return performAction(ACTION_COPY);
}
@JavascriptInterface
public boolean paste() {
return performAction(ACTION_PASTE);
}
@JavascriptInterface
public boolean select() {
return performAction(ACTION_SELECT);
}
@JavascriptInterface
public boolean cut() {
return performAction(ACTION_CUT);
}
@JavascriptInterface
public boolean collapse() {
return performAction(ACTION_COLLAPSE);
}
@JavascriptInterface
public boolean expand() {
return performAction(ACTION_EXPAND);
}
@JavascriptInterface
public boolean dismiss() {
return performAction(ACTION_DISMISS);
}
@JavascriptInterface
public boolean show() {
return performAction(ACTION_SHOW_ON_SCREEN.getId());
}
@JavascriptInterface
public boolean scrollForward() {
return performAction(ACTION_SCROLL_FORWARD);
}
@JavascriptInterface
public boolean scrollBackward() {
return performAction(ACTION_SCROLL_BACKWARD);
}
@JavascriptInterface
public boolean scrollUp() {
return performAction(ACTION_SCROLL_UP.getId());
}
@JavascriptInterface
public boolean scrollDown() {
return performAction(ACTION_SCROLL_DOWN.getId());
}
@JavascriptInterface
public boolean scrollLeft() {
return performAction(ACTION_SCROLL_LEFT.getId());
}
@JavascriptInterface
public boolean scrollRight() {
return performAction(ACTION_SCROLL_RIGHT.getId());
}
@JavascriptInterface
public boolean contextClick() {
return performAction(ACTION_CONTEXT_CLICK.getId());
}
@JavascriptInterface
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));
}
@JavascriptInterface
public boolean setText(String text) {
return performAction(ACTION_SET_TEXT,
new ActionArgument.CharSequenceActionArgument(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text));
}
@JavascriptInterface
public boolean setProgress(float value) {
return performAction(ACTION_SET_PROGRESS.getId(),
new ActionArgument.FloatActionArgument(ACTION_ARGUMENT_PROGRESS_VALUE, value));
}
@JavascriptInterface
public boolean scrollTo(int row, int column) {
return performAction(ACTION_SCROLL_TO_POSITION.getId(),
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_ROW_INT, row),

View File

@@ -1,9 +1,10 @@
package com.stardust.autojs.runtime.action;
package com.stardust.autojs.runtime.simple_action;
import android.accessibilityservice.AccessibilityService;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -11,14 +12,17 @@ import com.stardust.autojs.runtime.AccessibilityBridge;
import com.stardust.autojs.runtime.JavascriptInterface;
import com.stardust.autojs.runtime.ScriptRuntime;
import com.stardust.automator.AccessibilityEventCommandHost;
import com.stardust.automator.simple_action.SimpleAction;
import com.stardust.automator.simple_action.ActionFactory;
import com.stardust.automator.simple_action.ActionTarget;
/**
* Created by Stardust on 2017/4/2.
*/
public class ActionAutomator {
public class SimpleActionAutomator {
private static class PerformGlobalActionCommand implements AccessibilityEventCommandHost.Command {
private static class PerformGlobalActionCommand extends AccessibilityEventCommandHost.AbstractCommand {
boolean result;
private int mGlobalAction;
@@ -31,12 +35,13 @@ public class ActionAutomator {
public void execute(AccessibilityService service, AccessibilityEvent event) {
result = service.performGlobalAction(mGlobalAction);
}
}
private AccessibilityBridge mAccessibilityBridge;
private ScriptRuntime mScriptRuntime;
public ActionAutomator(AccessibilityBridge accessibilityBridge, ScriptRuntime scriptRuntime) {
public SimpleActionAutomator(AccessibilityBridge accessibilityBridge, ScriptRuntime scriptRuntime) {
mAccessibilityBridge = accessibilityBridge;
mScriptRuntime = scriptRuntime;
}
@@ -159,9 +164,10 @@ public class ActionAutomator {
private boolean performGlobalAction(final int action) {
ensureAccessibilityServiceEnabled();
PerformGlobalActionCommand command = new PerformGlobalActionCommand(action);
mAccessibilityBridge.getCommandHost().executeAndWaitForEvent(command);
return command.result;
AccessibilityService service = mAccessibilityBridge.getService();
if (service == null)
return false;
return service.performGlobalAction(action);
}
@JavascriptInterface
@@ -174,17 +180,16 @@ public class ActionAutomator {
}
@SuppressWarnings("unchecked")
private <T> T performAction(Action action) {
private <T> T performAction(SimpleAction simpleAction) {
ensureAccessibilityServiceEnabled();
mAccessibilityBridge.getActionPerformHost().addAction(action);
synchronized (action) {
try {
action.wait();
} catch (InterruptedException e) {
action.setValid(false);
mScriptRuntime.stoppedByInterrupted(e);
AccessibilityService service = mAccessibilityBridge.getService();
if (service != null) {
AccessibilityNodeInfo root = service.getRootInActiveWindow();
Log.i("Automator", "performAction: " + simpleAction + " root = " + root);
if (root != null) {
simpleAction.setResult(simpleAction.perform(root));
}
}
return (T) action.getResult();
return (T) simpleAction.getResult();
}
}

View File

@@ -66,4 +66,8 @@ public abstract class ScriptSource implements Serializable {
return mode;
}
@Override
public String toString() {
return mName + ".js";
}
}

View File

@@ -23,4 +23,5 @@ public class StringScriptSource extends ScriptSource {
public String getScript() {
return mScript;
}
}