add: javascript support

This commit is contained in:
hyb1996
2017-01-27 19:45:07 +08:00
parent 307dbe8859
commit c6fd10c7f9
60 changed files with 1352 additions and 1126 deletions

2
.gitignore vendored
View File

@@ -6,4 +6,4 @@
.DS_Store
/build
/captures
.externalNativeBuild
.externalNativeBuild

View File

@@ -40,4 +40,10 @@ dependencies {
compile 'com.afollestad.material-dialogs:core:0.9.2.3'
compile 'com.google.code.gson:gson:2.8.0'
compile 'com.sothree.slidinguppanel:library:3.3.1'
compile 'com.squareup.duktape:duktape-android:1.1.0'
compile 'com.furture.react:DuktapeJava:1.1.0'
compile files('libs/JSTransformer.jar')
}

BIN
app/libs/JSTransformer.jar Normal file

Binary file not shown.

View File

@@ -8,6 +8,7 @@
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/>
<application
android:name=".App"
android:allowBackup="true"
android:icon="@drawable/script_droid"
android:label="@string/app_name"
@@ -36,7 +37,7 @@
</activity>
<service
android:name=".action.ActionPerformService"
android:name=".droid.runtime.action.ActionPerformService"
android:enabled="true"
android:exported="true"
android:label="脚本执行机器人"
@@ -51,8 +52,8 @@
</service>
<service
android:name="com.stardust.scriptdroid.tile.TileService"
android:icon="@drawable/ic_accessibility_service"
android:name="com.stardust.scriptdroid.tile.BoundsAssistEnableTileService"
android:icon="@drawable/ic_robot_head"
android:label="脚本辅助"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>

View File

@@ -0,0 +1,112 @@
var toast = function(text){
droid.toast(text);
}
var launchPackage = function(package){
droid.launchPackage(package);
}
var launch = function(a, b){
if(arguments.length == 2){
droid.launch(a, b);
}else{
droid.launchPackage(a);
}
}
var launchApp = function(appName){
droid.launchApp(appName);
}
var text = function(str){
return droid.text(str);
}
var bounds = function(left, top, right, bottom){
return droid.bounds(left, top, right, bottom);
}
function performAction(action, args){
if(args.length == 4){
return action(bounds(args[0], args[1], args[2], args[3]));
}else{
return action(text(args[0]));
}
}
var click = function(){
return performAction(function(target){
return droid.click(target);
}, arguments);
}
var longClick = function(a, b, c, d){
return performAction(function(target){
return droid.longClick(target);
}, arguments);
}
var scrollAllUp = function(){
droid.scrollAllUp();
}
var scrollAllDown = function(){
droid.scrollAllDown();
}
var scrollUp = function(a, b, c, d){
if(arguments.length == 0)
return scrollAllUp();
return performAction(function(target){
return droid.scrollUp(target);
}, arguments);
}
var scrollDown = function(a, b, c, d){
if(arguments.length == 0)
return scrollAllDown();
return performAction(function(target){
return droid.scrollDown(target);
}, arguments);
}
var select = function(a, b, c, d){
return performAction(function(target){
return droid.select(target);
}, arguments);
}
var focus = function(a, b, c, d){
return performAction(function(target){
return droid.focus(target);
}, arguments);
}
var paste = function(a, b, c, d){
return performAction(function(target){
return droid.paste(target);
}, arguments);
}
var editable = function(i){
return droid.editable(i);
}
var setText = function(target, str){
return droid.setText(target, str);
}
var input = function(a, b){
if(arguments.length == 1){
return droid.setText(editable(-1), a);
}else{
return droid.setText(editable(a), b);
}
}
var sleep = function(millis){
droid.sleep(millis);
}

View File

@@ -0,0 +1,3 @@
droid.toast("略略略");
droid.click(droid.text("哈哈哈"));

View File

@@ -0,0 +1,21 @@
package com.stardust.scriptdroid;
import android.app.Application;
/**
* Created by Stardust on 2017/1/27.
*/
public class App extends Application {
private static App instance;
public static App getApp() {
return instance;
}
public void onCreate() {
super.onCreate();
instance = this;
}
}

View File

@@ -10,16 +10,17 @@ import android.view.MenuItem;
import android.view.View;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.action.ActionPerformService;
import com.stardust.scriptdroid.data.ScriptFile;
import com.stardust.scriptdroid.data.ScriptFileList;
import com.stardust.scriptdroid.data.SharedPrefScriptFileList;
import com.stardust.scriptdroid.droid.runtime.action.ActionPerformService;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList;
import com.stardust.scriptdroid.file.FileChooser;
import com.stardust.scriptdroid.file.FileUtils;
import com.stardust.scriptdroid.ui.ScriptFileOperation;
import com.stardust.scriptdroid.ui.ScriptListRecyclerView;
import com.stardust.scriptdroid.ui.SlidingUpPanel;
import com.stardust.util.MapEntries;
import com.stardust.view.accessibility.AccessibilityServiceUtils;
import java.util.Map;
import java.util.Optional;
@@ -42,13 +43,27 @@ public class MainActivity extends BaseActivity {
setUpFileChooser();
checkPermissions();
}
public void onStart() {
super.onStart();
}
private void checkPermissions() {
//checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
ActionPerformService.goToPermissionSettingIfDisabled(this);
goToAccessibilityPermissionSettingIfDisabled();
}
private void goToAccessibilityPermissionSettingIfDisabled() {
if (!AccessibilityServiceUtils.isAccessibilityServiceEnabled(this, ActionPerformService.class)) {
new MaterialDialog.Builder(this)
.content(R.string.explain_accessibility_permission)
.positiveText(R.string.text_go_to_setting)
.negativeText(R.string.text_cancel)
.onPositive((dialog, which) -> AccessibilityServiceUtils.goToPermissionSetting(this)).show();
}
}
private void setUpFileChooser() {
mFileChooser = new FileChooser(this);
@@ -80,7 +95,7 @@ public class MainActivity extends BaseActivity {
private void setUpToolbar() {
Toolbar toolbar = $(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setNavigationIcon(R.drawable.script_droid_50);
toolbar.setNavigationIcon(R.drawable.ic_script_droid_40);
toolbar.setTitle(R.string.app_name);
}
@@ -100,7 +115,7 @@ public class MainActivity extends BaseActivity {
new MaterialDialog.Builder(this).title(R.string.text_name)
.inputType(InputType.TYPE_CLASS_TEXT)
.input(getString(R.string.text_please_input_name), "", (dialog, input) -> {
String path = ScriptFile.DEFAULT_FOLDER + input + ".text";
String path = ScriptFile.DEFAULT_FOLDER + input + ".js";
createScriptFile(input.toString(), path);
}).show();
}
@@ -131,7 +146,6 @@ public class MainActivity extends BaseActivity {
private final Map<Integer, Runnable> mOptionActionMap = new MapEntries<Integer, Runnable>()
.entry(R.id.action_exit, this::finish)
.entry(R.id.action_disable_service, this::disableAccessibilityService)
.entry(R.id.action_settings, () -> ActionPerformService.setActions(ActionPerformService.NO_ACTION))
.map();

View File

@@ -4,6 +4,7 @@ import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
import com.stardust.scriptdroid.droid.Droid;
@@ -38,9 +39,14 @@ public class ShortcutActivity extends Activity {
.fall();
}
public void onStart() {
super.onStart();
finish();
}
private void runScriptFile(String path) {
try {
Droid.run(this, new File(path));
Droid.getInstance().runScriptFile(path);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();

View File

@@ -1,466 +0,0 @@
package com.stardust.scriptdroid.action;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityNodeInfo;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT;
/**
* Created by Stardust on 2017/1/21.
*/
public abstract class Action {
private static WeakReference<Context> mContext;
public static void setActionContext(Context context) {
mContext = new WeakReference<>(context);
}
public abstract boolean perform(AccessibilityNodeInfo rootNodeInfo);
interface TargetFilter {
AccessibilityNodeInfo findTarget(AccessibilityNodeInfo nodeInfo);
}
public static class MultiAction extends Action {
private List<Action> mActions;
private boolean mDependent;
public MultiAction(List<Action> actions, boolean dependent) {
if (actions == null)
throw new NullPointerException("actions = null");
mDependent = dependent;
mActions = actions;
}
@Override
public boolean perform(AccessibilityNodeInfo rootNodeInfo) {
boolean succeed = true;
for (Action action : mActions) {
if (!action.perform(rootNodeInfo)) {
succeed = false;
if (!mDependent)
break;
}
}
return succeed;
}
}
public static abstract class TargetAction extends Action {
static final int TYPE_ID = 1;
static final int TYPE_TEXT = 2;
static final int TYPE_BOUNDS = 3;
static final int TYPE_DESCRIPTION = 4;
private int mType;
private String mString;
private Rect mBoundsInScreen;
private TargetAction(String str, int type) {
if (str == null)
throw new NullPointerException("str == null");
if (type != TYPE_TEXT && type != TYPE_ID && type != TYPE_DESCRIPTION)
throw new IllegalArgumentException("type illegal");
mString = str;
mType = type;
}
private TargetAction(Rect boundsInScreen) {
mType = TYPE_BOUNDS;
mBoundsInScreen = boundsInScreen;
}
@Override
public boolean perform(AccessibilityNodeInfo rootNodeInfo) {
List<AccessibilityNodeInfo> target = null;
switch (mType) {
case TYPE_TEXT:
target = rootNodeInfo.findAccessibilityNodeInfosByText(mString);
break;
case TYPE_ID:
target = rootNodeInfo.findAccessibilityNodeInfosByViewId(mString);
break;
case TYPE_BOUNDS:
AccessibilityNodeInfo nodeInfo = findAccessibilityNodeInfosByBounds(rootNodeInfo, mBoundsInScreen);
target = nodeInfo == null ? Collections.EMPTY_LIST : Collections.singletonList(nodeInfo);
break;
case TYPE_DESCRIPTION:
target = findAccessibilityNodeInfosByDescription(rootNodeInfo, mString);
}
return target != null && perform(target);
}
private List<AccessibilityNodeInfo> findAccessibilityNodeInfosByDescription(AccessibilityNodeInfo rootNodeInfo, String description) {
if (description.startsWith("edittext") && !description.equals("edittext")) {
int i = Integer.parseInt(description.substring(8));
return Collections.singletonList(findEditText(rootNodeInfo).get(i));
}
if (description.equals("edittext")) {
return findEditText(rootNodeInfo);
}
return null;
}
private List<AccessibilityNodeInfo> findEditText(AccessibilityNodeInfo rootNodeInfo) {
if (rootNodeInfo == null) {
return Collections.EMPTY_LIST;
}
if (rootNodeInfo.isEditable()) {
return Collections.singletonList(rootNodeInfo);
}
List<AccessibilityNodeInfo> list = new LinkedList<>();
for (int i = 0; i < rootNodeInfo.getChildCount(); i++) {
list.addAll(findEditText(rootNodeInfo.getChild(i)));
}
return list;
}
//(882, 1722 - 1036, 1876)
private AccessibilityNodeInfo findAccessibilityNodeInfosByBounds(AccessibilityNodeInfo root, Rect boundsInScreen) {
if (root == null)
return null;
Rect rect = new Rect();
root.getBoundsInScreen(rect);
if (rect.equals(boundsInScreen)) {
return root;
}
for (int i = 0; i < root.getChildCount(); i++) {
AccessibilityNodeInfo child = root.getChild(i);
if (child == null)
continue;
AccessibilityNodeInfo nodeInfo = findAccessibilityNodeInfosByBounds(child, boundsInScreen);
if (nodeInfo != null)
return nodeInfo;
else
child.recycle();
}
return null;
}
abstract boolean perform(@NonNull List<AccessibilityNodeInfo> nodeInfoList);
}
public static class SimpleAction extends TargetAction {
private int mAction;
SimpleAction(int action, String str, int type) {
super(str, type);
mAction = action;
}
SimpleAction(int action, Rect boundsInScreen) {
super(boundsInScreen);
mAction = action;
}
@Override
boolean perform(@NonNull List<AccessibilityNodeInfo> nodeInfoList) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
performAction(nodeInfo);
}
return true;
}
boolean performAction(AccessibilityNodeInfo nodeInfo) {
return nodeInfo.performAction(mAction);
}
void setAction(int action) {
mAction = action;
}
int getAction() {
return mAction;
}
}
public static class SimpleFilterAction extends SimpleAction {
private TargetFilter mFilter;
SimpleFilterAction(int action, String str, int type, TargetFilter filter) {
super(action, str, type);
mFilter = filter;
}
SimpleFilterAction(int action, Rect boundsInScreen, TargetFilter filter) {
super(action, boundsInScreen);
mFilter = filter;
}
@Override
boolean perform(@NonNull List<AccessibilityNodeInfo> nodeInfoList) {
boolean performed = false;
for (AccessibilityNodeInfo node : nodeInfoList) {
node = mFilter.findTarget(node);
if (node != null) {
performAction(node);
performed = true;
}
}
return performed;
}
}
interface Able {
boolean isAble(AccessibilityNodeInfo node);
}
private static final SparseArray<Able> ACTION_ABLE_MAP = new SparseArray<>();
static {
ACTION_ABLE_MAP.put(ACTION_CLICK, new Able() {
@Override
public boolean isAble(AccessibilityNodeInfo node) {
return node.isClickable();
}
});
ACTION_ABLE_MAP.put(ACTION_LONG_CLICK, new Able() {
@Override
public boolean isAble(AccessibilityNodeInfo node) {
return node.isLongClickable();
}
});
ACTION_ABLE_MAP.put(ACTION_FOCUS, new Able() {
@Override
public boolean isAble(AccessibilityNodeInfo node) {
return node.isFocusable();
}
});
ACTION_ABLE_MAP.put(ACTION_SCROLL_FORWARD, new Able() {
@Override
public boolean isAble(AccessibilityNodeInfo node) {
return node.isScrollable();
}
});
ACTION_ABLE_MAP.put(ACTION_SCROLL_BACKWARD, new Able() {
@Override
public boolean isAble(AccessibilityNodeInfo node) {
return node.isScrollable();
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ACTION_ABLE_MAP.put(ACTION_SET_TEXT, new Able() {
@Override
public boolean isAble(AccessibilityNodeInfo node) {
return node.isEditable();
}
});
}
}
public static class FindDownwardlyDfsFilterAction extends SimpleFilterAction {
public static FindDownwardlyDfsFilterAction createActionByBounds(int action, Rect boundInScreen) {
Able able = ACTION_ABLE_MAP.get(action);
return new FindDownwardlyDfsFilterAction(action, boundInScreen, able);
}
FindDownwardlyDfsFilterAction(int action, Rect boundInScreen, Able able) {
super(action, boundInScreen, new FindDownwardlyDfsTargetFilter(able));
}
FindDownwardlyDfsFilterAction(int action, String str, int type, Able able) {
super(action, str, type, new FindDownwardlyDfsTargetFilter(able));
}
private static class FindDownwardlyDfsTargetFilter implements TargetFilter {
Able mAble;
FindDownwardlyDfsTargetFilter(Able able) {
mAble = able;
}
@Override
public AccessibilityNodeInfo findTarget(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 = findTarget(child);
if (node != null)
return node;
else
child.recycle();
}
return null;
}
}
}
public static class FindUpwardlyFilterAction extends SimpleFilterAction {
public static FindUpwardlyFilterAction createActionById(int action, String id) {
return createActionInner(action, id, TYPE_ID);
}
public static FindUpwardlyFilterAction createActionByText(int action, String text) {
return createActionInner(action, text, TYPE_TEXT);
}
public static Action createActionByDescription(int action, String description) {
return createActionInner(action, description, TYPE_DESCRIPTION);
}
private static FindUpwardlyFilterAction createActionInner(int action, String str, int type) {
Able able = ACTION_ABLE_MAP.get(action);
return new FindUpwardlyFilterAction(action, str, type, able);
}
private static class FindUpwardlyTargetFilter implements TargetFilter {
Able mAble;
FindUpwardlyTargetFilter(Able able) {
mAble = able;
}
@Override
public AccessibilityNodeInfo findTarget(AccessibilityNodeInfo n) {
AccessibilityNodeInfo node = n;
while (node != null && !mAble.isAble(node)) {
AccessibilityNodeInfo parent = node.getParent();
if (node != n) {
node.recycle();
}
node = parent;
}
return node;
}
}
FindUpwardlyFilterAction(int action, String str, int type, Able able) {
super(action, str, type, new FindUpwardlyTargetFilter(able));
}
FindUpwardlyFilterAction(int action, Rect boundsInScreen, Able able) {
super(action, boundsInScreen, new FindUpwardlyTargetFilter(able));
}
}
public static class ScrollAction extends Action {
public static final int SCROLL_FORWARD = AccessibilityNodeInfo.ACTION_SCROLL_FORWARD;
public static final int SCROLL_BACKWARD = AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
private int mScrollAction;
private int mTimes;
public ScrollAction(int scrollAction) {
this(scrollAction, 1);
}
public ScrollAction(int scrollAction, int times) {
if (scrollAction != SCROLL_BACKWARD && scrollAction != SCROLL_FORWARD)
throw new IllegalArgumentException("scrollAction illegal");
mScrollAction = scrollAction;
mTimes = times;
}
@Override
public boolean perform(AccessibilityNodeInfo rootNodeInfo) {
AccessibilityNodeInfo scrollableNodeInfo = findScrollableNodeInfo(rootNodeInfo);
if (scrollableNodeInfo == null) {
return false;
} else {
for (int i = 0; i < mTimes; i++) {
scrollableNodeInfo.performAction(mScrollAction);
}
return true;
}
}
private AccessibilityNodeInfo findScrollableNodeInfo(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null)
return null;
if (nodeInfo.isScrollable()) {
return nodeInfo;
}
for (int i = 0; i < nodeInfo.getChildCount(); i++) {
AccessibilityNodeInfo node = findScrollableNodeInfo(nodeInfo.getChild(i));
if (node != null) {
return node;
}
}
return null;
}
}
public static class IntentAction extends Action {
private final Intent mIntent;
public IntentAction(Intent intent) {
mIntent = intent;
}
@Override
public boolean perform(AccessibilityNodeInfo rootNodeInfo) {
if (mContext == null || mContext.get() == null)
return false;
mContext.get().startActivity(mIntent);
return true;
}
public Intent getIntent() {
return mIntent;
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static class InputAction extends FindUpwardlyFilterAction {
private static final Able EDITABLE = new Able() {
@Override
public boolean isAble(AccessibilityNodeInfo node) {
return node.isEditable();
}
};
private String mText;
public InputAction(String description, String text) {
super(ACTION_SET_TEXT, description, TYPE_DESCRIPTION, EDITABLE);
mText = text;
}
boolean performAction(AccessibilityNodeInfo nodeInfo) {
Bundle arg = new Bundle();
arg.putCharSequence(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, mText);
return nodeInfo.performAction(ACTION_SET_TEXT, arg);
}
}
}

View File

@@ -1,184 +0,0 @@
package com.stardust.scriptdroid.action;
import android.accessibilityservice.AccessibilityService;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.provider.Settings;
import android.text.TextUtils;
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.stardust.scriptdroid.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Created by Stardust on 2017/1/21.
*/
public class ActionPerformService extends AccessibilityService {
private static final String TAG = "SettingRunningServiceSS";
private static ActionPerformService instance;
public static void goToPermissionSettingIfDisabled(final Context context) {
if (!isAccessibilityServiceEnabled(context, ActionPerformService.class)) {
new MaterialDialog.Builder(context)
.content(R.string.explain_accessibility_permission)
.positiveText(R.string.text_go_to_setting)
.negativeText(R.string.text_cancel)
.onPositive((dialog, which) -> context.startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))).show();
}
}
public static boolean isAccessibilityServiceEnabled(Context context, Class<?> accessibilityService) {
ComponentName expectedComponentName = new ComponentName(context, accessibilityService);
String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (enabledServicesSetting == null)
return false;
TextUtils.SimpleStringSplitter colonSplitter = new TextUtils.SimpleStringSplitter(':');
colonSplitter.setString(enabledServicesSetting);
while (colonSplitter.hasNext()) {
String componentNameString = colonSplitter.next();
ComponentName enabledService = ComponentName.unflattenFromString(componentNameString);
if (enabledService != null && enabledService.equals(expectedComponentName))
return true;
}
return false;
}
public static final int STATE_WAIT = 0;
@SuppressWarnings("unchecked")
public static final List<Action> NO_ACTION = Collections.EMPTY_LIST;
public static final List<Action> actions = new ArrayList<>();
private static int state = STATE_WAIT;
public static boolean assistModeEnable = true;
private AccessibilityNodeInfo mLastFocus;
public static void setActions(Collection<Action> collection) {
synchronized (actions) {
actions.clear();
actions.addAll(collection);
state = STATE_WAIT;
}
}
public static ActionPerformService getInstance() {
return instance;
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.v(TAG, "event type=" + event.getEventType());
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
performAssistance(event, event.getSource());
Log.v(TAG, "rootInActiveWindow = " + nodeInfo);
if (nodeInfo == null)
return;
Log.v(TAG, "state = " + state);
Action action = nextAction();
if (action == null) {
reset();
} else if (action.perform(nodeInfo)) {
state++;
}
}
private void performAssistance(AccessibilityEvent event, AccessibilityNodeInfo nodeInfo) {
if (!assistModeEnable || nodeInfo == null)
return;
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED || event.getEventType() == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) {
nodeInfo.refresh();
Log.v(TAG, "click: " + nodeInfo);
Rect rect = new Rect();
nodeInfo.getBoundsInScreen(rect);
String str = rect.toString().replace('-', ',').replace(" ", "");
ClipboardManager manager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
manager.setPrimaryClip(ClipData.newPlainText("", str));
Toast.makeText(this, "id=" + nodeInfo.getViewIdResourceName() + " bounds=" + str, Toast.LENGTH_SHORT).show();
}
}
private int[] findAddress(AccessibilityNodeInfo nodeInfo) {
List<Integer> address = new ArrayList<>();
AccessibilityNodeInfo lastNodeInfo = null;
while (nodeInfo.getParent() != null) {
address.add(findPositionInParent(nodeInfo));
if (lastNodeInfo != null)
lastNodeInfo.recycle();
lastNodeInfo = nodeInfo;
nodeInfo = nodeInfo.getParent();
}
int[] array = new int[address.size()];
for (int i = 0; i < address.size(); i++) {
array[i] = address.get(address.size() - i - 1);
}
return array;
}
private int findPositionInParent(AccessibilityNodeInfo nodeInfo) {
AccessibilityNodeInfo parent = nodeInfo.getParent();
for (int i = 0; i < parent.getChildCount(); i++) {
AccessibilityNodeInfo child = parent.getChild(i);
if (child != null && child.equals(nodeInfo)) {
parent.recycle();
child.recycle();
return i;
}
if (child != null) {
child.recycle();
}
}
parent.recycle();
return -1;
}
private void reset() {
state = STATE_WAIT;
synchronized (actions) {
actions.clear();
}
}
private Action nextAction() {
synchronized (actions) {
if (state >= actions.size()) {
return null;
}
return actions.get(state);
}
}
@Override
public void onInterrupt() {
}
@Override
public void onServiceConnected() {
Log.v(TAG, "onServiceConnected");
Action.setActionContext(this);
instance = this;
}
}

View File

@@ -1,52 +0,0 @@
package com.stardust.scriptdroid.data;
import android.content.Context;
import android.os.Environment;
import com.stardust.scriptdroid.action.Action;
import com.stardust.scriptdroid.action.ActionPerformService;
import com.stardust.scriptdroid.droid.Interpreter;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/1/23.
*/
public class ScriptFile {
public static final String DEFAULT_FOLDER = Environment.getExternalStorageDirectory() + "/脚本/";
public String name;
public String path;
public ScriptFile(String name, String path) {
this.name = name;
this.path = path;
}
public void run(Context context) {
Interpreter interpreter = new Interpreter(context);
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
List<Action> actions = new ArrayList<>();
String line = reader.readLine();
while (line != null) {
actions.add(interpreter.interpreter(line));
line = reader.readLine();
}
ActionPerformService.setActions(actions);
} catch (Exception e) {
e.printStackTrace();
}
}
public File toFile() {
return new File(path);
}
}

View File

@@ -1,12 +1,12 @@
package com.stardust.scriptdroid.droid;
import android.content.Context;
import com.stardust.scriptdroid.action.ActionPerformService;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.scriptdroid.droid.script.GBJDuktapeJavaScriptEngine;
import com.stardust.scriptdroid.droid.script.JavaScriptEngine;
import com.stardust.scriptdroid.file.FileUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
/**
* Created by Stardust on 2017/1/23.
@@ -14,16 +14,48 @@ import java.io.IOException;
public class Droid {
public static void run(Context context, File file) {
Interpreter interpreter = new Interpreter(context);
try (FileInputStream fis = new FileInputStream(file)) {
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
fis.close();
String str = new String(bytes);
ActionPerformService.setActions(interpreter.interpreterAll(str));
} catch (IOException e) {
e.printStackTrace();
private static final DroidRuntime RUNTIME = DroidRuntime.getRuntime();
private static final JavaScriptEngine JAVA_SCRIPT_ENGINE = new GBJDuktapeJavaScriptEngine(RUNTIME);
private static Droid instance = new Droid();
protected Droid() {
}
public static Droid getInstance() {
return instance;
}
public void runScriptFile(File file) {
checkFile(file);
runScript(FileUtils.readString(file));
}
public void runScriptFile(String path) {
runScriptFile(new File(path));
}
public void runScript(String script) {
new Thread(() -> {
try {
JAVA_SCRIPT_ENGINE.execute(script);
} catch (Exception e) {
RUNTIME.toast("错误" + e.getMessage());
}
}).start();
}
private void checkFile(File file) {
if (file == null) {
throw new NullPointerException("file = null");
}
if (!file.exists()) {
throw new RuntimeException(new FileNotFoundException(file.getAbsolutePath()));
}
if (!file.canRead()) {
throw new RuntimeException("file is not readable: path=" + file.getAbsolutePath());
}
}
}

View File

@@ -1,210 +0,0 @@
package com.stardust.scriptdroid.droid;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Build;
import android.util.Pair;
import com.stardust.scriptdroid.action.Action;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Stream;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT;
/**
* Created by Stardust on 2017/1/21.
*/
public class Interpreter {
interface Filter {
boolean filter(String str);
}
private static final Filter FILTER_STRING = new Filter() {
@Override
public boolean filter(String str) {
return str.startsWith("\"") && str.endsWith("\"");
}
};
private static final Filter FILTER_ADDRESS = (str) -> str.startsWith("(") && str.endsWith(")");
private static final Filter FILTER_DESCRIPTION = new Filter() {
@Override
public boolean filter(String str) {
return str.startsWith("[") && str.endsWith("]");
}
};
private static final Filter FILTER_ANY = new Filter() {
@Override
public boolean filter(String str) {
return true;
}
};
private static class StateTree {
private List<Pair<Filter, StateTree>> mChildren = new LinkedList<>();
StateTree child(final String str, StateTree child) {
return child(new Filter() {
@Override
public boolean filter(String s) {
return str.equals(s);
}
}, child);
}
StateTree child(Filter filter, StateTree child) {
mChildren.add(new Pair<>(filter, child));
return this;
}
StateLeaf match(String[] command, int startIndex) {
for (Pair<Filter, StateTree> child : mChildren) {
if (child.first.filter(command[startIndex])) {
return child.second.match(command, startIndex + 1);
}
}
return null;
}
public StateTree child(String command, final int action) {
return child(command, new StateTree()
.child(FILTER_STRING, new StateLeaf() {
@Override
Action handle(String[] command) {
return Action.FindUpwardlyFilterAction.createActionByText(action, StringTool.removeDoubleQuotes(command[1]));
}
})
.child(FILTER_DESCRIPTION, new StateLeaf() {
@Override
Action handle(String[] command) {
return Action.FindUpwardlyFilterAction.createActionByDescription(action, StringTool.removeDoubleQuotes(command[1]));
}
})
.child(FILTER_ADDRESS, new StateLeaf() {
@Override
Action handle(String[] command) {
String[] intStrings = StringTool.removeDoubleQuotes(command[1]).split(",");
int[] bounds = Stream.of(intStrings).mapToInt(Integer::parseInt).toArray();
Rect rect = new Rect(bounds[0], bounds[1], bounds[2], bounds[3]);
return Action.FindDownwardlyDfsFilterAction.createActionByBounds(action, rect);
}
})
.child(FILTER_ANY, new StateLeaf() {
@Override
Action handle(String[] command) {
return Action.FindUpwardlyFilterAction.createActionById(action, StringTool.removeDoubleQuotes(command[1]));
}
}));
}
}
private abstract static class StateLeaf extends StateTree {
abstract Action handle(String[] command);
StateLeaf match(String[] command, int startIndex) {
return this;
}
}
private Context mContext;
private StateTree stateTree = new StateTree()
.child("click", ACTION_CLICK)
.child("longclick", ACTION_LONG_CLICK)
.child("focus", ACTION_FOCUS)
.child("select", ACTION_SELECT)
.child("forward", ACTION_SCROLL_FORWARD)
.child("backward", ACTION_SCROLL_BACKWARD)
.child("scrollup", new StateLeaf() {
@Override
Action handle(String[] command) {
return new Action.ScrollAction(Action.ScrollAction.SCROLL_BACKWARD);
}
}).child("scrolldown", new StateLeaf() {
@Override
Action handle(String[] command) {
return new Action.ScrollAction(Action.ScrollAction.SCROLL_FORWARD);
}
}).child("input", new StateTree().child(FILTER_DESCRIPTION, new StateLeaf() {
@Override
Action handle(String[] command) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return new Action.InputAction(StringTool.removeDoubleQuotes(command[1]), command[2]);
} else {
return null;
}
}
})).child("open", new StateTree()
.child(FILTER_STRING, new StateLeaf() {
@Override
Action handle(String[] command) {
String appName = StringTool.removeDoubleQuotes(command[1]);
List<ApplicationInfo> installedApplications = mContext.getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo applicationInfo : installedApplications) {
if (mContext.getPackageManager().getApplicationLabel(applicationInfo).toString().equals(appName)) {
return new Action.IntentAction(mContext.getPackageManager().getLaunchIntentForPackage(applicationInfo.packageName));
}
}
return null;
}
}).child(FILTER_ANY, new StateLeaf() {
@Override
Action handle(String[] command) {
return new Action.IntentAction(mContext.getPackageManager().getLaunchIntentForPackage(command[1]));
}
}));
public Interpreter(Context context) {
mContext = context;
}
public Action interpreterInner(String line) {
String[] words = line.split(" ");
return stateTree.match(words, 0).handle(words);
}
public Action interpreter(String line) {
boolean independent = true;
String[] actions = line.split("\\|");
if (actions.length == 1) {
actions = line.split("\\&");
independent = false;
}
if (actions.length == 1)
return interpreterInner(line);
List<Action> list = new ArrayList<>(actions.length);
for (String action : actions) {
list.add(interpreterInner(action));
}
return new Action.MultiAction(list, independent);
}
public List<Action> interpreterAll(String str) {
String[] lines = str.split("\n");
List<Action> list = new ArrayList<>(lines.length);
for (String line : lines) {
list.add(interpreter(line));
}
return list;
}
}

View File

@@ -1,10 +0,0 @@
package com.stardust.scriptdroid.droid;
/**
* Created by Stardust on 2017/1/22.
*/
public class StringTool {
public static String removeDoubleQuotes(String str) {
return str.substring(1, str.length() - 1);
}
}

View File

@@ -1,30 +0,0 @@
package com.stardust.scriptdroid.droid;
/**
* Created by Stardust on 2017/1/21.
*/
public class SyntaxDefinition {
/**
* click 文本
* 点击文本所在区域。e.g. click 朋友圈
* click 编号
* 点击编号对应区域
* longclick 文本
*
* longclick 编号
*
* scrollup
*
* scrolldown
*
* scrollup 编号 [幅度]
*
* scrolldown 编号 [幅度]
*
*
*
*/
}

View File

@@ -0,0 +1,171 @@
package com.stardust.scriptdroid.droid.runtime;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Handler;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.runtime.action.Action;
import com.stardust.scriptdroid.droid.runtime.action.ActionFactory;
import com.stardust.scriptdroid.droid.runtime.action.ActionPerformService;
import com.stardust.scriptdroid.droid.runtime.action.ActionTarget;
import com.stardust.scriptdroid.droid.runtime.api.IDroidRuntime;
import java.util.List;
/**
* Created by Stardust on 2017/1/27.
*/
public class DroidRuntime implements IDroidRuntime {
private static final String TAG = "DroidRuntime";
private final Object mLock = new Object();
private static DroidRuntime runtime = new DroidRuntime();
private boolean mActionPerformResult;
private Handler mUIHandler;
public static DroidRuntime getRuntime() {
return runtime;
}
protected DroidRuntime() {
mUIHandler = new Handler(App.getApp().getMainLooper());
}
@Override
public void launchPackage(String packageName) {
PackageManager packageManager = App.getApp().getPackageManager();
App.getApp().startActivity(packageManager.getLaunchIntentForPackage(packageName));
}
@Override
public void launch(String packageName, String className) {
App.getApp().startActivity(new Intent().setClassName(packageName, className));
}
@Override
public void launchApp(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)) {
launchPackage(applicationInfo.packageName);
break;
}
}
}
@Override
public String[] getPackageName(String appName) {
return new String[0];
}
@Override
public ActionTarget text(String text) {
return new ActionTarget.TextActionTarget(text);
}
@Override
public ActionTarget bounds(int left, int top, int right, int bottom) {
return new ActionTarget.BoundsActionTarget(new Rect(left, top, right, bottom));
}
@Override
public ActionTarget editable(int i) {
return new ActionTarget.EditableActionTarget(i);
}
@Override
public boolean click(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_CLICK));
}
@Override
public boolean longClick(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_LONG_CLICK));
}
@Override
public boolean scrollUp(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD));
}
@Override
public boolean scrollDown(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD));
}
public boolean scrollAllUp(){
return performAction(ActionFactory.createScrollAllAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD));
}
public boolean scrollAllDown(){
return performAction(ActionFactory.createScrollAllAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD));
}
@Override
public boolean focus(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_FOCUS));
}
@Override
public boolean select(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SELECT));
}
@Override
public boolean setText(ActionTarget target, String text) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SET_TEXT, text));
}
@Override
public boolean paste(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE));
}
private boolean performAction(Action action) {
if (ActionPerformService.getInstance() == null) {
toast("没有权限");
return false;
}
ActionPerformService.setAction(action);
synchronized (mLock) {
try {
mLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return mActionPerformResult;
}
@Override
public void toast(String text) {
mUIHandler.post(() -> Toast.makeText(App.getApp(), text, Toast.LENGTH_SHORT).show());
}
@Override
public void sleep(long millis) {
try{
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void notifyActionPerformed(boolean succeed) {
mActionPerformResult = succeed;
synchronized (mLock) {
mLock.notify();
}
}
}

View File

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

View File

@@ -0,0 +1,34 @@
package com.stardust.scriptdroid.droid.runtime.action;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/1/27.
*/
public abstract class Action {
private boolean mPerformUtilSucceed = 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;
}
}

View File

@@ -0,0 +1,56 @@
package com.stardust.scriptdroid.droid.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) {
if (searchUpAction.containsKey(action))
return new SearchUpTargetAction(action, new FilterAction.TextFilter(text));
else
return new DepthFirstSearchTargetAction(action, new FilterAction.TextFilter(text));
}
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 createScrollAllAction(int action) {
return new ScrollAllAction(action);
}
}

View File

@@ -0,0 +1,144 @@
package com.stardust.scriptdroid.droid.runtime.action;
import android.accessibilityservice.AccessibilityService;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.graphics.Rect;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Created by Stardust on 2017/1/21.
*/
public class ActionPerformService extends AccessibilityService {
private static final String TAG = "ActionPerformService";
private static final String KEY_ASSIST_MODE_ENABLE = "ASSIST_MODE_ENABLE";
private static ActionPerformService instance;
@SuppressWarnings("unchecked")
public static final List<Action> NO_ACTION = Collections.EMPTY_LIST;
public static final int STATE_INACTIVE = -1;
public static final int STATE_PERFORM = 0;
public static final List<Action> actions = new ArrayList<>();
private static int state = STATE_INACTIVE;
private static boolean assistModeEnable = false;
public static void setActions(Collection<Action> collection) {
synchronized (actions) {
actions.clear();
actions.addAll(collection);
state = actions.size() > 0 ? STATE_PERFORM : STATE_INACTIVE;
}
}
public static void setAction(Action action) {
setActions(Collections.singletonList(action));
}
public static ActionPerformService getInstance() {
return instance;
}
public static boolean isAssistModeEnable() {
return assistModeEnable;
}
public static void setAssistModeEnable(boolean assistModeEnable) {
ActionPerformService.assistModeEnable = assistModeEnable;
PreferenceManager.getDefaultSharedPreferences(App.getApp()).edit().putBoolean(KEY_ASSIST_MODE_ENABLE, assistModeEnable).apply();
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.v(TAG, "onAccessibilityEvent: state=" + state + " event=" + event);
performAssistance(event);
if (state == STATE_INACTIVE)
return;
AccessibilityNodeInfo root = getRootInActiveWindow();
if (root == null) {
Log.v(TAG, "root = null");
}
Action action = nextAction();
if (action == null) {
onActionPerformed(true);
} else {
Log.i(TAG, "perform action:" + action);
if (action.perform(root)) {
state++;
} else if (!action.performUtilSucceed()) {
onActionPerformed(false);
}
}
}
private void onActionPerformed(boolean succeed) {
state = STATE_INACTIVE;
synchronized (actions) {
actions.clear();
}
DroidRuntime.getRuntime().notifyActionPerformed(succeed);
}
private Action nextAction() {
synchronized (actions) {
if (state >= actions.size()) {
return null;
}
return actions.get(state);
}
}
private void performAssistance(AccessibilityEvent event) {
if (!assistModeEnable) {
return;
}
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED || event.getEventType() == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) {
AccessibilityNodeInfo nodeInfo = event.getSource();
if (nodeInfo == null) {
return;
}nodeInfo.refresh();
Log.v(TAG, "click: " + nodeInfo);
Rect rect = new Rect();
nodeInfo.getBoundsInScreen(rect);
String str = rect.toString().replace('-', ',').replace(" ", "").substring(4);
ClipboardManager manager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
manager.setPrimaryClip(ClipData.newPlainText("", str));
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
nodeInfo.recycle();
}
}
@Override
public void onInterrupt() {
}
@Override
public void onServiceConnected() {
Log.v(TAG, "onServiceConnected");
instance = this;
}
static {
assistModeEnable = PreferenceManager.getDefaultSharedPreferences(App.getApp()).getBoolean(KEY_ASSIST_MODE_ENABLE, false);
}
}

View File

@@ -0,0 +1,53 @@
package com.stardust.scriptdroid.droid.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;
public TextActionTarget(String text) {
mText = text;
}
@Override
public Action createAction(int action, Object... params) {
return ActionFactory.createActionWithTextFilter(action, mText);
}
}
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());
}
}
}

View File

@@ -0,0 +1,40 @@
package com.stardust.scriptdroid.droid.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,117 @@
package com.stardust.scriptdroid.droid.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;
public TextFilter(String text) {
mText = text;
}
@Override
public List<AccessibilityNodeInfo> filter(AccessibilityNodeInfo root) {
return root.findAccessibilityNodeInfosByText(mText);
}
}
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;
}
}
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 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;
return Collections.singletonList(editableList.get(mIndex));
}
@SuppressWarnings("unchecked")
private 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;
}
}
}

View File

@@ -0,0 +1,38 @@
package com.stardust.scriptdroid.droid.runtime.action;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/1/27.
*/
public class ScrollAllAction extends Action {
private int mScrollAction;
public ScrollAllAction(int scrollAction) {
mScrollAction = scrollAction;
}
@Override
public boolean perform(AccessibilityNodeInfo rootNodeInfo) {
AccessibilityNodeInfo scrollableNodeInfo = findScrollableNodeInfo(rootNodeInfo);
return scrollableNodeInfo != null && scrollableNodeInfo.performAction(mScrollAction);
}
private AccessibilityNodeInfo findScrollableNodeInfo(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null)
return null;
if (nodeInfo.isScrollable()) {
return nodeInfo;
}
for (int i = 0; i < nodeInfo.getChildCount(); i++) {
AccessibilityNodeInfo node = findScrollableNodeInfo(nodeInfo.getChild(i));
if (node != null) {
return node;
}
}
return null;
}
}

View File

@@ -0,0 +1,47 @@
package com.stardust.scriptdroid.droid.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 com.stardust.scriptdroid.droid.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,44 @@
package com.stardust.scriptdroid.droid.runtime.api;
import com.stardust.scriptdroid.droid.runtime.action.ActionTarget;
/**
* Created by Stardust on 2017/1/27.
*/
public interface IDroidRuntime {
void launchPackage(String packageName);
void launch(String packageName, String className);
void launchApp(String appName);
String[] getPackageName(String appName);
ActionTarget text(String text);
ActionTarget bounds(int left, int top, int right, int bottom);
ActionTarget editable(int i);
boolean click(ActionTarget target);
boolean longClick(ActionTarget target);
boolean scrollUp(ActionTarget target);
boolean scrollDown(ActionTarget target);
boolean focus(ActionTarget target);
boolean select(ActionTarget target);
boolean setText(ActionTarget target, String text);
boolean paste(ActionTarget target);
void toast(String text);
void sleep(long millis);
}

View File

@@ -0,0 +1,61 @@
package com.stardust.scriptdroid.droid.script;
import android.util.Log;
import com.squareup.duktape.Duktape;
import com.stardust.scriptdroid.droid.runtime.api.IDroidRuntime;
import static com.stardust.scriptdroid.droid.script.JavaScriptEngine.Init.readInitScript;
/**
* Created by Stardust on 2017/1/27.
*/
public class DuktapeJavaScriptEngine implements JavaScriptEngine {
private static final String TAG = "DuktapeJavaScriptEngine";
private Duktape mDuktape = Duktape.create();
private boolean mRecycled = false;
public DuktapeJavaScriptEngine(IDroidRuntime runtime) {
setRuntime(runtime);
execute(readInitScript());
}
public void setRuntime(IDroidRuntime runtime) {
set("droid", IDroidRuntime.class, runtime);
}
@Override
public void execute(String script) {
mDuktape.evaluate(script);
}
public <T> void set(String varName, Class<T> c, T value) {
mDuktape.set(varName, c, value);
}
public void recycle() {
if (!mRecycled) {
mDuktape.close();
mRecycled = true;
}
}
@Override
public void finalize() throws Throwable {
try {
if (!mRecycled) {
Log.w(TAG, "Not recycled before finalize");
recycle();
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
super.finalize();
}
}

View File

@@ -0,0 +1,46 @@
package com.stardust.scriptdroid.droid.script;
import com.efurture.script.JSTransformer;
import com.furture.react.DuktapeEngine;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.runtime.api.IDroidRuntime;
import java.io.IOException;
import java.io.StringReader;
/**
* Created by Stardust on 2017/1/27.
*/
public class GBJDuktapeJavaScriptEngine implements JavaScriptEngine {
private DuktapeEngine mDuktapeEngine = new DuktapeEngine();
private boolean mRecycled = false;
private String init = Init.readInitScript();
public GBJDuktapeJavaScriptEngine(IDroidRuntime runtime) {
setRuntime(runtime);
mDuktapeEngine.put("context", App.getApp());
execute(init);
}
private void setRuntime(IDroidRuntime runtime) {
set("droid", IDroidRuntime.class, runtime);
}
@Override
public void execute(String script) {
try {
mDuktapeEngine.execute(JSTransformer.parse(new StringReader(script)));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public <T> void set(String varName, Class<T> c, T value) {
mDuktapeEngine.put(varName, value);
}
}

View File

@@ -0,0 +1,21 @@
package com.stardust.scriptdroid.droid.script;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.script.file.AssetScript;
/**
* Created by Stardust on 2017/1/27.
*/
public interface JavaScriptEngine {
void execute(String script);
<T> void set(String varName, Class<T> c, T value);
class Init {
static String readInitScript() {
return AssetScript.read(App.getApp(), "javasccript_engine_init.js");
}
}
}

View File

@@ -0,0 +1,74 @@
package com.stardust.scriptdroid.droid.script.file;
import android.content.Context;
import android.util.Log;
import com.efurture.script.JSTransformer;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
public class AssetScript {
public static String read(Context context, String fileName){
try {
InputStream is = context.getAssets().open(fileName);
ByteArrayOutputStream output = new ByteArrayOutputStream();
BufferedInputStream bufferIn = new BufferedInputStream(is);
byte[] buffer = new byte[2048];
int length = 0;
while ((length = bufferIn.read(buffer)) >= 0){
output.write(buffer, 0, length);
}
String script = output.toString();
Log.e("compiled ScriptEngine", script);
return script;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String toScript(Context context, String fileName){
try {
InputStream is = context.getAssets().open(fileName);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String script = JSTransformer.parse(br);
Log.e("ScriptEngine", script);
return script;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String toScript(String fileName){
try {
InputStream is = new FileInputStream(fileName);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String script = JSTransformer.parse(br);
Log.e("ScriptEngine", script);
return script;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String toScript(InputStream is){
try {
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String script = JSTransformer.parse(br);
Log.e("ScriptEngine", script);
return script;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,34 @@
package com.stardust.scriptdroid.droid.script.file;
import android.content.Context;
import android.os.Environment;
import com.stardust.scriptdroid.droid.Droid;
import java.io.File;
/**
* Created by Stardust on 2017/1/23.
*/
public class ScriptFile {
public static final String DEFAULT_FOLDER = Environment.getExternalStorageDirectory() + "/脚本/";
public String name;
public String path;
public ScriptFile(String name, String path) {
this.name = name;
this.path = path;
}
public void run(Context context) {
Droid.getInstance().runScriptFile(toFile());
}
public File toFile() {
return new File(path);
}
}

View File

@@ -1,4 +1,4 @@
package com.stardust.scriptdroid.data;
package com.stardust.scriptdroid.droid.script.file;
import java.io.File;

View File

@@ -1,4 +1,4 @@
package com.stardust.scriptdroid.data;
package com.stardust.scriptdroid.droid.script.file;
import android.content.Context;
import android.content.SharedPreferences;

View File

@@ -76,4 +76,20 @@ public class FileUtils {
return false;
}
}
public static String readString(File file, String encoding) {
try {
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
return new String(bytes, encoding);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static String readString(File file) {
return readString(file, "utf-8");
}
}

View File

@@ -1,78 +0,0 @@
package com.stardust.scriptdroid.runningservices;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.provider.Settings;
import java.util.Collections;
import java.util.List;
/**
* Created by Stardust on 2017/1/20.
*/
public class RunningPackageTool {
public static String getRunningPackage(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return getRunningPackageLollipop(context);
} else {
return getRunningPackageBeforeLollipop(context);
}
}
private static String getRunningPackageBeforeLollipop(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
@SuppressWarnings("deprecation")
ComponentName runningActivity = am.getRunningTasks(1).get(0).topActivity;
return runningActivity.getPackageName();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static String getRunningPackageLollipop(Context context) {
List<UsageStats> usageStats = getPastTwoSecondsUsageStats(context);
UsageStats latestStats = findLatestUsageStats(usageStats);
if (latestStats == null)
return null;
return latestStats.getPackageName();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static void goToUsageAccessSettings(Context context) {
Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
context.startActivity(intent);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static UsageStats findLatestUsageStats(List<UsageStats> usageStats) {
UsageStats latestStats = null;
for (UsageStats us : usageStats) {
if (latestStats == null || latestStats.getLastTimeUsed() < us.getLastTimeUsed()) {
latestStats = us;
}
}
return latestStats;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
@SuppressWarnings("unchecked")
private static List<UsageStats> getPastTwoSecondsUsageStats(Context context) {
long ts = System.currentTimeMillis();
UsageStatsManager usageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
List<UsageStats> usageStats = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, ts - 2000, ts);
if (usageStats == null) {
usageStats = Collections.EMPTY_LIST;
}
return usageStats;
}
}

View File

@@ -0,0 +1,29 @@
package com.stardust.scriptdroid.tile;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import com.stardust.scriptdroid.droid.runtime.action.ActionPerformService;
/**
* Created by Stardust on 2017/1/26.
*/
public class BoundsAssistEnableTileService extends TileService {
public void onClick() {
ActionPerformService.setAssistModeEnable(!ActionPerformService.isAssistModeEnable());
updateTile();
}
private void updateTile() {
getQsTile().setState(ActionPerformService.isAssistModeEnable() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
getQsTile().updateTile();
}
@Override
public void onStartListening() {
updateTile();
}
}

View File

@@ -1,14 +0,0 @@
package com.stardust.scriptdroid.tile;
import com.stardust.scriptdroid.action.ActionPerformService;
/**
* Created by Stardust on 2017/1/26.
*/
public class TileService extends android.service.quicksettings.TileService {
public void onClick(){
ActionPerformService.assistModeEnable = !ActionPerformService.assistModeEnable;
}
}

View File

@@ -8,8 +8,8 @@ import android.support.design.widget.Snackbar;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.ShortcutActivity;
import com.stardust.scriptdroid.data.ScriptFile;
import com.stardust.scriptdroid.data.ScriptFileList;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.shortcut.Shortcut;
import java.util.ArrayList;

View File

@@ -14,8 +14,8 @@ import android.widget.TextView;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.data.ScriptFile;
import com.stardust.scriptdroid.data.ScriptFileList;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.tool.ClassTool;
import static com.stardust.scriptdroid.tool.ViewTool.$;

View File

@@ -1,40 +0,0 @@
package com.stardust.view.accessibility;
import android.view.accessibility.AccessibilityNodeInfo;
import java.lang.reflect.Field;
/**
* Created by Stardust on 2017/1/26.
*/
public class AccessibilityNodeInfoReflect {
private final AccessibilityNodeInfo mAccessibilityNodeInfo;
public AccessibilityNodeInfoReflect(AccessibilityNodeInfo accessibilityNodeInfo) {
mAccessibilityNodeInfo = accessibilityNodeInfo;
}
public void getChildNodeIds() {
try {
Field mChildNodeIdsField = AccessibilityNodeInfo.class.getDeclaredField("mChildNodeIds");
mChildNodeIdsField.setAccessible(true);
Object o = mChildNodeIdsField.get(mAccessibilityNodeInfo);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public long getSourceNodeId() {
try {
Field mSourceNodeIdField = AccessibilityNodeInfo.class.getDeclaredField("mSourceNodeId");
mSourceNodeIdField.setAccessible(true);
return (long) mSourceNodeIdField.get(mSourceNodeIdField);
} catch (IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
return -1;
}
}

View File

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

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -78,7 +78,7 @@
android:layout_width="60dp"
android:layout_height="60dp"
android:padding="10dp"
android:src="@drawable/sdcard_icon_black"/>
android:src="@drawable/ic_sdcard_black"/>
<TextView
android:layout_width="match_parent"

View File

@@ -17,7 +17,7 @@
android:layout_centerVertical="true"
android:contentDescription="@string/app_name"
android:scaleType="fitCenter"
android:src="@drawable/robot_icon_green"/>
android:src="@drawable/ic_robot_green"/>
<LinearLayout
android:layout_width="match_parent"
@@ -81,7 +81,7 @@
android:layout_height="20dp"
android:layout_gravity="center"
android:contentDescription="@string/app_name"
android:src="@drawable/navigation_show_more_vertical_512"/>
android:src="@drawable/ic_navigation_show_more_vertical_512"/>
</FrameLayout>

View File

@@ -1,5 +1,5 @@
<resources>
<string name="app_name">免Root脚本精灵</string>
<string name="app_name">免Root脚本机器人</string>
<string name="action_settings">设置</string>
<string name="action_disable_service">关闭服务</string>
<string name="text_accesibility_service_description">略略略</string>

View File

@@ -2,9 +2,6 @@ package com.stardust.scriptdroid;
import org.junit.Test;
import java.util.Iterator;
import java.util.Stack;
/**
* Example local unit test, which will execute on the development machine (host).
*
@@ -14,6 +11,5 @@ public class ExampleUnitTest {
@Test
public void testStack() throws Exception {
}
}