Add: tasker plugin support, injectable webview

This commit is contained in:
hyb1996
2017-04-02 12:41:00 +08:00
parent 121949f9c0
commit e7500e6e21
127 changed files with 5293 additions and 1667 deletions

View File

@@ -0,0 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.autojs"
>
<application android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
>
</application>
</manifest>

View File

@@ -0,0 +1,172 @@
package autojs;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.scriptdroid.droid.script.JavaScriptEngine;
import com.stardust.scriptdroid.droid.script.ScriptExecuteActivity;
import com.stardust.scriptdroid.service.VolumeChangeObverseService;
import com.stardust.scriptdroid.tool.FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.text.DateFormat;
import java.util.Date;
import timber.log.Timber;
/**
* Created by Stardust on 2017/1/23.
*/
public class Droid {
public static final String STAY = "\"stay\";";
public static final String UI = "\"ui\";";
public static final String AUTO = "\"auto\";";
public interface OnRunFinishedListener extends Serializable {
void onRunFinished(Object result);
void onException(@NonNull Exception e);
}
public static class SimpleOnRunFinishedListener implements OnRunFinishedListener {
@Override
public void onRunFinished(Object result) {
}
@Override
public void onException(@NonNull Exception e) {
}
}
private static final DroidRuntime RUNTIME = DroidRuntime.getRuntime();
public static final JavaScriptEngine JAVA_SCRIPT_ENGINE = JavaScriptEngine.getDefault();
private static final OnRunFinishedListener DEFAULT_LISTENER = new SimpleOnRunFinishedListener() {
@Override
public void onException(@NonNull Exception e) {
if (!causedByInterruptedException(e)) {
RUNTIME.toast(App.getApp().getString(R.string.text_error) + ": " + e.getMessage());
Timber.e(e.getMessage());
}
}
};
private static Droid instance = new Droid();
private VolumeChangeObverseService.OnVolumeChangeListener mOnVolumeChangeListener = new VolumeChangeObverseService.OnVolumeChangeListener() {
@Override
public void onVolumeChange() {
if (Pref.isRunningVolumeControlEnabled()) {
stopAllAndToast();
}
}
};
private Droid() {
VolumeChangeObverseService.addOnVolumeChangeListener(mOnVolumeChangeListener);
}
public static Droid getInstance() {
return instance;
}
public static boolean causedByInterruptedException(Throwable e) {
while (e != null) {
if (e instanceof InterruptedException) {
return true;
}
e = e.getCause();
}
return false;
}
public void runScriptFile(File file) {
runScriptFile(file, null, new RunningConfig());
}
public void runScriptFile(File file, OnRunFinishedListener listener, RunningConfig config) {
Timber.v(DateFormat.getTimeInstance().format(new Date()) + " " + App.getResString(R.string.text_start_running) + " " + file);
listener = listener == null ? DEFAULT_LISTENER : listener;
int errorMsgId = PathChecker.check(file.getPath());
if(errorMsgId != PathChecker.CHECK_RESULT_OK){
listener.onException(new IOException(App.getResString(errorMsgId)));
}
ScriptEngine
runScript(FileUtils.readString(file), listener, config.path(file.getPath()));
}
public void runScriptFile(String path) {
runScriptFile(new File(path));
}
public void runScript(String script) {
runScript(script, null, RunningConfig.getDefault());
}
public void runScript(String script, OnRunFinishedListener listener, RunningConfig config) {
App.getApp().startService(new Intent(App.getApp(), VolumeChangeObverseService.class));
listener = listener == null ? DEFAULT_LISTENER : listener;
if (!TextUtils.isEmpty(config.prepareScript))
script = config.prepareScript + "\n" + script;
if (config.runInNewThread) {
if (script.startsWith(UI)) {
ScriptExecuteActivity.runScript(script, listener, config);
} else {
new Thread(new RunScriptRunnable(script, listener, config)).start();
}
} else {
new RunScriptRunnable(script, listener, config).run();
}
}
public int stopAll() {
return JAVA_SCRIPT_ENGINE.stopAll();
}
public void stopAllAndToast() {
int n = stopAll();
if (n > 0)
RUNTIME.toast(String.format(App.getResString(R.string.text_already_stop_n_scripts), n));
else
RUNTIME.toast(App.getResString(R.string.text_no_running_script));
}
private static class RunScriptRunnable implements Runnable {
private final String mScript;
private OnRunFinishedListener mOnRunFinishedListener;
RunScriptRunnable(String script, OnRunFinishedListener listener, RunningConfig config) {
mOnRunFinishedListener = listener;
mScript = script;
}
@Override
public void run() {
try {
if (mScript.startsWith(AUTO))
DroidRuntime.getRuntime().ensureAccessibilityServiceEnabled();
mOnRunFinishedListener.onRunFinished(JAVA_SCRIPT_ENGINE.execute(mScript));
} catch (Exception e) {
mOnRunFinishedListener.onException(e);
}
}
}
}

View File

@@ -0,0 +1,62 @@
package autojs;
import android.app.Activity;
import android.os.Build;
import android.text.TextUtils;
import android.widget.Toast;
import com.stardust.scriptdroid.R;
import java.io.File;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
/**
* Created by Stardust on 2017/4/1.
*/
public class PathChecker {
public static final int CHECK_RESULT_OK = 0;
private Activity mActivity;
public PathChecker(Activity activity) {
mActivity = activity;
}
public static int check(final String path) {
if (TextUtils.isEmpty(path))
return R.string.text_path_is_empty;
if (!new File(path).exists())
return R.string.text_file_not_exists;
return CHECK_RESULT_OK;
}
public boolean checkAndToastError(String path) {
int result = checkWithStoragePermission(path);
if (result != CHECK_RESULT_OK) {
Toast.makeText(mActivity, result, Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
private int checkWithStoragePermission(String path) {
if (!hasStorageReadPermission(mActivity)) {
return R.string.text_no_file_rw_permission;
}
return check(path);
}
private static boolean hasStorageReadPermission(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
activity.checkSelfPermission(READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED;
}
return true;
}
}

View File

@@ -0,0 +1,32 @@
package autojs;
/**
* Created by Stardust on 2017/2/1.
*/
public class RunningConfig {
private static final RunningConfig RUNNING_CONFIG = new RunningConfig();
public String path;
public String prepareScript = "";
public static RunningConfig getDefault() {
return RUNNING_CONFIG;
}
public boolean runInNewThread = true;
public RunningConfig runInNewThread(boolean runInNewThread) {
this.runInNewThread = runInNewThread;
return this;
}
public RunningConfig path(String path) {
this.path = path;
return this;
}
public RunningConfig prepareScript(String script) {
this.prepareScript = script;
return this;
}
}

View File

@@ -0,0 +1,442 @@
package autojs.runtime;
import android.accessibilityservice.AccessibilityService;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Environment;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.jraska.console.Console;
import com.jraska.console.timber.ConsoleTree;
import com.stardust.automator.AccessibilityEventCommandHost;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.accessibility.AccessibilityInfoProvider;
import com.stardust.scriptdroid.droid.runtime.action.Action;
import com.stardust.scriptdroid.droid.runtime.action.ActionFactory;
import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityDelegate;
import com.stardust.scriptdroid.droid.runtime.action.ActionTarget;
import com.stardust.scriptdroid.droid.runtime.api.UiSelector;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
import com.stardust.scriptdroid.tool.FileUtils;
import com.stardust.scriptdroid.tool.IntentTool;
import com.stardust.scriptdroid.tool.Shell;
import com.stardust.scriptdroid.ui.console.ConsoleActivity;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.view.accessibility.AccessibilityServiceUtils;
import java.io.File;
import java.util.List;
import timber.log.Timber;
;
/**
* Created by Stardust on 2017/1/27.
*/
public class DroidRuntime {
static {
Timber.plant(new ConsoleTree.Builder()
.minPriority(Log.VERBOSE)
.verboseColor(0xff909090)
.debugColor(0xffc88b48)
.infoColor(0xffc9c9c9)
.warnColor(0xffa97db6)
.errorColor(0xffff534e)
.assertColor(0xffff5540)
.build());
}
private static class PerformGlobalActionCommand implements AccessibilityEventCommandHost.Command {
boolean result;
private int mGlobalAction;
PerformGlobalActionCommand(int globalAction) {
mGlobalAction = globalAction;
}
@Override
public void execute(AccessibilityService service, AccessibilityEvent event) {
result = service.performGlobalAction(mGlobalAction);
}
}
private static final String TAG = "DroidRuntime";
private static DroidRuntime runtime;
private final Object mActionPerformLock = new Object();
private Handler mUIHandler;
private Context mContext;
public static void initRuntime(Context context){
runtime = new DroidRuntime(context);
}
public static DroidRuntime getRuntime() {
return runtime;
}
private DroidRuntime(Context context) {
mContext = context;
mUIHandler = new Handler(mContext.getMainLooper());
}
public boolean launchPackage(String packageName) {
try {
PackageManager packageManager = App.getApp().getPackageManager();
App.getApp().startActivity(packageManager.getLaunchIntentForPackage(packageName));
return true;
} catch (Exception e) {
return false;
}
}
public boolean launchApp(String appName) {
return launchPackage(getPackageName(appName));
}
public String getPackageName(String appName) {
PackageManager packageManager = App.getApp().getPackageManager();
List<ApplicationInfo> installedApplications = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo applicationInfo : installedApplications) {
if (packageManager.getApplicationLabel(applicationInfo).toString().equals(appName)) {
return applicationInfo.processName;
}
}
return "";
}
public boolean openAppSetting(String packageName) {
return IntentTool.goToAppSetting(App.getApp(), packageName);
}
public ActionTarget text(String text, int i) {
return new ActionTarget.TextActionTarget(text, i);
}
public ActionTarget bounds(int left, int top, int right, int bottom) {
return new ActionTarget.BoundsActionTarget(new Rect(left, top, right, bottom));
}
public ActionTarget editable(int i) {
return new ActionTarget.EditableActionTarget(i);
}
public ActionTarget id(String id) {
return new ActionTarget.IdActionTarget(id);
}
public boolean click(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_CLICK));
}
public boolean longClick(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_LONG_CLICK));
}
public boolean scrollUp(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD));
}
public boolean scrollDown(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD));
}
public boolean scrollUp(int i) {
return performAction(ActionFactory.createScrollAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, i));
}
public boolean scrollDown(int i) {
return performAction(ActionFactory.createScrollAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, i));
}
public boolean scrollAllUp() {
return performAction(ActionFactory.createScrollMaxAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD));
}
public boolean scrollAllDown() {
return performAction(ActionFactory.createScrollMaxAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD));
}
public boolean focus(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_FOCUS));
}
public boolean select(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SELECT));
}
public boolean setText(ActionTarget target, String text) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SET_TEXT, text));
}
public void setClip(final String text) {
mUIHandler.post(new Runnable() {
@Override
public void run() {
((ClipboardManager) App.getApp().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText(TAG, text));
}
});
}
public boolean back() {
return performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
}
public boolean home() {
return performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);
}
public boolean powerDialog() {
return performGlobalAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG);
}
public boolean notifications() {
return performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
}
public boolean quickSettings() {
return performGlobalAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS);
}
public boolean recents() {
return performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS);
}
public boolean splitScreen() {
return performGlobalAction(AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN);
}
public boolean swipeDown() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_DOWN);
}
public boolean swipeDownLeft() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT);
}
public boolean swipeDownRight() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT);
}
public boolean swipeDownUp() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP);
}
public boolean swipeUp() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_UP);
}
public boolean swipeUpLeft() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT);
}
public boolean swipeUpRight() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT);
}
public boolean swipeUpDown() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN);
}
public boolean swipeLeft() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_LEFT);
}
public boolean swipeLeftRight() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT);
}
public boolean swipeLeftUp() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP);
}
public boolean swipeLeftDown() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN);
}
public boolean swipeRight() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_RIGHT);
}
public boolean swipeRightLeft() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT);
}
public boolean swipeRightUp() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP);
}
public boolean swipeRightDown() {
return performGlobalAction(AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN);
}
private boolean performGlobalAction(final int action) {
ensureAccessibilityServiceEnabled();
PerformGlobalActionCommand command = new PerformGlobalActionCommand(action);
AccessibilityEventCommandHost.getInstance().executeAndWaitForEvent(command);
return command.result;
}
public boolean paste(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE));
}
public void log(@Nullable Object o) {
String str = o + "";
SpannableString spannableString = new SpannableString(str);
spannableString.setSpan(new ForegroundColorSpan(Color.BLACK), 0, str.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Console.writeLine(spannableString);
}
public void err(@Nullable Object o) {
Timber.e("" + o);
}
public void console() {
App.getApp().startActivity(new Intent(App.getApp(), ConsoleActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
public void clearConsole() {
Console.clear();
}
private <T> T performAction(Action action) {
ensureAccessibilityServiceEnabled();
ActionPerformAccessibilityDelegate.setAction(action);
synchronized (mActionPerformLock) {
try {
mActionPerformLock.wait();
} catch (InterruptedException e) {
ActionPerformAccessibilityDelegate.setAction(ActionPerformAccessibilityDelegate.NO_ACTION);
throw new ScriptStopException(App.getApp().getString(R.string.text_script_stopped), e);
}
}
return (T) action.getResult();
}
public void ensureAccessibilityServiceEnabled() {
if (AccessibilityWatchDogService.getInstance() == null) {
String errorMessage = null;
if (AccessibilityServiceUtils.isAccessibilityServiceEnabled(App.getApp(), AccessibilityWatchDogService.class)) {
errorMessage = App.getApp().getString(R.string.text_auto_operate_service_enabled_but_not_running);
} else {
if (Pref.def().getBoolean(App.getApp().getString(R.string.key_enable_accessibility_service_by_root), false)) {
if (!AccessibilityServiceTool.enableAccessibilityServiceByRootAndWaitFor(AccessibilityWatchDogService.class, 3000)) {
errorMessage = App.getApp().getString(R.string.text_enable_accessibility_service_by_root_timeout);
}
} else {
errorMessage = App.getApp().getString(R.string.text_no_accessibility_permission);
}
}
if (errorMessage != null) {
toast(errorMessage);
throw new ScriptStopException(errorMessage);
}
}
}
public void toast(final String text) {
mUIHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(App.getApp(), text, Toast.LENGTH_SHORT).show();
}
});
}
public void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new ScriptStopException(e);
}
}
public void addAccessibilityEventListener(final Object delegate) {
if (delegate == null)
return;
}
public Shell.CommandResult shell(String cmd, int root) {
return Shell.execCommand(cmd, root != 0);
}
public String currentPackage() {
ensureAccessibilityServiceEnabled();
return AccessibilityInfoProvider.getInstance().getLatestPackage();
}
public String currentActivity() {
ensureAccessibilityServiceEnabled();
return AccessibilityInfoProvider.getInstance().getLatestActivity();
}
public UiSelector selector() {
return new UiSelector();
}
public boolean isStopped() {
return Thread.currentThread().isInterrupted();
}
public void stop() {
Thread.interrupted();
}
public String readFile(String path) {
return FileUtils.readString(new File(Environment.getExternalStorageDirectory() + "/" + path));
}
public ThemeColorMaterialDialogBuilder dialog() {
if (App.currentActivity() == null) {
throw new ScriptStopException(App.getApp().getString(R.string.text_cannot_create_dialog_when_app_invisible));
}
return new ThemeColorMaterialDialogBuilder(App.currentActivity()) {
public MaterialDialog show() {
mUIHandler.post(new Runnable() {
@Override
public void run() {
superShow();
}
});
return null;
}
private void superShow() {
super.show();
}
};
}
public void notifyActionPerformed() {
synchronized (mActionPerformLock) {
mActionPerformLock.notify();
}
}
}

View File

@@ -0,0 +1,24 @@
package autojs.runtime;
/**
* Created by Stardust on 2017/1/29.
*/
public class ScriptStopException extends RuntimeException {
public ScriptStopException(String message, Throwable cause) {
super(message, cause);
}
public ScriptStopException(String message) {
super(message);
}
public ScriptStopException() {
}
public ScriptStopException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,49 @@
package 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

@@ -0,0 +1,40 @@
package autojs.runtime.action;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/1/27.
*/
public abstract class Action {
private boolean mPerformUtilSucceed = false;
private volatile Object mResult = false;
public Action(boolean performUtilSucceed) {
mPerformUtilSucceed = performUtilSucceed;
}
public Action() {
this(false);
}
public abstract boolean perform(AccessibilityNodeInfo root);
public boolean performUtilSucceed() {
return mPerformUtilSucceed;
}
public Action setPerformUtilSucceed(boolean performUtilSucceed) {
mPerformUtilSucceed = performUtilSucceed;
return this;
}
public Object getResult() {
return mResult;
}
public void setResult(Object result) {
mResult = result;
}
}

View File

@@ -0,0 +1,64 @@
package autojs.runtime.action;
import android.graphics.Rect;
import android.os.Bundle;
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));
}
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

@@ -0,0 +1,75 @@
package autojs.runtime.action;
import android.accessibilityservice.AccessibilityService;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.view.accessibility.AccessibilityDelegate;
/**
* Created by Stardust on 2017/1/21.
*/
public class ActionPerformAccessibilityDelegate implements AccessibilityDelegate {
private static final String TAG = "ActionPerformDelegate";
public static final Action NO_ACTION = null;
private static final Object ACTION_LOCK = new Object();
private static Action action;
//private Executor mExecutor = Executors.newFixedThreadPool(5);
public static void setAction(Action action) {
synchronized (ACTION_LOCK) {
ActionPerformAccessibilityDelegate.action = action;
}
}
@Override
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
synchronized (ACTION_LOCK) {
if (action == NO_ACTION)
return false;
AccessibilityNodeInfo root = service.getRootInActiveWindow();
if (root == null) {
Log.v(TAG, "root = null");
return false;
}
performAction(root, action);
return false;
}
}
private void performAction(final AccessibilityNodeInfo root, final Action action) {
new Runnable() {
@Override
public void run() {
Log.i(TAG, "perform action:" + action);
if (action.perform(root)) {
action.setResult(true);
onActionPerformed();
} else if (!action.performUtilSucceed()) {
action.setResult(false);
onActionPerformed();
}
}
}.run();
}
private void onActionPerformed() {
synchronized (ACTION_LOCK) {
action = NO_ACTION;
DroidRuntime.getRuntime().notifyActionPerformed();
}
}
}

View File

@@ -0,0 +1,68 @@
package autojs.runtime.action;
import android.graphics.Rect;
/**
* 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);
}
}
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

@@ -0,0 +1,40 @@
package 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

@@ -0,0 +1,165 @@
package 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

@@ -0,0 +1,42 @@
package 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

@@ -0,0 +1,57 @@
package 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

@@ -0,0 +1,70 @@
package 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

@@ -0,0 +1,47 @@
package 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

@@ -0,0 +1,30 @@
package 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

@@ -0,0 +1,140 @@
package autojs.runtime.api;
import android.annotation.SuppressLint;
import android.util.Log;
import android.util.Pair;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.stardust.scriptdroid.droid.runtime.ScriptStopException;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import java.util.LinkedList;
import java.util.Queue;
/**
* Created by Stardust on 2017/4/1.
*/
public class InjectableWebClient extends WebViewClient {
private static final String TAG = "InjectableWebClient";
private Queue<Pair<String, ValueCallback<String>>> mToInjectJavaScripts = new LinkedList<>();
private final ValueCallback<String> defaultCallback = new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.i(TAG, "onReceiveValue: " + value);
}
};
private WebView mWebView;
private Context mContext;
private Scriptable mScriptable;
private ScriptBridge mScriptBridge = new ScriptBridge();
public InjectableWebClient(Context context, Scriptable scriptable) {
mContext = context;
mScriptable = scriptable;
}
@Override
public void onPageFinished(WebView view, String url) {
mWebView = view;
setUpWebView(view);
while (!mToInjectJavaScripts.isEmpty()) {
Pair<String, ValueCallback<String>> pair = mToInjectJavaScripts.poll();
inject(view, pair.first, pair.second);
}
super.onPageFinished(view, url);
}
@SuppressLint("SetJavaScriptEnabled")
private void setUpWebView(WebView view) {
view.addJavascriptInterface(mScriptBridge, "rhino");
WebSettings webSettings = view.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);
}
private void inject(WebView view, String script, ValueCallback<String> callback) {
view.evaluateJavascript(script, callback);
}
public void inject(String script, ValueCallback<String> callback) {
if (mWebView != null) {
inject(mWebView, script, callback);
return;
}
mToInjectJavaScripts.offer(new Pair<>(script, callback));
}
public void inject(String script) {
inject(script, defaultCallback);
}
public String injectAndWait(String script) {
InjectReturnCallback callback = new InjectReturnCallback();
inject(script, callback);
return callback.waitResult();
}
private class ScriptBridge {
private Object result;
@JavascriptInterface
public String eval(final String script) {
result = null;
mWebView.post(new Runnable() {
@Override
public void run() {
Log.v(TAG, "ScriptBridge.eval: " + script);
result = mContext.evaluateString(mScriptable, script, "<eval-local>", 1, null);
Log.v(TAG, "ScriptBridge.eval = " + result);
synchronized (ScriptBridge.this) {
ScriptBridge.this.notify();
}
}
});
synchronized (ScriptBridge.this) {
try {
ScriptBridge.this.wait();
} catch (InterruptedException e) {
throw new ScriptStopException(e);
}
}
return result.toString();
}
}
private static class InjectReturnCallback implements ValueCallback<String> {
private String result;
@Override
public void onReceiveValue(String value) {
result = value;
synchronized (this) {
this.notify();
}
}
String waitResult() {
synchronized (this) {
try {
this.wait();
} catch (InterruptedException e) {
throw new ScriptStopException(e);
}
}
return result;
}
}
}

View File

@@ -0,0 +1,34 @@
package autojs.runtime.api;
import android.content.Context;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import org.mozilla.javascript.Scriptable;
/**
* Created by Stardust on 2017/4/1.
*/
public class InjectableWebView extends WebView {
private InjectableWebClient mInjectableWebClient;
public InjectableWebView(Context context, org.mozilla.javascript.Context jsCtx, Scriptable scriptable) {
super(context);
init(jsCtx, scriptable);
}
private void init(org.mozilla.javascript.Context jsCtx, Scriptable scriptable) {
mInjectableWebClient = new InjectableWebClient(jsCtx, scriptable);
setWebViewClient(mInjectableWebClient);
}
public void inject(String script, ValueCallback<String> callback) {
mInjectableWebClient.inject(script, callback);
}
public void inject(String script) {
mInjectableWebClient.inject(script);
}
}

View File

@@ -0,0 +1,85 @@
package autojs.runtime.api;
/**
* Created by Stardust on 2017/3/7.
*/
public class Shell extends com.stardust.scriptdroid.tool.Shell {
public Shell(boolean root) {
super(root);
}
public void Tap(int x, int y) {
execute("input tap " + x + " " + y);
}
public void Swipe(int x1, int y1, int x2, int y2) {
execute("input swipe " + x1 + " " + y1 + " " + x2 + " " + y2);
}
public void Swipe(int x1, int y1, int x2, int y2, long duration) {
execute("input swipe " + x1 + " " + y1 + " " + x2 + " " + y2 + " " + duration);
}
public void KeyCode(int keyCode) {
execute("input keyevent " + keyCode);
}
public void KeyCode(String keyCode) {
execute("input keyevent " + keyCode);
}
public void Home() {
KeyCode(3);
}
public void Back() {
KeyCode(4);
}
public void Power() {
KeyCode(26);
}
public void Up() {
KeyCode(19);
}
public void Down() {
KeyCode(20);
}
public void Left() {
KeyCode(21);
}
public void Right() {
KeyCode(22);
}
public void OK() {
KeyCode(23);
}
public void VolumeUp() {
KeyCode(24);
}
public void VolumeDown() {
KeyCode(25);
}
public void Menu() {
KeyCode(1);
}
public void Camera() {
KeyCode(27);
}
public void Text(String text) {
execute("input text " + text);
}
}

View File

@@ -0,0 +1,227 @@
package autojs.runtime.api;
import android.accessibilityservice.AccessibilityService;
import android.support.annotation.NonNull;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
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.scriptdroid.accessibility.AccessibilityInfoProvider;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_COLUMN_INT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_PROGRESS_VALUE;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_ROW_INT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SELECTION_END_INT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SELECTION_START_INT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLICK;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_COLLAPSE;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_COPY;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CUT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_DISMISS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_EXPAND;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_FOCUS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_LONG_CLICK;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_PASTE;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_SELECT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_SET_SELECTION;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_SET_TEXT;
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 UiSelector extends UiGlobalSelector {
private class FindCommand implements AccessibilityEventCommandHost.Command {
UiObjectCollection result;
@Override
public void execute(AccessibilityService service, AccessibilityEvent event) {
AccessibilityNodeInfo root = service.getRootInActiveWindow();
if (root != null) {
result = findOf(root);
}
}
}
private static final String TAG = "UiSelector";
public UiObjectCollection find() {
ensureAccessibilityServiceEnabled();
FindCommand command = new FindCommand();
AccessibilityEventCommandHost.getInstance().executeAndWaitForEvent(command);
return command.result;
}
private void ensureAccessibilityServiceEnabled(){
DroidRuntime.getRuntime().ensureAccessibilityServiceEnabled();
}
@NonNull
public UiObjectCollection untilFind() {
UiObjectCollection uiObjectCollection;
do {
uiObjectCollection = find();
} while (uiObjectCollection == null || uiObjectCollection.size() == 0);
return uiObjectCollection;
}
public UiObject findOne() {
return untilFindOne();
}
@NonNull
public UiObject untilFindOne() {
UiObjectCollection collection = untilFind();
return new UiObject(collection.get(0).getInfo());
}
public UiSelector id(final String id) {
if (!id.contains(":")) {
addFilter(new DfsFilter() {
@Override
protected boolean isIncluded(AccessibilityNodeInfo nodeInfo) {
String fullId = AccessibilityInfoProvider.getInstance().getLatestPackage() + ":id/" + id;
return fullId.equals(nodeInfo.getViewIdResourceName());
}
});
} else {
super.id(id);
}
return this;
}
public boolean performAction(int action, ActionArgument... arguments) {
return untilFind().performAction(action, arguments);
}
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,61 @@
package autojs.script;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.BuildConfig;
import com.stardust.scriptdroid.tool.FileUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Stardust on 2017/1/27.
*/
public abstract class JavaScriptEngine {
private static JavaScriptEngine defaultEngine;
public static JavaScriptEngine getDefault() {
return defaultEngine;
}
public static void setDefault(JavaScriptEngine engine) {
defaultEngine = engine;
}
Map<String, Object> mVariableMap = new HashMap<>();
public abstract Object execute(String script);
public void set(String varName, Object value) {
mVariableMap.put(varName, value);
}
public abstract void removeAndDestroy();
public abstract int stopAll();
public abstract String[] getGlobalFunctions();
static class Init {
private static final String INIT_SCRIPT = readInitScript();
private static String readInitScript() {
try {
return FileUtils.readString(App.getApp().getAssets().open("javasccript_engine_init.js"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static String getInitScript() {
if (BuildConfig.DEBUG) {
// 调试时不缓存INIT_SCRIPT否则修改javasccript_engine_init.js后不会更新
return readInitScript();
} else {
return INIT_SCRIPT;
}
}
}
}

View File

@@ -0,0 +1,59 @@
package autojs.script;
import com.iwebpp.node.NodeContext;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.scriptdroid.tool.FileUtils;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import java.io.IOException;
/**
* Created by Stardust on 2017/3/25.
*/
public class NodeJsJavaScriptEngine extends RhinoJavaScriptEngine {
public NodeJsJavaScriptEngine(DroidRuntime runtime) {
super(runtime);
}
@Override
public Object execute(String script) {
NodeContext nodectx = new NodeContext();
Context context = createContext();
Scriptable scope = createScope(context);
ScriptableObject.putProperty(scope, "NodeCurrentContext", Context.javaToJS(nodectx, scope));
init(context, scope);
Object result = context.evaluateString(scope, script, "<script>", 1, null);
try {
nodectx.execute();
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
if (!script.startsWith(Droid.UI) && !script.startsWith(Droid.STAY))
removeAndDestroy();
return result;
}
@Override
protected void init(Context context, Scriptable scope) {
super.init(context, scope);
try {
context.evaluateString(scope, FileUtils.readString(App.getApp().getAssets().open("nodejs_engine_init.js")), "<nodejs_init>", 1, null);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void putProperties(Context context, Scriptable scope) {
super.putProperties(context, scope);
ScriptableObject.putProperty(scope, "NodeHostEnv", Context.javaToJS(this, scope));
}
}

View File

@@ -0,0 +1,191 @@
package autojs.script;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.scriptdroid.droid.runtime.ScriptStopException;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.commonjs.module.RequireBuilder;
import org.mozilla.javascript.commonjs.module.provider.ModuleSource;
import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider;
import org.mozilla.javascript.commonjs.module.provider.UrlModuleSourceProvider;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Created by Stardust on 2017/3/1.
*/
public class RhinoJavaScriptEngine extends JavaScriptEngine {
static {
ContextFactory.initGlobal(new InterruptibleContextFactory());
}
private final Set<Thread> mThreads = new HashSet<>();
private String[] mFunctions;
public RhinoJavaScriptEngine(DroidRuntime runtime) {
set("autojs", runtime);
}
@Override
public Object execute(String script) {
Context context = createContext();
Scriptable scope = createScope(context);
init(context, scope);
Object result = context.evaluateString(scope, script, "<script>", 1, null);
if (!script.startsWith(Droid.UI) && !script.startsWith(Droid.STAY))
removeAndDestroy();
return result;
}
protected Scriptable createScope(Context context) {
ImporterTopLevel importerTopLevel = new ImporterTopLevel();
importerTopLevel.initStandardObjects(context, false);
return importerTopLevel;
}
protected Context createContext() {
Context context = Context.enter();
context.setOptimizationLevel(-1);
context.setLanguageVersion(Context.VERSION_1_7);
context.setInstructionObserverThreshold(10000);
return context;
}
protected void init(Context context, Scriptable scope) {
putProperties(context, scope);
initRequireBuilder(context, scope);
context.evaluateString(scope, Init.getInitScript(), "<init>", 1, null);
mThreads.add(Thread.currentThread());
}
protected void putProperties(Context context, Scriptable scope) {
ScriptableObject.putProperty(scope, "context", App.getApp());
ScriptableObject.putProperty(scope, "__engine__", "rhino");
for (Map.Entry<String, Object> variable : mVariableMap.entrySet()) {
ScriptableObject.putProperty(scope, variable.getKey(), Context.javaToJS(variable.getValue(), scope));
}
}
private void initRequireBuilder(Context context, Scriptable scope) {
List<URI> list = Collections.singletonList(new File(ScriptFile.DEFAULT_DIRECTORY_PATH).toURI());
AssetAndUrlModuleSourceProvider provider = new AssetAndUrlModuleSourceProvider(App.getApp(), list);
new RequireBuilder()
.setModuleScriptProvider(new SoftCachingModuleScriptProvider(provider))
.setSandboxed(true)
.createRequire(context, scope)
.install(scope);
}
@Override
public int stopAll() {
int n;
synchronized (mThreads) {
for (Thread thread : mThreads) {
thread.interrupt();
}
n = mThreads.size();
mThreads.clear();
}
return n;
}
@Override
public String[] getGlobalFunctions() {
if (mFunctions == null)
mFunctions = getGlobalFunctionsInner();
return mFunctions;
}
private String[] getGlobalFunctionsInner() {
Scriptable scriptable = (Scriptable) execute("this");
Object[] ids = scriptable.getIds();
String[] functions = new String[ids.length];
for (int i = 0; i < ids.length; i++) {
functions[i] = ids[i].toString();
}
return functions;
}
@Override
public void removeAndDestroy() {
synchronized (mThreads) {
mThreads.remove(Thread.currentThread());
}
}
private static class InterruptibleContextFactory extends ContextFactory {
@Override
protected void observeInstructionCount(Context cx, int instructionCount) {
if (Thread.currentThread().isInterrupted()) {
Context.exit();
throw new ScriptStopException();
}
}
@Override
protected Context makeContext() {
Context cx = super.makeContext();
cx.setInstructionObserverThreshold(10000);
return cx;
}
}
private static class AssetAndUrlModuleSourceProvider extends UrlModuleSourceProvider {
private static final String MODULES_PATH = "modules";
private android.content.Context mContext;
private List<String> mModules;
private final URI mBaseURI = URI.create("file:///android_asset/modules");
public AssetAndUrlModuleSourceProvider(android.content.Context context, List<URI> list) {
super(list, null);
mContext = context;
try {
mModules = Arrays.asList(mContext.getAssets().list(MODULES_PATH));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected ModuleSource loadFromPrivilegedLocations(String moduleId, Object validator) throws IOException, URISyntaxException {
String moduleIdWithExtension = moduleId;
if (!moduleIdWithExtension.endsWith(".js")) {
moduleIdWithExtension += ".js";
}
if (mModules.contains(moduleIdWithExtension)) {
return new ModuleSource(new InputStreamReader(mContext.getAssets().open(MODULES_PATH + "/" + moduleIdWithExtension)), null,
URI.create(moduleIdWithExtension), mBaseURI, validator);
}
return super.loadFromPrivilegedLocations(moduleId, validator);
}
}
}

View File

@@ -0,0 +1,65 @@
package autojs.script;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.droid.RunningConfig;
/**
* Created by Stardust on 2017/2/5.
*/
public class ScriptExecuteActivity extends Activity {
private static final String EXTRA_SCRIPT = "EXTRA_SCRIPT";
private static final String EXTRA_ON_RUN_FINISHED_LISTENER = "EXTRA_ON_RUN_FINISHED_LISTENER";
private Object mResult;
private String mScript;
private Droid.OnRunFinishedListener mOnRunFinishedListener;
public static void runScript(String script, Droid.OnRunFinishedListener listener, RunningConfig config) {
App.getApp().startActivity(new Intent(App.getApp(), ScriptExecuteActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(EXTRA_SCRIPT, script)
.putExtra(EXTRA_ON_RUN_FINISHED_LISTENER, listener));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handleIntent(getIntent());
runScript();
}
private void handleIntent(Intent intent) {
mScript = intent.getStringExtra(EXTRA_SCRIPT);
mOnRunFinishedListener = (Droid.OnRunFinishedListener) intent.getSerializableExtra(EXTRA_ON_RUN_FINISHED_LISTENER);
}
private void runScript() {
Droid.JAVA_SCRIPT_ENGINE.set("activity", this);
try {
mResult = Droid.JAVA_SCRIPT_ENGINE.execute(mScript);
} catch (Exception e) {
mOnRunFinishedListener.onException(e);
finish();
}
}
@Override
public void finish() {
mOnRunFinishedListener.onRunFinished(mResult);
super.finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
Droid.JAVA_SCRIPT_ENGINE.set("activity", null);
Droid.JAVA_SCRIPT_ENGINE.removeAndDestroy();
}
}

View File

@@ -0,0 +1,104 @@
package autojs.script.file;
import android.os.Environment;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.tool.FileUtils;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.util.ArrayList;
/**
* Created by Stardust on 2017/1/23.
*/
public class ScriptFile extends File {
public static final String DEFAULT_DIRECTORY_PATH = Environment.getExternalStorageDirectory() + App.getApp().getString(R.string.folder_name);
public static final ScriptFile DEFAULT_DIRECTORY = new ScriptFile(DEFAULT_DIRECTORY_PATH);
private String mSimplifyPath;
private String mSimplifiedName;
public ScriptFile(String path) {
super(path);
init();
}
private void init() {
mSimplifiedName = FileUtils.getNameWithoutExtension(getPath());
mSimplifyPath = getPath();
if (mSimplifyPath.startsWith(Environment.getExternalStorageDirectory().getPath())) {
mSimplifyPath = mSimplifyPath.substring(Environment.getExternalStorageDirectory().getPath().length());
}
}
public ScriptFile(ScriptFile parent, String child) {
super(parent, child);
init();
}
public void run() {
Droid.getInstance().runScriptFile(this);
}
public boolean renameTo(String newName) {
return renameTo(new File(getParent(), newName));
}
public String getSimplifiedPath() {
return mSimplifyPath;
}
@Override
public ScriptFile getParentFile() {
String p = this.getParent();
if (p == null)
return null;
return new ScriptFile(p);
}
@Override
public ScriptFile[] listFiles() {
return listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory() || (file.getName().endsWith(".js") && !file.getName().startsWith("."));
}
});
}
@Override
public ScriptFile[] listFiles(FilenameFilter filter) {
String ss[] = list();
if (ss == null) return null;
ArrayList<ScriptFile> files = new ArrayList<>();
for (String s : ss)
if ((filter == null) || filter.accept(this, s))
files.add(new ScriptFile(this, s));
return files.toArray(new ScriptFile[files.size()]);
}
@Override
public ScriptFile[] listFiles(FileFilter filter) {
String ss[] = list();
if (ss == null) return null;
ArrayList<ScriptFile> files = new ArrayList<>();
for (String s : ss) {
ScriptFile f = new ScriptFile(this, s);
if ((filter == null) || filter.accept(f))
files.add(f);
}
return files.toArray(new ScriptFile[files.size()]);
}
public String getSimplifiedName() {
return mSimplifiedName;
}
}

View File

@@ -0,0 +1,42 @@
package autojs.script.file;
import java.io.File;
/**
* Created by Stardust on 2017/1/23.
*/
public abstract class ScriptFileList {
private static ScriptFileList impl;
public static void setImpl(ScriptFileList list) {
impl = list;
}
public static ScriptFileList getImpl() {
return impl;
}
public abstract void add(ScriptFile scriptFile);
public abstract ScriptFile get(int i);
public abstract void remove(int i);
public abstract void rename(int position, String newName, boolean renameFile);
public abstract int size();
public boolean deleteFromFileSystem(int i) {
if (i < 0 || i >= size())
return false;
File file = get(i);
remove(i);
return file.delete();
}
public abstract boolean containsPath(String path);
}

View File

@@ -0,0 +1,103 @@
package autojs.script.file;
import android.content.Context;
import android.content.SharedPreferences;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.tool.FileUtils;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/1/23.
*/
public class SharedPrefScriptFileList extends ScriptFileList {
private static final Gson GSON = new Gson();
private static final String SP_KEY_SCRIPT_NAME = "script_name";
private static final String SP_KEY_SCRIPT_PATH = "script_path";
private static ScriptFileList instance = new SharedPrefScriptFileList(App.getApp());
private SharedPreferences mSharedPreferences;
private List<String> mScriptPath;
private List<String> mScriptName;
public static ScriptFileList getInstance() {
return instance;
}
public SharedPrefScriptFileList(Context context) {
mSharedPreferences = context.getSharedPreferences("SharedPrefScriptFileList", Context.MODE_PRIVATE);
readFromSharedPref();
}
private void readFromSharedPref() {
Type type = new TypeToken<List<String>>() {
}.getType();
mScriptName = GSON.fromJson(mSharedPreferences.getString(SP_KEY_SCRIPT_NAME, ""), type);
mScriptPath = GSON.fromJson(mSharedPreferences.getString(SP_KEY_SCRIPT_PATH, ""), type);
if (mScriptName == null || mScriptPath == null || mScriptName.size() != mScriptPath.size()) {
reset();
}
}
private void reset() {
mScriptName = new ArrayList<>();
mScriptPath = new ArrayList<>();
syncWithSharedPref();
}
@Override
public void add(ScriptFile scriptFile) {
mScriptName.add(scriptFile.getSimplifiedName());
mScriptPath.add(scriptFile.getSimplifiedPath());
syncWithSharedPref();
}
@Override
public ScriptFile get(int i) {
return new ScriptFile(mScriptPath.get(i));
}
@Override
public void remove(int i) {
mScriptName.remove(i);
mScriptPath.remove(i);
syncWithSharedPref();
}
@Override
public void rename(int position, String newName, boolean renameFile) {
mScriptName.set(position, newName);
if (renameFile) {
String newPath = FileUtils.renameWithoutExtension(mScriptPath.get(position), newName);
mScriptPath.set(position, newPath);
}
syncWithSharedPref();
}
@Override
public int size() {
return mScriptPath.size();
}
@Override
public boolean containsPath(String path) {
return mScriptPath.contains(path);
}
protected void syncWithSharedPref() {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(SP_KEY_SCRIPT_NAME, GSON.toJson(mScriptName));
editor.putString(SP_KEY_SCRIPT_PATH, GSON.toJson(mScriptPath));
editor.apply();
}
}

View File

@@ -0,0 +1,92 @@
package autojs.script.file;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Created by Stardust on 2017/3/27.
*/
public class StorageScriptFileList extends ScriptFileList {
private List<ScriptFile> mFiles;
private ScriptFile mFolder;
public StorageScriptFileList() {
this(ScriptFile.DEFAULT_DIRECTORY_PATH);
}
public ScriptFile getFolder() {
return mFolder;
}
public StorageScriptFileList(ScriptFile folder) {
if (!folder.isDirectory()) {
throw new IllegalArgumentException("file is not folder:" + folder);
}
mFolder = folder;
mFiles = Arrays.asList(folder.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory() || (file.getName().endsWith(".js") && !file.getName().startsWith("."));
}
}));
Collections.sort(mFiles, new Comparator<ScriptFile>() {
@Override
public int compare(ScriptFile o1, ScriptFile o2) {
if (o1.isDirectory() != o2.isDirectory()) {
return o1.isDirectory() ? -1 : 1;
}
return 0;
}
});
}
public StorageScriptFileList(String path) {
this(new ScriptFile(path));
}
@Override
public void add(ScriptFile scriptFile) {
try {
scriptFile.createNewFile();
mFiles.add(scriptFile);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public ScriptFile get(int i) {
return mFiles.get(i);
}
@Override
public void remove(int i) {
throw new UnsupportedOperationException();
}
@Override
public void rename(int position, String newName, boolean renameFile) {
if (!renameFile)
throw new UnsupportedOperationException();
mFiles.get(position).renameTo(newName);
}
@Override
public int size() {
return mFiles.size();
}
@Override
public boolean containsPath(String path) {
return false;
}
}

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">autojs</string>
</resources>